aboutsummaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
Diffstat (limited to 'src/or')
-rw-r--r--src/or/addressmap.c30
-rw-r--r--src/or/buffers.c468
-rw-r--r--src/or/buffers.h43
-rw-r--r--src/or/channel.c46
-rw-r--r--src/or/channel.h10
-rw-r--r--src/or/channeltls.c43
-rw-r--r--src/or/channeltls.h8
-rw-r--r--src/or/circpathbias.c5
-rw-r--r--src/or/circuitbuild.c355
-rw-r--r--src/or/circuitbuild.h5
-rw-r--r--src/or/circuitlist.c14
-rw-r--r--src/or/circuitmux.c91
-rw-r--r--src/or/circuitmux_ewma.c29
-rw-r--r--src/or/circuitmux_ewma.h5
-rw-r--r--src/or/circuitstats.c53
-rw-r--r--src/or/circuituse.c42
-rw-r--r--src/or/command.c20
-rw-r--r--src/or/config.c794
-rw-r--r--src/or/config.h30
-rw-r--r--src/or/confparse.c59
-rw-r--r--src/or/confparse.h22
-rw-r--r--src/or/connection.c805
-rw-r--r--src/or/connection.h57
-rw-r--r--src/or/connection_edge.c252
-rw-r--r--src/or/connection_or.c217
-rw-r--r--src/or/connection_or.h2
-rw-r--r--src/or/control.c767
-rw-r--r--src/or/control.h27
-rw-r--r--src/or/cpuworker.c11
-rw-r--r--src/or/dircollate.c40
-rw-r--r--src/or/directory.c712
-rw-r--r--src/or/directory.h25
-rw-r--r--src/or/dirserv.c279
-rw-r--r--src/or/dirserv.h6
-rw-r--r--src/or/dirvote.c499
-rw-r--r--src/or/dirvote.h59
-rw-r--r--src/or/dns.c182
-rw-r--r--src/or/dns.h14
-rw-r--r--src/or/dns_structs.h12
-rw-r--r--src/or/dnsserv.c18
-rw-r--r--src/or/entrynodes.c65
-rw-r--r--src/or/entrynodes.h4
-rw-r--r--src/or/eventdns_tor.h22
-rw-r--r--src/or/ext_orport.c25
-rw-r--r--src/or/fp_pair.c8
-rw-r--r--src/or/geoip.c47
-rw-r--r--src/or/hibernate.c81
-rw-r--r--src/or/include.am19
-rw-r--r--src/or/keypin.c22
-rw-r--r--src/or/main.c303
-rw-r--r--src/or/main.h12
-rw-r--r--src/or/microdesc.c5
-rw-r--r--src/or/networkstatus.c383
-rw-r--r--src/or/networkstatus.h20
-rw-r--r--src/or/nodelist.c44
-rw-r--r--src/or/ntmain.c15
-rw-r--r--src/or/onion.c85
-rw-r--r--src/or/onion_fast.c26
-rw-r--r--src/or/onion_ntor.c18
-rw-r--r--src/or/onion_tap.c24
-rw-r--r--src/or/or.h283
-rw-r--r--src/or/periodic.c8
-rw-r--r--src/or/policies.c292
-rw-r--r--src/or/policies.h15
-rw-r--r--src/or/protover.c742
-rw-r--r--src/or/protover.h74
-rw-r--r--src/or/reasons.c6
-rw-r--r--src/or/relay.c47
-rw-r--r--src/or/rendcache.c12
-rw-r--r--src/or/rendcache.h7
-rw-r--r--src/or/rendclient.c176
-rw-r--r--src/or/rendclient.h3
-rw-r--r--src/or/rendcommon.c177
-rw-r--r--src/or/rendcommon.h15
-rw-r--r--src/or/rendmid.c2
-rw-r--r--src/or/rendservice.c938
-rw-r--r--src/or/rendservice.h77
-rw-r--r--src/or/rephist.c148
-rw-r--r--src/or/rephist.h7
-rw-r--r--src/or/replaycache.c14
-rw-r--r--src/or/router.c196
-rw-r--r--src/or/router.h3
-rw-r--r--src/or/routerkeys.c26
-rw-r--r--src/or/routerlist.c559
-rw-r--r--src/or/routerlist.h34
-rw-r--r--src/or/routerparse.c1098
-rw-r--r--src/or/routerparse.h33
-rw-r--r--src/or/routerset.c14
-rw-r--r--src/or/scheduler.c12
-rw-r--r--src/or/scheduler.h7
-rw-r--r--src/or/shared_random.c1363
-rw-r--r--src/or/shared_random.h168
-rw-r--r--src/or/shared_random_state.c1359
-rw-r--r--src/or/shared_random_state.h147
-rw-r--r--src/or/statefile.c20
-rw-r--r--src/or/status.c8
-rw-r--r--src/or/tor_main.c8
-rw-r--r--src/or/transports.c18
98 files changed, 11783 insertions, 3717 deletions
diff --git a/src/or/addressmap.c b/src/or/addressmap.c
index 047a863ef5..33fd7e0f4a 100644
--- a/src/or/addressmap.c
+++ b/src/or/addressmap.c
@@ -264,18 +264,18 @@ addressmap_clear_invalid_automaps(const or_options_t *options)
clear_all = 1; /* This should be impossible, but let's be sure. */
STRMAP_FOREACH_MODIFY(addressmap, src_address, addressmap_entry_t *, ent) {
- int remove = clear_all;
+ int remove_this = clear_all;
if (ent->source != ADDRMAPSRC_AUTOMAP)
continue; /* not an automap mapping. */
- if (!remove) {
- remove = ! addressmap_address_should_automap(src_address, options);
+ if (!remove_this) {
+ remove_this = ! addressmap_address_should_automap(src_address, options);
}
- if (!remove && ! address_is_in_virtual_range(ent->new_address))
- remove = 1;
+ if (!remove_this && ! address_is_in_virtual_range(ent->new_address))
+ remove_this = 1;
- if (remove) {
+ if (remove_this) {
addressmap_ent_remove(src_address, ent);
MAP_DEL_CURRENT(src_address);
}
@@ -774,7 +774,7 @@ parse_virtual_addr_network(const char *val, sa_family_t family,
const int ipv6 = (family == AF_INET6);
tor_addr_t addr;
maskbits_t bits;
- const int max_bits = ipv6 ? 40 : 16;
+ const int max_prefix_bits = ipv6 ? 104 : 16;
virtual_addr_conf_t *conf = ipv6 ? &virtaddr_conf_ipv6 : &virtaddr_conf_ipv4;
if (!val || val[0] == '\0') {
@@ -804,10 +804,10 @@ parse_virtual_addr_network(const char *val, sa_family_t family,
}
#endif
- if (bits > max_bits) {
+ if (bits > max_prefix_bits) {
if (msg)
tor_asprintf(msg, "VirtualAddressNetwork%s expects a /%d "
- "network or larger",ipv6?"IPv6":"", max_bits);
+ "network or larger",ipv6?"IPv6":"", max_prefix_bits);
return -1;
}
@@ -896,10 +896,10 @@ addressmap_get_virtual_address(int type)
tor_assert(addressmap);
if (type == RESOLVED_TYPE_HOSTNAME) {
- char rand[10];
+ char rand_bytes[10];
do {
- crypto_rand(rand, sizeof(rand));
- base32_encode(buf,sizeof(buf),rand,sizeof(rand));
+ crypto_rand(rand_bytes, sizeof(rand_bytes));
+ base32_encode(buf,sizeof(buf),rand_bytes,sizeof(rand_bytes));
strlcat(buf, ".virtual", sizeof(buf));
} while (strmap_get(addressmap, buf));
return tor_strdup(buf);
@@ -1107,11 +1107,11 @@ addressmap_get_mappings(smartlist_t *sl, time_t min_expires,
smartlist_add_asprintf(sl, "%s%s %s%s NEVER",
src_wc, key, dst_wc, val->new_address);
else {
- char time[ISO_TIME_LEN+1];
- format_iso_time(time, val->expires);
+ char isotime[ISO_TIME_LEN+1];
+ format_iso_time(isotime, val->expires);
smartlist_add_asprintf(sl, "%s%s %s%s \"%s\"",
src_wc, key, dst_wc, val->new_address,
- time);
+ isotime);
}
} else {
smartlist_add_asprintf(sl, "%s%s %s%s",
diff --git a/src/or/buffers.c b/src/or/buffers.c
index a41af5f429..89382d1d8e 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -6,10 +6,22 @@
/**
* \file buffers.c
- * \brief Implements a generic interface buffer. Buffers are
- * fairly opaque string holders that can read to or flush from:
- * memory, file descriptors, or TLS connections. Buffers are implemented
- * as linked lists of memory chunks.
+ * \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"
@@ -128,7 +140,7 @@ chunk_repack(chunk_t *chunk)
/** Keep track of total size of allocated chunks for consistency asserts */
static size_t total_bytes_allocated_in_chunks = 0;
static void
-chunk_free_unchecked(chunk_t *chunk)
+buf_chunk_free_unchecked(chunk_t *chunk)
{
if (!chunk)
return;
@@ -190,9 +202,12 @@ chunk_grow(chunk_t *chunk, size_t sz)
/** Return the allocation size we'd like to use to hold <b>target</b>
* bytes. */
-static inline size_t
+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;
@@ -252,7 +267,7 @@ buf_pullup(buf_t *buf, size_t bytes)
dest->next = src->next;
if (buf->tail == src)
buf->tail = dest;
- chunk_free_unchecked(src);
+ buf_chunk_free_unchecked(src);
} else {
memcpy(CHUNK_WRITE_PTR(dest), src->data, n);
dest->datalen += n;
@@ -298,7 +313,7 @@ buf_remove_from_front(buf_t *buf, size_t n)
buf->head = victim->next;
if (buf->tail == victim)
buf->tail = NULL;
- chunk_free_unchecked(victim);
+ buf_chunk_free_unchecked(victim);
}
}
check();
@@ -338,7 +353,7 @@ buf_clear(buf_t *buf)
buf->datalen = 0;
for (chunk = buf->head; chunk; chunk = next) {
next = chunk->next;
- chunk_free_unchecked(chunk);
+ buf_chunk_free_unchecked(chunk);
}
buf->head = buf->tail = NULL;
}
@@ -429,7 +444,7 @@ static chunk_t *
buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped)
{
chunk_t *chunk;
- struct timeval now;
+
if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) {
chunk = chunk_new_with_alloc_size(buf->default_chunk_size);
} else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) {
@@ -438,8 +453,7 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped)
chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity));
}
- tor_gettimeofday_cached_monotonic(&now);
- chunk->inserted_time = (uint32_t)tv_to_msec(&now);
+ chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec();
if (buf->tail) {
tor_assert(buf->head);
@@ -454,8 +468,8 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped)
}
/** Return the age of the oldest chunk in the buffer <b>buf</b>, in
- * milliseconds. Requires the current time, in truncated milliseconds since
- * the epoch, as its input <b>now</b>.
+ * 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)
@@ -533,12 +547,12 @@ read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls,
* (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on
* error; else return the number of bytes read.
*/
-/* XXXX024 indicate "read blocked" somehow? */
+/* 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)
{
- /* XXXX024 It's stupid to overload the return values for these functions:
+ /* 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;
@@ -711,7 +725,7 @@ flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk,
int
flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen)
{
- /* XXXX024 It's stupid to overload the return values for these functions:
+ /* 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;
@@ -938,97 +952,6 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto)
return 1;
}
-#ifdef USE_BUFFEREVENTS
-/** Try to read <b>n</b> bytes from <b>buf</b> at <b>pos</b> (which may be
- * NULL for the start of the buffer), copying the data only if necessary. Set
- * *<b>data_out</b> to a pointer to the desired bytes. Set <b>free_out</b>
- * to 1 if we needed to malloc *<b>data</b> because the original bytes were
- * noncontiguous; 0 otherwise. Return the number of bytes actually available
- * at *<b>data_out</b>.
- */
-static ssize_t
-inspect_evbuffer(struct evbuffer *buf, char **data_out, size_t n,
- int *free_out, struct evbuffer_ptr *pos)
-{
- int n_vecs, i;
-
- if (evbuffer_get_length(buf) < n)
- n = evbuffer_get_length(buf);
- if (n == 0)
- return 0;
- n_vecs = evbuffer_peek(buf, n, pos, NULL, 0);
- tor_assert(n_vecs > 0);
- if (n_vecs == 1) {
- struct evbuffer_iovec v;
- i = evbuffer_peek(buf, n, pos, &v, 1);
- tor_assert(i == 1);
- *data_out = v.iov_base;
- *free_out = 0;
- return v.iov_len;
- } else {
- ev_ssize_t copied;
- *data_out = tor_malloc(n);
- *free_out = 1;
- copied = evbuffer_copyout(buf, *data_out, n);
- tor_assert(copied >= 0 && (size_t)copied == n);
- return copied;
- }
-}
-
-/** As fetch_var_cell_from_buf, buf works on an evbuffer. */
-int
-fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out,
- int linkproto)
-{
- char *hdr = NULL;
- int free_hdr = 0;
- size_t n;
- size_t buf_len;
- uint8_t command;
- uint16_t cell_length;
- var_cell_t *cell;
- int result = 0;
- 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;
- buf_len = evbuffer_get_length(buf);
- if (buf_len < header_len)
- return 0;
-
- n = inspect_evbuffer(buf, &hdr, header_len, &free_hdr, NULL);
- tor_assert(n >= header_len);
-
- command = get_uint8(hdr + circ_id_len);
- if (!(cell_command_is_var_length(command, linkproto))) {
- goto done;
- }
-
- cell_length = ntohs(get_uint16(hdr + circ_id_len + 1));
- if (buf_len < (size_t)(header_len+cell_length)) {
- result = 1; /* Not all here yet. */
- goto done;
- }
-
- cell = var_cell_new(cell_length);
- cell->command = command;
- if (wide_circ_ids)
- cell->circ_id = ntohl(get_uint32(hdr));
- else
- cell->circ_id = ntohs(get_uint16(hdr));
- evbuffer_drain(buf, header_len);
- evbuffer_remove(buf, cell->payload, cell_length);
- *out = cell;
- result = 1;
-
- done:
- if (free_hdr && hdr)
- tor_free(hdr);
- return result;
-}
-#endif
-
/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to
* <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately.
* Return the number of bytes actually copied.
@@ -1271,94 +1194,6 @@ fetch_from_buf_http(buf_t *buf,
return 1;
}
-#ifdef USE_BUFFEREVENTS
-/** As fetch_from_buf_http, buf works on an evbuffer. */
-int
-fetch_from_evbuffer_http(struct evbuffer *buf,
- char **headers_out, size_t max_headerlen,
- char **body_out, size_t *body_used, size_t max_bodylen,
- int force_complete)
-{
- struct evbuffer_ptr crlf, content_length;
- size_t headerlen, bodylen, contentlen;
-
- /* Find the first \r\n\r\n in the buffer */
- crlf = evbuffer_search(buf, "\r\n\r\n", 4, NULL);
- if (crlf.pos < 0) {
- /* We didn't find one. */
- if (evbuffer_get_length(buf) > max_headerlen)
- return -1; /* Headers too long. */
- return 0; /* Headers not here yet. */
- } else if (crlf.pos > (int)max_headerlen) {
- return -1; /* Headers too long. */
- }
-
- headerlen = crlf.pos + 4; /* Skip over the \r\n\r\n */
- bodylen = evbuffer_get_length(buf) - headerlen;
- if (bodylen > max_bodylen)
- return -1; /* body too long */
-
- /* Look for the first occurrence of CONTENT_LENGTH insize buf before the
- * crlfcrlf */
- content_length = evbuffer_search_range(buf, CONTENT_LENGTH,
- strlen(CONTENT_LENGTH), NULL, &crlf);
-
- if (content_length.pos >= 0) {
- /* We found a content_length: parse it and figure out if the body is here
- * yet. */
- struct evbuffer_ptr eol;
- char *data = NULL;
- int free_data = 0;
- int n, i;
- n = evbuffer_ptr_set(buf, &content_length, strlen(CONTENT_LENGTH),
- EVBUFFER_PTR_ADD);
- tor_assert(n == 0);
- eol = evbuffer_search_eol(buf, &content_length, NULL, EVBUFFER_EOL_CRLF);
- tor_assert(eol.pos > content_length.pos);
- tor_assert(eol.pos <= crlf.pos);
- inspect_evbuffer(buf, &data, eol.pos - content_length.pos, &free_data,
- &content_length);
-
- i = atoi(data);
- if (free_data)
- tor_free(data);
- if (i < 0) {
- log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like "
- "someone is trying to crash us.");
- return -1;
- }
- contentlen = i;
- /* if content-length is malformed, then our body length is 0. fine. */
- log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen);
- if (bodylen < contentlen) {
- if (!force_complete) {
- log_debug(LD_HTTP,"body not all here yet.");
- return 0; /* not all there yet */
- }
- }
- if (bodylen > contentlen) {
- bodylen = contentlen;
- log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen);
- }
- }
-
- if (headers_out) {
- *headers_out = tor_malloc(headerlen+1);
- evbuffer_remove(buf, *headers_out, headerlen);
- (*headers_out)[headerlen] = '\0';
- }
- if (body_out) {
- tor_assert(headers_out);
- tor_assert(body_used);
- *body_used = bodylen;
- *body_out = tor_malloc(bodylen+1);
- evbuffer_remove(buf, *body_out, bodylen);
- (*body_out)[bodylen] = '\0';
- }
- return 1;
-}
-#endif
-
/**
* Wait this many seconds before warning the user about using SOCKS unsafely
* again (requires that WarnUnsafeSocks is turned on). */
@@ -1478,86 +1313,6 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
return res;
}
-#ifdef USE_BUFFEREVENTS
-/* As fetch_from_buf_socks(), but targets an evbuffer instead. */
-int
-fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req,
- int log_sockstype, int safe_socks)
-{
- char *data;
- ssize_t n_drain;
- size_t datalen, buflen, want_length;
- int res;
-
- buflen = evbuffer_get_length(buf);
- if (buflen < 2)
- return 0;
-
- {
- /* See if we can find the socks request in the first chunk of the buffer.
- */
- struct evbuffer_iovec v;
- int i;
- n_drain = 0;
- i = evbuffer_peek(buf, -1, NULL, &v, 1);
- tor_assert(i == 1);
- data = v.iov_base;
- datalen = v.iov_len;
- want_length = 0;
-
- res = parse_socks(data, datalen, req, log_sockstype,
- safe_socks, &n_drain, &want_length);
-
- if (n_drain < 0)
- evbuffer_drain(buf, evbuffer_get_length(buf));
- else if (n_drain > 0)
- evbuffer_drain(buf, n_drain);
-
- if (res)
- return res;
- }
-
- /* Okay, the first chunk of the buffer didn't have a complete socks request.
- * That means that either we don't have a whole socks request at all, or
- * it's gotten split up. We're going to try passing parse_socks() bigger
- * and bigger chunks until either it says "Okay, I got it", or it says it
- * will need more data than we currently have. */
-
- /* Loop while we have more data that we haven't given parse_socks() yet. */
- do {
- int free_data = 0;
- const size_t last_wanted = want_length;
- n_drain = 0;
- data = NULL;
- datalen = inspect_evbuffer(buf, &data, want_length, &free_data, NULL);
-
- want_length = 0;
- res = parse_socks(data, datalen, req, log_sockstype,
- safe_socks, &n_drain, &want_length);
-
- if (free_data)
- tor_free(data);
-
- if (n_drain < 0)
- evbuffer_drain(buf, evbuffer_get_length(buf));
- else if (n_drain > 0)
- evbuffer_drain(buf, n_drain);
-
- if (res == 0 && n_drain == 0 && want_length <= last_wanted) {
- /* If we drained nothing, and we didn't ask for more than last time,
- * then we probably wanted more data than the buffer actually had,
- * and we're finding out that we're not satisified with it. It's
- * time to break until we have more data. */
- break;
- }
-
- buflen = evbuffer_get_length(buf);
- } while (res == 0 && want_length <= buflen && buflen >= 2);
-
- return res;
-}
-#endif
-
/** The size of the header of an Extended ORPort message: 2 bytes for
* COMMAND, 2 bytes for BODYLEN */
#define EXT_OR_CMD_HEADER_SIZE 4
@@ -1588,34 +1343,6 @@ fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out)
return 1;
}
-#ifdef USE_BUFFEREVENTS
-/** Read <b>buf</b>, which should contain an Extended ORPort message
- * from a transport proxy. If well-formed, create and populate
- * <b>out</b> with the Extended ORport message. Return 0 if the
- * buffer was incomplete, 1 if it was well-formed and -1 if we
- * encountered an error while parsing it. */
-int
-fetch_ext_or_command_from_evbuffer(struct evbuffer *buf, ext_or_cmd_t **out)
-{
- char hdr[EXT_OR_CMD_HEADER_SIZE];
- uint16_t len;
- size_t buf_len = evbuffer_get_length(buf);
-
- if (buf_len < EXT_OR_CMD_HEADER_SIZE)
- return 0;
- evbuffer_copyout(buf, hdr, EXT_OR_CMD_HEADER_SIZE);
- len = ntohs(get_uint16(hdr+2));
- if (buf_len < (unsigned)len + EXT_OR_CMD_HEADER_SIZE)
- return 0;
- *out = ext_or_cmd_new(len);
- (*out)->cmd = ntohs(get_uint16(hdr));
- (*out)->len = len;
- evbuffer_drain(buf, EXT_OR_CMD_HEADER_SIZE);
- evbuffer_remove(buf, (*out)->body, len);
- return 1;
-}
-#endif
-
/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and
* have Tor send it as error response to <b>req</b>.
*/
@@ -1868,6 +1595,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
return -1;
}
tor_assert(0);
+ break;
case 4: { /* socks4 */
enum {socks4, socks4a} socks4_prot = socks4a;
const char *authstart, *authend;
@@ -2060,34 +1788,6 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason)
return r;
}
-#ifdef USE_BUFFEREVENTS
-/** As fetch_from_buf_socks_client, buf works on an evbuffer */
-int
-fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state,
- char **reason)
-{
- ssize_t drain = 0;
- uint8_t *data;
- size_t datalen;
- int r;
-
- /* Linearize the SOCKS response in the buffer, up to 128 bytes.
- * (parse_socks_client shouldn't need to see anything beyond that.) */
- datalen = evbuffer_get_length(buf);
- if (datalen > MAX_SOCKS_MESSAGE_LEN)
- datalen = MAX_SOCKS_MESSAGE_LEN;
- data = evbuffer_pullup(buf, datalen);
-
- r = parse_socks_client(data, datalen, state, reason, &drain);
- if (drain > 0)
- evbuffer_drain(buf, drain);
- else if (drain < 0)
- evbuffer_drain(buf, evbuffer_get_length(buf));
-
- return r;
-}
-#endif
-
/** Implementation logic for fetch_from_*_socks_client. */
static int
parse_socks_client(const uint8_t *data, size_t datalen,
@@ -2218,27 +1918,6 @@ peek_buf_has_control0_command(buf_t *buf)
return 0;
}
-#ifdef USE_BUFFEREVENTS
-int
-peek_evbuffer_has_control0_command(struct evbuffer *buf)
-{
- int result = 0;
- if (evbuffer_get_length(buf) >= 4) {
- int free_out = 0;
- char *data = NULL;
- size_t n = inspect_evbuffer(buf, &data, 4, &free_out, NULL);
- uint16_t cmd;
- tor_assert(n >= 4);
- cmd = ntohs(get_uint16(data+2));
- if (cmd <= 0x14)
- result = 1;
- if (free_out)
- tor_free(data);
- }
- return result;
-}
-#endif
-
/** Return the index within <b>buf</b> at which <b>ch</b> first appears,
* or -1 if <b>ch</b> does not appear on buf. */
static off_t
@@ -2336,93 +2015,14 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state,
return 0;
}
-#ifdef USE_BUFFEREVENTS
-int
-write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state,
- const char *data, size_t data_len,
- int done)
-{
- char *next;
- size_t old_avail, avail;
- int over = 0, n;
- struct evbuffer_iovec vec[1];
- do {
- {
- size_t cap = data_len / 4;
- if (cap < 128)
- cap = 128;
- /* XXXX NM this strategy is fragmentation-prone. We should really have
- * two iovecs, and write first into the one, and then into the
- * second if the first gets full. */
- n = evbuffer_reserve_space(buf, cap, vec, 1);
- tor_assert(n == 1);
- }
-
- next = vec[0].iov_base;
- avail = old_avail = vec[0].iov_len;
-
- switch (tor_zlib_process(state, &next, &avail, &data, &data_len, done)) {
- case TOR_ZLIB_DONE:
- over = 1;
- break;
- case TOR_ZLIB_ERR:
- return -1;
- case TOR_ZLIB_OK:
- if (data_len == 0)
- over = 1;
- break;
- case TOR_ZLIB_BUF_FULL:
- if (avail) {
- /* Zlib says we need more room (ZLIB_BUF_FULL). Start a new chunk
- * automatically, whether were going to or not. */
- }
- break;
- }
-
- /* XXXX possible infinite loop on BUF_FULL. */
- vec[0].iov_len = old_avail - avail;
- evbuffer_commit_space(buf, vec, 1);
-
- } while (!over);
- check();
- return 0;
-}
-#endif
-
/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */
int
-generic_buffer_set_to_copy(generic_buffer_t **output,
- const generic_buffer_t *input)
+buf_set_to_copy(buf_t **output,
+ const buf_t *input)
{
-#ifdef USE_BUFFEREVENTS
- struct evbuffer_ptr ptr;
- size_t remaining = evbuffer_get_length(input);
- if (*output) {
- evbuffer_drain(*output, evbuffer_get_length(*output));
- } else {
- if (!(*output = evbuffer_new()))
- return -1;
- }
- evbuffer_ptr_set((struct evbuffer*)input, &ptr, 0, EVBUFFER_PTR_SET);
- while (remaining) {
- struct evbuffer_iovec v[4];
- int n_used, i;
- n_used = evbuffer_peek((struct evbuffer*)input, -1, &ptr, v, 4);
- if (n_used < 0)
- return -1;
- for (i=0;i<n_used;++i) {
- evbuffer_add(*output, v[i].iov_base, v[i].iov_len);
- tor_assert(v[i].iov_len <= remaining);
- remaining -= v[i].iov_len;
- evbuffer_ptr_set((struct evbuffer*)input,
- &ptr, v[i].iov_len, EVBUFFER_PTR_ADD);
- }
- }
-#else
if (*output)
buf_free(*output);
*output = buf_copy(input);
-#endif
return 0;
}
diff --git a/src/or/buffers.h b/src/or/buffers.h
index 2b43ea14b1..52b21d5885 100644
--- a/src/or/buffers.h
+++ b/src/or/buffers.h
@@ -56,46 +56,8 @@ int peek_buf_has_control0_command(buf_t *buf);
int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out);
-#ifdef USE_BUFFEREVENTS
-int fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out,
- int linkproto);
-int fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req,
- int log_sockstype, int safe_socks);
-int fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state,
- char **reason);
-int fetch_from_evbuffer_http(struct evbuffer *buf,
- char **headers_out, size_t max_headerlen,
- char **body_out, size_t *body_used, size_t max_bodylen,
- int force_complete);
-int peek_evbuffer_has_control0_command(struct evbuffer *buf);
-int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state,
- const char *data, size_t data_len,
- int done);
-int fetch_ext_or_command_from_evbuffer(struct evbuffer *buf,
- ext_or_cmd_t **out);
-#endif
-
-#ifdef USE_BUFFEREVENTS
-#define generic_buffer_new() evbuffer_new()
-#define generic_buffer_len(b) evbuffer_get_length((b))
-#define generic_buffer_add(b,dat,len) evbuffer_add((b),(dat),(len))
-#define generic_buffer_get(b,buf,buflen) evbuffer_remove((b),(buf),(buflen))
-#define generic_buffer_clear(b) evbuffer_drain((b), evbuffer_get_length((b)))
-#define generic_buffer_free(b) evbuffer_free((b))
-#define generic_buffer_fetch_ext_or_cmd(b, out) \
- fetch_ext_or_command_from_evbuffer((b), (out))
-#else
-#define generic_buffer_new() buf_new()
-#define generic_buffer_len(b) buf_datalen((b))
-#define generic_buffer_add(b,dat,len) write_to_buf((dat),(len),(b))
-#define generic_buffer_get(b,buf,buflen) fetch_from_buf((buf),(buflen),(b))
-#define generic_buffer_clear(b) buf_clear((b))
-#define generic_buffer_free(b) buf_free((b))
-#define generic_buffer_fetch_ext_or_cmd(b, out) \
- fetch_ext_or_command_from_buf((b), (out))
-#endif
-int generic_buffer_set_to_copy(generic_buffer_t **output,
- const generic_buffer_t *input);
+int buf_set_to_copy(buf_t **output,
+ const buf_t *input);
void assert_buf_ok(buf_t *buf);
@@ -103,6 +65,7 @@ void assert_buf_ok(buf_t *buf);
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);
void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz);
+STATIC size_t preferred_chunk_size(size_t target);
#define DEBUG_CHUNK_ALLOC
/** A single chunk on a buffer. */
diff --git a/src/or/channel.c b/src/or/channel.c
index 5f69a0864b..f547aea1b3 100644
--- a/src/or/channel.c
+++ b/src/or/channel.c
@@ -8,6 +8,32 @@
* transfer cells from Tor instance to Tor instance.
* Currently, there is only one implementation of the channel abstraction: in
* channeltls.c.
+ *
+ * Channels are a higher-level abstraction than or_connection_t: In general,
+ * any means that two Tor relays use to exchange cells, or any means that a
+ * relay and a client use to exchange cells, is a channel.
+ *
+ * Channels differ from pluggable transports in that they do not wrap an
+ * underlying protocol over which cells are transmitted: they <em>are</em> the
+ * underlying protocol.
+ *
+ * This module defines the generic parts of the channel_t interface, and
+ * provides the machinery necessary for specialized implementations to be
+ * created. At present, there is one specialized implementation in
+ * channeltls.c, which uses connection_or.c to send cells over a TLS
+ * connection.
+ *
+ * Every channel implementation is responsible for being able to transmit
+ * cells that are added to it with channel_write_cell() and related functions,
+ * and to receive incoming cells with the channel_queue_cell() and related
+ * functions. See the channel_t documentation for more information.
+ *
+ * When new cells arrive on a channel, they are passed to cell handler
+ * functions, which can be set by channel_set_cell_handlers()
+ * functions. (Tor's cell handlers are in command.c.)
+ *
+ * Tor flushes cells to channels from relay.c in
+ * channel_flush_from_first_active_circuit().
**/
/*
@@ -122,7 +148,7 @@ STATIC uint64_t estimated_total_queue_size = 0;
* If more than one channel exists, follow the next_with_same_id pointer
* as a linked list.
*/
-HT_HEAD(channel_idmap, channel_idmap_entry_s) channel_identity_map =
+static HT_HEAD(channel_idmap, channel_idmap_entry_s) channel_identity_map =
HT_INITIALIZER();
typedef struct channel_idmap_entry_s {
@@ -145,9 +171,9 @@ channel_idmap_eq(const channel_idmap_entry_t *a,
}
HT_PROTOTYPE(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash,
- channel_idmap_eq);
+ channel_idmap_eq)
HT_GENERATE2(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash,
- channel_idmap_eq, 0.5, tor_reallocarray_, tor_free_);
+ channel_idmap_eq, 0.5, tor_reallocarray_, tor_free_)
static cell_queue_entry_t * cell_queue_entry_dup(cell_queue_entry_t *q);
#if 0
@@ -838,7 +864,7 @@ channel_free(channel_t *chan)
}
/* Call a free method if there is one */
- if (chan->free) chan->free(chan);
+ if (chan->free_fn) chan->free_fn(chan);
channel_clear_remote_end(chan);
@@ -878,7 +904,7 @@ channel_listener_free(channel_listener_t *chan_l)
tor_assert(!(chan_l->registered));
/* Call a free method if there is one */
- if (chan_l->free) chan_l->free(chan_l);
+ if (chan_l->free_fn) chan_l->free_fn(chan_l);
/*
* We're in CLOSED or ERROR, so the incoming channel queue is already
@@ -916,7 +942,7 @@ channel_force_free(channel_t *chan)
}
/* Call a free method if there is one */
- if (chan->free) chan->free(chan);
+ if (chan->free_fn) chan->free_fn(chan);
channel_clear_remote_end(chan);
@@ -958,7 +984,7 @@ channel_listener_force_free(channel_listener_t *chan_l)
chan_l);
/* Call a free method if there is one */
- if (chan_l->free) chan_l->free(chan_l);
+ if (chan_l->free_fn) chan_l->free_fn(chan_l);
/*
* The incoming list just gets emptied and freed; we request close on
@@ -3510,7 +3536,7 @@ channel_dump_statistics, (channel_t *chan, int severity))
have_remote_addr = channel_get_addr_if_possible(chan, &remote_addr);
if (have_remote_addr) {
char *actual = tor_strdup(channel_get_actual_remote_descr(chan));
- remote_addr_str = tor_dup_addr(&remote_addr);
+ remote_addr_str = tor_addr_to_str_dup(&remote_addr);
tor_log(severity, LD_GENERAL,
" * Channel " U64_FORMAT " says its remote address"
" is %s, and gives a canonical description of \"%s\" and an "
@@ -4524,8 +4550,8 @@ channel_update_xmit_queue_size(channel_t *chan)
/* Next, adjust by the overhead factor, if any is available */
if (chan->get_overhead_estimate) {
overhead = chan->get_overhead_estimate(chan);
- if (overhead >= 1.0f) {
- queued *= overhead;
+ if (overhead >= 1.0) {
+ queued = (uint64_t)(queued * overhead);
} else {
/* Ignore silly overhead factors */
log_notice(LD_CHANNEL, "Ignoring silly overhead factor %f", overhead);
diff --git a/src/or/channel.h b/src/or/channel.h
index 129c0c2013..a711b56d44 100644
--- a/src/or/channel.h
+++ b/src/or/channel.h
@@ -18,7 +18,7 @@ typedef void (*channel_cell_handler_fn_ptr)(channel_t *, cell_t *);
typedef void (*channel_var_cell_handler_fn_ptr)(channel_t *, var_cell_t *);
struct cell_queue_entry_s;
-TOR_SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s) incoming_queue;
+TOR_SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s);
typedef struct chan_cell_queue chan_cell_queue_t;
/**
@@ -90,7 +90,7 @@ struct channel_s {
/* Methods implemented by the lower layer */
/** Free a channel */
- void (*free)(channel_t *);
+ void (*free_fn)(channel_t *);
/** Close an open channel */
void (*close)(channel_t *);
/** Describe the transport subclass for this channel */
@@ -273,7 +273,7 @@ struct channel_listener_s {
/* Methods implemented by the lower layer */
/** Free a channel */
- void (*free)(channel_listener_t *);
+ void (*free_fn)(channel_listener_t *);
/** Close an open channel */
void (*close)(channel_listener_t *);
/** Describe the transport subclass for this channel */
@@ -469,6 +469,10 @@ void channel_notify_flushed(channel_t *chan);
/* Handle stuff we need to do on open like notifying circuits */
void channel_do_open_actions(channel_t *chan);
+#ifdef TOR_UNIT_TESTS
+extern uint64_t estimated_total_queue_size;
+#endif
+
#endif
/* Helper functions to perform operations on channels */
diff --git a/src/or/channeltls.c b/src/or/channeltls.c
index c65af5d040..09cca95b64 100644
--- a/src/or/channeltls.c
+++ b/src/or/channeltls.c
@@ -6,6 +6,28 @@
*
* \brief A concrete subclass of channel_t using or_connection_t to transfer
* cells between Tor instances.
+ *
+ * This module fills in the various function pointers in channel_t, to
+ * implement the channel_tls_t channels as used in Tor today. These channels
+ * are created from channel_tls_connect() and
+ * channel_tls_handle_incoming(). Each corresponds 1:1 to or_connection_t
+ * object, as implemented in connection_or.c. These channels transmit cells
+ * to the underlying or_connection_t by calling
+ * connection_or_write_*_cell_to_buf(), and receive cells from the underlying
+ * or_connection_t when connection_or_process_cells_from_inbuf() calls
+ * channel_tls_handle_*_cell().
+ *
+ * Here we also implement the server (responder) side of the v3+ Tor link
+ * handshake, which uses CERTS and AUTHENTICATE cell to negotiate versions,
+ * exchange expected and observed IP and time information, and bootstrap a
+ * level of authentication higher than we have gotten on the raw TLS
+ * handshake.
+ *
+ * NOTE: Since there is currently only one type of channel, there are probably
+ * more than a few cases where functionality that is currently in
+ * channeltls.c, connection_or.c, and channel.c ought to be divided up
+ * differently. The right time to do this is probably whenever we introduce
+ * our next channel type.
**/
/*
@@ -22,6 +44,7 @@
#include "channeltls.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
+#include "command.h"
#include "config.h"
#include "connection.h"
#include "connection_or.h"
@@ -51,7 +74,7 @@ uint64_t stats_n_authenticate_cells_processed = 0;
uint64_t stats_n_authorize_cells_processed = 0;
/** Active listener, if any */
-channel_listener_t *channel_tls_listener = NULL;
+static channel_listener_t *channel_tls_listener = NULL;
/* channel_tls_t method declarations */
@@ -116,7 +139,7 @@ channel_tls_common_init(channel_tls_t *tlschan)
chan->state = CHANNEL_STATE_OPENING;
chan->close = channel_tls_close_method;
chan->describe_transport = channel_tls_describe_transport_method;
- chan->free = channel_tls_free_method;
+ chan->free_fn = channel_tls_free_method;
chan->get_overhead_estimate = channel_tls_get_overhead_estimate_method;
chan->get_remote_addr = channel_tls_get_remote_addr_method;
chan->get_remote_descr = channel_tls_get_remote_descr_method;
@@ -445,7 +468,7 @@ channel_tls_free_method(channel_t *chan)
static double
channel_tls_get_overhead_estimate_method(channel_t *chan)
{
- double overhead = 1.0f;
+ double overhead = 1.0;
channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan);
tor_assert(tlschan);
@@ -462,7 +485,8 @@ channel_tls_get_overhead_estimate_method(channel_t *chan)
* Never estimate more than 2.0; otherwise we get silly large estimates
* at the very start of a new TLS connection.
*/
- if (overhead > 2.0f) overhead = 2.0f;
+ if (overhead > 2.0)
+ overhead = 2.0;
}
log_debug(LD_CHANNEL,
@@ -554,7 +578,7 @@ channel_tls_get_remote_descr_method(channel_t *chan, int flags)
break;
case GRD_FLAG_ORIGINAL:
/* Actual address with port */
- addr_str = tor_dup_addr(&(tlschan->conn->real_addr));
+ addr_str = tor_addr_to_str_dup(&(tlschan->conn->real_addr));
tor_snprintf(buf, MAX_DESCR_LEN + 1,
"%s:%u", addr_str, conn->port);
tor_free(addr_str);
@@ -567,7 +591,7 @@ channel_tls_get_remote_descr_method(channel_t *chan, int flags)
break;
case GRD_FLAG_ORIGINAL|GRD_FLAG_ADDR_ONLY:
/* Actual address, no port */
- addr_str = tor_dup_addr(&(tlschan->conn->real_addr));
+ addr_str = tor_addr_to_str_dup(&(tlschan->conn->real_addr));
strlcpy(buf, addr_str, sizeof(buf));
tor_free(addr_str);
answer = buf;
@@ -797,6 +821,7 @@ static int
channel_tls_write_packed_cell_method(channel_t *chan,
packed_cell_t *packed_cell)
{
+ tor_assert(chan);
channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan);
size_t cell_network_size = get_cell_network_size(chan->wide_circ_ids);
int written = 0;
@@ -1189,6 +1214,8 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn)
* notice "hey, data arrived!" before we notice "hey, the handshake
* finished!" And we need to be accepting both at once to handle both
* the v2 and v3 handshakes. */
+ /* But that should be happening any longer've disabled bufferevents. */
+ tor_assert_nonfatal_unreached_once();
/* fall through */
case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING:
@@ -1898,8 +1925,8 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
}
err:
- for (unsigned i = 0; i < ARRAY_LENGTH(certs); ++i) {
- tor_x509_cert_free(certs[i]);
+ for (unsigned u = 0; u < ARRAY_LENGTH(certs); ++u) {
+ tor_x509_cert_free(certs[u]);
}
certs_cell_free(cc);
#undef ERR
diff --git a/src/or/channeltls.h b/src/or/channeltls.h
index a4d9c7a095..8b5863a461 100644
--- a/src/or/channeltls.h
+++ b/src/or/channeltls.h
@@ -52,6 +52,14 @@ void channel_tls_update_marks(or_connection_t *conn);
/* Cleanup at shutdown */
void channel_tls_free_all(void);
+extern uint64_t stats_n_authorize_cells_processed;
+extern uint64_t stats_n_authenticate_cells_processed;
+extern uint64_t stats_n_versions_cells_processed;
+extern uint64_t stats_n_netinfo_cells_processed;
+extern uint64_t stats_n_vpadding_cells_processed;
+extern uint64_t stats_n_certs_cells_processed;
+extern uint64_t stats_n_auth_challenge_cells_processed;
+
#ifdef CHANNELTLS_PRIVATE
STATIC void channel_tls_process_certs_cell(var_cell_t *cell,
channel_tls_t *tlschan);
diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c
index 552947eba2..9f93e737f7 100644
--- a/src/or/circpathbias.c
+++ b/src/or/circpathbias.c
@@ -85,7 +85,6 @@ pathbias_get_notice_rate(const or_options_t *options)
DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0;
}
-/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */
/** The circuit success rate below which we issue a warn */
static double
pathbias_get_warn_rate(const or_options_t *options)
@@ -98,7 +97,7 @@ pathbias_get_warn_rate(const or_options_t *options)
DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0;
}
-/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */
+/* XXXX I'd like to have this be static again, but entrynodes.c needs it. */
/**
* The extreme rate is the rate at which we would drop the guard,
* if pb_dropguard is also set. Otherwise we just warn.
@@ -114,7 +113,7 @@ pathbias_get_extreme_rate(const or_options_t *options)
DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0;
}
-/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */
+/* XXXX I'd like to have this be static again, but entrynodes.c needs it. */
/**
* If 1, we actually disable use of guards that fall below
* the extreme_pct.
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 28d286cd72..cb9c146fb7 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -28,6 +28,7 @@
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
+#include "crypto.h"
#include "directory.h"
#include "entrynodes.h"
#include "main.h"
@@ -38,18 +39,14 @@
#include "onion_tap.h"
#include "onion_fast.h"
#include "policies.h"
-#include "transports.h"
#include "relay.h"
+#include "rendcommon.h"
#include "rephist.h"
#include "router.h"
#include "routerlist.h"
#include "routerparse.h"
#include "routerset.h"
-#include "crypto.h"
-
-#ifndef MIN
-#define MIN(a,b) ((a)<(b)?(a):(b))
-#endif
+#include "transports.h"
static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
uint16_t port,
@@ -62,7 +59,6 @@ static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath);
static int onion_extend_cpath(origin_circuit_t *circ);
static int count_acceptable_nodes(smartlist_t *routers);
static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice);
-static int circuits_can_use_ntor(void);
/** This function tries to get a channel to the specified endpoint,
* and then calls command_setup_channel() to give it the right
@@ -369,7 +365,7 @@ circuit_rep_hist_note_result(origin_circuit_t *circ)
} while (hop!=circ->cpath);
}
-/** Return 1 iff at least one node in circ's cpath supports ntor. */
+/** Return 1 iff every node in circ's cpath definitely supports ntor. */
static int
circuit_cpath_supports_ntor(const origin_circuit_t *circ)
{
@@ -377,16 +373,19 @@ circuit_cpath_supports_ntor(const origin_circuit_t *circ)
cpath = head = circ->cpath;
do {
- if (cpath->extend_info &&
- !tor_mem_is_zero(
- (const char*)cpath->extend_info->curve25519_onion_key.public_key,
- CURVE25519_PUBKEY_LEN))
- return 1;
+ /* if the extend_info is missing, we can't tell if it supports ntor */
+ if (!cpath->extend_info) {
+ return 0;
+ }
+ /* if the key is blank, it definitely doesn't support ntor */
+ if (!extend_info_supports_ntor(cpath->extend_info)) {
+ return 0;
+ }
cpath = cpath->next;
} while (cpath != head);
- return 0;
+ return 1;
}
/** Pick all the entries in our cpath. Stop and return 0 when we're
@@ -394,41 +393,61 @@ circuit_cpath_supports_ntor(const origin_circuit_t *circ)
static int
onion_populate_cpath(origin_circuit_t *circ)
{
- int n_tries = 0;
- const int using_ntor = circuits_can_use_ntor();
+ int r = 0;
-#define MAX_POPULATE_ATTEMPTS 32
+ /* onion_extend_cpath assumes these are non-NULL */
+ tor_assert(circ);
+ tor_assert(circ->build_state);
- while (1) {
- int r = onion_extend_cpath(circ);
+ while (r == 0) {
+ r = onion_extend_cpath(circ);
if (r < 0) {
log_info(LD_CIRC,"Generating cpath hop failed.");
return -1;
}
- if (r == 1) {
- /* This circuit doesn't need/shouldn't be forced to have an ntor hop */
- if (circ->build_state->desired_path_len <= 1 || ! using_ntor)
- return 0;
+ }
- /* This circuit has an ntor hop. great! */
- if (circuit_cpath_supports_ntor(circ))
- return 0;
+ /* The path is complete */
+ tor_assert(r == 1);
- /* No node in the circuit supports ntor. Have we already tried too many
- * times? */
- if (++n_tries >= MAX_POPULATE_ATTEMPTS)
- break;
+ /* Does every node in this path support ntor? */
+ int path_supports_ntor = circuit_cpath_supports_ntor(circ);
- /* Clear the path and retry */
- circuit_clear_cpath(circ);
+ /* We would like every path to support ntor, but we have to allow for some
+ * edge cases. */
+ tor_assert(circuit_get_cpath_len(circ));
+ if (circuit_can_use_tap(circ)) {
+ /* Circuits from clients to intro points, and hidden services to
+ * rend points do not support ntor, because the hidden service protocol
+ * does not include ntor onion keys. This is also true for Tor2web clients
+ * and Single Onion Services. */
+ return 0;
+ }
+
+ if (circuit_get_cpath_len(circ) == 1) {
+ /* Allow for bootstrapping: when we're fetching directly from a fallback,
+ * authority, or bridge, we have no way of knowing its ntor onion key
+ * before we connect to it. So instead, we try connecting, and end up using
+ * CREATE_FAST. */
+ tor_assert(circ->cpath);
+ tor_assert(circ->cpath->extend_info);
+ const node_t *node = node_get_by_id(
+ circ->cpath->extend_info->identity_digest);
+ /* If we don't know the node and its descriptor, we must be bootstrapping.
+ */
+ if (!node || !node_has_descriptor(node)) {
+ return 0;
}
}
- log_warn(LD_CIRC, "I tried for %d times, but I couldn't build a %d-hop "
- "circuit with at least one node that supports ntor.",
- MAX_POPULATE_ATTEMPTS,
- circ->build_state->desired_path_len);
- return -1;
+ if (BUG(!path_supports_ntor)) {
+ /* If we're building a multi-hop path, and it's not one of the HS or
+ * bootstrapping exceptions, and it doesn't support ntor, something has
+ * gone wrong. */
+ return -1;
+ }
+
+ return 0;
}
/** Create and return a new origin circuit. Initialize its purpose and
@@ -461,14 +480,14 @@ origin_circuit_init(uint8_t purpose, int flags)
* it's not open already.
*/
origin_circuit_t *
-circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags)
+circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags)
{
origin_circuit_t *circ;
int err_reason = 0;
circ = origin_circuit_init(purpose, flags);
- if (onion_pick_cpath_exit(circ, exit) < 0 ||
+ if (onion_pick_cpath_exit(circ, exit_ei) < 0 ||
onion_populate_cpath(circ) < 0) {
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH);
return NULL;
@@ -761,10 +780,13 @@ should_use_create_fast_for_circuit(origin_circuit_t *circ)
tor_assert(circ->cpath);
tor_assert(circ->cpath->extend_info);
- if (!circ->cpath->extend_info->onion_key)
- return 1; /* our hand is forced: only a create_fast will work. */
+ if (!circuit_has_usable_onion_key(circ)) {
+ /* We don't have ntor, and we don't have or can't use TAP,
+ * so our hand is forced: only a create_fast will work. */
+ return 1;
+ }
if (public_server_mode(options)) {
- /* We're a server, and we know an onion key. We can choose.
+ /* We're a server, and we have a usable onion key. We can choose.
* Prefer to blend our circuit into the other circuits we are
* creating on behalf of others. */
return 0;
@@ -789,62 +811,56 @@ circuit_timeout_want_to_count_circ(origin_circuit_t *circ)
&& circ->build_state->desired_path_len == DEFAULT_ROUTE_LEN;
}
-/** Return true if the ntor handshake is enabled in the configuration, or if
- * it's been set to "auto" in the configuration and it's enabled in the
- * consensus. */
-static int
-circuits_can_use_ntor(void)
-{
- const or_options_t *options = get_options();
- if (options->UseNTorHandshake != -1)
- return options->UseNTorHandshake;
- return networkstatus_get_param(NULL, "UseNTorHandshake", 0, 0, 1);
-}
-
/** Decide whether to use a TAP or ntor handshake for connecting to <b>ei</b>
* directly, and set *<b>cell_type_out</b> and *<b>handshake_type_out</b>
- * accordingly. */
+ * accordingly.
+ * Note that TAP handshakes in CREATE cells are only used for direct
+ * connections:
+ * - from Tor2web to intro points not in the client's consensus, and
+ * - from Single Onions to rend points not in the service's consensus.
+ * This is checked in onion_populate_cpath. */
static void
circuit_pick_create_handshake(uint8_t *cell_type_out,
uint16_t *handshake_type_out,
const extend_info_t *ei)
{
- if (!tor_mem_is_zero((const char*)ei->curve25519_onion_key.public_key,
- CURVE25519_PUBKEY_LEN) &&
- circuits_can_use_ntor()) {
+ /* torspec says: In general, clients SHOULD use CREATE whenever they are
+ * using the TAP handshake, and CREATE2 otherwise. */
+ if (extend_info_supports_ntor(ei)) {
*cell_type_out = CELL_CREATE2;
*handshake_type_out = ONION_HANDSHAKE_TYPE_NTOR;
- return;
+ } else {
+ /* XXXX030 Remove support for deciding to use TAP and EXTEND. */
+ *cell_type_out = CELL_CREATE;
+ *handshake_type_out = ONION_HANDSHAKE_TYPE_TAP;
}
-
- *cell_type_out = CELL_CREATE;
- *handshake_type_out = ONION_HANDSHAKE_TYPE_TAP;
}
-/** Decide whether to use a TAP or ntor handshake for connecting to <b>ei</b>
- * directly, and set *<b>handshake_type_out</b> accordingly. Decide whether,
- * in extending through <b>node</b> to do so, we should use an EXTEND2 or an
- * EXTEND cell to do so, and set *<b>cell_type_out</b> and
- * *<b>create_cell_type_out</b> accordingly. */
+/** Decide whether to use a TAP or ntor handshake for extending to <b>ei</b>
+ * and set *<b>handshake_type_out</b> accordingly. Decide whether we should
+ * use an EXTEND2 or an EXTEND cell to do so, and set *<b>cell_type_out</b>
+ * and *<b>create_cell_type_out</b> accordingly.
+ * Note that TAP handshakes in EXTEND cells are only used:
+ * - from clients to intro points, and
+ * - from hidden services to rend points.
+ * This is checked in onion_populate_cpath.
+ */
static void
circuit_pick_extend_handshake(uint8_t *cell_type_out,
uint8_t *create_cell_type_out,
uint16_t *handshake_type_out,
- const node_t *node_prev,
const extend_info_t *ei)
{
uint8_t t;
circuit_pick_create_handshake(&t, handshake_type_out, ei);
- /* XXXX024 The check for whether the node has a curve25519 key is a bad
- * proxy for whether it can do extend2 cells; once a version that
- * handles extend2 cells is out, remove it. */
- if (node_prev &&
- *handshake_type_out != ONION_HANDSHAKE_TYPE_TAP &&
- (node_has_curve25519_onion_key(node_prev) ||
- (node_prev->rs && node_prev->rs->version_supports_extend2_cells))) {
+
+ /* torspec says: Clients SHOULD use the EXTEND format whenever sending a TAP
+ * handshake... In other cases, clients SHOULD use EXTEND2. */
+ if (*handshake_type_out != ONION_HANDSHAKE_TYPE_TAP) {
*cell_type_out = RELAY_COMMAND_EXTEND2;
*create_cell_type_out = CELL_CREATE2;
} else {
+ /* XXXX030 Remove support for deciding to use TAP and EXTEND. */
*cell_type_out = RELAY_COMMAND_EXTEND;
*create_cell_type_out = CELL_CREATE;
}
@@ -888,14 +904,12 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
*/
circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
circ->cpath->extend_info);
- note_request("cell: create", 1);
} 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;
- note_request("cell: create fast", 1);
}
len = onion_skin_create(cc.handshake_type,
@@ -1002,15 +1016,10 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
return - END_CIRC_REASON_INTERNAL;
}
- {
- const node_t *prev_node;
- prev_node = node_get_by_id(hop->prev->extend_info->identity_digest);
- circuit_pick_extend_handshake(&ec.cell_type,
- &ec.create_cell.cell_type,
- &ec.create_cell.handshake_type,
- prev_node,
- hop->extend_info);
- }
+ 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;
@@ -1028,7 +1037,6 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
ec.create_cell.handshake_len = len;
log_info(LD_CIRC,"Sending extend relay cell.");
- note_request("cell: extend", 1);
{
uint8_t command = 0;
uint16_t payload_len=0;
@@ -1440,7 +1448,7 @@ onionskin_answer(or_circuit_t *circ,
* to handle the desired path length, return -1.
*/
static int
-new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes)
+new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes)
{
int num_acceptable_routers;
int routelen;
@@ -1448,7 +1456,7 @@ new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes)
tor_assert(nodes);
routelen = DEFAULT_ROUTE_LEN;
- if (exit &&
+ if (exit_ei &&
purpose != CIRCUIT_PURPOSE_TESTING &&
purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
routelen++;
@@ -1572,7 +1580,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
int n_best_support=0;
const or_options_t *options = get_options();
const smartlist_t *the_nodes;
- const node_t *node=NULL;
+ const node_t *selected_node=NULL;
connections = get_connection_array();
@@ -1699,7 +1707,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
smartlist_add(supporting, (void*)node);
});
- node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
+ selected_node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
smartlist_free(supporting);
} else {
/* Either there are no pending connections, or no routers even seem to
@@ -1737,8 +1745,8 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
}
} SMARTLIST_FOREACH_END(node);
- node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
- if (node)
+ selected_node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT);
+ if (selected_node)
break;
smartlist_clear(supporting);
/* If we reach this point, we can't actually support any unhandled
@@ -1752,9 +1760,9 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
}
tor_free(n_supported);
- if (node) {
- log_info(LD_CIRC, "Chose exit server '%s'", node_describe(node));
- return node;
+ if (selected_node) {
+ log_info(LD_CIRC, "Chose exit server '%s'", node_describe(selected_node));
+ return selected_node;
}
if (options->ExitNodes) {
log_warn(LD_CIRC,
@@ -1833,13 +1841,32 @@ pick_rendezvous_node(router_crn_flags_t flags)
flags |= CRN_ALLOW_INVALID;
#ifdef ENABLE_TOR2WEB_MODE
+ /* We want to connect directly to the node if we can */
+ router_crn_flags_t direct_flags = flags;
+ direct_flags |= CRN_PREF_ADDR;
+ direct_flags |= CRN_DIRECT_CONN;
+
/* The user wants us to pick specific RPs. */
if (options->Tor2webRendezvousPoints) {
- const node_t *tor2web_rp = pick_tor2web_rendezvous_node(flags, options);
+ const node_t *tor2web_rp = pick_tor2web_rendezvous_node(direct_flags,
+ options);
if (tor2web_rp) {
return tor2web_rp;
}
- /* Else, if no tor2web RP was found, fall back to choosing a random node */
+ }
+
+ /* Else, if no direct, preferred tor2web RP was found, fall back to choosing
+ * a random direct node */
+ const node_t *node = router_choose_random_node(NULL, options->ExcludeNodes,
+ direct_flags);
+ /* Return the direct node (if found), or log a message and fall back to an
+ * indirect connection. */
+ if (node) {
+ return node;
+ } else {
+ log_info(LD_REND,
+ "Unable to find a random rendezvous point that is reachable via "
+ "a direct connection, falling back to a 3-hop path.");
}
#endif
@@ -1892,7 +1919,8 @@ choose_good_exit_server(uint8_t purpose,
/** Log a warning if the user specified an exit for the circuit that
* has been excluded from use by ExcludeNodes or ExcludeExitNodes. */
static void
-warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
+warn_if_last_router_excluded(origin_circuit_t *circ,
+ const extend_info_t *exit_ei)
{
const or_options_t *options = get_options();
routerset_t *rs = options->ExcludeNodes;
@@ -1939,13 +1967,13 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
break;
}
- if (routerset_contains_extendinfo(rs, exit)) {
+ if (routerset_contains_extendinfo(rs, exit_ei)) {
/* We should never get here if StrictNodes is set to 1. */
if (options->StrictNodes) {
log_warn(LD_BUG, "Using %s '%s' which is listed in ExcludeNodes%s, "
"even though StrictNodes is set. Please report. "
"(Circuit purpose: %s)",
- description, extend_info_describe(exit),
+ description, extend_info_describe(exit_ei),
rs==options->ExcludeNodes?"":" or ExcludeExitNodes",
circuit_purpose_to_string(purpose));
} else {
@@ -1954,7 +1982,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
"prevent this (and possibly break your Tor functionality), "
"set the StrictNodes configuration option. "
"(Circuit purpose: %s)",
- description, extend_info_describe(exit),
+ description, extend_info_describe(exit_ei),
rs==options->ExcludeNodes?"":" or ExcludeExitNodes",
circuit_purpose_to_string(purpose));
}
@@ -1968,25 +1996,27 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit)
* router (or use <b>exit</b> if provided). Store these in the
* cpath. Return 0 if ok, -1 if circuit should be closed. */
static int
-onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit)
+onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei)
{
cpath_build_state_t *state = circ->build_state;
if (state->onehop_tunnel) {
- log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel.");
+ log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel%s.",
+ (rend_allow_non_anonymous_connection(get_options()) ?
+ ", or intro or rendezvous connection" : ""));
state->desired_path_len = 1;
} else {
- int r = new_route_len(circ->base_.purpose, exit, nodelist_get_list());
+ int r = new_route_len(circ->base_.purpose, exit_ei, nodelist_get_list());
if (r < 1) /* must be at least 1 */
return -1;
state->desired_path_len = r;
}
- if (exit) { /* the circuit-builder pre-requested one */
- warn_if_last_router_excluded(circ, exit);
+ if (exit_ei) { /* the circuit-builder pre-requested one */
+ warn_if_last_router_excluded(circ, exit_ei);
log_info(LD_CIRC,"Using requested exit node '%s'",
- extend_info_describe(exit));
- exit = extend_info_dup(exit);
+ extend_info_describe(exit_ei));
+ exit_ei = extend_info_dup(exit_ei);
} else { /* we have to decide one */
const node_t *node =
choose_good_exit_server(circ->base_.purpose, state->need_uptime,
@@ -1995,10 +2025,10 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit)
log_warn(LD_CIRC,"Failed to choose an exit server");
return -1;
}
- exit = extend_info_from_node(node, 0);
- tor_assert(exit);
+ exit_ei = extend_info_from_node(node, 0);
+ tor_assert(exit_ei);
}
- state->chosen_exit = exit;
+ state->chosen_exit = exit_ei;
return 0;
}
@@ -2007,19 +2037,19 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit)
* the caller will do this if it wants to.
*/
int
-circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *exit)
+circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *exit_ei)
{
cpath_build_state_t *state;
- tor_assert(exit);
+ tor_assert(exit_ei);
tor_assert(circ);
state = circ->build_state;
tor_assert(state);
extend_info_free(state->chosen_exit);
- state->chosen_exit = extend_info_dup(exit);
+ state->chosen_exit = extend_info_dup(exit_ei);
++circ->build_state->desired_path_len;
- onion_append_hop(&circ->cpath, exit);
+ onion_append_hop(&circ->cpath, exit_ei);
return 0;
}
@@ -2028,18 +2058,18 @@ circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *exit)
* send the next extend cell to begin connecting to that hop.
*/
int
-circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit)
+circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit_ei)
{
int err_reason = 0;
- warn_if_last_router_excluded(circ, exit);
+ warn_if_last_router_excluded(circ, exit_ei);
tor_gettimeofday(&circ->base_.timestamp_began);
- circuit_append_new_exit(circ, exit);
+ circuit_append_new_exit(circ, exit_ei);
circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
if ((err_reason = circuit_send_next_onion_skin(circ))<0) {
log_warn(LD_CIRC, "Couldn't extend circuit to new point %s.",
- extend_info_describe(exit));
+ extend_info_describe(exit_ei));
circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason);
return -1;
}
@@ -2064,15 +2094,18 @@ count_acceptable_nodes(smartlist_t *nodes)
if (! node->is_running)
// log_debug(LD_CIRC,"Nope, the directory says %d is not running.",i);
continue;
+ /* XXX This clause makes us count incorrectly: if AllowInvalidRouters
+ * allows this node in some places, then we're getting an inaccurate
+ * count. For now, be conservative and don't count it. But later we
+ * should try to be smarter. */
if (! node->is_valid)
// log_debug(LD_CIRC,"Nope, the directory says %d is not valid.",i);
continue;
if (! node_has_descriptor(node))
continue;
- /* XXX This clause makes us count incorrectly: if AllowInvalidRouters
- * allows this node in some places, then we're getting an inaccurate
- * count. For now, be conservative and don't count it. But later we
- * should try to be smarter. */
+ /* The node has a descriptor, so we can just check the ntor key directly */
+ if (!node_has_curve25519_onion_key(node))
+ continue;
++num;
} SMARTLIST_FOREACH_END(node);
@@ -2150,7 +2183,6 @@ choose_good_middle_server(uint8_t purpose,
* If <b>state</b> is NULL, we're choosing a router to serve as an entry
* guard, not for any particular circuit.
*/
-/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */
const node_t *
choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
{
@@ -2184,7 +2216,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
* This is an incomplete fix, but is no worse than the previous behaviour,
* and only applies to minimal, testing tor networks
* (so it's no less secure) */
- /*XXXX025 use the using_as_guard flag to accomplish this.*/
+ /*XXXX++ use the using_as_guard flag to accomplish this.*/
if (options->UseEntryGuards
&& (!options->TestingTorNetwork ||
smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards())
@@ -2363,6 +2395,14 @@ extend_info_from_node(const node_t *node, int for_direct_connect)
log_warn(LD_CIRC, "Could not choose valid address for %s",
node->ri ? node->ri->nickname : node->rs->nickname);
+ /* Every node we connect or extend to must support ntor */
+ if (!node_has_curve25519_onion_key(node)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_CIRC,
+ "Attempted to create extend_info for a node that does not support "
+ "ntor: %s", node_describe(node));
+ return NULL;
+ }
+
if (valid_addr && node->ri)
return extend_info_new(node->ri->nickname,
node->identity,
@@ -2448,3 +2488,66 @@ extend_info_addr_is_allowed(const tor_addr_t *addr)
return 0;
}
+/* Does ei have a valid TAP key? */
+int
+extend_info_supports_tap(const extend_info_t* ei)
+{
+ tor_assert(ei);
+ /* Valid TAP keys are not NULL */
+ return ei->onion_key != NULL;
+}
+
+/* Does ei have a valid ntor key? */
+int
+extend_info_supports_ntor(const extend_info_t* ei)
+{
+ tor_assert(ei);
+ /* Valid ntor keys have at least one non-zero byte */
+ return !tor_mem_is_zero(
+ (const char*)ei->curve25519_onion_key.public_key,
+ CURVE25519_PUBKEY_LEN);
+}
+
+/* Is circuit purpose allowed to use the deprecated TAP encryption protocol?
+ * The hidden service protocol still uses TAP for some connections, because
+ * ntor onion keys aren't included in HS descriptors or INTRODUCE cells. */
+static int
+circuit_purpose_can_use_tap_impl(uint8_t purpose)
+{
+ return (purpose == CIRCUIT_PURPOSE_S_CONNECT_REND ||
+ purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
+}
+
+/* Is circ allowed to use the deprecated TAP encryption protocol?
+ * The hidden service protocol still uses TAP for some connections, because
+ * ntor onion keys aren't included in HS descriptors or INTRODUCE cells. */
+int
+circuit_can_use_tap(const origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(circ->cpath);
+ tor_assert(circ->cpath->extend_info);
+ return (circuit_purpose_can_use_tap_impl(circ->base_.purpose) &&
+ extend_info_supports_tap(circ->cpath->extend_info));
+}
+
+/* Does circ have an onion key which it's allowed to use? */
+int
+circuit_has_usable_onion_key(const origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(circ->cpath);
+ tor_assert(circ->cpath->extend_info);
+ return (extend_info_supports_ntor(circ->cpath->extend_info) ||
+ circuit_can_use_tap(circ));
+}
+
+/* Does ei have an onion key which it would prefer to use?
+ * Currently, we prefer ntor keys*/
+int
+extend_info_has_preferred_onion_key(const extend_info_t* ei)
+{
+ tor_assert(ei);
+ return extend_info_supports_ntor(ei);
+}
+
diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h
index 7f5fd511a9..1244601f71 100644
--- a/src/or/circuitbuild.h
+++ b/src/or/circuitbuild.h
@@ -54,6 +54,11 @@ extend_info_t *extend_info_from_node(const node_t *r, int for_direct_connect);
extend_info_t *extend_info_dup(extend_info_t *info);
void extend_info_free(extend_info_t *info);
int extend_info_addr_is_allowed(const tor_addr_t *addr);
+int extend_info_supports_tap(const extend_info_t* ei);
+int extend_info_supports_ntor(const extend_info_t* ei);
+int circuit_can_use_tap(const origin_circuit_t *circ);
+int circuit_has_usable_onion_key(const origin_circuit_t *circ);
+int extend_info_has_preferred_onion_key(const extend_info_t* ei);
const node_t *build_state_get_exit_node(cpath_build_state_t *state);
const char *build_state_get_exit_nickname(cpath_build_state_t *state);
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index b710485908..977afca18d 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -109,7 +109,7 @@ HT_GENERATE2(chan_circid_map, chan_circid_circuit_map_t, node,
* used to improve performance when many cells arrive in a row from the
* same circuit.
*/
-chan_circid_circuit_map_t *_last_circid_chan_ent = NULL;
+static chan_circid_circuit_map_t *_last_circid_chan_ent = NULL;
/** Implementation helper for circuit_set_{p,n}_circid_channel: A circuit ID
* and/or channel for circ has just changed from <b>old_chan, old_id</b>
@@ -1613,7 +1613,8 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
return best;
}
-/** Return the number of hops in circuit's path. */
+/** Return the number of hops in circuit's path. If circ has no entries,
+ * or is NULL, returns 0. */
int
circuit_get_cpath_len(origin_circuit_t *circ)
{
@@ -1629,7 +1630,8 @@ circuit_get_cpath_len(origin_circuit_t *circ)
}
/** Return the <b>hopnum</b>th hop in <b>circ</b>->cpath, or NULL if there
- * aren't that many hops in the list. */
+ * aren't that many hops in the list. <b>hopnum</b> starts at 1.
+ * Returns NULL if <b>hopnum</b> is 0 or negative. */
crypt_path_t *
circuit_get_cpath_hop(origin_circuit_t *circ, int hopnum)
{
@@ -2022,7 +2024,7 @@ circuit_max_queued_cell_age(const circuit_t *c, uint32_t now)
/** Return the age in milliseconds of the oldest buffer chunk on <b>conn</b>,
* where age is taken in milliseconds before the time <b>now</b> (in truncated
- * milliseconds since the epoch). If the connection has no data, treat
+ * absolute monotonic msec). If the connection has no data, treat
* it as having age zero.
**/
static uint32_t
@@ -2145,7 +2147,6 @@ circuits_handle_oom(size_t current_allocation)
size_t mem_recovered=0;
int n_circuits_killed=0;
int n_dirconns_killed=0;
- struct timeval now;
uint32_t now_ms;
log_notice(LD_GENERAL, "We're low on memory. Killing circuits with "
"over-long queues. (This behavior is controlled by "
@@ -2159,8 +2160,7 @@ circuits_handle_oom(size_t current_allocation)
mem_to_recover = current_allocation - mem_target;
}
- tor_gettimeofday_cached_monotonic(&now);
- now_ms = (uint32_t)tv_to_msec(&now);
+ now_ms = (uint32_t)monotime_coarse_absolute_msec();
circlist = circuit_get_global_list();
SMARTLIST_FOREACH_BEGIN(circlist, circuit_t *, circ) {
diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c
index e1bf9e328c..036fb9570d 100644
--- a/src/or/circuitmux.c
+++ b/src/or/circuitmux.c
@@ -4,49 +4,20 @@
/**
* \file circuitmux.c
* \brief Circuit mux/cell selection abstraction
- **/
-
-#include "or.h"
-#include "channel.h"
-#include "circuitlist.h"
-#include "circuitmux.h"
-#include "relay.h"
-
-/*
- * Private typedefs for circuitmux.c
- */
-
-/*
- * Map of muxinfos for circuitmux_t to use; struct is defined below (name
- * of struct must match HT_HEAD line).
- */
-typedef struct chanid_circid_muxinfo_map chanid_circid_muxinfo_map_t;
-
-/*
- * Hash table entry (yeah, calling it chanid_circid_muxinfo_s seems to
- * break the hash table code).
- */
-typedef struct chanid_circid_muxinfo_t chanid_circid_muxinfo_t;
-
-/*
- * Anything the mux wants to store per-circuit in the map; right now just
- * a count of queued cells.
- */
-
-typedef struct circuit_muxinfo_s circuit_muxinfo_t;
-
-/*
- * Structures for circuitmux.c
- */
-
-/*
- * A circuitmux is a collection of circuits; it tracks which subset
- * of the attached circuits are 'active' (i.e., have cells available
- * to transmit) and how many cells on each. It expoes three distinct
+ *
+ * A circuitmux is responsible for <b>MU</b>ltiple<b>X</b>ing all of the
+ * circuits that are writing on a single channel. It keeps track of which of
+ * these circuits has something to write (aka, "active" circuits), and which
+ * one should write next. A circuitmux corresponds 1:1 with a channel.
+ *
+ * There can be different implementations of the circuitmux's rules (which
+ * decide which circuit is next to write).
+ *
+ * A circuitmux exposes three distinct
* interfaces to other components:
*
* To channels, which each have a circuitmux_t, the supported operations
- * are:
+ * (invoked from relay.c) are:
*
* circuitmux_get_first_active_circuit():
*
@@ -74,7 +45,9 @@ typedef struct circuit_muxinfo_s circuit_muxinfo_t;
*
* circuitmux_set_num_cells():
*
- * Set the circuitmux's cell counter for this circuit.
+ * Set the circuitmux's cell counter for this circuit. One of
+ * circuitmuc_clear_num_cells() or circuitmux_set_num_cells() MUST be
+ * called when the number of cells queued on a circuit changes.
*
* See circuitmux.h for the circuitmux_policy_t data structure, which contains
* a table of function pointers implementing a circuit selection policy, and
@@ -94,7 +67,39 @@ typedef struct circuit_muxinfo_s circuit_muxinfo_t;
*
* Install a policy on a circuitmux_t; the appropriate callbacks will be
* made to attach all existing circuits to the new policy.
- *
+ **/
+
+#include "or.h"
+#include "channel.h"
+#include "circuitlist.h"
+#include "circuitmux.h"
+#include "relay.h"
+
+/*
+ * Private typedefs for circuitmux.c
+ */
+
+/*
+ * Map of muxinfos for circuitmux_t to use; struct is defined below (name
+ * of struct must match HT_HEAD line).
+ */
+typedef struct chanid_circid_muxinfo_map chanid_circid_muxinfo_map_t;
+
+/*
+ * Hash table entry (yeah, calling it chanid_circid_muxinfo_s seems to
+ * break the hash table code).
+ */
+typedef struct chanid_circid_muxinfo_t chanid_circid_muxinfo_t;
+
+/*
+ * Anything the mux wants to store per-circuit in the map; right now just
+ * a count of queued cells.
+ */
+
+typedef struct circuit_muxinfo_s circuit_muxinfo_t;
+
+/*
+ * Structures for circuitmux.c
*/
struct circuitmux_s {
@@ -362,7 +367,7 @@ HT_HEAD(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t);
/* Emit a bunch of hash table stuff */
HT_PROTOTYPE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node,
- chanid_circid_entry_hash, chanid_circid_entries_eq);
+ chanid_circid_entry_hash, chanid_circid_entries_eq)
HT_GENERATE2(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node,
chanid_circid_entry_hash, chanid_circid_entries_eq, 0.6,
tor_reallocarray_, tor_free_)
diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c
index b784a140ac..5c2ebde73b 100644
--- a/src/or/circuitmux_ewma.c
+++ b/src/or/circuitmux_ewma.c
@@ -4,10 +4,34 @@
/**
* \file circuitmux_ewma.c
* \brief EWMA circuit selection as a circuitmux_t policy
+ *
+ * The "EWMA" in this module stands for the "exponentially weighted moving
+ * average" of the number of cells sent on each circuit. The goal is to
+ * prioritize cells on circuits that have been quiet recently, by looking at
+ * those that have sent few cells over time, prioritizing recent times
+ * more than older ones.
+ *
+ * Specifically, a cell sent at time "now" has weight 1, but a time X ticks
+ * before now has weight ewma_scale_factor ^ X , where ewma_scale_factor is
+ * between 0.0 and 1.0.
+ *
+ * For efficiency, we do not re-scale these averages every time we send a
+ * cell: that would be horribly inefficient. Instead, we we keep the cell
+ * count on all circuits on the same circuitmux scaled relative to a single
+ * tick. When we add a new cell, we scale its weight depending on the time
+ * that has elapsed since the tick. We do re-scale the circuits on the
+ * circuitmux periodically, so that we don't overflow double.
+ *
+ *
+ * This module should be used through the interfaces in circuitmux.c, which it
+ * implements.
+ *
**/
#define TOR_CIRCUITMUX_EWMA_C_
+#include "orconfig.h"
+
#include <math.h>
#include "or.h"
@@ -26,9 +50,10 @@
/*** Some useful constant #defines ***/
-/*DOCDOC*/
+/** Any halflife smaller than this number of seconds is considered to be
+ * "disabled". */
#define EPSILON 0.00001
-/*DOCDOC*/
+/** The natural logarithm of 0.5. */
#define LOG_ONEHALF -0.69314718055994529
/*** EWMA structures ***/
diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h
index 58aac1e196..a7b8961ac6 100644
--- a/src/or/circuitmux_ewma.h
+++ b/src/or/circuitmux_ewma.h
@@ -12,13 +12,8 @@
#include "or.h"
#include "circuitmux.h"
-/* Everything but circuitmux_ewma.c should see this extern */
-#ifndef TOR_CIRCUITMUX_EWMA_C_
-
extern circuitmux_policy_t ewma_policy;
-#endif /* !(TOR_CIRCUITMUX_EWMA_C_) */
-
/* Externally visible EWMA functions */
int cell_ewma_enabled(void);
unsigned int cell_ewma_get_tick(void);
diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c
index 9ac2d565b5..418acc0024 100644
--- a/src/or/circuitstats.c
+++ b/src/or/circuitstats.c
@@ -9,6 +9,18 @@
*
* \brief Maintains and analyzes statistics about circuit built times, so we
* can tell how long we may need to wait for a fast circuit to be constructed.
+ *
+ * By keeping these statistics, a client learns when it should time out a slow
+ * circuit for being too slow, and when it should keep a circuit open in order
+ * to wait for it to complete.
+ *
+ * The information here is kept in a circuit_built_times_t structure, which is
+ * currently a singleton, but doesn't need to be. It's updated by calls to
+ * circuit_build_times_count_timeout() from circuituse.c,
+ * circuit_build_times_count_close() from circuituse.c, and
+ * circuit_build_times_add_time() from circuitbuild.c, and inspected by other
+ * calls into this module, mostly from circuitlist.c. Observations are
+ * persisted to disk via the or_state_t-related calls.
*/
#define CIRCUITSTATS_PRIVATE
@@ -21,6 +33,8 @@
#include "control.h"
#include "main.h"
#include "networkstatus.h"
+#include "rendclient.h"
+#include "rendservice.h"
#include "statefile.h"
#undef log
@@ -81,12 +95,14 @@ get_circuit_build_timeout_ms(void)
/**
* This function decides if CBT learning should be disabled. It returns
- * true if one or more of the following four conditions are met:
+ * true if one or more of the following conditions are met:
*
* 1. If the cbtdisabled consensus parameter is set.
* 2. If the torrc option LearnCircuitBuildTimeout is false.
* 3. If we are a directory authority
* 4. If we fail to write circuit build time history to our state file.
+ * 5. If we are compiled or configured in Tor2web mode
+ * 6. If we are configured in Single Onion mode
*/
int
circuit_build_times_disabled(void)
@@ -94,14 +110,30 @@ circuit_build_times_disabled(void)
if (unit_tests) {
return 0;
} else {
+ const or_options_t *options = get_options();
int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
0, 0, 1);
- int config_disabled = !get_options()->LearnCircuitBuildTimeout;
- int dirauth_disabled = get_options()->AuthoritativeDir;
+ int config_disabled = !options->LearnCircuitBuildTimeout;
+ int dirauth_disabled = options->AuthoritativeDir;
int state_disabled = did_last_state_file_write_fail() ? 1 : 0;
+ /* LearnCircuitBuildTimeout and Tor2web/Single Onion Services are
+ * incompatible in two ways:
+ *
+ * - LearnCircuitBuildTimeout results in a low CBT, which
+ * Single Onion use of one-hop intro and rendezvous circuits lowers
+ * much further, producing *far* too many timeouts.
+ *
+ * - The adaptive CBT code does not update its timeout estimate
+ * using build times for single-hop circuits.
+ *
+ * If we fix both of these issues someday, we should test
+ * these modes with LearnCircuitBuildTimeout on again. */
+ int tor2web_disabled = rend_client_allow_non_anonymous_connection(options);
+ int single_onion_disabled = rend_service_allow_non_anonymous_connection(
+ options);
if (consensus_disabled || config_disabled || dirauth_disabled ||
- state_disabled) {
+ state_disabled || tor2web_disabled || single_onion_disabled) {
#if 0
log_debug(LD_CIRC,
"CircuitBuildTime learning is disabled. "
@@ -309,7 +341,6 @@ circuit_build_times_min_timeout(void)
"circuit_build_times_min_timeout() called, cbtmintimeout is %d",
num);
}
-
return num;
}
@@ -469,7 +500,7 @@ circuit_build_times_get_initial_timeout(void)
*/
if (!unit_tests && get_options()->CircuitBuildTimeout) {
timeout = get_options()->CircuitBuildTimeout*1000;
- if (get_options()->LearnCircuitBuildTimeout &&
+ if (!circuit_build_times_disabled() &&
timeout < circuit_build_times_min_timeout()) {
log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
circuit_build_times_min_timeout()/1000);
@@ -578,18 +609,18 @@ circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n)
* array is full.
*/
int
-circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time)
+circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t btime)
{
- if (time <= 0 || time > CBT_BUILD_TIME_MAX) {
+ if (btime <= 0 || btime > CBT_BUILD_TIME_MAX) {
log_warn(LD_BUG, "Circuit build time is too large (%u)."
- "This is probably a bug.", time);
+ "This is probably a bug.", btime);
tor_fragile_assert();
return -1;
}
- log_debug(LD_CIRC, "Adding circuit build time %u", time);
+ log_debug(LD_CIRC, "Adding circuit build time %u", btime);
- cbt->circuit_build_times[cbt->build_times_idx] = time;
+ cbt->circuit_build_times[cbt->build_times_idx] = btime;
cbt->build_times_idx = (cbt->build_times_idx + 1) % CBT_NCIRCUITS_TO_OBSERVE;
if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE)
cbt->total_build_times++;
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 2c724dee05..84574cd5b9 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -203,7 +203,7 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob,
timercmp(&a->timestamp_began, &b->timestamp_began, OP_GT))
return 1;
if (ob->build_state->is_internal)
- /* XXX023 what the heck is this internal thing doing here. I
+ /* XXXX++ what the heck is this internal thing doing here. I
* think we can get rid of it. circuit_is_acceptable() already
* makes sure that is_internal is exactly what we need it to
* be. -RD */
@@ -222,7 +222,7 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob,
break;
}
- /* XXXX023 Maybe this check should get a higher priority to avoid
+ /* XXXX Maybe this check should get a higher priority to avoid
* using up circuits too rapidly. */
a_bits = connection_edge_update_circuit_isolation(conn,
@@ -788,6 +788,8 @@ static time_t last_expired_clientside_circuits = 0;
* As a diagnostic for bug 8387, log information about how many one-hop
* circuits we have around that have been there for at least <b>age</b>
* seconds. Log a few of them.
+ * Ignores Single Onion Service intro and Tor2web redezvous circuits, they are
+ * expected to be long-term one-hop circuits.
*/
void
circuit_log_ancient_one_hop_circuits(int age)
@@ -797,6 +799,7 @@ circuit_log_ancient_one_hop_circuits(int age)
time_t cutoff = now - age;
int n_found = 0;
smartlist_t *log_these = smartlist_new();
+ const or_options_t *options = get_options();
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
const origin_circuit_t *ocirc;
@@ -804,6 +807,19 @@ circuit_log_ancient_one_hop_circuits(int age)
continue;
if (circ->timestamp_created.tv_sec >= cutoff)
continue;
+ /* Single Onion Services deliberately make long term one-hop intro
+ * connections. We only ignore active intro point connections, if we take
+ * a long time establishing, that's worth logging. */
+ if (rend_service_allow_non_anonymous_connection(options) &&
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO)
+ continue;
+ /* Tor2web deliberately makes long term one-hop rend connections,
+ * particularly when Tor2webRendezvousPoints is used. We only ignore
+ * active rend point connections, if we take a long time to rendezvous,
+ * that's worth logging. */
+ if (rend_client_allow_non_anonymous_connection(options) &&
+ circ->purpose == CIRCUIT_PURPOSE_C_REND_JOINED)
+ continue;
ocirc = CONST_TO_ORIGIN_CIRCUIT(circ);
if (ocirc->build_state && ocirc->build_state->onehop_tunnel) {
@@ -839,7 +855,7 @@ circuit_log_ancient_one_hop_circuits(int age)
tor_asprintf(&dirty, "Dirty since %s (%ld seconds vs %ld-second cutoff)",
dirty_since, (long)(now - circ->timestamp_dirty),
- (long) get_options()->MaxCircuitDirtiness);
+ (long) options->MaxCircuitDirtiness);
} else {
dirty = tor_strdup("Not marked dirty");
}
@@ -1067,7 +1083,7 @@ circuit_predict_and_launch_new(void)
if (rep_hist_get_predicted_internal(now, &hidserv_needs_uptime,
&hidserv_needs_capacity) &&
((num_uptime_internal<2 && hidserv_needs_uptime) ||
- num_internal<2)
+ num_internal<3)
&& router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
if (hidserv_needs_uptime)
flags |= CIRCLAUNCH_NEED_UPTIME;
@@ -1936,8 +1952,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
return -1;
}
} else {
- /* XXXX024 Duplicates checks in connection_ap_handshake_attach_circuit:
- * refactor into a single function? */
+ /* 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);
int opt = conn->chosen_exit_optional;
if (node && !connection_ap_can_use_exit(conn, node)) {
@@ -2028,7 +2044,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
char *hexdigest = conn->chosen_exit_name+1;
tor_addr_t addr;
if (strlen(hexdigest) < HEX_DIGEST_LEN ||
- base16_decode(digest,DIGEST_LEN,hexdigest,HEX_DIGEST_LEN)<0) {
+ base16_decode(digest,DIGEST_LEN,
+ hexdigest,HEX_DIGEST_LEN) != DIGEST_LEN) {
log_info(LD_DIR, "Broken exit digest on tunnel conn. Closing.");
return -1;
}
@@ -2146,10 +2163,11 @@ optimistic_data_enabled(void)
{
const or_options_t *options = get_options();
if (options->OptimisticData < 0) {
- /* XXX023 consider having auto default to 1 rather than 0 before
- * the 0.2.3 branch goes stable. See bug 3617. -RD */
+ /* Note: this default was 0 before #18815 was merged. We can't take the
+ * parameter out of the consensus until versions before that are all
+ * obsolete. */
const int32_t enabled =
- networkstatus_get_param(NULL, "UseOptimisticData", 0, 0, 1);
+ networkstatus_get_param(NULL, "UseOptimisticData", /*default*/ 1, 0, 1);
return (int)enabled;
}
return options->OptimisticData;
@@ -2415,7 +2433,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
/* find the circuit that we should use, if there is one. */
retval = circuit_get_open_circ_or_launch(
conn, CIRCUIT_PURPOSE_C_GENERAL, &circ);
- if (retval < 1) // XXX023 if we totally fail, this still returns 0 -RD
+ if (retval < 1) // XXXX++ if we totally fail, this still returns 0 -RD
return retval;
log_debug(LD_APP|LD_CIRC,
@@ -2590,7 +2608,7 @@ mark_circuit_unusable_for_new_conns(origin_circuit_t *circ)
const or_options_t *options = get_options();
tor_assert(circ);
- /* XXXX025 This is a kludge; we're only keeping it around in case there's
+ /* XXXX This is a kludge; we're only keeping it around in case there's
* something that doesn't check unusable_for_new_conns, and to avoid
* deeper refactoring of our expiration logic. */
if (! circ->base_.timestamp_dirty)
diff --git a/src/or/command.c b/src/or/command.c
index 5ad92bed1e..5866c386e4 100644
--- a/src/or/command.c
+++ b/src/or/command.c
@@ -7,6 +7,26 @@
/**
* \file command.c
* \brief Functions for processing incoming cells.
+ *
+ * When we receive a cell from a client or a relay, it arrives on some
+ * channel, and tells us what to do with it. In this module, we dispatch based
+ * on the cell type using the functions command_process_cell() and
+ * command_process_var_cell(), and deal with the cell accordingly. (These
+ * handlers are installed on a channel with the command_setup_channel()
+ * function.)
+ *
+ * Channels have a chance to handle some cell types on their own before they
+ * are ever passed here --- typically, they do this for cells that are
+ * specific to a given channel type. For example, in channeltls.c, the cells
+ * for the initial connection handshake are handled before we get here. (Of
+ * course, the fact that there _is_ only one channel type for now means that
+ * we may have gotten the factoring wrong here.)
+ *
+ * Handling other cell types is mainly farmed off to other modules, after
+ * initial sanity-checking. CREATE* cells are handled ultimately in onion.c,
+ * CREATED* cells trigger circuit creation in circuitbuild.c, DESTROY cells
+ * are handled here (since they're simple), and RELAY cells, in all their
+ * complexity, are passed off to relay.c.
**/
/* In-points to command.c:
diff --git a/src/or/config.c b/src/or/config.c
index 557790a81e..ddf49b037e 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -18,6 +18,7 @@
#include "circuitlist.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
+#include "circuitstats.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
@@ -65,11 +66,11 @@
#include <systemd/sd-daemon.h>
#endif
-/* From main.c */
-extern int quiet_level;
-
/* Prefix used to indicate a Unix socket in a FooPort configuration. */
static const char unix_socket_prefix[] = "unix:";
+/* Prefix used to indicate a Unix socket with spaces in it, in a FooPort
+ * configuration. */
+static const char unix_q_socket_prefix[] = "unix:\"";
/** A list of abbreviations and aliases to map command-line options, obsolete
* option names, or alternative option names, to their current values. */
@@ -99,7 +100,7 @@ static config_abbrev_t option_abbrevs_[] = {
{ "BandwidthRateBytes", "BandwidthRate", 0, 0},
{ "BandwidthBurstBytes", "BandwidthBurst", 0, 0},
{ "DirFetchPostPeriod", "StatusFetchPeriod", 0, 0},
- { "DirServer", "DirAuthority", 0, 0}, /* XXXX024 later, make this warn? */
+ { "DirServer", "DirAuthority", 0, 0}, /* XXXX later, make this warn? */
{ "MaxConn", "ConnLimit", 0, 1},
{ "MaxMemInCellQueues", "MaxMemInQueues", 0, 0},
{ "ORBindAddress", "ORListenAddress", 0, 0},
@@ -116,7 +117,6 @@ static config_abbrev_t option_abbrevs_[] = {
{ "BridgeAuthoritativeDirectory", "BridgeAuthoritativeDir", 0, 0},
{ "HashedControlPassword", "__HashedControlSessionPassword", 1, 0},
{ "VirtualAddrNetwork", "VirtualAddrNetworkIPv4", 0, 0},
- { "_UseFilteringSSLBufferevents", "UseFilteringSSLBufferevents", 0, 1},
{ NULL, NULL, 0, 0},
};
@@ -215,6 +215,7 @@ static config_var_t option_vars_[] = {
V(CountPrivateBandwidth, BOOL, "0"),
V(DataDirectory, FILENAME, NULL),
V(DataDirectoryGroupReadable, BOOL, "0"),
+ V(DisableOOSCheck, BOOL, "1"),
V(DisableNetwork, BOOL, "0"),
V(DirAllowPrivateAddresses, BOOL, "0"),
V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"),
@@ -228,7 +229,7 @@ static config_var_t option_vars_[] = {
V(DirAuthorityFallbackRate, DOUBLE, "1.0"),
V(DisableAllSwap, BOOL, "0"),
V(DisableDebuggerAttachment, BOOL, "1"),
- V(DisableIOCP, BOOL, "1"),
+ OBSOLETE("DisableIOCP"),
OBSOLETE("DisableV2DirectoryInfo_"),
OBSOLETE("DynamicDHGroups"),
VPORT(DNSPort, LINELIST, NULL),
@@ -247,6 +248,7 @@ static config_var_t option_vars_[] = {
V(ExitNodes, ROUTERSET, NULL),
V(ExitPolicy, LINELIST, NULL),
V(ExitPolicyRejectPrivate, BOOL, "1"),
+ V(ExitPolicyRejectLocalInterfaces, BOOL, "0"),
V(ExitPortStatistics, BOOL, "0"),
V(ExtendAllowPrivateAddresses, BOOL, "0"),
V(ExitRelay, AUTOBOOL, "auto"),
@@ -299,6 +301,8 @@ static config_var_t option_vars_[] = {
V(HidServAuth, LINELIST, NULL),
V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
+ V(HiddenServiceSingleHopMode, BOOL, "0"),
+ V(HiddenServiceNonAnonymousMode,BOOL, "0"),
V(HTTPProxy, STRING, NULL),
V(HTTPProxyAuthenticator, STRING, NULL),
V(HTTPSProxy, STRING, NULL),
@@ -328,6 +332,7 @@ static config_var_t option_vars_[] = {
VAR("MaxMemInQueues", MEMUNIT, MaxMemInQueues_raw, "0"),
OBSOLETE("MaxOnionsPending"),
V(MaxOnionQueueDelay, MSEC_INTERVAL, "1750 msec"),
+ V(MaxUnparseableDescSizeToLog, MEMUNIT, "10 MB"),
V(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, "500"),
V(MyFamily, STRING, NULL),
V(NewCircuitPeriod, INTERVAL, "30 seconds"),
@@ -435,13 +440,14 @@ static config_var_t option_vars_[] = {
OBSOLETE("TunnelDirConns"),
V(UpdateBridgesFromAuthority, BOOL, "0"),
V(UseBridges, BOOL, "0"),
- V(UseEntryGuards, BOOL, "1"),
+ VAR("UseEntryGuards", BOOL, UseEntryGuards_option, "1"),
V(UseEntryGuardsAsDirGuards, BOOL, "1"),
V(UseGuardFraction, AUTOBOOL, "auto"),
V(UseMicrodescriptors, AUTOBOOL, "auto"),
- V(UseNTorHandshake, AUTOBOOL, "1"),
+ OBSOLETE("UseNTorHandshake"),
V(User, STRING, NULL),
- V(UserspaceIOCPBuffers, BOOL, "0"),
+ OBSOLETE("UserspaceIOCPBuffers"),
+ V(AuthDirSharedRandomness, BOOL, "1"),
OBSOLETE("V1AuthoritativeDirectory"),
OBSOLETE("V2AuthoritativeDirectory"),
VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"),
@@ -461,7 +467,8 @@ static config_var_t option_vars_[] = {
V(VirtualAddrNetworkIPv4, STRING, "127.192.0.0/10"),
V(VirtualAddrNetworkIPv6, STRING, "[FE80::]/10"),
V(WarnPlaintextPorts, CSV, "23,109,110,143"),
- V(UseFilteringSSLBufferevents, BOOL, "0"),
+ OBSOLETE("UseFilteringSSLBufferevents"),
+ OBSOLETE("__UseFilteringSSLBufferevents"),
VAR("__ReloadTorrcOnSIGHUP", BOOL, ReloadTorrcOnSIGHUP, "1"),
VAR("__AllDirActionsPrivate", BOOL, AllDirActionsPrivate, "0"),
VAR("__DisablePredictedCircuits",BOOL,DisablePredictedCircuits, "0"),
@@ -494,7 +501,7 @@ static config_var_t option_vars_[] = {
* When clients have authorities and fallbacks available, they use these
* schedules: (we stagger the times to avoid thundering herds) */
V(ClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL,
- "10, 11, 3600, 10800, 25200, 54000, 111600, 262800" /* 3 days + 1 hour */),
+ "6, 11, 3600, 10800, 25200, 54000, 111600, 262800" /* 3 days + 1 hour */),
V(ClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL,
"0, 1, 4, 11, 3600, 10800, 25200, 54000, 111600, 262800"),
/* When clients only have authorities available, they use this schedule: */
@@ -505,7 +512,7 @@ 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, "3600, 900, 900, 3600"),
+ V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "1200, 900, 900, 3600"),
V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"),
V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"),
V(TestingConsensusMaxDownloadTries, UINT, "8"),
@@ -545,7 +552,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"),
+ V(ClientDNSRejectInternalAddresses, BOOL,"0"), // deprecated in 0.2.9.2-alpha
V(ClientRejectInternalAddresses, BOOL, "0"),
V(CountPrivateBandwidth, BOOL, "1"),
V(ExitPolicyRejectPrivate, BOOL, "0"),
@@ -588,6 +595,44 @@ static const config_var_t testing_tor_network_defaults[] = {
#undef V
#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." },
+ { "AllowInvalidNodes", "There is no reason to enable this option; at best "
+ "it will make you easier to track." },
+ { "AllowSingleHopCircuits", "Almost no relays actually allow single-hop "
+ "exits, making this option pointless." },
+ { "AllowSingleHopExits", "Turning this on will make your relay easier "
+ "to abuse." },
+ { "ClientDNSRejectInternalAddresses", "Turning this on makes your client "
+ "easier to fingerprint, and may open you to esoteric attacks." },
+ { "ExcludeSingleHopRelays", "Turning it on makes your client easier to "
+ "fingerprint." },
+ { "FastFirstHopPK", "Changing this option does not make your client more "
+ "secure, but does make it easier to fingerprint." },
+ { "CloseHSClientCircuitsImmediatelyOnTimeout", "This option makes your "
+ "client easier to fingerprint." },
+ { "CloseHSServiceRendCircuitsImmediatelyOnTimeout", "This option makes "
+ "your hidden services easier to fingerprint." },
+ { "WarnUnsafeSocks", "Changing this option makes it easier for you "
+ "to accidentally lose your anonymity by leaking DNS information" },
+ { "TLSECGroup", "The default is a nice secure choice; the other option "
+ "is less secure." },
+ { "ControlListenAddress", "Use ControlPort instead." },
+ { "DirListenAddress", "Use DirPort instead, possibly with the "
+ "NoAdvertise sub-option" },
+ { "DNSListenAddress", "Use DNSPort instead." },
+ { "SocksListenAddress", "Use SocksPort instead." },
+ { "TransListenAddress", "Use TransPort instead." },
+ { "NATDListenAddress", "Use NATDPort instead." },
+ { "ORListenAddress", "Use ORPort instead, possibly with the "
+ "NoAdvertise sub-option" },
+ /* End of options deprecated since 0.2.9.2-alpha. */
+
+ { NULL, NULL }
+};
+
#ifdef _WIN32
static char *get_windows_conf_root(void);
#endif
@@ -636,6 +681,7 @@ STATIC config_format_t options_format = {
OR_OPTIONS_MAGIC,
STRUCT_OFFSET(or_options_t, magic_),
option_abbrevs_,
+ option_deprecation_notes_,
option_vars_,
options_validate_cb,
NULL
@@ -746,7 +792,7 @@ set_options(or_options_t *new_val, char **msg)
}
if (old_options != global_options)
- config_free(&options_format, old_options);
+ or_options_free(old_options);
return 0;
}
@@ -1337,6 +1383,35 @@ options_act_reversible(const or_options_t *old_options, char **msg)
connection_mark_for_close(conn);
}
});
+
+ if (set_conn_limit) {
+ /*
+ * If we adjusted the conn limit, recompute the OOS threshold too
+ *
+ * How many possible sockets to keep in reserve? If we have lots of
+ * possible sockets, keep this below a limit and set ConnLimit_high_thresh
+ * very close to ConnLimit_, but if ConnLimit_ is low, shrink it in
+ * proportion.
+ *
+ * Somewhat arbitrarily, set socks_in_reserve to 5% of ConnLimit_, but
+ * cap it at 64.
+ */
+ int socks_in_reserve = options->ConnLimit_ / 20;
+ if (socks_in_reserve > 64) socks_in_reserve = 64;
+
+ options->ConnLimit_high_thresh = options->ConnLimit_ - socks_in_reserve;
+ options->ConnLimit_low_thresh = (options->ConnLimit_ / 4) * 3;
+ log_info(LD_GENERAL,
+ "Recomputed OOS thresholds: ConnLimit %d, ConnLimit_ %d, "
+ "ConnLimit_high_thresh %d, ConnLimit_low_thresh %d",
+ options->ConnLimit, options->ConnLimit_,
+ options->ConnLimit_high_thresh,
+ options->ConnLimit_low_thresh);
+
+ /* Give the OOS handler a chance with the new thresholds */
+ connection_check_oos(get_n_open_sockets(), 0);
+ }
+
goto done;
rollback:
@@ -1492,10 +1567,10 @@ options_act(const or_options_t *old_options)
if (consider_adding_dir_servers(options, old_options) < 0)
return -1;
-#ifdef NON_ANONYMOUS_MODE_ENABLED
- log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a "
- "non-anonymous mode. It will provide NO ANONYMITY.");
-#endif
+ if (rend_non_anonymous_mode_enabled(options)) {
+ log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run "
+ "in a non-anonymous mode. It will provide NO ANONYMITY.");
+ }
#ifdef ENABLE_TOR2WEB_MODE
/* LCOV_EXCL_START */
@@ -1658,7 +1733,7 @@ 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()<0) {
+ if (rend_service_load_all_keys(NULL)<0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
return -1;
}
@@ -1677,17 +1752,6 @@ options_act(const or_options_t *old_options)
if (accounting_is_enabled(options))
configure_accounting(time(NULL));
-#ifdef USE_BUFFEREVENTS
- /* If we're using the bufferevents implementation and our rate limits
- * changed, we need to tell the rate-limiting system about it. */
- if (!old_options ||
- old_options->BandwidthRate != options->BandwidthRate ||
- old_options->BandwidthBurst != options->BandwidthBurst ||
- old_options->RelayBandwidthRate != options->RelayBandwidthRate ||
- old_options->RelayBandwidthBurst != options->RelayBandwidthBurst)
- connection_bucket_init();
-#endif
-
old_ewma_enabled = cell_ewma_enabled();
/* Change the cell EWMA settings */
cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus());
@@ -2002,11 +2066,6 @@ static const struct {
{ "--list-fingerprint", TAKES_NO_ARGUMENT },
{ "--keygen", TAKES_NO_ARGUMENT },
{ "--newpass", TAKES_NO_ARGUMENT },
-#if 0
-/* XXXX028: This is not working yet in 0.2.7, so disabling with the
- * minimal code modification. */
- { "--master-key", ARGUMENT_NECESSARY },
-#endif
{ "--no-passphrase", TAKES_NO_ARGUMENT },
{ "--passphrase-fd", ARGUMENT_NECESSARY },
{ "--verify-config", TAKES_NO_ARGUMENT },
@@ -2018,6 +2077,7 @@ static const struct {
{ "-h", TAKES_NO_ARGUMENT },
{ "--help", TAKES_NO_ARGUMENT },
{ "--list-torrc-options", TAKES_NO_ARGUMENT },
+ { "--list-deprecated-options",TAKES_NO_ARGUMENT },
{ "--nt-service", TAKES_NO_ARGUMENT },
{ "-nt-service", TAKES_NO_ARGUMENT },
{ NULL, 0 },
@@ -2080,7 +2140,7 @@ config_parse_commandline(int argc, char **argv, int ignore_errors,
if (want_arg == ARGUMENT_NECESSARY && is_last) {
if (ignore_errors) {
- arg = strdup("");
+ arg = tor_strdup("");
} else {
log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.",
argv[i]);
@@ -2154,31 +2214,30 @@ option_get_assignment(const or_options_t *options, const char *key)
* what went wrong.
*/
setopt_err_t
-options_trial_assign(config_line_t *list, int use_defaults,
- int clear_first, char **msg)
+options_trial_assign(config_line_t *list, unsigned flags, char **msg)
{
int r;
or_options_t *trial_options = config_dup(&options_format, get_options());
if ((r=config_assign(&options_format, trial_options,
- list, use_defaults, clear_first, msg)) < 0) {
- config_free(&options_format, trial_options);
+ list, flags, msg)) < 0) {
+ or_options_free(trial_options);
return r;
}
if (options_validate(get_options_mutable(), trial_options,
global_default_options, 1, msg) < 0) {
- config_free(&options_format, trial_options);
+ or_options_free(trial_options);
return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */
}
if (options_transition_allowed(get_options(), trial_options, msg) < 0) {
- config_free(&options_format, trial_options);
+ or_options_free(trial_options);
return SETOPT_ERR_TRANSITION;
}
if (set_options(trial_options, msg)<0) {
- config_free(&options_format, trial_options);
+ or_options_free(trial_options);
return SETOPT_ERR_SETTING;
}
@@ -2204,7 +2263,6 @@ static void
list_torrc_options(void)
{
int i;
- smartlist_t *lines = smartlist_new();
for (i = 0; option_vars_[i].name; ++i) {
const config_var_t *var = &option_vars_[i];
if (var->type == CONFIG_TYPE_OBSOLETE ||
@@ -2212,7 +2270,16 @@ list_torrc_options(void)
continue;
printf("%s\n", var->name);
}
- smartlist_free(lines);
+}
+
+/** Print all deprecated but non-obsolete torrc options. */
+static void
+list_deprecated_options(void)
+{
+ const config_deprecation_t *d;
+ for (d = option_deprecation_notes_; d->name; ++d) {
+ printf("%s\n", d->name);
+ }
}
/** Last value actually set by resolve_my_address. */
@@ -2232,6 +2299,14 @@ reset_last_resolved_addr(void)
last_resolved_addr = 0;
}
+/* Return true if <b>options</b> is using the default authorities, and false
+ * if any authority-related option has been overridden. */
+int
+using_default_dir_authorities(const or_options_t *options)
+{
+ return (!options->DirAuthorities && !options->AlternateDirAuthority);
+}
+
/**
* Attempt getting our non-local (as judged by tor_addr_is_internal()
* function) IP address using following techniques, listed in
@@ -2391,7 +2466,7 @@ resolve_my_address(int warn_severity, const or_options_t *options,
addr_string = tor_dup_ip(addr);
if (tor_addr_is_internal(&myaddr, 0)) {
/* make sure we're ok with publishing an internal IP */
- if (!options->DirAuthorities && !options->AlternateDirAuthority) {
+ if (using_default_dir_authorities(options)) {
/* if they are using the default authorities, disallow internal IPs
* always. */
log_fn(warn_severity, LD_CONFIG,
@@ -2487,7 +2562,6 @@ is_local_addr, (const tor_addr_t *addr))
if (get_options()->EnforceDistinctSubnets == 0)
return 0;
if (tor_addr_family(addr) == AF_INET) {
- /*XXXX023 IP6 what corresponds to an /24? */
uint32_t ip = tor_addr_to_ipv4h(addr);
/* It's possible that this next check will hit before the first time
@@ -2681,7 +2755,7 @@ options_validate_cb(void *old_options, void *options, void *default_options,
#define REJECT(arg) \
STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END
-#ifdef __GNUC__
+#if defined(__GNUC__) && __GNUC__ <= 3
#define COMPLAIN(args...) \
STMT_BEGIN log_warn(LD_CONFIG, args); STMT_END
#else
@@ -2739,6 +2813,71 @@ warn_about_relative_paths(or_options_t *options)
}
}
+/* Validate options related to single onion services.
+ * Modifies some options that are incompatible with single onion services.
+ * On failure returns -1, and sets *msg to an error string.
+ * Returns 0 on success. */
+STATIC int
+options_validate_single_onion(or_options_t *options, char **msg)
+{
+ /* The two single onion service options must have matching values. */
+ if (options->HiddenServiceSingleHopMode &&
+ !options->HiddenServiceNonAnonymousMode) {
+ REJECT("HiddenServiceSingleHopMode does not provide any server anonymity. "
+ "It must be used with HiddenServiceNonAnonymousMode set to 1.");
+ }
+ if (options->HiddenServiceNonAnonymousMode &&
+ !options->HiddenServiceSingleHopMode) {
+ REJECT("HiddenServiceNonAnonymousMode does not provide any server "
+ "anonymity. It must be used with HiddenServiceSingleHopMode set to "
+ "1.");
+ }
+
+ /* Now that we've checked that the two options are consistent, we can safely
+ * call the rend_service_* functions that abstract these options. */
+
+ /* If you run an anonymous client with an active Single Onion service, the
+ * client loses anonymity. */
+ const int client_port_set = (options->SocksPort_set ||
+ options->TransPort_set ||
+ options->NATDPort_set ||
+ options->DNSPort_set);
+ if (rend_service_non_anonymous_mode_enabled(options) && client_port_set &&
+ !options->Tor2webMode) {
+ REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as "
+ "an anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or "
+ "HiddenServiceNonAnonymousMode to 0, or use the non-anonymous "
+ "Tor2webMode.");
+ }
+
+ /* If you run a hidden service in non-anonymous mode, the hidden service
+ * loses anonymity, even if SOCKSPort / Tor2web mode isn't used. */
+ if (!rend_service_non_anonymous_mode_enabled(options) &&
+ options->RendConfigLines && options->Tor2webMode) {
+ REJECT("Non-anonymous (Tor2web) mode is incompatible with using Tor as a "
+ "hidden service. Please remove all HiddenServiceDir lines, or use "
+ "a version of tor compiled without --enable-tor2web-mode, or use "
+ " HiddenServiceNonAnonymousMode.");
+ }
+
+ if (rend_service_allow_non_anonymous_connection(options)
+ && options->UseEntryGuards) {
+ /* Single Onion services only use entry guards when uploading descriptors,
+ * all other connections are one-hop. Further, Single Onions causes the
+ * hidden service code to do things which break the path bias
+ * detector, and it's far easier to turn off entry guards (and
+ * thus the path bias detector with it) than to figure out how to
+ * make path bias compatible with single onions.
+ */
+ log_notice(LD_CONFIG,
+ "HiddenServiceSingleHopMode is enabled; disabling "
+ "UseEntryGuards.");
+ options->UseEntryGuards = 0;
+ }
+
+ return 0;
+}
+
/** Return 0 if every setting in <b>options</b> is reasonable, is a
* permissible transition from <b>old_options</b>, and none of the
* testing-only settings differ from <b>default_options</b> unless in
@@ -2765,6 +2904,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
tor_assert(msg);
*msg = NULL;
+ /* Set UseEntryGuards from the configured value, before we check it below.
+ * We change UseEntryGuards whenn it's incompatible with other options,
+ * but leave UseEntryGuards_option with the original value.
+ * Always use the value of UseEntryGuards, not UseEntryGuards_option. */
+ options->UseEntryGuards = options->UseEntryGuards_option;
+
warn_about_relative_paths(options);
if (server_mode(options) &&
@@ -2794,7 +2939,8 @@ options_validate(or_options_t *old_options, or_options_t *options,
} else {
if (!is_legal_nickname(options->Nickname)) {
tor_asprintf(msg,
- "Nickname '%s' is wrong length or contains illegal characters.",
+ "Nickname '%s', nicknames must be between 1 and 19 characters "
+ "inclusive, and must contain only the characters [a-zA-Z0-9].",
options->Nickname);
return -1;
}
@@ -2861,7 +3007,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
} else if (!strcasecmp(options->TransProxyType, "ipfw")) {
#ifndef KERNEL_MAY_SUPPORT_IPFW
/* Earlier versions of OS X have ipfw */
- REJECT("ipfw is a FreeBSD-specific"
+ REJECT("ipfw is a FreeBSD-specific "
"and OS X/Darwin-specific feature.");
#else
options->TransProxyType_parsed = TPT_IPFW;
@@ -3139,10 +3285,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (options->UseBridges && options->EntryNodes)
REJECT("You cannot set both UseBridges and EntryNodes.");
- if (options->EntryNodes && !options->UseEntryGuards) {
- REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
- }
-
options->MaxMemInQueues =
compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
server_mode(options));
@@ -3233,25 +3375,11 @@ options_validate(or_options_t *old_options, or_options_t *options,
options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE;
}
-#ifdef ENABLE_TOR2WEB_MODE
- if (options->Tor2webMode && options->LearnCircuitBuildTimeout) {
- /* LearnCircuitBuildTimeout and Tor2webMode are incompatible in
- * two ways:
- *
- * - LearnCircuitBuildTimeout results in a low CBT, which
- * Tor2webMode's use of one-hop rendezvous circuits lowers
- * much further, producing *far* too many timeouts.
- *
- * - The adaptive CBT code does not update its timeout estimate
- * using build times for single-hop circuits.
- *
- * If we fix both of these issues someday, we should test
- * Tor2webMode with LearnCircuitBuildTimeout on again. */
- log_notice(LD_CONFIG,"Tor2webMode is enabled; turning "
- "LearnCircuitBuildTimeout off.");
- options->LearnCircuitBuildTimeout = 0;
- }
+ /* Check the Single Onion Service options */
+ if (options_validate_single_onion(options, msg) < 0)
+ return -1;
+#ifdef ENABLE_TOR2WEB_MODE
if (options->Tor2webMode && options->UseEntryGuards) {
/* tor2web mode clients do not (and should not) use entry guards
* in any meaningful way. Further, tor2web mode causes the hidden
@@ -3271,8 +3399,13 @@ options_validate(or_options_t *old_options, or_options_t *options,
REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode.");
}
+ if (options->EntryNodes && !options->UseEntryGuards) {
+ REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
+ }
+
if (!(options->UseEntryGuards) &&
- (options->RendConfigLines != NULL)) {
+ (options->RendConfigLines != NULL) &&
+ !rend_service_allow_non_anonymous_connection(options)) {
log_warn(LD_CONFIG,
"UseEntryGuards is disabled, but you have configured one or more "
"hidden services on this Tor instance. Your hidden services "
@@ -3295,6 +3428,17 @@ options_validate(or_options_t *old_options, or_options_t *options,
return -1;
}
+ /* Single Onion Services: non-anonymous hidden services */
+ if (rend_service_non_anonymous_mode_enabled(options)) {
+ log_warn(LD_CONFIG,
+ "HiddenServiceNonAnonymousMode is set. Every hidden service on "
+ "this tor instance is NON-ANONYMOUS. If "
+ "the HiddenServiceNonAnonymousMode option is changed, Tor will "
+ "refuse to launch hidden services from the same directories, to "
+ "protect your anonymity against config errors. This setting is "
+ "for experimental use only.");
+ }
+
if (!options->LearnCircuitBuildTimeout && options->CircuitBuildTimeout &&
options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) {
log_warn(LD_CONFIG,
@@ -3306,8 +3450,15 @@ options_validate(or_options_t *old_options, or_options_t *options,
RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT );
} else if (!options->LearnCircuitBuildTimeout &&
!options->CircuitBuildTimeout) {
- log_notice(LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but didn't "
- "a CircuitBuildTimeout. I'll pick a plausible default.");
+ int severity = LOG_NOTICE;
+ /* Be a little quieter if we've deliberately disabled
+ * LearnCircuitBuildTimeout. */
+ if (circuit_build_times_disabled()) {
+ severity = LOG_INFO;
+ }
+ log_fn(severity, LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but "
+ "didn't specify a CircuitBuildTimeout. I'll pick a plausible "
+ "default.");
}
if (options->PathBiasNoticeRate > 1.0) {
@@ -3497,10 +3648,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
}
if (server_mode(options)) {
- char *msg = NULL;
- if (have_enough_mem_for_dircache(options, 0, &msg)) {
- log_warn(LD_CONFIG, "%s", msg);
- tor_free(msg);
+ char *dircache_msg = NULL;
+ if (have_enough_mem_for_dircache(options, 0, &dircache_msg)) {
+ log_warn(LD_CONFIG, "%s", dircache_msg);
+ tor_free(dircache_msg);
}
}
@@ -4130,11 +4281,11 @@ have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
if (options->DirCache) {
if (total_mem < DIRCACHE_MIN_BANDWIDTH) {
if (options->BridgeRelay) {
- *msg = strdup("Running a Bridge with less than "
+ *msg = tor_strdup("Running a Bridge with less than "
STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
"not recommended.");
} else {
- *msg = strdup("Being a directory cache (default) with less than "
+ *msg = tor_strdup("Being a directory cache (default) with less than "
STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
"not recommended and may consume most of the available "
"resources, consider disabling this functionality by "
@@ -4143,7 +4294,7 @@ have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
}
} else {
if (total_mem >= DIRCACHE_MIN_BANDWIDTH) {
- *msg = strdup("DirCache is disabled and we are configured as a "
+ *msg = tor_strdup("DirCache is disabled and we are configured as a "
"relay. This may disqualify us from becoming a guard in the "
"future.");
}
@@ -4237,9 +4388,16 @@ options_transition_allowed(const or_options_t *old,
return -1;
}
- if (old->DisableIOCP != new_val->DisableIOCP) {
- *msg = tor_strdup("While Tor is running, changing DisableIOCP "
- "is not allowed.");
+ if (old->HiddenServiceSingleHopMode != new_val->HiddenServiceSingleHopMode) {
+ *msg = tor_strdup("While Tor is running, changing "
+ "HiddenServiceSingleHopMode is not allowed.");
+ return -1;
+ }
+
+ if (old->HiddenServiceNonAnonymousMode !=
+ new_val->HiddenServiceNonAnonymousMode) {
+ *msg = tor_strdup("While Tor is running, changing "
+ "HiddenServiceNonAnonymousMode is not allowed.");
return -1;
}
@@ -4299,6 +4457,7 @@ options_transition_affects_workers(const or_options_t *old_options,
new_options->ServerDNSSearchDomains ||
old_options->SafeLogging_ != new_options->SafeLogging_ ||
old_options->ClientOnly != new_options->ClientOnly ||
+ server_mode(old_options) != server_mode(new_options) ||
public_server_mode(old_options) != public_server_mode(new_options) ||
!config_lines_eq(old_options->Logs, new_options->Logs) ||
old_options->LogMessageDomains != new_options->LogMessageDomains)
@@ -4325,6 +4484,8 @@ options_transition_affects_descriptor(const or_options_t *old_options,
old_options->ExitRelay != new_options->ExitRelay ||
old_options->ExitPolicyRejectPrivate !=
new_options->ExitPolicyRejectPrivate ||
+ old_options->ExitPolicyRejectLocalInterfaces !=
+ new_options->ExitPolicyRejectLocalInterfaces ||
old_options->IPv6Exit != new_options->IPv6Exit ||
!config_lines_eq(old_options->ORPort_lines,
new_options->ORPort_lines) ||
@@ -4670,10 +4831,15 @@ options_init_from_torrc(int argc, char **argv)
exit(0);
}
if (config_line_find(cmdline_only_options, "--list-torrc-options")) {
- /* For documenting validating whether we've documented everything. */
+ /* For validating whether we've documented everything. */
list_torrc_options();
exit(0);
}
+ if (config_line_find(cmdline_only_options, "--list-deprecated-options")) {
+ /* For validating whether what we have deprecated really exists. */
+ list_deprecated_options();
+ exit(0);
+ }
if (config_line_find(cmdline_only_options, "--version")) {
printf("Tor version %s.\n",get_version());
@@ -4829,7 +4995,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
{
or_options_t *oldoptions, *newoptions, *newdefaultoptions=NULL;
config_line_t *cl;
- int retval, i;
+ int retval;
setopt_err_t err = SETOPT_ERR_MISC;
tor_assert(msg);
@@ -4842,7 +5008,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
newoptions->command = command;
newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
- for (i = 0; i < 2; ++i) {
+ for (int i = 0; i < 2; ++i) {
const char *body = i==0 ? cf_defaults : cf;
if (!body)
continue;
@@ -4852,7 +5018,8 @@ options_init_from_string(const char *cf_defaults, const char *cf,
err = SETOPT_ERR_PARSE;
goto err;
}
- retval = config_assign(&options_format, newoptions, cl, 0, 0, msg);
+ retval = config_assign(&options_format, newoptions, cl,
+ CAL_WARN_DEPRECATIONS, msg);
config_free_lines(cl);
if (retval < 0) {
err = SETOPT_ERR_PARSE;
@@ -4868,7 +5035,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
/* Go through command-line variables too */
retval = config_assign(&options_format, newoptions,
- global_cmdline_options, 0, 0, msg);
+ global_cmdline_options, CAL_WARN_DEPRECATIONS, msg);
if (retval < 0) {
err = SETOPT_ERR_PARSE;
goto err;
@@ -4886,19 +5053,24 @@ options_init_from_string(const char *cf_defaults, const char *cf,
* let's clean it up. -NM */
/* Change defaults. */
- int i;
- for (i = 0; testing_tor_network_defaults[i].name; ++i) {
+ for (int i = 0; testing_tor_network_defaults[i].name; ++i) {
const config_var_t *new_var = &testing_tor_network_defaults[i];
config_var_t *old_var =
config_find_option_mutable(&options_format, new_var->name);
tor_assert(new_var);
tor_assert(old_var);
old_var->initvalue = new_var->initvalue;
+
+ if ((config_find_deprecation(&options_format, new_var->name))) {
+ log_warn(LD_GENERAL, "Testing options override the deprecated "
+ "option %s. Is that intentional?",
+ new_var->name);
+ }
}
/* Clear newoptions and re-initialize them with new defaults. */
- config_free(&options_format, newoptions);
- config_free(&options_format, newdefaultoptions);
+ or_options_free(newoptions);
+ or_options_free(newdefaultoptions);
newdefaultoptions = NULL;
newoptions = tor_malloc_zero(sizeof(or_options_t));
newoptions->magic_ = OR_OPTIONS_MAGIC;
@@ -4907,7 +5079,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
/* Assign all options a second time. */
- for (i = 0; i < 2; ++i) {
+ for (int i = 0; i < 2; ++i) {
const char *body = i==0 ? cf_defaults : cf;
if (!body)
continue;
@@ -4917,7 +5089,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
err = SETOPT_ERR_PARSE;
goto err;
}
- retval = config_assign(&options_format, newoptions, cl, 0, 0, msg);
+ retval = config_assign(&options_format, newoptions, cl, 0, msg);
config_free_lines(cl);
if (retval < 0) {
err = SETOPT_ERR_PARSE;
@@ -4928,7 +5100,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
}
/* Assign command-line variables a second time too */
retval = config_assign(&options_format, newoptions,
- global_cmdline_options, 0, 0, msg);
+ global_cmdline_options, 0, msg);
if (retval < 0) {
err = SETOPT_ERR_PARSE;
goto err;
@@ -4951,14 +5123,14 @@ options_init_from_string(const char *cf_defaults, const char *cf,
err = SETOPT_ERR_SETTING;
goto err; /* frees and replaces old options */
}
- config_free(&options_format, global_default_options);
+ or_options_free(global_default_options);
global_default_options = newdefaultoptions;
return SETOPT_OK;
err:
- config_free(&options_format, newoptions);
- config_free(&options_format, newdefaultoptions);
+ or_options_free(newoptions);
+ or_options_free(newdefaultoptions);
if (*msg) {
char *old_msg = *msg;
tor_asprintf(msg, "Failed to parse/validate config: %s", old_msg);
@@ -5028,7 +5200,7 @@ config_register_addressmaps(const or_options_t *options)
/** As addressmap_register(), but detect the wildcarded status of "from" and
* "to", and do not steal a reference to <b>to</b>. */
-/* XXXX024 move to connection_edge.c */
+/* XXXX move to connection_edge.c */
int
addressmap_register_auto(const char *from, const char *to,
time_t expires,
@@ -5080,7 +5252,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options,
config_line_t *opt;
int ok;
smartlist_t *elts;
- int daemon =
+ int run_as_daemon =
#ifdef _WIN32
0;
#else
@@ -5141,7 +5313,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options,
int err = smartlist_len(elts) &&
!strcasecmp(smartlist_get(elts,0), "stderr");
if (!validate_only) {
- if (daemon) {
+ if (run_as_daemon) {
log_warn(LD_CONFIG,
"Can't log to %s with RunAsDaemon set; skipping stdout",
err?"stderr":"stdout");
@@ -5170,19 +5342,19 @@ options_init_logs(const or_options_t *old_options, or_options_t *options,
char *fname = expand_filename(smartlist_get(elts, 1));
/* Truncate if TruncateLogFile is set and we haven't seen this option
line before. */
- int truncate = 0;
+ int truncate_log = 0;
if (options->TruncateLogFile) {
- truncate = 1;
+ truncate_log = 1;
if (old_options) {
config_line_t *opt2;
for (opt2 = old_options->Logs; opt2; opt2 = opt2->next)
if (!strcmp(opt->value, opt2->value)) {
- truncate = 0;
+ truncate_log = 0;
break;
}
}
}
- if (add_file_log(severity, fname, truncate) < 0) {
+ if (add_file_log(severity, fname, truncate_log) < 0) {
log_warn(LD_CONFIG, "Couldn't open file for 'Log %s': %s",
opt->value, strerror(errno));
ok = 0;
@@ -5260,10 +5432,14 @@ bridge_line_free(bridge_line_t *bridge_line)
tor_free(bridge_line);
}
-/** Read the contents of a Bridge line from <b>line</b>. Return 0
- * if the line is well-formed, and -1 if it isn't. If
- * <b>validate_only</b> is 0, and the line is well-formed, then add
- * the bridge described in the line to our internal bridge list.
+/** Parse the contents of a string, <b>line</b>, containing a Bridge line,
+ * into a bridge_line_t.
+ *
+ * Validates that the IP:PORT, fingerprint, and SOCKS arguments (given to the
+ * Pluggable Transport, if a one was specified) are well-formed.
+ *
+ * Returns NULL If the Bridge line could not be validated, and returns a
+ * bridge_line_t containing the parsed information otherwise.
*
* Bridge line format:
* Bridge [transport] IP:PORT [id-fingerprint] [k=v] [k=v] ...
@@ -5336,7 +5512,7 @@ parse_bridge_line(const char *line)
goto err;
}
if (base16_decode(bridge_line->digest, DIGEST_LEN,
- fingerprint, HEX_DIGEST_LEN)<0) {
+ fingerprint, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_CONFIG, "Unable to decode Bridge key digest.");
goto err;
}
@@ -5779,7 +5955,7 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
} else if (!strcmpstart(flag, "weight=")) {
int ok;
const char *wstring = flag + strlen("weight=");
- weight = tor_parse_double(wstring, 0, UINT64_MAX, &ok, NULL);
+ weight = tor_parse_double(wstring, 0, (double)UINT64_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_CONFIG, "Invalid weight '%s' on DirAuthority line.",flag);
weight=1.0;
@@ -5787,7 +5963,8 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
} else if (!strcasecmpstart(flag, "v3ident=")) {
char *idstr = flag + strlen("v3ident=");
if (strlen(idstr) != HEX_DIGEST_LEN ||
- base16_decode(v3_digest, DIGEST_LEN, idstr, HEX_DIGEST_LEN)<0) {
+ base16_decode(v3_digest, DIGEST_LEN,
+ idstr, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_CONFIG, "Bad v3 identity digest '%s' on DirAuthority line",
flag);
} else {
@@ -5836,7 +6013,8 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
fingerprint, (int)strlen(fingerprint));
goto err;
}
- if (base16_decode(digest, DIGEST_LEN, fingerprint, HEX_DIGEST_LEN)<0) {
+ if (base16_decode(digest, DIGEST_LEN,
+ fingerprint, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_CONFIG, "Unable to decode DirAuthority key digest.");
goto err;
}
@@ -5904,8 +6082,8 @@ parse_dir_fallback_line(const char *line,
orport = (int)tor_parse_long(cp+strlen("orport="), 10,
1, 65535, &ok, NULL);
} else if (!strcmpstart(cp, "id=")) {
- ok = !base16_decode(id, DIGEST_LEN,
- cp+strlen("id="), strlen(cp)-strlen("id="));
+ ok = base16_decode(id, DIGEST_LEN, cp+strlen("id="),
+ strlen(cp)-strlen("id=")) == DIGEST_LEN;
} else if (!strcasecmpstart(cp, "ipv6=")) {
if (ipv6_addrport_ptr) {
log_warn(LD_CONFIG, "Redundant ipv6 addr/port on FallbackDir line");
@@ -5921,10 +6099,10 @@ parse_dir_fallback_line(const char *line,
ipv6_addrport_ptr = &ipv6_addrport;
}
} else if (!strcmpstart(cp, "weight=")) {
- int ok;
+ int num_ok;
const char *wstring = cp + strlen("weight=");
- weight = tor_parse_double(wstring, 0, UINT64_MAX, &ok, NULL);
- if (!ok) {
+ weight = tor_parse_double(wstring, 0, (double)UINT64_MAX, &num_ok, NULL);
+ if (!num_ok) {
log_warn(LD_CONFIG, "Invalid weight '%s' on FallbackDir line.", cp);
weight=1.0;
}
@@ -5987,6 +6165,8 @@ port_cfg_new(size_t namelen)
tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1);
port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t) + namelen + 1);
cfg->entry_cfg.ipv4_traffic = 1;
+ cfg->entry_cfg.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;
@@ -6089,54 +6269,75 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid_nonlocal)
} SMARTLIST_FOREACH_END(port);
}
-#ifdef HAVE_SYS_UN_H
-
-/** Parse the given <b>addrport</b> and set <b>path_out</b> if a Unix socket
- * path is found. Return 0 on success. On error, a negative value is
- * returned, -ENOENT if no Unix statement found, -EINVAL if the socket path
- * is empty and -ENOSYS if AF_UNIX is not supported (see function in the
- * #else statement below). */
-
+/**
+ * Take a string (<b>line</b>) that begins with either an address:port, a
+ * port, or an AF_UNIX address, optionally quoted, prefixed with
+ * "unix:". Parse that line, and on success, set <b>addrport_out</b> to a new
+ * string containing the beginning portion (without prefix). Iff there was a
+ * unix: prefix, set <b>is_unix_out</b> to true. On success, also set
+ * <b>rest_out</b> to point to the part of the line after the address portion.
+ *
+ * Return 0 on success, -1 on failure.
+ */
int
-config_parse_unix_port(const char *addrport, char **path_out)
+port_cfg_line_extract_addrport(const char *line,
+ char **addrport_out,
+ int *is_unix_out,
+ const char **rest_out)
{
- tor_assert(path_out);
- tor_assert(addrport);
-
- if (strcmpstart(addrport, unix_socket_prefix)) {
- /* Not a Unix socket path. */
- return -ENOENT;
- }
+ tor_assert(line);
+ tor_assert(addrport_out);
+ tor_assert(is_unix_out);
+ tor_assert(rest_out);
+
+ line = eat_whitespace(line);
+
+ if (!strcmpstart(line, unix_q_socket_prefix)) {
+ // It starts with unix:"
+ size_t sz;
+ *is_unix_out = 1;
+ *addrport_out = NULL;
+ line += strlen(unix_socket_prefix); /*No q: Keep the quote */
+ *rest_out = unescape_string(line, addrport_out, &sz);
+ if (!*rest_out || (*addrport_out && sz != strlen(*addrport_out))) {
+ tor_free(*addrport_out);
+ return -1;
+ }
+ *rest_out = eat_whitespace(*rest_out);
+ return 0;
+ } else {
+ // Is there a unix: prefix?
+ if (!strcmpstart(line, unix_socket_prefix)) {
+ line += strlen(unix_socket_prefix);
+ *is_unix_out = 1;
+ } else {
+ *is_unix_out = 0;
+ }
- if (strlen(addrport + strlen(unix_socket_prefix)) == 0) {
- /* Empty socket path, not very usable. */
- return -EINVAL;
+ const char *end = find_whitespace(line);
+ if (BUG(!end)) {
+ end = strchr(line, '\0'); // LCOV_EXCL_LINE -- this can't be NULL
+ }
+ tor_assert(end && end >= line);
+ *addrport_out = tor_strndup(line, end - line);
+ *rest_out = eat_whitespace(end);
+ return 0;
}
-
- *path_out = tor_strdup(addrport + strlen(unix_socket_prefix));
- return 0;
}
-#else /* defined(HAVE_SYS_UN_H) */
-
-int
-config_parse_unix_port(const char *addrport, char **path_out)
+static void
+warn_client_dns_cache(const char *option, int disabling)
{
- tor_assert(path_out);
- tor_assert(addrport);
-
- if (strcmpstart(addrport, unix_socket_prefix)) {
- /* Not a Unix socket path. */
- return -ENOENT;
- }
+ if (disabling)
+ return;
- log_warn(LD_CONFIG,
- "Port configuration %s is for an AF_UNIX socket, but we have no"
- "support available on this platform",
- escaped(addrport));
- return -ENOSYS;
+ warn_deprecated_option(option,
+ "Client-side DNS cacheing enables a wide variety of route-"
+ "capture attacks. If a single bad exit node lies to you about "
+ "an IP address, cacheing that address would make you visit "
+ "an address of the attacker's choice every time you connected "
+ "to your destination.");
}
-#endif /* defined(HAVE_SYS_UN_H) */
/**
* Parse port configuration for a single port type.
@@ -6243,8 +6444,7 @@ parse_port_config(smartlist_t *out,
tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */
cfg->server_cfg.no_listen = 1;
cfg->server_cfg.bind_ipv4_only = 1;
- cfg->entry_cfg.ipv4_traffic = 1;
- cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
+ /* cfg->entry_cfg defaults are already set by port_cfg_new */
smartlist_add(out, cfg);
}
@@ -6303,44 +6503,54 @@ parse_port_config(smartlist_t *out,
/* At last we can actually parse the FooPort lines. The syntax is:
* [Addr:](Port|auto) [Options].*/
elts = smartlist_new();
+ char *addrport = NULL;
for (; ports; ports = ports->next) {
tor_addr_t addr;
- int port, ret;
+ int port;
int sessiongroup = SESSION_GROUP_UNSET;
unsigned isolation = ISO_DEFAULT;
int prefer_no_auth = 0;
int socks_iso_keep_alive = 0;
- char *addrport;
uint16_t ptmp=0;
int ok;
+ /* This must be kept in sync with port_cfg_new's defaults */
int no_listen = 0, no_advertise = 0, all_addrs = 0,
bind_ipv4_only = 0, bind_ipv6_only = 0,
- ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0,
+ ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0, dns_request = 1,
+ onion_traffic = 1,
cache_ipv4 = 1, 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,
has_used_unix_socket_only_option = 0;
- smartlist_split_string(elts, ports->value, NULL,
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
- if (smartlist_len(elts) == 0) {
- log_warn(LD_CONFIG, "Invalid %sPort line with no value", portname);
+ int is_unix_tagged_addr = 0;
+ const char *rest_of_line = NULL;
+ if (port_cfg_line_extract_addrport(ports->value,
+ &addrport, &is_unix_tagged_addr, &rest_of_line)<0) {
+ log_warn(LD_CONFIG, "Invalid %sPort line with unparsable address",
+ portname);
+ goto err;
+ }
+ if (strlen(addrport) == 0) {
+ log_warn(LD_CONFIG, "Invalid %sPort line with no address", portname);
goto err;
}
- /* Now parse the addr/port value */
- addrport = smartlist_get(elts, 0);
+ /* Split the remainder... */
+ smartlist_split_string(elts, rest_of_line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
/* Let's start to check if it's a Unix socket path. */
- ret = config_parse_unix_port(addrport, &unix_socket_path);
- if (ret < 0 && ret != -ENOENT) {
- if (ret == -EINVAL) {
- log_warn(LD_CONFIG, "Empty Unix socket path.");
- }
+ if (is_unix_tagged_addr) {
+#ifndef HAVE_SYS_UN_H
+ log_warn(LD_CONFIG, "Unix sockets not supported on this system.");
goto err;
+#endif
+ unix_socket_path = addrport;
+ addrport = NULL;
}
if (unix_socket_path &&
@@ -6352,6 +6562,8 @@ parse_port_config(smartlist_t *out,
if (unix_socket_path) {
port = 1;
} else if (is_unix_socket) {
+ if (BUG(!addrport))
+ goto err; // LCOV_EXCL_LINE unreachable, but coverity can't tell that
unix_socket_path = tor_strdup(addrport);
if (!strcmp(addrport, "0"))
port = 0;
@@ -6398,9 +6610,6 @@ parse_port_config(smartlist_t *out,
if (use_server_options) {
/* This is a server port; parse advertising options */
SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
- if (elt_sl_idx == 0)
- continue; /* Skip addr:port */
-
if (!strcasecmp(elt, "NoAdvertise")) {
no_advertise = 1;
} else if (!strcasecmp(elt, "NoListen")) {
@@ -6448,8 +6657,6 @@ parse_port_config(smartlist_t *out,
SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
int no = 0, isoflag = 0;
const char *elt_orig = elt;
- if (elt_sl_idx == 0)
- continue; /* Skip addr:port */
if (!strcasecmpstart(elt, "SessionGroup=")) {
int group = (int)tor_parse_long(elt+strlen("SessionGroup="),
@@ -6503,24 +6710,48 @@ parse_port_config(smartlist_t *out,
} else if (!strcasecmp(elt, "PreferIPv6")) {
prefer_ipv6 = ! no;
continue;
+ } else if (!strcasecmp(elt, "DNSRequest")) {
+ dns_request = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "OnionTraffic")) {
+ onion_traffic = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "OnionTrafficOnly")) {
+ /* Only connect to .onion addresses. Equivalent to
+ * NoDNSRequest, NoIPv4Traffic, NoIPv6Traffic. The option
+ * NoOnionTrafficOnly is not supported, it's too confusing. */
+ if (no) {
+ log_warn(LD_CONFIG, "Unsupported %sPort option 'No%s'. Use "
+ "DNSRequest, IPv4Traffic, and/or IPv6Traffic instead.",
+ portname, escaped(elt));
+ } else {
+ ipv4_traffic = ipv6_traffic = dns_request = 0;
+ }
+ continue;
}
}
if (!strcasecmp(elt, "CacheIPv4DNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
cache_ipv4 = ! no;
continue;
} else if (!strcasecmp(elt, "CacheIPv6DNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
cache_ipv6 = ! no;
continue;
} else if (!strcasecmp(elt, "CacheDNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
cache_ipv4 = cache_ipv6 = ! no;
continue;
} else if (!strcasecmp(elt, "UseIPv4Cache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
use_cached_ipv4 = ! no;
continue;
} else if (!strcasecmp(elt, "UseIPv6Cache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
use_cached_ipv6 = ! no;
continue;
} else if (!strcasecmp(elt, "UseDNSCache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
use_cached_ipv4 = use_cached_ipv6 = ! no;
continue;
} else if (!strcasecmp(elt, "PreferIPv6Automap")) {
@@ -6565,9 +6796,24 @@ parse_port_config(smartlist_t *out,
else
got_zero_port = 1;
- if (ipv4_traffic == 0 && ipv6_traffic == 0) {
- log_warn(LD_CONFIG, "You have a %sPort entry with both IPv4 and "
- "IPv6 disabled; that won't work.", portname);
+ if (dns_request == 0 && listener_type == CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with DNS disabled; that "
+ "won't work.", portname);
+ goto err;
+ }
+
+ if (ipv4_traffic == 0 && ipv6_traffic == 0 && onion_traffic == 0
+ && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with all of IPv4 and "
+ "IPv6 and .onion disabled; that won't work.", portname);
+ goto err;
+ }
+
+ if (dns_request == 1 && ipv4_traffic == 0 && ipv6_traffic == 0
+ && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with DNSRequest enabled, "
+ "but IPv4 and IPv6 disabled; DNS-based sites won't work.",
+ portname);
goto err;
}
@@ -6585,6 +6831,13 @@ parse_port_config(smartlist_t *out,
goto err;
}
+ if (unix_socket_path && (isolation & ISO_CLIENTADDR)) {
+ /* `IsolateClientAddr` is nonsensical in the context of AF_LOCAL.
+ * just silently remove the isolation flag.
+ */
+ isolation &= ~ISO_CLIENTADDR;
+ }
+
if (out && port) {
size_t namelen = unix_socket_path ? strlen(unix_socket_path) : 0;
port_cfg_t *cfg = port_cfg_new(namelen);
@@ -6611,6 +6864,8 @@ parse_port_config(smartlist_t *out,
cfg->entry_cfg.ipv4_traffic = ipv4_traffic;
cfg->entry_cfg.ipv6_traffic = ipv6_traffic;
cfg->entry_cfg.prefer_ipv6 = prefer_ipv6;
+ cfg->entry_cfg.dns_request = dns_request;
+ cfg->entry_cfg.onion_traffic = onion_traffic;
cfg->entry_cfg.cache_ipv4_answers = cache_ipv4;
cfg->entry_cfg.cache_ipv6_answers = cache_ipv6;
cfg->entry_cfg.use_cached_ipv4_answers = use_cached_ipv4;
@@ -6625,6 +6880,7 @@ parse_port_config(smartlist_t *out,
}
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_clear(elts);
+ tor_free(addrport);
}
if (warn_nonlocal && out) {
@@ -6648,18 +6904,22 @@ parse_port_config(smartlist_t *out,
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_free(elts);
tor_free(unix_socket_path);
+ tor_free(addrport);
return retval;
}
/** Return the number of ports which are actually going to listen with type
- * <b>listenertype</b>. Do not count no_listen ports. Do not count unix
- * sockets. */
+ * <b>listenertype</b>. Do not count no_listen ports. Only count unix
+ * sockets if count_sockets is true. */
static int
-count_real_listeners(const smartlist_t *ports, int listenertype)
+count_real_listeners(const smartlist_t *ports, int listenertype,
+ int count_sockets)
{
int n = 0;
SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) {
- if (port->server_cfg.no_listen || port->is_unix_addr)
+ if (port->server_cfg.no_listen)
+ continue;
+ if (!count_sockets && port->is_unix_addr)
continue;
if (port->type != listenertype)
continue;
@@ -6668,9 +6928,8 @@ count_real_listeners(const smartlist_t *ports, int listenertype)
return n;
}
-/** Parse all client port types (Socks, DNS, Trans, NATD) from
- * <b>options</b>. On success, set *<b>n_ports_out</b> to the number
- * of ports that are listed, update the *Port_set values in
+/** Parse all ports from <b>options</b>. On success, set *<b>n_ports_out</b>
+ * to the number of ports that are listed, update the *Port_set values in
* <b>options</b>, and return 0. On failure, set *<b>msg</b> to a
* description of the problem and return -1.
*
@@ -6796,21 +7055,22 @@ parse_ports(or_options_t *options, int validate_only,
/* Update the *Port_set options. The !! here is to force a boolean out of
an integer. */
options->ORPort_set =
- !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER, 0);
options->SocksPort_set =
- !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER, 1);
options->TransPort_set =
- !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1);
options->NATDPort_set =
- !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1);
+ /* Use options->ControlSocket to test if a control socket is set */
options->ControlPort_set =
- !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0);
options->DirPort_set =
- !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER, 0);
options->DNSPort_set =
- !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER, 1);
options->ExtORPort_set =
- !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER);
+ !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER, 0);
if (world_writable_control_socket) {
SMARTLIST_FOREACH(ports, port_cfg_t *, p,
@@ -6840,6 +7100,24 @@ parse_ports(or_options_t *options, int validate_only,
return retval;
}
+/* Does port bind to IPv4? */
+static int
+port_binds_ipv4(const port_cfg_t *port)
+{
+ return tor_addr_family(&port->addr) == AF_INET ||
+ (tor_addr_family(&port->addr) == AF_UNSPEC
+ && !port->server_cfg.bind_ipv6_only);
+}
+
+/* Does port bind to IPv6? */
+static int
+port_binds_ipv6(const port_cfg_t *port)
+{
+ return tor_addr_family(&port->addr) == AF_INET6 ||
+ (tor_addr_family(&port->addr) == AF_UNSPEC
+ && !port->server_cfg.bind_ipv4_only);
+}
+
/** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal
* consistency and warn as appropriate. Set *<b>n_low_ports_out</b> to the
* number of sub-1024 ports we will be binding. */
@@ -6865,9 +7143,7 @@ check_server_ports(const smartlist_t *ports,
} else if (port->type == CONN_TYPE_OR_LISTENER) {
if (! port->server_cfg.no_advertise) {
++n_orport_advertised;
- if (tor_addr_family(&port->addr) == AF_INET ||
- (tor_addr_family(&port->addr) == AF_UNSPEC &&
- !port->server_cfg.bind_ipv6_only))
+ if (port_binds_ipv4(port))
++n_orport_advertised_ipv4;
}
if (! port->server_cfg.no_listen)
@@ -7001,19 +7277,20 @@ get_first_listener_addrport_string(int listener_type)
}
/** Return the first advertised port of type <b>listener_type</b> in
- <b>address_family</b>. */
+ * <b>address_family</b>. Returns 0 when no port is found, and when passed
+ * AF_UNSPEC. */
int
get_first_advertised_port_by_type_af(int listener_type, int address_family)
{
+ if (address_family == AF_UNSPEC)
+ return 0;
+
const smartlist_t *conf_ports = get_configured_ports();
SMARTLIST_FOREACH_BEGIN(conf_ports, const port_cfg_t *, cfg) {
if (cfg->type == listener_type &&
- !cfg->server_cfg.no_advertise &&
- (tor_addr_family(&cfg->addr) == address_family ||
- tor_addr_family(&cfg->addr) == AF_UNSPEC)) {
- if (tor_addr_family(&cfg->addr) != AF_UNSPEC ||
- (address_family == AF_INET && !cfg->server_cfg.bind_ipv6_only) ||
- (address_family == AF_INET6 && !cfg->server_cfg.bind_ipv4_only)) {
+ !cfg->server_cfg.no_advertise) {
+ if ((address_family == AF_INET && port_binds_ipv4(cfg)) ||
+ (address_family == AF_INET6 && port_binds_ipv6(cfg))) {
return cfg->port;
}
}
@@ -7021,6 +7298,87 @@ get_first_advertised_port_by_type_af(int listener_type, int address_family)
return 0;
}
+/** Return the first advertised address of type <b>listener_type</b> in
+ * <b>address_family</b>. Returns NULL if there is no advertised address,
+ * and when passed AF_UNSPEC. */
+const tor_addr_t *
+get_first_advertised_addr_by_type_af(int listener_type, int address_family)
+{
+ if (address_family == AF_UNSPEC)
+ return NULL;
+ if (!configured_ports)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+ if (cfg->type == listener_type &&
+ !cfg->server_cfg.no_advertise) {
+ if ((address_family == AF_INET && port_binds_ipv4(cfg)) ||
+ (address_family == AF_INET6 && port_binds_ipv6(cfg))) {
+ return &cfg->addr;
+ }
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+ return NULL;
+}
+
+/** Return 1 if a port exists of type <b>listener_type</b> on <b>addr</b> and
+ * <b>port</b>. If <b>check_wildcard</b> is true, INADDR[6]_ANY and AF_UNSPEC
+ * addresses match any address of the appropriate family; and port -1 matches
+ * any port.
+ * To match auto ports, pass CFG_PORT_AUTO. (Does not match on the actual
+ * automatically chosen listener ports.) */
+int
+port_exists_by_type_addr_port(int listener_type, const tor_addr_t *addr,
+ int port, int check_wildcard)
+{
+ if (!configured_ports || !addr)
+ return 0;
+ SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+ if (cfg->type == listener_type) {
+ if (cfg->port == port || (check_wildcard && port == -1)) {
+ /* Exact match */
+ if (tor_addr_eq(&cfg->addr, addr)) {
+ return 1;
+ }
+ /* Skip wildcard matches if we're not doing them */
+ if (!check_wildcard) {
+ continue;
+ }
+ /* Wildcard matches IPv4 */
+ const int cfg_v4 = port_binds_ipv4(cfg);
+ const int cfg_any_v4 = tor_addr_is_null(&cfg->addr) && cfg_v4;
+ const int addr_v4 = tor_addr_family(addr) == AF_INET ||
+ tor_addr_family(addr) == AF_UNSPEC;
+ const int addr_any_v4 = tor_addr_is_null(&cfg->addr) && addr_v4;
+ if ((cfg_any_v4 && addr_v4) || (cfg_v4 && addr_any_v4)) {
+ return 1;
+ }
+ /* Wildcard matches IPv6 */
+ const int cfg_v6 = port_binds_ipv6(cfg);
+ const int cfg_any_v6 = tor_addr_is_null(&cfg->addr) && cfg_v6;
+ const int addr_v6 = tor_addr_family(addr) == AF_INET6 ||
+ tor_addr_family(addr) == AF_UNSPEC;
+ const int addr_any_v6 = tor_addr_is_null(&cfg->addr) && addr_v6;
+ if ((cfg_any_v6 && addr_v6) || (cfg_v6 && addr_any_v6)) {
+ return 1;
+ }
+ }
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+ return 0;
+}
+
+/* Like port_exists_by_type_addr_port, but accepts a host-order IPv4 address
+ * instead. */
+int
+port_exists_by_type_addr32h_port(int listener_type, uint32_t addr_ipv4h,
+ int port, int check_wildcard)
+{
+ tor_addr_t ipv4;
+ tor_addr_from_ipv4h(&ipv4, addr_ipv4h);
+ return port_exists_by_type_addr_port(listener_type, &ipv4, port,
+ check_wildcard);
+}
+
/** Adjust the value of options->DataDirectory, or fill it in if it's
* absent. Return 0 on success, -1 on failure. */
static int
@@ -7206,10 +7564,7 @@ init_libevent(const or_options_t *options)
*/
suppress_libevent_log_msg("Function not implemented");
- tor_check_libevent_header_compatibility();
-
memset(&cfg, 0, sizeof(cfg));
- cfg.disable_iocp = options->DisableIOCP;
cfg.num_cpus = get_num_cpus(options);
cfg.msec_per_tick = options->TokenBucketRefillInterval;
@@ -7232,10 +7587,10 @@ init_libevent(const or_options_t *options)
*
* Note: Consider using the get_datadir_fname* macros in or.h.
*/
-char *
-options_get_datadir_fname2_suffix(const or_options_t *options,
- const char *sub1, const char *sub2,
- const char *suffix)
+MOCK_IMPL(char *,
+options_get_datadir_fname2_suffix,(const or_options_t *options,
+ const char *sub1, const char *sub2,
+ const char *suffix))
{
char *fname = NULL;
size_t len;
@@ -7416,8 +7771,8 @@ getinfo_helper_config(control_connection_t *conn,
smartlist_free(sl);
} else if (!strcmp(question, "config/defaults")) {
smartlist_t *sl = smartlist_new();
- int i, dirauth_lines_seen = 0, fallback_lines_seen = 0;
- for (i = 0; option_vars_[i].name; ++i) {
+ int dirauth_lines_seen = 0, fallback_lines_seen = 0;
+ for (int i = 0; option_vars_[i].name; ++i) {
const config_var_t *var = &option_vars_[i];
if (var->initvalue != NULL) {
if (strcmp(option_vars_[i].name, "DirAuthority") == 0) {
@@ -7445,14 +7800,13 @@ getinfo_helper_config(control_connection_t *conn,
* We didn't see any directory authorities with default values,
* so add the list of default authorities manually.
*/
- const char **i;
/*
* default_authorities is defined earlier in this file and
* is a const char ** NULL-terminated array of dirauth config
* lines.
*/
- for (i = default_authorities; *i != NULL; ++i) {
+ for (const char **i = default_authorities; *i != NULL; ++i) {
char *val = esc_for_log(*i);
smartlist_add_asprintf(sl, "DirAuthority %s\n", val);
tor_free(val);
@@ -7569,7 +7923,7 @@ static void
config_maybe_load_geoip_files_(const or_options_t *options,
const or_options_t *old_options)
{
- /* XXXX024 Reload GeoIPFile on SIGHUP. -NM */
+ /* XXXX Reload GeoIPFile on SIGHUP. -NM */
if (options->GeoIPFile &&
((!old_options || !opt_streq(old_options->GeoIPFile,
diff --git a/src/or/config.h b/src/or/config.h
index 02121cf95c..6645532514 100644
--- a/src/or/config.h
+++ b/src/or/config.h
@@ -29,8 +29,8 @@ const char *escaped_safe_str_client(const char *address);
const char *escaped_safe_str(const char *address);
const char *get_version(void);
const char *get_short_version(void);
-setopt_err_t options_trial_assign(config_line_t *list, int use_defaults,
- int clear_first, char **msg);
+setopt_err_t options_trial_assign(config_line_t *list, unsigned flags,
+ char **msg);
uint32_t get_last_resolved_addr(void);
void reset_last_resolved_addr(void);
@@ -53,9 +53,11 @@ config_line_t *option_get_assignment(const or_options_t *options,
const char *key);
int options_save_current(void);
const char *get_torrc_fname(int defaults_fname);
-char *options_get_datadir_fname2_suffix(const or_options_t *options,
- const char *sub1, const char *sub2,
- const char *suffix);
+MOCK_DECL(char *,
+ options_get_datadir_fname2_suffix,
+ (const or_options_t *options,
+ const char *sub1, const char *sub2,
+ const char *suffix));
#define get_datadir_fname2_suffix(sub1, sub2, suffix) \
options_get_datadir_fname2_suffix(get_options(), (sub1), (sub2), (suffix))
/** Return a newly allocated string containing datadir/sub1. See
@@ -74,6 +76,8 @@ char *options_get_datadir_fname2_suffix(const or_options_t *options,
#define get_datadir_fname_suffix(sub1, suffix) \
get_datadir_fname2_suffix((sub1), NULL, (suffix))
+int using_default_dir_authorities(const or_options_t *options);
+
int check_or_create_data_subdir(const char *subdir);
int write_to_data_subdir(const char* subdir, const char* fname,
const char* str, const char* descr);
@@ -87,6 +91,12 @@ int get_first_advertised_port_by_type_af(int listener_type,
(get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, AF_INET))
#define get_primary_dir_port() \
(get_first_advertised_port_by_type_af(CONN_TYPE_DIR_LISTENER, AF_INET))
+const tor_addr_t *get_first_advertised_addr_by_type_af(int listener_type,
+ int address_family);
+int port_exists_by_type_addr_port(int listener_type, const tor_addr_t *addr,
+ int port, int check_wildcard);
+int port_exists_by_type_addr32h_port(int listener_type, uint32_t addr_ipv4h,
+ int port, int check_wildcard);
char *get_first_listener_addrport_string(int listener_type);
@@ -115,12 +125,16 @@ int config_parse_commandline(int argc, char **argv, int ignore_errors,
config_line_t **cmdline_result);
void config_register_addressmaps(const or_options_t *options);
-/* XXXX024 move to connection_edge.h */
+/* XXXX move to connection_edge.h */
int addressmap_register_auto(const char *from, const char *to,
time_t expires,
addressmap_entry_source_t addrmap_source,
const char **msg);
-int config_parse_unix_port(const char *addrport, char **path_out);
+
+int port_cfg_line_extract_addrport(const char *line,
+ char **addrport_out,
+ int *is_unix_out,
+ const char **rest_out);
/** Represents the information stored in a torrc Bridge line. */
typedef struct bridge_line_t {
@@ -158,6 +172,8 @@ extern struct config_format_t options_format;
STATIC port_cfg_t *port_cfg_new(size_t namelen);
STATIC void port_cfg_free(port_cfg_t *port);
STATIC void or_options_free(or_options_t *options);
+STATIC int options_validate_single_onion(or_options_t *options,
+ char **msg);
STATIC int options_validate(or_options_t *old_options,
or_options_t *options,
or_options_t *default_options,
diff --git a/src/or/confparse.c b/src/or/confparse.c
index 4f446d07c3..efcf4f981e 100644
--- a/src/or/confparse.c
+++ b/src/or/confparse.c
@@ -181,6 +181,26 @@ config_free_lines(config_line_t *front)
}
}
+/** If <b>key</b> is a deprecated configuration option, return the message
+ * explaining why it is deprecated (which may be an empty string). Return NULL
+ * if it is not deprecated. The <b>key</b> field must be fully expanded. */
+const char *
+config_find_deprecation(const config_format_t *fmt, const char *key)
+{
+ if (BUG(fmt == NULL) || BUG(key == NULL))
+ return NULL;
+ if (fmt->deprecations == NULL)
+ return NULL;
+
+ const config_deprecation_t *d;
+ for (d = fmt->deprecations; d->name; ++d) {
+ if (!strcasecmp(d->name, key)) {
+ return d->why_deprecated ? d->why_deprecated : "";
+ }
+ }
+ return NULL;
+}
+
/** As config_find_option, but return a non-const pointer. */
config_var_t *
config_find_option_mutable(config_format_t *fmt, const char *key)
@@ -463,6 +483,16 @@ config_mark_lists_fragile(const config_format_t *fmt, void *options)
}
}
+void
+warn_deprecated_option(const char *what, const char *why)
+{
+ const char *space = (why && strlen(why)) ? " " : "";
+ log_warn(LD_CONFIG, "The %s option is deprecated, and will most likely "
+ "be removed in a future version of Tor.%s%s (If you think this is "
+ "a mistake, please let us know!)",
+ what, space, why);
+}
+
/** If <b>c</b> is a syntactically valid configuration line, update
* <b>options</b> with its value and return 0. Otherwise return -1 for bad
* key, -2 for bad value.
@@ -474,9 +504,12 @@ config_mark_lists_fragile(const config_format_t *fmt, void *options)
*/
static int
config_assign_line(const config_format_t *fmt, void *options,
- config_line_t *c, int use_defaults,
- int clear_first, bitarray_t *options_seen, char **msg)
+ config_line_t *c, unsigned flags,
+ bitarray_t *options_seen, char **msg)
{
+ const unsigned use_defaults = flags & CAL_USE_DEFAULTS;
+ const unsigned clear_first = flags & CAL_CLEAR_FIRST;
+ const unsigned warn_deprecations = flags & CAL_WARN_DEPRECATIONS;
const config_var_t *var;
CONFIG_CHECK(fmt, options);
@@ -502,6 +535,12 @@ config_assign_line(const config_format_t *fmt, void *options,
c->key = tor_strdup(var->name);
}
+ const char *deprecation_msg;
+ if (warn_deprecations &&
+ (deprecation_msg = config_find_deprecation(fmt, var->name))) {
+ warn_deprecated_option(var->name, deprecation_msg);
+ }
+
if (!strlen(c->value)) {
/* reset or clear it, then return */
if (!clear_first) {
@@ -604,7 +643,7 @@ config_lines_dup(const config_line_t *inp)
* escape that value. Return NULL if no such key exists. */
config_line_t *
config_get_assigned_option(const config_format_t *fmt, const void *options,
- const char *key, int escape_val)
+ const char *key, int escape_val)
{
const config_var_t *var;
const void *value;
@@ -804,11 +843,13 @@ options_trial_assign() calls config_assign(1, 1)
*/
int
config_assign(const config_format_t *fmt, void *options, config_line_t *list,
- int use_defaults, int clear_first, char **msg)
+ unsigned config_assign_flags, char **msg)
{
config_line_t *p;
bitarray_t *options_seen;
const int n_options = config_count_options(fmt);
+ const unsigned clear_first = config_assign_flags & CAL_CLEAR_FIRST;
+ const unsigned use_defaults = config_assign_flags & CAL_USE_DEFAULTS;
CONFIG_CHECK(fmt, options);
@@ -832,8 +873,8 @@ config_assign(const config_format_t *fmt, void *options, config_line_t *list,
/* pass 3: assign. */
while (list) {
int r;
- if ((r=config_assign_line(fmt, options, list, use_defaults,
- clear_first, options_seen, msg))) {
+ if ((r=config_assign_line(fmt, options, list, config_assign_flags,
+ options_seen, msg))) {
bitarray_free(options_seen);
return r;
}
@@ -1029,7 +1070,7 @@ config_dup(const config_format_t *fmt, const void *old)
line = config_get_assigned_option(fmt, old, fmt->vars[i].name, 0);
if (line) {
char *msg = NULL;
- if (config_assign(fmt, newopts, line, 0, 0, &msg) < 0) {
+ if (config_assign(fmt, newopts, line, 0, &msg) < 0) {
log_err(LD_BUG, "config_get_assigned_option() generated "
"something we couldn't config_assign(): %s", msg);
tor_free(msg);
@@ -1238,7 +1279,7 @@ config_parse_units(const char *val, struct unit_table_t *u, int *ok)
v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
if (!*ok || (cp && *cp == '.')) {
- d = tor_parse_double(val, 0, UINT64_MAX, ok, &cp);
+ d = tor_parse_double(val, 0, (double)UINT64_MAX, ok, &cp);
if (!*ok)
goto done;
use_float = 1;
@@ -1255,7 +1296,7 @@ config_parse_units(const char *val, struct unit_table_t *u, int *ok)
for ( ;u->unit;++u) {
if (!strcasecmp(u->unit, cp)) {
if (use_float)
- v = u->multiplier * d;
+ v = (uint64_t)(u->multiplier * d);
else
v *= u->multiplier;
*ok = 1;
diff --git a/src/or/confparse.h b/src/or/confparse.h
index 885c615202..8d915d266b 100644
--- a/src/or/confparse.h
+++ b/src/or/confparse.h
@@ -48,6 +48,11 @@ typedef struct config_abbrev_t {
int warn;
} config_abbrev_t;
+typedef struct config_deprecation_t {
+ const char *name;
+ const char *why_deprecated;
+} config_deprecation_t;
+
/* Handy macro for declaring "In the config file or on the command line,
* you can abbreviate <b>tok</b>s as <b>tok</b>". */
#define PLURAL(tok) { #tok, #tok "s", 0, 0 }
@@ -61,13 +66,6 @@ typedef struct config_var_t {
const char *initvalue; /**< String (or null) describing initial value. */
} config_var_t;
-/** Represents an English description of a configuration variable; used when
- * generating configuration file comments. */
-typedef struct config_var_description_t {
- const char *name;
- const char *description;
-} config_var_description_t;
-
/** Type of a callback to validate whether a given configuration is
* well-formed and consistent. See options_trial_assign() for documentation
* of arguments. */
@@ -83,6 +81,7 @@ typedef struct config_format_t {
off_t magic_offset; /**< Offset of the magic value within the struct. */
config_abbrev_t *abbrevs; /**< List of abbreviations that we expand when
* parsing this format. */
+ const config_deprecation_t *deprecations; /** List of deprecated options */
config_var_t *vars; /**< List of variables we recognize, their default
* values, and where we stick them in the structure. */
validate_fn_t validate_fn; /**< Function to validate config. */
@@ -99,6 +98,10 @@ typedef struct config_format_t {
*(uint32_t*)STRUCT_VAR_P(cfg,fmt->magic_offset)); \
STMT_END
+#define CAL_USE_DEFAULTS (1u<<0)
+#define CAL_CLEAR_FIRST (1u<<1)
+#define CAL_WARN_DEPRECATIONS (1u<<2)
+
void *config_new(const config_format_t *fmt);
void config_line_append(config_line_t **lst,
const char *key, const char *val);
@@ -121,9 +124,11 @@ char *config_dump(const config_format_t *fmt, const void *default_options,
int comment_defaults);
int config_assign(const config_format_t *fmt, void *options,
config_line_t *list,
- int use_defaults, int clear_first, char **msg);
+ unsigned flags, char **msg);
config_var_t *config_find_option_mutable(config_format_t *fmt,
const char *key);
+const char *config_find_deprecation(const config_format_t *fmt,
+ const char *key);
const config_var_t *config_find_option(const config_format_t *fmt,
const char *key);
@@ -132,6 +137,7 @@ void config_free_lines(config_line_t *front);
const char *config_expand_abbrev(const config_format_t *fmt,
const char *option,
int command_line, int warn_obsolete);
+void warn_deprecated_option(const char *what, const char *why);
#endif
diff --git a/src/or/connection.c b/src/or/connection.c
index 05e56c1a52..8b00d637f6 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -8,6 +8,50 @@
* \file connection.c
* \brief General high-level functions to handle reading and writing
* on connections.
+ *
+ * Each connection (ideally) represents a TLS connection, a TCP socket, a unix
+ * socket, or a UDP socket on which reads and writes can occur. (But see
+ * connection_edge.c for cases where connections can also represent streams
+ * that do not have a corresponding socket.)
+ *
+ * The module implements the abstract type, connection_t. The subtypes are:
+ * <ul>
+ * <li>listener_connection_t, implemented here in connection.c
+ * <li>dir_connection_t, implemented in directory.c
+ * <li>or_connection_t, implemented in connection_or.c
+ * <li>edge_connection_t, implemented in connection_edge.c, along with
+ * its subtype(s):
+ * <ul><li>entry_connection_t, also implemented in connection_edge.c
+ * </ul>
+ * <li>control_connection_t, implemented in control.c
+ * </ul>
+ *
+ * The base type implemented in this module is responsible for basic
+ * rate limiting, flow control, and marshalling bytes onto and off of the
+ * network (either directly or via TLS).
+ *
+ * Connections are registered with the main loop with connection_add(). As
+ * they become able to read or write register the fact with the event main
+ * loop by calling connection_watch_events(), connection_start_reading(), or
+ * connection_start_writing(). When they no longer want to read or write,
+ * they call connection_stop_reading() or connection_start_writing().
+ *
+ * To queue data to be written on a connection, call
+ * connection_write_to_buf(). 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
+ * receive notification via connection_flushed_some() and
+ * connection_finished_flushing(). These functions all delegate to
+ * type-specific implementations.
+ *
+ * Additionally, beyond the core of connection_t, this module also implements:
+ * <ul>
+ * <li>Listeners, which wait for incoming sockets and launch connections
+ * <li>Outgoing SOCKS proxy support
+ * <li>Outgoing HTTP proxy support
+ * <li>An out-of-sockets handler for dealing with socket exhaustion
+ * </ul>
**/
#define CONNECTION_PRIVATE
@@ -52,10 +96,6 @@
#include "sandbox.h"
#include "transports.h"
-#ifdef USE_BUFFEREVENTS
-#include <event2/event.h>
-#endif
-
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
@@ -75,10 +115,8 @@ static void connection_init(time_t now, connection_t *conn, int type,
static int connection_init_accepted_conn(connection_t *conn,
const listener_connection_t *listener);
static int connection_handle_listener_read(connection_t *conn, int new_type);
-#ifndef USE_BUFFEREVENTS
static int connection_bucket_should_increase(int bucket,
or_connection_t *conn);
-#endif
static int connection_finished_flushing(connection_t *conn);
static int connection_flushed_some(connection_t *conn);
static int connection_finished_connecting(connection_t *conn);
@@ -98,7 +136,7 @@ static int get_proxy_type(void);
/** The last addresses that our network interface seemed to have been
* binding to. We use this as one way to detect when our IP changes.
*
- * XXX024 We should really use the entire list of interfaces here.
+ * XXXX+ We should really use the entire list of interfaces here.
**/
static tor_addr_t *last_interface_ipv4 = NULL;
/* DOCDOC last_interface_ipv6 */
@@ -236,26 +274,6 @@ conn_state_to_string(int type, int state)
return buf;
}
-#ifdef USE_BUFFEREVENTS
-/** Return true iff the connection's type is one that can use a
- bufferevent-based implementation. */
-int
-connection_type_uses_bufferevent(connection_t *conn)
-{
- switch (conn->type) {
- case CONN_TYPE_AP:
- case CONN_TYPE_EXIT:
- case CONN_TYPE_DIR:
- case CONN_TYPE_CONTROL:
- case CONN_TYPE_OR:
- case CONN_TYPE_EXT_OR:
- return 1;
- default:
- return 0;
- }
-}
-#endif
-
/** Allocate and return a new dir_connection_t, initialized as by
* connection_init(). */
dir_connection_t *
@@ -427,13 +445,11 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family)
conn->type = type;
conn->socket_family = socket_family;
-#ifndef USE_BUFFEREVENTS
if (!connection_is_listener(conn)) {
/* listeners never use their buf */
conn->inbuf = buf_new();
conn->outbuf = buf_new();
}
-#endif
conn->timestamp_created = now;
conn->timestamp_lastread = now;
@@ -577,10 +593,10 @@ connection_free_(connection_t *conn)
if (entry_conn->socks_request)
socks_request_free(entry_conn->socks_request);
if (entry_conn->pending_optimistic_data) {
- generic_buffer_free(entry_conn->pending_optimistic_data);
+ buf_free(entry_conn->pending_optimistic_data);
}
if (entry_conn->sending_optimistic_data) {
- generic_buffer_free(entry_conn->sending_optimistic_data);
+ buf_free(entry_conn->sending_optimistic_data);
}
}
if (CONN_IS_EDGE(conn)) {
@@ -603,15 +619,6 @@ connection_free_(connection_t *conn)
tor_event_free(conn->read_event);
tor_event_free(conn->write_event);
conn->read_event = conn->write_event = NULL;
- IF_HAS_BUFFEREVENT(conn, {
- /* This was a workaround to handle bugs in some old versions of libevent
- * where callbacks can occur after calling bufferevent_free(). Setting
- * the callbacks to NULL prevented this. It shouldn't be necessary any
- * more, but let's not tempt fate for now. */
- bufferevent_setcb(conn->bufev, NULL, NULL, NULL, NULL);
- bufferevent_free(conn->bufev);
- conn->bufev = NULL;
- });
if (conn->type == CONN_TYPE_DIR) {
dir_connection_t *dir_conn = TO_DIR_CONN(conn);
@@ -645,13 +652,6 @@ connection_free_(connection_t *conn)
tor_free(TO_OR_CONN(conn)->ext_or_transport);
}
-#ifdef USE_BUFFEREVENTS
- if (conn->type == CONN_TYPE_OR && TO_OR_CONN(conn)->bucket_cfg) {
- ev_token_bucket_cfg_free(TO_OR_CONN(conn)->bucket_cfg);
- TO_OR_CONN(conn)->bucket_cfg = NULL;
- }
-#endif
-
memwipe(mem, 0xCC, memlen); /* poison memory */
tor_free(mem);
}
@@ -665,9 +665,7 @@ connection_free,(connection_t *conn))
return;
tor_assert(!connection_is_on_closeable_list(conn));
tor_assert(!connection_in_array(conn));
- if (conn->linked_conn) {
- log_err(LD_BUG, "Called with conn->linked_conn still set.");
- tor_fragile_assert();
+ if (BUG(conn->linked_conn)) {
conn->linked_conn->linked_conn = NULL;
if (! conn->linked_conn->marked_for_close &&
conn->linked_conn->reading_from_linked_conn)
@@ -804,9 +802,9 @@ connection_mark_for_close_(connection_t *conn, int line, const char *file)
* For all other cases, use connection_mark_and_flush() instead, which
* checks for or_connection_t properly, instead. See below.
*/
-void
-connection_mark_for_close_internal_(connection_t *conn,
- int line, const char *file)
+MOCK_IMPL(void,
+connection_mark_for_close_internal_, (connection_t *conn,
+ int line, const char *file))
{
assert_connection_ok(conn,0);
tor_assert(line);
@@ -1140,6 +1138,7 @@ connection_listener_new(const struct sockaddr *listensockaddr,
int start_reading = 0;
static int global_next_session_group = SESSION_GROUP_FIRST_AUTO;
tor_addr_t addr;
+ int exhaustion = 0;
if (listensockaddr->sa_family == AF_INET ||
listensockaddr->sa_family == AF_INET6) {
@@ -1158,6 +1157,11 @@ connection_listener_new(const struct sockaddr *listensockaddr,
int e = tor_socket_errno(s);
if (ERRNO_IS_RESOURCE_LIMIT(e)) {
warn_too_many_conns();
+ /*
+ * We'll call the OOS handler at the error exit, so set the
+ * exhaustion flag for it.
+ */
+ exhaustion = 1;
} else {
log_warn(LD_NET, "Socket creation failed: %s",
tor_socket_strerror(e));
@@ -1276,6 +1280,11 @@ connection_listener_new(const struct sockaddr *listensockaddr,
int e = tor_socket_errno(s);
if (ERRNO_IS_RESOURCE_LIMIT(e)) {
warn_too_many_conns();
+ /*
+ * We'll call the OOS handler at the error exit, so set the
+ * exhaustion flag for it.
+ */
+ exhaustion = 1;
} else {
log_warn(LD_NET,"Socket creation failed: %s.", strerror(e));
}
@@ -1394,6 +1403,12 @@ connection_listener_new(const struct sockaddr *listensockaddr,
dnsserv_configure_listener(conn);
}
+ /*
+ * Normal exit; call the OOS handler since connection count just changed;
+ * the exhaustion flag will always be zero here though.
+ */
+ connection_check_oos(get_n_open_sockets(), 0);
+
return conn;
err:
@@ -1402,6 +1417,9 @@ connection_listener_new(const struct sockaddr *listensockaddr,
if (conn)
connection_free(conn);
+ /* Call the OOS handler, indicate if we saw an exhaustion-related error */
+ connection_check_oos(get_n_open_sockets(), exhaustion);
+
return NULL;
}
@@ -1492,21 +1510,34 @@ connection_handle_listener_read(connection_t *conn, int new_type)
if (!SOCKET_OK(news)) { /* accept() error */
int e = tor_socket_errno(conn->s);
if (ERRNO_IS_ACCEPT_EAGAIN(e)) {
- return 0; /* they hung up before we could accept(). that's fine. */
+ /*
+ * they hung up before we could accept(). that's fine.
+ *
+ * give the OOS handler a chance to run though
+ */
+ connection_check_oos(get_n_open_sockets(), 0);
+ return 0;
} else if (ERRNO_IS_RESOURCE_LIMIT(e)) {
warn_too_many_conns();
+ /* Exhaustion; tell the OOS handler */
+ connection_check_oos(get_n_open_sockets(), 1);
return 0;
}
/* else there was a real error. */
log_warn(LD_NET,"accept() failed: %s. Closing listener.",
tor_socket_strerror(e));
connection_mark_for_close(conn);
+ /* Tell the OOS handler about this too */
+ connection_check_oos(get_n_open_sockets(), 0);
return -1;
}
log_debug(LD_NET,
"Connection accepted on socket %d (child of fd %d).",
(int)news,(int)conn->s);
+ /* We accepted a new conn; run OOS handler */
+ connection_check_oos(get_n_open_sockets(), 0);
+
if (make_socket_reuseable(news) < 0) {
if (tor_socket_errno(news) == EINVAL) {
/* This can happen on OSX if we get a badly timed shutdown. */
@@ -1567,16 +1598,19 @@ connection_handle_listener_read(connection_t *conn, int new_type)
/* remember the remote address */
tor_addr_copy(&newconn->addr, &addr);
- newconn->port = port;
- newconn->address = tor_dup_addr(&addr);
+ if (new_type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) {
+ newconn->port = 0;
+ newconn->address = tor_strdup(conn->address);
+ } else {
+ newconn->port = port;
+ newconn->address = tor_addr_to_str_dup(&addr);
+ }
if (new_type == CONN_TYPE_AP && conn->socket_family != AF_UNIX) {
log_info(LD_NET, "New SOCKS connection opened from %s.",
fmt_and_decorate_addr(&addr));
}
if (new_type == CONN_TYPE_AP && conn->socket_family == AF_UNIX) {
- newconn->port = 0;
- newconn->address = tor_strdup(conn->address);
log_info(LD_NET, "New SOCKS AF_UNIX connection opened");
}
if (new_type == CONN_TYPE_CONTROL) {
@@ -1711,12 +1745,18 @@ connection_connect_sockaddr,(connection_t *conn,
s = tor_open_socket_nonblocking(protocol_family, SOCK_STREAM, proto);
if (! SOCKET_OK(s)) {
+ /*
+ * Early OOS handler calls; it matters if it's an exhaustion-related
+ * error or not.
+ */
*socket_error = tor_socket_errno(s);
if (ERRNO_IS_RESOURCE_LIMIT(*socket_error)) {
warn_too_many_conns();
+ connection_check_oos(get_n_open_sockets(), 1);
} else {
log_warn(LD_NET,"Error creating network socket: %s",
tor_socket_strerror(*socket_error));
+ connection_check_oos(get_n_open_sockets(), 0);
}
return -1;
}
@@ -1726,6 +1766,13 @@ connection_connect_sockaddr,(connection_t *conn,
tor_socket_strerror(errno));
}
+ /*
+ * We've got the socket open; give the OOS handler a chance to check
+ * against configuured maximum socket number, but tell it no exhaustion
+ * failure.
+ */
+ connection_check_oos(get_n_open_sockets(), 0);
+
if (bindaddr && bind(s, bindaddr, bindaddr_len) < 0) {
*socket_error = tor_socket_errno(s);
log_warn(LD_NET,"Error binding network socket: %s",
@@ -2246,7 +2293,7 @@ connection_send_socks5_connect(connection_t *conn)
} else { /* AF_INET6 */
buf[3] = 4;
reqsize += 16;
- memcpy(buf + 4, tor_addr_to_in6(&conn->addr), 16);
+ memcpy(buf + 4, tor_addr_to_in6_addr8(&conn->addr), 16);
memcpy(buf + 20, &port, 2);
}
@@ -2255,18 +2302,13 @@ connection_send_socks5_connect(connection_t *conn)
conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK;
}
-/** Wrapper around fetch_from_(buf/evbuffer)_socks_client: see those functions
+/** Wrapper around fetch_from_buf_socks_client: see that functions
* for documentation of its behavior. */
static int
connection_fetch_from_buf_socks_client(connection_t *conn,
int state, char **reason)
{
- IF_HAS_BUFFEREVENT(conn, {
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- return fetch_from_evbuffer_socks_client(input, state, reason);
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_from_buf_socks_client(conn->inbuf, state, reason);
- }
+ return fetch_from_buf_socks_client(conn->inbuf, state, reason);
}
/** Call this from connection_*_process_inbuf() to advance the proxy
@@ -2542,7 +2584,7 @@ retry_listener_ports(smartlist_t *old_conns,
real_port,
listensockaddr,
sizeof(struct sockaddr_storage));
- address = tor_dup_addr(&port->addr);
+ address = tor_addr_to_str_dup(&port->addr);
}
if (listensockaddr) {
@@ -2700,23 +2742,15 @@ connection_is_rate_limited(connection_t *conn)
return 1;
}
-#ifdef USE_BUFFEREVENTS
-static struct bufferevent_rate_limit_group *global_rate_limit = NULL;
-#else
-extern int global_read_bucket, global_write_bucket;
-extern int global_relayed_read_bucket, global_relayed_write_bucket;
-
/** Did either global write bucket run dry last second? If so,
* we are likely to run dry again this second, so be stingy with the
* tokens we just put in. */
static int write_buckets_empty_last_second = 0;
-#endif
/** How many seconds of no active local circuits will make the
* connection revert to the "relayed" bandwidth class? */
#define CLIENT_IDLE_TIME_FOR_PRIORITY 30
-#ifndef USE_BUFFEREVENTS
/** Return 1 if <b>conn</b> should use tokens from the "relayed"
* bandwidth rates, else 0. Currently, only OR conns with bandwidth
* class 1, and directory conns that are serving data out, count.
@@ -2828,20 +2862,6 @@ connection_bucket_write_limit(connection_t *conn, time_t now)
return connection_bucket_round_robin(base, priority,
global_bucket, conn_bucket);
}
-#else
-static ssize_t
-connection_bucket_read_limit(connection_t *conn, time_t now)
-{
- (void) now;
- return bufferevent_get_max_to_read(conn->bufev);
-}
-ssize_t
-connection_bucket_write_limit(connection_t *conn, time_t now)
-{
- (void) now;
- return bufferevent_get_max_to_write(conn->bufev);
-}
-#endif
/** Return 1 if the global write buckets are low enough that we
* shouldn't send <b>attempt</b> bytes of low-priority directory stuff
@@ -2865,12 +2885,8 @@ connection_bucket_write_limit(connection_t *conn, time_t now)
int
global_write_bucket_low(connection_t *conn, size_t attempt, int priority)
{
-#ifdef USE_BUFFEREVENTS
- ssize_t smaller_bucket = bufferevent_get_max_to_write(conn->bufev);
-#else
int smaller_bucket = global_write_bucket < global_relayed_write_bucket ?
global_write_bucket : global_relayed_write_bucket;
-#endif
if (authdir_mode(get_options()) && priority>1)
return 0; /* there's always room to answer v2 if we're an auth dir */
@@ -2880,10 +2896,8 @@ global_write_bucket_low(connection_t *conn, size_t attempt, int priority)
if (smaller_bucket < (int)attempt)
return 1; /* not enough space no matter the priority */
-#ifndef USE_BUFFEREVENTS
if (write_buckets_empty_last_second)
return 1; /* we're already hitting our limits, no more please */
-#endif
if (priority == 1) { /* old-style v1 query */
/* Could we handle *two* of these requests within the next two seconds? */
@@ -2931,29 +2945,6 @@ record_num_bytes_transferred_impl(connection_t *conn,
rep_hist_note_exit_bytes(conn->port, num_written, num_read);
}
-#ifdef USE_BUFFEREVENTS
-/** Wrapper around fetch_from_(buf/evbuffer)_socks_client: see those functions
- * for documentation of its behavior. */
-static void
-record_num_bytes_transferred(connection_t *conn,
- time_t now, size_t num_read, size_t num_written)
-{
- /* XXX024 check if this is necessary */
- if (num_written >= INT_MAX || num_read >= INT_MAX) {
- log_err(LD_BUG, "Value out of range. num_read=%lu, num_written=%lu, "
- "connection type=%s, state=%s",
- (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();
- }
-
- record_num_bytes_transferred_impl(conn,now,num_read,num_written);
-}
-#endif
-
/** Helper: convert given <b>tvnow</b> time value to milliseconds since
* midnight. */
static uint32_t
@@ -2998,7 +2989,6 @@ connection_buckets_note_empty_ts(uint32_t *timestamp_var,
*timestamp_var = msec_since_midnight(tvnow);
}
-#ifndef USE_BUFFEREVENTS
/** Last time at which the global or relay buckets were emptied in msec
* since midnight. */
static uint32_t global_relayed_read_emptied = 0,
@@ -3329,92 +3319,6 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn)
return 1;
}
-#else
-static void
-connection_buckets_decrement(connection_t *conn, time_t now,
- size_t num_read, size_t num_written)
-{
- (void) conn;
- (void) now;
- (void) num_read;
- (void) num_written;
- /* Libevent does this for us. */
-}
-
-void
-connection_bucket_refill(int seconds_elapsed, time_t now)
-{
- (void) seconds_elapsed;
- (void) now;
- /* Libevent does this for us. */
-}
-void
-connection_bucket_init(void)
-{
- const or_options_t *options = get_options();
- const struct timeval *tick = tor_libevent_get_one_tick_timeout();
- struct ev_token_bucket_cfg *bucket_cfg;
-
- uint64_t rate, burst;
- if (options->RelayBandwidthRate) {
- rate = options->RelayBandwidthRate;
- burst = options->RelayBandwidthBurst;
- } else {
- rate = options->BandwidthRate;
- burst = options->BandwidthBurst;
- }
-
- /* This can't overflow, since TokenBucketRefillInterval <= 1000,
- * and rate started out less than INT32_MAX. */
- rate = (rate * options->TokenBucketRefillInterval) / 1000;
-
- bucket_cfg = ev_token_bucket_cfg_new((uint32_t)rate, (uint32_t)burst,
- (uint32_t)rate, (uint32_t)burst,
- tick);
-
- if (!global_rate_limit) {
- global_rate_limit =
- bufferevent_rate_limit_group_new(tor_libevent_get_base(), bucket_cfg);
- } else {
- bufferevent_rate_limit_group_set_cfg(global_rate_limit, bucket_cfg);
- }
- ev_token_bucket_cfg_free(bucket_cfg);
-}
-
-void
-connection_get_rate_limit_totals(uint64_t *read_out, uint64_t *written_out)
-{
- if (global_rate_limit == NULL) {
- *read_out = *written_out = 0;
- } else {
- bufferevent_rate_limit_group_get_totals(
- global_rate_limit, read_out, written_out);
- }
-}
-
-/** Perform whatever operations are needed on <b>conn</b> to enable
- * rate-limiting. */
-void
-connection_enable_rate_limiting(connection_t *conn)
-{
- if (conn->bufev) {
- if (!global_rate_limit)
- connection_bucket_init();
- tor_add_bufferevent_to_rate_limit_group(conn->bufev, global_rate_limit);
- }
-}
-
-static void
-connection_consider_empty_write_buckets(connection_t *conn)
-{
- (void) conn;
-}
-static void
-connection_consider_empty_read_buckets(connection_t *conn)
-{
- (void) conn;
-}
-#endif
/** Read bytes from conn-\>s and process them.
*
@@ -3648,7 +3552,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read,
* 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);
- if (r2<0) {
+ if (BUG(r2<0)) {
log_warn(LD_BUG, "apparently, reading pending bytes can fail.");
return -1;
}
@@ -3745,171 +3649,11 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read,
return 0;
}
-#ifdef USE_BUFFEREVENTS
-/* XXXX These generic versions could be simplified by making them
- type-specific */
-
-/** Callback: Invoked whenever bytes are added to or drained from an input
- * evbuffer. Used to track the number of bytes read. */
-static void
-evbuffer_inbuf_callback(struct evbuffer *buf,
- const struct evbuffer_cb_info *info, void *arg)
-{
- connection_t *conn = arg;
- (void) buf;
- /* XXXX These need to get real counts on the non-nested TLS case. - NM */
- if (info->n_added) {
- time_t now = approx_time();
- conn->timestamp_lastread = now;
- record_num_bytes_transferred(conn, now, info->n_added, 0);
- connection_consider_empty_read_buckets(conn);
- if (conn->type == CONN_TYPE_AP) {
- edge_connection_t *edge_conn = TO_EDGE_CONN(conn);
- /*XXXX024 check for overflow*/
- edge_conn->n_read += (int)info->n_added;
- }
- }
-}
-
-/** Callback: Invoked whenever bytes are added to or drained from an output
- * evbuffer. Used to track the number of bytes written. */
-static void
-evbuffer_outbuf_callback(struct evbuffer *buf,
- const struct evbuffer_cb_info *info, void *arg)
-{
- connection_t *conn = arg;
- (void)buf;
- if (info->n_deleted) {
- time_t now = approx_time();
- conn->timestamp_lastwritten = now;
- record_num_bytes_transferred(conn, now, 0, info->n_deleted);
- connection_consider_empty_write_buckets(conn);
- if (conn->type == CONN_TYPE_AP) {
- edge_connection_t *edge_conn = TO_EDGE_CONN(conn);
- /*XXXX024 check for overflow*/
- edge_conn->n_written += (int)info->n_deleted;
- }
- }
-}
-
-/** Callback: invoked whenever a bufferevent has read data. */
-void
-connection_handle_read_cb(struct bufferevent *bufev, void *arg)
-{
- connection_t *conn = arg;
- (void) bufev;
- if (!conn->marked_for_close) {
- if (connection_process_inbuf(conn, 1)<0) /* XXXX Always 1? */
- if (!conn->marked_for_close)
- connection_mark_for_close(conn);
- }
-}
-
-/** Callback: invoked whenever a bufferevent has written data. */
-void
-connection_handle_write_cb(struct bufferevent *bufev, void *arg)
-{
- connection_t *conn = arg;
- struct evbuffer *output;
- if (connection_flushed_some(conn)<0) {
- if (!conn->marked_for_close)
- connection_mark_for_close(conn);
- return;
- }
-
- output = bufferevent_get_output(bufev);
- if (!evbuffer_get_length(output)) {
- connection_finished_flushing(conn);
- if (conn->marked_for_close && conn->hold_open_until_flushed) {
- conn->hold_open_until_flushed = 0;
- if (conn->linked) {
- /* send eof */
- bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED);
- }
- }
- }
-}
-
-/** Callback: invoked whenever a bufferevent has had an event (like a
- * connection, or an eof, or an error) occur. */
-void
-connection_handle_event_cb(struct bufferevent *bufev, short event, void *arg)
-{
- connection_t *conn = arg;
- (void) bufev;
- if (conn->marked_for_close)
- return;
-
- if (event & BEV_EVENT_CONNECTED) {
- tor_assert(connection_state_is_connecting(conn));
- if (connection_finished_connecting(conn)<0)
- return;
- }
- if (event & BEV_EVENT_EOF) {
- if (!conn->marked_for_close) {
- conn->inbuf_reached_eof = 1;
- if (connection_reached_eof(conn)<0)
- return;
- }
- }
- if (event & BEV_EVENT_ERROR) {
- int socket_error = evutil_socket_geterror(conn->s);
- if (conn->type == CONN_TYPE_OR &&
- conn->state == OR_CONN_STATE_CONNECTING) {
- connection_or_connect_failed(TO_OR_CONN(conn),
- errno_to_orconn_end_reason(socket_error),
- tor_socket_strerror(socket_error));
- } else if (CONN_IS_EDGE(conn)) {
- edge_connection_t *edge_conn = TO_EDGE_CONN(conn);
- if (!edge_conn->edge_has_sent_end)
- connection_edge_end_errno(edge_conn);
- if (conn->type == CONN_TYPE_AP && TO_ENTRY_CONN(conn)->socks_request) {
- /* broken, don't send a socks reply back */
- TO_ENTRY_CONN(conn)->socks_request->has_finished = 1;
- }
- }
- connection_close_immediate(conn); /* Connection is dead. */
- if (!conn->marked_for_close)
- connection_mark_for_close(conn);
- }
-}
-
-/** Set up the generic callbacks for the bufferevent on <b>conn</b>. */
-void
-connection_configure_bufferevent_callbacks(connection_t *conn)
-{
- struct bufferevent *bufev;
- struct evbuffer *input, *output;
- tor_assert(conn->bufev);
- bufev = conn->bufev;
- bufferevent_setcb(bufev,
- connection_handle_read_cb,
- connection_handle_write_cb,
- connection_handle_event_cb,
- conn);
- /* Set a fairly high write low-watermark so that we get the write callback
- called whenever data is written to bring us under 128K. Leave the
- high-watermark at 0.
- */
- bufferevent_setwatermark(bufev, EV_WRITE, 128*1024, 0);
-
- input = bufferevent_get_input(bufev);
- output = bufferevent_get_output(bufev);
- evbuffer_add_cb(input, evbuffer_inbuf_callback, conn);
- evbuffer_add_cb(output, evbuffer_outbuf_callback, conn);
-}
-#endif
-
/** A pass-through to fetch_from_buf. */
int
connection_fetch_from_buf(char *string, size_t len, connection_t *conn)
{
- IF_HAS_BUFFEREVENT(conn, {
- /* XXX overflow -seb */
- return (int)bufferevent_read(conn->bufev, string, len);
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_from_buf(string, len, conn->inbuf);
- }
+ return fetch_from_buf(string, len, conn->inbuf);
}
/** As fetch_from_buf_line(), but read from a connection's input buffer. */
@@ -3917,43 +3661,19 @@ int
connection_fetch_from_buf_line(connection_t *conn, char *data,
size_t *data_len)
{
- IF_HAS_BUFFEREVENT(conn, {
- int r;
- size_t eol_len=0;
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- struct evbuffer_ptr ptr =
- evbuffer_search_eol(input, NULL, &eol_len, EVBUFFER_EOL_LF);
- if (ptr.pos == -1)
- return 0; /* No EOL found. */
- if ((size_t)ptr.pos+eol_len >= *data_len) {
- return -1; /* Too long */
- }
- *data_len = ptr.pos+eol_len;
- r = evbuffer_remove(input, data, ptr.pos+eol_len);
- tor_assert(r >= 0);
- data[ptr.pos+eol_len] = '\0';
- return 1;
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_from_buf_line(conn->inbuf, data, data_len);
- }
+ return fetch_from_buf_line(conn->inbuf, data, data_len);
}
-/** As fetch_from_buf_http, but fetches from a connection's input buffer_t or
- * its bufferevent as appropriate. */
+/** As fetch_from_buf_http, but fetches from a connection's input buffer_t as
+ * appropriate. */
int
connection_fetch_from_buf_http(connection_t *conn,
char **headers_out, size_t max_headerlen,
char **body_out, size_t *body_used,
size_t max_bodylen, int force_complete)
{
- IF_HAS_BUFFEREVENT(conn, {
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- return fetch_from_evbuffer_http(input, headers_out, max_headerlen,
- body_out, body_used, max_bodylen, force_complete);
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_from_buf_http(conn->inbuf, headers_out, max_headerlen,
- body_out, body_used, max_bodylen, force_complete);
- }
+ return fetch_from_buf_http(conn->inbuf, headers_out, max_headerlen,
+ body_out, body_used, max_bodylen, force_complete);
}
/** Return conn-\>outbuf_flushlen: how many bytes conn wants to flush
@@ -4145,7 +3865,7 @@ connection_handle_write_impl(connection_t *conn, int force)
or_conn->bytes_xmitted += result;
or_conn->bytes_xmitted_by_tls += n_written;
/* So we notice bytes were written even on error */
- /* XXXX024 This cast is safe since we can never write INT_MAX bytes in a
+ /* XXXX This cast is safe since we can never write INT_MAX bytes in a
* single set of TLS operations. But it looks kinda ugly. If we refactor
* the *_buf_tls functions, we should make them return ssize_t or size_t
* or something. */
@@ -4257,7 +3977,7 @@ connection_handle_write(connection_t *conn, int force)
* Try to flush data that's waiting for a write on <b>conn</b>. Return
* -1 on failure, 0 on success.
*
- * Don't use this function for regular writing; the buffers/bufferevents
+ * Don't use this function for regular writing; the buffers
* system should be good enough at scheduling writes there. Instead, this
* function is for cases when we're about to exit or something and we want
* to report it right away.
@@ -4265,10 +3985,6 @@ connection_handle_write(connection_t *conn, int force)
int
connection_flush(connection_t *conn)
{
- IF_HAS_BUFFEREVENT(conn, {
- int r = bufferevent_flush(conn->bufev, EV_WRITE, BEV_FLUSH);
- return (r < 0) ? -1 : 0;
- });
return connection_handle_write(conn, 1);
}
@@ -4297,22 +4013,6 @@ connection_write_to_buf_impl_,(const char *string, size_t len,
if (conn->marked_for_close && !conn->hold_open_until_flushed)
return;
- IF_HAS_BUFFEREVENT(conn, {
- if (zlib) {
- int done = zlib < 0;
- r = write_to_evbuffer_zlib(bufferevent_get_output(conn->bufev),
- TO_DIR_CONN(conn)->zlib_state,
- string, len, done);
- } else {
- r = bufferevent_write(conn->bufev, string, len);
- }
- if (r < 0) {
- /* XXXX mark for close? */
- log_warn(LD_NET, "bufferevent_write failed! That shouldn't happen.");
- }
- return;
- });
-
old_datalen = buf_datalen(conn->outbuf);
if (zlib) {
dir_connection_t *dir_conn = TO_DIR_CONN(conn);
@@ -4758,7 +4458,7 @@ connection_flushed_some(connection_t *conn)
}
/** We just finished flushing bytes to the appropriately low network layer,
- * and there are no more bytes remaining in conn-\>outbuf, conn-\>bev, or
+ * and there are no more bytes remaining in conn-\>outbuf or
* conn-\>tls to be flushed.
*
* This function just passes conn to the connection-specific
@@ -4775,8 +4475,7 @@ connection_finished_flushing(connection_t *conn)
// log_fn(LOG_DEBUG,"entered. Socket %u.", conn->s);
- IF_HAS_NO_BUFFEREVENT(conn)
- connection_stop_writing(conn);
+ connection_stop_writing(conn);
switch (conn->type) {
case CONN_TYPE_OR:
@@ -4852,6 +4551,256 @@ connection_reached_eof(connection_t *conn)
}
}
+/** Comparator for the two-orconn case in OOS victim sort */
+static int
+oos_victim_comparator_for_orconns(or_connection_t *a, or_connection_t *b)
+{
+ int a_circs, b_circs;
+ /* Fewer circuits == higher priority for OOS kill, sort earlier */
+
+ a_circs = connection_or_get_num_circuits(a);
+ b_circs = connection_or_get_num_circuits(b);
+
+ if (a_circs < b_circs) return 1;
+ else if (a_circs > b_circs) return -1;
+ else return 0;
+}
+
+/** Sort comparator for OOS victims; better targets sort before worse
+ * ones. */
+static int
+oos_victim_comparator(const void **a_v, const void **b_v)
+{
+ connection_t *a = NULL, *b = NULL;
+
+ /* Get connection pointers out */
+
+ a = (connection_t *)(*a_v);
+ b = (connection_t *)(*b_v);
+
+ tor_assert(a != NULL);
+ tor_assert(b != NULL);
+
+ /*
+ * We always prefer orconns as victims currently; we won't even see
+ * these non-orconn cases, but if we do, sort them after orconns.
+ */
+ if (a->type == CONN_TYPE_OR && b->type == CONN_TYPE_OR) {
+ return oos_victim_comparator_for_orconns(TO_OR_CONN(a), TO_OR_CONN(b));
+ } else {
+ /*
+ * One isn't an orconn; if one is, it goes first. We currently have no
+ * opinions about cases where neither is an orconn.
+ */
+ if (a->type == CONN_TYPE_OR) return -1;
+ else if (b->type == CONN_TYPE_OR) return 1;
+ else return 0;
+ }
+}
+
+/** Pick n victim connections for the OOS handler and return them in a
+ * smartlist.
+ */
+MOCK_IMPL(STATIC smartlist_t *,
+pick_oos_victims, (int n))
+{
+ smartlist_t *eligible = NULL, *victims = NULL;
+ smartlist_t *conns;
+ int conn_counts_by_type[CONN_TYPE_MAX_ + 1], i;
+
+ /*
+ * Big damn assumption (someone improve this someday!):
+ *
+ * Socket exhaustion normally happens on high-volume relays, and so
+ * most of the connections involved are orconns. We should pick victims
+ * by assembling a list of all orconns, and sorting them in order of
+ * how much 'damage' by some metric we'd be doing by dropping them.
+ *
+ * If we move on from orconns, we should probably think about incoming
+ * directory connections next, or exit connections. Things we should
+ * probably never kill are controller connections and listeners.
+ *
+ * This function will count how many connections of different types
+ * exist and log it for purposes of gathering data on typical OOS
+ * situations to guide future improvements.
+ */
+
+ /* First, get the connection array */
+ conns = get_connection_array();
+ /*
+ * Iterate it and pick out eligible connection types, and log some stats
+ * along the way.
+ */
+ eligible = smartlist_new();
+ memset(conn_counts_by_type, 0, sizeof(conn_counts_by_type));
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, c) {
+ /* Bump the counter */
+ tor_assert(c->type <= CONN_TYPE_MAX_);
+ ++(conn_counts_by_type[c->type]);
+
+ /* Skip anything without a socket we can free */
+ if (!(SOCKET_OK(c->s))) {
+ continue;
+ }
+
+ /* Skip anything we would count as moribund */
+ if (connection_is_moribund(c)) {
+ continue;
+ }
+
+ switch (c->type) {
+ case CONN_TYPE_OR:
+ /* We've got an orconn, it's eligible to be OOSed */
+ smartlist_add(eligible, c);
+ break;
+ default:
+ /* We don't know what to do with it, ignore it */
+ break;
+ }
+ } SMARTLIST_FOREACH_END(c);
+
+ /* Log some stats */
+ if (smartlist_len(conns) > 0) {
+ /* At least one counter must be non-zero */
+ log_info(LD_NET, "Some stats on conn types seen during OOS follow");
+ for (i = CONN_TYPE_MIN_; i <= CONN_TYPE_MAX_; ++i) {
+ /* Did we see any? */
+ if (conn_counts_by_type[i] > 0) {
+ log_info(LD_NET, "%s: %d conns",
+ conn_type_to_string(i),
+ conn_counts_by_type[i]);
+ }
+ }
+ log_info(LD_NET, "Done with OOS conn type stats");
+ }
+
+ /* Did we find more eligible targets than we want to kill? */
+ if (smartlist_len(eligible) > n) {
+ /* Sort the list in order of target preference */
+ smartlist_sort(eligible, oos_victim_comparator);
+ /* Pick first n as victims */
+ victims = smartlist_new();
+ for (i = 0; i < n; ++i) {
+ smartlist_add(victims, smartlist_get(eligible, i));
+ }
+ /* Free the original list */
+ smartlist_free(eligible);
+ } else {
+ /* No, we can just call them all victims */
+ victims = eligible;
+ }
+
+ return victims;
+}
+
+/** Kill a list of connections for the OOS handler. */
+MOCK_IMPL(STATIC void,
+kill_conn_list_for_oos, (smartlist_t *conns))
+{
+ if (!conns) return;
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, c) {
+ /* Make sure the channel layer gets told about orconns */
+ if (c->type == CONN_TYPE_OR) {
+ connection_or_close_for_error(TO_OR_CONN(c), 1);
+ } else {
+ connection_mark_for_close(c);
+ }
+ } SMARTLIST_FOREACH_END(c);
+
+ log_notice(LD_NET,
+ "OOS handler marked %d connections",
+ smartlist_len(conns));
+}
+
+/** Out-of-Sockets handler; n_socks is the current number of open
+ * sockets, and failed is non-zero if a socket exhaustion related
+ * error immediately preceded this call. This is where to do
+ * circuit-killing heuristics as needed.
+ */
+void
+connection_check_oos(int n_socks, int failed)
+{
+ int target_n_socks = 0, moribund_socks, socks_to_kill;
+ smartlist_t *conns;
+
+ /* Early exit: is OOS checking disabled? */
+ if (get_options()->DisableOOSCheck) {
+ return;
+ }
+
+ /* Sanity-check args */
+ tor_assert(n_socks >= 0);
+
+ /*
+ * Make some log noise; keep it at debug level since this gets a chance
+ * to run on every connection attempt.
+ */
+ log_debug(LD_NET,
+ "Running the OOS handler (%d open sockets, %s)",
+ n_socks, (failed != 0) ? "exhaustion seen" : "no exhaustion");
+
+ /*
+ * Check if we're really handling an OOS condition, and if so decide how
+ * many sockets we want to get down to. Be sure we check if the threshold
+ * is distinct from zero first; it's possible for this to be called a few
+ * times before we've finished reading the config.
+ */
+ if (n_socks >= get_options()->ConnLimit_high_thresh &&
+ get_options()->ConnLimit_high_thresh != 0 &&
+ get_options()->ConnLimit_ != 0) {
+ /* Try to get down to the low threshold */
+ target_n_socks = get_options()->ConnLimit_low_thresh;
+ log_notice(LD_NET,
+ "Current number of sockets %d is greater than configured "
+ "limit %d; OOS handler trying to get down to %d",
+ n_socks, get_options()->ConnLimit_high_thresh,
+ target_n_socks);
+ } else if (failed) {
+ /*
+ * If we're not at the limit but we hit a socket exhaustion error, try to
+ * drop some (but not as aggressively as ConnLimit_low_threshold, which is
+ * 3/4 of ConnLimit_)
+ */
+ target_n_socks = (n_socks * 9) / 10;
+ log_notice(LD_NET,
+ "We saw socket exhaustion at %d open sockets; OOS handler "
+ "trying to get down to %d",
+ n_socks, target_n_socks);
+ }
+
+ if (target_n_socks > 0) {
+ /*
+ * It's an OOS!
+ *
+ * Count moribund sockets; it's be important that anything we decide
+ * to get rid of here but don't immediately close get counted as moribund
+ * on subsequent invocations so we don't try to kill too many things if
+ * connection_check_oos() gets called multiple times.
+ */
+ moribund_socks = connection_count_moribund();
+
+ if (moribund_socks < n_socks - target_n_socks) {
+ socks_to_kill = n_socks - target_n_socks - moribund_socks;
+
+ conns = pick_oos_victims(socks_to_kill);
+ if (conns) {
+ kill_conn_list_for_oos(conns);
+ log_notice(LD_NET,
+ "OOS handler killed %d conns", smartlist_len(conns));
+ smartlist_free(conns);
+ } else {
+ log_notice(LD_NET, "OOS handler failed to pick any victim conns");
+ }
+ } else {
+ log_notice(LD_NET,
+ "Not killing any sockets for OOS because there are %d "
+ "already moribund, and we only want to eliminate %d",
+ moribund_socks, n_socks - target_n_socks);
+ }
+ }
+}
+
/** Log how many bytes are used by buffers of different kinds and sizes. */
void
connection_dump_buffer_mem_stats(int severity)
@@ -4910,15 +4859,6 @@ assert_connection_ok(connection_t *conn, time_t now)
tor_assert(conn->type >= CONN_TYPE_MIN_);
tor_assert(conn->type <= CONN_TYPE_MAX_);
-#ifdef USE_BUFFEREVENTS
- if (conn->bufev) {
- tor_assert(conn->read_event == NULL);
- tor_assert(conn->write_event == NULL);
- tor_assert(conn->inbuf == NULL);
- tor_assert(conn->outbuf == NULL);
- }
-#endif
-
switch (conn->type) {
case CONN_TYPE_OR:
case CONN_TYPE_EXT_OR:
@@ -5182,11 +5122,6 @@ connection_free_all(void)
tor_free(last_interface_ipv4);
tor_free(last_interface_ipv6);
-
-#ifdef USE_BUFFEREVENTS
- if (global_rate_limit)
- bufferevent_rate_limit_group_free(global_rate_limit);
-#endif
}
/** Log a warning, and possibly emit a control event, that <b>received</b> came
diff --git a/src/or/connection.h b/src/or/connection.h
index 4835235fba..d25e002fa4 100644
--- a/src/or/connection.h
+++ b/src/or/connection.h
@@ -34,8 +34,8 @@ void connection_about_to_close_connection(connection_t *conn);
void connection_close_immediate(connection_t *conn);
void connection_mark_for_close_(connection_t *conn,
int line, const char *file);
-void connection_mark_for_close_internal_(connection_t *conn,
- int line, const char *file);
+MOCK_DECL(void, connection_mark_for_close_internal_,
+ (connection_t *conn, int line, const char *file));
#define connection_mark_for_close(c) \
connection_mark_for_close_((c), __LINE__, SHORT_FILE__)
@@ -52,13 +52,11 @@ void connection_mark_for_close_internal_(connection_t *conn,
* For all other cases, use connection_mark_and_flush() instead, which
* checks for or_connection_t properly, instead. See below.
*/
-#define connection_mark_and_flush_internal_(c,line,file) \
- do { \
- connection_t *tmp_conn_ = (c); \
- connection_mark_for_close_internal_(tmp_conn_, (line), (file)); \
- tmp_conn_->hold_open_until_flushed = 1; \
- IF_HAS_BUFFEREVENT(tmp_conn_, \
- connection_start_writing(tmp_conn_)); \
+#define connection_mark_and_flush_internal_(c,line,file) \
+ do { \
+ connection_t *tmp_conn__ = (c); \
+ connection_mark_for_close_internal_(tmp_conn__, (line), (file)); \
+ tmp_conn__->hold_open_until_flushed = 1; \
} while (0)
#define connection_mark_and_flush_internal(c) \
@@ -166,21 +164,13 @@ static size_t connection_get_outbuf_len(connection_t *conn);
static inline size_t
connection_get_inbuf_len(connection_t *conn)
{
- IF_HAS_BUFFEREVENT(conn, {
- return evbuffer_get_length(bufferevent_get_input(conn->bufev));
- }) ELSE_IF_NO_BUFFEREVENT {
- return conn->inbuf ? buf_datalen(conn->inbuf) : 0;
- }
+ return conn->inbuf ? buf_datalen(conn->inbuf) : 0;
}
static inline size_t
connection_get_outbuf_len(connection_t *conn)
{
- IF_HAS_BUFFEREVENT(conn, {
- return evbuffer_get_length(bufferevent_get_output(conn->bufev));
- }) ELSE_IF_NO_BUFFEREVENT {
return conn->outbuf ? buf_datalen(conn->outbuf) : 0;
- }
}
connection_t *connection_get_by_global_id(uint64_t id);
@@ -257,19 +247,21 @@ void clock_skew_warning(const connection_t *conn, long apparent_skew,
int trusted, log_domain_mask_t domain,
const char *received, const char *source);
-#ifdef USE_BUFFEREVENTS
-int connection_type_uses_bufferevent(connection_t *conn);
-void connection_configure_bufferevent_callbacks(connection_t *conn);
-void connection_handle_read_cb(struct bufferevent *bufev, void *arg);
-void connection_handle_write_cb(struct bufferevent *bufev, void *arg);
-void connection_handle_event_cb(struct bufferevent *bufev, short event,
- void *arg);
-void connection_get_rate_limit_totals(uint64_t *read_out,
- uint64_t *written_out);
-void connection_enable_rate_limiting(connection_t *conn);
-#else
-#define connection_type_uses_bufferevent(c) (0)
-#endif
+/** Check if a connection is on the way out so the OOS handler doesn't try
+ * to kill more than it needs. */
+static inline int
+connection_is_moribund(connection_t *conn)
+{
+ if (conn != NULL &&
+ (conn->conn_array_index < 0 ||
+ conn->marked_for_close)) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+void connection_check_oos(int n_socks, int failed);
#ifdef CONNECTION_PRIVATE
STATIC void connection_free_(connection_t *conn);
@@ -289,6 +281,9 @@ MOCK_DECL(STATIC int,connection_connect_sockaddr,
const struct sockaddr *bindaddr,
socklen_t bindaddr_len,
int *socket_error));
+MOCK_DECL(STATIC void, kill_conn_list_for_oos, (smartlist_t *conns));
+MOCK_DECL(STATIC smartlist_t *, pick_oos_victims, (int n));
+
#endif
#endif
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 8098fb017b..12fe2f57c9 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -7,6 +7,51 @@
/**
* \file connection_edge.c
* \brief Handle edge streams.
+ *
+ * An edge_connection_t is a subtype of a connection_t, and represents two
+ * critical concepts in Tor: a stream, and an edge connection. From the Tor
+ * protocol's point of view, a stream is a bi-directional channel that is
+ * multiplexed on a single circuit. Each stream on a circuit is identified
+ * with a separate 16-bit stream ID, local to the (circuit,exit) pair.
+ * Streams are created in response to client requests.
+ *
+ * An edge connection is one thing that can implement a stream: it is either a
+ * TCP application socket that has arrived via (e.g.) a SOCKS request, or an
+ * exit connection.
+ *
+ * Not every instance of edge_connection_t truly represents an edge connction,
+ * however. (Sorry!) We also create edge_connection_t objects for streams that
+ * we will not be handling with TCP. The types of these streams are:
+ * <ul>
+ * <li>DNS lookup streams, created on the client side in response to
+ * a UDP DNS request received on a DNSPort, or a RESOLVE command
+ * on a controller.
+ * <li>DNS lookup streams, created on the exit side in response to
+ * a RELAY_RESOLVE cell from a client.
+ * <li>Tunneled directory streams, created on the directory cache side
+ * in response to a RELAY_BEGINDIR cell. These streams attach directly
+ * to a dir_connection_t object without ever using TCP.
+ * </ul>
+ *
+ * This module handles general-purpose functionality having to do with
+ * edge_connection_t. On the client side, it accepts various types of
+ * application requests on SocksPorts, TransPorts, and NATDPorts, and
+ * creates streams appropriately.
+ *
+ * This module is also responsible for implementing stream isolation:
+ * ensuring that streams that should not be linkable to one another are
+ * kept to different circuits.
+ *
+ * On the exit side, this module handles the various stream-creating
+ * type of RELAY cells by launching appropriate outgoing connections,
+ * DNS requests, or directory connection objects.
+ *
+ * And for all edge connections, this module is responsible for handling
+ * incoming and outdoing data as it arrives or leaves in the relay.c
+ * module. (Outgoing data will be packaged in
+ * connection_edge_process_inbuf() as it calls
+ * connection_edge_package_raw_inbuf(); incoming data from RELAY_DATA
+ * cells is applied in connection_edge_process_relay_cell().)
**/
#define CONNECTION_EDGE_PRIVATE
@@ -27,6 +72,7 @@
#include "control.h"
#include "dns.h"
#include "dnsserv.h"
+#include "directory.h"
#include "dirserv.h"
#include "hibernate.h"
#include "main.h"
@@ -214,6 +260,7 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial)
}
/* Fall through if the connection is on a circuit without optimistic
* data support. */
+ /* Falls through. */
case EXIT_CONN_STATE_CONNECTING:
case AP_CONN_STATE_RENDDESC_WAIT:
case AP_CONN_STATE_CIRCUIT_WAIT:
@@ -478,8 +525,7 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn)
rep_hist_note_exit_stream_opened(conn->port);
conn->state = EXIT_CONN_STATE_OPEN;
- IF_HAS_NO_BUFFEREVENT(conn)
- connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */
+ connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */
if (connection_get_outbuf_len(conn)) /* in case there are any queued relay
* cells */
connection_start_writing(conn);
@@ -820,7 +866,9 @@ connection_ap_attach_pending(int retry)
continue;
}
if (conn->state != AP_CONN_STATE_CIRCUIT_WAIT) {
- log_warn(LD_BUG, "%p is no longer in circuit_wait. Its current state "
+ // XXXX 030 -- this is downgraded in 0.2.9, since we apparently
+ // XXXX are running into it in practice. It's harmless.
+ log_info(LD_BUG, "%p is no longer in circuit_wait. Its current state "
"is %s. Why is it on pending_entry_connections?",
entry_conn,
conn_state_to_string(conn->type, conn->state));
@@ -928,7 +976,7 @@ connection_ap_warn_and_unmark_if_pending_circ(entry_connection_t *entry_conn,
/** Tell any AP streams that are waiting for a one-hop tunnel to
* <b>failed_digest</b> that they are going to fail. */
-/* XXX024 We should get rid of this function, and instead attach
+/* XXXX We should get rid of this function, and instead attach
* one-hop streams to circ->p_streams so they get marked in
* circuit_mark_for_close like normal p_streams. */
void
@@ -1035,8 +1083,8 @@ connection_ap_detach_retriable(entry_connection_t *conn,
pathbias_mark_use_rollback(circ);
if (conn->pending_optimistic_data) {
- generic_buffer_set_to_copy(&conn->sending_optimistic_data,
- conn->pending_optimistic_data);
+ buf_set_to_copy(&conn->sending_optimistic_data,
+ conn->pending_optimistic_data);
}
if (!get_options()->LeaveStreamsUnattached || conn->use_begindir) {
@@ -1237,7 +1285,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
/* Hang on, did we find an answer saying that this is a reverse lookup for
- * an internal address? If so, we should reject it if we're condigured to
+ * an internal address? If so, we should reject it if we're configured to
* do so. */
if (options->ClientDNSRejectInternalAddresses) {
/* Don't let people try to do a reverse lookup on 10.0.0.1. */
@@ -1451,7 +1499,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
return -1;
}
- /* XXXX024-1090 Should we also allow foo.bar.exit if ExitNodes is set and
+ /* XXXX-1090 Should we also allow foo.bar.exit if ExitNodes is set and
Bar is not listed in it? I say yes, but our revised manpage branch
implies no. */
}
@@ -1476,14 +1524,61 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
/* If we're running in Tor2webMode, we don't allow anything BUT .onion
* addresses. */
if (options->Tor2webMode) {
- log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname %s "
- "because tor2web mode is enabled.",
+ log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname "
+ "or IP address %s because tor2web mode is enabled.",
safe_str_client(socks->address));
connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
return -1;
}
#endif
+ /* socks->address is a non-onion hostname or IP address.
+ * If we can't do any non-onion requests, refuse the connection.
+ * If we have a hostname but can't do DNS, refuse the connection.
+ * If we have an IP address, but we can't use that address family,
+ * refuse the connection.
+ *
+ * If we can do DNS requests, and we can use at least one address family,
+ * then we have to resolve the address first. Then we'll know if it
+ * resolves to a usable address family. */
+
+ /* First, check if all non-onion traffic is disabled */
+ if (!conn->entry_cfg.dns_request && !conn->entry_cfg.ipv4_traffic
+ && !conn->entry_cfg.ipv6_traffic) {
+ log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname "
+ "or IP address %s because Port has OnionTrafficOnly set (or "
+ "NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic).",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+
+ /* Then check if we have a hostname or IP address, and whether DNS or
+ * the IP address family are permitted */
+ tor_addr_t dummy_addr;
+ int socks_family = tor_addr_parse(&dummy_addr, socks->address);
+ /* family will be -1 for a non-onion hostname that's not an IP */
+ if (socks_family == -1 && !conn->entry_cfg.dns_request) {
+ log_warn(LD_APP, "Refusing to connect to hostname %s "
+ "because Port has NoDNSRequest set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ } else if (socks_family == AF_INET && !conn->entry_cfg.ipv4_traffic) {
+ log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
+ "Port has NoIPv4Traffic set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ } else if (socks_family == AF_INET6 && !conn->entry_cfg.ipv6_traffic) {
+ log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
+ "Port has NoIPv6Traffic set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+ /* No else, we've covered all possible returned value. */
+
/* See if this is a hostname lookup that we can answer immediately.
* (For example, an attempt to look up the IP address for an IP address.)
*/
@@ -1671,6 +1766,14 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
/* If we get here, it's a request for a .onion address! */
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)) {
@@ -1700,7 +1803,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
rend_service_authorization_t *client_auth =
rend_client_lookup_service_authorization(socks->address);
- const char *cookie = NULL;
+ 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 "
@@ -1712,7 +1815,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
/* 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, cookie, auth_type);
+ rend_data_client_create(socks->address, NULL, (char *) cookie,
+ auth_type);
if (rend_data == NULL) {
return -1;
}
@@ -1807,8 +1911,8 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req)
socklen_t orig_dst_len = sizeof(orig_dst);
tor_addr_t addr;
-#ifdef TRANS_TRPOXY
- if (options->TransProxyType_parsed == TPT_TPROXY) {
+#ifdef TRANS_TPROXY
+ if (get_options()->TransProxyType_parsed == TPT_TPROXY) {
if (getsockname(ENTRY_TO_CONN(conn)->s, (struct sockaddr*)&orig_dst,
&orig_dst_len) < 0) {
int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s);
@@ -2016,14 +2120,8 @@ connection_ap_handshake_process_socks(entry_connection_t *conn)
log_debug(LD_APP,"entered.");
- IF_HAS_BUFFEREVENT(base_conn, {
- struct evbuffer *input = bufferevent_get_input(base_conn->bufev);
- sockshere = fetch_from_evbuffer_socks(input, socks,
- options->TestSocks, options->SafeSocks);
- }) ELSE_IF_NO_BUFFEREVENT {
- sockshere = fetch_from_buf_socks(base_conn->inbuf, socks,
- options->TestSocks, options->SafeSocks);
- };
+ sockshere = fetch_from_buf_socks(base_conn->inbuf, socks,
+ options->TestSocks, options->SafeSocks);
if (socks->replylen) {
had_reply = 1;
@@ -2286,6 +2384,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
char payload[CELL_PAYLOAD_SIZE];
int payload_len;
int begin_type;
+ const or_options_t *options = get_options();
origin_circuit_t *circ;
edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn);
connection_t *base_conn = TO_CONN(edge_conn);
@@ -2299,7 +2398,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
edge_conn->stream_id = get_unique_stream_id_by_circ(circ);
if (edge_conn->stream_id==0) {
- /* XXXX024 Instead of closing this stream, we should make it get
+ /* XXXX+ Instead of closing this stream, we should make it get
* retried on another circuit. */
connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
@@ -2329,10 +2428,37 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
begin_type = ap_conn->use_begindir ?
RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN;
+
+ /* Check that circuits are anonymised, based on their type. */
if (begin_type == RELAY_COMMAND_BEGIN) {
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(circ->build_state->onehop_tunnel == 0);
-#endif
+ /* This connection is a standard OR connection.
+ * Make sure its path length is anonymous, or that we're in a
+ * non-anonymous mode. */
+ assert_circ_anonymity_ok(circ, options);
+ } else if (begin_type == RELAY_COMMAND_BEGIN_DIR) {
+ /* This connection is a begindir directory connection.
+ * Look at the linked directory connection to access the directory purpose.
+ * If a BEGINDIR connection is ever not linked, that's a bug. */
+ if (BUG(!base_conn->linked)) {
+ return -1;
+ }
+ connection_t *linked_dir_conn_base = base_conn->linked_conn;
+ /* If the linked connection has been unlinked by other code, we can't send
+ * a begin cell on it. */
+ if (!linked_dir_conn_base) {
+ return -1;
+ }
+ /* Sensitive directory connections must have an anonymous path length.
+ * Otherwise, directory connections are typically one-hop.
+ * This matches the earlier check for directory connection path anonymity
+ * in directory_initiate_command_rend(). */
+ if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) {
+ assert_circ_anonymity_ok(circ, options);
+ }
+ } else {
+ /* This code was written for the two connection types BEGIN and BEGIN_DIR
+ */
+ tor_assert_unreached();
}
if (connection_edge_send_command(edge_conn, begin_type,
@@ -2355,7 +2481,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
log_info(LD_APP, "Sending up to %ld + %ld bytes of queued-up data",
(long)connection_get_inbuf_len(base_conn),
ap_conn->sending_optimistic_data ?
- (long)generic_buffer_len(ap_conn->sending_optimistic_data) : 0);
+ (long)buf_datalen(ap_conn->sending_optimistic_data) : 0);
if (connection_edge_package_raw_inbuf(edge_conn, 1, NULL) < 0) {
connection_mark_for_close(base_conn);
}
@@ -2391,7 +2517,7 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn)
edge_conn->stream_id = get_unique_stream_id_by_circ(circ);
if (edge_conn->stream_id==0) {
- /* XXXX024 Instead of closing this stream, we should make it get
+ /* XXXX+ Instead of closing this stream, we should make it get
* retried on another circuit. */
connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
@@ -2442,7 +2568,7 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn)
if (!base_conn->address) {
/* This might be unnecessary. XXXX */
- base_conn->address = tor_dup_addr(&base_conn->addr);
+ base_conn->address = tor_addr_to_str_dup(&base_conn->addr);
}
base_conn->state = AP_CONN_STATE_RESOLVE_WAIT;
log_info(LD_APP,"Address sent for resolve, ap socket "TOR_SOCKET_T_FORMAT
@@ -2889,7 +3015,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
or_circuit_t *or_circ = NULL;
const or_options_t *options = get_options();
begin_cell_t bcell;
- int r;
+ int rv;
uint8_t end_reason=0;
assert_circuit_ok(circ);
@@ -2914,10 +3040,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
return 0;
}
- r = begin_cell_parse(cell, &bcell, &end_reason);
- if (r < -1) {
+ rv = begin_cell_parse(cell, &bcell, &end_reason);
+ if (rv < -1) {
return -END_CIRC_REASON_TORPROTOCOL;
- } else if (r == -1) {
+ } else if (rv == -1) {
tor_free(bcell.address);
relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, NULL);
return 0;
@@ -3155,6 +3281,24 @@ connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ)
return 0;
}
+/** Helper: Return true and set *<b>why_rejected</b> to an optional clarifying
+ * message message iff we do not allow connections to <b>addr</b>:<b>port</b>.
+ */
+static int
+my_exit_policy_rejects(const tor_addr_t *addr,
+ uint16_t port,
+ const char **why_rejected)
+{
+ if (router_compare_to_my_exit_policy(addr, port)) {
+ *why_rejected = "";
+ return 1;
+ } else if (tor_addr_family(addr) == AF_INET6 && !get_options()->IPv6Exit) {
+ *why_rejected = " (IPv6 address without IPv6Exit configured)";
+ return 1;
+ }
+ return 0;
+}
+
/** Connect to conn's specified addr and port. If it worked, conn
* has now been added to the connection_array.
*
@@ -3169,14 +3313,18 @@ connection_exit_connect(edge_connection_t *edge_conn)
uint16_t port;
connection_t *conn = TO_CONN(edge_conn);
int socket_error = 0, result;
-
- if ( (!connection_edge_is_rendezvous_stream(edge_conn) &&
- router_compare_to_my_exit_policy(&edge_conn->base_.addr,
- edge_conn->base_.port)) ||
- (tor_addr_family(&conn->addr) == AF_INET6 &&
- ! get_options()->IPv6Exit)) {
- log_info(LD_EXIT,"%s:%d failed exit policy. Closing.",
- escaped_safe_str_client(conn->address), conn->port);
+ const char *why_failed_exit_policy = NULL;
+
+ /* Apply exit policy to non-rendezvous connections. */
+ if (! connection_edge_is_rendezvous_stream(edge_conn) &&
+ my_exit_policy_rejects(&edge_conn->base_.addr,
+ edge_conn->base_.port,
+ &why_failed_exit_policy)) {
+ if (BUG(!why_failed_exit_policy))
+ why_failed_exit_policy = "";
+ log_info(LD_EXIT,"%s:%d failed exit policy%s. Closing.",
+ escaped_safe_str_client(conn->address), conn->port,
+ why_failed_exit_policy);
connection_edge_end(edge_conn, END_STREAM_REASON_EXITPOLICY);
circuit_detach_stream(circuit_get_by_edge_conn(edge_conn), edge_conn);
connection_free(conn);
@@ -3233,11 +3381,9 @@ connection_exit_connect(edge_connection_t *edge_conn)
conn->state = EXIT_CONN_STATE_OPEN;
if (connection_get_outbuf_len(conn)) {
/* in case there are any queued data cells, from e.g. optimistic data */
- IF_HAS_NO_BUFFEREVENT(conn)
- connection_watch_events(conn, READ_EVENT|WRITE_EVENT);
+ connection_watch_events(conn, READ_EVENT|WRITE_EVENT);
} else {
- IF_HAS_NO_BUFFEREVENT(conn)
- connection_watch_events(conn, READ_EVENT);
+ connection_watch_events(conn, READ_EVENT);
}
/* also, deliver a 'connected' cell back through the circuit. */
@@ -3341,19 +3487,20 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn)
return 0;
}
-/** Return 1 if router <b>exit</b> is likely to allow stream <b>conn</b>
+/** Return 1 if router <b>exit_node</b> is likely to allow stream <b>conn</b>
* to exit from it, or 0 if it probably will not allow it.
* (We might be uncertain if conn's destination address has not yet been
* resolved.)
*/
int
-connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit)
+connection_ap_can_use_exit(const entry_connection_t *conn,
+ const node_t *exit_node)
{
const or_options_t *options = get_options();
tor_assert(conn);
tor_assert(conn->socks_request);
- tor_assert(exit);
+ tor_assert(exit_node);
/* If a particular exit node has been requested for the new connection,
* make sure the exit node of the existing circuit matches exactly.
@@ -3362,7 +3509,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit)
const node_t *chosen_exit =
node_get_by_nickname(conn->chosen_exit_name, 1);
if (!chosen_exit || tor_memneq(chosen_exit->identity,
- exit->identity, DIGEST_LEN)) {
+ exit_node->identity, DIGEST_LEN)) {
/* doesn't match */
// log_debug(LD_APP,"Requested node '%s', considering node '%s'. No.",
// conn->chosen_exit_name, exit->nickname);
@@ -3387,7 +3534,8 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit)
tor_addr_make_null(&addr, AF_INET);
addrp = &addr;
}
- r = compare_tor_addr_to_node_policy(addrp, conn->socks_request->port,exit);
+ r = compare_tor_addr_to_node_policy(addrp, conn->socks_request->port,
+ exit_node);
if (r == ADDR_POLICY_REJECTED)
return 0; /* We know the address, and the exit policy rejects it. */
if (r == ADDR_POLICY_PROBABLY_REJECTED && !conn->chosen_exit_name)
@@ -3396,10 +3544,10 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit)
* this node, err on the side of caution. */
} else if (SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)) {
/* Don't send DNS requests to non-exit servers by default. */
- if (!conn->chosen_exit_name && node_exit_policy_rejects_all(exit))
+ if (!conn->chosen_exit_name && node_exit_policy_rejects_all(exit_node))
return 0;
}
- if (routerset_contains_node(options->ExcludeExitNodesUnion_, exit)) {
+ if (routerset_contains_node(options->ExcludeExitNodesUnion_, exit_node)) {
/* Not a suitable exit. Refuse it. */
return 0;
}
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index 3892ac02fb..dadfdc4380 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -8,6 +8,17 @@
* \file connection_or.c
* \brief Functions to handle OR connections, TLS handshaking, and
* cells on the network.
+ *
+ * An or_connection_t is a subtype of connection_t (as implemented in
+ * connection.c) that uses a TLS connection to send and receive cells on the
+ * Tor network. (By sending and receiving cells connection_or.c, it cooperates
+ * with channeltls.c to implement a the channel interface of channel.c.)
+ *
+ * Every OR connection has an underlying tortls_t object (as implemented in
+ * tortls.c) which it uses as its TLS stream. It is responsible for
+ * sending and receiving cells over that TLS.
+ *
+ * This module also implements the client side of the v3 Tor link handshake,
**/
#include "or.h"
#include "buffers.h"
@@ -42,10 +53,6 @@
#include "ext_orport.h"
#include "scheduler.h"
-#ifdef USE_BUFFEREVENTS
-#include <event2/bufferevent_ssl.h>
-#endif
-
static int connection_tls_finish_handshake(or_connection_t *conn);
static int connection_or_launch_v3_or_handshake(or_connection_t *conn);
static int connection_or_process_cells_from_inbuf(or_connection_t *conn);
@@ -66,12 +73,6 @@ static void connection_or_mark_bad_for_new_circs(or_connection_t *or_conn);
static void connection_or_change_state(or_connection_t *conn, uint8_t state);
-#ifdef USE_BUFFEREVENTS
-static void connection_or_handle_event_cb(struct bufferevent *bufev,
- short event, void *arg);
-#include <event2/buffer.h>/*XXXX REMOVE */
-#endif
-
/**************************************************************/
/** Map from identity digest of connected OR or desired OR to a connection_t
@@ -404,8 +405,8 @@ connection_or_change_state(or_connection_t *conn, uint8_t state)
* be an or_connection_t field, but it got moved to channel_t and we
* shouldn't maintain two copies. */
-int
-connection_or_get_num_circuits(or_connection_t *conn)
+MOCK_IMPL(int,
+connection_or_get_num_circuits, (or_connection_t *conn))
{
tor_assert(conn);
@@ -567,13 +568,6 @@ connection_or_process_inbuf(or_connection_t *conn)
return ret;
case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING:
-#ifdef USE_BUFFEREVENTS
- if (tor_tls_server_got_renegotiate(conn->tls))
- connection_or_tls_renegotiated_cb(conn->tls, conn);
- if (conn->base_.marked_for_close)
- return 0;
- /* fall through. */
-#endif
case OR_CONN_STATE_OPEN:
case OR_CONN_STATE_OR_HANDSHAKING_V2:
case OR_CONN_STATE_OR_HANDSHAKING_V3:
@@ -586,7 +580,7 @@ connection_or_process_inbuf(or_connection_t *conn)
* check would otherwise just let data accumulate. It serves no purpose
* in 0.2.3.
*
- * XXX024 Remove this check once we verify that the above paragraph is
+ * XXXX Remove this check once we verify that the above paragraph is
* 100% true. */
if (buf_datalen(conn->base_.inbuf) > MAX_OR_INBUF_WHEN_NONOPEN) {
log_fn(LOG_PROTOCOL_WARN, LD_NET, "Accumulated too much data (%d bytes) "
@@ -809,27 +803,6 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset,
conn->bandwidthrate = rate;
conn->bandwidthburst = burst;
-#ifdef USE_BUFFEREVENTS
- {
- const struct timeval *tick = tor_libevent_get_one_tick_timeout();
- struct ev_token_bucket_cfg *cfg, *old_cfg;
- int64_t rate64 = (((int64_t)rate) * options->TokenBucketRefillInterval)
- / 1000;
- /* This can't overflow, since TokenBucketRefillInterval <= 1000,
- * and rate started out less than INT_MAX. */
- int rate_per_tick = (int) rate64;
-
- cfg = ev_token_bucket_cfg_new(rate_per_tick, burst, rate_per_tick,
- burst, tick);
- old_cfg = conn->bucket_cfg;
- if (conn->base_.bufev)
- tor_set_bufferevent_rate_limit(conn->base_.bufev, cfg);
- if (old_cfg)
- ev_token_bucket_cfg_free(old_cfg);
- conn->bucket_cfg = cfg;
- (void) reset; /* No way to do this with libevent yet. */
- }
-#else
if (reset) { /* set up the token buckets to be full */
conn->read_bucket = conn->write_bucket = burst;
return;
@@ -840,7 +813,6 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset,
conn->read_bucket = burst;
if (conn->write_bucket > burst)
conn->write_bucket = burst;
-#endif
}
/** Either our set of relays or our per-conn rate limits have changed.
@@ -937,7 +909,7 @@ connection_or_init_conn_from_address(or_connection_t *conn,
}
conn->nickname = tor_strdup(node_get_nickname(r));
tor_free(conn->base_.address);
- conn->base_.address = tor_dup_addr(&node_ap.addr);
+ conn->base_.address = tor_addr_to_str_dup(&node_ap.addr);
} else {
conn->nickname = tor_malloc(HEX_DIGEST_LEN+2);
conn->nickname[0] = '$';
@@ -945,7 +917,7 @@ connection_or_init_conn_from_address(or_connection_t *conn,
conn->identity_digest, DIGEST_LEN);
tor_free(conn->base_.address);
- conn->base_.address = tor_dup_addr(addr);
+ conn->base_.address = tor_addr_to_str_dup(addr);
}
/*
@@ -1284,11 +1256,9 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
switch (connection_connect(TO_CONN(conn), conn->base_.address,
&addr, port, &socket_error)) {
case -1:
- /* If the connection failed immediately, and we're using
- * a proxy, our proxy is down. Don't blame the Tor server. */
- if (conn->base_.proxy_state == PROXY_INFANT)
- entry_guard_register_connect_status(conn->identity_digest,
- 0, 1, time(NULL));
+ /* We failed to establish a connection probably because of a local
+ * error. No need to blame the guard in this case. Notify the networking
+ * system of this failure. */
connection_or_connect_failed(conn,
errno_to_orconn_end_reason(socket_error),
tor_socket_strerror(socket_error));
@@ -1399,40 +1369,14 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving))
tor_tls_set_logged_address(conn->tls, // XXX client and relay?
escaped_safe_str(conn->base_.address));
-#ifdef USE_BUFFEREVENTS
- if (connection_type_uses_bufferevent(TO_CONN(conn))) {
- const int filtering = get_options()->UseFilteringSSLBufferevents;
- struct bufferevent *b =
- tor_tls_init_bufferevent(conn->tls, conn->base_.bufev, conn->base_.s,
- receiving, filtering);
- if (!b) {
- log_warn(LD_BUG,"tor_tls_init_bufferevent failed. Closing.");
- return -1;
- }
- conn->base_.bufev = b;
- if (conn->bucket_cfg)
- tor_set_bufferevent_rate_limit(conn->base_.bufev, conn->bucket_cfg);
- connection_enable_rate_limiting(TO_CONN(conn));
-
- connection_configure_bufferevent_callbacks(TO_CONN(conn));
- bufferevent_setcb(b,
- connection_handle_read_cb,
- connection_handle_write_cb,
- connection_or_handle_event_cb,/* overriding this one*/
- TO_CONN(conn));
- }
-#endif
connection_start_reading(TO_CONN(conn));
log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT,
conn->base_.s);
note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C);
- IF_HAS_BUFFEREVENT(TO_CONN(conn), {
- /* ???? */;
- }) ELSE_IF_NO_BUFFEREVENT {
- if (connection_tls_continue_handshake(conn) < 0)
- return -1;
- }
+ if (connection_tls_continue_handshake(conn) < 0)
+ return -1;
+
return 0;
}
@@ -1521,75 +1465,6 @@ connection_tls_continue_handshake(or_connection_t *conn)
return 0;
}
-#ifdef USE_BUFFEREVENTS
-static void
-connection_or_handle_event_cb(struct bufferevent *bufev, short event,
- void *arg)
-{
- struct or_connection_t *conn = TO_OR_CONN(arg);
-
- /* XXXX cut-and-paste code; should become a function. */
- if (event & BEV_EVENT_CONNECTED) {
- if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) {
- if (tor_tls_finish_handshake(conn->tls) < 0) {
- log_warn(LD_OR, "Problem finishing handshake");
- connection_or_close_for_error(conn, 0);
- return;
- }
- }
-
- if (! tor_tls_used_v1_handshake(conn->tls)) {
- if (!tor_tls_is_server(conn->tls)) {
- if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) {
- if (connection_or_launch_v3_or_handshake(conn) < 0)
- connection_or_close_for_error(conn, 0);
- }
- } else {
- const int handshakes = tor_tls_get_num_server_handshakes(conn->tls);
-
- if (handshakes == 1) {
- /* v2 or v3 handshake, as a server. Only got one handshake, so
- * wait for the next one. */
- tor_tls_set_renegotiate_callback(conn->tls,
- connection_or_tls_renegotiated_cb,
- conn);
- connection_or_change_state(conn,
- OR_CONN_STATE_TLS_SERVER_RENEGOTIATING);
- } else if (handshakes == 2) {
- /* v2 handshake, as a server. Two handshakes happened already,
- * so we treat renegotiation as done.
- */
- connection_or_tls_renegotiated_cb(conn->tls, conn);
- } else if (handshakes > 2) {
- log_warn(LD_OR, "More than two handshakes done on connection. "
- "Closing.");
- connection_or_close_for_error(conn, 0);
- } else {
- log_warn(LD_BUG, "We were unexpectedly told that a connection "
- "got %d handshakes. Closing.", handshakes);
- connection_or_close_for_error(conn, 0);
- }
- return;
- }
- }
- connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT);
- if (connection_tls_finish_handshake(conn) < 0)
- connection_or_close_for_error(conn, 0); /* ???? */
- return;
- }
-
- if (event & BEV_EVENT_ERROR) {
- unsigned long err;
- while ((err = bufferevent_get_openssl_error(bufev))) {
- tor_tls_log_one_error(conn->tls, err, LOG_WARN, LD_OR,
- "handshaking (with bufferevent)");
- }
- }
-
- connection_handle_event_cb(bufev, event, arg);
-}
-#endif
-
/** Return 1 if we initiated this connection, or 0 if it started
* out as an incoming connection.
*/
@@ -2007,11 +1882,7 @@ connection_or_set_state_open(or_connection_t *conn)
or_handshake_state_free(conn->handshake_state);
conn->handshake_state = NULL;
- IF_HAS_BUFFEREVENT(TO_CONN(conn), {
- connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT);
- }) ELSE_IF_NO_BUFFEREVENT {
- connection_start_reading(TO_CONN(conn));
- }
+ connection_start_reading(TO_CONN(conn));
return 0;
}
@@ -2071,12 +1942,7 @@ static int
connection_fetch_var_cell_from_buf(or_connection_t *or_conn, var_cell_t **out)
{
connection_t *conn = TO_CONN(or_conn);
- IF_HAS_BUFFEREVENT(conn, {
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- return fetch_var_cell_from_evbuffer(input, out, or_conn->link_proto);
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_var_cell_from_buf(conn->inbuf, out, or_conn->link_proto);
- }
+ return fetch_var_cell_from_buf(conn->inbuf, out, or_conn->link_proto);
}
/** Process cells from <b>conn</b>'s inbuf.
@@ -2273,22 +2139,29 @@ connection_or_send_netinfo,(or_connection_t *conn))
int
connection_or_send_certs_cell(or_connection_t *conn)
{
- const tor_x509_cert_t *link_cert = NULL, *id_cert = NULL;
+ const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL,
+ *using_link_cert = NULL;
+ tor_x509_cert_t *own_link_cert = NULL;
const uint8_t *link_encoded = NULL, *id_encoded = NULL;
size_t link_len, id_len;
var_cell_t *cell;
size_t cell_len;
ssize_t pos;
- int server_mode;
tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
if (! conn->handshake_state)
return -1;
- server_mode = ! conn->handshake_state->started_here;
- if (tor_tls_get_my_certs(server_mode, &link_cert, &id_cert) < 0)
+ const int conn_in_server_mode = ! conn->handshake_state->started_here;
+ if (tor_tls_get_my_certs(conn_in_server_mode,
+ &global_link_cert, &id_cert) < 0)
return -1;
- tor_x509_cert_get_der(link_cert, &link_encoded, &link_len);
+ if (conn_in_server_mode) {
+ using_link_cert = own_link_cert = tor_tls_get_own_cert(conn->tls);
+ } else {
+ using_link_cert = global_link_cert;
+ }
+ tor_x509_cert_get_der(using_link_cert, &link_encoded, &link_len);
tor_x509_cert_get_der(id_cert, &id_encoded, &id_len);
cell_len = 1 /* 1 byte: num certs in cell */ +
@@ -2299,7 +2172,7 @@ connection_or_send_certs_cell(or_connection_t *conn)
cell->payload[0] = 2;
pos = 1;
- if (server_mode)
+ if (conn_in_server_mode)
cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */
else
cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */
@@ -2316,6 +2189,7 @@ connection_or_send_certs_cell(or_connection_t *conn)
connection_or_write_var_cell_to_buf(cell, conn);
var_cell_free(cell);
+ tor_x509_cert_free(own_link_cert);
return 0;
}
@@ -2395,10 +2269,10 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
memcpy(auth1_getarray_type(auth), "AUTH0001", 8);
{
- const tor_x509_cert_t *id_cert=NULL, *link_cert=NULL;
+ const tor_x509_cert_t *id_cert=NULL;
const common_digests_t *my_digests, *their_digests;
const uint8_t *my_id, *their_id, *client_id, *server_id;
- if (tor_tls_get_my_certs(server, &link_cert, &id_cert))
+ if (tor_tls_get_my_certs(server, NULL, &id_cert))
goto err;
my_digests = tor_x509_cert_get_id_digests(id_cert);
their_digests =
@@ -2437,13 +2311,11 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
{
/* Digest of cert used on TLS link : 32 octets. */
- const tor_x509_cert_t *cert = NULL;
- tor_x509_cert_t *freecert = NULL;
+ tor_x509_cert_t *cert = NULL;
if (server) {
- tor_tls_get_my_certs(1, &cert, NULL);
+ cert = tor_tls_get_own_cert(conn->tls);
} else {
- freecert = tor_tls_get_peer_cert(conn->tls);
- cert = freecert;
+ cert = tor_tls_get_peer_cert(conn->tls);
}
if (!cert) {
log_warn(LD_OR, "Unable to find cert when making AUTH1 data.");
@@ -2453,8 +2325,7 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
memcpy(auth->scert,
tor_x509_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32);
- if (freecert)
- tor_x509_cert_free(freecert);
+ tor_x509_cert_free(cert);
}
/* HMAC of clientrandom and serverrandom using master key : 32 octets */
diff --git a/src/or/connection_or.h b/src/or/connection_or.h
index e2ec47a4f2..2e8c6066cc 100644
--- a/src/or/connection_or.h
+++ b/src/or/connection_or.h
@@ -64,7 +64,7 @@ void connection_or_init_conn_from_address(or_connection_t *conn,
int connection_or_client_learned_peer_id(or_connection_t *conn,
const uint8_t *peer_id);
time_t connection_or_client_used(or_connection_t *conn);
-int connection_or_get_num_circuits(or_connection_t *conn);
+MOCK_DECL(int, connection_or_get_num_circuits, (or_connection_t *conn));
void or_handshake_state_free(or_handshake_state_t *state);
void or_handshake_state_record_cell(or_connection_t *conn,
or_handshake_state_t *state,
diff --git a/src/or/control.c b/src/or/control.c
index e2ad8cc6dc..03d9fcee2a 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -5,7 +5,31 @@
/**
* \file control.c
* \brief Implementation for Tor's control-socket interface.
- * See doc/spec/control-spec.txt for full details on protocol.
+ *
+ * A "controller" is an external program that monitors and controls a Tor
+ * instance via a text-based protocol. It connects to Tor via a connection
+ * to a local socket.
+ *
+ * The protocol is line-driven. The controller sends commands terminated by a
+ * CRLF. Tor sends lines that are either <em>replies</em> to what the
+ * controller has said, or <em>events</em> that Tor sends to the controller
+ * asynchronously based on occurrences in the Tor network model.
+ *
+ * See the control-spec.txt file in the torspec.git repository for full
+ * details on protocol.
+ *
+ * This module generally has two kinds of entry points: those based on having
+ * received a command on a controller socket, which are handled in
+ * connection_control_process_inbuf(), and dispatched to individual functions
+ * with names like control_handle_COMMANDNAME(); and those based on events
+ * that occur elsewhere in Tor, which are handled by functions with names like
+ * control_event_EVENTTYPE().
+ *
+ * Controller events are not sent immediately; rather, they are inserted into
+ * the queued_control_events array, and flushed later from
+ * flush_queued_events_cb(). Doing this simplifies our callgraph greatly,
+ * by limiting the number of places in Tor that can call back into the network
+ * stack.
**/
#define CONTROL_PRIVATE
@@ -51,11 +75,7 @@
#include <sys/resource.h>
#endif
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
#include "crypto_s2k.h"
#include "procmon.h"
@@ -190,6 +210,8 @@ static void set_cached_network_liveness(int liveness);
static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg);
+static char * download_status_to_string(const download_status_t *dl);
+
/** Given a control event code for a message event, return the corresponding
* log severity. */
static inline int
@@ -596,7 +618,7 @@ typedef struct queued_event_s {
/** Pointer to int. If this is greater than 0, we don't allow new events to be
* queued. */
-static tor_threadlocal_t block_event_queue;
+static tor_threadlocal_t block_event_queue_flag;
/** Holds a smartlist of queued_event_t objects that may need to be sent
* to one or more controllers */
@@ -631,17 +653,17 @@ control_initialize_event_queue(void)
if (queued_control_events_lock == NULL) {
queued_control_events_lock = tor_mutex_new();
- tor_threadlocal_init(&block_event_queue);
+ tor_threadlocal_init(&block_event_queue_flag);
}
}
static int *
get_block_event_queue(void)
{
- int *val = tor_threadlocal_get(&block_event_queue);
+ int *val = tor_threadlocal_get(&block_event_queue_flag);
if (PREDICT_UNLIKELY(val == NULL)) {
val = tor_malloc_zero(sizeof(int));
- tor_threadlocal_set(&block_event_queue, val);
+ tor_threadlocal_set(&block_event_queue_flag, val);
}
return val;
}
@@ -873,7 +895,8 @@ control_setconf_helper(control_connection_t *conn, uint32_t len, char *body,
config_line_t *lines=NULL;
char *start = body;
char *errstring = NULL;
- const int clear_first = 1;
+ const unsigned flags =
+ CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0);
char *config;
smartlist_t *entries = smartlist_new();
@@ -933,7 +956,7 @@ control_setconf_helper(control_connection_t *conn, uint32_t len, char *body,
}
tor_free(config);
- opt_err = options_trial_assign(lines, use_defaults, clear_first, &errstring);
+ opt_err = options_trial_assign(lines, flags, &errstring);
{
const char *msg;
switch (opt_err) {
@@ -1211,7 +1234,8 @@ decode_hashed_passwords(config_line_t *passwords)
const char *hashed = cl->value;
if (!strcmpstart(hashed, "16:")) {
- if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3))<0
+ if (base16_decode(decoded, sizeof(decoded), hashed+3, strlen(hashed+3))
+ != S2K_RFC2440_SPECIFIER_LEN + DIGEST_LEN
|| strlen(hashed+3) != (S2K_RFC2440_SPECIFIER_LEN+DIGEST_LEN)*2) {
goto err;
}
@@ -1262,7 +1286,8 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len,
tor_assert(i>0);
password_len = i/2;
password = tor_malloc(password_len + 1);
- if (base16_decode(password, password_len+1, body, i)<0) {
+ if (base16_decode(password, password_len+1, body, i)
+ != (int) password_len) {
connection_write_str_to_buf(
"551 Invalid hexadecimal encoding. Maybe you tried a plain text "
"password? If so, the standard requires that you put it in "
@@ -1370,7 +1395,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len,
goto err;
}
bad_password = 1;
- SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(sl, char *, str, tor_free(str));
smartlist_free(sl);
sl = NULL;
} else {
@@ -1382,7 +1407,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len,
received, DIGEST_LEN))
goto ok;
});
- SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(sl, char *, str, tor_free(str));
smartlist_free(sl);
sl = NULL;
@@ -1410,7 +1435,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len,
connection_printf_to_buf(conn, "515 Authentication failed: %s\r\n", errstr);
connection_mark_for_close(TO_CONN(conn));
if (sl) { /* clean up */
- SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(sl, char *, str, tor_free(str));
smartlist_free(sl);
}
return 0;
@@ -1421,7 +1446,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len,
conn->base_.state = CONTROL_CONN_STATE_OPEN;
tor_free(password);
if (sl) { /* clean up */
- SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(sl, char *, str, tor_free(str));
smartlist_free(sl);
}
return 0;
@@ -1679,7 +1704,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question,
*answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS");
} else if (!strcmp(question, "address")) {
uint32_t addr;
- if (router_pick_published_address(get_options(), &addr) < 0) {
+ if (router_pick_published_address(get_options(), &addr, 0) < 0) {
*errmsg = "Address unknown";
return -1;
}
@@ -1724,8 +1749,6 @@ getinfo_helper_misc(control_connection_t *conn, const char *question,
} else if (!strcmp(question, "limits/max-mem-in-queues")) {
tor_asprintf(answer, U64_FORMAT,
U64_PRINTF_ARG(get_options()->MaxMemInQueues));
- } else if (!strcmp(question, "dir-usage")) {
- *answer = directory_dump_request_log();
} else if (!strcmp(question, "fingerprint")) {
crypto_pk_t *server_key;
if (!server_mode(get_options())) {
@@ -1852,11 +1875,10 @@ getinfo_helper_dir(control_connection_t *control_conn,
const char *question, char **answer,
const char **errmsg)
{
- const node_t *node;
- const routerinfo_t *ri = NULL;
(void) control_conn;
if (!strcmpstart(question, "desc/id/")) {
- node = node_get_by_hex_id(question+strlen("desc/id/"));
+ const routerinfo_t *ri = NULL;
+ const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"));
if (node)
ri = node->ri;
if (ri) {
@@ -1865,9 +1887,11 @@ getinfo_helper_dir(control_connection_t *control_conn,
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
}
} else if (!strcmpstart(question, "desc/name/")) {
- /* XXX023 Setting 'warn_if_unnamed' here is a bit silly -- the
+ 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. */
- node = node_get_by_nickname(question+strlen("desc/name/"), 1);
+ const node_t *node =
+ node_get_by_nickname(question+strlen("desc/name/"), 1);
if (node)
ri = node->ri;
if (ri) {
@@ -1951,7 +1975,7 @@ getinfo_helper_dir(control_connection_t *control_conn,
*answer = tor_strndup(md->body, md->bodylen);
}
} else if (!strcmpstart(question, "md/name/")) {
- /* XXX023 Setting 'warn_if_unnamed' here is a bit silly -- the
+ /* 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);
/* XXXX duplicated code */
@@ -1961,7 +1985,9 @@ getinfo_helper_dir(control_connection_t *control_conn,
*answer = tor_strndup(md->body, md->bodylen);
}
} else if (!strcmpstart(question, "desc-annotations/id/")) {
- node = node_get_by_hex_id(question+strlen("desc-annotations/id/"));
+ const routerinfo_t *ri = NULL;
+ const node_t *node =
+ node_get_by_hex_id(question+strlen("desc-annotations/id/"));
if (node)
ri = node->ri;
if (ri) {
@@ -2028,7 +2054,8 @@ getinfo_helper_dir(control_connection_t *control_conn,
if (strlen(question) == HEX_DIGEST_LEN) {
char d[DIGEST_LEN];
signed_descriptor_t *sd = NULL;
- if (base16_decode(d, sizeof(d), question, strlen(question))==0) {
+ if (base16_decode(d, sizeof(d), question, strlen(question))
+ == sizeof(d)) {
/* XXXX this test should move into extrainfo_get_by_descriptor_digest,
* but I don't want to risk affecting other parts of the code,
* especially since the rules for using our own extrainfo (including
@@ -2050,6 +2077,411 @@ getinfo_helper_dir(control_connection_t *control_conn,
return 0;
}
+/** Given a smartlist of 20-byte digests, return a newly allocated string
+ * containing each of those digests in order, formatted in HEX, and terminated
+ * with a newline. */
+static char *
+digest_list_to_string(const smartlist_t *sl)
+{
+ int len;
+ char *result, *s;
+
+ /* Allow for newlines, and a \0 at the end */
+ len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1;
+ result = tor_malloc_zero(len);
+
+ s = result;
+ SMARTLIST_FOREACH_BEGIN(sl, const char *, digest) {
+ base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN);
+ s[HEX_DIGEST_LEN] = '\n';
+ s += HEX_DIGEST_LEN + 1;
+ } SMARTLIST_FOREACH_END(digest);
+ *s = '\0';
+
+ return result;
+}
+
+/** Turn a download_status_t into a human-readable description in a newly
+ * allocated string. The format is specified in control-spec.txt, under
+ * the documentation for "GETINFO download/..." . */
+static char *
+download_status_to_string(const download_status_t *dl)
+{
+ char *rv = NULL, *tmp;
+ char tbuf[ISO_TIME_LEN+1];
+ const char *schedule_str, *want_authority_str;
+ const char *increment_on_str, *backoff_str;
+
+ if (dl) {
+ /* Get some substrings of the eventual output ready */
+ format_iso_time(tbuf, dl->next_attempt_at);
+
+ switch (dl->schedule) {
+ case DL_SCHED_GENERIC:
+ schedule_str = "DL_SCHED_GENERIC";
+ break;
+ case DL_SCHED_CONSENSUS:
+ schedule_str = "DL_SCHED_CONSENSUS";
+ break;
+ case DL_SCHED_BRIDGE:
+ schedule_str = "DL_SCHED_BRIDGE";
+ break;
+ default:
+ schedule_str = "unknown";
+ break;
+ }
+
+ switch (dl->want_authority) {
+ case DL_WANT_ANY_DIRSERVER:
+ want_authority_str = "DL_WANT_ANY_DIRSERVER";
+ break;
+ case DL_WANT_AUTHORITY:
+ want_authority_str = "DL_WANT_AUTHORITY";
+ break;
+ default:
+ want_authority_str = "unknown";
+ break;
+ }
+
+ switch (dl->increment_on) {
+ case DL_SCHED_INCREMENT_FAILURE:
+ increment_on_str = "DL_SCHED_INCREMENT_FAILURE";
+ break;
+ case DL_SCHED_INCREMENT_ATTEMPT:
+ increment_on_str = "DL_SCHED_INCREMENT_ATTEMPT";
+ break;
+ default:
+ increment_on_str = "unknown";
+ break;
+ }
+
+ switch (dl->backoff) {
+ case DL_SCHED_DETERMINISTIC:
+ backoff_str = "DL_SCHED_DETERMINISTIC";
+ break;
+ case DL_SCHED_RANDOM_EXPONENTIAL:
+ backoff_str = "DL_SCHED_RANDOM_EXPONENTIAL";
+ break;
+ default:
+ backoff_str = "unknown";
+ break;
+ }
+
+ /* Now assemble them */
+ tor_asprintf(&tmp,
+ "next-attempt-at %s\n"
+ "n-download-failures %u\n"
+ "n-download-attempts %u\n"
+ "schedule %s\n"
+ "want-authority %s\n"
+ "increment-on %s\n"
+ "backoff %s\n",
+ tbuf,
+ dl->n_download_failures,
+ dl->n_download_attempts,
+ schedule_str,
+ want_authority_str,
+ increment_on_str,
+ backoff_str);
+
+ if (dl->backoff == DL_SCHED_RANDOM_EXPONENTIAL) {
+ /* Additional fields become relevant in random-exponential mode */
+ tor_asprintf(&rv,
+ "%s"
+ "last-backoff-position %u\n"
+ "last-delay-used %d\n",
+ tmp,
+ dl->last_backoff_position,
+ dl->last_delay_used);
+ tor_free(tmp);
+ } else {
+ /* That was it */
+ rv = tmp;
+ }
+ }
+
+ return rv;
+}
+
+/** Handle the consensus download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_networkstatus(const char *flavor,
+ download_status_t **dl_to_emit,
+ const char **errmsg)
+{
+ /*
+ * We get the one for the current bootstrapped status by default, or
+ * take an extra /bootstrap or /running suffix
+ */
+ if (strcmp(flavor, "ns") == 0) {
+ *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
+ } else if (strcmp(flavor, "ns/bootstrap") == 0) {
+ *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
+ } else if (strcmp(flavor, "ns/running") == 0 ) {
+ *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
+ } else if (strcmp(flavor, "microdesc") == 0) {
+ *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
+ } else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
+ *dl_to_emit =
+ networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
+ } else if (strcmp(flavor, "microdesc/running") == 0) {
+ *dl_to_emit =
+ networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
+ } else {
+ *errmsg = "Unknown flavor";
+ }
+}
+
+/** Handle the cert download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_cert(const char *fp_sk_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg)
+{
+ const char *sk_req;
+ char id_digest[DIGEST_LEN];
+ char sk_digest[DIGEST_LEN];
+
+ /*
+ * We have to handle four cases; fp_sk_req is the request with
+ * a prefix of "downloads/cert/" snipped off.
+ *
+ * Case 1: fp_sk_req = "fps"
+ * - We should emit a digest_list with a list of all the identity
+ * fingerprints that can be queried for certificate download status;
+ * get it by calling list_authority_ids_with_downloads().
+ *
+ * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp
+ * - We want the default certificate for this identity fingerprint's
+ * download status; this is the download we get from URLs starting
+ * in /fp/ on the directory server. We can get it with
+ * id_only_download_status_for_authority_id().
+ *
+ * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp
+ * - We want a list of all signing key digests for this identity
+ * fingerprint which can be queried for certificate download status.
+ * Get it with list_sk_digests_for_authority_id().
+ *
+ * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and
+ * signing key digest sk
+ * - We want the download status for the certificate for this specific
+ * signing key and fingerprint. These correspond to the ones we get
+ * from URLs starting in /fp-sk/ on the directory server. Get it with
+ * list_sk_digests_for_authority_id().
+ */
+
+ if (strcmp(fp_sk_req, "fps") == 0) {
+ *digest_list = list_authority_ids_with_downloads();
+ if (!(*digest_list)) {
+ *errmsg = "Failed to get list of authority identity digests (!)";
+ }
+ } else if (!strcmpstart(fp_sk_req, "fp/")) {
+ fp_sk_req += strlen("fp/");
+ /* Okay, look for another / to tell the fp from fp-sk cases */
+ sk_req = strchr(fp_sk_req, '/');
+ if (sk_req) {
+ /* okay, split it here and try to parse <fp> */
+ if (base16_decode(id_digest, DIGEST_LEN,
+ fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) {
+ /* Skip past the '/' */
+ ++sk_req;
+ if (strcmp(sk_req, "sks") == 0) {
+ /* We're asking for the list of signing key fingerprints */
+ *digest_list = list_sk_digests_for_authority_id(id_digest);
+ if (!(*digest_list)) {
+ *errmsg = "Failed to get list of signing key digests for this "
+ "authority identity digest";
+ }
+ } else {
+ /* We've got a signing key digest */
+ if (base16_decode(sk_digest, DIGEST_LEN,
+ sk_req, strlen(sk_req)) == DIGEST_LEN) {
+ *dl_to_emit =
+ download_status_for_authority_id_and_sk(id_digest, sk_digest);
+ if (!(*dl_to_emit)) {
+ *errmsg = "Failed to get download status for this identity/"
+ "signing key digest pair";
+ }
+ } else {
+ *errmsg = "That didn't look like a signing key digest";
+ }
+ }
+ } else {
+ *errmsg = "That didn't look like an identity digest";
+ }
+ } else {
+ /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */
+ if (strlen(fp_sk_req) == HEX_DIGEST_LEN) {
+ if (base16_decode(id_digest, DIGEST_LEN,
+ fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) {
+ *dl_to_emit = id_only_download_status_for_authority_id(id_digest);
+ if (!(*dl_to_emit)) {
+ *errmsg = "Failed to get download status for this authority "
+ "identity digest";
+ }
+ } else {
+ *errmsg = "That didn't look like a digest";
+ }
+ } else {
+ *errmsg = "That didn't look like a digest";
+ }
+ }
+ } else {
+ *errmsg = "Unknown certificate download status query";
+ }
+}
+
+/** Handle the routerdesc download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_desc(const char *desc_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg)
+{
+ char desc_digest[DIGEST_LEN];
+ /*
+ * Two cases to handle here:
+ *
+ * Case 1: desc_req = "descs"
+ * - Emit a list of all router descriptor digests, which we get by
+ * calling router_get_descriptor_digests(); this can return NULL
+ * if we have no current ns-flavor consensus.
+ *
+ * Case 2: desc_req = <fp>
+ * - Check on the specified fingerprint and emit its download_status_t
+ * using router_get_dl_status_by_descriptor_digest().
+ */
+
+ if (strcmp(desc_req, "descs") == 0) {
+ *digest_list = router_get_descriptor_digests();
+ if (!(*digest_list)) {
+ *errmsg = "We don't seem to have a networkstatus-flavored consensus";
+ }
+ /*
+ * Microdescs don't use the download_status_t mechanism, so we don't
+ * answer queries about their downloads here; see microdesc.c.
+ */
+ } else if (strlen(desc_req) == HEX_DIGEST_LEN) {
+ if (base16_decode(desc_digest, DIGEST_LEN,
+ desc_req, strlen(desc_req)) == DIGEST_LEN) {
+ /* Okay we got a digest-shaped thing; try asking for it */
+ *dl_to_emit = router_get_dl_status_by_descriptor_digest(desc_digest);
+ if (!(*dl_to_emit)) {
+ *errmsg = "No such descriptor digest found";
+ }
+ } else {
+ *errmsg = "That didn't look like a digest";
+ }
+ } else {
+ *errmsg = "Unknown router descriptor download status query";
+ }
+}
+
+/** Handle the bridge download cases for getinfo_helper_downloads() */
+STATIC void
+getinfo_helper_downloads_bridge(const char *bridge_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg)
+{
+ char bridge_digest[DIGEST_LEN];
+ /*
+ * Two cases to handle here:
+ *
+ * Case 1: bridge_req = "bridges"
+ * - Emit a list of all bridge identity digests, which we get by
+ * calling list_bridge_identities(); this can return NULL if we are
+ * not using bridges.
+ *
+ * Case 2: bridge_req = <fp>
+ * - Check on the specified fingerprint and emit its download_status_t
+ * using get_bridge_dl_status_by_id().
+ */
+
+ if (strcmp(bridge_req, "bridges") == 0) {
+ *digest_list = list_bridge_identities();
+ if (!(*digest_list)) {
+ *errmsg = "We don't seem to be using bridges";
+ }
+ } else if (strlen(bridge_req) == HEX_DIGEST_LEN) {
+ if (base16_decode(bridge_digest, DIGEST_LEN,
+ bridge_req, strlen(bridge_req)) == DIGEST_LEN) {
+ /* Okay we got a digest-shaped thing; try asking for it */
+ *dl_to_emit = get_bridge_dl_status_by_id(bridge_digest);
+ if (!(*dl_to_emit)) {
+ *errmsg = "No such bridge identity digest found";
+ }
+ } else {
+ *errmsg = "That didn't look like a digest";
+ }
+ } else {
+ *errmsg = "Unknown bridge descriptor download status query";
+ }
+}
+
+/** Implementation helper for GETINFO: knows the answers for questions about
+ * download status information. */
+STATIC int
+getinfo_helper_downloads(control_connection_t *control_conn,
+ const char *question, char **answer,
+ const char **errmsg)
+{
+ download_status_t *dl_to_emit = NULL;
+ smartlist_t *digest_list = NULL;
+
+ /* Assert args are sane */
+ tor_assert(control_conn != NULL);
+ tor_assert(question != NULL);
+ tor_assert(answer != NULL);
+ tor_assert(errmsg != NULL);
+
+ /* We check for this later to see if we should supply a default */
+ *errmsg = NULL;
+
+ /* Are we after networkstatus downloads? */
+ if (!strcmpstart(question, "downloads/networkstatus/")) {
+ getinfo_helper_downloads_networkstatus(
+ question + strlen("downloads/networkstatus/"),
+ &dl_to_emit, errmsg);
+ /* Certificates? */
+ } else if (!strcmpstart(question, "downloads/cert/")) {
+ getinfo_helper_downloads_cert(
+ question + strlen("downloads/cert/"),
+ &dl_to_emit, &digest_list, errmsg);
+ /* Router descriptors? */
+ } else if (!strcmpstart(question, "downloads/desc/")) {
+ getinfo_helper_downloads_desc(
+ question + strlen("downloads/desc/"),
+ &dl_to_emit, &digest_list, errmsg);
+ /* Bridge descriptors? */
+ } else if (!strcmpstart(question, "downloads/bridge/")) {
+ getinfo_helper_downloads_bridge(
+ question + strlen("downloads/bridge/"),
+ &dl_to_emit, &digest_list, errmsg);
+ } else {
+ *errmsg = "Unknown download status query";
+ }
+
+ if (dl_to_emit) {
+ *answer = download_status_to_string(dl_to_emit);
+
+ return 0;
+ } else if (digest_list) {
+ *answer = digest_list_to_string(digest_list);
+ SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s));
+ smartlist_free(digest_list);
+
+ return 0;
+ } else {
+ if (!(*errmsg)) {
+ *errmsg = "Unknown error";
+ }
+
+ return -1;
+ }
+}
+
/** Allocate and return a description of <b>circ</b>'s current status,
* including its path (if any). */
static char *
@@ -2489,6 +2921,49 @@ static const getinfo_item_t getinfo_items[] = {
DOC("config/defaults",
"List of default values for configuration options. "
"See also config/names"),
+ PREFIX("downloads/networkstatus/", downloads,
+ "Download statuses for networkstatus objects"),
+ DOC("downloads/networkstatus/ns",
+ "Download status for current-mode networkstatus download"),
+ DOC("downloads/networkstatus/ns/bootstrap",
+ "Download status for bootstrap-time networkstatus download"),
+ DOC("downloads/networkstatus/ns/running",
+ "Download status for run-time networkstatus download"),
+ DOC("downloads/networkstatus/microdesc",
+ "Download status for current-mode microdesc download"),
+ DOC("downloads/networkstatus/microdesc/bootstrap",
+ "Download status for bootstrap-time microdesc download"),
+ DOC("downloads/networkstatus/microdesc/running",
+ "Download status for run-time microdesc download"),
+ PREFIX("downloads/cert/", downloads,
+ "Download statuses for certificates, by id fingerprint and "
+ "signing key"),
+ DOC("downloads/cert/fps",
+ "List of authority fingerprints for which any download statuses "
+ "exist"),
+ DOC("downloads/cert/fp/<fp>",
+ "Download status for <fp> with the default signing key; corresponds "
+ "to /fp/ URLs on directory server."),
+ DOC("downloads/cert/fp/<fp>/sks",
+ "List of signing keys for which specific download statuses are "
+ "available for this id fingerprint"),
+ DOC("downloads/cert/fp/<fp>/<sk>",
+ "Download status for <fp> with signing key <sk>; corresponds "
+ "to /fp-sk/ URLs on directory server."),
+ PREFIX("downloads/desc/", downloads,
+ "Download statuses for router descriptors, by descriptor digest"),
+ DOC("downloads/desc/descs",
+ "Return a list of known router descriptor digests"),
+ DOC("downloads/desc/<desc>",
+ "Return a download status for a given descriptor digest"),
+ PREFIX("downloads/bridge/", downloads,
+ "Download statuses for bridge descriptors, by bridge identity "
+ "digest"),
+ DOC("downloads/bridge/bridges",
+ "Return a list of configured bridge identity digests with download "
+ "statuses"),
+ DOC("downloads/bridge/<desc>",
+ "Return a download status for a given bridge identity digest"),
ITEM("info/names", misc,
"List of GETINFO options, types, and documentation."),
ITEM("events/names", misc,
@@ -2561,7 +3036,6 @@ static const getinfo_item_t getinfo_items[] = {
"Username under which the tor process is running."),
ITEM("process/descriptor-limit", misc, "File descriptor limit."),
ITEM("limits/max-mem-in-queues", misc, "Actual limit on memory in queues"),
- ITEM("dir-usage", misc, "Breakdown of bytes transferred over DirPort."),
PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."),
PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."),
PREFIX("dir/status/", dir,
@@ -2575,7 +3049,7 @@ static const getinfo_item_t getinfo_items[] = {
" ExitPolicyRejectPrivate."),
ITEM("exit-policy/reject-private/relay", policies,
"The relay-specific rules appended to the configured exit policy by"
- " ExitPolicyRejectPrivate."),
+ " ExitPolicyRejectPrivate and/or ExitPolicyRejectLocalInterfaces."),
ITEM("exit-policy/full", policies, "The entire exit policy of onion router"),
ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"),
ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"),
@@ -3094,12 +3568,15 @@ handle_control_postdescriptor(control_connection_t *conn, uint32_t len,
int cache = 0; /* eventually, we may switch this to 1 */
const char *cp = memchr(body, '\n', len);
- smartlist_t *args = smartlist_new();
- tor_assert(cp);
+
+ if (cp == NULL) {
+ connection_printf_to_buf(conn, "251 Empty body\r\n");
+ return 0;
+ }
++cp;
char *cmdline = tor_memdup_nulterm(body, cp-body);
-
+ smartlist_t *args = smartlist_new();
smartlist_split_string(args, cmdline, " ",
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
SMARTLIST_FOREACH_BEGIN(args, char *, option) {
@@ -3445,7 +3922,8 @@ handle_control_authchallenge(control_connection_t *conn, uint32_t len,
client_nonce = tor_malloc_zero(client_nonce_len);
if (base16_decode(client_nonce, client_nonce_len,
- cp, client_nonce_encoded_len) < 0) {
+ cp, client_nonce_encoded_len)
+ != (int) client_nonce_len) {
connection_write_str_to_buf("513 Invalid base16 client nonce\r\n",
conn);
connection_mark_for_close(TO_CONN(conn));
@@ -3683,14 +4161,19 @@ handle_control_hspost(control_connection_t *conn,
const char *body)
{
static const char *opt_server = "SERVER=";
- smartlist_t *args = smartlist_new();
smartlist_t *hs_dirs = NULL;
const char *encoded_desc = body;
size_t encoded_desc_len = len;
char *cp = memchr(body, '\n', len);
+ if (cp == NULL) {
+ connection_printf_to_buf(conn, "251 Empty body\r\n");
+ return 0;
+ }
char *argline = tor_strndup(body, cp-body);
+ smartlist_t *args = smartlist_new();
+
/* If any SERVER= options were specified, try parse the options line */
if (!strcasecmpstart(argline, opt_server)) {
/* encoded_desc begins after a newline character */
@@ -3791,14 +4274,20 @@ handle_control_add_onion(control_connection_t *conn,
* the other arguments are malformed.
*/
smartlist_t *port_cfgs = smartlist_new();
+ smartlist_t *auth_clients = NULL;
+ smartlist_t *auth_created_clients = NULL;
int discard_pk = 0;
int detach = 0;
int max_streams = 0;
int max_streams_close_circuit = 0;
+ rend_auth_type_t auth_type = REND_NO_AUTH;
+ /* Default to adding an anonymous hidden service if no flag is given */
+ int non_anonymous = 0;
for (size_t i = 1; i < arg_len; i++) {
static const char *port_prefix = "Port=";
static const char *flags_prefix = "Flags=";
static const char *max_s_prefix = "MaxStreams=";
+ static const char *auth_prefix = "ClientAuth=";
const char *arg = smartlist_get(args, i);
if (!strcasecmpstart(arg, port_prefix)) {
@@ -3829,10 +4318,17 @@ handle_control_add_onion(control_connection_t *conn,
* connection.
* * 'MaxStreamsCloseCircuit' - Close the circuit if MaxStreams is
* exceeded.
+ * * 'BasicAuth' - Client authorization using the 'basic' method.
+ * * 'NonAnonymous' - Add a non-anonymous Single Onion Service. If this
+ * flag is present, tor must be in non-anonymous
+ * hidden service mode. If this flag is absent,
+ * tor must be in anonymous hidden service mode.
*/
static const char *discard_flag = "DiscardPK";
static const char *detach_flag = "Detach";
static const char *max_s_close_flag = "MaxStreamsCloseCircuit";
+ static const char *basicauth_flag = "BasicAuth";
+ static const char *non_anonymous_flag = "NonAnonymous";
smartlist_t *flags = smartlist_new();
int bad = 0;
@@ -3851,6 +4347,10 @@ handle_control_add_onion(control_connection_t *conn,
detach = 1;
} else if (!strcasecmp(flag, max_s_close_flag)) {
max_streams_close_circuit = 1;
+ } else if (!strcasecmp(flag, basicauth_flag)) {
+ auth_type = REND_BASIC_AUTH;
+ } else if (!strcasecmp(flag, non_anonymous_flag)) {
+ non_anonymous = 1;
} else {
connection_printf_to_buf(conn,
"512 Invalid 'Flags' argument: %s\r\n",
@@ -3863,6 +4363,42 @@ handle_control_add_onion(control_connection_t *conn,
smartlist_free(flags);
if (bad)
goto out;
+ } else if (!strcasecmpstart(arg, auth_prefix)) {
+ char *err_msg = NULL;
+ int created = 0;
+ rend_authorized_client_t *client =
+ add_onion_helper_clientauth(arg + strlen(auth_prefix),
+ &created, &err_msg);
+ if (!client) {
+ if (err_msg) {
+ connection_write_str_to_buf(err_msg, conn);
+ tor_free(err_msg);
+ }
+ goto out;
+ }
+
+ if (auth_clients != NULL) {
+ int bad = 0;
+ SMARTLIST_FOREACH_BEGIN(auth_clients, rend_authorized_client_t *, ac) {
+ if (strcmp(ac->client_name, client->client_name) == 0) {
+ bad = 1;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ac);
+ if (bad) {
+ connection_printf_to_buf(conn,
+ "512 Duplicate name in ClientAuth\r\n");
+ rend_authorized_client_free(client);
+ goto out;
+ }
+ } else {
+ auth_clients = smartlist_new();
+ auth_created_clients = smartlist_new();
+ }
+ smartlist_add(auth_clients, client);
+ if (created) {
+ smartlist_add(auth_created_clients, client);
+ }
} else {
connection_printf_to_buf(conn, "513 Invalid argument\r\n");
goto out;
@@ -3871,6 +4407,31 @@ handle_control_add_onion(control_connection_t *conn,
if (smartlist_len(port_cfgs) == 0) {
connection_printf_to_buf(conn, "512 Missing 'Port' argument\r\n");
goto out;
+ } else if (auth_type == REND_NO_AUTH && auth_clients != NULL) {
+ connection_printf_to_buf(conn, "512 No auth type specified\r\n");
+ goto out;
+ } else if (auth_type != REND_NO_AUTH && auth_clients == NULL) {
+ connection_printf_to_buf(conn, "512 No auth clients specified\r\n");
+ goto out;
+ } else if ((auth_type == REND_BASIC_AUTH &&
+ smartlist_len(auth_clients) > 512) ||
+ (auth_type == REND_STEALTH_AUTH &&
+ smartlist_len(auth_clients) > 16)) {
+ connection_printf_to_buf(conn, "512 Too many auth clients\r\n");
+ goto out;
+ } else if (non_anonymous != rend_service_non_anonymous_mode_enabled(
+ get_options())) {
+ /* If we failed, and the non-anonymous flag is set, Tor must be in
+ * anonymous hidden service mode.
+ * The error message changes based on the current Tor config:
+ * 512 Tor is in anonymous hidden service mode
+ * 512 Tor is in non-anonymous hidden service mode
+ * (I've deliberately written them out in full here to aid searchability.)
+ */
+ connection_printf_to_buf(conn, "512 Tor is in %sanonymous hidden service "
+ "mode\r\n",
+ non_anonymous ? "" : "non-");
+ goto out;
}
/* Parse the "keytype:keyblob" argument. */
@@ -3891,35 +4452,21 @@ handle_control_add_onion(control_connection_t *conn,
}
tor_assert(!err_msg);
- /* Create the HS, using private key pk, and port config port_cfg.
+ /* Create the HS, using private key pk, client authentication auth_type,
+ * the list of auth_clients, and port config port_cfg.
* rend_service_add_ephemeral() will take ownership of pk and port_cfg,
* regardless of success/failure.
*/
char *service_id = NULL;
int ret = rend_service_add_ephemeral(pk, port_cfgs, max_streams,
max_streams_close_circuit,
+ auth_type, auth_clients,
&service_id);
port_cfgs = NULL; /* port_cfgs is now owned by the rendservice code. */
+ auth_clients = NULL; /* so is auth_clients */
switch (ret) {
case RSAE_OKAY:
{
- char *buf = NULL;
- tor_assert(service_id);
- if (key_new_alg) {
- tor_assert(key_new_blob);
- tor_asprintf(&buf,
- "250-ServiceID=%s\r\n"
- "250-PrivateKey=%s:%s\r\n"
- "250 OK\r\n",
- service_id,
- key_new_alg,
- key_new_blob);
- } else {
- tor_asprintf(&buf,
- "250-ServiceID=%s\r\n"
- "250 OK\r\n",
- service_id);
- }
if (detach) {
if (!detached_onion_services)
detached_onion_services = smartlist_new();
@@ -3930,9 +4477,26 @@ handle_control_add_onion(control_connection_t *conn,
smartlist_add(conn->ephemeral_onion_services, service_id);
}
- connection_write_str_to_buf(buf, conn);
- memwipe(buf, 0, strlen(buf));
- tor_free(buf);
+ tor_assert(service_id);
+ connection_printf_to_buf(conn, "250-ServiceID=%s\r\n", service_id);
+ if (key_new_alg) {
+ tor_assert(key_new_blob);
+ connection_printf_to_buf(conn, "250-PrivateKey=%s:%s\r\n",
+ key_new_alg, key_new_blob);
+ }
+ if (auth_created_clients) {
+ SMARTLIST_FOREACH(auth_created_clients, rend_authorized_client_t *, ac, {
+ char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie,
+ auth_type);
+ tor_assert(encoded);
+ connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n",
+ ac->client_name, encoded);
+ memwipe(encoded, 0, strlen(encoded));
+ tor_free(encoded);
+ });
+ }
+
+ connection_printf_to_buf(conn, "250 OK\r\n");
break;
}
case RSAE_BADPRIVKEY:
@@ -3944,6 +4508,9 @@ handle_control_add_onion(control_connection_t *conn,
case RSAE_BADVIRTPORT:
connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n");
break;
+ case RSAE_BADAUTH:
+ connection_printf_to_buf(conn, "512 Invalid client authorization\r\n");
+ break;
case RSAE_INTERNAL: /* FALLSTHROUGH */
default:
connection_printf_to_buf(conn, "551 Failed to add Onion Service\r\n");
@@ -3960,6 +4527,16 @@ handle_control_add_onion(control_connection_t *conn,
smartlist_free(port_cfgs);
}
+ if (auth_clients) {
+ SMARTLIST_FOREACH(auth_clients, rend_authorized_client_t *, ac,
+ rend_authorized_client_free(ac));
+ smartlist_free(auth_clients);
+ }
+ if (auth_created_clients) {
+ // Do not free entries; they are the same as auth_clients
+ smartlist_free(auth_created_clients);
+ }
+
SMARTLIST_FOREACH(args, char *, cp, {
memwipe(cp, 0, strlen(cp));
tor_free(cp);
@@ -4068,6 +4645,65 @@ add_onion_helper_keyarg(const char *arg, int discard_pk,
return pk;
}
+/** Helper function to handle parsing a ClientAuth argument to the
+ * ADD_ONION command. Return a new rend_authorized_client_t, or NULL
+ * and an optional control protocol error message on failure. The
+ * caller is responsible for freeing the returned auth_client and err_msg.
+ *
+ * If 'created' is specified, it will be set to 1 when a new cookie has
+ * been generated.
+ */
+STATIC rend_authorized_client_t *
+add_onion_helper_clientauth(const char *arg, int *created, char **err_msg)
+{
+ int ok = 0;
+
+ tor_assert(arg);
+ tor_assert(created);
+ tor_assert(err_msg);
+ *err_msg = NULL;
+
+ smartlist_t *auth_args = smartlist_new();
+ rend_authorized_client_t *client =
+ tor_malloc_zero(sizeof(rend_authorized_client_t));
+ smartlist_split_string(auth_args, arg, ":", 0, 0);
+ if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) {
+ *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n");
+ goto err;
+ }
+ client->client_name = tor_strdup(smartlist_get(auth_args, 0));
+ if (smartlist_len(auth_args) == 2) {
+ char *decode_err_msg = NULL;
+ if (rend_auth_decode_cookie(smartlist_get(auth_args, 1),
+ client->descriptor_cookie,
+ NULL, &decode_err_msg) < 0) {
+ tor_assert(decode_err_msg);
+ tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg);
+ tor_free(decode_err_msg);
+ goto err;
+ }
+ *created = 0;
+ } else {
+ crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN);
+ *created = 1;
+ }
+
+ if (!rend_valid_client_name(client->client_name)) {
+ *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n");
+ goto err;
+ }
+
+ ok = 1;
+ err:
+ SMARTLIST_FOREACH(auth_args, char *, item, tor_free(item));
+ smartlist_free(auth_args);
+ if (!ok) {
+ rend_authorized_client_free(client);
+ client = NULL;
+ }
+ return client;
+}
+
/** Called when we get a DEL_ONION command; parse the body, and remove
* the existing ephemeral Onion Service. */
static int
@@ -4213,19 +4849,14 @@ is_valid_initial_command(control_connection_t *conn, const char *cmd)
* interfaces is broken. */
#define MAX_COMMAND_LINE_LENGTH (1024*1024)
-/** Wrapper around peek_(evbuffer|buf)_has_control0 command: presents the same
- * interface as those underlying functions, but takes a connection_t intead of
- * an evbuffer or a buf_t.
+/** Wrapper around peek_buf_has_control0 command: presents the same
+ * interface as that underlying functions, but takes a connection_t intead of
+ * a buf_t.
*/
static int
peek_connection_has_control0_command(connection_t *conn)
{
- IF_HAS_BUFFEREVENT(conn, {
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- return peek_evbuffer_has_control0_command(input);
- }) ELSE_IF_NO_BUFFEREVENT {
- return peek_buf_has_control0_command(conn->inbuf);
- }
+ return peek_buf_has_control0_command(conn->inbuf);
}
/** Called when data has arrived on a v1 control connection: Try to fetch
@@ -5504,14 +6135,14 @@ control_event_buildtimeout_set(buildtimeout_set_event_t type,
/** Called when a signal has been processed from signal_callback */
int
-control_event_signal(uintptr_t signal)
+control_event_signal(uintptr_t signal_num)
{
const char *signal_string = NULL;
if (!control_event_is_interesting(EVENT_GOT_SIGNAL))
return 0;
- switch (signal) {
+ switch (signal_num) {
case SIGHUP:
signal_string = "RELOAD";
break;
@@ -5532,7 +6163,7 @@ control_event_signal(uintptr_t signal)
break;
default:
log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal",
- (unsigned long)signal);
+ (unsigned long)signal_num);
return -1;
}
diff --git a/src/or/control.h b/src/or/control.h
index 008bfb1c3b..6330c85571 100644
--- a/src/or/control.h
+++ b/src/or/control.h
@@ -259,6 +259,33 @@ STATIC crypto_pk_t *add_onion_helper_keyarg(const char *arg, int discard_pk,
const char **key_new_alg_out,
char **key_new_blob_out,
char **err_msg_out);
+STATIC rend_authorized_client_t *
+add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out);
+
+STATIC void getinfo_helper_downloads_networkstatus(
+ const char *flavor,
+ download_status_t **dl_to_emit,
+ const char **errmsg);
+STATIC void getinfo_helper_downloads_cert(
+ const char *fp_sk_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg);
+STATIC void getinfo_helper_downloads_desc(
+ const char *desc_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg);
+STATIC void getinfo_helper_downloads_bridge(
+ const char *bridge_req,
+ download_status_t **dl_to_emit,
+ smartlist_t **digest_list,
+ const char **errmsg);
+STATIC int getinfo_helper_downloads(
+ control_connection_t *control_conn,
+ const char *question, char **answer,
+ const char **errmsg);
+
#endif
#endif
diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c
index 3109d5a177..fd6de6ea7c 100644
--- a/src/or/cpuworker.c
+++ b/src/or/cpuworker.c
@@ -8,7 +8,11 @@
* \brief Uses the workqueue/threadpool code to farm CPU-intensive activities
* out to subprocesses.
*
- * Right now, we only use this for processing onionskins.
+ * The multithreading backend for this module is in workqueue.c; this module
+ * specializes workqueue.c.
+ *
+ * Right now, we only use this for processing onionskins, and invoke it mostly
+ * from onion.c.
**/
#include "or.h"
#include "channel.h"
@@ -23,11 +27,7 @@
#include "router.h"
#include "workqueue.h"
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
static void queue_pending_tasks(void);
@@ -168,6 +168,7 @@ update_state_threadfn(void *state_, void *work_)
server_onion_keys_free(state->onion_keys);
state->onion_keys = update->onion_keys;
update->onion_keys = NULL;
+ worker_state_free(update);
++state->generation;
return WQ_RPL_REPLY;
}
diff --git a/src/or/dircollate.c b/src/or/dircollate.c
index 3f9d78f02d..033a7afe0f 100644
--- a/src/or/dircollate.c
+++ b/src/or/dircollate.c
@@ -8,6 +8,17 @@
*
* \brief Collation code for figuring out which identities to vote for in
* the directory voting process.
+ *
+ * During the consensus calculation, when an authority is looking at the vote
+ * documents from all the authorities, it needs to compute the consensus for
+ * each relay listed by at least one authority. But the notion of "each
+ * relay" can be tricky: some relays have Ed25519 keys, and others don't.
+ *
+ * Moreover, older consensus methods did RSA-based ID collation alone, and
+ * ignored Ed25519 keys. We need to support those too until we're completely
+ * sure that authorities will never downgrade.
+ *
+ * This module is invoked exclusively from dirvote.c.
*/
#define DIRCOLLATE_PRIVATE
@@ -21,6 +32,9 @@ static void dircollator_collate_by_ed25519(dircollator_t *dc);
* RSA SHA1 digest) to an array of vote_routerstatus_t. */
typedef struct ddmap_entry_s {
HT_ENTRY(ddmap_entry_s) node;
+ /** A SHA1-RSA1024 identity digest and Ed25519 identity key,
+ * concatenated. (If there is no ed25519 identity key, there is no
+ * entry in this table.) */
uint8_t d[DIGEST_LEN + DIGEST256_LEN];
/* The nth member of this array corresponds to the vote_routerstatus_t (if
* any) received for this digest pair from the nth voter. */
@@ -43,12 +57,16 @@ ddmap_entry_new(int n_votes)
sizeof(vote_routerstatus_t *) * n_votes);
}
+/** Helper: compute a hash of a single ddmap_entry_t's identity (or
+ * identities) */
static unsigned
ddmap_entry_hash(const ddmap_entry_t *ent)
{
return (unsigned) siphash24g(ent->d, sizeof(ent->d));
}
+/** Helper: return true if <b>a</b> and <b>b</b> have the same
+ * identity/identities. */
static unsigned
ddmap_entry_eq(const ddmap_entry_t *a, const ddmap_entry_t *b)
{
@@ -56,7 +74,7 @@ ddmap_entry_eq(const ddmap_entry_t *a, const ddmap_entry_t *b)
}
/** Record the RSA identity of <b>ent</b> as <b>rsa_sha1</b>, and the
- * ed25519 identity as <b>ed25519</b>. */
+ * ed25519 identity as <b>ed25519</b>. Both must be provided. */
static void
ddmap_entry_set_digests(ddmap_entry_t *ent,
const uint8_t *rsa_sha1,
@@ -67,13 +85,17 @@ ddmap_entry_set_digests(ddmap_entry_t *ent,
}
HT_PROTOTYPE(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash,
- ddmap_entry_eq);
+ ddmap_entry_eq)
HT_GENERATE2(double_digest_map, ddmap_entry_s, node, ddmap_entry_hash,
- ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_);
+ ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_)
/** Helper: add a single vote_routerstatus_t <b>vrs</b> to the collator
- * <b>dc</b>, indexing it by its RSA key digest, and by the 2-tuple of
- * its RSA key digest and Ed25519 key. */
+ * <b>dc</b>, indexing it by its RSA key digest, and by the 2-tuple of its RSA
+ * key digest and Ed25519 key. It must come from the <b>vote_num</b>th
+ * vote.
+ *
+ * Requires that the vote is well-formed -- that is, that it has no duplicate
+ * routerstatus entries. We already checked for that when parsing the vote. */
static void
dircollator_add_routerstatus(dircollator_t *dc,
int vote_num,
@@ -82,12 +104,15 @@ dircollator_add_routerstatus(dircollator_t *dc,
{
const char *id = vrs->status.identity_digest;
+ /* Clear this flag; we might set it later during the voting process */
vrs->ed25519_reflects_consensus = 0;
- (void) vote;
+ (void) vote; // We don't currently need this.
+
+ /* First, add this item to the appropriate RSA-SHA-Id array. */
vote_routerstatus_t **vrs_lst = digestmap_get(dc->by_rsa_sha1, id);
if (NULL == vrs_lst) {
- vrs_lst = tor_calloc(sizeof(vote_routerstatus_t *), dc->n_votes);
+ vrs_lst = tor_calloc(dc->n_votes, sizeof(vote_routerstatus_t *));
digestmap_set(dc->by_rsa_sha1, id, vrs_lst);
}
tor_assert(vrs_lst[vote_num] == NULL);
@@ -98,6 +123,7 @@ dircollator_add_routerstatus(dircollator_t *dc,
if (! vrs->has_ed25519_listing)
return;
+ /* Now add it to the appropriate <Ed,RSA-SHA-Id> array. */
ddmap_entry_t search, *found;
memset(&search, 0, sizeof(search));
ddmap_entry_set_digests(&search, (const uint8_t *)id, ed);
diff --git a/src/or/directory.c b/src/or/directory.c
index 89b08223d2..f285e4c6ed 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -30,6 +30,7 @@
#include "routerlist.h"
#include "routerparse.h"
#include "routerset.h"
+#include "shared_random.h"
#if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
#ifndef OPENBSD
@@ -80,7 +81,6 @@ static void dir_routerdesc_download_failed(smartlist_t *failed,
int was_descriptor_digests);
static void dir_microdesc_download_failed(smartlist_t *failed,
int status_code);
-static void note_client_request(int purpose, int compressed, size_t bytes);
static int client_likes_consensus(networkstatus_t *v, const char *want_url);
static void directory_initiate_command_rend(
@@ -123,7 +123,7 @@ static void connection_dir_close_consensus_fetches(
/** Return true iff the directory purpose <b>dir_purpose</b> (and if it's
* fetching descriptors, it's fetching them for <b>router_purpose</b>)
* must use an anonymous connection to a directory. */
-STATIC int
+int
purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose)
{
if (get_options()->AllDirActionsPrivate)
@@ -495,8 +495,6 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
* sort of dir fetch we'll be doing, so it won't return a bridge
* that can't answer our question.
*/
- /* XXX024 Not all bridges handle conditional consensus downloading,
- * so, for now, never assume the server supports that. -PP */
const node_t *node = choose_random_dirguard(type);
if (node && node->ri) {
/* every bridge has a routerinfo. */
@@ -727,6 +725,10 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
node = node_get_by_id(status->identity_digest);
+ /* XXX The below check is wrong: !node means it's not in the consensus,
+ * but we haven't checked if we have a descriptor for it -- and also,
+ * we only care about the descriptor if it's a begindir-style anonymized
+ * connection. */
if (!node && anonymized_connection) {
log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we "
"don't have its router descriptor.",
@@ -744,7 +746,7 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
return;
}
- /* At this point, if we are a clients making a direct connection to a
+ /* At this point, if we are a client making a direct connection to a
* directory server, we have selected a server that has at least one address
* allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This
* selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if
@@ -869,7 +871,7 @@ connection_dir_retry_bridges(smartlist_t *descs)
char digest[DIGEST_LEN];
SMARTLIST_FOREACH(descs, const char *, cp,
{
- if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp))<0) {
+ if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp)) != DIGEST_LEN) {
log_warn(LD_BUG, "Malformed fingerprint in list: %s",
escaped(cp));
continue;
@@ -1080,7 +1082,7 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
* <b>dir_purpose</b> reveals sensitive information about a Tor
* instance's client activities. (Such connections must be performed
* through normal three-hop Tor circuits.) */
-static int
+int
is_sensitive_dir_purpose(uint8_t dir_purpose)
{
return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) ||
@@ -1135,12 +1137,10 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(is_sensitive_dir_purpose(dir_purpose) &&
- !anonymized_connection));
-#else
- (void)is_sensitive_dir_purpose;
-#endif
+ if (is_sensitive_dir_purpose(dir_purpose)) {
+ tor_assert(anonymized_connection ||
+ rend_non_anonymous_mode_enabled(options));
+ }
/* use encrypted begindir connections for everything except relays
* this provides better protection for directory fetches */
@@ -1178,7 +1178,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
/* set up conn so it's got all the data we need to remember */
tor_addr_copy(&conn->base_.addr, &addr);
conn->base_.port = port;
- conn->base_.address = tor_dup_addr(&addr);
+ conn->base_.address = tor_addr_to_str_dup(&addr);
memcpy(conn->identity_digest, digest, DIGEST_LEN);
conn->base_.purpose = dir_purpose;
@@ -1267,11 +1267,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
if_modified_since);
connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT);
- IF_HAS_BUFFEREVENT(ENTRY_TO_CONN(linked_conn), {
- connection_watch_events(ENTRY_TO_CONN(linked_conn),
- READ_EVENT|WRITE_EVENT);
- }) ELSE_IF_NO_BUFFEREVENT
- connection_start_reading(ENTRY_TO_CONN(linked_conn));
+ connection_start_reading(ENTRY_TO_CONN(linked_conn));
}
}
@@ -1306,9 +1302,9 @@ compare_strs_(const void **a, const void **b)
/** Return the URL we should use for a consensus download.
*
- * This url depends on whether or not the server we go to
- * is sufficiently new to support conditional consensus downloading,
- * i.e. GET .../consensus/<b>fpr</b>+<b>fpr</b>+<b>fpr</b>
+ * Use the "conditional consensus downloading" feature described in
+ * dir-spec.txt, i.e.
+ * GET .../consensus/<b>fpr</b>+<b>fpr</b>+<b>fpr</b>
*
* If 'resource' is provided, it is the name of a consensus flavor to request.
*/
@@ -1839,7 +1835,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
char *body;
char *headers;
char *reason = NULL;
- size_t body_len = 0, orig_len = 0;
+ size_t body_len = 0;
int status_code;
time_t date_header = 0;
long apparent_skew;
@@ -1849,7 +1845,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
int allow_partial = (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC ||
conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO ||
conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC);
- int was_compressed = 0;
time_t now = time(NULL);
int src_code;
@@ -1868,7 +1863,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
return -1;
/* case 1, fall through */
}
- orig_len = body_len;
if (parse_http_response(headers, &status_code, &date_header,
&compression, &reason) < 0) {
@@ -1986,7 +1980,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
tor_free(body);
body = new_body;
body_len = new_len;
- was_compressed = 1;
}
}
@@ -2006,7 +1999,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
}
log_info(LD_DIR,"Received consensus directory (size %d) from server "
"'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port);
- if ((r=networkstatus_set_current_consensus(body, flavname, 0))<0) {
+ if ((r=networkstatus_set_current_consensus(body, flavname, 0,
+ conn->identity_digest))<0) {
log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR,
"Unable to load %s consensus directory downloaded from "
"server '%s:%d'. I'll try again soon.",
@@ -2024,6 +2018,10 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
update_microdescs_from_networkstatus(now);
update_microdesc_downloads(now);
directory_info_has_arrived(now, 0, 0);
+ if (authdir_mode_v3(get_options())) {
+ sr_act_post_consensus(
+ networkstatus_get_latest_consensus_by_flavor(FLAV_NS));
+ }
log_info(LD_DIR, "Successfully loaded consensus.");
}
@@ -2053,7 +2051,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
}
if (src_code != -1) {
- if (trusted_dirs_load_certs_from_string(body, src_code, 1)<0) {
+ if (trusted_dirs_load_certs_from_string(body, src_code, 1,
+ conn->identity_digest)<0) {
log_warn(LD_DIR, "Unable to parse fetched certificates");
/* if we fetched more than one and only some failed, the successful
* ones got flushed to disk so it's safe to call this on them */
@@ -2249,7 +2248,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
ds->nickname);
/* XXXX use this information; be sure to upload next one
* sooner. -NM */
- /* XXXX023 On further thought, the task above implies that we're
+ /* XXXX++ On further thought, the task above implies that we're
* basing our regenerate-descriptor time on when we uploaded the
* last descriptor, not on the published time of the last
* descriptor. If those are different, that's a bad thing to
@@ -2450,7 +2449,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
break;
}
}
- note_client_request(conn->base_.purpose, was_compressed, orig_len);
tor_free(body); tor_free(headers); tor_free(reason);
return 0;
}
@@ -2651,129 +2649,6 @@ write_http_response_header(dir_connection_t *conn, ssize_t length,
cache_lifetime);
}
-#if defined(INSTRUMENT_DOWNLOADS) || defined(RUNNING_DOXYGEN)
-/* DOCDOC */
-typedef struct request_t {
- uint64_t bytes; /**< How many bytes have we transferred? */
- uint64_t count; /**< How many requests have we made? */
-} request_t;
-
-/** Map used to keep track of how much data we've up/downloaded in what kind
- * of request. Maps from request type to pointer to request_t. */
-static strmap_t *request_map = NULL;
-
-/** Record that a client request of <b>purpose</b> was made, and that
- * <b>bytes</b> bytes of possibly <b>compressed</b> data were sent/received.
- * Used to keep track of how much we've up/downloaded in what kind of
- * request. */
-static void
-note_client_request(int purpose, int compressed, size_t bytes)
-{
- char *key;
- const char *kind = NULL;
- switch (purpose) {
- case DIR_PURPOSE_FETCH_CONSENSUS: kind = "dl/consensus"; break;
- case DIR_PURPOSE_FETCH_CERTIFICATE: kind = "dl/cert"; break;
- case DIR_PURPOSE_FETCH_STATUS_VOTE: kind = "dl/vote"; break;
- case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: kind = "dl/detached_sig";
- break;
- case DIR_PURPOSE_FETCH_SERVERDESC: kind = "dl/server"; break;
- case DIR_PURPOSE_FETCH_EXTRAINFO: kind = "dl/extra"; break;
- case DIR_PURPOSE_UPLOAD_DIR: kind = "dl/ul-dir"; break;
- case DIR_PURPOSE_UPLOAD_VOTE: kind = "dl/ul-vote"; break;
- case DIR_PURPOSE_UPLOAD_SIGNATURES: kind = "dl/ul-sig"; break;
- case DIR_PURPOSE_FETCH_RENDDESC_V2: kind = "dl/rend2"; break;
- case DIR_PURPOSE_UPLOAD_RENDDESC_V2: kind = "dl/ul-rend2"; break;
- }
- if (kind) {
- tor_asprintf(&key, "%s%s", kind, compressed?".z":"");
- } else {
- tor_asprintf(&key, "unknown purpose (%d)%s",
- purpose, compressed?".z":"");
- }
- note_request(key, bytes);
- tor_free(key);
-}
-
-/** Helper: initialize the request map to instrument downloads. */
-static void
-ensure_request_map_initialized(void)
-{
- if (!request_map)
- request_map = strmap_new();
-}
-
-/** Called when we just transmitted or received <b>bytes</b> worth of data
- * because of a request of type <b>key</b> (an arbitrary identifier): adds
- * <b>bytes</b> to the total associated with key. */
-void
-note_request(const char *key, size_t bytes)
-{
- request_t *r;
- ensure_request_map_initialized();
-
- r = strmap_get(request_map, key);
- if (!r) {
- r = tor_malloc_zero(sizeof(request_t));
- strmap_set(request_map, key, r);
- }
- r->bytes += bytes;
- r->count++;
-}
-
-/** Return a newly allocated string holding a summary of bytes used per
- * request type. */
-char *
-directory_dump_request_log(void)
-{
- smartlist_t *lines;
- char *result;
- strmap_iter_t *iter;
-
- ensure_request_map_initialized();
-
- lines = smartlist_new();
-
- for (iter = strmap_iter_init(request_map);
- !strmap_iter_done(iter);
- iter = strmap_iter_next(request_map, iter)) {
- const char *key;
- void *val;
- request_t *r;
- strmap_iter_get(iter, &key, &val);
- r = val;
- smartlist_add_asprintf(lines, "%s "U64_FORMAT" "U64_FORMAT"\n",
- key, U64_PRINTF_ARG(r->bytes), U64_PRINTF_ARG(r->count));
- }
- smartlist_sort_strings(lines);
- result = smartlist_join_strings(lines, "", 0, NULL);
- SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
- smartlist_free(lines);
- return result;
-}
-#else
-static void
-note_client_request(int purpose, int compressed, size_t bytes)
-{
- (void)purpose;
- (void)compressed;
- (void)bytes;
-}
-
-void
-note_request(const char *key, size_t bytes)
-{
- (void)key;
- (void)bytes;
-}
-
-char *
-directory_dump_request_log(void)
-{
- return tor_strdup("Not supported.");
-}
-#endif
-
/** Decide whether a client would accept the consensus we have.
*
* Clients can say they only want a consensus if it's signed by more
@@ -2803,7 +2678,8 @@ client_likes_consensus(networkstatus_t *v, const char *want_url)
if (want_len > DIGEST_LEN)
want_len = DIGEST_LEN;
- if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2) < 0) {
+ if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2)
+ != (int) want_len) {
log_fn(LOG_PROTOCOL_WARN, LD_DIR,
"Failed to decode requested authority digest %s.", escaped(d));
continue;
@@ -2845,18 +2721,81 @@ choose_compression_level(ssize_t n_bytes)
}
}
+/** Information passed to handle a GET request. */
+typedef struct get_handler_args_t {
+ /** True if the client asked for compressed data. */
+ int compressed;
+ /** If nonzero, the time included an if-modified-since header with this
+ * value. */
+ time_t if_modified_since;
+ /** String containing the requested URL or resource. */
+ const char *url;
+ /** String containing the HTTP headers */
+ const char *headers;
+} get_handler_args_t;
+
+/** Entry for handling an HTTP GET request.
+ *
+ * This entry matches a request if "string" is equal to the requested
+ * resource, or if "is_prefix" is true and "string" is a prefix of the
+ * requested resource.
+ *
+ * The 'handler' function is called to handle the request. It receives
+ * an arguments structure, and must return 0 on success or -1 if we should
+ * close the connection.
+ **/
+typedef struct url_table_ent_s {
+ const char *string;
+ int is_prefix;
+ int (*handler)(dir_connection_t *conn, const get_handler_args_t *args);
+} url_table_ent_t;
+
+static int handle_get_frontpage(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_current_consensus(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_status_vote(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_microdesc(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_descriptor(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_keys(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_rendezvous2(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_robots(dir_connection_t *conn,
+ const get_handler_args_t *args);
+static int handle_get_networkstatus_bridges(dir_connection_t *conn,
+ const get_handler_args_t *args);
+
+/** Table for handling GET requests. */
+static const url_table_ent_t url_table[] = {
+ { "/tor/", 0, handle_get_frontpage },
+ { "/tor/status-vote/current/consensus", 1, handle_get_current_consensus },
+ { "/tor/status-vote/current/", 1, handle_get_status_vote },
+ { "/tor/status-vote/next/", 1, handle_get_status_vote },
+ { "/tor/micro/d/", 1, handle_get_microdesc },
+ { "/tor/server/", 1, handle_get_descriptor },
+ { "/tor/extra/", 1, handle_get_descriptor },
+ { "/tor/keys/", 1, handle_get_keys },
+ { "/tor/rendezvous2/", 1, handle_get_rendezvous2 },
+ { "/tor/robots.txt", 0, handle_get_robots },
+ { "/tor/networkstatus-bridges", 0, handle_get_networkstatus_bridges },
+ { NULL, 0, NULL },
+};
+
/** Helper function: called when a dirserver gets a complete HTTP GET
* request. Look for a request for a directory or for a rendezvous
* service descriptor. On finding one, write a response into
- * conn-\>outbuf. If the request is unrecognized, send a 400.
- * Always return 0. */
+ * conn-\>outbuf. If the request is unrecognized, send a 404.
+ * Return 0 if we handled this successfully, or -1 if we need to close
+ * the connection. */
STATIC int
directory_handle_command_get(dir_connection_t *conn, const char *headers,
const char *req_body, size_t req_body_len)
{
- size_t dlen;
char *url, *url_mem, *header;
- const or_options_t *options = get_options();
time_t if_modified_since = 0;
int compressed;
size_t url_len;
@@ -2896,29 +2835,73 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
url_len -= 2;
}
- if (!strcmp(url,"/tor/")) {
- const char *frontpage = get_dirportfrontpage();
-
- if (frontpage) {
- dlen = strlen(frontpage);
- /* Let's return a disclaimer page (users shouldn't use V1 anymore,
- and caches don't fetch '/', so this is safe). */
-
- /* [We don't check for write_bucket_low here, since we want to serve
- * this page no matter what.] */
- note_request(url, dlen);
- write_http_response_header_impl(conn, dlen, "text/html", "identity",
- NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME);
- connection_write_to_buf(frontpage, dlen, TO_CONN(conn));
+ get_handler_args_t args;
+ args.url = url;
+ args.headers = headers;
+ args.if_modified_since = if_modified_since;
+ args.compressed = compressed;
+
+ int i, result = -1;
+ for (i = 0; url_table[i].string; ++i) {
+ int match;
+ if (url_table[i].is_prefix) {
+ match = !strcmpstart(url, url_table[i].string);
+ } else {
+ match = !strcmp(url, url_table[i].string);
+ }
+ if (match) {
+ result = url_table[i].handler(conn, &args);
goto done;
}
- /* if no disclaimer file, fall through and continue */
}
- if (!strcmpstart(url, "/tor/status-vote/current/consensus")) {
+ /* we didn't recognize the url */
+ write_http_status_line(conn, 404, "Not found");
+ result = 0;
+
+ done:
+ tor_free(url_mem);
+ return result;
+}
+
+/** Helper function for GET / or GET /tor/
+ */
+static int
+handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ (void) args; /* unused */
+ const char *frontpage = get_dirportfrontpage();
+
+ if (frontpage) {
+ size_t dlen;
+ dlen = strlen(frontpage);
+ /* Let's return a disclaimer page (users shouldn't use V1 anymore,
+ and caches don't fetch '/', so this is safe). */
+
+ /* [We don't check for write_bucket_low here, since we want to serve
+ * 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));
+ } else {
+ write_http_status_line(conn, 404, "Not found");
+ }
+ return 0;
+}
+
+/** Helper function for GET /tor/status-vote/current/consensus
+ */
+static int
+handle_get_current_consensus(dir_connection_t *conn,
+ const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ const int compressed = args->compressed;
+ const time_t if_modified_since = args->if_modified_since;
+
+ {
/* v3 network status fetch. */
smartlist_t *dir_fps = smartlist_new();
- const char *request_type = NULL;
long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
if (1) {
@@ -2967,7 +2950,6 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
tor_free(flavor);
smartlist_add(dir_fps, fp);
}
- request_type = compressed?"v3.z":"v3";
lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0;
}
@@ -2992,7 +2974,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
goto done;
}
- dlen = dirserv_estimate_data_size(dir_fps, 0, compressed);
+ size_t dlen = dirserv_estimate_data_size(dir_fps, 0, compressed);
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
log_debug(LD_DIRSERV,
"Client asked for network status lists, but we've been "
@@ -3022,8 +3004,6 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
}
}
- // note_request(request_type,dlen);
- (void) request_type;
write_http_response_header(conn, -1, compressed,
smartlist_len(dir_fps) == 1 ? lifetime : 0);
conn->fingerprint_stack = dir_fps;
@@ -3036,17 +3016,24 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
goto done;
}
- if (!strcmpstart(url,"/tor/status-vote/current/") ||
- !strcmpstart(url,"/tor/status-vote/next/")) {
- /* XXXX If-modified-since is only implemented for the current
- * consensus: that's probably fine, since it's the only vote document
- * people fetch much. */
+ done:
+ return 0;
+}
+
+/** Helper function for GET /tor/status-vote/{current,next}/...
+ */
+static int
+handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ const int compressed = args->compressed;
+ {
int current;
ssize_t body_len = 0;
ssize_t estimated_len = 0;
smartlist_t *items = smartlist_new();
smartlist_t *dir_items = smartlist_new();
- int lifetime = 60; /* XXXX023 should actually use vote intervals. */
+ int lifetime = 60; /* XXXX?? should actually use vote intervals. */
url += strlen("/tor/status-vote/");
current = !strcmpstart(url, "current/");
url = strchr(url, '/');
@@ -3136,8 +3123,18 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
smartlist_free(dir_items);
goto done;
}
+ done:
+ return 0;
+}
- if (!strcmpstart(url, "/tor/micro/d/")) {
+/** Helper function for GET /tor/micro/d/...
+ */
+static int
+handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ const int compressed = args->compressed;
+ {
smartlist_t *fps = smartlist_new();
dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"),
@@ -3150,7 +3147,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
smartlist_free(fps);
goto done;
}
- dlen = dirserv_estimate_microdesc_size(fps, compressed);
+ size_t dlen = dirserv_estimate_microdesc_size(fps, compressed);
if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
log_info(LD_DIRSERV,
"Client asked for server descriptors, but we've been "
@@ -3173,12 +3170,24 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
goto done;
}
+ done:
+ return 0;
+}
+
+/** Helper function for GET /tor/{server,extra}/...
+ */
+static int
+handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ const int compressed = args->compressed;
+ const or_options_t *options = get_options();
if (!strcmpstart(url,"/tor/server/") ||
(!options->BridgeAuthoritativeDir &&
!options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
+ size_t dlen;
int res;
const char *msg;
- const char *request_type = NULL;
int cache_lifetime = 0;
int is_extra = !strcmpstart(url,"/tor/extra/");
url += is_extra ? strlen("/tor/extra/") : strlen("/tor/server/");
@@ -3189,24 +3198,16 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
is_extra);
if (!strcmpstart(url, "fp/")) {
- request_type = compressed?"/tor/server/fp.z":"/tor/server/fp";
if (smartlist_len(conn->fingerprint_stack) == 1)
cache_lifetime = ROUTERDESC_CACHE_LIFETIME;
} else if (!strcmpstart(url, "authority")) {
- request_type = compressed?"/tor/server/authority.z":
- "/tor/server/authority";
cache_lifetime = ROUTERDESC_CACHE_LIFETIME;
} else if (!strcmpstart(url, "all")) {
- request_type = compressed?"/tor/server/all.z":"/tor/server/all";
cache_lifetime = FULL_DIR_CACHE_LIFETIME;
} else if (!strcmpstart(url, "d/")) {
- request_type = compressed?"/tor/server/d.z":"/tor/server/d";
if (smartlist_len(conn->fingerprint_stack) == 1)
cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME;
- } else {
- request_type = "/tor/server/?";
}
- (void) request_type; /* usable for note_request. */
if (!strcmpstart(url, "d/"))
conn->dir_spool_src =
is_extra ? DIR_SPOOL_EXTRA_BY_DIGEST : DIR_SPOOL_SERVER_BY_DIGEST;
@@ -3242,8 +3243,19 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
}
goto done;
}
+ done:
+ return 0;
+}
- if (!strcmpstart(url,"/tor/keys/")) {
+/** Helper function for GET /tor/keys/...
+ */
+static int
+handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ const int compressed = args->compressed;
+ const time_t if_modified_since = args->if_modified_since;
+ {
smartlist_t *certs = smartlist_new();
ssize_t len = -1;
if (!strcmp(url, "/tor/keys/all")) {
@@ -3328,9 +3340,17 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
smartlist_free(certs);
goto done;
}
+ done:
+ return 0;
+}
- if (connection_dir_is_encrypted(conn) &&
- !strcmpstart(url,"/tor/rendezvous2/")) {
+/** Helper function for GET /tor/rendezvous2/
+ */
+static int
+handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ if (connection_dir_is_encrypted(conn)) {
/* Handle v2 rendezvous descriptor fetch request. */
const char *descp;
const char *query = url + strlen("/tor/rendezvous2/");
@@ -3353,16 +3373,30 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
write_http_status_line(conn, 400, "Bad request");
}
goto done;
+ } else {
+ /* Not encrypted! */
+ write_http_status_line(conn, 404, "Not found");
}
+ done:
+ return 0;
+}
+/** Helper function for GET /tor/networkstatus-bridges
+ */
+static int
+handle_get_networkstatus_bridges(dir_connection_t *conn,
+ const get_handler_args_t *args)
+{
+ const char *headers = args->headers;
+
+ const or_options_t *options = get_options();
if (options->BridgeAuthoritativeDir &&
options->BridgePassword_AuthDigest_ &&
- connection_dir_is_encrypted(conn) &&
- !strcmp(url,"/tor/networkstatus-bridges")) {
+ connection_dir_is_encrypted(conn)) {
char *status;
char digest[DIGEST256_LEN];
- header = http_get_header(headers, "Authorization: Basic ");
+ char *header = http_get_header(headers, "Authorization: Basic ");
if (header)
crypto_digest256(digest, header, strlen(header), DIGEST_SHA256);
@@ -3378,75 +3412,27 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
/* all happy now. send an answer. */
status = networkstatus_getinfo_by_purpose("bridge", time(NULL));
- dlen = strlen(status);
+ size_t dlen = strlen(status);
write_http_response_header(conn, dlen, 0, 0);
connection_write_to_buf(status, dlen, TO_CONN(conn));
tor_free(status);
goto done;
}
+ done:
+ return 0;
+}
- if (!strcmpstart(url,"/tor/bytes.txt")) {
- char *bytes = directory_dump_request_log();
- size_t len = strlen(bytes);
- write_http_response_header(conn, len, 0, 0);
- connection_write_to_buf(bytes, len, TO_CONN(conn));
- tor_free(bytes);
- goto done;
- }
-
- if (!strcmp(url,"/tor/robots.txt")) { /* /robots.txt will have been
- rewritten to /tor/robots.txt */
- char robots[] = "User-agent: *\r\nDisallow: /\r\n";
+/** Helper function for GET robots.txt or /tor/robots.txt */
+static int
+handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args)
+{
+ (void)args;
+ {
+ const char robots[] = "User-agent: *\r\nDisallow: /\r\n";
size_t len = strlen(robots);
write_http_response_header(conn, len, 0, ROBOTS_CACHE_LIFETIME);
connection_write_to_buf(robots, len, TO_CONN(conn));
- goto done;
- }
-
-#if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
-#define ADD_MALLINFO_LINE(x) do { \
- smartlist_add_asprintf(lines, "%s %d\n", #x, mi.x); \
- }while(0);
-
- if (!strcmp(url,"/tor/mallinfo.txt") &&
- (tor_addr_eq_ipv4h(&conn->base_.addr, 0x7f000001ul))) {
- char *result;
- size_t len;
- struct mallinfo mi;
- smartlist_t *lines;
-
- memset(&mi, 0, sizeof(mi));
- mi = mallinfo();
- lines = smartlist_new();
-
- ADD_MALLINFO_LINE(arena)
- ADD_MALLINFO_LINE(ordblks)
- ADD_MALLINFO_LINE(smblks)
- ADD_MALLINFO_LINE(hblks)
- ADD_MALLINFO_LINE(hblkhd)
- ADD_MALLINFO_LINE(usmblks)
- ADD_MALLINFO_LINE(fsmblks)
- ADD_MALLINFO_LINE(uordblks)
- ADD_MALLINFO_LINE(fordblks)
- ADD_MALLINFO_LINE(keepcost)
-
- result = smartlist_join_strings(lines, "", 0, NULL);
- SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp));
- smartlist_free(lines);
-
- len = strlen(result);
- write_http_response_header(conn, len, 0, 0);
- connection_write_to_buf(result, len, TO_CONN(conn));
- tor_free(result);
- goto done;
}
-#endif
-
- /* we didn't recognize the url */
- write_http_status_line(conn, 404, "Not found");
-
- done:
- tor_free(url_mem);
return 0;
}
@@ -3646,16 +3632,8 @@ connection_dir_finished_flushing(dir_connection_t *conn)
return 0;
case DIR_CONN_STATE_SERVER_WRITING:
if (conn->dir_spool_src != DIR_SPOOL_NONE) {
-#ifdef USE_BUFFEREVENTS
- /* This can happen with paired bufferevents, since a paired connection
- * can flush immediately when you write to it, making the subsequent
- * check in connection_handle_write_cb() decide that the connection
- * is flushed. */
- log_debug(LD_DIRSERV, "Emptied a dirserv buffer, but still spooling.");
-#else
log_warn(LD_BUG, "Emptied a dirserv buffer, but it's still spooling!");
connection_mark_for_close(TO_CONN(conn));
-#endif
} else {
log_debug(LD_DIRSERV, "Finished writing server response. Closing.");
connection_mark_for_close(TO_CONN(conn));
@@ -3725,26 +3703,24 @@ connection_dir_finished_connecting(dir_connection_t *conn)
STATIC const smartlist_t *
find_dl_schedule(download_status_t *dls, const or_options_t *options)
{
- const int dir_server = dir_server_mode(options);
- const int multi_d = networkstatus_consensus_can_use_multiple_directories(
- options);
- const int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping(
- time(NULL));
- const int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks(
- options);
switch (dls->schedule) {
case DL_SCHED_GENERIC:
- if (dir_server) {
+ /* Any other directory document */
+ if (dir_server_mode(options)) {
+ /* A directory authority or directory mirror */
return options->TestingServerDownloadSchedule;
} else {
return options->TestingClientDownloadSchedule;
}
case DL_SCHED_CONSENSUS:
- if (!multi_d) {
+ if (!networkstatus_consensus_can_use_multiple_directories(options)) {
+ /* A public relay */
return options->TestingServerConsensusDownloadSchedule;
} else {
- if (we_are_bootstrapping) {
- if (!use_fallbacks) {
+ /* A client or bridge */
+ if (networkstatus_consensus_is_bootstrapping(time(NULL))) {
+ /* During bootstrapping */
+ if (!networkstatus_consensus_can_use_extra_fallbacks(options)) {
/* A bootstrapping client without extra fallback directories */
return
options->ClientBootstrapConsensusAuthorityOnlyDownloadSchedule;
@@ -3760,6 +3736,8 @@ find_dl_schedule(download_status_t *dls, const or_options_t *options)
options->ClientBootstrapConsensusFallbackDownloadSchedule;
}
} else {
+ /* A client with a reasonably live consensus, with or without
+ * certificates */
return options->TestingClientConsensusDownloadSchedule;
}
}
@@ -3773,17 +3751,100 @@ find_dl_schedule(download_status_t *dls, const or_options_t *options)
return NULL;
}
-/* Find the current delay for dls based on schedule.
- * Set dls->next_attempt_at based on now, and return the delay.
+/** Decide which minimum and maximum delay step we want to use based on
+ * descriptor type in <b>dls</b> and <b>options</b>.
+ * Helper function for download_status_schedule_get_delay(). */
+STATIC void
+find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options,
+ int *min, int *max)
+{
+ tor_assert(dls);
+ tor_assert(options);
+ tor_assert(min);
+ tor_assert(max);
+
+ /*
+ * For now, just use the existing schedule config stuff and pick the
+ * first/last entries off to get min/max delay for backoff purposes
+ */
+ const smartlist_t *schedule = find_dl_schedule(dls, options);
+ tor_assert(schedule != NULL && smartlist_len(schedule) >= 2);
+ *min = *((int *)(smartlist_get(schedule, 0)));
+ 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.
+ *
+ * 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)
+{
+ /* 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 (delay && delay < (INT_MAX-1) / multiplier) {
+ max_increment = delay * multiplier;
+ } else if (delay) {
+ max_increment = INT_MAX-1;
+ } else {
+ max_increment = 1;
+ }
+
+ if (BUG(max_increment < 1))
+ max_increment = 1;
+
+ /* the + 1 here is so that we always wait longer than last time. */
+ int increment = crypto_rand_int(max_increment)+1;
+
+ if (increment < max_delay - delay)
+ return delay + increment;
+ else
+ return max_delay;
+}
+
+/** Find the current delay for dls based on schedule or min_delay/
+ * max_delay if we're using exponential backoff. If dls->backoff is
+ * DL_SCHED_RANDOM_EXPONENTIAL, we must have 0 <= min_delay <= max_delay <=
+ * INT_MAX, but schedule may be set to NULL; otherwise schedule is required.
+ * This function sets dls->next_attempt_at based on now, and returns the delay.
* Helper for download_status_increment_failure and
* download_status_increment_attempt. */
STATIC int
download_status_schedule_get_delay(download_status_t *dls,
const smartlist_t *schedule,
+ int min_delay, int max_delay,
time_t now)
{
tor_assert(dls);
- tor_assert(schedule);
+ /* We don't need a schedule if we're using random exponential backoff */
+ tor_assert(dls->backoff == DL_SCHED_RANDOM_EXPONENTIAL ||
+ schedule != NULL);
+ /* If we're using random exponential backoff, we do need min/max delay */
+ tor_assert(dls->backoff != DL_SCHED_RANDOM_EXPONENTIAL ||
+ (min_delay >= 0 && max_delay >= min_delay));
int delay = INT_MAX;
uint8_t dls_schedule_position = (dls->increment_on
@@ -3791,20 +3852,50 @@ download_status_schedule_get_delay(download_status_t *dls,
? dls->n_download_attempts
: dls->n_download_failures);
- if (dls_schedule_position < smartlist_len(schedule))
- delay = *(int *)smartlist_get(schedule, dls_schedule_position);
- else if (dls_schedule_position == IMPOSSIBLE_TO_DOWNLOAD)
- delay = INT_MAX;
- else
- delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1);
+ if (dls->backoff == DL_SCHED_DETERMINISTIC) {
+ if (dls_schedule_position < smartlist_len(schedule))
+ delay = *(int *)smartlist_get(schedule, dls_schedule_position);
+ else if (dls_schedule_position == IMPOSSIBLE_TO_DOWNLOAD)
+ delay = INT_MAX;
+ else
+ delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1);
+ } else if (dls->backoff == DL_SCHED_RANDOM_EXPONENTIAL) {
+ /* Check if we missed a reset somehow */
+ if (dls->last_backoff_position > dls_schedule_position) {
+ dls->last_backoff_position = 0;
+ dls->last_delay_used = 0;
+ }
+
+ if (dls_schedule_position > 0) {
+ delay = dls->last_delay_used;
+
+ while (dls->last_backoff_position < dls_schedule_position) {
+ /* Do one increment step */
+ delay = next_random_exponential_delay(delay, max_delay);
+ /* Update our position */
+ ++(dls->last_backoff_position);
+ }
+ } else {
+ /* If we're just starting out, use the minimum delay */
+ delay = min_delay;
+ }
+
+ /* Clamp it within min/max if we have them */
+ if (min_delay >= 0 && delay < min_delay) delay = min_delay;
+ if (max_delay != INT_MAX && delay > max_delay) delay = max_delay;
+
+ /* Store it for next time */
+ dls->last_backoff_position = dls_schedule_position;
+ dls->last_delay_used = delay;
+ }
/* A negative delay makes no sense. Knowing that delay is
* non-negative allows us to safely do the wrapping check below. */
tor_assert(delay >= 0);
- /* Avoid now+delay overflowing INT_MAX, by comparing with a subtraction
+ /* Avoid now+delay overflowing TIME_MAX, by comparing with a subtraction
* that won't overflow (since delay is non-negative). */
- if (delay < INT_MAX && now <= INT_MAX - delay) {
+ if (delay < INT_MAX && now <= TIME_MAX - delay) {
dls->next_attempt_at = now+delay;
} else {
dls->next_attempt_at = TIME_MAX;
@@ -3856,13 +3947,16 @@ time_t
download_status_increment_failure(download_status_t *dls, int status_code,
const char *item, int server, time_t now)
{
+ (void) status_code; // XXXX no longer used.
+ (void) server; // XXXX no longer used.
int increment = -1;
+ int min_delay = 0, max_delay = INT_MAX;
+
tor_assert(dls);
- /* only count the failure if it's permanent, or we're a server */
- if (status_code != 503 || server) {
- if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1)
- ++dls->n_download_failures;
+ /* count the failure */
+ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) {
+ ++dls->n_download_failures;
}
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
@@ -3877,7 +3971,9 @@ download_status_increment_failure(download_status_t *dls, int status_code,
/* only return a failure retry time if this schedule increments on failures
*/
const smartlist_t *schedule = find_dl_schedule(dls, get_options());
- increment = download_status_schedule_get_delay(dls, schedule, now);
+ find_dl_min_and_max_delay(dls, get_options(), &min_delay, &max_delay);
+ increment = download_status_schedule_get_delay(dls, schedule,
+ min_delay, max_delay, now);
}
download_status_log_helper(item, !dls->increment_on, "failed",
@@ -3906,12 +4002,14 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
time_t now)
{
int delay = -1;
+ int min_delay = 0, max_delay = INT_MAX;
+
tor_assert(dls);
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
/* this schedule should retry on failure, and not launch any concurrent
attempts */
- log_info(LD_BUG, "Tried to launch an attempt-based connection on a "
+ log_warn(LD_BUG, "Tried to launch an attempt-based connection on a "
"failure-based schedule.");
return TIME_MAX;
}
@@ -3920,7 +4018,9 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
++dls->n_download_attempts;
const smartlist_t *schedule = find_dl_schedule(dls, get_options());
- delay = download_status_schedule_get_delay(dls, schedule, now);
+ find_dl_min_and_max_delay(dls, get_options(), &min_delay, &max_delay);
+ delay = download_status_schedule_get_delay(dls, schedule,
+ min_delay, max_delay, now);
download_status_log_helper(item, dls->increment_on, "attempted",
"on failure", dls->n_download_attempts,
@@ -3952,6 +4052,8 @@ download_status_reset(download_status_t *dls)
dls->n_download_failures = 0;
dls->n_download_attempts = 0;
dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0);
+ dls->last_backoff_position = 0;
+ dls->last_delay_used = 0;
/* Don't reset dls->want_authority or dls->increment_on */
}
@@ -4001,7 +4103,7 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code,
}
SMARTLIST_FOREACH_BEGIN(failed, const char *, cp) {
download_status_t *dls = NULL;
- if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp)) < 0) {
+ if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp)) != DIGEST_LEN) {
log_warn(LD_BUG, "Malformed fingerprint in list: %s", escaped(cp));
continue;
}
@@ -4098,9 +4200,10 @@ dir_split_resource_into_fingerprint_pairs(const char *res,
"Skipping digest pair %s with missing dash.", escaped(cp));
} else {
fp_pair_t pair;
- if (base16_decode(pair.first, DIGEST_LEN, cp, HEX_DIGEST_LEN)<0 ||
- base16_decode(pair.second,
- DIGEST_LEN, cp+HEX_DIGEST_LEN+1, HEX_DIGEST_LEN)<0) {
+ if (base16_decode(pair.first, DIGEST_LEN,
+ cp, HEX_DIGEST_LEN) != DIGEST_LEN ||
+ base16_decode(pair.second,DIGEST_LEN,
+ cp+HEX_DIGEST_LEN+1, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_info(LD_DIR, "Skipping non-decodable digest pair %s", escaped(cp));
} else {
smartlist_add(pairs_result, tor_memdup(&pair, sizeof(pair)));
@@ -4178,8 +4281,9 @@ dir_split_resource_into_fingerprints(const char *resource,
}
d = tor_malloc_zero(digest_len);
if (decode_hex ?
- (base16_decode(d, digest_len, cp, hex_digest_len)<0) :
- (base64_decode(d, digest_len, cp, base64_digest_len)<0)) {
+ (base16_decode(d, digest_len, cp, hex_digest_len) != digest_len) :
+ (base64_decode(d, digest_len, cp, base64_digest_len)
+ != digest_len)) {
log_info(LD_DIR, "Skipping non-decodable digest %s", escaped(cp));
smartlist_del_keeporder(fp_tmp, i--);
goto again;
diff --git a/src/or/directory.h b/src/or/directory.h
index 7646cac03f..629b3ead90 100644
--- a/src/or/directory.h
+++ b/src/or/directory.h
@@ -114,9 +114,15 @@ static inline int
download_status_is_ready(download_status_t *dls, time_t now,
int max_failures)
{
- int under_failure_limit = (dls->n_download_failures <= max_failures
- && dls->n_download_attempts <= max_failures);
- return (under_failure_limit && dls->next_attempt_at <= now);
+ if (dls->backoff == DL_SCHED_DETERMINISTIC) {
+ /* Deterministic schedules can hit an endpoint; exponential backoff
+ * schedules just wait longer and longer. */
+ int under_failure_limit = (dls->n_download_failures <= max_failures
+ && dls->n_download_attempts <= max_failures);
+ if (!under_failure_limit)
+ return 0;
+ }
+ return dls->next_attempt_at <= now;
}
static void download_status_mark_impossible(download_status_t *dl);
@@ -132,12 +138,15 @@ 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);
+/* Yes, these two functions are confusingly similar.
+ * Let's sort that out in #20077. */
+int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose);
+int is_sensitive_dir_purpose(uint8_t dir_purpose);
+
#ifdef TOR_UNIT_TESTS
/* Used only by directory.c and test_dir.c */
STATIC int parse_http_url(const char *headers, char **url);
-STATIC int purpose_needs_anonymity(uint8_t dir_purpose,
- uint8_t router_purpose);
STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,
const char *resource);
STATIC int directory_handle_command_get(dir_connection_t *conn,
@@ -146,6 +155,7 @@ STATIC int directory_handle_command_get(dir_connection_t *conn,
size_t req_body_len);
STATIC int download_status_schedule_get_delay(download_status_t *dls,
const smartlist_t *schedule,
+ int min_delay, int max_delay,
time_t now);
STATIC char* authdir_type_to_string(dirinfo_type_t auth);
@@ -154,6 +164,11 @@ STATIC int should_use_directory_guards(const or_options_t *options);
STATIC zlib_compression_level_t choose_compression_level(ssize_t n_bytes);
STATIC const smartlist_t *find_dl_schedule(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);
+
#endif
#endif
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index dafaed8bf2..da34c196f4 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -19,10 +19,12 @@
#include "dirvote.h"
#include "hibernate.h"
#include "keypin.h"
+#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
+#include "protover.h"
#include "rephist.h"
#include "router.h"
#include "routerlist.h"
@@ -34,6 +36,24 @@
* \file dirserv.c
* \brief Directory server core implementation. Manages directory
* contents and generates directories.
+ *
+ * This module implements most of directory cache functionality, and some of
+ * the directory authority functionality. The directory.c module delegates
+ * here in order to handle incoming requests from clients, via
+ * connection_dirserv_flushed_some() and its kin. In order to save RAM, this
+ * module is reponsible for spooling directory objects (in whole or in part)
+ * onto buf_t instances, and then closing the dir_connection_t once the
+ * objects are totally flushed.
+ *
+ * The directory.c module also delegates here for handling descriptor uploads
+ * via dirserv_add_multiple_descriptors().
+ *
+ * Additionally, this module handles some aspects of voting, including:
+ * deciding how to vote on individual flags (based on decisions reached in
+ * rephist.c), of formatting routerstatus lines, and deciding what relays to
+ * include in an authority's vote. (TODO: Those functions could profitably be
+ * split off. They only live in this file because historically they were
+ * shared among the v1, v2, and v3 directory code.)
*/
/** How far in the future do we allow a router to get? (seconds) */
@@ -44,10 +64,6 @@
* directory authorities. */
#define MAX_UNTRUSTED_NETWORKSTATUSES 16
-extern time_t time_of_process_start; /* from main.c */
-
-extern long stats_n_seconds_working; /* from main.c */
-
/** Total number of routers with measured bandwidth; this is set by
* dirserv_count_measured_bws() before the loop in
* dirserv_generate_networkstatus_vote_obj() and checked by
@@ -125,7 +141,8 @@ add_fingerprint_to_dir(const char *fp, authdir_config_t *list,
fingerprint = tor_strdup(fp);
tor_strstrip(fingerprint, " ");
- if (base16_decode(d, DIGEST_LEN, fingerprint, strlen(fingerprint))) {
+ if (base16_decode(d, DIGEST_LEN,
+ fingerprint, strlen(fingerprint)) != DIGEST_LEN) {
log_warn(LD_DIRSERV, "Couldn't decode fingerprint \"%s\"",
escaped(fp));
tor_free(fingerprint);
@@ -202,7 +219,7 @@ dirserv_load_fingerprint_file(void)
tor_strstrip(fingerprint, " "); /* remove spaces */
if (strlen(fingerprint) != HEX_DIGEST_LEN ||
base16_decode(digest_tmp, sizeof(digest_tmp),
- fingerprint, HEX_DIGEST_LEN) < 0) {
+ fingerprint, HEX_DIGEST_LEN) != sizeof(digest_tmp)) {
log_notice(LD_CONFIG,
"Invalid fingerprint (nickname '%s', "
"fingerprint %s). Skipping.",
@@ -257,6 +274,20 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg,
return FP_REJECT;
}
+ /* dirserv_get_status_impl already rejects versions older than 0.2.4.18-rc,
+ * and onion_curve25519_pkey was introduced in 0.2.4.8-alpha.
+ * But just in case a relay doesn't provide or lies about its version, or
+ * doesn't include an ntor key in its descriptor, check that it exists,
+ * and is non-zero (clients check that it's non-zero before using it). */
+ if (!routerinfo_has_curve25519_onion_key(router)) {
+ log_fn(severity, LD_DIR,
+ "Descriptor from router %s is missing an ntor curve25519 onion "
+ "key.", router_describe(router));
+ if (msg)
+ *msg = "Missing ntor curve25519 onion key. Please upgrade!";
+ return FP_REJECT;
+ }
+
if (router->cache_info.signing_key_cert) {
/* This has an ed25519 identity key. */
if (KEYPIN_MISMATCH ==
@@ -334,6 +365,16 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname,
strmap_size(fingerprint_list->fp_by_name),
digestmap_size(fingerprint_list->status_by_digest));
+ if (platform) {
+ tor_version_t ver_tmp;
+ if (tor_version_parse_platform(platform, &ver_tmp, 1) < 0) {
+ if (msg) {
+ *msg = "Malformed platform string.";
+ }
+ return FP_REJECT;
+ }
+ }
+
/* Versions before Tor 0.2.4.18-rc are too old to support, and are
* missing some important security fixes too. Disable them. */
if (platform && !tor_version_as_new_as(platform,"0.2.4.18-rc")) {
@@ -342,6 +383,17 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname,
return FP_REJECT;
}
+ /* Tor 0.2.9.x where x<5 suffers from bug #20499, where relays don't
+ * keep their consensus up to date so they make bad guards.
+ * The simple fix is to just drop them from the network. */
+ if (platform &&
+ tor_version_as_new_as(platform,"0.2.9.0-alpha") &&
+ !tor_version_as_new_as(platform,"0.2.9.5-alpha")) {
+ if (msg)
+ *msg = "Tor version contains bug 20499. Please upgrade!";
+ return FP_REJECT;
+ }
+
status_by_digest = digestmap_get(fingerprint_list->status_by_digest,
id_digest);
if (status_by_digest)
@@ -349,7 +401,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname,
if (result & FP_REJECT) {
if (msg)
- *msg = "Fingerprint is marked rejected";
+ *msg = "Fingerprint is marked rejected -- please contact us?";
return FP_REJECT;
} else if (result & FP_INVALID) {
if (msg)
@@ -367,7 +419,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname,
log_fn(severity, LD_DIRSERV, "Rejecting '%s' because of address '%s'",
nickname, fmt_addr32(addr));
if (msg)
- *msg = "Authdir is rejecting routers in this range.";
+ *msg = "Suspicious relay address range -- please contact us?";
return FP_REJECT;
}
if (!authdir_policy_valid_address(addr, or_port)) {
@@ -574,7 +626,11 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose,
* passed back to the origin of this descriptor, or NULL if there is no such
* message. Use <b>source</b> to produce better log messages.
*
- * Return the status of the operation
+ * If <b>ri</b> is not added to the list of server descriptors, free it.
+ * That means the caller must not access <b>ri</b> after this function
+ * returns, since it might have been freed.
+ *
+ * Return the status of the operation.
*
* This function is only called when fresh descriptors are posted, not when
* we re-load the cache.
@@ -602,8 +658,8 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
control_event_or_authdir_new_descriptor("REJECTED",
ri->cache_info.signed_descriptor_body,
desclen, *msg);
- routerinfo_free(ri);
- return ROUTER_AUTHDIR_REJECTS;
+ r = ROUTER_AUTHDIR_REJECTS;
+ goto fail;
}
/* Check whether this descriptor is semantically identical to the last one
@@ -623,8 +679,8 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
control_event_or_authdir_new_descriptor("DROPPED",
ri->cache_info.signed_descriptor_body,
desclen, *msg);
- routerinfo_free(ri);
- return ROUTER_IS_ALREADY_KNOWN;
+ r = ROUTER_IS_ALREADY_KNOWN;
+ goto fail;
}
/* Do keypinning again ... this time, to add the pin if appropriate */
@@ -647,7 +703,8 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
"its key did not match an older RSA/Ed25519 keypair",
router_describe(ri), source);
*msg = "Looks like your keypair does not match its older value.";
- return ROUTER_AUTHDIR_REJECTS;
+ r = ROUTER_AUTHDIR_REJECTS;
+ goto fail;
}
/* Make a copy of desc, since router_add_to_routerlist might free
@@ -685,6 +742,20 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
tor_free(desc);
tor_free(nickname);
return r;
+ fail:
+ {
+ const char *desc_digest = ri->cache_info.signed_descriptor_digest;
+ download_status_t *dls =
+ router_get_dl_status_by_descriptor_digest(desc_digest);
+ if (dls) {
+ log_info(LD_GENERAL, "Marking router with descriptor %s as rejected, "
+ "and therefore undownloadable",
+ hex_str(desc_digest, DIGEST_LEN));
+ download_status_mark_impossible(dls);
+ }
+ routerinfo_free(ri);
+ }
+ return r;
}
/** As dirserv_add_descriptor, but for an extrainfo_t <b>ei</b>. */
@@ -693,6 +764,7 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
{
routerinfo_t *ri;
int r;
+ was_router_added_t rv;
tor_assert(msg);
*msg = NULL;
@@ -701,8 +773,8 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
ri = router_get_mutable_by_digest(ei->cache_info.identity_digest);
if (!ri) {
*msg = "No corresponding router descriptor for extra-info descriptor";
- extrainfo_free(ei);
- return ROUTER_BAD_EI;
+ rv = ROUTER_BAD_EI;
+ goto fail;
}
/* If it's too big, refuse it now. Otherwise we'll cache it all over the
@@ -714,17 +786,34 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
(int)ei->cache_info.signed_descriptor_len,
MAX_EXTRAINFO_UPLOAD_SIZE);
*msg = "Extrainfo document was too large";
- extrainfo_free(ei);
- return ROUTER_BAD_EI;
+ rv = ROUTER_BAD_EI;
+ goto fail;
}
if ((r = routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
&ri->cache_info, msg))) {
- extrainfo_free(ei);
- return r < 0 ? ROUTER_IS_ALREADY_KNOWN : ROUTER_BAD_EI;
+ if (r<0) {
+ extrainfo_free(ei);
+ return ROUTER_IS_ALREADY_KNOWN;
+ }
+ rv = ROUTER_BAD_EI;
+ goto fail;
}
router_add_extrainfo_to_routerlist(ei, msg, 0, 0);
return ROUTER_ADDED_SUCCESSFULLY;
+ fail:
+ {
+ const char *d = ei->cache_info.signed_descriptor_digest;
+ signed_descriptor_t *sd = router_get_by_extrainfo_digest((char*)d);
+ if (sd) {
+ log_info(LD_GENERAL, "Marking extrainfo with descriptor %s as "
+ "rejected, and therefore undownloadable",
+ hex_str((char*)d,DIGEST_LEN));
+ download_status_mark_impossible(&sd->ei_dl_status);
+ }
+ extrainfo_free(ei);
+ }
+ return rv;
}
/** Remove all descriptors whose nicknames or fingerprints no longer
@@ -823,7 +912,7 @@ running_long_enough_to_decide_unreachable(void)
void
dirserv_set_router_is_running(routerinfo_t *router, time_t now)
{
- /*XXXX024 This function is a mess. Separate out the part that calculates
+ /*XXXX This function is a mess. Separate out the part that calculates
whether it's reachable and the part that tells rephist that the router was
unreachable.
*/
@@ -985,94 +1074,6 @@ router_is_active(const routerinfo_t *ri, const node_t *node, time_t now)
return 1;
}
-/** Generate a new v1 directory and write it into a newly allocated string.
- * Point *<b>dir_out</b> to the allocated string. Sign the
- * directory with <b>private_key</b>. Return 0 on success, -1 on
- * failure. If <b>complete</b> is set, give us all the descriptors;
- * otherwise leave out non-running and non-valid ones.
- */
-int
-dirserv_dump_directory_to_string(char **dir_out,
- crypto_pk_t *private_key)
-{
- /* XXXX 024 Get rid of this function if we can confirm that nobody's
- * fetching these any longer */
- char *cp;
- char *identity_pkey; /* Identity key, DER64-encoded. */
- char *recommended_versions;
- char digest[DIGEST_LEN];
- char published[ISO_TIME_LEN+1];
- char *buf = NULL;
- size_t buf_len;
- size_t identity_pkey_len;
- time_t now = time(NULL);
-
- tor_assert(dir_out);
- *dir_out = NULL;
-
- if (crypto_pk_write_public_key_to_string(private_key,&identity_pkey,
- &identity_pkey_len)<0) {
- log_warn(LD_BUG,"write identity_pkey to string failed!");
- return -1;
- }
-
- recommended_versions =
- format_versions_list(get_options()->RecommendedVersions);
-
- format_iso_time(published, now);
-
- buf_len = 2048+strlen(recommended_versions);
-
- buf = tor_malloc(buf_len);
- /* We'll be comparing against buf_len throughout the rest of the
- function, though strictly speaking we shouldn't be able to exceed
- it. This is C, after all, so we may as well check for buffer
- overruns.*/
-
- tor_snprintf(buf, buf_len,
- "signed-directory\n"
- "published %s\n"
- "recommended-software %s\n"
- "router-status %s\n"
- "dir-signing-key\n%s\n",
- published, recommended_versions, "",
- identity_pkey);
-
- tor_free(recommended_versions);
- tor_free(identity_pkey);
-
- cp = buf + strlen(buf);
- *cp = '\0';
-
- /* These multiple strlcat calls are inefficient, but dwarfed by the RSA
- signature. */
- if (strlcat(buf, "directory-signature ", buf_len) >= buf_len)
- goto truncated;
- if (strlcat(buf, get_options()->Nickname, buf_len) >= buf_len)
- goto truncated;
- if (strlcat(buf, "\n", buf_len) >= buf_len)
- goto truncated;
-
- if (router_get_dir_hash(buf,digest)) {
- log_warn(LD_BUG,"couldn't compute digest");
- tor_free(buf);
- return -1;
- }
- note_crypto_pk_op(SIGN_DIR);
- if (router_append_dirobj_signature(buf,buf_len,digest,DIGEST_LEN,
- private_key)<0) {
- tor_free(buf);
- return -1;
- }
-
- *dir_out = buf;
- return 0;
- truncated:
- log_warn(LD_BUG,"tried to exceed string length.");
- tor_free(buf);
- return -1;
-}
-
/********************************************************************/
/* A set of functions to answer questions about how we'd like to behave
@@ -1090,7 +1091,8 @@ directory_fetches_from_authorities(const or_options_t *options)
return 1;
if (options->BridgeRelay == 1)
return 0;
- if (server_mode(options) && router_pick_published_address(options, &addr)<0)
+ if (server_mode(options) &&
+ router_pick_published_address(options, &addr, 1) < 0)
return 1; /* we don't know our IP address; ask an authority. */
refuseunknown = ! router_my_exit_policy_is_reject_star() &&
should_refuse_unknown_exits(options);
@@ -1329,7 +1331,7 @@ dirserv_thinks_router_is_unreliable(time_t now,
{
if (need_uptime) {
if (!enough_mtbf_info) {
- /* XXX024 Once most authorities are on v3, we should change the rule from
+ /* XXXX We should change the rule from
* "use uptime if we don't have mtbf data" to "don't advertise Stable on
* v3 if we don't have enough mtbf data." Or maybe not, since if we ever
* hit a point where we need to reset a lot of authorities at once,
@@ -1871,6 +1873,7 @@ version_from_platform(const char *platform)
*/
char *
routerstatus_format_entry(const routerstatus_t *rs, const char *version,
+ const char *protocols,
routerstatus_format_type_t format,
const vote_routerstatus_t *vrs)
{
@@ -1934,6 +1937,9 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
if (version && strlen(version) < MAX_V_LINE_LEN - V_LINE_OVERHEAD) {
smartlist_add_asprintf(chunks, "v %s\n", version);
}
+ if (protocols) {
+ smartlist_add_asprintf(chunks, "pr %s\n", protocols);
+ }
if (format != NS_V2) {
const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest);
@@ -2152,8 +2158,8 @@ routers_make_ed_keys_unique(smartlist_t *routers)
const time_t ri2_pub = ri2->cache_info.published_on;
if (ri2_pub < ri_pub ||
(ri2_pub == ri_pub &&
- memcmp(ri->cache_info.signed_descriptor_digest,
- ri2->cache_info.signed_descriptor_digest,DIGEST_LEN)<0)) {
+ fast_memcmp(ri->cache_info.signed_descriptor_digest,
+ ri2->cache_info.signed_descriptor_digest,DIGEST_LEN)<0)) {
digest256map_set(by_ed_key, pk, ri);
ri2->omit_from_vote = 1;
} else {
@@ -2206,7 +2212,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs,
rs->is_valid = node->is_valid;
- if (node->is_fast &&
+ if (node->is_fast && node->is_stable &&
((options->AuthDirGuardBWGuarantee &&
routerbw_kb >= options->AuthDirGuardBWGuarantee/1000) ||
routerbw_kb >= MIN(guard_bandwidth_including_exits_kb,
@@ -2285,13 +2291,17 @@ dirserv_set_routerstatus_testing(routerstatus_t *rs)
/** Routerstatus <b>rs</b> is part of a group of routers that are on
* too narrow an IP-space. Clear out its flags: we don't want people
* using it.
+ *
+ * Leave its BadExit flag alone though, since if we think it's a bad exit,
+ * we want to vote that way in case all the other authorities are voting
+ * Running and Exit.
*/
static void
clear_status_flags_on_sybil(routerstatus_t *rs)
{
rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast =
rs->is_flagged_running = rs->is_named = rs->is_valid =
- rs->is_hs_dir = rs->is_possible_guard = rs->is_bad_exit = 0;
+ rs->is_hs_dir = rs->is_v2_dir = rs->is_possible_guard = 0;
/* FFFF we might want some mechanism to check later on if we
* missed zeroing any flags: it's easy to add a new flag but
* forget to add it to this clause. */
@@ -2365,7 +2375,8 @@ guardfraction_file_parse_guard_line(const char *guard_line,
inputs_tmp = smartlist_get(sl, 0);
if (strlen(inputs_tmp) != HEX_DIGEST_LEN ||
- base16_decode(guard_id, DIGEST_LEN, inputs_tmp, HEX_DIGEST_LEN)) {
+ base16_decode(guard_id, DIGEST_LEN,
+ inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) {
tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp);
goto done;
}
@@ -2669,7 +2680,8 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line)
cp+=strlen("node_id=$");
if (strlen(cp) != HEX_DIGEST_LEN ||
- base16_decode(out->node_id, DIGEST_LEN, cp, HEX_DIGEST_LEN)) {
+ base16_decode(out->node_id, DIGEST_LEN,
+ cp, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
escaped(orig_line));
tor_free(line);
@@ -2910,6 +2922,12 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
rs->is_flagged_running = 0;
vrs->version = version_from_platform(ri->platform);
+ if (ri->protocol_list) {
+ vrs->protocols = tor_strdup(ri->protocol_list);
+ } else {
+ vrs->protocols = tor_strdup(
+ protover_compute_for_old_tor(vrs->version));
+ }
vrs->microdesc = dirvote_format_all_microdesc_vote_lines(ri, now,
microdescriptors);
@@ -2982,6 +3000,31 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
v3_out->client_versions = client_versions;
v3_out->server_versions = server_versions;
+
+ /* These are hardwired, to avoid disaster. */
+ v3_out->recommended_relay_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->recommended_client_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->required_client_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->required_relay_protocols =
+ tor_strdup("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=3-4 LinkAuth=1 Microdesc=1 Relay=1-2");
+
+ /* We are not allowed to vote to require anything we don't have. */
+ tor_assert(protover_all_supported(v3_out->required_relay_protocols, NULL));
+ tor_assert(protover_all_supported(v3_out->required_client_protocols, NULL));
+
+ /* We should not recommend anything we don't have. */
+ tor_assert_nonfatal(protover_all_supported(
+ v3_out->recommended_relay_protocols, NULL));
+ tor_assert_nonfatal(protover_all_supported(
+ v3_out->recommended_client_protocols, NULL));
+
v3_out->package_lines = smartlist_new();
{
config_line_t *cl;
@@ -3340,7 +3383,7 @@ lookup_cached_dir_by_fp(const char *fp)
d = strmap_get(cached_consensuses, "ns");
} else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses &&
(d = strmap_get(cached_consensuses, fp))) {
- /* this here interface is a nasty hack XXXX024 */;
+ /* this here interface is a nasty hack XXXX */;
}
return d;
}
diff --git a/src/or/dirserv.h b/src/or/dirserv.h
index 9a9725ad6f..1e4f27e3d7 100644
--- a/src/or/dirserv.h
+++ b/src/or/dirserv.h
@@ -47,8 +47,6 @@ enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri,
void dirserv_set_router_is_running(routerinfo_t *router, time_t now);
int list_server_status_v1(smartlist_t *routers, char **router_status_out,
int for_controller);
-int dirserv_dump_directory_to_string(char **dir_out,
- crypto_pk_t *private_key);
char *dirserv_get_flag_thresholds_line(void);
void dirserv_compute_bridge_flag_thresholds(void);
@@ -98,7 +96,9 @@ size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs,
size_t dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed);
char *routerstatus_format_entry(
- const routerstatus_t *rs, const char *platform,
+ const routerstatus_t *rs,
+ const char *version,
+ const char *protocols,
routerstatus_format_type_t format,
const vote_routerstatus_t *vrs);
void dirserv_free_all(void);
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index 62f85877fe..738ab35bc1 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -13,12 +13,15 @@
#include "microdesc.h"
#include "networkstatus.h"
#include "policies.h"
+#include "protover.h"
#include "rephist.h"
#include "router.h"
+#include "routerkeys.h"
#include "routerlist.h"
#include "routerparse.h"
#include "entrynodes.h" /* needed for guardfraction methods */
#include "torcert.h"
+#include "shared_random_state.h"
/**
* \file dirvote.c
@@ -59,6 +62,58 @@ static int dirvote_publish_consensus(void);
* Voting
* =====*/
+/* If <b>opt_value</b> is non-NULL, return "keyword opt_value\n" in a new
+ * string. Otherwise return a new empty string. */
+static char *
+format_line_if_present(const char *keyword, const char *opt_value)
+{
+ if (opt_value) {
+ char *result = NULL;
+ tor_asprintf(&result, "%s %s\n", keyword, opt_value);
+ return result;
+ } else {
+ return tor_strdup("");
+ }
+}
+
+/** Format the recommended/required-relay-client protocols lines for a vote in
+ * a newly allocated string, and return that string. */
+static char *
+format_protocols_lines_for_vote(const networkstatus_t *v3_ns)
+{
+ char *recommended_relay_protocols_line = NULL;
+ char *recommended_client_protocols_line = NULL;
+ char *required_relay_protocols_line = NULL;
+ char *required_client_protocols_line = NULL;
+
+ recommended_relay_protocols_line =
+ format_line_if_present("recommended-relay-protocols",
+ v3_ns->recommended_relay_protocols);
+ recommended_client_protocols_line =
+ format_line_if_present("recommended-client-protocols",
+ v3_ns->recommended_client_protocols);
+ required_relay_protocols_line =
+ format_line_if_present("required-relay-protocols",
+ v3_ns->required_relay_protocols);
+ required_client_protocols_line =
+ format_line_if_present("required-client-protocols",
+ v3_ns->required_client_protocols);
+
+ char *result = NULL;
+ tor_asprintf(&result, "%s%s%s%s",
+ recommended_relay_protocols_line,
+ recommended_client_protocols_line,
+ required_relay_protocols_line,
+ required_client_protocols_line);
+
+ tor_free(recommended_relay_protocols_line);
+ tor_free(recommended_client_protocols_line);
+ tor_free(required_relay_protocols_line);
+ tor_free(required_client_protocols_line);
+
+ return result;
+}
+
/** Return a new string containing the string representation of the vote in
* <b>v3_ns</b>, signed with our v3 signing key <b>private_signing_key</b>.
* For v3 authorities. */
@@ -67,12 +122,13 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
networkstatus_t *v3_ns)
{
smartlist_t *chunks = smartlist_new();
- const char *client_versions = NULL, *server_versions = NULL;
char *packages = NULL;
char fingerprint[FINGERPRINT_LEN+1];
char digest[DIGEST_LEN];
uint32_t addr;
+ char *protocols_lines = NULL;
char *client_versions_line = NULL, *server_versions_line = NULL;
+ char *shared_random_vote_str = NULL;
networkstatus_voter_info_t *voter;
char *status = NULL;
@@ -85,27 +141,19 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
base16_encode(fingerprint, sizeof(fingerprint),
v3_ns->cert->cache_info.identity_digest, DIGEST_LEN);
- client_versions = v3_ns->client_versions;
- server_versions = v3_ns->server_versions;
- if (client_versions) {
- tor_asprintf(&client_versions_line, "client-versions %s\n",
- client_versions);
- } else {
- client_versions_line = tor_strdup("");
- }
- if (server_versions) {
- tor_asprintf(&server_versions_line, "server-versions %s\n",
- server_versions);
- } else {
- server_versions_line = tor_strdup("");
- }
+ client_versions_line = format_line_if_present("client-versions",
+ v3_ns->client_versions);
+ server_versions_line = format_line_if_present("server-versions",
+ v3_ns->server_versions);
+ protocols_lines = format_protocols_lines_for_vote(v3_ns);
if (v3_ns->package_lines) {
smartlist_t *tmp = smartlist_new();
SMARTLIST_FOREACH(v3_ns->package_lines, const char *, p,
if (validate_recommended_package_line(p))
smartlist_add_asprintf(tmp, "package %s\n", p));
+ smartlist_sort_strings(tmp);
packages = smartlist_join_strings(tmp, "", 0, NULL);
SMARTLIST_FOREACH(tmp, char *, cp, tor_free(cp));
smartlist_free(tmp);
@@ -113,6 +161,9 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
packages = tor_strdup("");
}
+ /* Get shared random commitments/reveals line(s). */
+ shared_random_vote_str = sr_get_string_for_vote();
+
{
char published[ISO_TIME_LEN+1];
char va[ISO_TIME_LEN+1];
@@ -147,30 +198,36 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
"valid-until %s\n"
"voting-delay %d %d\n"
"%s%s" /* versions */
+ "%s" /* protocols */
"%s" /* packages */
"known-flags %s\n"
"flag-thresholds %s\n"
"params %s\n"
"dir-source %s %s %s %s %d %d\n"
- "contact %s\n",
+ "contact %s\n"
+ "%s", /* shared randomness information */
v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion",
methods,
published, va, fu, vu,
v3_ns->vote_seconds, v3_ns->dist_seconds,
client_versions_line,
server_versions_line,
+ protocols_lines,
packages,
flags,
flag_thresholds,
params,
voter->nickname, fingerprint, voter->address,
fmt_addr32(addr), voter->dir_port, voter->or_port,
- voter->contact);
+ voter->contact,
+ shared_random_vote_str ?
+ shared_random_vote_str : "");
tor_free(params);
tor_free(flags);
tor_free(flag_thresholds);
tor_free(methods);
+ tor_free(shared_random_vote_str);
if (!tor_digest_is_zero(voter->legacy_id_digest)) {
char fpbuf[HEX_DIGEST_LEN+1];
@@ -187,7 +244,8 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
char *rsf;
vote_microdesc_hash_t *h;
rsf = routerstatus_format_entry(&vrs->status,
- vrs->version, NS_V3_VOTE, vrs);
+ vrs->version, vrs->protocols,
+ NS_V3_VOTE, vrs);
if (rsf)
smartlist_add(chunks, rsf);
@@ -247,6 +305,7 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
done:
tor_free(client_versions_line);
tor_free(server_versions_line);
+ tor_free(protocols_lines);
tor_free(packages);
SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
@@ -362,16 +421,30 @@ compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b)
b->status.descriptor_digest,
DIGEST_LEN)))
return r;
- if ((r = (int)(b->status.published_on - a->status.published_on)))
- return r;
+ /* If we actually reached this point, then the identities and
+ * the descriptor digests matched, so somebody is making SHA1 collisions.
+ */
+#define CMP_FIELD(utype, itype, field) do { \
+ utype aval = (utype) (itype) a->status.field; \
+ utype bval = (utype) (itype) b->status.field; \
+ utype u = bval - aval; \
+ itype r2 = (itype) u; \
+ if (r2 < 0) { \
+ return -1; \
+ } else if (r2 > 0) { \
+ return 1; \
+ } \
+ } while (0)
+
+ CMP_FIELD(uint64_t, int64_t, published_on);
+
if ((r = strcmp(b->status.nickname, a->status.nickname)))
return r;
- if ((r = (((int)b->status.addr) - ((int)a->status.addr))))
- return r;
- if ((r = (((int)b->status.or_port) - ((int)a->status.or_port))))
- return r;
- if ((r = (((int)b->status.dir_port) - ((int)a->status.dir_port))))
- return r;
+
+ CMP_FIELD(unsigned, int, addr);
+ CMP_FIELD(unsigned, int, or_port);
+ CMP_FIELD(unsigned, int, dir_port);
+
return 0;
}
@@ -607,15 +680,47 @@ compute_consensus_versions_list(smartlist_t *lst, int n_versioning)
return result;
}
+/** Given a list of K=V values, return the int32_t value corresponding to
+ * KEYWORD=, or default_val if no such value exists, or if the value is
+ * corrupt.
+ */
+STATIC int32_t
+dirvote_get_intermediate_param_value(const smartlist_t *param_list,
+ const char *keyword,
+ int32_t default_val)
+{
+ unsigned int n_found = 0;
+ int32_t value = default_val;
+
+ SMARTLIST_FOREACH_BEGIN(param_list, const char *, k_v_pair) {
+ if (!strcmpstart(k_v_pair, keyword) && k_v_pair[strlen(keyword)] == '=') {
+ const char *integer_str = &k_v_pair[strlen(keyword)+1];
+ int ok;
+ value = (int32_t)
+ tor_parse_long(integer_str, 10, INT32_MIN, INT32_MAX, &ok, NULL);
+ if (BUG(! ok))
+ return default_val;
+ ++n_found;
+ }
+ } SMARTLIST_FOREACH_END(k_v_pair);
+
+ if (n_found == 1)
+ return value;
+ else if (BUG(n_found > 1))
+ return default_val;
+ else
+ return default_val;
+}
+
/** Minimum number of directory authorities voting for a parameter to
* include it in the consensus, if consensus method 12 or later is to be
* used. See proposal 178 for details. */
#define MIN_VOTES_FOR_PARAM 3
-/** Helper: given a list of valid networkstatus_t, return a new string
+/** Helper: given a list of valid networkstatus_t, return a new smartlist
* containing the contents of the consensus network parameter set.
*/
-STATIC char *
+STATIC smartlist_t *
dirvote_compute_params(smartlist_t *votes, int method, int total_authorities)
{
int i;
@@ -624,7 +729,6 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities)
int cur_param_len;
const char *cur_param;
const char *eq;
- char *result;
const int n_votes = smartlist_len(votes);
smartlist_t *output;
@@ -646,8 +750,7 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities)
if (smartlist_len(param_list) == 0) {
tor_free(vals);
- smartlist_free(param_list);
- return NULL;
+ return param_list;
}
smartlist_sort_strings(param_list);
@@ -695,12 +798,9 @@ dirvote_compute_params(smartlist_t *votes, int method, int total_authorities)
}
} SMARTLIST_FOREACH_END(param);
- result = smartlist_join_strings(output, " ", 0, NULL);
- SMARTLIST_FOREACH(output, char *, cp, tor_free(cp));
- smartlist_free(output);
smartlist_free(param_list);
tor_free(vals);
- return result;
+ return output;
}
#define RANGE_CHECK(a,b,c,d,e,f,g,mx) \
@@ -815,7 +915,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
}
/*
- * Computed from cases in 3.4.3 of dir-spec.txt
+ * Computed from cases in 3.8.3 of dir-spec.txt
*
* 1. Neither are scarce
* 2. Both Guard and Exit are scarce
@@ -1114,6 +1214,72 @@ update_total_bandwidth_weights(const routerstatus_t *rs,
}
}
+/** Considering the different recommended/required protocols sets as a
+ * 4-element array, return the element from <b>vote</b> for that protocol
+ * set.
+ */
+static const char *
+get_nth_protocol_set_vote(int n, const networkstatus_t *vote)
+{
+ switch (n) {
+ case 0: return vote->recommended_client_protocols;
+ case 1: return vote->recommended_relay_protocols;
+ case 2: return vote->required_client_protocols;
+ case 3: return vote->required_relay_protocols;
+ default:
+ tor_assert_unreached();
+ return NULL;
+ }
+}
+
+/** Considering the different recommended/required protocols sets as a
+ * 4-element array, return a newly allocated string for the consensus value
+ * for the n'th set.
+ */
+static char *
+compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes)
+{
+ const char *keyword;
+ smartlist_t *proto_votes = smartlist_new();
+ int threshold;
+ switch (n) {
+ case 0:
+ keyword = "recommended-client-protocols";
+ threshold = CEIL_DIV(n_voters, 2);
+ break;
+ case 1:
+ keyword = "recommended-relay-protocols";
+ threshold = CEIL_DIV(n_voters, 2);
+ break;
+ case 2:
+ keyword = "required-client-protocols";
+ threshold = CEIL_DIV(n_voters * 2, 3);
+ break;
+ case 3:
+ keyword = "required-relay-protocols";
+ threshold = CEIL_DIV(n_voters * 2, 3);
+ break;
+ default:
+ tor_assert_unreached();
+ return NULL;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(votes, const networkstatus_t *, ns) {
+ const char *v = get_nth_protocol_set_vote(n, ns);
+ if (v)
+ smartlist_add(proto_votes, (void*)v);
+ } SMARTLIST_FOREACH_END(ns);
+
+ char *protocols = protover_compute_vote(proto_votes, threshold);
+ smartlist_free(proto_votes);
+
+ char *result = NULL;
+ tor_asprintf(&result, "%s %s\n", keyword, protocols);
+ tor_free(protocols);
+
+ return result;
+}
+
/** Given a list of vote networkstatus_t in <b>votes</b>, our public
* authority <b>identity_key</b>, our private authority <b>signing_key</b>,
* and the number of <b>total_authorities</b> that we believe exist in our
@@ -1147,6 +1313,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
char *packages = NULL;
int added_weights = 0;
dircollator_t *collator = NULL;
+ smartlist_t *param_list = NULL;
+
tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC);
tor_assert(total_authorities >= smartlist_len(votes));
tor_assert(total_authorities > 0);
@@ -1291,14 +1459,42 @@ networkstatus_compute_consensus(smartlist_t *votes,
tor_free(flaglist);
}
- params = dirvote_compute_params(votes, consensus_method,
- total_authorities);
- if (params) {
+ if (consensus_method >= MIN_METHOD_FOR_RECOMMENDED_PROTOCOLS) {
+ int num_dirauth = get_n_authorities(V3_DIRINFO);
+ int idx;
+ for (idx = 0; idx < 4; ++idx) {
+ char *proto_line = compute_nth_protocol_set(idx, num_dirauth, votes);
+ if (BUG(!proto_line))
+ continue;
+ smartlist_add(chunks, proto_line);
+ }
+ }
+
+ param_list = dirvote_compute_params(votes, consensus_method,
+ total_authorities);
+ if (smartlist_len(param_list)) {
+ params = smartlist_join_strings(param_list, " ", 0, NULL);
smartlist_add(chunks, tor_strdup("params "));
smartlist_add(chunks, params);
smartlist_add(chunks, tor_strdup("\n"));
}
+ if (consensus_method >= MIN_METHOD_FOR_SHARED_RANDOM) {
+ int num_dirauth = get_n_authorities(V3_DIRINFO);
+ /* Default value of this is 2/3 of the total number of authorities. For
+ * instance, if we have 9 dirauth, the default value is 6. The following
+ * calculation will round it down. */
+ int32_t num_srv_agreements =
+ dirvote_get_intermediate_param_value(param_list,
+ "AuthDirNumSRVAgreements",
+ (num_dirauth * 2) / 3);
+ /* Add the shared random value. */
+ char *srv_lines = sr_get_string_for_consensus(votes, num_srv_agreements);
+ if (srv_lines != NULL) {
+ smartlist_add(chunks, srv_lines);
+ }
+ }
+
/* Sort the votes. */
smartlist_sort(votes, compare_votes_by_authority_id_);
/* Add the authority sections. */
@@ -1350,7 +1546,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
if (consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW) {
char *max_unmeasured_param = NULL;
- /* XXXX Extract this code into a common function */
+ /* XXXX Extract this code into a common function. Or don't! see #19011 */
if (params) {
if (strcmpstart(params, "maxunmeasuredbw=") == 0)
max_unmeasured_param = params;
@@ -1374,7 +1570,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
/* Add the actual router entries. */
{
- int *index; /* index[j] is the current index into votes[j]. */
int *size; /* size[j] is the number of routerstatuses in votes[j]. */
int *flag_counts; /* The number of voters that list flag[j] for the
* currently considered router. */
@@ -1382,6 +1577,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_t *matching_descs = smartlist_new();
smartlist_t *chosen_flags = smartlist_new();
smartlist_t *versions = smartlist_new();
+ smartlist_t *protocols = smartlist_new();
smartlist_t *exitsummaries = smartlist_new();
uint32_t *bandwidths_kb = tor_calloc(smartlist_len(votes),
sizeof(uint32_t));
@@ -1409,7 +1605,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
memset(conflict, 0, sizeof(conflict));
memset(unknown, 0xff, sizeof(conflict));
- index = tor_calloc(smartlist_len(votes), sizeof(int));
size = tor_calloc(smartlist_len(votes), sizeof(int));
n_voter_flags = tor_calloc(smartlist_len(votes), sizeof(int));
n_flag_voters = tor_calloc(smartlist_len(flags), sizeof(int));
@@ -1525,9 +1720,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
routerstatus_t rs_out;
const char *current_rsa_id = NULL;
const char *chosen_version;
+ const char *chosen_protocol_list;
const char *chosen_name = NULL;
int exitsummary_disagreement = 0;
- int is_named = 0, is_unnamed = 0, is_running = 0;
+ int is_named = 0, is_unnamed = 0, is_running = 0, is_valid = 0;
int is_guard = 0, is_exit = 0, is_bad_exit = 0;
int naming_conflict = 0;
int n_listing = 0;
@@ -1538,6 +1734,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_clear(matching_descs);
smartlist_clear(chosen_flags);
smartlist_clear(versions);
+ smartlist_clear(protocols);
num_bandwidths = 0;
num_mbws = 0;
num_guardfraction_inputs = 0;
@@ -1557,6 +1754,12 @@ networkstatus_compute_consensus(smartlist_t *votes,
if (rs->version && rs->version[0])
smartlist_add(versions, rs->version);
+ if (rs->protocols) {
+ /* We include this one even if it's empty: voting for an
+ * empty protocol list actually is meaningful. */
+ smartlist_add(protocols, rs->protocols);
+ }
+
/* Tally up all the flags. */
for (int flag = 0; flag < n_voter_flags[voter_idx]; ++flag) {
if (rs->flags & (U64_LITERAL(1) << flag))
@@ -1678,6 +1881,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
is_running = 1;
else if (!strcmp(fl, "BadExit"))
is_bad_exit = 1;
+ else if (!strcmp(fl, "Valid"))
+ is_valid = 1;
}
}
} SMARTLIST_FOREACH_END(fl);
@@ -1687,6 +1892,12 @@ networkstatus_compute_consensus(smartlist_t *votes,
if (!is_running)
continue;
+ /* Starting with consensus method 24, we don't list servers
+ * that are not valid in a consensus. See Proposal 272 */
+ if (!is_valid &&
+ consensus_method >= MIN_METHOD_FOR_EXCLUDING_INVALID_NODES)
+ continue;
+
/* Pick the version. */
if (smartlist_len(versions)) {
sort_version_list(versions, 0);
@@ -1695,6 +1906,14 @@ networkstatus_compute_consensus(smartlist_t *votes,
chosen_version = NULL;
}
+ /* Pick the protocol list */
+ if (smartlist_len(protocols)) {
+ smartlist_sort_strings(protocols);
+ chosen_protocol_list = get_most_frequent_member(protocols);
+ } else {
+ chosen_protocol_list = NULL;
+ }
+
/* If it's a guard and we have enough guardfraction votes,
calculate its consensus guardfraction value. */
if (is_guard && num_guardfraction_inputs > 2 &&
@@ -1828,7 +2047,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
char *buf;
/* Okay!! Now we can write the descriptor... */
/* First line goes into "buf". */
- buf = routerstatus_format_entry(&rs_out, NULL, rs_format, NULL);
+ buf = routerstatus_format_entry(&rs_out, NULL, NULL, rs_format, NULL);
if (buf)
smartlist_add(chunks, buf);
}
@@ -1848,6 +2067,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_add(chunks, tor_strdup(chosen_version));
}
smartlist_add(chunks, tor_strdup("\n"));
+ if (chosen_protocol_list &&
+ consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) {
+ smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list);
+ }
/* Now the weight line. */
if (rs_out.has_bandwidth) {
char *guardfraction_str = NULL;
@@ -1875,7 +2098,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
/* And the loop is over and we move on to the next router */
}
- tor_free(index);
tor_free(size);
tor_free(n_voter_flags);
tor_free(n_flag_voters);
@@ -1889,6 +2111,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(matching_descs);
smartlist_free(chosen_flags);
smartlist_free(versions);
+ smartlist_free(protocols);
smartlist_free(exitsummaries);
tor_free(bandwidths_kb);
tor_free(measured_bws_kb);
@@ -1905,7 +2128,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
// Parse params, extract BW_WEIGHT_SCALE if present
// DO NOT use consensus_param_bw_weight_scale() in this code!
// The consensus is not formed yet!
- /* XXXX Extract this code into a common function */
+ /* XXXX Extract this code into a common function. Or not: #19011. */
if (params) {
if (strcmpstart(params, "bwweightscale=") == 0)
bw_weight_param = params;
@@ -2025,6 +2248,8 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(flags);
SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
smartlist_free(chunks);
+ SMARTLIST_FOREACH(param_list, char *, cp, tor_free(cp));
+ smartlist_free(param_list);
return result;
}
@@ -2168,7 +2393,7 @@ networkstatus_add_detached_signatures(networkstatus_t *target,
}
}
if (!n_matches) {
- *msg_out = "No regognized digests for given consensus flavor";
+ *msg_out = "No recognized digests for given consensus flavor";
}
}
@@ -2363,15 +2588,15 @@ networkstatus_get_detached_signatures(smartlist_t *consensuses)
/* Now get all the sigs for non-FLAV_NS consensuses */
SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
- char *sigs;
+ char *sigs_on_this_consensus;
if (ns->flavor == FLAV_NS)
continue;
- sigs = networkstatus_format_signatures(ns, 1);
- if (!sigs) {
+ sigs_on_this_consensus = networkstatus_format_signatures(ns, 1);
+ if (!sigs_on_this_consensus) {
log_warn(LD_DIR, "Couldn't format signatures");
goto err;
}
- smartlist_add(elements, sigs);
+ smartlist_add(elements, sigs_on_this_consensus);
} SMARTLIST_FOREACH_END(ns);
/* Now add the FLAV_NS consensus signatrures. */
@@ -2510,50 +2735,60 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset)
return next;
}
-/** Scheduling information for a voting interval. */
-static struct {
- /** When do we generate and distribute our vote for this interval? */
- time_t voting_starts;
- /** When do we send an HTTP request for any votes that we haven't
- * been posted yet?*/
- time_t fetch_missing_votes;
- /** When do we give up on getting more votes and generate a consensus? */
- time_t voting_ends;
- /** When do we send an HTTP request for any signatures we're expecting to
- * see on the consensus? */
- time_t fetch_missing_signatures;
- /** When do we publish the consensus? */
- time_t interval_starts;
-
- /* True iff we have generated and distributed our vote. */
- int have_voted;
- /* True iff we've requested missing votes. */
- int have_fetched_missing_votes;
- /* True iff we have built a consensus and sent the signatures around. */
- int have_built_consensus;
- /* True iff we've fetched missing signatures. */
- int have_fetched_missing_signatures;
- /* True iff we have published our consensus. */
- int have_published_consensus;
-} voting_schedule = {0,0,0,0,0,0,0,0,0,0};
+/* 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 *
+get_voting_schedule(const or_options_t *options, time_t now, int severity)
+{
int interval, vote_delay, dist_delay;
time_t start;
time_t end;
networkstatus_t *consensus;
+ voting_schedule_t *new_voting_schedule;
- if (!authdir_mode_v3(options))
- return;
+ new_voting_schedule = tor_malloc_zero(sizeof(voting_schedule_t));
consensus = networkstatus_get_live_consensus(now);
- memset(&voting_schedule, 0, sizeof(voting_schedule));
-
if (consensus) {
interval = (int)( consensus->fresh_until - consensus->valid_after );
vote_delay = consensus->vote_seconds;
@@ -2569,7 +2804,7 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now)
if (vote_delay + dist_delay > interval/2)
vote_delay = dist_delay = interval / 4;
- start = voting_schedule.interval_starts =
+ start = new_voting_schedule->interval_starts =
dirvote_get_start_of_next_interval(now,interval,
options->TestingV3AuthVotingStartOffset);
end = dirvote_get_start_of_next_interval(start+1, interval,
@@ -2577,18 +2812,31 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now)
tor_assert(end > start);
- voting_schedule.fetch_missing_signatures = start - (dist_delay/2);
- voting_schedule.voting_ends = start - dist_delay;
- voting_schedule.fetch_missing_votes = start - dist_delay - (vote_delay/2);
- voting_schedule.voting_starts = start - dist_delay - vote_delay;
+ new_voting_schedule->fetch_missing_signatures = start - (dist_delay/2);
+ new_voting_schedule->voting_ends = start - dist_delay;
+ new_voting_schedule->fetch_missing_votes =
+ start - dist_delay - (vote_delay/2);
+ new_voting_schedule->voting_starts = start - dist_delay - vote_delay;
{
char tbuf[ISO_TIME_LEN+1];
- format_iso_time(tbuf, voting_schedule.interval_starts);
- log_notice(LD_DIR,"Choosing expected valid-after time as %s: "
- "consensus_set=%d, interval=%d",
- tbuf, consensus?1:0, interval);
+ format_iso_time(tbuf, new_voting_schedule->interval_starts);
+ tor_log(severity, LD_DIR,"Choosing expected valid-after time as %s: "
+ "consensus_set=%d, interval=%d",
+ tbuf, consensus?1:0, interval);
}
+
+ return new_voting_schedule;
+}
+
+/** Frees a voting_schedule_t. This should be used instead of the generic
+ * tor_free. */
+void
+voting_schedule_free(voting_schedule_t *voting_schedule_to_free)
+{
+ if (!voting_schedule_to_free)
+ return;
+ tor_free(voting_schedule_to_free);
}
/** Entry point: Take whatever voting actions are pending as of <b>now</b>. */
@@ -2637,6 +2885,9 @@ dirvote_act(const or_options_t *options, time_t now)
dirvote_publish_consensus();
dirvote_clear_votes(0);
voting_schedule.have_published_consensus = 1;
+ /* Update our shared random state with the consensus just published. */
+ sr_act_post_consensus(
+ networkstatus_get_latest_consensus_by_flavor(FLAV_NS));
/* XXXX We will want to try again later if we haven't got enough
* signatures yet. Implement this if it turns out to ever happen. */
dirvote_recalculate_timing(options, now);
@@ -2916,7 +3167,8 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
/* Hey, it's a new cert! */
trusted_dirs_load_certs_from_string(
vote->cert->cache_info.signed_descriptor_body,
- TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/);
+ TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/,
+ NULL);
if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest,
vote->cert->signing_key_digest)) {
log_warn(LD_BUG, "We added a cert, but still couldn't find it.");
@@ -2975,6 +3227,10 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
}
} SMARTLIST_FOREACH_END(v);
+ /* This a valid vote, update our shared random state. */
+ sr_handle_received_commits(vote->sr_info.commits,
+ vote->cert->identity_key);
+
pending_vote = tor_malloc_zero(sizeof(pending_vote_t));
pending_vote->vote_body = new_cached_dir(tor_strndup(vote_body,
end_of_vote-vote_body),
@@ -3019,6 +3275,30 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
return any_failed ? NULL : pending_vote;
}
+/* Write the votes in <b>pending_vote_list</b> to disk. */
+static void
+write_v3_votes_to_disk(const smartlist_t *pending_votes)
+{
+ smartlist_t *votestrings = smartlist_new();
+ char *votefile = NULL;
+
+ SMARTLIST_FOREACH(pending_votes, pending_vote_t *, v,
+ {
+ sized_chunk_t *c = tor_malloc(sizeof(sized_chunk_t));
+ c->bytes = v->vote_body->dir;
+ c->len = v->vote_body->dir_len;
+ smartlist_add(votestrings, c); /* collect strings to write to disk */
+ });
+
+ votefile = get_datadir_fname("v3-status-votes");
+ write_chunks_to_file(votefile, votestrings, 0, 0);
+ log_debug(LD_DIR, "Wrote votes to disk (%s)!", votefile);
+
+ tor_free(votefile);
+ SMARTLIST_FOREACH(votestrings, sized_chunk_t *, c, tor_free(c));
+ smartlist_free(votestrings);
+}
+
/** Try to compute a v3 networkstatus consensus from the currently pending
* votes. Return 0 on success, -1 on failure. Store the consensus in
* pending_consensus: it won't be ready to be published until we have
@@ -3028,8 +3308,8 @@ dirvote_compute_consensuses(void)
{
/* Have we got enough votes to try? */
int n_votes, n_voters, n_vote_running = 0;
- smartlist_t *votes = NULL, *votestrings = NULL;
- char *consensus_body = NULL, *signatures = NULL, *votefile;
+ smartlist_t *votes = NULL;
+ char *consensus_body = NULL, *signatures = NULL;
networkstatus_t *consensus = NULL;
authority_cert_t *my_cert;
pending_consensus_t pending[N_CONSENSUS_FLAVORS];
@@ -3040,6 +3320,17 @@ dirvote_compute_consensuses(void)
if (!pending_vote_list)
pending_vote_list = smartlist_new();
+ /* Write votes to disk */
+ write_v3_votes_to_disk(pending_vote_list);
+
+ /* Setup votes smartlist */
+ votes = smartlist_new();
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v,
+ {
+ smartlist_add(votes, v->vote); /* collect votes to compute consensus */
+ });
+
+ /* See if consensus managed to achieve majority */
n_voters = get_n_authorities(V3_DIRINFO);
n_votes = smartlist_len(pending_vote_list);
if (n_votes <= n_voters/2) {
@@ -3066,24 +3357,6 @@ dirvote_compute_consensuses(void)
goto err;
}
- votes = smartlist_new();
- votestrings = smartlist_new();
- SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v,
- {
- sized_chunk_t *c = tor_malloc(sizeof(sized_chunk_t));
- c->bytes = v->vote_body->dir;
- c->len = v->vote_body->dir_len;
- smartlist_add(votestrings, c); /* collect strings to write to disk */
-
- smartlist_add(votes, v->vote); /* collect votes to compute consensus */
- });
-
- votefile = get_datadir_fname("v3-status-votes");
- write_chunks_to_file(votefile, votestrings, 0, 0);
- tor_free(votefile);
- SMARTLIST_FOREACH(votestrings, sized_chunk_t *, c, tor_free(c));
- smartlist_free(votestrings);
-
{
char legacy_dbuf[DIGEST_LEN];
crypto_pk_t *legacy_sign=NULL;
@@ -3373,7 +3646,7 @@ dirvote_publish_consensus(void)
continue;
}
- if (networkstatus_set_current_consensus(pending->body, name, 0))
+ if (networkstatus_set_current_consensus(pending->body, name, 0, NULL))
log_warn(LD_DIR, "Error publishing %s consensus", name);
else
log_notice(LD_DIR, "Published %s consensus", name);
@@ -3515,7 +3788,7 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method)
if (consensus_method >= MIN_METHOD_FOR_P6_LINES &&
ri->ipv6_exit_policy) {
- /* XXXX024 This doesn't match proposal 208, which says these should
+ /* XXXX+++ This doesn't match proposal 208, which says these should
* be taken unchanged from the routerinfo. That's bogosity, IMO:
* the proposal should have said to do this instead.*/
char *p6 = write_short_policy(ri->ipv6_exit_policy);
diff --git a/src/or/dirvote.h b/src/or/dirvote.h
index 0b1d284060..efd233ef5f 100644
--- a/src/or/dirvote.h
+++ b/src/or/dirvote.h
@@ -55,7 +55,7 @@
#define MIN_SUPPORTED_CONSENSUS_METHOD 13
/** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 22
+#define MAX_SUPPORTED_CONSENSUS_METHOD 25
/** Lowest consensus method where microdesc consensuses omit any entry
* with no microdesc. */
@@ -90,10 +90,27 @@
* ed25519 identities in microdescriptors. (Broken; see
* consensus_method_is_supported() for more info.) */
#define MIN_METHOD_FOR_ED25519_ID_IN_MD 21
+
/** Lowest consensus method where authorities vote on ed25519 ids and ensure
* ed25519 id consistency. */
#define MIN_METHOD_FOR_ED25519_ID_VOTING 22
+/** Lowest consensus method where authorities may include a shared random
+ * value(s). */
+#define MIN_METHOD_FOR_SHARED_RANDOM 23
+
+/** Lowest consensus method where authorities drop all nodes that don't get
+ * the Valid flag. */
+#define MIN_METHOD_FOR_EXCLUDING_INVALID_NODES 24
+
+/** Lowest consensus method where authorities vote on required/recommended
+ * protocols. */
+#define MIN_METHOD_FOR_RECOMMENDED_PROTOCOLS 25
+
+/** Lowest consensus method where authorities add protocols to routerstatus
+ * entries. */
+#define MIN_METHOD_FOR_RS_PROTOCOLS 25
+
/** Default bandwidth to clip unmeasured bandwidths to using method >=
* MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not
* get confused with the above macros.) */
@@ -121,12 +138,46 @@ void ns_detached_signatures_free(ns_detached_signatures_t *s);
authority_cert_t *authority_cert_dup(authority_cert_t *cert);
/* vote scheduling */
+
+/** Scheduling information for a voting interval. */
+typedef struct {
+ /** When do we generate and distribute our vote for this interval? */
+ time_t voting_starts;
+ /** When do we send an HTTP request for any votes that we haven't
+ * been posted yet?*/
+ time_t fetch_missing_votes;
+ /** When do we give up on getting more votes and generate a consensus? */
+ time_t voting_ends;
+ /** When do we send an HTTP request for any signatures we're expecting to
+ * see on the consensus? */
+ time_t fetch_missing_signatures;
+ /** When do we publish the consensus? */
+ time_t interval_starts;
+
+ /* True iff we have generated and distributed our vote. */
+ int have_voted;
+ /* True iff we've requested missing votes. */
+ int have_fetched_missing_votes;
+ /* True iff we have built a consensus and sent the signatures around. */
+ int have_built_consensus;
+ /* True iff we've fetched missing signatures. */
+ 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);
+
void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out);
time_t dirvote_get_start_of_next_interval(time_t now,
int interval,
int offset);
void dirvote_recalculate_timing(const or_options_t *options, time_t now);
void dirvote_act(const or_options_t *options, time_t now);
+time_t get_next_valid_after_time(time_t now);
/* invoked on timers and by outside triggers. */
struct pending_vote_t * dirvote_add_vote(const char *vote_body,
@@ -173,9 +224,13 @@ document_signature_t *voter_get_sig_by_algorithm(
digest_algorithm_t alg);
#ifdef DIRVOTE_PRIVATE
+STATIC int32_t dirvote_get_intermediate_param_value(
+ const smartlist_t *param_list,
+ const char *keyword,
+ int32_t default_val);
STATIC char *format_networkstatus_vote(crypto_pk_t *private_key,
networkstatus_t *v3_ns);
-STATIC char *dirvote_compute_params(smartlist_t *votes, int method,
+STATIC smartlist_t *dirvote_compute_params(smartlist_t *votes, int method,
int total_authorities);
STATIC char *compute_consensus_package_lines(smartlist_t *votes);
STATIC char *make_consensus_method_list(int low, int high, const char *sep);
diff --git a/src/or/dns.c b/src/or/dns.c
index c7adfbc971..c1e3c3256e 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -9,6 +9,42 @@
* This is implemented as a wrapper around Adam Langley's eventdns.c code.
* (We can't just use gethostbyname() and friends because we really need to
* be nonblocking.)
+ *
+ * There are three main cases when a Tor relay uses dns.c to launch a DNS
+ * request:
+ * <ol>
+ * <li>To check whether the DNS server is working more or less correctly.
+ * This happens via dns_launch_correctness_checks(). The answer is
+ * reported in the return value from later calls to
+ * dns_seems_to_be_broken().
+ * <li>When a client has asked the relay, in a RELAY_BEGIN cell, to connect
+ * to a given server by hostname. This happens via dns_resolve().
+ * <li>When a client has asked the rela, in a RELAY_RESOLVE cell, to look
+ * up a given server's IP address(es) by hostname. This also happens via
+ * dns_resolve().
+ * </ol>
+ *
+ * Each of these gets handled a little differently.
+ *
+ * To check for correctness, we look up some hostname we expect to exist and
+ * have real entries, some hostnames which we expect to definitely not exist,
+ * and some hostnames that we expect to probably not exist. If too many of
+ * the hostnames that shouldn't exist do exist, that's a DNS hijacking
+ * attempt. If too many of the hostnames that should exist have the same
+ * addresses as the ones that shouldn't exist, that's a very bad DNS hijacking
+ * attempt, or a very naughty captive portal. And if the hostnames that
+ * should exist simply don't exist, we probably have a broken nameserver.
+ *
+ * To handle client requests, we first check our cache for answers. If there
+ * isn't something up-to-date, we've got to launch A or AAAA requests as
+ * appropriate. How we handle responses to those in particular is a bit
+ * complex; see dns_lookup() and set_exitconn_info_from_resolve().
+ *
+ * When a lookup is finally complete, the inform_pending_connections()
+ * function will tell all of the streams that have been waiting for the
+ * resolve, by calling connection_exit_connect() if the client sent a
+ * RELAY_BEGIN cell, and by calling send_resolved_cell() or
+ * send_hostname_cell() if the client sent a RELAY_RESOLVE cell.
**/
#define DNS_PRIVATE
@@ -27,61 +63,8 @@
#include "router.h"
#include "ht.h"
#include "sandbox.h"
-#ifdef HAVE_EVENT2_DNS_H
#include <event2/event.h>
#include <event2/dns.h>
-#else
-#include <event.h>
-#include "eventdns.h"
-#ifndef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
-#define HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
-#endif
-#endif
-
-#ifndef HAVE_EVENT2_DNS_H
-struct evdns_base;
-struct evdns_request;
-#define evdns_base_new(x,y) tor_malloc(1)
-#define evdns_base_clear_nameservers_and_suspend(base) \
- evdns_clear_nameservers_and_suspend()
-#define evdns_base_search_clear(base) evdns_search_clear()
-#define evdns_base_set_default_outgoing_bind_address(base, a, len) \
- evdns_set_default_outgoing_bind_address((a),(len))
-#define evdns_base_resolv_conf_parse(base, options, fname) \
- evdns_resolv_conf_parse((options), (fname))
-#define evdns_base_count_nameservers(base) \
- evdns_count_nameservers()
-#define evdns_base_resume(base) \
- evdns_resume()
-#define evdns_base_config_windows_nameservers(base) \
- evdns_config_windows_nameservers()
-#define evdns_base_set_option_(base, opt, val) \
- evdns_set_option((opt),(val),DNS_OPTIONS_ALL)
-/* Note: our internal eventdns.c, plus Libevent 1.4, used a 1 return to
- * signify failure to launch a resolve. Libevent 2.0 uses a -1 return to
- * signify a failure on a resolve, though if we're on Libevent 2.0, we should
- * have event2/dns.h and never hit these macros. Regardless, 0 is success. */
-#define evdns_base_resolve_ipv4(base, addr, options, cb, ptr) \
- ((evdns_resolve_ipv4((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_ipv6(base, addr, options, cb, ptr) \
- ((evdns_resolve_ipv6((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_reverse(base, addr, options, cb, ptr) \
- ((evdns_resolve_reverse((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_reverse_ipv6(base, addr, options, cb, ptr) \
- ((evdns_resolve_reverse_ipv6((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-
-#elif defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER < 0x02000303
-#define evdns_base_set_option_(base, opt, val) \
- evdns_base_set_option((base), (opt),(val),DNS_OPTIONS_ALL)
-
-#else
-#define evdns_base_set_option_ evdns_base_set_option
-
-#endif
/** How long will we wait for an answer from the resolver before we decide
* that the resolver is wedged? */
@@ -260,29 +243,19 @@ has_dns_init_failed(void)
}
/** Helper: Given a TTL from a DNS response, determine what TTL to give the
- * OP that asked us to resolve it. */
+ * OP that asked us to resolve it, and how long to cache that record
+ * ourselves. */
uint32_t
dns_clip_ttl(uint32_t ttl)
{
- if (ttl < MIN_DNS_TTL)
- return MIN_DNS_TTL;
- else if (ttl > MAX_DNS_TTL)
- return MAX_DNS_TTL;
- else
- return ttl;
-}
-
-/** Helper: Given a TTL from a DNS response, determine how long to hold it in
- * our cache. */
-STATIC uint32_t
-dns_get_expiry_ttl(uint32_t ttl)
-{
- if (ttl < MIN_DNS_TTL)
- return MIN_DNS_TTL;
- else if (ttl > MAX_DNS_ENTRY_AGE)
- return MAX_DNS_ENTRY_AGE;
+ /* This logic is a defense against "DefectTor" DNS-based traffic
+ * confirmation attacks, as in https://nymity.ch/tor-dns/tor-dns.pdf .
+ * We only give two values: a "low" value and a "high" value.
+ */
+ if (ttl < MIN_DNS_TTL_AT_EXIT)
+ return MIN_DNS_TTL_AT_EXIT;
else
- return ttl;
+ return MAX_DNS_TTL_AT_EXIT;
}
/** Helper: free storage held by an entry in the DNS cache. */
@@ -353,7 +326,7 @@ cached_resolve_add_answer(cached_resolve_t *resolve,
resolve->result_ipv4.err_ipv4 = dns_result;
resolve->res_status_ipv4 = RES_STATUS_DONE_ERR;
}
-
+ resolve->ttl_ipv4 = ttl;
} else if (query_type == DNS_IPv6_AAAA) {
if (resolve->res_status_ipv6 != RES_STATUS_INFLIGHT)
return;
@@ -368,6 +341,7 @@ cached_resolve_add_answer(cached_resolve_t *resolve,
resolve->result_ipv6.err_ipv6 = dns_result;
resolve->res_status_ipv6 = RES_STATUS_DONE_ERR;
}
+ resolve->ttl_ipv6 = ttl;
}
}
@@ -548,6 +522,7 @@ send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type,
answer_type = RESOLVED_TYPE_ERROR;
/* fall through. */
}
+ /* Falls through. */
case RESOLVED_TYPE_ERROR_TRANSIENT:
case RESOLVED_TYPE_ERROR:
{
@@ -846,8 +821,14 @@ dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve,
}
/** Given an exit connection <b>exitconn</b>, and a cached_resolve_t
- * <b>resolve</b> whose DNS lookups have all succeeded or failed, update the
- * appropriate fields (address_ttl and addr) of <b>exitconn</b>.
+ * <b>resolve</b> whose DNS lookups have all either succeeded or failed,
+ * update the appropriate fields (address_ttl and addr) of <b>exitconn</b>.
+ *
+ * The logic can be complicated here, since we might have launched both
+ * an A lookup and an AAAA lookup, and since either of those might have
+ * succeeded or failed, and since we want to answer a RESOLVE cell with
+ * a full answer but answer a BEGIN cell with whatever answer the client
+ * would accept <i>and</i> we could still connect to.
*
* If this is a reverse lookup, set *<b>hostname_out</b> to a newly allocated
* copy of the name resulting hostname.
@@ -1190,7 +1171,12 @@ dns_found_answer(const char *address, uint8_t query_type,
/** Given a pending cached_resolve_t that we just finished resolving,
* inform every connection that was waiting for the outcome of that
- * resolution. */
+ * resolution.
+ *
+ * Do this by sending a RELAY_RESOLVED cell (if the pending stream had sent us
+ * RELAY_RESOLVE cell), or by launching an exit connection (if the pending
+ * stream had send us a RELAY_BEGIN cell).
+ */
static void
inform_pending_connections(cached_resolve_t *resolve)
{
@@ -1323,7 +1309,7 @@ make_pending_resolve_cached(cached_resolve_t *resolve)
resolve->ttl_hostname < ttl)
ttl = resolve->ttl_hostname;
- set_expiry(new_resolve, time(NULL) + dns_get_expiry_ttl(ttl));
+ set_expiry(new_resolve, time(NULL) + dns_clip_ttl(ttl));
}
assert_cache_ok();
@@ -1373,23 +1359,6 @@ configure_nameservers(int force)
}
}
-#ifdef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
- if (! tor_addr_is_null(&options->OutboundBindAddressIPv4_)) {
- int socklen;
- struct sockaddr_storage ss;
- socklen = tor_addr_to_sockaddr(&options->OutboundBindAddressIPv4_, 0,
- (struct sockaddr *)&ss, sizeof(ss));
- if (socklen <= 0) {
- log_warn(LD_BUG, "Couldn't convert outbound bind address to sockaddr."
- " Ignoring.");
- } else {
- evdns_base_set_default_outgoing_bind_address(the_evdns_base,
- (struct sockaddr *)&ss,
- socklen);
- }
- }
-#endif
-
evdns_set_log_fn(evdns_log_cb);
if (conf_fname) {
log_debug(LD_FS, "stat()ing %s", conf_fname);
@@ -1454,16 +1423,33 @@ configure_nameservers(int force)
}
#endif
-#define SET(k,v) evdns_base_set_option_(the_evdns_base, (k), (v))
+#define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v))
+ // If we only have one nameserver, it does not make sense to back off
+ // from it for a timeout. Unfortunately, the value for max-timeouts is
+ // currently clamped by libevent to 255, but it does not hurt to set
+ // it higher in case libevent gets a patch for this.
+ // Reducing attempts in the case of just one name server too, because
+ // it is very likely to be a local one where a network connectivity
+ // issue should not cause an attempt to fail.
if (evdns_base_count_nameservers(the_evdns_base) == 1) {
- SET("max-timeouts:", "16");
- SET("timeout:", "10");
+ SET("max-timeouts:", "1000000");
+ SET("attempts:", "1");
} else {
SET("max-timeouts:", "3");
- SET("timeout:", "5");
}
+ // Elongate the queue of maximum inflight dns requests, so if a bunch
+ // time out at the resolver (happens commonly with unbound) we won't
+ // stall every other DNS request. This potentially means some wasted
+ // CPU as there's a walk over a linear queue involved, but this is a
+ // much better tradeoff compared to just failing DNS requests because
+ // of a full queue.
+ SET("max-inflight:", "8192");
+
+ // Time out after 5 seconds if no reply.
+ SET("timeout:", "5");
+
if (options->ServerDNSRandomizeCase)
SET("randomize-case:", "1");
else
diff --git a/src/or/dns.h b/src/or/dns.h
index b14f7dd29c..951a2a3467 100644
--- a/src/or/dns.h
+++ b/src/or/dns.h
@@ -12,6 +12,18 @@
#ifndef TOR_DNS_H
#define TOR_DNS_H
+/** Lowest value for DNS ttl that a server will give. */
+#define MIN_DNS_TTL_AT_EXIT (5*60)
+/** Highest value for DNS ttl that a server will give. */
+#define MAX_DNS_TTL_AT_EXIT (60*60)
+
+/** How long do we keep DNS cache entries before purging them (regardless of
+ * their TTL)? */
+#define MAX_DNS_ENTRY_AGE (3*60*60)
+/** How long do we cache/tell clients to cache DNS records when no TTL is
+ * known? */
+#define DEFAULT_DNS_TTL (30*60)
+
int dns_init(void);
int has_dns_init_failed(void);
void dns_free_all(void);
@@ -31,8 +43,6 @@ void dump_dns_mem_usage(int severity);
#ifdef DNS_PRIVATE
#include "dns_structs.h"
-STATIC uint32_t dns_get_expiry_ttl(uint32_t ttl);
-
MOCK_DECL(STATIC int,dns_resolve_impl,(edge_connection_t *exitconn,
int is_resolve,or_circuit_t *oncirc, char **hostname_out,
int *made_connection_pending_out, cached_resolve_t **resolve_out));
diff --git a/src/or/dns_structs.h b/src/or/dns_structs.h
index bb67459d7b..bc6067213d 100644
--- a/src/or/dns_structs.h
+++ b/src/or/dns_structs.h
@@ -1,3 +1,15 @@
+/* Copyright (c) 2003-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file dns_structs.h
+ *
+ * \brief Structures used in dns.c. Exposed to dns.c, and to the unit tests
+ * that declare DNS_PRIVATE.
+ */
+
#ifndef TOR_DNS_STRUCTS_H
#define TOR_DNS_STRUCTS_H
diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c
index 74f17ce78c..f5a4f2ac0f 100644
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@ -17,14 +17,10 @@
#include "control.h"
#include "main.h"
#include "policies.h"
-#ifdef HAVE_EVENT2_DNS_H
#include <event2/dns.h>
#include <event2/dns_compat.h>
/* XXXX this implies we want an improved evdns */
#include <event2/dns_struct.h>
-#else
-#include "eventdns.h"
-#endif
/** Helper function: called by evdns whenever the client sends a request to our
* DNSPort. We need to eventually answer the request <b>req</b>.
@@ -130,7 +126,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_)
tor_addr_copy(&TO_CONN(conn)->addr, &tor_addr);
TO_CONN(conn)->port = port;
- TO_CONN(conn)->address = tor_dup_addr(&tor_addr);
+ TO_CONN(conn)->address = tor_addr_to_str_dup(&tor_addr);
if (q->type == EVDNS_TYPE_A || q->type == EVDNS_TYPE_AAAA ||
q->type == EVDNS_QTYPE_ALL) {
@@ -140,6 +136,8 @@ evdns_server_callback(struct evdns_server_request *req, void *data_)
entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR;
}
+ /* This serves our DNS port so enable DNS request by default. */
+ entry_conn->entry_cfg.dns_request = 1;
if (q->type == EVDNS_TYPE_A || q->type == EVDNS_QTYPE_ALL) {
entry_conn->entry_cfg.ipv4_traffic = 1;
entry_conn->entry_cfg.ipv6_traffic = 0;
@@ -205,7 +203,7 @@ dnsserv_launch_request(const char *name, int reverse,
tor_addr_copy(&TO_CONN(conn)->addr, &control_conn->base_.addr);
#ifdef AF_UNIX
/*
- * The control connection can be AF_UNIX and if so tor_dup_addr will
+ * The control connection can be AF_UNIX and if so tor_addr_to_str_dup will
* unhelpfully say "<unknown address type>"; say "(Tor_internal)"
* instead.
*/
@@ -214,11 +212,11 @@ dnsserv_launch_request(const char *name, int reverse,
TO_CONN(conn)->address = tor_strdup("(Tor_internal)");
} else {
TO_CONN(conn)->port = control_conn->base_.port;
- TO_CONN(conn)->address = tor_dup_addr(&control_conn->base_.addr);
+ TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr);
}
#else
TO_CONN(conn)->port = control_conn->base_.port;
- TO_CONN(conn)->address = tor_dup_addr(&control_conn->base_.addr);
+ TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr);
#endif
if (reverse)
@@ -292,6 +290,10 @@ evdns_get_orig_address(const struct evdns_server_request *req,
case RESOLVED_TYPE_IPV6:
type = EVDNS_TYPE_AAAA;
break;
+ case RESOLVED_TYPE_ERROR:
+ case RESOLVED_TYPE_ERROR_TRANSIENT:
+ /* Addr doesn't matter, since we're not sending it back in the reply.*/
+ return addr;
default:
tor_fragile_assert();
return addr;
diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c
index 310a948b35..265b6dcda1 100644
--- a/src/or/entrynodes.c
+++ b/src/or/entrynodes.c
@@ -76,6 +76,14 @@ static const node_t *choose_random_entry_impl(cpath_build_state_t *state,
int *n_options_out);
static int num_bridges_usable(void);
+/* Default number of entry guards in the case where the NumEntryGuards
+ * consensus parameter is not set */
+#define DEFAULT_N_GUARDS 1
+/* Minimum and maximum number of entry guards (in case the NumEntryGuards
+ * consensus parameter is set). */
+#define MIN_N_GUARDS 1
+#define MAX_N_GUARDS 10
+
/** Return the list of entry guards, creating it if necessary. */
const smartlist_t *
get_entry_guards(void)
@@ -488,7 +496,8 @@ decide_num_guards(const or_options_t *options, int for_directory)
return options->NumEntryGuards;
/* Use the value from the consensus, or 3 if no guidance. */
- return networkstatus_get_param(NULL, "NumEntryGuards", 3, 1, 10);
+ return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS,
+ MIN_N_GUARDS, MAX_N_GUARDS);
}
/** If the use of entry guards is configured, choose more entry guards
@@ -722,8 +731,9 @@ entry_guards_compute_status(const or_options_t *options, time_t now)
*
* If <b>mark_relay_status</b>, also call router_set_status() on this
* relay.
- *
- * XXX024 change succeeded and mark_relay_status into 'int flags'.
+ */
+/* XXX We could change succeeded and mark_relay_status into 'int flags'.
+ * Too many boolean arguments is a recipe for confusion.
*/
int
entry_guard_register_connect_status(const char *digest, int succeeded,
@@ -1243,7 +1253,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg)
} else {
strlcpy(node->nickname, smartlist_get(args,0), MAX_NICKNAME_LEN+1);
if (base16_decode(node->identity, DIGEST_LEN, smartlist_get(args,1),
- strlen(smartlist_get(args,1)))<0) {
+ strlen(smartlist_get(args,1))) != DIGEST_LEN) {
*msg = tor_strdup("Unable to parse entry nodes: "
"Bad hex digest for EntryGuard");
}
@@ -1299,8 +1309,9 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg)
log_warn(LD_BUG, "EntryGuardAddedBy line is not long enough.");
continue;
}
- if (base16_decode(d, sizeof(d), line->value, HEX_DIGEST_LEN)<0 ||
- line->value[HEX_DIGEST_LEN] != ' ') {
+ if (base16_decode(d, sizeof(d),
+ line->value, HEX_DIGEST_LEN) != sizeof(d) ||
+ line->value[HEX_DIGEST_LEN] != ' ') {
log_warn(LD_BUG, "EntryGuardAddedBy line %s does not begin with "
"hex digest", escaped(line->value));
continue;
@@ -1444,7 +1455,6 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg)
}
} else {
if (state_version) {
- time_t now = time(NULL);
e->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now);
e->chosen_by_version = tor_strdup(state_version);
}
@@ -1466,7 +1476,7 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg)
}
entry_guards = new_entry_guards;
entry_guards_dirty = 0;
- /* XXX024 hand new_entry_guards to this func, and move it up a
+ /* XXX hand new_entry_guards to this func, and move it up a
* few lines, so we don't have to re-dirty it */
if (remove_obsolete_entry_guards(now))
entry_guards_dirty = 1;
@@ -2022,6 +2032,7 @@ bridge_add_from_config(bridge_line_t *bridge_line)
if (bridge_line->transport_name)
b->transport_name = bridge_line->transport_name;
b->fetch_status.schedule = DL_SCHED_BRIDGE;
+ b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL;
b->socks_args = bridge_line->socks_args;
if (!bridge_list)
bridge_list = smartlist_new();
@@ -2412,6 +2423,44 @@ num_bridges_usable(void)
return n_options;
}
+/** Return a smartlist containing all bridge identity digests */
+MOCK_IMPL(smartlist_t *,
+list_bridge_identities, (void))
+{
+ smartlist_t *result = NULL;
+ char *digest_tmp;
+
+ if (get_options()->UseBridges && bridge_list) {
+ result = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ digest_tmp = tor_malloc(DIGEST_LEN);
+ memcpy(digest_tmp, b->identity, DIGEST_LEN);
+ smartlist_add(result, digest_tmp);
+ } SMARTLIST_FOREACH_END(b);
+ }
+
+ return result;
+}
+
+/** Get the download status for a bridge descriptor given its identity */
+MOCK_IMPL(download_status_t *,
+get_bridge_dl_status_by_id, (const char *digest))
+{
+ download_status_t *dl = NULL;
+
+ if (digest && get_options()->UseBridges && bridge_list) {
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ if (tor_memeq(digest, b->identity, DIGEST_LEN)) {
+ dl = &(b->fetch_status);
+ break;
+ }
+ } SMARTLIST_FOREACH_END(b);
+ }
+
+ return dl;
+}
+
/** Return 1 if we have at least one descriptor for an entry guard
* (bridge or member of EntryNodes) and all descriptors we know are
* down. Else return 0. If <b>act</b> is 1, then mark the down guards
diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h
index 247c80940e..1021e67d43 100644
--- a/src/or/entrynodes.h
+++ b/src/or/entrynodes.h
@@ -179,5 +179,9 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
int orig_bandwidth,
uint32_t guardfraction_percentage);
+MOCK_DECL(smartlist_t *, list_bridge_identities, (void));
+MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id,
+ (const char *digest));
+
#endif
diff --git a/src/or/eventdns_tor.h b/src/or/eventdns_tor.h
deleted file mode 100644
index 5db09ae043..0000000000
--- a/src/or/eventdns_tor.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* Copyright (c) 2007-2016, The Tor Project, Inc. */
-/* See LICENSE for licensing information */
-
-#ifndef TOR_EVENTDNS_TOR_H
-#define TOR_EVENTDNS_TOR_H
-
-#include "orconfig.h"
-#define DNS_USE_OPENSSL_FOR_ID
-#ifndef HAVE_UINT
-typedef unsigned int uint;
-#endif
-#ifndef HAVE_U_CHAR
-typedef unsigned char u_char;
-#endif
-#include "torint.h"
-
-/* These are for debugging possible memory leaks. */
-#include "util.h"
-#include "compat.h"
-
-#endif
-
diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c
index aa1b3e26fe..676adfd8bf 100644
--- a/src/or/ext_orport.c
+++ b/src/or/ext_orport.c
@@ -4,7 +4,17 @@
/**
* \file ext_orport.c
* \brief Code implementing the Extended ORPort.
-*/
+ *
+ * The Extended ORPort interface is used by pluggable transports to
+ * communicate additional information to a Tor bridge, including
+ * address information. For more information on this interface,
+ * see pt-spec.txt in torspec.git.
+ *
+ * There is no separate structure for extended ORPort connections; they use
+ * or_connection_t objects, and share most of their implementation with
+ * connection_or.c. Once the handshake is done, an extended ORPort connection
+ * turns into a regular OR connection, using connection_ext_or_transition().
+ */
#define EXT_ORPORT_PRIVATE
#include "or.h"
@@ -41,12 +51,7 @@ ext_or_cmd_free(ext_or_cmd_t *cmd)
static int
connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out)
{
- IF_HAS_BUFFEREVENT(conn, {
- struct evbuffer *input = bufferevent_get_input(conn->bufev);
- return fetch_ext_or_command_from_evbuffer(input, out);
- }) ELSE_IF_NO_BUFFEREVENT {
- return fetch_ext_or_command_from_buf(conn->inbuf, out);
- }
+ return fetch_ext_or_command_from_buf(conn->inbuf, out);
}
/** Write an Extended ORPort message to <b>conn</b>. Use
@@ -461,8 +466,8 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn,
return -1;
{ /* do some logging */
- char *old_address = tor_dup_addr(&conn->addr);
- char *new_address = tor_dup_addr(&addr);
+ char *old_address = tor_addr_to_str_dup(&conn->addr);
+ char *new_address = tor_addr_to_str_dup(&addr);
log_debug(LD_NET, "Received USERADDR."
"We rewrite our address from '%s:%u' to '%s:%u'.",
@@ -478,7 +483,7 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn,
if (conn->address) {
tor_free(conn->address);
}
- conn->address = tor_dup_addr(&addr);
+ conn->address = tor_addr_to_str_dup(&addr);
return 0;
}
diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c
index 53b311e580..eeeb0f1de3 100644
--- a/src/or/fp_pair.c
+++ b/src/or/fp_pair.c
@@ -7,6 +7,14 @@
* \brief Manages data structures for associating pairs of fingerprints. Used
* to handle combinations of identity/signing-key fingerprints for
* authorities.
+ *
+ * This is a nice, simple, compact data structure module that handles a map
+ * from (signing key fingerprint, identity key fingerprint) to void *. The
+ * fingerprints here are SHA1 digests of RSA keys.
+ *
+ * This structure is used in directory.c and in routerlist.c for handling
+ * handling authority certificates, since we never want more than a single
+ * certificate for any (ID key, signing key) pair.
**/
#include "or.h"
diff --git a/src/or/geoip.c b/src/or/geoip.c
index 681cb900f2..00c055bbe7 100644
--- a/src/or/geoip.c
+++ b/src/or/geoip.c
@@ -7,6 +7,24 @@
* to summarizing client connections by country to entry guards, bridges,
* and directory servers; and for statistics on answering network status
* requests.
+ *
+ * There are two main kinds of functions in this module: geoip functions,
+ * which map groups of IPv4 and IPv6 addresses to country codes, and
+ * statistical functions, which collect statistics about different kinds of
+ * per-country usage.
+ *
+ * The geoip lookup tables are implemented as sorted lists of disjoint address
+ * ranges, each mapping to a singleton geoip_country_t. These country objects
+ * are also indexed by their names in a hashtable.
+ *
+ * The tables are populated from disk at startup by the geoip_load_file()
+ * function. For more information on the file format they read, see that
+ * function. See the scripts and the README file in src/config for more
+ * information about how those files are generated.
+ *
+ * Tor uses GeoIP information in order to implement user requests (such as
+ * ExcludeNodes {cc}), and to keep track of how much usage relays are getting
+ * for each country.
*/
#define GEOIP_PRIVATE
@@ -80,9 +98,9 @@ geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high,
intptr_t idx;
void *idxplus1_;
- if (tor_addr_family(low) != tor_addr_family(high))
+ IF_BUG_ONCE(tor_addr_family(low) != tor_addr_family(high))
return;
- if (tor_addr_compare(high, low, CMP_EXACT) < 0)
+ IF_BUG_ONCE(tor_addr_compare(high, low, CMP_EXACT) < 0)
return;
idxplus1_ = strmap_get_lc(country_idxplus1_by_lc_code, country);
@@ -110,8 +128,8 @@ geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high,
smartlist_add(geoip_ipv4_entries, ent);
} else if (tor_addr_family(low) == AF_INET6) {
geoip_ipv6_entry_t *ent = tor_malloc_zero(sizeof(geoip_ipv6_entry_t));
- ent->ip_low = *tor_addr_to_in6(low);
- ent->ip_high = *tor_addr_to_in6(high);
+ ent->ip_low = *tor_addr_to_in6_assert(low);
+ ent->ip_high = *tor_addr_to_in6_assert(high);
ent->country = idx;
smartlist_add(geoip_ipv6_entries, ent);
}
@@ -504,7 +522,7 @@ clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b)
}
HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
- clientmap_entries_eq);
+ clientmap_entries_eq)
HT_GENERATE2(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_)
@@ -718,7 +736,7 @@ dirreq_map_ent_hash(const dirreq_map_entry_t *entry)
}
HT_PROTOTYPE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
- dirreq_map_ent_eq);
+ dirreq_map_ent_eq)
HT_GENERATE2(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_)
@@ -824,7 +842,6 @@ geoip_get_transport_history(void)
static const char* no_transport_str = "<OR>";
clientmap_entry_t **ent;
- const char *transport_name = NULL;
smartlist_t *string_chunks = smartlist_new();
char *the_string = NULL;
@@ -850,7 +867,7 @@ geoip_get_transport_history(void)
HT_FOREACH(ent, clientmap, &client_history) {
uintptr_t val;
void *ptr;
- transport_name = (*ent)->transport_name;
+ const char *transport_name = (*ent)->transport_name;
if (!transport_name)
transport_name = no_transport_str;
@@ -916,13 +933,13 @@ geoip_get_dirreq_history(dirreq_type_t type)
smartlist_t *dirreq_completed = NULL;
uint32_t complete = 0, timeouts = 0, running = 0;
int bufsize = 1024, written;
- dirreq_map_entry_t **ptr, **next, *ent;
+ dirreq_map_entry_t **ptr, **next;
struct timeval now;
tor_gettimeofday(&now);
dirreq_completed = smartlist_new();
for (ptr = HT_START(dirreqmap, &dirreq_map); ptr; ptr = next) {
- ent = *ptr;
+ dirreq_map_entry_t *ent = *ptr;
if (ent->type != type) {
next = HT_NEXT(dirreqmap, &dirreq_map, ptr);
continue;
@@ -1024,7 +1041,7 @@ geoip_get_client_history(geoip_client_action_t action,
smartlist_t *entries = NULL;
int n_countries = geoip_get_n_countries();
int i;
- clientmap_entry_t **ent;
+ clientmap_entry_t **cm_ent;
unsigned *counts = NULL;
unsigned total = 0;
unsigned ipv4_count = 0, ipv6_count = 0;
@@ -1033,17 +1050,17 @@ geoip_get_client_history(geoip_client_action_t action,
return -1;
counts = tor_calloc(n_countries, sizeof(unsigned));
- HT_FOREACH(ent, clientmap, &client_history) {
+ HT_FOREACH(cm_ent, clientmap, &client_history) {
int country;
- if ((*ent)->action != (int)action)
+ if ((*cm_ent)->action != (int)action)
continue;
- country = geoip_get_country_by_addr(&(*ent)->addr);
+ country = geoip_get_country_by_addr(&(*cm_ent)->addr);
if (country < 0)
country = 0; /** unresolved requests are stored at index 0. */
tor_assert(0 <= country && country < n_countries);
++counts[country];
++total;
- switch (tor_addr_family(&(*ent)->addr)) {
+ switch (tor_addr_family(&(*cm_ent)->addr)) {
case AF_INET:
ipv4_count++;
break;
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index 9408925d96..e3c80b5f14 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -8,6 +8,12 @@
* etc in preparation for closing down or going dormant; and to track
* bandwidth and time intervals to know when to hibernate and when to
* stop hibernating.
+ *
+ * Ordinarily a Tor relay is "Live".
+ *
+ * A live relay can stop accepting connections for one of two reasons: either
+ * it is trying to conserve bandwidth because of bandwidth accounting rules
+ * ("soft hibernation"), or it is about to shut down ("exiting").
**/
/*
@@ -28,13 +34,12 @@ hibernating, phase 2:
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
+#include "control.h"
#include "hibernate.h"
#include "main.h"
#include "router.h"
#include "statefile.h"
-extern long stats_n_seconds_working; /* published uptime */
-
/** Are we currently awake, asleep, running out of bandwidth, or shutting
* down? */
static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL;
@@ -50,8 +55,10 @@ typedef enum {
UNIT_MONTH=1, UNIT_WEEK=2, UNIT_DAY=3,
} time_unit_t;
-/* Fields for accounting logic. Accounting overview:
+/*
+ * @file hibernate.c
*
+ * <h4>Accounting</h4>
* Accounting is designed to ensure that no more than N bytes are sent in
* either direction over a given interval (currently, one month, one week, or
* one day) We could
@@ -65,17 +72,21 @@ typedef enum {
*
* Each interval runs as follows:
*
- * 1. We guess our bandwidth usage, based on how much we used
+ * <ol>
+ * <li>We guess our bandwidth usage, based on how much we used
* last time. We choose a "wakeup time" within the interval to come up.
- * 2. Until the chosen wakeup time, we hibernate.
- * 3. We come up at the wakeup time, and provide bandwidth until we are
+ * <li>Until the chosen wakeup time, we hibernate.
+ * <li> We come up at the wakeup time, and provide bandwidth until we are
* "very close" to running out.
- * 4. Then we go into low-bandwidth mode, and stop accepting new
+ * <li> Then we go into low-bandwidth mode, and stop accepting new
* connections, but provide bandwidth until we run out.
- * 5. Then we hibernate until the end of the interval.
+ * <li> Then we hibernate until the end of the interval.
*
* If the interval ends before we run out of bandwidth, we go back to
* step one.
+ *
+ * Accounting is controlled by the AccountingMax, AccountingRule, and
+ * AccountingStart options.
*/
/** How many bytes have we read in this accounting interval? */
@@ -111,11 +122,34 @@ static int cfg_start_day = 0,
cfg_start_min = 0;
/** @} */
+static const char *hibernate_state_to_string(hibernate_state_t state);
static void reset_accounting(time_t now);
static int read_bandwidth_usage(void);
static time_t start_of_accounting_period_after(time_t now);
static time_t start_of_accounting_period_containing(time_t now);
static void accounting_set_wakeup_time(void);
+static void on_hibernate_state_change(hibernate_state_t prev_state);
+
+/**
+ * Return the human-readable name for the hibernation state <b>state</b>
+ */
+static const char *
+hibernate_state_to_string(hibernate_state_t state)
+{
+ static char buf[64];
+ switch (state) {
+ case HIBERNATE_STATE_EXITING: return "EXITING";
+ case HIBERNATE_STATE_LOWBANDWIDTH: return "SOFT";
+ case HIBERNATE_STATE_DORMANT: return "HARD";
+ case HIBERNATE_STATE_INITIAL:
+ case HIBERNATE_STATE_LIVE:
+ return "AWAKE";
+ default:
+ log_warn(LD_BUG, "unknown hibernate state %d", state);
+ tor_snprintf(buf, sizeof(buf), "unknown [%d]", state);
+ return buf;
+ }
+}
/* ************
* Functions for bandwidth accounting.
@@ -297,7 +331,7 @@ edge_of_accounting_period_containing(time_t now, int get_end)
case UNIT_MONTH: {
/* If this is before the Nth, we want the Nth of last month. */
if (tm.tm_mday < cfg_start_day ||
- (tm.tm_mday < cfg_start_day && before)) {
+ (tm.tm_mday == cfg_start_day && before)) {
--tm.tm_mon;
}
/* Otherwise, the month is correct. */
@@ -670,7 +704,7 @@ read_bandwidth_usage(void)
int res;
res = unlink(fname);
- if (res != 0) {
+ if (res != 0 && errno != ENOENT) {
log_warn(LD_FS,
"Failed to unlink %s: %s",
fname, strerror(errno));
@@ -935,6 +969,7 @@ consider_hibernation(time_t now)
{
int accounting_enabled = get_options()->AccountingMax != 0;
char buf[ISO_TIME_LEN+1];
+ hibernate_state_t prev_state = hibernate_state;
/* If we're in 'exiting' mode, then we just shut down after the interval
* elapses. */
@@ -990,6 +1025,10 @@ consider_hibernation(time_t now)
hibernate_end_time_elapsed(now);
}
}
+
+ /* Dispatch a controller event if the hibernation state changed. */
+ if (hibernate_state != prev_state)
+ on_hibernate_state_change(prev_state);
}
/** Helper function: called when we get a GETINFO request for an
@@ -1007,12 +1046,8 @@ getinfo_helper_accounting(control_connection_t *conn,
if (!strcmp(question, "accounting/enabled")) {
*answer = tor_strdup(accounting_is_enabled(get_options()) ? "1" : "0");
} else if (!strcmp(question, "accounting/hibernating")) {
- if (hibernate_state == HIBERNATE_STATE_DORMANT)
- *answer = tor_strdup("hard");
- else if (hibernate_state == HIBERNATE_STATE_LOWBANDWIDTH)
- *answer = tor_strdup("soft");
- else
- *answer = tor_strdup("awake");
+ *answer = tor_strdup(hibernate_state_to_string(hibernate_state));
+ tor_strlower(*answer);
} else if (!strcmp(question, "accounting/bytes")) {
tor_asprintf(answer, U64_FORMAT" "U64_FORMAT,
U64_PRINTF_ARG(n_bytes_read_in_interval),
@@ -1062,6 +1097,20 @@ getinfo_helper_accounting(control_connection_t *conn,
return 0;
}
+/**
+ * Helper function: called when the hibernation state changes, and sends a
+ * SERVER_STATUS event to notify interested controllers of the accounting
+ * state change.
+ */
+static void
+on_hibernate_state_change(hibernate_state_t prev_state)
+{
+ (void)prev_state; /* Should we do something with this? */
+ control_event_server_status(LOG_NOTICE,
+ "HIBERNATION_STATUS STATUS=%s",
+ hibernate_state_to_string(hibernate_state));
+}
+
#ifdef TOR_UNIT_TESTS
/**
* Manually change the hibernation state. Private; used only by the unit
diff --git a/src/or/include.am b/src/or/include.am
index 712ae18406..ae493b7225 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -17,12 +17,6 @@ endif
EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake
-if USE_EXTERNAL_EVDNS
-evdns_source=
-else
-evdns_source=src/ext/eventdns.c
-endif
-
LIBTOR_A_SOURCES = \
src/or/addressmap.c \
src/or/buffers.c \
@@ -62,8 +56,11 @@ LIBTOR_A_SOURCES = \
src/or/onion.c \
src/or/onion_fast.c \
src/or/onion_tap.c \
+ src/or/shared_random.c \
+ src/or/shared_random_state.c \
src/or/transports.c \
src/or/periodic.c \
+ src/or/protover.c \
src/or/policies.c \
src/or/reasons.c \
src/or/relay.c \
@@ -84,7 +81,6 @@ LIBTOR_A_SOURCES = \
src/or/status.c \
src/or/torcert.c \
src/or/onion_ntor.c \
- $(evdns_source) \
$(tor_platform_source)
src_or_libtor_a_SOURCES = $(LIBTOR_A_SOURCES)
@@ -109,7 +105,7 @@ src_or_libtor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
-src_or_tor_LDADD = src/or/libtor.a src/common/libor.a \
+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 \
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
@@ -121,6 +117,7 @@ src_or_tor_cov_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
src_or_tor_cov_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
src_or_tor_cov_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \
+ src/common/libor-ctime-testing.a \
src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \
src/common/libor-event-testing.a src/trunnel/libor-trunnel-testing.a \
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
@@ -154,7 +151,6 @@ ORHEADERS = \
src/or/dns.h \
src/or/dns_structs.h \
src/or/dnsserv.h \
- src/or/eventdns_tor.h \
src/or/ext_orport.h \
src/or/fallback_dirs.inc \
src/or/fp_pair.h \
@@ -172,9 +168,12 @@ ORHEADERS = \
src/or/onion_ntor.h \
src/or/onion_tap.h \
src/or/or.h \
+ src/or/shared_random.h \
+ src/or/shared_random_state.h \
src/or/transports.h \
src/or/periodic.h \
src/or/policies.h \
+ src/or/protover.h \
src/or/reasons.h \
src/or/relay.h \
src/or/rendcache.h \
@@ -199,7 +198,7 @@ noinst_HEADERS+= $(ORHEADERS) micro-revision.i
micro-revision.i: FORCE
$(AM_V_at)rm -f micro-revision.tmp; \
- if test -d "$(top_srcdir)/.git" && \
+ if test -r "$(top_srcdir)/.git" && \
test -x "`which git 2>&1;true`"; then \
HASH="`cd "$(top_srcdir)" && git rev-parse --short=16 HEAD`"; \
echo \"$$HASH\" > micro-revision.tmp; \
diff --git a/src/or/keypin.c b/src/or/keypin.c
index 1f82eccf86..2d4c4e92d2 100644
--- a/src/or/keypin.c
+++ b/src/or/keypin.c
@@ -39,16 +39,28 @@
* @brief Key-pinning for RSA and Ed25519 identity keys at directory
* authorities.
*
+ * Many older clients, and many internal interfaces, still refer to relays by
+ * their RSA1024 identity keys. We can make this more secure, however:
+ * authorities use this module to track which RSA keys have been used along
+ * with which Ed25519 keys, and force such associations to be permanent.
+ *
* This module implements a key-pinning mechanism to ensure that it's safe
* to use RSA keys as identitifers even as we migrate to Ed25519 keys. It
* remembers, for every Ed25519 key we've seen, what the associated Ed25519
* key is. This way, if we see a different Ed25519 key with that RSA key,
* we'll know that there's a mismatch.
*
+ * (As of this writing, these key associations are advisory only, mostly
+ * because some relay operators kept mishandling their Ed25519 keys during
+ * the initial Ed25519 rollout. We should fix this problem, and then toggle
+ * the AuthDirPinKeys option.)
+ *
* We persist these entries to disk using a simple format, where each line
* has a base64-encoded RSA SHA1 hash, then a base64-endoded Ed25519 key.
* Empty lines, misformed lines, and lines beginning with # are
* ignored. Lines beginning with @ are reserved for future extensions.
+ *
+ * The dirserv.c module is the main user of these functions.
*/
static int keypin_journal_append_entry(const uint8_t *rsa_id_digest,
@@ -93,14 +105,14 @@ return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key));
}
HT_PROTOTYPE(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
- keypin_ents_eq_rsa);
+ keypin_ents_eq_rsa)
HT_GENERATE2(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa,
- keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_);
+ keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_)
HT_PROTOTYPE(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
- keypin_ents_eq_ed);
+ keypin_ents_eq_ed)
HT_GENERATE2(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed,
- keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_);
+ keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_)
/**
* Check whether we already have an entry in the key pinning table for a
@@ -479,7 +491,7 @@ keypin_clear(void)
HT_CLEAR(rsamap,&the_rsa_map);
if (bad_entries) {
- log_warn(LD_BUG, "Found %d discrepencies in the the keypin database.",
+ log_warn(LD_BUG, "Found %d discrepencies in the keypin database.",
bad_entries);
}
}
diff --git a/src/or/main.c b/src/or/main.c
index d4d98ee317..187b255bfb 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -46,6 +46,7 @@
#include "onion.h"
#include "periodic.h"
#include "policies.h"
+#include "protover.h"
#include "transports.h"
#include "relay.h"
#include "rendclient.h"
@@ -57,6 +58,7 @@
#include "routerlist.h"
#include "routerparse.h"
#include "scheduler.h"
+#include "shared_random.h"
#include "statefile.h"
#include "status.h"
#include "util_process.h"
@@ -68,15 +70,7 @@
#include "memarea.h"
#include "sandbox.h"
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
-
-#ifdef USE_BUFFEREVENTS
-#include <event2/bufferevent.h>
-#endif
#ifdef HAVE_SYSTEMD
# if defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__)
@@ -104,8 +98,6 @@ static int run_main_loop_until_done(void);
static void process_signal(int sig);
/********* START VARIABLES **********/
-
-#ifndef USE_BUFFEREVENTS
int global_read_bucket; /**< Max number of bytes I can read this second. */
int global_write_bucket; /**< Max number of bytes I can write this second. */
@@ -119,7 +111,6 @@ static int stats_prev_global_read_bucket;
/** What was the write bucket before the last second_elapsed_callback() call?
* (used to determine how many bytes we've written). */
static int stats_prev_global_write_bucket;
-#endif
/* DOCDOC stats_prev_n_read */
static uint64_t stats_prev_n_read = 0;
@@ -172,9 +163,6 @@ static int can_complete_circuits = 0;
/** How often do we check for router descriptors that we should download
* when we have enough directory info? */
#define LAZY_DESCRIPTOR_RETRY_INTERVAL (60)
-/** How often do we 'forgive' undownloadable router descriptors and attempt
- * to download them again? */
-#define DESCRIPTOR_FAILURE_RESET_INTERVAL (60*60)
/** Decides our behavior when no logs are configured/before any
* logs have been configured. For 0, we log notice to stdout as normal.
@@ -191,28 +179,6 @@ int quiet_level = 0;
*
****************************************************************************/
-#if defined(_WIN32) && defined(USE_BUFFEREVENTS)
-/** Remove the kernel-space send and receive buffers for <b>s</b>. For use
- * with IOCP only. */
-static int
-set_buffer_lengths_to_zero(tor_socket_t s)
-{
- int zero = 0;
- int r = 0;
- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&zero,
- (socklen_t)sizeof(zero))) {
- log_warn(LD_NET, "Unable to clear SO_SNDBUF");
- r = -1;
- }
- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&zero,
- (socklen_t)sizeof(zero))) {
- log_warn(LD_NET, "Unable to clear SO_RCVBUF");
- r = -1;
- }
- return r;
-}
-#endif
-
/** Return 1 if we have successfully built a circuit, and nothing has changed
* to make us think that maybe we can't.
*/
@@ -255,66 +221,9 @@ connection_add_impl(connection_t *conn, int is_connecting)
conn->conn_array_index = smartlist_len(connection_array);
smartlist_add(connection_array, conn);
-#ifdef USE_BUFFEREVENTS
- if (connection_type_uses_bufferevent(conn)) {
- if (SOCKET_OK(conn->s) && !conn->linked) {
-
-#ifdef _WIN32
- if (tor_libevent_using_iocp_bufferevents() &&
- get_options()->UserspaceIOCPBuffers) {
- set_buffer_lengths_to_zero(conn->s);
- }
-#endif
-
- conn->bufev = bufferevent_socket_new(
- tor_libevent_get_base(),
- conn->s,
- BEV_OPT_DEFER_CALLBACKS);
- if (!conn->bufev) {
- log_warn(LD_BUG, "Unable to create socket bufferevent");
- smartlist_del(connection_array, conn->conn_array_index);
- conn->conn_array_index = -1;
- return -1;
- }
- if (is_connecting) {
- /* Put the bufferevent into a "connecting" state so that we'll get
- * a "connected" event callback on successful write. */
- bufferevent_socket_connect(conn->bufev, NULL, 0);
- }
- connection_configure_bufferevent_callbacks(conn);
- } else if (conn->linked && conn->linked_conn &&
- connection_type_uses_bufferevent(conn->linked_conn)) {
- tor_assert(!(SOCKET_OK(conn->s)));
- if (!conn->bufev) {
- struct bufferevent *pair[2] = { NULL, NULL };
- if (bufferevent_pair_new(tor_libevent_get_base(),
- BEV_OPT_DEFER_CALLBACKS,
- pair) < 0) {
- log_warn(LD_BUG, "Unable to create bufferevent pair");
- smartlist_del(connection_array, conn->conn_array_index);
- conn->conn_array_index = -1;
- return -1;
- }
- tor_assert(pair[0]);
- conn->bufev = pair[0];
- conn->linked_conn->bufev = pair[1];
- } /* else the other side already was added, and got a bufferevent_pair */
- connection_configure_bufferevent_callbacks(conn);
- } else {
- tor_assert(!conn->linked);
- }
-
- if (conn->bufev)
- tor_assert(conn->inbuf == NULL);
-
- if (conn->linked_conn && conn->linked_conn->bufev)
- tor_assert(conn->linked_conn->inbuf == NULL);
- }
-#else
(void) is_connecting;
-#endif
- if (!HAS_BUFFEREVENT(conn) && (SOCKET_OK(conn->s) || conn->linked)) {
+ if (SOCKET_OK(conn->s) || conn->linked) {
conn->read_event = tor_event_new(tor_libevent_get_base(),
conn->s, EV_READ|EV_PERSIST, conn_read_callback, conn);
conn->write_event = tor_event_new(tor_libevent_get_base(),
@@ -343,12 +252,6 @@ connection_unregister_events(connection_t *conn)
log_warn(LD_BUG, "Error removing write event for %d", (int)conn->s);
tor_free(conn->write_event);
}
-#ifdef USE_BUFFEREVENTS
- if (conn->bufev) {
- bufferevent_free(conn->bufev);
- conn->bufev = NULL;
- }
-#endif
if (conn->type == CONN_TYPE_AP_DNS_LISTENER) {
dnsserv_close_listener(conn);
}
@@ -479,8 +382,8 @@ connection_in_array(connection_t *conn)
/** Set <b>*array</b> to an array of all connections. <b>*array</b> must not
* be modified.
*/
-smartlist_t *
-get_connection_array(void)
+MOCK_IMPL(smartlist_t *,
+get_connection_array, (void))
{
if (!connection_array)
connection_array = smartlist_new();
@@ -508,17 +411,6 @@ get_bytes_written,(void))
void
connection_watch_events(connection_t *conn, watchable_events_t events)
{
- IF_HAS_BUFFEREVENT(conn, {
- short ev = ((short)events) & (EV_READ|EV_WRITE);
- short old_ev = bufferevent_get_enabled(conn->bufev);
- if ((ev & ~old_ev) != 0) {
- bufferevent_enable(conn->bufev, ev);
- }
- if ((old_ev & ~ev) != 0) {
- bufferevent_disable(conn->bufev, old_ev & ~ev);
- }
- return;
- });
if (events & READ_EVENT)
connection_start_reading(conn);
else
@@ -536,9 +428,6 @@ connection_is_reading(connection_t *conn)
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn,
- return (bufferevent_get_enabled(conn->bufev) & EV_READ) != 0;
- );
return conn->reading_from_linked_conn ||
(conn->read_event && event_pending(conn->read_event, EV_READ, NULL));
}
@@ -589,11 +478,6 @@ connection_stop_reading,(connection_t *conn))
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn, {
- bufferevent_disable(conn->bufev, EV_READ);
- return;
- });
-
if (connection_check_event(conn, conn->read_event) < 0) {
return;
}
@@ -616,11 +500,6 @@ connection_start_reading,(connection_t *conn))
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn, {
- bufferevent_enable(conn->bufev, EV_READ);
- return;
- });
-
if (connection_check_event(conn, conn->read_event) < 0) {
return;
}
@@ -644,10 +523,6 @@ connection_is_writing(connection_t *conn)
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn,
- return (bufferevent_get_enabled(conn->bufev) & EV_WRITE) != 0;
- );
-
return conn->writing_to_linked_conn ||
(conn->write_event && event_pending(conn->write_event, EV_WRITE, NULL));
}
@@ -658,11 +533,6 @@ connection_stop_writing,(connection_t *conn))
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn, {
- bufferevent_disable(conn->bufev, EV_WRITE);
- return;
- });
-
if (connection_check_event(conn, conn->write_event) < 0) {
return;
}
@@ -686,11 +556,6 @@ connection_start_writing,(connection_t *conn))
{
tor_assert(conn);
- IF_HAS_BUFFEREVENT(conn, {
- bufferevent_enable(conn->bufev, EV_WRITE);
- return;
- });
-
if (connection_check_event(conn, conn->write_event) < 0) {
return;
}
@@ -796,6 +661,23 @@ close_closeable_connections(void)
}
}
+/** Count moribund connections for the OOS handler */
+MOCK_IMPL(int,
+connection_count_moribund, (void))
+{
+ int moribund = 0;
+
+ /*
+ * Count things we'll try to kill when close_closeable_connections()
+ * runs next.
+ */
+ SMARTLIST_FOREACH_BEGIN(closeable_connection_lst, connection_t *, conn) {
+ if (SOCKET_OK(conn->s) && connection_is_moribund(conn)) ++moribund;
+ } SMARTLIST_FOREACH_END(conn);
+
+ return moribund;
+}
+
/** Libevent callback: this gets invoked when (connection_t*)<b>conn</b> has
* some data to read. */
static void
@@ -888,21 +770,6 @@ conn_close_if_marked(int i)
assert_connection_ok(conn, now);
/* assert_all_pending_dns_resolves_ok(); */
-#ifdef USE_BUFFEREVENTS
- if (conn->bufev) {
- if (conn->hold_open_until_flushed &&
- evbuffer_get_length(bufferevent_get_output(conn->bufev))) {
- /* don't close yet. */
- return 0;
- }
- if (conn->linked_conn && ! conn->linked_conn->marked_for_close) {
- /* We need to do this explicitly so that the linked connection
- * notices that there was an EOF. */
- bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED);
- }
- }
-#endif
-
log_debug(LD_NET,"Cleaning up connection (fd "TOR_SOCKET_T_FORMAT").",
conn->s);
@@ -912,7 +779,6 @@ conn_close_if_marked(int i)
if (conn->proxy_state == PROXY_INFANT)
log_failed_proxy_connection(conn);
- IF_HAS_BUFFEREVENT(conn, goto unlink);
if ((SOCKET_OK(conn->s) || conn->linked_conn) &&
connection_wants_to_flush(conn)) {
/* s == -1 means it's an incomplete edge connection, or that the socket
@@ -971,7 +837,7 @@ conn_close_if_marked(int i)
connection_stop_writing(conn);
}
if (connection_is_reading(conn)) {
- /* XXXX024 We should make this code unreachable; if a connection is
+ /* XXXX+ We should make this code unreachable; if a connection is
* marked for close and flushing, there is no point in reading to it
* at all. Further, checking at this point is a bit of a hack: it
* would make much more sense to react in
@@ -997,9 +863,6 @@ conn_close_if_marked(int i)
}
}
-#ifdef USE_BUFFEREVENTS
- unlink:
-#endif
connection_unlink(conn); /* unlink, remove, free */
return 1;
}
@@ -1145,11 +1008,7 @@ run_connection_housekeeping(int i, time_t now)
the connection or send a keepalive, depending. */
or_conn = TO_OR_CONN(conn);
-#ifdef USE_BUFFEREVENTS
- tor_assert(conn->bufev);
-#else
tor_assert(conn->outbuf);
-#endif
chan = TLS_CHAN_TO_BASE(or_conn->chan);
tor_assert(chan);
@@ -1259,7 +1118,6 @@ static int periodic_events_initialized = 0;
CALLBACK(rotate_onion_key);
CALLBACK(check_ed_keys);
CALLBACK(launch_descriptor_fetches);
-CALLBACK(reset_descriptor_failures);
CALLBACK(rotate_x509_certificate);
CALLBACK(add_entropy);
CALLBACK(launch_reachability_tests);
@@ -1291,7 +1149,6 @@ static periodic_event_item_t periodic_events[] = {
CALLBACK(rotate_onion_key),
CALLBACK(check_ed_keys),
CALLBACK(launch_descriptor_fetches),
- CALLBACK(reset_descriptor_failures),
CALLBACK(rotate_x509_certificate),
CALLBACK(add_entropy),
CALLBACK(launch_reachability_tests),
@@ -1624,15 +1481,6 @@ launch_descriptor_fetches_callback(time_t now, const or_options_t *options)
}
static int
-reset_descriptor_failures_callback(time_t now, const or_options_t *options)
-{
- (void)now;
- (void)options;
- router_reset_descriptor_download_failures();
- return DESCRIPTOR_FAILURE_RESET_INTERVAL;
-}
-
-static int
rotate_x509_certificate_callback(time_t now, const or_options_t *options)
{
static int first = 1;
@@ -1647,8 +1495,8 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options)
* TLS context. */
log_info(LD_GENERAL,"Rotating tls context.");
if (router_initialize_tls_context() < 0) {
- log_warn(LD_BUG, "Error reinitializing TLS context");
- tor_assert(0);
+ log_err(LD_BUG, "Error reinitializing TLS context");
+ tor_assert_unreached();
}
/* We also make sure to rotate the TLS connections themselves if they've
@@ -2069,25 +1917,10 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
/* the second has rolled over. check more stuff. */
seconds_elapsed = current_second ? (int)(now - current_second) : 0;
-#ifdef USE_BUFFEREVENTS
- {
- uint64_t cur_read,cur_written;
- connection_get_rate_limit_totals(&cur_read, &cur_written);
- bytes_written = (size_t)(cur_written - stats_prev_n_written);
- bytes_read = (size_t)(cur_read - stats_prev_n_read);
- stats_n_bytes_read += bytes_read;
- stats_n_bytes_written += bytes_written;
- if (accounting_is_enabled(options) && seconds_elapsed >= 0)
- accounting_add_bytes(bytes_read, bytes_written, seconds_elapsed);
- stats_prev_n_written = cur_written;
- stats_prev_n_read = cur_read;
- }
-#else
bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read);
bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written);
stats_prev_n_read = stats_n_bytes_read;
stats_prev_n_written = stats_n_bytes_written;
-#endif
control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written);
control_event_stream_bandwidth_used();
@@ -2159,12 +1992,11 @@ systemd_watchdog_callback(periodic_timer_t *timer, void *arg)
}
#endif
-#ifndef USE_BUFFEREVENTS
/** Timer: used to invoke refill_callback(). */
static periodic_timer_t *refill_timer = NULL;
/** Libevent callback: invoked periodically to refill token buckets
- * and count r/w bytes. It is only used when bufferevents are disabled. */
+ * and count r/w bytes. */
static void
refill_callback(periodic_timer_t *timer, void *arg)
{
@@ -2208,7 +2040,6 @@ refill_callback(periodic_timer_t *timer, void *arg)
current_millisecond = now; /* remember what time it is, for next time */
}
-#endif
#ifndef _WIN32
/** Called when a possibly ignorable libevent error occurs; ensures that we
@@ -2235,8 +2066,8 @@ ip_address_changed(int at_interface)
{
const or_options_t *options = get_options();
int server = server_mode(options);
- int exit_reject_private = (server && options->ExitRelay
- && options->ExitPolicyRejectPrivate);
+ int exit_reject_interfaces = (server && options->ExitRelay
+ && options->ExitPolicyRejectLocalInterfaces);
if (at_interface) {
if (! server) {
@@ -2254,8 +2085,8 @@ ip_address_changed(int at_interface)
}
/* Exit relays incorporate interface addresses in their exit policies when
- * ExitPolicyRejectPrivate is set */
- if (exit_reject_private || (server && !at_interface)) {
+ * ExitPolicyRejectLocalInterfaces is set */
+ if (exit_reject_interfaces || (server && !at_interface)) {
mark_my_descriptor_dirty("IP address changed");
}
@@ -2384,13 +2215,6 @@ do_main_loop(void)
}
}
-#ifdef USE_BUFFEREVENTS
- log_warn(LD_GENERAL, "Tor was compiled with the --enable-bufferevents "
- "option. This is still experimental, and might cause strange "
- "bugs. If you want a more stable Tor, be sure to build without "
- "--enable-bufferevents.");
-#endif
-
handle_signals(1);
/* load the private keys, if we're supposed to have them, and set up the
@@ -2404,10 +2228,8 @@ do_main_loop(void)
/* Set up our buckets */
connection_bucket_init();
-#ifndef USE_BUFFEREVENTS
stats_prev_global_read_bucket = global_read_bucket;
stats_prev_global_write_bucket = global_write_bucket;
-#endif
/* initialize the bootstrap status events to know we're starting up */
control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0);
@@ -2462,6 +2284,13 @@ do_main_loop(void)
cpu_init();
}
+ /* Setup shared random protocol subsystem. */
+ if (authdir_mode_publishes_statuses(get_options())) {
+ if (sr_init(1) < 0) {
+ return -1;
+ }
+ }
+
/* set up once-a-second callback. */
if (! second_timer) {
struct timeval one_second;
@@ -2497,7 +2326,6 @@ do_main_loop(void)
}
#endif
-#ifndef USE_BUFFEREVENTS
if (!refill_timer) {
struct timeval refill_interval;
int msecs = get_options()->TokenBucketRefillInterval;
@@ -2511,7 +2339,6 @@ do_main_loop(void)
NULL);
tor_assert(refill_timer);
}
-#endif
#ifdef HAVE_SYSTEMD
{
@@ -2573,9 +2400,7 @@ run_main_loop_once(void)
return -1;
#endif
} else {
- if (ERRNO_IS_EINPROGRESS(e))
- log_warn(LD_BUG,
- "libevent call returned EINPROGRESS? Please report.");
+ tor_assert_nonfatal_once(! ERRNO_IS_EINPROGRESS(e));
log_debug(LD_NET,"libevent call interrupted.");
/* You can't trust the results of this poll(). Go back to the
* top of the big for loop. */
@@ -2706,9 +2531,6 @@ get_uptime,(void))
return stats_n_seconds_working;
}
-extern uint64_t rephist_total_alloc;
-extern uint32_t rephist_total_num;
-
/**
* Write current memory usage information to the log.
*/
@@ -3010,14 +2832,9 @@ tor_init(int argc, char *argv[])
{
const char *version = get_version();
- const char *bev_str =
-#ifdef USE_BUFFEREVENTS
- "(with bufferevents) ";
-#else
- "";
-#endif
- log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s, "
- "OpenSSL %s and Zlib %s.", version, bev_str,
+
+ log_notice(LD_GENERAL, "Tor %s running on %s with Libevent %s, "
+ "OpenSSL %s and Zlib %s.", version,
get_uname(),
tor_libevent_get_version_str(),
crypto_openssl_get_version_str(),
@@ -3032,11 +2849,6 @@ tor_init(int argc, char *argv[])
"Expect more bugs than usual.");
}
-#ifdef NON_ANONYMOUS_MODE_ENABLED
- log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a "
- "non-anonymous mode. It will provide NO ANONYMITY.");
-#endif
-
if (network_init()<0) {
log_err(LD_BUG,"Error initializing network; exiting.");
return -1;
@@ -3048,15 +2860,18 @@ tor_init(int argc, char *argv[])
return -1;
}
+ /* The options are now initialised */
+ const or_options_t *options = get_options();
+
#ifndef _WIN32
if (geteuid()==0)
log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, "
"and you probably shouldn't.");
#endif
- if (crypto_global_init(get_options()->HardwareAccel,
- get_options()->AccelName,
- get_options()->AccelDir)) {
+ if (crypto_global_init(options->HardwareAccel,
+ options->AccelName,
+ options->AccelDir)) {
log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
return -1;
}
@@ -3065,6 +2880,9 @@ tor_init(int argc, char *argv[])
log_warn(LD_NET, "Problem initializing libevent RNG.");
}
+ /* Scan/clean unparseable descroptors; after reading config */
+ routerparse_init();
+
return 0;
}
@@ -3166,9 +2984,11 @@ tor_free_all(int postfork)
scheduler_free_all();
nodelist_free_all();
microdesc_free_all();
+ routerparse_free_all();
ext_orport_free_all();
control_free_all();
sandbox_free_getaddrinfo_cache();
+ protover_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();
@@ -3189,9 +3009,7 @@ tor_free_all(int postfork)
smartlist_free(active_linked_connection_lst);
periodic_timer_free(second_timer);
teardown_periodic_events();
-#ifndef USE_BUFFEREVENTS
periodic_timer_free(refill_timer);
-#endif
if (!postfork) {
release_lockfile();
@@ -3230,6 +3048,9 @@ tor_cleanup(void)
accounting_record_bandwidth_usage(now, get_or_state());
or_state_mark_dirty(get_or_state(), 0); /* force an immediate save. */
or_state_save(now);
+ if (authdir_mode(options)) {
+ sr_save_and_cleanup();
+ }
if (authdir_mode_tests_reachability(options))
rep_hist_record_mtbf_data(now, 0);
keypin_close_journal();
@@ -3388,6 +3209,7 @@ sandbox_init_filter(void)
OPEN_DATADIR_SUFFIX("cached-extrainfo.new", ".tmp");
OPEN_DATADIR("cached-extrainfo.tmp.tmp");
OPEN_DATADIR_SUFFIX("state", ".tmp");
+ OPEN_DATADIR_SUFFIX("sr-state", ".tmp");
OPEN_DATADIR_SUFFIX("unparseable-desc", ".tmp");
OPEN_DATADIR_SUFFIX("v3-status-votes", ".tmp");
OPEN_DATADIR("key-pinning-journal");
@@ -3440,6 +3262,7 @@ sandbox_init_filter(void)
RENAME_SUFFIX("cached-extrainfo", ".new");
RENAME_SUFFIX("cached-extrainfo.new", ".tmp");
RENAME_SUFFIX("state", ".tmp");
+ RENAME_SUFFIX("sr-state", ".tmp");
RENAME_SUFFIX("unparseable-desc", ".tmp");
RENAME_SUFFIX("v3-status-votes", ".tmp");
@@ -3603,6 +3426,11 @@ tor_main(int argc, char *argv[])
int result = 0;
#ifdef _WIN32
+#ifndef HeapEnableTerminationOnCorruption
+#define HeapEnableTerminationOnCorruption 1
+#endif
+ /* On heap corruption, just give up; don't try to play along. */
+ HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
/* Call SetProcessDEPPolicy to permanently enable DEP.
The function will not resolve on earlier versions of Windows,
and failure is not dangerous. */
@@ -3611,7 +3439,10 @@ tor_main(int argc, char *argv[])
typedef BOOL (WINAPI *PSETDEP)(DWORD);
PSETDEP setdeppolicy = (PSETDEP)GetProcAddress(hMod,
"SetProcessDEPPolicy");
- if (setdeppolicy) setdeppolicy(1); /* PROCESS_DEP_ENABLE */
+ if (setdeppolicy) {
+ /* PROCESS_DEP_ENABLE | PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION */
+ setdeppolicy(3);
+ }
}
#endif
@@ -3653,6 +3484,8 @@ tor_main(int argc, char *argv[])
#endif
}
+ monotime_init();
+
switch (get_options()->command) {
case CMD_RUN_TOR:
#ifdef NT_SERVICE
diff --git a/src/or/main.h b/src/or/main.h
index 6949376f3e..07b22598b1 100644
--- a/src/or/main.h
+++ b/src/or/main.h
@@ -25,7 +25,7 @@ int connection_in_array(connection_t *conn);
void add_connection_to_closeable_list(connection_t *conn);
int connection_is_on_closeable_list(connection_t *conn);
-smartlist_t *get_connection_array(void);
+MOCK_DECL(smartlist_t *, get_connection_array, (void));
MOCK_DECL(uint64_t,get_bytes_read,(void));
MOCK_DECL(uint64_t,get_bytes_written,(void));
@@ -49,6 +49,8 @@ void tell_event_loop_to_finish(void);
void connection_stop_reading_from_linked_conn(connection_t *conn);
+MOCK_DECL(int, connection_count_moribund, (void));
+
void directory_all_unreachable(time_t now);
void directory_info_has_arrived(time_t now, int from_cache, int suppress_logs);
@@ -77,6 +79,14 @@ int tor_main(int argc, char *argv[]);
int do_main_loop(void);
int tor_init(int argc, char **argv);
+extern time_t time_of_process_start;
+extern long stats_n_seconds_working;
+extern int quiet_level;
+extern int global_read_bucket;
+extern int global_write_bucket;
+extern int global_relayed_read_bucket;
+extern int global_relayed_write_bucket;
+
#ifdef MAIN_PRIVATE
STATIC void init_connection_lists(void);
STATIC void close_closeable_connections(void);
diff --git a/src/or/microdesc.c b/src/or/microdesc.c
index 5b5c29a6d2..a81dc54628 100644
--- a/src/or/microdesc.c
+++ b/src/or/microdesc.c
@@ -69,7 +69,7 @@ microdesc_eq_(microdesc_t *a, microdesc_t *b)
}
HT_PROTOTYPE(microdesc_map, microdesc_t, node,
- microdesc_hash_, microdesc_eq_);
+ microdesc_hash_, microdesc_eq_)
HT_GENERATE2(microdesc_map, microdesc_t, node,
microdesc_hash_, microdesc_eq_, 0.6,
tor_reallocarray_, tor_free_)
@@ -108,6 +108,7 @@ dump_microdescriptor(int fd, microdesc_t *md, size_t *annotation_len_out)
md->off = tor_fd_getpos(fd);
written = write_all(fd, md->body, md->bodylen, 0);
if (written != (ssize_t)md->bodylen) {
+ written = written < 0 ? 0 : written;
log_warn(LD_DIR,
"Couldn't dump microdescriptor (wrote %ld out of %lu): %s",
(long)written, (unsigned long)md->bodylen,
@@ -925,7 +926,7 @@ we_use_microdescriptors_for_circuits(const or_options_t *options)
return 0;
/* Otherwise, we decide that we'll use microdescriptors iff we are
* not a server, and we're not autofetching everything. */
- /* XXX023 what does not being a server have to do with it? also there's
+ /* XXXX++ what does not being a server have to do with it? also there's
* a partitioning issue here where bridges differ from clients. */
ret = !server_mode(options) && !options->FetchUselessDescriptors;
}
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 1cedfef9b7..991cf80121 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -28,11 +28,14 @@
#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
+#include "protover.h"
#include "relay.h"
#include "router.h"
#include "routerlist.h"
#include "routerparse.h"
+#include "shared_random.h"
#include "transports.h"
+#include "torcert.h"
/** Map from lowercase nickname to identity digest of named server, if any. */
static strmap_t *named_server_map = NULL;
@@ -40,14 +43,6 @@ static strmap_t *named_server_map = NULL;
* as unnamed for some server in the consensus. */
static strmap_t *unnamed_server_map = NULL;
-/** Most recently received and validated v3 consensus network status,
- * of whichever type we are using for our own circuits. This will be the same
- * as one of current_ns_consensus or current_md_consensus.
- */
-#define current_consensus \
- (we_use_microdescriptors_for_circuits(get_options()) ? \
- current_md_consensus : current_ns_consensus)
-
/** Most recently received and validated v3 "ns"-flavored consensus network
* status. */
static networkstatus_t *current_ns_consensus = NULL;
@@ -86,9 +81,9 @@ static time_t time_to_download_next_consensus[N_CONSENSUS_FLAVORS];
static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS] =
{
{ 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER,
- DL_SCHED_INCREMENT_FAILURE },
+ DL_SCHED_INCREMENT_FAILURE, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 },
{ 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER,
- DL_SCHED_INCREMENT_FAILURE },
+ DL_SCHED_INCREMENT_FAILURE, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 },
};
#define N_CONSENSUS_BOOTSTRAP_SCHEDULES 2
@@ -105,10 +100,10 @@ static download_status_t
consensus_bootstrap_dl_status[N_CONSENSUS_BOOTSTRAP_SCHEDULES] =
{
{ 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_AUTHORITY,
- DL_SCHED_INCREMENT_ATTEMPT },
+ DL_SCHED_INCREMENT_ATTEMPT, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 },
/* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */
{ 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER,
- DL_SCHED_INCREMENT_ATTEMPT },
+ DL_SCHED_INCREMENT_ATTEMPT, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 },
};
/** True iff we have logged a warning about this OR's version being older than
@@ -122,16 +117,17 @@ static void routerstatus_list_update_named_server_map(void);
static void update_consensus_bootstrap_multiple_downloads(
time_t now,
const or_options_t *options);
+static int networkstatus_check_required_protocols(const networkstatus_t *ns,
+ int client_mode,
+ char **warning_out);
/** Forget that we've warned about anything networkstatus-related, so we will
* give fresh warnings if the same behavior happens again. */
void
networkstatus_reset_warnings(void)
{
- if (current_consensus) {
- SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node,
- node->name_lookup_warned = 0);
- }
+ SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node,
+ node->name_lookup_warned = 0);
have_warned_about_old_version = 0;
have_warned_about_new_version = 0;
@@ -145,6 +141,9 @@ networkstatus_reset_download_failures(void)
{
int i;
+ log_debug(LD_GENERAL,
+ "In networkstatus_reset_download_failures()");
+
for (i=0; i < N_CONSENSUS_FLAVORS; ++i)
download_status_reset(&consensus_dl_status[i]);
@@ -173,7 +172,7 @@ router_reload_consensus_networkstatus(void)
}
s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
if (s) {
- if (networkstatus_set_current_consensus(s, flavor, flags) < -1) {
+ if (networkstatus_set_current_consensus(s, flavor, flags, NULL) < -1) {
log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
flavor, filename);
}
@@ -191,7 +190,8 @@ router_reload_consensus_networkstatus(void)
s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
if (s) {
if (networkstatus_set_current_consensus(s, flavor,
- flags|NSSET_WAS_WAITING_FOR_CERTS)) {
+ flags|NSSET_WAS_WAITING_FOR_CERTS,
+ NULL)) {
log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
flavor, filename);
}
@@ -200,7 +200,7 @@ router_reload_consensus_networkstatus(void)
tor_free(filename);
}
- if (!current_consensus) {
+ if (!networkstatus_get_latest_consensus()) {
if (!named_server_map)
named_server_map = strmap_new();
if (!unnamed_server_map)
@@ -223,6 +223,7 @@ vote_routerstatus_free(vote_routerstatus_t *rs)
if (!rs)
return;
tor_free(rs->version);
+ tor_free(rs->protocols);
tor_free(rs->status.exitsummary);
for (h = rs->microdesc; h; h = next) {
tor_free(h->microdesc_hash_line);
@@ -269,6 +270,11 @@ networkstatus_vote_free(networkstatus_t *ns)
tor_free(ns->client_versions);
tor_free(ns->server_versions);
+ tor_free(ns->recommended_client_protocols);
+ tor_free(ns->recommended_relay_protocols);
+ tor_free(ns->required_client_protocols);
+ tor_free(ns->required_relay_protocols);
+
if (ns->known_flags) {
SMARTLIST_FOREACH(ns->known_flags, char *, c, tor_free(c));
smartlist_free(ns->known_flags);
@@ -319,6 +325,14 @@ networkstatus_vote_free(networkstatus_t *ns)
digestmap_free(ns->desc_digest_map, NULL);
+ if (ns->sr_info.commits) {
+ SMARTLIST_FOREACH(ns->sr_info.commits, sr_commit_t *, c,
+ sr_commit_free(c));
+ smartlist_free(ns->sr_info.commits);
+ }
+ tor_free(ns->sr_info.previous_srv);
+ tor_free(ns->sr_info.current_srv);
+
memwipe(ns, 11, sizeof(*ns));
tor_free(ns);
}
@@ -633,7 +647,7 @@ router_get_mutable_consensus_status_by_descriptor_digest,(
const char *digest))
{
if (!consensus)
- consensus = current_consensus;
+ consensus = networkstatus_get_latest_consensus();
if (!consensus)
return NULL;
if (!consensus->desc_digest_map) {
@@ -658,6 +672,43 @@ router_get_consensus_status_by_descriptor_digest(networkstatus_t *consensus,
consensus, digest);
}
+/** Return a smartlist of all router descriptor digests in a consensus */
+static smartlist_t *
+router_get_descriptor_digests_in_consensus(networkstatus_t *consensus)
+{
+ smartlist_t *result = smartlist_new();
+ digestmap_iter_t *i;
+ const char *digest;
+ void *rs;
+ char *digest_tmp;
+
+ for (i = digestmap_iter_init(consensus->desc_digest_map);
+ !(digestmap_iter_done(i));
+ i = digestmap_iter_next(consensus->desc_digest_map, i)) {
+ digestmap_iter_get(i, &digest, &rs);
+ digest_tmp = tor_malloc(DIGEST_LEN);
+ memcpy(digest_tmp, digest, DIGEST_LEN);
+ smartlist_add(result, digest_tmp);
+ }
+
+ return result;
+}
+
+/** Return a smartlist of all router descriptor digests in the current
+ * consensus */
+MOCK_IMPL(smartlist_t *,
+router_get_descriptor_digests,(void))
+{
+ smartlist_t *result = NULL;
+
+ if (current_ns_consensus) {
+ result =
+ router_get_descriptor_digests_in_consensus(current_ns_consensus);
+ }
+
+ return result;
+}
+
/** Given the digest of a router descriptor, return its current download
* status, or NULL if the digest is unrecognized. */
MOCK_IMPL(download_status_t *,
@@ -677,9 +728,11 @@ router_get_dl_status_by_descriptor_digest,(const char *d))
routerstatus_t *
router_get_mutable_consensus_status_by_id(const char *digest)
{
- if (!current_consensus)
+ const networkstatus_t *ns = networkstatus_get_latest_consensus();
+ if (!ns)
return NULL;
- return smartlist_bsearch(current_consensus->routerstatus_list, digest,
+ smartlist_t *rslist = ns->routerstatus_list;
+ return smartlist_bsearch(rslist, digest,
compare_digest_to_routerstatus_entry);
}
@@ -762,9 +815,15 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor)
* fetching certs before we check whether there is a better one? */
#define DELAY_WHILE_FETCHING_CERTS (20*60)
+/** What is the minimum time we need to have waited fetching certs, before we
+ * increment the consensus download schedule on failure? */
+#define MIN_DELAY_FOR_FETCH_CERT_STATUS_FAILURE (1*60)
+
/* Check if a downloaded consensus flavor should still wait for certificates
- * to download now.
- * If so, return 1. If not, fail dls and return 0. */
+ * to download now. If we decide not to wait, check if enough time has passed
+ * to consider the certificate download failure a separate failure. If so,
+ * fail dls.
+ * If waiting for certificates to download, return 1. If not, return 0. */
static int
check_consensus_waiting_for_certs(int flavor, time_t now,
download_status_t *dls)
@@ -778,11 +837,14 @@ check_consensus_waiting_for_certs(int flavor, time_t now,
waiting = &consensus_waiting_for_certs[flavor];
if (waiting->consensus) {
/* XXXX make sure this doesn't delay sane downloads. */
- if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) {
+ if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now &&
+ waiting->consensus->valid_until > now) {
return 1;
} else {
if (!waiting->dl_failed) {
- download_status_failed(dls, 0);
+ if (waiting->set_at + MIN_DELAY_FOR_FETCH_CERT_STATUS_FAILURE > now) {
+ download_status_failed(dls, 0);
+ }
waiting->dl_failed=1;
}
}
@@ -827,7 +889,7 @@ update_consensus_networkstatus_downloads(time_t now)
resource = networkstatus_get_flavor_name(i);
/* Check if we already have enough connections in progress */
- if (we_are_bootstrapping) {
+ if (we_are_bootstrapping && use_multi_conn) {
max_in_progress_conns =
options->ClientBootstrapConsensusMaxInProgressTries;
}
@@ -1160,13 +1222,13 @@ update_certificate_downloads(time_t now)
for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
if (consensus_waiting_for_certs[i].consensus)
authority_certs_fetch_missing(consensus_waiting_for_certs[i].consensus,
- now);
+ now, NULL);
}
if (current_ns_consensus)
- authority_certs_fetch_missing(current_ns_consensus, now);
+ authority_certs_fetch_missing(current_ns_consensus, now, NULL);
if (current_md_consensus)
- authority_certs_fetch_missing(current_md_consensus, now);
+ authority_certs_fetch_missing(current_md_consensus, now, NULL);
}
/** Return 1 if we have a consensus but we don't have enough certificates
@@ -1178,12 +1240,61 @@ consensus_is_waiting_for_certs(void)
? 1 : 0;
}
+/** Look up the currently active (depending on bootstrap status) download
+ * status for this consensus flavor and return a pointer to it.
+ */
+MOCK_IMPL(download_status_t *,
+networkstatus_get_dl_status_by_flavor,(consensus_flavor_t flavor))
+{
+ download_status_t *dl = NULL;
+ const int we_are_bootstrapping =
+ networkstatus_consensus_is_bootstrapping(time(NULL));
+
+ if ((int)flavor <= N_CONSENSUS_FLAVORS) {
+ dl = &((we_are_bootstrapping ?
+ consensus_bootstrap_dl_status : consensus_dl_status)[flavor]);
+ }
+
+ return dl;
+}
+
+/** Look up the bootstrap download status for this consensus flavor
+ * and return a pointer to it. */
+MOCK_IMPL(download_status_t *,
+networkstatus_get_dl_status_by_flavor_bootstrap,(consensus_flavor_t flavor))
+{
+ download_status_t *dl = NULL;
+
+ if ((int)flavor <= N_CONSENSUS_FLAVORS) {
+ dl = &(consensus_bootstrap_dl_status[flavor]);
+ }
+
+ return dl;
+}
+
+/** Look up the running (non-bootstrap) download status for this consensus
+ * flavor and return a pointer to it. */
+MOCK_IMPL(download_status_t *,
+networkstatus_get_dl_status_by_flavor_running,(consensus_flavor_t flavor))
+{
+ download_status_t *dl = NULL;
+
+ if ((int)flavor <= N_CONSENSUS_FLAVORS) {
+ dl = &(consensus_dl_status[flavor]);
+ }
+
+ return dl;
+}
+
/** Return the most recent consensus that we have downloaded, or NULL if we
* don't have one. */
-networkstatus_t *
-networkstatus_get_latest_consensus(void)
+MOCK_IMPL(networkstatus_t *,
+networkstatus_get_latest_consensus,(void))
{
- return current_consensus;
+ if (we_use_microdescriptors_for_circuits(get_options()))
+ return current_md_consensus;
+ else
+ return current_ns_consensus;
}
/** Return the latest consensus we have whose flavor matches <b>f</b>, or NULL
@@ -1203,13 +1314,13 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f))
/** Return the most recent consensus that we have downloaded, or NULL if it is
* no longer live. */
-networkstatus_t *
-networkstatus_get_live_consensus(time_t now)
+MOCK_IMPL(networkstatus_t *,
+networkstatus_get_live_consensus,(time_t now))
{
- if (current_consensus &&
- current_consensus->valid_after <= now &&
- now <= current_consensus->valid_until)
- return current_consensus;
+ if (networkstatus_get_latest_consensus() &&
+ networkstatus_get_latest_consensus()->valid_after <= now &&
+ now <= networkstatus_get_latest_consensus()->valid_until)
+ return networkstatus_get_latest_consensus();
else
return NULL;
}
@@ -1349,8 +1460,9 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b)
a->is_valid != b->is_valid ||
a->is_possible_guard != b->is_possible_guard ||
a->is_bad_exit != b->is_bad_exit ||
- a->is_hs_dir != b->is_hs_dir ||
- a->version_known != b->version_known;
+ a->is_hs_dir != b->is_hs_dir;
+ // XXXX this function needs a huge refactoring; it has gotten out
+ // XXXX of sync with routerstatus_t, and it will do so again.
}
/** Notify controllers of any router status entries that changed between
@@ -1449,6 +1561,66 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
}
#endif //TOR_UNIT_TESTS
+/**
+ * Return true if any option is set in <b>options</b> to make us behave
+ * as a client.
+ *
+ * XXXX If we need this elsewhere at any point, we should make it nonstatic
+ * XXXX and move it into another file.
+ */
+static int
+any_client_port_set(const or_options_t *options)
+{
+ return (options->SocksPort_set ||
+ options->TransPort_set ||
+ options->NATDPort_set ||
+ options->ControlPort_set ||
+ options->DNSPort_set);
+}
+
+/**
+ * Helper for handle_missing_protocol_warning: handles either the
+ * client case (if <b>is_client</b> is set) or the server case otherwise.
+ */
+static void
+handle_missing_protocol_warning_impl(const networkstatus_t *c,
+ int is_client)
+{
+ char *protocol_warning = NULL;
+
+ int should_exit = networkstatus_check_required_protocols(c,
+ is_client,
+ &protocol_warning);
+ if (protocol_warning) {
+ tor_log(should_exit ? LOG_ERR : LOG_WARN,
+ LD_GENERAL,
+ "%s", protocol_warning);
+ }
+ if (should_exit) {
+ tor_assert_nonfatal(protocol_warning);
+ }
+ tor_free(protocol_warning);
+ if (should_exit)
+ exit(1);
+}
+
+/** Called when we have received a networkstatus <b>c</b>. If there are
+ * any _required_ protocols we are missing, log an error and exit
+ * immediately. If there are any _recommended_ protocols we are missing,
+ * warn. */
+static void
+handle_missing_protocol_warning(const networkstatus_t *c,
+ const or_options_t *options)
+{
+ const int is_server = server_mode(options);
+ const int is_client = any_client_port_set(options) || !is_server;
+
+ if (is_server)
+ handle_missing_protocol_warning_impl(c, 0);
+ if (is_client)
+ handle_missing_protocol_warning_impl(c, 1);
+}
+
/** Try to replace the current cached v3 networkstatus with the one in
* <b>consensus</b>. If we don't have enough certificates to validate it,
* store it in consensus_waiting_for_certs and launch a certificate fetch.
@@ -1460,6 +1632,10 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
* If flags & NSSET_ACCEPT_OBSOLETE, then we should be willing to take this
* consensus, even if it comes from many days in the past.
*
+ * If source_dir is non-NULL, it's the identity digest for a directory that
+ * we've just successfully retrieved a consensus or certificates from, so try
+ * it first to fetch any missing certificates.
+ *
* Return 0 on success, <0 on failure. On failure, caller should increment
* the failure count as appropriate.
*
@@ -1469,7 +1645,8 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
int
networkstatus_set_current_consensus(const char *consensus,
const char *flavor,
- unsigned flags)
+ unsigned flags,
+ const char *source_dir)
{
networkstatus_t *c=NULL;
int r, result = -1;
@@ -1487,6 +1664,7 @@ networkstatus_set_current_consensus(const char *consensus,
time_t current_valid_after = 0;
int free_consensus = 1; /* Free 'c' at the end of the function */
int old_ewma_enabled;
+ int checked_protocols_already = 0;
if (flav < 0) {
/* XXXX we don't handle unrecognized flavors yet. */
@@ -1502,6 +1680,16 @@ networkstatus_set_current_consensus(const char *consensus,
goto done;
}
+ 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
+ * 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. */
+ checked_protocols_already = 1;
+ handle_missing_protocol_warning(c, options);
+ }
+
if ((int)c->flavor != flav) {
/* This wasn't the flavor we thought we were getting. */
if (require_flavor) {
@@ -1591,7 +1779,7 @@ networkstatus_set_current_consensus(const char *consensus,
write_str_to_file(unverified_fname, consensus, 0);
}
if (dl_certs)
- authority_certs_fetch_missing(c, now);
+ authority_certs_fetch_missing(c, now, source_dir);
/* This case is not a success or a failure until we get the certs
* or fail to get the certs. */
result = 0;
@@ -1627,20 +1815,25 @@ networkstatus_set_current_consensus(const char *consensus,
if (!from_cache && flav == usable_consensus_flavor())
control_event_client_status(LOG_NOTICE, "CONSENSUS_ARRIVED");
+ if (!checked_protocols_already) {
+ handle_missing_protocol_warning(c, options);
+ }
+
/* Are we missing any certificates at all? */
if (r != 1 && dl_certs)
- authority_certs_fetch_missing(c, now);
+ authority_certs_fetch_missing(c, now, source_dir);
const int is_usable_flavor = flav == usable_consensus_flavor();
if (is_usable_flavor) {
- notify_control_networkstatus_changed(current_consensus, c);
+ notify_control_networkstatus_changed(
+ networkstatus_get_latest_consensus(), c);
}
if (flav == FLAV_NS) {
if (current_ns_consensus) {
networkstatus_copy_old_consensus_info(c, current_ns_consensus);
networkstatus_vote_free(current_ns_consensus);
- /* Defensive programming : we should set current_consensus very soon,
+ /* Defensive programming : we should set current_ns_consensus very soon
* but we're about to call some stuff in the meantime, and leaving this
* dangling pointer around has proven to be trouble. */
current_ns_consensus = NULL;
@@ -1688,7 +1881,7 @@ networkstatus_set_current_consensus(const char *consensus,
/* Update ewma and adjust policy if needed; first cache the old value */
old_ewma_enabled = cell_ewma_enabled();
/* Change the cell EWMA settings */
- cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus());
+ cell_ewma_set_scale_factor(options, c);
/* If we just enabled ewma, set the cmux policy on all active channels */
if (cell_ewma_enabled() && !old_ewma_enabled) {
channel_set_cmux_policy_everywhere(&ewma_policy);
@@ -1697,12 +1890,12 @@ networkstatus_set_current_consensus(const char *consensus,
channel_set_cmux_policy_everywhere(NULL);
}
- /* XXXX024 this call might be unnecessary here: can changing the
+ /* XXXX this call might be unnecessary here: can changing the
* current consensus really alter our view of any OR's rate limits? */
connection_or_update_token_buckets(get_connection_array(), options);
- circuit_build_times_new_consensus_params(get_circuit_build_times_mutable(),
- current_consensus);
+ circuit_build_times_new_consensus_params(
+ get_circuit_build_times_mutable(), c);
}
/* Reset the failure count only if this consensus is actually valid. */
@@ -1754,9 +1947,14 @@ networkstatus_set_current_consensus(const char *consensus,
}
/** Called when we have gotten more certificates: see whether we can
- * now verify a pending consensus. */
+ * now verify a pending consensus.
+ *
+ * If source_dir is non-NULL, it's the identity digest for a directory that
+ * we've just successfully retrieved certificates from, so try it first to
+ * fetch any missing certificates.
+ */
void
-networkstatus_note_certs_arrived(void)
+networkstatus_note_certs_arrived(const char *source_dir)
{
int i;
for (i=0; i<N_CONSENSUS_FLAVORS; ++i) {
@@ -1768,7 +1966,8 @@ networkstatus_note_certs_arrived(void)
if (!networkstatus_set_current_consensus(
waiting_body,
networkstatus_get_flavor_name(i),
- NSSET_WAS_WAITING_FOR_CERTS)) {
+ NSSET_WAS_WAITING_FOR_CERTS,
+ source_dir)) {
tor_free(waiting_body);
}
}
@@ -1846,15 +2045,16 @@ routers_update_all_from_networkstatus(time_t now, int dir_version)
static void
routerstatus_list_update_named_server_map(void)
{
- if (!current_consensus)
+ 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_FOREACH_BEGIN(current_consensus->routerstatus_list,
- const routerstatus_t *, rs) {
+ 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));
@@ -1874,7 +2074,7 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers,
{
const or_options_t *options = get_options();
int authdir = authdir_mode_v3(options);
- networkstatus_t *ns = current_consensus;
+ networkstatus_t *ns = networkstatus_get_latest_consensus();
if (!ns || !smartlist_len(ns->routerstatus_list))
return;
@@ -1943,7 +2143,7 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs)
char *
networkstatus_getinfo_helper_single(const routerstatus_t *rs)
{
- return routerstatus_format_entry(rs, NULL, NS_CONTROL_PORT, NULL);
+ return routerstatus_format_entry(rs, NULL, NULL, NS_CONTROL_PORT, NULL);
}
/** Alloc and return a string describing routerstatuses for the most
@@ -2160,15 +2360,16 @@ client_would_use_router(const routerstatus_t *rs, time_t now,
* But, if we want to have a complete list, fetch it anyway. */
return 0;
}
- if (rs->published_on + options->TestingEstimatedDescriptorPropagationTime
- > now) {
- /* Most caches probably don't have this descriptor yet. */
- return 0;
- }
if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) {
/* We'd drop it immediately for being too old. */
return 0;
}
+ if (!routerstatus_version_supports_extend2_cells(rs, 1)) {
+ /* We'd ignore it because it doesn't support EXTEND2 cells.
+ * If we don't know the version, download the descriptor so we can
+ * check if it supports EXTEND2 cells and ntor. */
+ return 0;
+ }
return 1;
}
@@ -2184,14 +2385,14 @@ getinfo_helper_networkstatus(control_connection_t *conn,
const routerstatus_t *status;
(void) conn;
- if (!current_consensus) {
+ if (!networkstatus_get_latest_consensus()) {
*answer = tor_strdup("");
return 0;
}
if (!strcmp(question, "ns/all")) {
smartlist_t *statuses = smartlist_new();
- SMARTLIST_FOREACH(current_consensus->routerstatus_list,
+ SMARTLIST_FOREACH(networkstatus_get_latest_consensus()->routerstatus_list,
const routerstatus_t *, rs,
{
smartlist_add(statuses, networkstatus_getinfo_helper_single(rs));
@@ -2206,7 +2407,7 @@ getinfo_helper_networkstatus(control_connection_t *conn,
if (*q == '$')
++q;
- if (base16_decode(d, DIGEST_LEN, q, strlen(q))) {
+ if (base16_decode(d, DIGEST_LEN, q, strlen(q)) != DIGEST_LEN) {
*errmsg = "Data not decodeable as hex";
return -1;
}
@@ -2252,6 +2453,56 @@ getinfo_helper_networkstatus(control_connection_t *conn,
return 0;
}
+/** Check whether the networkstatus <b>ns</b> lists any protocol
+ * versions as "required" or "recommended" that we do not support. If
+ * so, set *<b>warning_out</b> to a newly allocated string describing
+ * the problem.
+ *
+ * Return 1 if we should exit, 0 if we should not. */
+int
+networkstatus_check_required_protocols(const networkstatus_t *ns,
+ int client_mode,
+ char **warning_out)
+{
+ const char *func = client_mode ? "client" : "relay";
+ const char *required, *recommended;
+ char *missing = NULL;
+
+ tor_assert(warning_out);
+
+ if (client_mode) {
+ required = ns->required_client_protocols;
+ recommended = ns->recommended_client_protocols;
+ } else {
+ required = ns->required_relay_protocols;
+ recommended = ns->recommended_relay_protocols;
+ }
+
+ if (!protover_all_supported(required, &missing)) {
+ tor_asprintf(warning_out, "At least one protocol listed as required in "
+ "the consensus is not supported by this version of Tor. "
+ "You should upgrade. This version of Tor will not work as a "
+ "%s on the Tor network. The missing protocols are: %s",
+ func, missing);
+ tor_free(missing);
+ return 1;
+ }
+
+ if (! protover_all_supported(recommended, &missing)) {
+ tor_asprintf(warning_out, "At least one protocol listed as recommended in "
+ "the consensus is not supported by this version of Tor. "
+ "You should upgrade. This version of Tor will eventually "
+ "stop working as a %s on the Tor network. The missing "
+ "protocols are: %s",
+ func, missing);
+ tor_free(missing);
+ }
+
+ tor_assert_nonfatal(missing == NULL);
+
+ return 0;
+}
+
/** Free all storage held locally in this module. */
void
networkstatus_free_all(void)
diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h
index ac93e5de91..71f36b69ed 100644
--- a/src/or/networkstatus.h
+++ b/src/or/networkstatus.h
@@ -38,6 +38,17 @@ routerstatus_t *networkstatus_vote_find_mutable_entry(networkstatus_t *ns,
int networkstatus_vote_find_entry_idx(networkstatus_t *ns,
const char *digest, int *found_out);
+MOCK_DECL(download_status_t *,
+ networkstatus_get_dl_status_by_flavor,
+ (consensus_flavor_t flavor));
+MOCK_DECL(download_status_t *,
+ networkstatus_get_dl_status_by_flavor_bootstrap,
+ (consensus_flavor_t flavor));
+MOCK_DECL(download_status_t *,
+ networkstatus_get_dl_status_by_flavor_running,
+ (consensus_flavor_t flavor));
+
+MOCK_DECL(smartlist_t *, router_get_descriptor_digests, (void));
MOCK_DECL(download_status_t *,router_get_dl_status_by_descriptor_digest,
(const char *d));
@@ -64,10 +75,10 @@ void update_certificate_downloads(time_t now);
int consensus_is_waiting_for_certs(void);
int client_would_use_router(const routerstatus_t *rs, time_t now,
const or_options_t *options);
-networkstatus_t *networkstatus_get_latest_consensus(void);
+MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void));
MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor,
(consensus_flavor_t f));
-networkstatus_t *networkstatus_get_live_consensus(time_t now);
+MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now));
networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now,
int flavor);
MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now));
@@ -84,8 +95,9 @@ int networkstatus_consensus_is_already_downloading(const char *resource);
#define NSSET_REQUIRE_FLAVOR 16
int networkstatus_set_current_consensus(const char *consensus,
const char *flavor,
- unsigned flags);
-void networkstatus_note_certs_arrived(void);
+ unsigned flags,
+ const char *source_dir);
+void networkstatus_note_certs_arrived(const char *source_dir);
void routers_update_all_from_networkstatus(time_t now, int dir_version);
void routers_update_status_from_consensus_networkstatus(smartlist_t *routers,
int reset_failures);
diff --git a/src/or/nodelist.c b/src/or/nodelist.c
index 89b5355c8d..0e9a651818 100644
--- a/src/or/nodelist.c
+++ b/src/or/nodelist.c
@@ -77,7 +77,7 @@ node_id_eq(const node_t *node1, const node_t *node2)
return tor_memeq(node1->identity, node2->identity, DIGEST_LEN);
}
-HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq);
+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_)
@@ -542,13 +542,15 @@ node_get_by_hex_id(const char *hex_id)
MOCK_IMPL(const node_t *,
node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
{
- const node_t *node;
if (!the_nodelist)
return NULL;
/* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */
- if ((node = node_get_by_hex_id(nickname)) != NULL)
+ {
+ const node_t *node;
+ if ((node = node_get_by_hex_id(nickname)) != NULL)
return node;
+ }
if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME))
return NULL;
@@ -1029,6 +1031,14 @@ node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
+ /* Clear the address, as a safety precaution if calling functions ignore the
+ * return value */
+ tor_addr_make_null(&ap_out->addr, AF_INET);
+ ap_out->port = 0;
+
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address. */
+
RETURN_IPV4_AP(node->ri, or_port, ap_out);
RETURN_IPV4_AP(node->rs, or_port, ap_out);
/* Microdescriptors only have an IPv6 address */
@@ -1171,14 +1181,38 @@ node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out)
}
}
+/** Return true iff <b>md</b> has a curve25519 onion key.
+ * Use node_has_curve25519_onion_key() instead of calling this directly. */
+static int
+microdesc_has_curve25519_onion_key(const microdesc_t *md)
+{
+ if (!md) {
+ return 0;
+ }
+
+ if (!md->onion_curve25519_pkey) {
+ return 0;
+ }
+
+ if (tor_mem_is_zero((const char*)md->onion_curve25519_pkey->public_key,
+ CURVE25519_PUBKEY_LEN)) {
+ return 0;
+ }
+
+ return 1;
+}
+
/** Return true iff <b>node</b> has a curve25519 onion key. */
int
node_has_curve25519_onion_key(const node_t *node)
{
+ if (!node)
+ return 0;
+
if (node->ri)
- return node->ri->onion_curve25519_pkey != NULL;
+ return routerinfo_has_curve25519_onion_key(node->ri);
else if (node->md)
- return node->md->onion_curve25519_pkey != NULL;
+ return microdesc_has_curve25519_onion_key(node->md);
else
return 0;
}
diff --git a/src/or/ntmain.c b/src/or/ntmain.c
index ded0e0d307..0e6f296d24 100644
--- a/src/or/ntmain.c
+++ b/src/or/ntmain.c
@@ -6,7 +6,15 @@
/**
* \file ntmain.c
*
- * \brief Entry points for running/configuring Tor as Windows Service.
+ * \brief Entry points for running/configuring Tor as a Windows Service.
+ *
+ * Windows Services expect to be registered with the operating system, and to
+ * have entry points for starting, stopping, and monitoring them. This module
+ * implements those entry points so that a tor relay or client or hidden
+ * service can run as a Windows service. Therefore, this module
+ * is only compiled when building for Windows.
+ *
+ * Warning: this module is not very well tested or very well maintained.
*/
#ifdef _WIN32
@@ -16,11 +24,7 @@
#include "main.h"
#include "ntmain.h"
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
#include <windows.h>
#define GENSRV_SERVICENAME "tor"
@@ -289,6 +293,7 @@ nt_service_body(int argc, char **argv)
* event loop */
service_status.dwCurrentState = SERVICE_RUNNING;
service_fns.SetServiceStatus_fn(hStatus, &service_status);
+ set_main_thread();
do_main_loop();
tor_cleanup();
}
diff --git a/src/or/onion.c b/src/or/onion.c
index d6ef3673dd..4b803a785c 100644
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@ -8,9 +8,62 @@
* \file onion.c
* \brief Functions to queue create cells, wrap the various onionskin types,
* and parse and create the CREATE cell and its allies.
+ *
+ * This module has a few functions, all related to the CREATE/CREATED
+ * handshake that we use on links in order to create a circuit, and the
+ * related EXTEND/EXTENDED handshake that we use over circuits in order to
+ * extend them an additional hop.
+ *
+ * In this module, we provide a set of abstractions to create a uniform
+ * interface over the three circuit extension handshakes that Tor has used
+ * over the years (TAP, CREATE_FAST, and ntor). These handshakes are
+ * implemented in onion_tap.c, onion_fast.c, and onion_ntor.c respectively.
+ *
+ * All[*] of these handshakes follow a similar pattern: a client, knowing
+ * some key from the relay it wants to extend through, generates the
+ * first part of a handshake. A relay receives that handshake, and sends
+ * a reply. Once the client handles the reply, it knows that it is
+ * talking to the right relay, and it shares some freshly negotiated key
+ * material with that relay.
+ *
+ * We sometimes call the client's part of the handshake an "onionskin".
+ * We do this because historically, Onion Routing used a multi-layer
+ * structure called an "onion" to construct circuits. Each layer of the
+ * onion contained key material chosen by the client, the identity of
+ * the next relay in the circuit, and a smaller onion, encrypted with
+ * the key of the next relay. When we changed Tor to use a telescoping
+ * circuit extension design, it corresponded to sending each layer of the
+ * onion separately -- as a series of onionskins.
+ *
+ * Clients invoke these functions when creating or extending a circuit,
+ * from circuitbuild.c.
+ *
+ * Relays invoke these functions when they receive a CREATE or EXTEND
+ * cell in command.c or relay.c, in order to queue the pending request.
+ * They also invoke them from cpuworker.c, which handles dispatching
+ * onionskin requests to different worker threads.
+ *
+ * <br>
+ *
+ * This module also handles:
+ * <ul>
+ * <li> Queueing incoming onionskins on the relay side before passing
+ * them to worker threads.
+ * <li>Expiring onionskins on the relay side if they have waited for
+ * too long.
+ * <li>Packaging private keys on the server side in order to pass
+ * them to worker threads.
+ * <li>Encoding and decoding CREATE, CREATED, CREATE2, and CREATED2 cells.
+ * <li>Encoding and decodign EXTEND, EXTENDED, EXTEND2, and EXTENDED2
+ * relay cells.
+ * </ul>
+ *
+ * [*] The CREATE_FAST handshake is weaker than described here; see
+ * onion_fast.c for more information.
**/
#include "or.h"
+#include "circuitbuild.h"
#include "circuitlist.h"
#include "config.h"
#include "cpuworker.h"
@@ -38,9 +91,9 @@ typedef struct onion_queue_t {
/** Array of queues of circuits waiting for CPU workers. An element is NULL
* if that queue is empty.*/
-TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t)
- ol_list[MAX_ONION_HANDSHAKE_TYPE+1] = {
- TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */
+static TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t)
+ ol_list[MAX_ONION_HANDSHAKE_TYPE+1] =
+{ TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */
TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]), /* fast */
TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]), /* ntor */
};
@@ -51,7 +104,7 @@ static int ol_entries[MAX_ONION_HANDSHAKE_TYPE+1];
static int num_ntors_per_tap(void);
static void onion_queue_entry_remove(onion_queue_t *victim);
-/* XXXX024 Check lengths vs MAX_ONIONSKIN_{CHALLENGE,REPLY}_LEN.
+/* XXXX Check lengths vs MAX_ONIONSKIN_{CHALLENGE,REPLY}_LEN.
*
* (By which I think I meant, "make sure that no
* X_ONIONSKIN_CHALLENGE/REPLY_LEN is greater than
@@ -130,9 +183,12 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin)
time_t now = time(NULL);
if (onionskin->handshake_type > MAX_ONION_HANDSHAKE_TYPE) {
+ /* LCOV_EXCL_START
+ * We should have rejected this far before this point */
log_warn(LD_BUG, "Handshake %d out of range! Dropping.",
onionskin->handshake_type);
return -1;
+ /* LCOV_EXCL_STOP */
}
tmp = tor_malloc_zero(sizeof(onion_queue_t));
@@ -179,7 +235,9 @@ onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin)
onion_queue_entry_remove(head);
log_info(LD_CIRC,
"Circuit create request is too old; canceling due to overload.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT);
+ if (! TO_CIRCUIT(circ)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT);
+ }
}
return 0;
}
@@ -305,10 +363,13 @@ static void
onion_queue_entry_remove(onion_queue_t *victim)
{
if (victim->handshake_type > MAX_ONION_HANDSHAKE_TYPE) {
+ /* LCOV_EXCL_START
+ * We should have rejected this far before this point */
log_warn(LD_BUG, "Handshake %d out of range! Dropping.",
victim->handshake_type);
/* XXX leaks */
return;
+ /* LCOV_EXCL_STOP */
}
TOR_TAILQ_REMOVE(&ol_list[victim->handshake_type], victim, next);
@@ -391,9 +452,12 @@ onion_handshake_state_release(onion_handshake_state_t *state)
state->u.ntor = NULL;
break;
default:
+ /* LCOV_EXCL_START
+ * This state should not even exist. */
log_warn(LD_BUG, "called with unknown handshake state type %d",
(int)state->tag);
tor_fragile_assert();
+ /* LCOV_EXCL_STOP */
}
}
@@ -429,8 +493,7 @@ onion_skin_create(int type,
r = CREATE_FAST_LEN;
break;
case ONION_HANDSHAKE_TYPE_NTOR:
- if (tor_mem_is_zero((const char*)node->curve25519_onion_key.public_key,
- CURVE25519_PUBKEY_LEN))
+ if (!extend_info_supports_ntor(node))
return -1;
if (onion_skin_ntor_create((const uint8_t*)node->identity_digest,
&node->curve25519_onion_key,
@@ -441,9 +504,12 @@ onion_skin_create(int type,
r = NTOR_ONIONSKIN_LEN;
break;
default:
+ /* LCOV_EXCL_START
+ * We should never try to create an impossible handshake type. */
log_warn(LD_BUG, "called with unknown handshake state type %d", type);
tor_fragile_assert();
r = -1;
+ /* LCOV_EXCL_STOP */
}
if (r > 0)
@@ -512,9 +578,12 @@ onion_skin_server_handshake(int type,
}
break;
default:
+ /* LCOV_EXCL_START
+ * We should have rejected this far before this point */
log_warn(LD_BUG, "called with unknown handshake state type %d", type);
tor_fragile_assert();
return -1;
+ /* LCOV_EXCL_STOP */
}
return r;
@@ -527,7 +596,7 @@ onion_skin_server_handshake(int type,
* <b>rend_authenticator_out</b> to the "KH" field that can be used to
* establish introduction points at this hop, and return 0. On failure,
* return -1, and set *msg_out to an error message if this is worth
- * complaining to the usre about. */
+ * complaining to the user about. */
int
onion_skin_client_handshake(int type,
const onion_handshake_state_t *handshake_state,
diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c
index 1f79860596..8dcbfe22d8 100644
--- a/src/or/onion_fast.c
+++ b/src/or/onion_fast.c
@@ -7,6 +7,24 @@
/**
* \file onion_fast.c
* \brief Functions implement the CREATE_FAST circuit handshake.
+ *
+ * The "CREATE_FAST" handshake is an unauthenticated, non-forward-secure
+ * key derivation mechanism based on SHA1. We used to use it for the
+ * first hop of each circuit, since the TAP handshake provided no
+ * additional security beyond the security already provided by the TLS
+ * handshake [*].
+ *
+ * When we switched to ntor, we deprecated CREATE_FAST, since ntor is
+ * stronger than our TLS handshake was, and fast enough to not be worrisome.
+ *
+ * This handshake, like the other circuit-extension handshakes, is
+ * invoked from onion.c.
+ *
+ * [*]Actually, it's possible that TAP _was_ a little better than TLS with
+ * RSA1024 certificates and EDH1024 for forward secrecy, if you
+ * hypothesize an adversary who can compute discrete logarithms on a
+ * small number of targetted DH1024 fields, but who can't break all that
+ * many RSA1024 keys.
**/
#include "or.h"
@@ -59,8 +77,8 @@ fast_server_handshake(const uint8_t *key_in, /* DIGEST_LEN bytes */
memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN);
out_len = key_out_len+DIGEST_LEN;
out = tor_malloc(out_len);
- if (crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len)) {
- goto done;
+ if (BUG(crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len))) {
+ goto done; // LCOV_EXCL_LINE
}
memcpy(handshake_reply_out+DIGEST_LEN, out, DIGEST_LEN);
memcpy(key_out, out+DIGEST_LEN, key_out_len);
@@ -100,10 +118,12 @@ fast_client_handshake(const fast_handshake_state_t *handshake_state,
memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN);
out_len = key_out_len+DIGEST_LEN;
out = tor_malloc(out_len);
- if (crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len)) {
+ if (BUG(crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len))) {
+ /* LCOV_EXCL_START */
if (msg_out)
*msg_out = "Failed to expand key material";
goto done;
+ /* LCOV_EXCL_STOP */
}
if (tor_memneq(out, handshake_reply_out+DIGEST_LEN, DIGEST_LEN)) {
/* H(K) does *not* match. Something fishy. */
diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c
index 9f97a4cfbe..ded97ee73d 100644
--- a/src/or/onion_ntor.c
+++ b/src/or/onion_ntor.c
@@ -5,6 +5,17 @@
* \file onion_ntor.c
*
* \brief Implementation for the ntor handshake.
+ *
+ * The ntor circuit-extension handshake was developed as a replacement
+ * for the old TAP handshake. It uses Elliptic-curve Diffie-Hellman and
+ * a hash function in order to perform a one-way authenticated key
+ * exchange. The ntor handshake is meant to replace the old "TAP"
+ * handshake.
+ *
+ * We instantiate ntor with curve25519, HMAC-SHA256, and HKDF.
+ *
+ * This handshake, like the other circuit-extension handshakes, is
+ * invoked from onion.c.
*/
#include "orconfig.h"
@@ -47,7 +58,7 @@ typedef struct tweakset_t {
} tweakset_t;
/** The tweaks to be used with our handshake. */
-const tweakset_t proto1_tweaks = {
+static const tweakset_t proto1_tweaks = {
#define PROTOID "ntor-curve25519-sha256-1"
#define PROTOID_LEN 24
PROTOID ":mac",
@@ -85,8 +96,13 @@ onion_skin_ntor_create(const uint8_t *router_id,
memcpy(state->router_id, router_id, DIGEST_LEN);
memcpy(&state->pubkey_B, router_key, sizeof(curve25519_public_key_t));
if (curve25519_secret_key_generate(&state->seckey_x, 0) < 0) {
+ /* LCOV_EXCL_START
+ * Secret key generation should be unable to fail when the key isn't
+ * marked as "extra-strong" */
+ tor_assert_nonfatal_unreached();
tor_free(state);
return -1;
+ /* LCOV_EXCL_STOP */
}
curve25519_public_key_generate(&state->pubkey_X, &state->seckey_x);
diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c
index bfd472351f..2769300945 100644
--- a/src/or/onion_tap.c
+++ b/src/or/onion_tap.c
@@ -9,10 +9,22 @@
* \brief Functions to implement the original Tor circuit extension handshake
* (a.k.a TAP).
*
+ * The "TAP" handshake is the first one that was widely used in Tor: It
+ * combines RSA1024-OAEP and AES128-CTR to perform a hybrid encryption over
+ * the first message DH1024 key exchange. (The RSA-encrypted part of the
+ * encryption is authenticated; the AES-encrypted part isn't. This was
+ * not a smart choice.)
+ *
* We didn't call it "TAP" ourselves -- Ian Goldberg named it in "On the
* Security of the Tor Authentication Protocol". (Spoiler: it's secure, but
* its security is kind of fragile and implementation dependent. Never modify
* this implementation without reading and understanding that paper at least.)
+ *
+ * We have deprecated TAP since the ntor handshake came into general use. It
+ * is still used for hidden service IP and RP connections, however.
+ *
+ * This handshake, like the other circuit-extension handshakes, is
+ * invoked from onion.c.
**/
#include "or.h"
@@ -74,9 +86,13 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key,
return 0;
err:
+ /* LCOV_EXCL_START
+ * We only get here if RSA encryption fails or DH keygen fails. Those
+ * shouldn't be possible. */
memwipe(challenge, 0, sizeof(challenge));
if (dh) crypto_dh_free(dh);
return -1;
+ /* LCOV_EXCL_STOP */
}
/** Given an encrypted DH public key as generated by onion_skin_create,
@@ -130,12 +146,20 @@ onion_skin_TAP_server_handshake(
dh = crypto_dh_new(DH_TYPE_CIRCUIT);
if (!dh) {
+ /* LCOV_EXCL_START
+ * Failure to allocate a DH key should be impossible.
+ */
log_warn(LD_BUG, "Couldn't allocate DH key");
goto err;
+ /* LCOV_EXCL_STOP */
}
if (crypto_dh_get_public(dh, handshake_reply_out, DH_KEY_LEN)) {
+ /* LCOV_EXCL_START
+ * This can only fail if the length of the key we just allocated is too
+ * big. That should be impossible. */
log_info(LD_GENERAL, "crypto_dh_get_public failed.");
goto err;
+ /* LCOV_EXCP_STOP */
}
key_material_len = DIGEST_LEN+key_out_len;
diff --git a/src/or/or.h b/src/or/or.h
index c26052809c..75a02a531e 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -14,14 +14,6 @@
#include "orconfig.h"
-#if defined(__clang_analyzer__) || defined(__COVERITY__)
-/* If we're building for a static analysis, turn on all the off-by-default
- * features. */
-#ifndef INSTRUMENT_DOWNLOADS
-#define INSTRUMENT_DOWNLOADS 1
-#endif
-#endif
-
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
@@ -74,12 +66,6 @@
#include <windows.h>
#endif
-#ifdef USE_BUFFEREVENTS
-#include <event2/bufferevent.h>
-#include <event2/buffer.h>
-#include <event2/util.h>
-#endif
-
#include "crypto.h"
#include "crypto_format.h"
#include "tortls.h"
@@ -157,18 +143,6 @@
/** Maximum size of a single extrainfo document, as above. */
#define MAX_EXTRAINFO_UPLOAD_SIZE 50000
-/** How long do we keep DNS cache entries before purging them (regardless of
- * their TTL)? */
-#define MAX_DNS_ENTRY_AGE (30*60)
-/** How long do we cache/tell clients to cache DNS records when no TTL is
- * known? */
-#define DEFAULT_DNS_TTL (30*60)
-/** How long can a TTL be before we stop believing it? */
-#define MAX_DNS_TTL (3*60*60)
-/** How small can a TTL be before we stop believing it? Provides rudimentary
- * pinning. */
-#define MIN_DNS_TTL 60
-
/** How often do we rotate onion keys? */
#define MIN_ONION_KEY_LIFETIME (7*24*60*60)
/** How often do we rotate TLS contexts? */
@@ -784,7 +758,7 @@ typedef enum rend_auth_type_t {
/** Client-side configuration of authorization for a hidden service. */
typedef struct rend_service_authorization_t {
- char descriptor_cookie[REND_DESC_COOKIE_LEN];
+ uint8_t descriptor_cookie[REND_DESC_COOKIE_LEN];
char onion_address[REND_SERVICE_ADDRESS_LEN+1];
rend_auth_type_t auth_type;
} rend_service_authorization_t;
@@ -1160,11 +1134,8 @@ typedef struct {
typedef struct buf_t buf_t;
typedef struct socks_request_t socks_request_t;
-#ifdef USE_BUFFEREVENTS
-#define generic_buffer_t struct evbuffer
-#else
-#define generic_buffer_t buf_t
-#endif
+
+#define buf_t buf_t
typedef struct entry_port_cfg_t {
/* Client port types (socks, dns, trans, natd) only: */
@@ -1183,6 +1154,8 @@ typedef struct entry_port_cfg_t {
unsigned int ipv4_traffic : 1;
unsigned int ipv6_traffic : 1;
unsigned int prefer_ipv6 : 1;
+ unsigned int dns_request : 1;
+ unsigned int onion_traffic : 1;
/** For a socks listener: should we cache IPv4/IPv6 DNS information that
* exit nodes tell us?
@@ -1303,27 +1276,28 @@ typedef struct connection_t {
time_t timestamp_lastwritten; /**< When was the last time libevent said we
* could write? */
-#ifdef USE_BUFFEREVENTS
- struct bufferevent *bufev; /**< A Libevent buffered IO structure. */
-#endif
-
time_t timestamp_created; /**< When was this connection_t created? */
- /* XXXX_IP6 make this IPv6-capable */
int socket_family; /**< Address family of this connection's socket. Usually
- * AF_INET, but it can also be AF_UNIX, or in the future
- * AF_INET6 */
- tor_addr_t addr; /**< IP of the other side of the connection; used to
- * identify routers, along with port. */
- uint16_t port; /**< If non-zero, port on the other end
- * of the connection. */
+ * AF_INET, but it can also be AF_UNIX, or AF_INET6 */
+ tor_addr_t addr; /**< IP that socket "s" is directly connected to;
+ * may be the IP address for a proxy or pluggable transport,
+ * see "address" for the address of the final destination.
+ */
+ uint16_t port; /**< If non-zero, port that socket "s" is directly connected
+ * to; may be the port for a proxy or pluggable transport,
+ * see "address" for the port at the final destination. */
uint16_t marked_for_close; /**< Should we close this conn on the next
* iteration of the main loop? (If true, holds
* the line number where this connection was
* marked.) */
const char *marked_for_close_file; /**< For debugging: in which file were
* we marked for close? */
- char *address; /**< FQDN (or IP) of the other end.
+ char *address; /**< FQDN (or IP) and port of the final destination for this
+ * connection; this is always the remote address, it is
+ * passed to a proxy or pluggable transport if one in use.
+ * See "addr" and "port" for the address that socket "s" is
+ * directly connected to.
* strdup into this, because free_connection() frees it. */
/** Another connection that's connected to this one in lieu of a socket. */
struct connection_t *linked_conn;
@@ -1541,17 +1515,10 @@ typedef struct or_connection_t {
/* bandwidth* and *_bucket only used by ORs in OPEN state: */
int bandwidthrate; /**< Bytes/s added to the bucket. (OPEN ORs only.) */
int bandwidthburst; /**< Max bucket size for this conn. (OPEN ORs only.) */
-#ifndef USE_BUFFEREVENTS
int read_bucket; /**< When this hits 0, stop receiving. Every second we
* add 'bandwidthrate' to this, capping it at
* bandwidthburst. (OPEN ORs only) */
int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */
-#else
- /** A rate-limiting configuration object to determine how this connection
- * set its read- and write- limits. */
- /* XXXX we could share this among all connections. */
- struct ev_token_bucket_cfg *bucket_cfg;
-#endif
struct or_connection_t *next_with_same_id; /**< Next connection with same
* identity digest as this one. */
@@ -1657,11 +1624,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. */
- generic_buffer_t *pending_optimistic_data;
+ 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. */
- generic_buffer_t *sending_optimistic_data;
+ 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. */
@@ -1864,51 +1831,6 @@ static inline listener_connection_t *TO_LISTENER_CONN(connection_t *c)
return DOWNCAST(listener_connection_t, c);
}
-/* Conditional macros to help write code that works whether bufferevents are
- disabled or not.
-
- We can't just write:
- if (conn->bufev) {
- do bufferevent stuff;
- } else {
- do other stuff;
- }
- because the bufferevent stuff won't even compile unless we have a fairly
- new version of Libevent. Instead, we say:
- IF_HAS_BUFFEREVENT(conn, { do_bufferevent_stuff } );
- or:
- IF_HAS_BUFFEREVENT(conn, {
- do bufferevent stuff;
- }) ELSE_IF_NO_BUFFEREVENT {
- do non-bufferevent stuff;
- }
- If we're compiling with bufferevent support, then the macros expand more or
- less to:
- if (conn->bufev) {
- do_bufferevent_stuff;
- } else {
- do non-bufferevent stuff;
- }
- and if we aren't using bufferevents, they expand more or less to:
- { do non-bufferevent stuff; }
-*/
-#ifdef USE_BUFFEREVENTS
-#define HAS_BUFFEREVENT(c) (((c)->bufev) != NULL)
-#define IF_HAS_BUFFEREVENT(c, stmt) \
- if ((c)->bufev) do { \
- stmt ; \
- } while (0)
-#define ELSE_IF_NO_BUFFEREVENT ; else
-#define IF_HAS_NO_BUFFEREVENT(c) \
- if (NULL == (c)->bufev)
-#else
-#define HAS_BUFFEREVENT(c) (0)
-#define IF_HAS_BUFFEREVENT(c, stmt) (void)0
-#define ELSE_IF_NO_BUFFEREVENT ;
-#define IF_HAS_NO_BUFFEREVENT(c) \
- if (1)
-#endif
-
/** What action type does an address policy indicate: accept or reject? */
typedef enum {
ADDR_POLICY_ACCEPT=1,
@@ -2005,6 +1927,15 @@ typedef enum {
#define download_schedule_increment_bitfield_t \
ENUM_BF(download_schedule_increment_t)
+/** Enumeration: do we want to use the random exponential backoff
+ * mechanism? */
+typedef enum {
+ DL_SCHED_DETERMINISTIC = 0,
+ DL_SCHED_RANDOM_EXPONENTIAL = 1,
+} download_schedule_backoff_t;
+#define download_schedule_backoff_bitfield_t \
+ ENUM_BF(download_schedule_backoff_t)
+
/** Information about our plans for retrying downloads for a downloadable
* directory object.
* Each type of downloadable directory object has a corresponding retry
@@ -2051,6 +1982,15 @@ typedef struct download_status_t {
download_schedule_increment_bitfield_t increment_on : 1; /**< does this
* schedule increment on each attempt,
* or after each failure? */
+ download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the
+ * deterministic schedule, or random
+ * exponential backoffs? */
+ uint8_t last_backoff_position; /**< number of attempts/failures, depending
+ * on increment_on, when we last recalculated
+ * the delay. Only updated if backoff
+ * == 1. */
+ int last_delay_used; /**< last delay used for random exponential backoff;
+ * only updated if backoff == 1 */
} download_status_t;
/** If n_download_failures is this high, the download can never happen. */
@@ -2138,6 +2078,9 @@ typedef struct {
char *platform; /**< What software/operating system is this OR using? */
+ char *protocol_list; /**< Encoded list of subprotocol versions supported
+ * by this OR */
+
/* link info */
uint32_t bandwidthrate; /**< How many bytes does this OR add to its token
* bucket per second? */
@@ -2255,14 +2198,13 @@ typedef struct routerstatus_t {
unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort
* or it claims to accept tunnelled dir requests.
*/
- /** True iff we know version info for this router. (i.e., a "v" entry was
- * included.) We'll replace all these with a big tor_version_t or a char[]
- * if the number of traits we care about ever becomes incredibly big. */
- unsigned int version_known:1;
+ /** True iff we have a proto line for this router, or a versions line
+ * from which we could infer the protocols. */
+ unsigned int protocols_known:1;
- /** True iff this router has a version that allows it to accept EXTEND2
- * cells */
- unsigned int version_supports_extend2_cells:1;
+ /** True iff this router has a version or protocol list that allows it to
+ * accept EXTEND2 cells */
+ unsigned int supports_extend2_cells:1;
unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */
unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */
@@ -2470,6 +2412,8 @@ typedef struct vote_routerstatus_t {
* networkstatus_t.known_flags. */
char *version; /**< The version that the authority says this router is
* running. */
+ char *protocols; /**< The protocols that this authority says this router
+ * provides. */
unsigned int has_measured_bw:1; /**< The vote had a measured bw */
/** True iff the vote included an entry for ed25519 ID, or included
* "id ed25519 none" to indicate that there was no ed25519 ID. */
@@ -2523,6 +2467,18 @@ typedef struct networkstatus_voter_info_t {
smartlist_t *sigs;
} networkstatus_voter_info_t;
+typedef struct networkstatus_sr_info_t {
+ /* Indicate if the dirauth partitipates in the SR protocol with its vote.
+ * This is tied to the SR flag in the vote. */
+ unsigned int participate:1;
+ /* Both vote and consensus: Current and previous SRV. If list is empty,
+ * this means none were found in either the consensus or vote. */
+ struct sr_srv_t *previous_srv;
+ struct sr_srv_t *current_srv;
+ /* Vote only: List of commitments. */
+ smartlist_t *commits;
+} networkstatus_sr_info_t;
+
/** Enumerates the possible seriousness values of a networkstatus document. */
typedef enum {
NS_TYPE_VOTE,
@@ -2575,6 +2531,16 @@ typedef struct networkstatus_t {
* voter has no opinion. */
char *client_versions;
char *server_versions;
+
+ /** Lists of subprotocol versions which are _recommended_ for relays and
+ * clients, or which are _require_ for relays and clients. Tor shouldn't
+ * make any more network connections if a required protocol is missing.
+ */
+ char *recommended_relay_protocols;
+ char *recommended_client_protocols;
+ char *required_relay_protocols;
+ char *required_client_protocols;
+
/** List of flags that this vote/consensus applies to routers. If a flag is
* not listed here, the voter has no opinion on what its value should be. */
smartlist_t *known_flags;
@@ -2605,6 +2571,9 @@ typedef struct networkstatus_t {
/** If present, a map from descriptor digest to elements of
* routerstatus_list. */
digestmap_t *desc_digest_map;
+
+ /** Contains the shared random protocol data from a vote or consensus. */
+ networkstatus_sr_info_t sr_info;
} networkstatus_t;
/** A set of signatures for a networkstatus consensus. Unless otherwise
@@ -2973,17 +2942,17 @@ typedef struct circuit_t {
/** When the circuit was first used, or 0 if the circuit is clean.
*
- * XXXX023 Note that some code will artifically adjust this value backward
+ * XXXX Note that some code will artifically adjust this value backward
* in time in order to indicate that a circuit shouldn't be used for new
* streams, but that it can stay alive as long as it has streams on it.
* That's a kludge we should fix.
*
- * XXX023 The CBT code uses this field to record when HS-related
+ * XXX The CBT code uses this field to record when HS-related
* circuits entered certain states. This usage probably won't
* interfere with this field's primary purpose, but we should
* document it more thoroughly to make sure of that.
*
- * XXX027 The SocksPort option KeepaliveIsolateSOCKSAuth will artificially
+ * XXX The SocksPort option KeepaliveIsolateSOCKSAuth will artificially
* adjust this value forward each time a suitable stream is attached to an
* already constructed circuit, potentially keeping the circuit alive
* indefinitely.
@@ -2998,11 +2967,11 @@ typedef struct circuit_t {
/** For what reason (See END_CIRC_REASON...) is this circuit being closed?
* This field is set in circuit_mark_for_close and used later in
* circuit_about_to_free. */
- uint16_t marked_for_close_reason;
+ int marked_for_close_reason;
/** As marked_for_close_reason, but reflects the underlying reason for
* closing this circuit.
*/
- uint16_t marked_for_close_orig_reason;
+ int marked_for_close_orig_reason;
/** Unique ID for measuring tunneled network status requests. */
uint64_t dirreq_id;
@@ -3573,7 +3542,13 @@ typedef struct {
/** Bitmask; derived from AllowInvalidNodes. */
invalid_router_usage_t AllowInvalid_;
config_line_t *ExitPolicy; /**< Lists of exit policy components. */
- int ExitPolicyRejectPrivate; /**< Should we not exit to local addresses? */
+ int ExitPolicyRejectPrivate; /**< Should we not exit to reserved private
+ * addresses, and our own published addresses?
+ */
+ int ExitPolicyRejectLocalInterfaces; /**< Should we not exit to local
+ * interface addresses?
+ * Includes OutboundBindAddresses and
+ * configured ports. */
config_line_t *SocksPolicy; /**< Lists of socks policy components */
config_line_t *DirPolicy; /**< Lists of dir policy components */
/** Addresses to bind for listening for SOCKS connections. */
@@ -3648,9 +3623,13 @@ typedef struct {
/** @name port booleans
*
- * Derived booleans: True iff there is a non-listener port on an AF_INET or
- * AF_INET6 address of the given type configured in one of the _lines
- * options above.
+ * Derived booleans: For server ports and ControlPort, true iff there is a
+ * non-listener port on an AF_INET or AF_INET6 address of the given type
+ * 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
+ * rather than SocksPort_set.
*
* @{
*/
@@ -3741,8 +3720,32 @@ typedef struct {
* they reach the normal circuit-build timeout. */
int CloseHSServiceRendCircuitsImmediatelyOnTimeout;
+ /** Onion Services in HiddenServiceSingleHopMode make one-hop (direct)
+ * circuits between the onion service server, and the introduction and
+ * rendezvous points. (Onion service descriptors are still posted using
+ * 3-hop paths, to avoid onion service directories blocking the service.)
+ * This option makes every hidden service instance hosted by
+ * this tor instance a Single Onion Service.
+ * HiddenServiceSingleHopMode requires HiddenServiceNonAnonymousMode to be
+ * set to 1.
+ * Use rend_service_allow_non_anonymous_connection() or
+ * rend_service_reveal_startup_time() instead of using this option directly.
+ */
+ int HiddenServiceSingleHopMode;
+ /* Makes hidden service clients and servers non-anonymous on this tor
+ * instance. Allows the non-anonymous HiddenServiceSingleHopMode. Enables
+ * non-anonymous behaviour in the hidden service protocol.
+ * Use rend_service_non_anonymous_mode_enabled() instead of using this option
+ * directly.
+ */
+ int HiddenServiceNonAnonymousMode;
+
int ConnLimit; /**< Demanded minimum number of simultaneous connections. */
int ConnLimit_; /**< Maximum allowed number of simultaneous connections. */
+ int ConnLimit_high_thresh; /**< start trying to lower socket usage if we
+ * have this many. */
+ int ConnLimit_low_thresh; /**< try to get down to here after socket
+ * exhaustion. */
int RunAsDaemon; /**< If true, run in the background. (Unix only) */
int FascistFirewall; /**< Whether to prefer ORs reachable on open ports. */
smartlist_t *FirewallPorts; /**< Which ports our firewall allows
@@ -3792,7 +3795,8 @@ typedef struct {
* unattached before we fail it? */
int LearnCircuitBuildTimeout; /**< If non-zero, we attempt to learn a value
* for CircuitBuildTimeout based on timeout
- * history */
+ * history. Use circuit_build_times_disabled()
+ * rather than checking this value directly. */
int CircuitBuildTimeout; /**< Cull non-open circuits that were born at
* least this many seconds ago. Used until
* adaptive algorithm learns a new value. */
@@ -3978,8 +3982,16 @@ typedef struct {
int TokenBucketRefillInterval;
char *AccelName; /**< Optional hardware acceleration engine name. */
char *AccelDir; /**< Optional hardware acceleration engine search dir. */
- int UseEntryGuards; /**< Boolean: Do we try to enter from a smallish number
- * of fixed nodes? */
+
+ /** Boolean: Do we try to enter from a smallish number
+ * of fixed nodes? */
+ int UseEntryGuards_option;
+ /** Internal variable to remember whether we're actually acting on
+ * UseEntryGuards_option -- when we're a non-anonymous Tor2web client or
+ * Single Onion Service, it is alwasy false, otherwise we use the value of
+ * UseEntryGuards_option. */
+ int UseEntryGuards;
+
int NumEntryGuards; /**< How many entry guards do we try to establish? */
int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
* from a smallish number of fixed nodes? */
@@ -4336,12 +4348,6 @@ typedef struct {
*/
double CircuitPriorityHalflife;
- /** If true, do not enable IOCP on windows with bufferevents, even if
- * we think we could. */
- int DisableIOCP;
- /** For testing only: will go away eventually. */
- int UseFilteringSSLBufferevents;
-
/** Set to true if the TestingTorNetwork configuration option is set.
* This is used so that options_validate() has a chance to realize that
* the defaults have changed. */
@@ -4365,11 +4371,6 @@ typedef struct {
* never use it. If -1, we do what the consensus says. */
int OptimisticData;
- /** If 1, and we are using IOCP, we set the kernel socket SNDBUF and RCVBUF
- * to 0 to try to save kernel memory and avoid the dread "Out of buffers"
- * issue. */
- int UserspaceIOCPBuffers;
-
/** If 1, we accept and launch no external network connections, except on
* control ports. */
int DisableNetwork;
@@ -4435,9 +4436,6 @@ typedef struct {
char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */
- /** Autobool: should we use the ntor handshake if we can? */
- int UseNTorHandshake;
-
/** Fraction: */
double PathsNeededToBuildCircuits;
@@ -4498,6 +4496,20 @@ typedef struct {
/** Autobool: Do we try to retain capabilities if we can? */
int KeepBindCapabilities;
+
+ /** Maximum total size of unparseable descriptors to log during the
+ * lifetime of this Tor process.
+ */
+ uint64_t MaxUnparseableDescSizeToLog;
+
+ /** Bool (default: 1): Switch for the shared random protocol. Only
+ * relevant to a directory authority. If off, the authority won't
+ * participate in the protocol. If on (default), a flag is added to the
+ * vote indicating participation. */
+ int AuthDirSharedRandomness;
+
+ /** If 1, we skip all OOS checks. */
+ int DisableOOSCheck;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
@@ -5047,7 +5059,7 @@ typedef enum {
/** Hidden-service side configuration of client authorization. */
typedef struct rend_authorized_client_t {
char *client_name;
- char descriptor_cookie[REND_DESC_COOKIE_LEN];
+ uint8_t descriptor_cookie[REND_DESC_COOKIE_LEN];
crypto_pk_t *client_key;
} rend_authorized_client_t;
@@ -5075,12 +5087,12 @@ typedef struct rend_encoded_v2_service_descriptor_t {
* INTRO_POINT_LIFETIME_INTRODUCTIONS INTRODUCE2 cells, it may expire
* sooner.)
*
- * XXX023 Should this be configurable? */
+ * XXX Should this be configurable? */
#define INTRO_POINT_LIFETIME_MIN_SECONDS (18*60*60)
/** The maximum number of seconds that an introduction point will last
* before expiring due to old age.
*
- * XXX023 Should this be configurable? */
+ * XXX Should this be configurable? */
#define INTRO_POINT_LIFETIME_MAX_SECONDS (24*60*60)
/** The maximum number of circuit creation retry we do to an intro point
@@ -5092,7 +5104,8 @@ typedef struct rend_encoded_v2_service_descriptor_t {
* the service side) and in rend_service_descriptor_t (on both the
* client and service side). */
typedef struct rend_intro_point_t {
- extend_info_t *extend_info; /**< Extend info of this introduction point. */
+ extend_info_t *extend_info; /**< Extend info for connecting to this
+ * introduction point via a multi-hop path. */
crypto_pk_t *intro_key; /**< Introduction key that replaces the service
* key, if this descriptor is V2. */
diff --git a/src/or/periodic.c b/src/or/periodic.c
index 057fcf672e..d02d4a7bbb 100644
--- a/src/or/periodic.c
+++ b/src/or/periodic.c
@@ -5,6 +5,10 @@
* \file periodic.c
*
* \brief Generic backend for handling periodic events.
+ *
+ * The events in this module are used by main.c to track items that need
+ * to fire once every N seconds, possibly picking a new interval each time
+ * that they fire. See periodic_events[] in main.c for examples.
*/
#include "or.h"
@@ -12,11 +16,7 @@
#include "config.h"
#include "periodic.h"
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
/** We disable any interval greater than this number of seconds, on the
* grounds that it is probably an absolute time mistakenly passed in as a
diff --git a/src/or/policies.c b/src/or/policies.c
index 50fec3a773..28770bb38d 100644
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@ -103,7 +103,7 @@ policy_expand_private(smartlist_t **policy)
if (tor_addr_parse_mask_ports(private_nets[i], 0,
&newpolicy.addr,
&newpolicy.maskbits, &port_min, &port_max)<0) {
- tor_assert(0);
+ tor_assert_unreached();
}
smartlist_add(tmp, addr_policy_get_canonical_entry(&newpolicy));
}
@@ -274,28 +274,22 @@ parse_reachable_addresses(void)
/* We ignore ReachableAddresses for relays */
if (!server_mode(options)) {
- if ((reachable_or_addr_policy
- && policy_is_reject_star(reachable_or_addr_policy, AF_UNSPEC))
- || (reachable_dir_addr_policy
- && policy_is_reject_star(reachable_dir_addr_policy, AF_UNSPEC))) {
+ if (policy_is_reject_star(reachable_or_addr_policy, AF_UNSPEC, 0)
+ || policy_is_reject_star(reachable_dir_addr_policy, AF_UNSPEC,0)) {
log_warn(LD_CONFIG, "Tor cannot connect to the Internet if "
"ReachableAddresses, ReachableORAddresses, or "
"ReachableDirAddresses reject all addresses. Please accept "
"some addresses in these options.");
} else if (options->ClientUseIPv4 == 1
- && ((reachable_or_addr_policy
- && policy_is_reject_star(reachable_or_addr_policy, AF_INET))
- || (reachable_dir_addr_policy
- && policy_is_reject_star(reachable_dir_addr_policy, AF_INET)))) {
+ && (policy_is_reject_star(reachable_or_addr_policy, AF_INET, 0)
+ || policy_is_reject_star(reachable_dir_addr_policy, AF_INET, 0))) {
log_warn(LD_CONFIG, "You have set ClientUseIPv4 1, but "
"ReachableAddresses, ReachableORAddresses, or "
"ReachableDirAddresses reject all IPv4 addresses. "
"Tor will not connect using IPv4.");
} else if (fascist_firewall_use_ipv6(options)
- && ((reachable_or_addr_policy
- && policy_is_reject_star(reachable_or_addr_policy, AF_INET6))
- || (reachable_dir_addr_policy
- && policy_is_reject_star(reachable_dir_addr_policy, AF_INET6)))) {
+ && (policy_is_reject_star(reachable_or_addr_policy, AF_INET6, 0)
+ || policy_is_reject_star(reachable_dir_addr_policy, AF_INET6, 0))) {
log_warn(LD_CONFIG, "You have configured tor to use IPv6 "
"(ClientUseIPv6 1 or UseBridges 1), but "
"ReachableAddresses, ReachableORAddresses, or "
@@ -1084,8 +1078,8 @@ validate_addr_policies(const or_options_t *options, char **msg)
const int exitrelay_setting_is_auto = options->ExitRelay == -1;
const int policy_accepts_something =
- ! (policy_is_reject_star(addr_policy, AF_INET) &&
- policy_is_reject_star(addr_policy, AF_INET6));
+ ! (policy_is_reject_star(addr_policy, AF_INET, 1) &&
+ policy_is_reject_star(addr_policy, AF_INET6, 1));
if (server_mode(options) &&
! warned_about_exitrelay &&
@@ -1204,48 +1198,48 @@ policies_parse_from_options(const or_options_t *options)
return ret;
}
-/** Compare two provided address policy items, and return -1, 0, or 1
+/** Compare two provided address policy items, and renturn -1, 0, or 1
* if the first is less than, equal to, or greater than the second. */
static int
-cmp_single_addr_policy(addr_policy_t *a, addr_policy_t *b)
+single_addr_policy_eq(const addr_policy_t *a, const addr_policy_t *b)
{
int r;
- if ((r=((int)a->policy_type - (int)b->policy_type)))
- return r;
- if ((r=((int)a->is_private - (int)b->is_private)))
- return r;
+#define CMP_FIELD(field) do { \
+ if (a->field != b->field) { \
+ return 0; \
+ } \
+ } while (0)
+ CMP_FIELD(policy_type);
+ CMP_FIELD(is_private);
/* refcnt and is_canonical are irrelevant to equality,
* they are hash table implementation details */
if ((r=tor_addr_compare(&a->addr, &b->addr, CMP_EXACT)))
- return r;
- if ((r=((int)a->maskbits - (int)b->maskbits)))
- return r;
- if ((r=((int)a->prt_min - (int)b->prt_min)))
- return r;
- if ((r=((int)a->prt_max - (int)b->prt_max)))
- return r;
- return 0;
+ return 0;
+ CMP_FIELD(maskbits);
+ CMP_FIELD(prt_min);
+ CMP_FIELD(prt_max);
+#undef CMP_FIELD
+ return 1;
}
-/** Like cmp_single_addr_policy() above, but looks at the
- * whole set of policies in each case. */
+/** As single_addr_policy_eq, but compare every element of two policies.
+ */
int
-cmp_addr_policies(smartlist_t *a, smartlist_t *b)
+addr_policies_eq(const smartlist_t *a, const smartlist_t *b)
{
- int r, i;
+ int i;
int len_a = a ? smartlist_len(a) : 0;
int len_b = b ? smartlist_len(b) : 0;
- for (i = 0; i < len_a && i < len_b; ++i) {
- if ((r = cmp_single_addr_policy(smartlist_get(a, i), smartlist_get(b, i))))
- return r;
- }
- if (i == len_a && i == len_b)
+ if (len_a != len_b)
return 0;
- if (i < len_a)
- return -1;
- else
- return 1;
+
+ for (i = 0; i < len_a; ++i) {
+ if (! single_addr_policy_eq(smartlist_get(a, i), smartlist_get(b, i)))
+ return 0;
+ }
+
+ return 1;
}
/** Node in hashtable used to store address policy entries. */
@@ -1261,7 +1255,7 @@ static HT_HEAD(policy_map, policy_map_ent_t) policy_root = HT_INITIALIZER();
static inline int
policy_eq(policy_map_ent_t *a, policy_map_ent_t *b)
{
- return cmp_single_addr_policy(a->policy, b->policy) == 0;
+ return single_addr_policy_eq(a->policy, b->policy);
}
/** Return a hashcode for <b>ent</b> */
@@ -1312,7 +1306,7 @@ addr_policy_get_canonical_entry(addr_policy_t *e)
HT_INSERT(policy_map, &policy_root, found);
}
- tor_assert(!cmp_single_addr_policy(found->policy, e));
+ tor_assert(single_addr_policy_eq(found->policy, e));
++found->policy->refcnt;
return found->policy;
}
@@ -1837,10 +1831,18 @@ policies_log_first_redundant_entry(const smartlist_t *policy)
*
* If <b>ipv6_exit</b> is false, prepend "reject *6:*" to the policy.
*
+ * If <b>configured_addresses</b> contains addresses:
+ * - prepend entries that reject the addresses in this list. These may be the
+ * advertised relay addresses and/or the outbound bind addresses,
+ * depending on the ExitPolicyRejectPrivate and
+ * ExitPolicyRejectLocalInterfaces settings.
* If <b>rejectprivate</b> is true:
* - prepend "reject private:*" to the policy.
- * - prepend entries that reject publicly routable addresses on this exit
- * relay by calling policies_parse_exit_policy_reject_private
+ * If <b>reject_interface_addresses</b> is true:
+ * - prepend entries that reject publicly routable interface addresses on
+ * this exit relay by calling policies_parse_exit_policy_reject_private
+ * If <b>reject_configured_port_addresses</b> is true:
+ * - prepend entries that reject all configured port addresses
*
* If cfg doesn't end in an absolute accept or reject and if
* <b>add_default_policy</b> is true, add the default exit
@@ -1868,13 +1870,16 @@ policies_parse_exit_policy_internal(config_line_t *cfg,
if (rejectprivate) {
/* Reject IPv4 and IPv6 reserved private netblocks */
append_exit_policy_string(dest, "reject private:*");
- /* Reject IPv4 and IPv6 publicly routable addresses on this exit relay */
- policies_parse_exit_policy_reject_private(
- dest, ipv6_exit,
+ }
+
+ /* Consider rejecting IPv4 and IPv6 advertised relay addresses, outbound bind
+ * addresses, publicly routable addresses, and configured port addresses
+ * on this exit relay */
+ policies_parse_exit_policy_reject_private(dest, ipv6_exit,
configured_addresses,
reject_interface_addresses,
reject_configured_port_addresses);
- }
+
if (parse_addr_policy(cfg, dest, -1))
return -1;
@@ -1902,8 +1907,14 @@ policies_parse_exit_policy_internal(config_line_t *cfg,
* If <b>EXIT_POLICY_REJECT_PRIVATE</b> bit is set in <b>options</b>:
* - prepend an entry that rejects all destinations in all netblocks
* reserved for private use.
+ * - prepend entries that reject the advertised relay addresses in
+ * configured_addresses
+ * If <b>EXIT_POLICY_REJECT_LOCAL_INTERFACES</b> bit is set in <b>options</b>:
* - prepend entries that reject publicly routable addresses on this exit
* relay by calling policies_parse_exit_policy_internal
+ * - prepend entries that reject the outbound bind addresses in
+ * configured_addresses
+ * - prepend entries that reject all configured port addresses
*
* If <b>EXIT_POLICY_ADD_DEFAULT</b> bit is set in <b>options</b>, append
* default exit policy entries to <b>result</b> smartlist.
@@ -1916,12 +1927,14 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
int ipv6_enabled = (options & EXIT_POLICY_IPV6_ENABLED) ? 1 : 0;
int reject_private = (options & EXIT_POLICY_REJECT_PRIVATE) ? 1 : 0;
int add_default = (options & EXIT_POLICY_ADD_DEFAULT) ? 1 : 0;
+ int reject_local_interfaces = (options &
+ EXIT_POLICY_REJECT_LOCAL_INTERFACES) ? 1 : 0;
return policies_parse_exit_policy_internal(cfg,dest,ipv6_enabled,
reject_private,
configured_addresses,
- reject_private,
- reject_private,
+ reject_local_interfaces,
+ reject_local_interfaces,
add_default);
}
@@ -1987,6 +2000,7 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list,
* add it to the list of configured addresses.
* - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it
* to the list of configured addresses.
+ * If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true:
* - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add
* it to the list of configured addresses.
* - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add
@@ -2030,11 +2044,20 @@ policies_parse_exit_policy_from_options(const or_options_t *or_options,
parser_cfg |= EXIT_POLICY_ADD_DEFAULT;
}
+ if (or_options->ExitPolicyRejectLocalInterfaces) {
+ parser_cfg |= EXIT_POLICY_REJECT_LOCAL_INTERFACES;
+ }
+
/* Copy the configured addresses into the tor_addr_t* list */
- policies_copy_ipv4h_to_smartlist(configured_addresses, local_address);
- policies_copy_addr_to_smartlist(configured_addresses, ipv6_local_address);
- policies_copy_outbound_addresses_to_smartlist(configured_addresses,
- or_options);
+ if (or_options->ExitPolicyRejectPrivate) {
+ policies_copy_ipv4h_to_smartlist(configured_addresses, local_address);
+ policies_copy_addr_to_smartlist(configured_addresses, ipv6_local_address);
+ }
+
+ if (or_options->ExitPolicyRejectLocalInterfaces) {
+ policies_copy_outbound_addresses_to_smartlist(configured_addresses,
+ or_options);
+ }
rv = policies_parse_exit_policy(or_options->ExitPolicy, result, parser_cfg,
configured_addresses);
@@ -2090,8 +2113,10 @@ exit_policy_is_general_exit_helper(smartlist_t *policy, int port)
if (subnet_status[i] != 0)
continue; /* We already reject some part of this /8 */
tor_addr_from_ipv4h(&addr, i<<24);
- if (tor_addr_is_internal(&addr, 0))
+ if (tor_addr_is_internal(&addr, 0) &&
+ !get_options()->DirAllowPrivateAddresses) {
continue; /* Local or non-routable addresses */
+ }
if (p->policy_type == ADDR_POLICY_ACCEPT) {
if (p->maskbits > 8)
continue; /* Narrower than a /8. */
@@ -2125,13 +2150,16 @@ exit_policy_is_general_exit(smartlist_t *policy)
}
/** Return false if <b>policy</b> might permit access to some addr:port;
- * otherwise if we are certain it rejects everything, return true. */
+ * otherwise if we are certain it rejects everything, return true. If no
+ * part of <b>policy</b> matches, return <b>default_reject</b>.
+ * NULL policies are allowed, and treated as empty. */
int
-policy_is_reject_star(const smartlist_t *policy, sa_family_t family)
+policy_is_reject_star(const smartlist_t *policy, sa_family_t family,
+ int default_reject)
{
- if (!policy) /*XXXX disallow NULL policies? */
- return 1;
- SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, p) {
+ if (!policy)
+ return default_reject;
+ SMARTLIST_FOREACH_BEGIN(policy, const addr_policy_t *, p) {
if (p->policy_type == ADDR_POLICY_ACCEPT &&
(tor_addr_family(&p->addr) == family ||
tor_addr_family(&p->addr) == AF_UNSPEC)) {
@@ -2144,7 +2172,7 @@ policy_is_reject_star(const smartlist_t *policy, sa_family_t family)
return 1;
}
} SMARTLIST_FOREACH_END(p);
- return 1;
+ return default_reject;
}
/** Write a single address policy to the buf_len byte buffer at buf. Return
@@ -2271,7 +2299,26 @@ policy_summary_item_split(policy_summary_item_t* old, uint16_t new_starts)
* my immortal soul, he can clean it up himself. */
#define AT(x) ((policy_summary_item_t*)smartlist_get(summary, x))
-#define REJECT_CUTOFF_COUNT (1<<25)
+#define IPV4_BITS (32)
+/* Every IPv4 address is counted as one rejection */
+#define REJECT_CUTOFF_SCALE_IPV4 (0)
+/* Ports are rejected in an IPv4 summary if they are rejected in more than two
+ * IPv4 /8 address blocks */
+#define REJECT_CUTOFF_COUNT_IPV4 (U64_LITERAL(1) << \
+ (IPV4_BITS - REJECT_CUTOFF_SCALE_IPV4 - 7))
+
+#define IPV6_BITS (128)
+/* IPv6 /64s are counted as one rejection, anything smaller is ignored */
+#define REJECT_CUTOFF_SCALE_IPV6 (64)
+/* Ports are rejected in an IPv6 summary if they are rejected in more than one
+ * IPv6 /16 address block.
+ * This is rougly equivalent to the IPv4 cutoff, as only five IPv6 /12s (and
+ * some scattered smaller blocks) have been allocated to the RIRs.
+ * Network providers are typically allocated one or more IPv6 /32s.
+ */
+#define REJECT_CUTOFF_COUNT_IPV6 (U64_LITERAL(1) << \
+ (IPV6_BITS - REJECT_CUTOFF_SCALE_IPV6 - 16))
+
/** Split an exit policy summary so that prt_min and prt_max
* fall at exactly the start and end of an item respectively.
*/
@@ -2304,53 +2351,102 @@ policy_summary_split(smartlist_t *summary,
return start_at_index;
}
-/** Mark port ranges as accepted if they are below the reject_count */
+/** Mark port ranges as accepted if they are below the reject_count for family
+ */
static void
policy_summary_accept(smartlist_t *summary,
- uint16_t prt_min, uint16_t prt_max)
+ uint16_t prt_min, uint16_t prt_max,
+ sa_family_t family)
{
+ tor_assert_nonfatal_once(family == AF_INET || family == AF_INET6);
+ uint64_t family_reject_count = ((family == AF_INET) ?
+ REJECT_CUTOFF_COUNT_IPV4 :
+ REJECT_CUTOFF_COUNT_IPV6);
+
int i = policy_summary_split(summary, prt_min, prt_max);
while (i < smartlist_len(summary) &&
AT(i)->prt_max <= prt_max) {
if (!AT(i)->accepted &&
- AT(i)->reject_count <= REJECT_CUTOFF_COUNT)
+ AT(i)->reject_count <= family_reject_count)
AT(i)->accepted = 1;
i++;
}
tor_assert(i < smartlist_len(summary) || prt_max==65535);
}
-/** Count the number of addresses in a network with prefixlen maskbits
- * against the given portrange. */
+/** Count the number of addresses in a network in family with prefixlen
+ * maskbits against the given portrange. */
static void
policy_summary_reject(smartlist_t *summary,
maskbits_t maskbits,
- uint16_t prt_min, uint16_t prt_max)
+ uint16_t prt_min, uint16_t prt_max,
+ sa_family_t family)
{
+ tor_assert_nonfatal_once(family == AF_INET || family == AF_INET6);
+
int i = policy_summary_split(summary, prt_min, prt_max);
- /* XXX: ipv4 specific */
- uint64_t count = (U64_LITERAL(1) << (32-maskbits));
+
+ /* The length of a single address mask */
+ int addrbits = (family == AF_INET) ? IPV4_BITS : IPV6_BITS;
+ tor_assert_nonfatal_once(addrbits >= maskbits);
+
+ /* We divide IPv6 address counts by (1 << scale) to keep them in a uint64_t
+ */
+ int scale = ((family == AF_INET) ?
+ REJECT_CUTOFF_SCALE_IPV4 :
+ REJECT_CUTOFF_SCALE_IPV6);
+
+ tor_assert_nonfatal_once(addrbits >= scale);
+ if (maskbits > (addrbits - scale)) {
+ tor_assert_nonfatal_once(family == AF_INET6);
+ /* The address range is so small, we'd need billions of them to reach the
+ * rejection limit. So we ignore this range in the reject count. */
+ return;
+ }
+
+ uint64_t count = 0;
+ if (addrbits - scale - maskbits >= 64) {
+ tor_assert_nonfatal_once(family == AF_INET6);
+ /* The address range is so large, it's an automatic rejection for all ports
+ * in the range. */
+ count = UINT64_MAX;
+ } else {
+ count = (U64_LITERAL(1) << (addrbits - scale - maskbits));
+ }
+ tor_assert_nonfatal_once(count > 0);
while (i < smartlist_len(summary) &&
AT(i)->prt_max <= prt_max) {
- AT(i)->reject_count += count;
+ if (AT(i)->reject_count <= UINT64_MAX - count) {
+ AT(i)->reject_count += count;
+ } else {
+ /* IPv4 would require a 4-billion address redundant policy to get here,
+ * but IPv6 just needs to have ::/0 */
+ if (family == AF_INET) {
+ tor_assert_nonfatal_unreached_once();
+ }
+ /* If we do get here, use saturating arithmetic */
+ AT(i)->reject_count = UINT64_MAX;
+ }
i++;
}
tor_assert(i < smartlist_len(summary) || prt_max==65535);
}
/** Add a single exit policy item to our summary:
- * If it is an accept ignore it unless it is for all IP addresses
- * ("*"), i.e. it's prefixlen/maskbits is 0, else call
+ *
+ * If it is an accept, ignore it unless it is for all IP addresses
+ * ("*", i.e. its prefixlen/maskbits is 0). Otherwise call
* policy_summary_accept().
- * If it's a reject ignore it if it is about one of the private
- * networks, else call policy_summary_reject().
+ *
+ * If it is a reject, ignore it if it is about one of the private
+ * networks. Otherwise call policy_summary_reject().
*/
static void
policy_summary_add_item(smartlist_t *summary, addr_policy_t *p)
{
if (p->policy_type == ADDR_POLICY_ACCEPT) {
if (p->maskbits == 0) {
- policy_summary_accept(summary, p->prt_min, p->prt_max);
+ policy_summary_accept(summary, p->prt_min, p->prt_max, p->addr.family);
}
} else if (p->policy_type == ADDR_POLICY_REJECT) {
@@ -2371,7 +2467,8 @@ policy_summary_add_item(smartlist_t *summary, addr_policy_t *p)
}
if (!is_private) {
- policy_summary_reject(summary, p->maskbits, p->prt_min, p->prt_max);
+ policy_summary_reject(summary, p->maskbits, p->prt_min, p->prt_max,
+ p->addr.family);
}
} else
tor_assert(0);
@@ -2405,7 +2502,6 @@ policy_summarize(smartlist_t *policy, sa_family_t family)
}
if (f != family)
continue;
- /* XXXX-ipv6 More family work is needed */
policy_summary_add_item(summary, p);
} SMARTLIST_FOREACH_END(p);
@@ -2594,8 +2690,7 @@ parse_short_policy(const char *summary)
return result;
}
-/** Write <b>policy</b> back out into a string. Used only for unit tests
- * currently. */
+/** Write <b>policy</b> back out into a string. */
char *
write_short_policy(const short_policy_t *policy)
{
@@ -2638,7 +2733,7 @@ compare_tor_addr_to_short_policy(const tor_addr_t *addr, uint16_t port,
{
int i;
int found_match = 0;
- int accept;
+ int accept_;
tor_assert(port != 0);
@@ -2658,9 +2753,9 @@ compare_tor_addr_to_short_policy(const tor_addr_t *addr, uint16_t port,
}
if (found_match)
- accept = policy->is_accept;
+ accept_ = policy->is_accept;
else
- accept = ! policy->is_accept;
+ accept_ = ! policy->is_accept;
/* ???? are these right? -NM */
/* We should be sure not to return ADDR_POLICY_ACCEPTED in the accept
@@ -2673,7 +2768,7 @@ compare_tor_addr_to_short_policy(const tor_addr_t *addr, uint16_t port,
*
* Once microdescriptors can handle addresses in special cases (e.g. if
* we ever solve ticket 1774), we can provide certainty here. -RD */
- if (accept)
+ if (accept_)
return ADDR_POLICY_PROBABLY_ACCEPTED;
else
return ADDR_POLICY_REJECTED;
@@ -2814,7 +2909,8 @@ getinfo_helper_policies(control_connection_t *conn,
return -1;
}
- if (!options->ExitPolicyRejectPrivate) {
+ if (!options->ExitPolicyRejectPrivate &&
+ !options->ExitPolicyRejectLocalInterfaces) {
*answer = tor_strdup("");
return 0;
}
@@ -2823,16 +2919,22 @@ getinfo_helper_policies(control_connection_t *conn,
smartlist_t *configured_addresses = smartlist_new();
/* Copy the configured addresses into the tor_addr_t* list */
- policies_copy_ipv4h_to_smartlist(configured_addresses, me->addr);
- policies_copy_addr_to_smartlist(configured_addresses, &me->ipv6_addr);
- policies_copy_outbound_addresses_to_smartlist(configured_addresses,
- options);
+ if (options->ExitPolicyRejectPrivate) {
+ policies_copy_ipv4h_to_smartlist(configured_addresses, me->addr);
+ policies_copy_addr_to_smartlist(configured_addresses, &me->ipv6_addr);
+ }
+
+ if (options->ExitPolicyRejectLocalInterfaces) {
+ policies_copy_outbound_addresses_to_smartlist(configured_addresses,
+ options);
+ }
policies_parse_exit_policy_reject_private(
- &private_policy_list,
- options->IPv6Exit,
- configured_addresses,
- 1, 1);
+ &private_policy_list,
+ options->IPv6Exit,
+ configured_addresses,
+ options->ExitPolicyRejectLocalInterfaces,
+ options->ExitPolicyRejectLocalInterfaces);
*answer = policy_dump_to_string(private_policy_list, 1, 1);
addr_policy_list_free(private_policy_list);
diff --git a/src/or/policies.h b/src/or/policies.h
index aaa6fa0a4e..f73f850c21 100644
--- a/src/or/policies.h
+++ b/src/or/policies.h
@@ -18,9 +18,13 @@
*/
#define POLICY_BUF_LEN 72
-#define EXIT_POLICY_IPV6_ENABLED (1 << 0)
-#define EXIT_POLICY_REJECT_PRIVATE (1 << 1)
-#define EXIT_POLICY_ADD_DEFAULT (1 << 2)
+#define EXIT_POLICY_IPV6_ENABLED (1 << 0)
+#define EXIT_POLICY_REJECT_PRIVATE (1 << 1)
+#define EXIT_POLICY_ADD_DEFAULT (1 << 2)
+#define EXIT_POLICY_REJECT_LOCAL_INTERFACES (1 << 3)
+#define EXIT_POLICY_OPTION_MAX EXIT_POLICY_REJECT_LOCAL_INTERFACES
+/* All options set: used for unit testing */
+#define EXIT_POLICY_OPTION_ALL ((EXIT_POLICY_OPTION_MAX << 1) - 1)
typedef enum firewall_connection_t {
FIREWALL_OR_CONNECTION = 0,
@@ -72,7 +76,7 @@ void policy_expand_unspec(smartlist_t **policy);
int policies_parse_from_options(const or_options_t *options);
addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent);
-int cmp_addr_policies(smartlist_t *a, smartlist_t *b);
+int addr_policies_eq(const smartlist_t *a, const smartlist_t *b);
MOCK_DECL(addr_policy_result_t, compare_tor_addr_to_addr_policy,
(const tor_addr_t *addr, uint16_t port, const smartlist_t *policy));
addr_policy_result_t compare_tor_addr_to_node_policy(const tor_addr_t *addr,
@@ -99,7 +103,8 @@ void addr_policy_append_reject_addr_list(smartlist_t **dest,
const smartlist_t *addrs);
void policies_set_node_exitpolicy_to_reject_all(node_t *exitrouter);
int exit_policy_is_general_exit(smartlist_t *policy);
-int policy_is_reject_star(const smartlist_t *policy, sa_family_t family);
+int policy_is_reject_star(const smartlist_t *policy, sa_family_t family,
+ int reject_by_default);
char * policy_dump_to_string(const smartlist_t *policy_list,
int include_ipv4,
int include_ipv6);
diff --git a/src/or/protover.c b/src/or/protover.c
new file mode 100644
index 0000000000..98957cabdf
--- /dev/null
+++ b/src/or/protover.c
@@ -0,0 +1,742 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file protover.c
+ * \brief Versioning information for different pieces of the Tor protocol.
+ *
+ * Starting in version 0.2.9.3-alpha, Tor places separate version numbers on
+ * each of the different components of its protocol. Relays use these numbers
+ * to advertise what versions of the protocols they can support, and clients
+ * use them to find what they can ask a given relay to do. Authorities vote
+ * on the supported protocol versions for each relay, and also vote on the
+ * which protocols you should have to support in order to be on the Tor
+ * network. All Tor instances use these required/recommended protocol versions
+ * to tell what level of support for recent protocols each relay has, and
+ * to decide whether they should be running given their current protocols.
+ *
+ * The main advantage of these protocol versions numbers over using Tor
+ * version numbers is that they allow different implementations of the Tor
+ * protocols to develop independently, without having to claim compatibility
+ * with specific versions of Tor.
+ **/
+
+#define PROTOVER_PRIVATE
+
+#include "or.h"
+#include "protover.h"
+#include "routerparse.h"
+
+static const smartlist_t *get_supported_protocol_list(void);
+static int protocol_list_contains(const smartlist_t *protos,
+ protocol_type_t pr, uint32_t ver);
+
+/** Mapping between protocol type string and protocol type. */
+static const struct {
+ protocol_type_t protover_type;
+ const char *name;
+} PROTOCOL_NAMES[] = {
+ { PRT_LINK, "Link" },
+ { PRT_LINKAUTH, "LinkAuth" },
+ { PRT_RELAY, "Relay" },
+ { PRT_DIRCACHE, "DirCache" },
+ { PRT_HSDIR, "HSDir" },
+ { PRT_HSINTRO, "HSIntro" },
+ { PRT_HSREND, "HSRend" },
+ { PRT_DESC, "Desc" },
+ { PRT_MICRODESC, "Microdesc"},
+ { PRT_CONS, "Cons" }
+};
+
+#define N_PROTOCOL_NAMES ARRAY_LENGTH(PROTOCOL_NAMES)
+
+/**
+ * Given a protocol_type_t, return the corresponding string used in
+ * descriptors.
+ */
+STATIC const char *
+protocol_type_to_str(protocol_type_t pr)
+{
+ unsigned i;
+ for (i=0; i < N_PROTOCOL_NAMES; ++i) {
+ if (PROTOCOL_NAMES[i].protover_type == pr)
+ return PROTOCOL_NAMES[i].name;
+ }
+ /* LCOV_EXCL_START */
+ tor_assert_nonfatal_unreached_once();
+ return "UNKNOWN";
+ /* LCOV_EXCL_STOP */
+}
+
+/**
+ * Given a string, find the corresponding protocol type and store it in
+ * <b>pr_out</b>. Return 0 on success, -1 on failure.
+ */
+STATIC int
+str_to_protocol_type(const char *s, protocol_type_t *pr_out)
+{
+ if (BUG(!pr_out))
+ return -1;
+
+ unsigned i;
+ for (i=0; i < N_PROTOCOL_NAMES; ++i) {
+ if (0 == strcmp(s, PROTOCOL_NAMES[i].name)) {
+ *pr_out = PROTOCOL_NAMES[i].protover_type;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * Release all space held by a single proto_entry_t structure
+ */
+STATIC void
+proto_entry_free(proto_entry_t *entry)
+{
+ if (!entry)
+ return;
+ tor_free(entry->name);
+ SMARTLIST_FOREACH(entry->ranges, proto_range_t *, r, tor_free(r));
+ smartlist_free(entry->ranges);
+ tor_free(entry);
+}
+
+/**
+ * Given a string <b>s</b> and optional end-of-string pointer
+ * <b>end_of_range</b>, parse the protocol range and store it in
+ * <b>low_out</b> and <b>high_out</b>. A protocol range has the format U, or
+ * U-U, where U is an unsigned 32-bit integer.
+ */
+static int
+parse_version_range(const char *s, const char *end_of_range,
+ uint32_t *low_out, uint32_t *high_out)
+{
+ uint32_t low, high;
+ char *next = NULL;
+ int ok;
+
+ tor_assert(high_out);
+ tor_assert(low_out);
+
+ if (BUG(!end_of_range))
+ end_of_range = s + strlen(s); // LCOV_EXCL_LINE
+
+ /* Note that this wouldn't be safe if we didn't know that eventually,
+ * we'd hit a NUL */
+ low = (uint32_t) tor_parse_ulong(s, 10, 0, UINT32_MAX, &ok, &next);
+ if (!ok)
+ goto error;
+ if (next > end_of_range)
+ goto error;
+ if (next == end_of_range) {
+ high = low;
+ goto done;
+ }
+
+ if (*next != '-')
+ goto error;
+ s = next+1;
+ /* ibid */
+ high = (uint32_t) tor_parse_ulong(s, 10, 0, UINT32_MAX, &ok, &next);
+ if (!ok)
+ goto error;
+ if (next != end_of_range)
+ goto error;
+
+ done:
+ *high_out = high;
+ *low_out = low;
+ return 0;
+
+ error:
+ return -1;
+}
+
+/** Parse a single protocol entry from <b>s</b> up to an optional
+ * <b>end_of_entry</b> pointer, and return that protocol entry. Return NULL
+ * on error.
+ *
+ * A protocol entry has a keyword, an = sign, and zero or more ranges. */
+static proto_entry_t *
+parse_single_entry(const char *s, const char *end_of_entry)
+{
+ proto_entry_t *out = tor_malloc_zero(sizeof(proto_entry_t));
+ const char *equals;
+
+ out->ranges = smartlist_new();
+
+ if (BUG (!end_of_entry))
+ end_of_entry = s + strlen(s); // LCOV_EXCL_LINE
+
+ /* There must be an =. */
+ equals = memchr(s, '=', end_of_entry - s);
+ if (!equals)
+ goto error;
+
+ /* The name must be nonempty */
+ if (equals == s)
+ goto error;
+
+ out->name = tor_strndup(s, equals-s);
+
+ tor_assert(equals < end_of_entry);
+
+ s = equals + 1;
+ while (s < end_of_entry) {
+ const char *comma = memchr(s, ',', end_of_entry-s);
+ proto_range_t *range = tor_malloc_zero(sizeof(proto_range_t));
+ if (! comma)
+ comma = end_of_entry;
+
+ smartlist_add(out->ranges, range);
+ if (parse_version_range(s, comma, &range->low, &range->high) < 0) {
+ goto error;
+ }
+
+ if (range->low > range->high) {
+ goto error;
+ }
+
+ s = comma;
+ while (*s == ',' && s < end_of_entry)
+ ++s;
+ }
+
+ return out;
+
+ error:
+ proto_entry_free(out);
+ return NULL;
+}
+
+/**
+ * Parse the protocol list from <b>s</b> and return it as a smartlist of
+ * proto_entry_t
+ */
+STATIC smartlist_t *
+parse_protocol_list(const char *s)
+{
+ smartlist_t *entries = smartlist_new();
+
+ while (*s) {
+ /* Find the next space or the NUL. */
+ const char *end_of_entry = strchr(s, ' ');
+ proto_entry_t *entry;
+ if (!end_of_entry)
+ end_of_entry = s + strlen(s);
+
+ entry = parse_single_entry(s, end_of_entry);
+
+ if (! entry)
+ goto error;
+
+ smartlist_add(entries, entry);
+
+ s = end_of_entry;
+ while (*s == ' ')
+ ++s;
+ }
+
+ return entries;
+
+ error:
+ SMARTLIST_FOREACH(entries, proto_entry_t *, ent, proto_entry_free(ent));
+ smartlist_free(entries);
+ return NULL;
+}
+
+/**
+ * Given a protocol type and version number, return true iff we know
+ * how to speak that protocol.
+ */
+int
+protover_is_supported_here(protocol_type_t pr, uint32_t ver)
+{
+ const smartlist_t *ours = get_supported_protocol_list();
+ return protocol_list_contains(ours, pr, ver);
+}
+
+/**
+ * Return true iff "list" encodes a protocol list that includes support for
+ * the indicated protocol and version.
+ */
+int
+protocol_list_supports_protocol(const char *list, protocol_type_t tp,
+ uint32_t version)
+{
+ /* NOTE: This is a pretty inefficient implementation. If it ever shows
+ * up in profiles, we should memoize it.
+ */
+ smartlist_t *protocols = parse_protocol_list(list);
+ if (!protocols) {
+ return 0;
+ }
+ int contains = protocol_list_contains(protocols, tp, version);
+
+ SMARTLIST_FOREACH(protocols, proto_entry_t *, ent, proto_entry_free(ent));
+ smartlist_free(protocols);
+ return contains;
+}
+
+/** Return the canonical string containing the list of protocols
+ * that we support. */
+const char *
+protover_get_supported_protocols(void)
+{
+ return
+ "Cons=1-2 "
+ "Desc=1-2 "
+ "DirCache=1 "
+ "HSDir=1 "
+ "HSIntro=3 "
+ "HSRend=1-2 "
+ "Link=1-4 "
+ "LinkAuth=1 "
+ "Microdesc=1-2 "
+ "Relay=1-2";
+}
+
+/** The protocols from protover_get_supported_protocols(), as parsed into a
+ * list of proto_entry_t values. Access this via
+ * get_supported_protocol_list. */
+static smartlist_t *supported_protocol_list = NULL;
+
+/** Return a pointer to a smartlist of proto_entry_t for the protocols
+ * we support. */
+static const smartlist_t *
+get_supported_protocol_list(void)
+{
+ if (PREDICT_UNLIKELY(supported_protocol_list == NULL)) {
+ supported_protocol_list =
+ parse_protocol_list(protover_get_supported_protocols());
+ }
+ return supported_protocol_list;
+}
+
+/**
+ * Given a protocol entry, encode it at the end of the smartlist <b>chunks</b>
+ * as one or more newly allocated strings.
+ */
+static void
+proto_entry_encode_into(smartlist_t *chunks, const proto_entry_t *entry)
+{
+ smartlist_add_asprintf(chunks, "%s=", entry->name);
+
+ SMARTLIST_FOREACH_BEGIN(entry->ranges, proto_range_t *, range) {
+ const char *comma = "";
+ if (range_sl_idx != 0)
+ comma = ",";
+
+ if (range->low == range->high) {
+ smartlist_add_asprintf(chunks, "%s%lu",
+ comma, (unsigned long)range->low);
+ } else {
+ smartlist_add_asprintf(chunks, "%s%lu-%lu",
+ comma, (unsigned long)range->low,
+ (unsigned long)range->high);
+ }
+ } SMARTLIST_FOREACH_END(range);
+}
+
+/** Given a list of space-separated proto_entry_t items,
+ * encode it into a newly allocated space-separated string. */
+STATIC char *
+encode_protocol_list(const smartlist_t *sl)
+{
+ const char *separator = "";
+ smartlist_t *chunks = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(sl, const proto_entry_t *, ent) {
+ smartlist_add(chunks, tor_strdup(separator));
+
+ proto_entry_encode_into(chunks, ent);
+
+ separator = " ";
+ } SMARTLIST_FOREACH_END(ent);
+
+ char *result = smartlist_join_strings(chunks, "", 0, NULL);
+
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+
+ return result;
+}
+
+/* We treat any protocol list with more than this many subprotocols in it
+ * as a DoS attempt. */
+static const int MAX_PROTOCOLS_TO_EXPAND = (1<<16);
+
+/** Voting helper: Given a list of proto_entry_t, return a newly allocated
+ * smartlist of newly allocated strings, one for each included protocol
+ * version. (So 'Foo=3,5-7' expands to a list of 'Foo=3', 'Foo=5', 'Foo=6',
+ * 'Foo=7'.)
+ *
+ * Do not list any protocol version more than once.
+ *
+ * Return NULL if the list would be too big.
+ */
+static smartlist_t *
+expand_protocol_list(const smartlist_t *protos)
+{
+ smartlist_t *expanded = smartlist_new();
+ if (!protos)
+ return expanded;
+
+ SMARTLIST_FOREACH_BEGIN(protos, const proto_entry_t *, ent) {
+ const char *name = ent->name;
+ SMARTLIST_FOREACH_BEGIN(ent->ranges, const proto_range_t *, range) {
+ uint32_t u;
+ for (u = range->low; u <= range->high; ++u) {
+ smartlist_add_asprintf(expanded, "%s=%lu", name, (unsigned long)u);
+ if (smartlist_len(expanded) > MAX_PROTOCOLS_TO_EXPAND)
+ goto too_many;
+ }
+ } SMARTLIST_FOREACH_END(range);
+ } SMARTLIST_FOREACH_END(ent);
+
+ smartlist_sort_strings(expanded);
+ smartlist_uniq_strings(expanded); // This makes voting work. do not remove
+ return expanded;
+
+ too_many:
+ SMARTLIST_FOREACH(expanded, char *, cp, tor_free(cp));
+ smartlist_free(expanded);
+ return NULL;
+}
+
+/** Voting helper: compare two singleton proto_entry_t items by version
+ * alone. (A singleton item is one with a single range entry where
+ * low==high.) */
+static int
+cmp_single_ent_by_version(const void **a_, const void **b_)
+{
+ const proto_entry_t *ent_a = *a_;
+ const proto_entry_t *ent_b = *b_;
+
+ tor_assert(smartlist_len(ent_a->ranges) == 1);
+ tor_assert(smartlist_len(ent_b->ranges) == 1);
+
+ const proto_range_t *a = smartlist_get(ent_a->ranges, 0);
+ const proto_range_t *b = smartlist_get(ent_b->ranges, 0);
+
+ tor_assert(a->low == a->high);
+ tor_assert(b->low == b->high);
+
+ if (a->low < b->low) {
+ return -1;
+ } else if (a->low == b->low) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/** Voting helper: Given a list of singleton protocol strings (of the form
+ * Foo=7), return a canonical listing of all the protocol versions listed,
+ * with as few ranges as possible, with protocol versions sorted lexically and
+ * versions sorted in numerically increasing order, using as few range entries
+ * as possible.
+ **/
+static char *
+contract_protocol_list(const smartlist_t *proto_strings)
+{
+ // map from name to list of single-version entries
+ strmap_t *entry_lists_by_name = strmap_new();
+ // list of protocol names
+ smartlist_t *all_names = smartlist_new();
+ // list of strings for the output we're building
+ smartlist_t *chunks = smartlist_new();
+
+ // Parse each item and stick it entry_lists_by_name. Build
+ // 'all_names' at the same time.
+ SMARTLIST_FOREACH_BEGIN(proto_strings, const char *, s) {
+ if (BUG(!s))
+ continue;// LCOV_EXCL_LINE
+ proto_entry_t *ent = parse_single_entry(s, s+strlen(s));
+ if (BUG(!ent))
+ continue; // LCOV_EXCL_LINE
+ smartlist_t *lst = strmap_get(entry_lists_by_name, ent->name);
+ if (!lst) {
+ smartlist_add(all_names, ent->name);
+ lst = smartlist_new();
+ strmap_set(entry_lists_by_name, ent->name, lst);
+ }
+ smartlist_add(lst, ent);
+ } SMARTLIST_FOREACH_END(s);
+
+ // We want to output the protocols sorted by their name.
+ smartlist_sort_strings(all_names);
+
+ SMARTLIST_FOREACH_BEGIN(all_names, const char *, name) {
+ const int first_entry = (name_sl_idx == 0);
+ smartlist_t *lst = strmap_get(entry_lists_by_name, name);
+ tor_assert(lst);
+ // Sort every entry with this name by version. They are
+ // singletons, so there can't be overlap.
+ smartlist_sort(lst, cmp_single_ent_by_version);
+
+ if (! first_entry)
+ smartlist_add(chunks, tor_strdup(" "));
+
+ /* We're going to construct this entry from the ranges. */
+ proto_entry_t *entry = tor_malloc_zero(sizeof(proto_entry_t));
+ entry->ranges = smartlist_new();
+ entry->name = tor_strdup(name);
+
+ // Now, find all the ranges of versions start..end where
+ // all of start, start+1, start+2, ..end are included.
+ int start_of_cur_series = 0;
+ while (start_of_cur_series < smartlist_len(lst)) {
+ const proto_entry_t *ent = smartlist_get(lst, start_of_cur_series);
+ const proto_range_t *range = smartlist_get(ent->ranges, 0);
+ const uint32_t ver_low = range->low;
+ uint32_t ver_high = ver_low;
+
+ int idx;
+ for (idx = start_of_cur_series+1; idx < smartlist_len(lst); ++idx) {
+ ent = smartlist_get(lst, idx);
+ range = smartlist_get(ent->ranges, 0);
+ if (range->low != ver_high + 1)
+ break;
+ ver_high += 1;
+ }
+
+ // Now idx is either off the end of the list, or the first sequence
+ // break in the list.
+ start_of_cur_series = idx;
+
+ proto_range_t *new_range = tor_malloc_zero(sizeof(proto_range_t));
+ new_range->low = ver_low;
+ new_range->high = ver_high;
+ smartlist_add(entry->ranges, new_range);
+ }
+ proto_entry_encode_into(chunks, entry);
+ proto_entry_free(entry);
+
+ } SMARTLIST_FOREACH_END(name);
+
+ // Build the result...
+ char *result = smartlist_join_strings(chunks, "", 0, NULL);
+
+ // And free all the stuff we allocated.
+ SMARTLIST_FOREACH_BEGIN(all_names, const char *, name) {
+ smartlist_t *lst = strmap_get(entry_lists_by_name, name);
+ tor_assert(lst);
+ SMARTLIST_FOREACH(lst, proto_entry_t *, e, proto_entry_free(e));
+ smartlist_free(lst);
+ } SMARTLIST_FOREACH_END(name);
+
+ strmap_free(entry_lists_by_name, NULL);
+ smartlist_free(all_names);
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+
+ return result;
+}
+
+/**
+ * Protocol voting implementation.
+ *
+ * Given a list of strings describing protocol versions, return a newly
+ * allocated string encoding all of the protocols that are listed by at
+ * least <b>threshold</b> of the inputs.
+ *
+ * The string is minimal and sorted according to the rules of
+ * contract_protocol_list above.
+ */
+char *
+protover_compute_vote(const smartlist_t *list_of_proto_strings,
+ int threshold)
+{
+ smartlist_t *all_entries = smartlist_new();
+
+ // First, parse the inputs and break them into singleton entries.
+ SMARTLIST_FOREACH_BEGIN(list_of_proto_strings, const char *, vote) {
+ smartlist_t *unexpanded = parse_protocol_list(vote);
+ smartlist_t *this_vote = expand_protocol_list(unexpanded);
+ if (this_vote == NULL) {
+ log_warn(LD_NET, "When expanding a protocol list from an authority, I "
+ "got too many protocols. This is possibly an attack or a bug, "
+ "unless the Tor network truly has expanded to support over %d "
+ "different subprotocol versions. The offending string was: %s",
+ MAX_PROTOCOLS_TO_EXPAND, escaped(vote));
+ } else {
+ smartlist_add_all(all_entries, this_vote);
+ smartlist_free(this_vote);
+ }
+ SMARTLIST_FOREACH(unexpanded, proto_entry_t *, e, proto_entry_free(e));
+ smartlist_free(unexpanded);
+ } SMARTLIST_FOREACH_END(vote);
+
+ // Now sort the singleton entries
+ smartlist_sort_strings(all_entries);
+
+ // Now find all the strings that appear at least 'threshold' times.
+ smartlist_t *include_entries = smartlist_new();
+ const char *cur_entry = smartlist_get(all_entries, 0);
+ int n_times = 0;
+ SMARTLIST_FOREACH_BEGIN(all_entries, const char *, ent) {
+ if (!strcmp(ent, cur_entry)) {
+ n_times++;
+ } else {
+ if (n_times >= threshold && cur_entry)
+ smartlist_add(include_entries, (void*)cur_entry);
+ cur_entry = ent;
+ n_times = 1 ;
+ }
+ } SMARTLIST_FOREACH_END(ent);
+
+ if (n_times >= threshold && cur_entry)
+ smartlist_add(include_entries, (void*)cur_entry);
+
+ // Finally, compress that list.
+ char *result = contract_protocol_list(include_entries);
+ smartlist_free(include_entries);
+ SMARTLIST_FOREACH(all_entries, char *, cp, tor_free(cp));
+ smartlist_free(all_entries);
+
+ return result;
+}
+
+/** Return true if every protocol version described in the string <b>s</b> is
+ * one that we support, and false otherwise. If <b>missing_out</b> is
+ * provided, set it to the list of protocols we do not support.
+ *
+ * NOTE: This is quadratic, but we don't do it much: only a few times per
+ * consensus. Checking signatures should be way more expensive than this
+ * ever would be.
+ **/
+int
+protover_all_supported(const char *s, char **missing_out)
+{
+ int all_supported = 1;
+ smartlist_t *missing;
+
+ if (!s) {
+ return 1;
+ }
+
+ smartlist_t *entries = parse_protocol_list(s);
+
+ missing = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(entries, const proto_entry_t *, ent) {
+ protocol_type_t tp;
+ if (str_to_protocol_type(ent->name, &tp) < 0) {
+ if (smartlist_len(ent->ranges)) {
+ goto unsupported;
+ }
+ continue;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(ent->ranges, const proto_range_t *, range) {
+ uint32_t i;
+ for (i = range->low; i <= range->high; ++i) {
+ if (!protover_is_supported_here(tp, i)) {
+ goto unsupported;
+ }
+ }
+ } SMARTLIST_FOREACH_END(range);
+
+ continue;
+
+ unsupported:
+ all_supported = 0;
+ smartlist_add(missing, (void*) ent);
+ } SMARTLIST_FOREACH_END(ent);
+
+ if (missing_out && !all_supported) {
+ tor_assert(0 != smartlist_len(missing));
+ *missing_out = encode_protocol_list(missing);
+ }
+ smartlist_free(missing);
+
+ SMARTLIST_FOREACH(entries, proto_entry_t *, ent, proto_entry_free(ent));
+ smartlist_free(entries);
+
+ return all_supported;
+}
+
+/** Helper: Given a list of proto_entry_t, return true iff
+ * <b>pr</b>=<b>ver</b> is included in that list. */
+static int
+protocol_list_contains(const smartlist_t *protos,
+ protocol_type_t pr, uint32_t ver)
+{
+ if (BUG(protos == NULL)) {
+ return 0; // LCOV_EXCL_LINE
+ }
+ const char *pr_name = protocol_type_to_str(pr);
+ if (BUG(pr_name == NULL)) {
+ return 0; // LCOV_EXCL_LINE
+ }
+
+ SMARTLIST_FOREACH_BEGIN(protos, const proto_entry_t *, ent) {
+ if (strcasecmp(ent->name, pr_name))
+ continue;
+ /* name matches; check the ranges */
+ SMARTLIST_FOREACH_BEGIN(ent->ranges, const proto_range_t *, range) {
+ if (ver >= range->low && ver <= range->high)
+ return 1;
+ } SMARTLIST_FOREACH_END(range);
+ } SMARTLIST_FOREACH_END(ent);
+
+ return 0;
+}
+
+/** Return a string describing the protocols supported by tor version
+ * <b>version</b>, or an empty string if we cannot tell.
+ *
+ * Note that this is only used to infer protocols for Tor versions that
+ * can't declare their own.
+ **/
+const char *
+protover_compute_for_old_tor(const char *version)
+{
+ if (version == NULL) {
+ /* No known version; guess the oldest series that is still supported. */
+ version = "0.2.5.15";
+ }
+
+ if (tor_version_as_new_as(version,
+ FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS)) {
+ return "";
+ } else if (tor_version_as_new_as(version, "0.2.9.1-alpha")) {
+ /* 0.2.9.1-alpha HSRend=2 */
+ return "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 "
+ "Link=1-4 LinkAuth=1 "
+ "Microdesc=1-2 Relay=1-2";
+ } else if (tor_version_as_new_as(version, "0.2.7.5")) {
+ /* 0.2.7-stable added Desc=2, Microdesc=2, Cons=2, which indicate
+ * ed25519 support. We'll call them present only in "stable" 027,
+ * though. */
+ return "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=1-4 LinkAuth=1 "
+ "Microdesc=1-2 Relay=1-2";
+ } else if (tor_version_as_new_as(version, "0.2.4.19")) {
+ /* No currently supported Tor server versions are older than this, or
+ * lack these protocols. */
+ return "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=1-4 LinkAuth=1 "
+ "Microdesc=1 Relay=1-2";
+ } else {
+ /* Cannot infer protocols. */
+ return "";
+ }
+}
+
+/**
+ * Release all storage held by static fields in protover.c
+ */
+void
+protover_free_all(void)
+{
+ if (supported_protocol_list) {
+ smartlist_t *entries = supported_protocol_list;
+ SMARTLIST_FOREACH(entries, proto_entry_t *, ent, proto_entry_free(ent));
+ smartlist_free(entries);
+ supported_protocol_list = NULL;
+ }
+}
+
diff --git a/src/or/protover.h b/src/or/protover.h
new file mode 100644
index 0000000000..5c658931ea
--- /dev/null
+++ b/src/or/protover.h
@@ -0,0 +1,74 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file protover.h
+ * \brief Headers and type declarations for protover.c
+ **/
+
+#ifndef TOR_PROTOVER_H
+#define TOR_PROTOVER_H
+
+#include "container.h"
+
+/** The first version of Tor that included "proto" entries in its
+ * descriptors. Authorities should use this to decide whether to
+ * guess proto lines. */
+/* This is a guess. */
+#define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha"
+
+/** List of recognized subprotocols. */
+typedef enum protocol_type_t {
+ PRT_LINK,
+ PRT_LINKAUTH,
+ PRT_RELAY,
+ PRT_DIRCACHE,
+ PRT_HSDIR,
+ PRT_HSINTRO,
+ PRT_HSREND,
+ PRT_DESC,
+ PRT_MICRODESC,
+ PRT_CONS,
+} protocol_type_t;
+
+int protover_all_supported(const char *s, char **missing);
+int protover_is_supported_here(protocol_type_t pr, uint32_t ver);
+const char *protover_get_supported_protocols(void);
+
+char *protover_compute_vote(const smartlist_t *list_of_proto_strings,
+ int threshold);
+const char *protover_compute_for_old_tor(const char *version);
+int protocol_list_supports_protocol(const char *list, protocol_type_t tp,
+ uint32_t version);
+
+void protover_free_all(void);
+
+#ifdef PROTOVER_PRIVATE
+/** Represents a range of subprotocols of a given type. All subprotocols
+ * between <b>low</b> and <b>high</b> inclusive are included. */
+typedef struct proto_range_t {
+ uint32_t low;
+ uint32_t high;
+} proto_range_t;
+
+/** Represents a set of ranges of subprotocols of a given type. */
+typedef struct proto_entry_t {
+ /** The name of the protocol.
+ *
+ * (This needs to handle voting on protocols which
+ * we don't recognize yet, so it's a char* rather than a protocol_type_t.)
+ */
+ char *name;
+ /** Smartlist of proto_range_t */
+ smartlist_t *ranges;
+} proto_entry_t;
+
+STATIC smartlist_t *parse_protocol_list(const char *s);
+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
+
diff --git a/src/or/reasons.c b/src/or/reasons.c
index 36921cafcd..a1566e2299 100644
--- a/src/or/reasons.c
+++ b/src/or/reasons.c
@@ -6,6 +6,12 @@
* \file reasons.c
* \brief Convert circuit, stream, and orconn error reasons to and/or from
* strings and errno values.
+ *
+ * This module is just a bunch of functions full of case statements that
+ * convert from one representation of our error codes to another. These are
+ * mainly used in generating log messages, in sending messages to the
+ * controller in control.c, and in converting errors from one protocol layer
+ * to another.
**/
#include "or.h"
diff --git a/src/or/relay.c b/src/or/relay.c
index a992b6e08e..3bf740348a 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -255,12 +255,12 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
if (! CIRCUIT_IS_ORIGIN(circ) &&
TO_OR_CIRCUIT(circ)->rend_splice &&
cell_direction == CELL_DIRECTION_OUT) {
- or_circuit_t *splice = TO_OR_CIRCUIT(circ)->rend_splice;
+ or_circuit_t *splice_ = TO_OR_CIRCUIT(circ)->rend_splice;
tor_assert(circ->purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED);
- tor_assert(splice->base_.purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED);
- cell->circ_id = splice->p_circ_id;
+ tor_assert(splice_->base_.purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED);
+ cell->circ_id = splice_->p_circ_id;
cell->command = CELL_RELAY; /* can't be relay_early anyway */
- if ((reason = circuit_receive_relay_cell(cell, TO_CIRCUIT(splice),
+ if ((reason = circuit_receive_relay_cell(cell, TO_CIRCUIT(splice_),
CELL_DIRECTION_IN)) < 0) {
log_warn(LD_REND, "Error relaying cell across rendezvous; closing "
"circuits");
@@ -383,6 +383,11 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
{
channel_t *chan; /* where to send the cell */
+ if (circ->marked_for_close) {
+ /* Circuit is marked; send nothing. */
+ return 0;
+ }
+
if (cell_direction == CELL_DIRECTION_OUT) {
crypt_path_t *thishop; /* counter for repeated crypts */
chan = circ->n_chan;
@@ -696,6 +701,12 @@ connection_edge_send_command(edge_connection_t *fromconn,
return -1;
}
+ if (circ->marked_for_close) {
+ /* The circuit has been marked, but not freed yet. When it's freed, it
+ * will mark this connection for close. */
+ return -1;
+ }
+
return relay_send_command_from_edge(fromconn->stream_id, circ,
relay_command, payload,
payload_len, cpath_layer);
@@ -859,6 +870,7 @@ connection_ap_process_end_not_open(
break; /* break means it'll close, below */
/* Else fall through: expire this circuit, clear the
* chosen_exit_name field, and try again. */
+ /* Falls through. */
case END_STREAM_REASON_RESOLVEFAILED:
case END_STREAM_REASON_TIMEOUT:
case END_STREAM_REASON_MISC:
@@ -1374,7 +1386,7 @@ connection_edge_process_relay_cell_not_open(
/* This is definitely a success, so forget about any pending data we
* had sent. */
if (entry_conn->pending_optimistic_data) {
- generic_buffer_free(entry_conn->pending_optimistic_data);
+ buf_free(entry_conn->pending_optimistic_data);
entry_conn->pending_optimistic_data = NULL;
}
@@ -1877,7 +1889,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
entry_conn->sending_optimistic_data != NULL;
if (PREDICT_UNLIKELY(sending_from_optimistic)) {
- bytes_to_process = generic_buffer_len(entry_conn->sending_optimistic_data);
+ bytes_to_process = buf_datalen(entry_conn->sending_optimistic_data);
if (PREDICT_UNLIKELY(!bytes_to_process)) {
log_warn(LD_BUG, "sending_optimistic_data was non-NULL but empty");
bytes_to_process = connection_get_inbuf_len(TO_CONN(conn));
@@ -1905,9 +1917,9 @@ 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. */
- generic_buffer_get(entry_conn->sending_optimistic_data, payload, length);
- if (!generic_buffer_len(entry_conn->sending_optimistic_data)) {
- generic_buffer_free(entry_conn->sending_optimistic_data);
+ fetch_from_buf(payload, length, entry_conn->sending_optimistic_data);
+ if (!buf_datalen(entry_conn->sending_optimistic_data)) {
+ buf_free(entry_conn->sending_optimistic_data);
entry_conn->sending_optimistic_data = NULL;
}
} else {
@@ -1922,8 +1934,8 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
/* This is new optimistic data; remember it in case we need to detach and
retry */
if (!entry_conn->pending_optimistic_data)
- entry_conn->pending_optimistic_data = generic_buffer_new();
- generic_buffer_add(entry_conn->pending_optimistic_data, payload, length);
+ entry_conn->pending_optimistic_data = buf_new();
+ write_to_buf(payload, length, entry_conn->pending_optimistic_data);
}
if (connection_edge_send_command(conn, RELAY_COMMAND_DATA,
@@ -2321,14 +2333,12 @@ cell_queue_append_packed_copy(circuit_t *circ, cell_queue_t *queue,
int exitward, const cell_t *cell,
int wide_circ_ids, int use_stats)
{
- struct timeval now;
packed_cell_t *copy = packed_cell_copy(cell, wide_circ_ids);
(void)circ;
(void)exitward;
(void)use_stats;
- tor_gettimeofday_cached_monotonic(&now);
- copy->inserted_time = (uint32_t)tv_to_msec(&now);
+ copy->inserted_time = (uint32_t) monotime_coarse_absolute_msec();
cell_queue_append(queue, copy);
}
@@ -2526,7 +2536,7 @@ update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction,
/* Cmux sanity check */
if (! circuitmux_is_circuit_attached(cmux, circ)) {
- log_warn(LD_BUG, "called on non-attachd circuit from %s:%d",
+ log_warn(LD_BUG, "called on non-attached circuit from %s:%d",
file, lineno);
return;
}
@@ -2595,7 +2605,7 @@ set_streams_blocked_on_circ(circuit_t *circ, channel_t *chan,
edge->edge_blocked_on_circ = block;
}
- if (!conn->read_event && !HAS_BUFFEREVENT(conn)) {
+ if (!conn->read_event) {
/* This connection is a placeholder for something; probably a DNS
* request. It can't actually stop or start reading.*/
continue;
@@ -2716,9 +2726,8 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
if (get_options()->CellStatistics ||
get_options()->TestingEnableCellStatsEvent) {
uint32_t msec_waiting;
- struct timeval tvnow;
- tor_gettimeofday_cached(&tvnow);
- msec_waiting = ((uint32_t)tv_to_msec(&tvnow)) - cell->inserted_time;
+ uint32_t msec_now = (uint32_t)monotime_coarse_absolute_msec();
+ msec_waiting = msec_now - cell->inserted_time;
if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) {
or_circ = TO_OR_CIRCUIT(circ);
diff --git a/src/or/rendcache.c b/src/or/rendcache.c
index f9ae6d1173..aa69d735fe 100644
--- a/src/or/rendcache.c
+++ b/src/or/rendcache.c
@@ -958,25 +958,25 @@ rend_cache_store_v2_desc_as_client(const char *desc,
* avoid an evil HSDir serving old descriptor. We validate if the
* timestamp is greater than and not equal because it's a rounded down
* timestamp to the hour so if the descriptor changed in the same hour,
- * the rend cache failure will tells us if we have a new descriptor. */
+ * the rend cache failure will tell us if we have a new descriptor. */
if (e && e->parsed->timestamp > parsed->timestamp) {
log_info(LD_REND, "We already have a new enough service descriptor for "
"service ID %s with the same desc ID and version.",
safe_str_client(service_id));
goto okay;
}
- /* Lookup our failure cache for intro point that might be unsuable. */
+ /* Lookup our failure cache for intro point that might be unusable. */
validate_intro_point_failure(parsed, service_id);
- /* It's now possible that our intro point list is empty, this means that
+ /* It's now possible that our intro point list is empty, which means that
* this descriptor is useless to us because intro points have all failed
* somehow before. Discard the descriptor. */
if (smartlist_len(parsed->intro_nodes) == 0) {
- log_info(LD_REND, "Service descriptor with service ID %s, every "
- "intro points are unusable. Discarding it.",
+ log_info(LD_REND, "Service descriptor with service ID %s has no "
+ "usable intro points. Discarding it.",
safe_str_client(service_id));
goto err;
}
- /* Now either purge the current one and replace it's content or create a
+ /* Now either purge the current one and replace its content or create a
* new one and add it to the rend cache. */
if (!e) {
e = tor_malloc_zero(sizeof(rend_cache_entry_t));
diff --git a/src/or/rendcache.h b/src/or/rendcache.h
index 0e8b918753..270b614c38 100644
--- a/src/or/rendcache.h
+++ b/src/or/rendcache.h
@@ -102,6 +102,13 @@ STATIC void validate_intro_point_failure(const rend_service_descriptor_t *desc,
const char *service_id);
STATIC void rend_cache_failure_entry_free_(void *entry);
+
+#ifdef TOR_UNIT_TESTS
+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 /* TOR_RENDCACHE_H */
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index 609c45c71d..a93bc94a9c 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -134,6 +134,7 @@ int
rend_client_send_introduction(origin_circuit_t *introcirc,
origin_circuit_t *rendcirc)
{
+ const or_options_t *options = get_options();
size_t payload_len;
int r, v3_shift = 0;
char payload[RELAY_PAYLOAD_SIZE];
@@ -150,10 +151,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
tor_assert(rendcirc->rend_data);
tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
rendcirc->rend_data->onion_address));
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(introcirc->build_state->onehop_tunnel));
- tor_assert(!(rendcirc->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(introcirc, options);
+ assert_circ_anonymity_ok(rendcirc, options);
r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
&entry);
@@ -387,6 +386,7 @@ int
rend_client_introduction_acked(origin_circuit_t *circ,
const uint8_t *request, size_t request_len)
{
+ const or_options_t *options = get_options();
origin_circuit_t *rendcirc;
(void) request; // XXXX Use this.
@@ -398,10 +398,9 @@ rend_client_introduction_acked(origin_circuit_t *circ,
return -1;
}
+ tor_assert(circ->build_state);
tor_assert(circ->build_state->chosen_exit);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(circ->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(circ, options);
tor_assert(circ->rend_data);
/* For path bias: This circuit was used successfully. Valid
@@ -416,9 +415,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
log_info(LD_REND,"Received ack. Telling rend circ...");
rendcirc = circuit_get_ready_rend_circ_by_rend_data(circ->rend_data);
if (rendcirc) { /* remember the ack */
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(rendcirc->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(rendcirc, options);
circuit_change_purpose(TO_CIRCUIT(rendcirc),
CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
/* Set timestamp_dirty, because circuit_expire_building expects
@@ -469,6 +466,23 @@ rend_client_introduction_acked(origin_circuit_t *circ,
/** 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
@@ -510,7 +524,7 @@ lookup_last_hid_serv_request(routerstatus_t *hs_dir,
tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s",
hsdir_id_base32,
desc_id_base32);
- /* XXX023 tor_assert(strlen(hsdir_desc_comb_id) ==
+ /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) ==
LAST_HID_SERV_REQUEST_KEY_LEN); */
if (set) {
time_t *oldptr;
@@ -532,7 +546,7 @@ static void
directory_clean_last_hid_serv_requests(time_t now)
{
strmap_iter_t *iter;
- time_t cutoff = now - REND_HID_SERV_DIR_REQUERY_PERIOD;
+ 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); ) {
@@ -572,7 +586,7 @@ purge_hid_serv_from_last_hid_serv_requests(const char *desc_id)
const char *key;
void *val;
strmap_iter_get(iter, &key, &val);
- /* XXX023 tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */
+ /* 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,
@@ -635,7 +649,7 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32)
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 + REND_HID_SERV_DIR_REQUERY_PERIOD >= now ||
+ if (last + hsdir_requery_period(options) >= now ||
!node || !node_has_descriptor(node)) {
SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
continue;
@@ -813,9 +827,9 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
while (tries_left > 0) {
- int rand = crypto_rand_int(tries_left);
- int chosen_replica = replicas_left_to_try[rand];
- replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
+ int rand_val = crypto_rand_int(tries_left);
+ int chosen_replica = replicas_left_to_try[rand_val];
+ replicas_left_to_try[rand_val] = replicas_left_to_try[--tries_left];
ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address,
query->auth_type == REND_STEALTH_AUTH ?
@@ -895,12 +909,6 @@ rend_client_refetch_v2_renddesc(rend_data_t *rend_query)
rend_cache_entry_t *e = NULL;
tor_assert(rend_query);
- /* Are we configured to fetch descriptors? */
- if (!get_options()->FetchHidServDescriptors) {
- log_warn(LD_REND, "We received an onion address for a v2 rendezvous "
- "service descriptor, but are not fetching service descriptors.");
- return;
- }
/* Before fetching, check if we already have a usable descriptor here. */
if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) == 0 &&
rend_client_any_intro_points_usable(e)) {
@@ -908,6 +916,12 @@ rend_client_refetch_v2_renddesc(rend_data_t *rend_query)
"already have a usable descriptor here. Not fetching.");
return;
}
+ /* Are we configured to fetch descriptors? */
+ if (!get_options()->FetchHidServDescriptors) {
+ log_warn(LD_REND, "We received an onion address for a v2 rendezvous "
+ "service descriptor, but are not fetching service descriptors.");
+ return;
+ }
log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
safe_str_client(rend_query->onion_address));
@@ -1099,7 +1113,7 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request,
* 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
+ /* 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
@@ -1351,40 +1365,20 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry,
i = crypto_rand_int(smartlist_len(usable_nodes));
intro = smartlist_get(usable_nodes, i);
- /* Do we need to look up the router or is the extend info complete? */
- if (!intro->extend_info->onion_key) {
- const node_t *node;
- extend_info_t *new_extend_info;
- if (tor_digest_is_zero(intro->extend_info->identity_digest))
- node = node_get_by_hex_id(intro->extend_info->nickname);
- else
- node = node_get_by_id(intro->extend_info->identity_digest);
- if (!node) {
- log_info(LD_REND, "Unknown router with nickname '%s'; trying another.",
- intro->extend_info->nickname);
- smartlist_del(usable_nodes, i);
- goto again;
- }
-#ifdef ENABLE_TOR2WEB_MODE
- new_extend_info = extend_info_from_node(node, options->Tor2webMode);
-#else
- new_extend_info = extend_info_from_node(node, 0);
-#endif
- if (!new_extend_info) {
- const char *alternate_reason = "";
-#ifdef ENABLE_TOR2WEB_MODE
- alternate_reason = ", or we cannot connect directly to it";
-#endif
- log_info(LD_REND, "We don't have a descriptor for the intro-point relay "
- "'%s'%s; trying another.",
- extend_info_describe(intro->extend_info), alternate_reason);
- smartlist_del(usable_nodes, i);
- goto again;
- } else {
- extend_info_free(intro->extend_info);
- intro->extend_info = new_extend_info;
- }
- tor_assert(intro->extend_info != NULL);
+ if (BUG(!intro->extend_info)) {
+ /* This should never happen, but it isn't fatal, just try another */
+ smartlist_del(usable_nodes, i);
+ goto again;
+ }
+ /* All version 2 HS descriptors come with a TAP onion key.
+ * Clients used to try to get the TAP onion key from the consensus, but this
+ * meant that hidden services could discover which consensus clients have. */
+ if (!extend_info_supports_tap(intro->extend_info)) {
+ log_info(LD_REND, "The HS descriptor is missing a TAP onion key for the "
+ "intro-point relay '%s'; trying another.",
+ safe_str_client(extend_info_describe(intro->extend_info)));
+ smartlist_del(usable_nodes, i);
+ goto again;
}
/* Check if we should refuse to talk to this router. */
if (strict &&
@@ -1466,12 +1460,10 @@ rend_parse_service_authorization(const or_options_t *options,
strmap_t *parsed = strmap_new();
smartlist_t *sl = smartlist_new();
rend_service_authorization_t *auth = NULL;
- char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2];
- char descriptor_cookie_base64ext[REND_DESC_COOKIE_LEN_BASE64+2+1];
+ char *err_msg = NULL;
for (line = options->HidServAuth; line; line = line->next) {
char *onion_address, *descriptor_cookie;
- int auth_type_val = 0;
auth = NULL;
SMARTLIST_FOREACH(sl, char *, c, tor_free(c););
smartlist_clear(sl);
@@ -1500,31 +1492,13 @@ rend_parse_service_authorization(const or_options_t *options,
}
/* Parse descriptor cookie. */
descriptor_cookie = smartlist_get(sl, 1);
- if (strlen(descriptor_cookie) != REND_DESC_COOKIE_LEN_BASE64) {
- log_warn(LD_CONFIG, "Authorization cookie has wrong length: '%s'",
- descriptor_cookie);
- goto err;
- }
- /* Add trailing zero bytes (AA) to make base64-decoding happy. */
- tor_snprintf(descriptor_cookie_base64ext,
- REND_DESC_COOKIE_LEN_BASE64+2+1,
- "%sAA", descriptor_cookie);
- if (base64_decode(descriptor_cookie_tmp, sizeof(descriptor_cookie_tmp),
- descriptor_cookie_base64ext,
- strlen(descriptor_cookie_base64ext)) < 0) {
- log_warn(LD_CONFIG, "Decoding authorization cookie failed: '%s'",
- descriptor_cookie);
+ if (rend_auth_decode_cookie(descriptor_cookie, auth->descriptor_cookie,
+ &auth->auth_type, &err_msg) < 0) {
+ tor_assert(err_msg);
+ log_warn(LD_CONFIG, "%s", err_msg);
+ tor_free(err_msg);
goto err;
}
- auth_type_val = (((uint8_t)descriptor_cookie_tmp[16]) >> 4) + 1;
- if (auth_type_val < 1 || auth_type_val > 2) {
- log_warn(LD_CONFIG, "Authorization cookie has unknown authorization "
- "type encoded.");
- goto err;
- }
- auth->auth_type = auth_type_val == 1 ? REND_BASIC_AUTH : REND_STEALTH_AUTH;
- memcpy(auth->descriptor_cookie, descriptor_cookie_tmp,
- REND_DESC_COOKIE_LEN);
if (strmap_get(parsed, auth->onion_address)) {
log_warn(LD_CONFIG, "Duplicate authorization for the same hidden "
"service.");
@@ -1547,8 +1521,38 @@ rend_parse_service_authorization(const or_options_t *options,
} else {
strmap_free(parsed, rend_service_authorization_strmap_item_free);
}
- memwipe(descriptor_cookie_tmp, 0, sizeof(descriptor_cookie_tmp));
- memwipe(descriptor_cookie_base64ext, 0, sizeof(descriptor_cookie_base64ext));
return res;
}
+/* Can Tor client code make direct (non-anonymous) connections to introduction
+ * or rendezvous points?
+ * Returns true if tor was compiled with NON_ANONYMOUS_MODE_ENABLED, and is
+ * configured in Tor2web mode. */
+int
+rend_client_allow_non_anonymous_connection(const or_options_t *options)
+{
+ /* Tor2web support needs to be compiled in to a tor binary. */
+#ifdef NON_ANONYMOUS_MODE_ENABLED
+ /* Tor2web */
+ return options->Tor2webMode ? 1 : 0;
+#else
+ (void)options;
+ return 0;
+#endif
+}
+
+/* At compile-time, was non-anonymous mode enabled via
+ * NON_ANONYMOUS_MODE_ENABLED ? */
+int
+rend_client_non_anonymous_mode_enabled(const or_options_t *options)
+{
+ (void)options;
+ /* Tor2web support needs to be compiled in to a tor binary. */
+#ifdef NON_ANONYMOUS_MODE_ENABLED
+ /* Tor2web */
+ return 1;
+#else
+ return 0;
+#endif
+}
+
diff --git a/src/or/rendclient.h b/src/or/rendclient.h
index e90dac07ab..b8f8c2f871 100644
--- a/src/or/rendclient.h
+++ b/src/or/rendclient.h
@@ -51,5 +51,8 @@ rend_service_authorization_t *rend_client_lookup_service_authorization(
const char *onion_address);
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
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index 438fbc4d9a..d9d39b1f19 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -211,7 +211,7 @@ rend_encode_v2_intro_points(char **encoded, rend_service_descriptor_t *desc)
goto done;
}
/* Assemble everything for this introduction point. */
- address = tor_dup_addr(&info->addr);
+ address = tor_addr_to_str_dup(&info->addr);
res = tor_snprintf(unenc + unenc_written, unenc_len - unenc_written,
"introduction-point %s\n"
"ip-address %s\n"
@@ -720,6 +720,22 @@ rend_valid_descriptor_id(const char *query)
return 0;
}
+/** Return true iff <b>client_name</b> is a syntactically valid name
+ * for rendezvous client authentication. */
+int
+rend_valid_client_name(const char *client_name)
+{
+ size_t len = strlen(client_name);
+ if (len < 1 || len > REND_CLIENTNAME_MAX_LEN) {
+ return 0;
+ }
+ if (strspn(client_name, REND_LEGAL_CLIENTNAME_CHARACTERS) != len) {
+ return 0;
+ }
+
+ return 1;
+}
+
/** Called when we get a rendezvous-related relay cell on circuit
* <b>circ</b>. Dispatch on rendezvous relay command. */
void
@@ -941,3 +957,162 @@ hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
return smartlist_len(responsible_dirs) ? 0 : -1;
}
+/* Length of the 'extended' auth cookie used to encode auth type before
+ * base64 encoding. */
+#define REND_DESC_COOKIE_LEN_EXT (REND_DESC_COOKIE_LEN + 1)
+/* Length of the zero-padded auth cookie when base64 encoded. These two
+ * padding bytes always (A=) are stripped off of the returned cookie. */
+#define REND_DESC_COOKIE_LEN_EXT_BASE64 (REND_DESC_COOKIE_LEN_BASE64 + 2)
+
+/** Encode a client authorization descriptor cookie.
+ * The result of this function is suitable for use in the HidServAuth
+ * option. The trailing padding characters are removed, and the
+ * auth type is encoded into the cookie.
+ *
+ * Returns a new base64-encoded cookie. This function cannot fail.
+ * The caller is responsible for freeing the returned value.
+ */
+char *
+rend_auth_encode_cookie(const uint8_t *cookie_in, rend_auth_type_t auth_type)
+{
+ uint8_t extended_cookie[REND_DESC_COOKIE_LEN_EXT];
+ char *cookie_out = tor_malloc_zero(REND_DESC_COOKIE_LEN_EXT_BASE64 + 1);
+ int re;
+
+ tor_assert(cookie_in);
+
+ memcpy(extended_cookie, cookie_in, REND_DESC_COOKIE_LEN);
+ extended_cookie[REND_DESC_COOKIE_LEN] = ((int)auth_type - 1) << 4;
+ re = base64_encode(cookie_out, REND_DESC_COOKIE_LEN_EXT_BASE64 + 1,
+ (const char *) extended_cookie, REND_DESC_COOKIE_LEN_EXT,
+ 0);
+ tor_assert(re == REND_DESC_COOKIE_LEN_EXT_BASE64);
+
+ /* Remove the trailing 'A='. Auth type is encoded in the high bits
+ * of the last byte, so the last base64 character will always be zero
+ * (A). This is subtly different behavior from base64_encode_nopad. */
+ cookie_out[REND_DESC_COOKIE_LEN_BASE64] = '\0';
+ memwipe(extended_cookie, 0, sizeof(extended_cookie));
+ return cookie_out;
+}
+
+/** Decode a base64-encoded client authorization descriptor cookie.
+ * The descriptor_cookie can be truncated to REND_DESC_COOKIE_LEN_BASE64
+ * characters (as given to clients), or may include the two padding
+ * characters (as stored by the service).
+ *
+ * The result is stored in REND_DESC_COOKIE_LEN bytes of cookie_out.
+ * The rend_auth_type_t decoded from the cookie is stored in the
+ * optional auth_type_out parameter.
+ *
+ * Return 0 on success, or -1 on error. The caller is responsible for
+ * freeing the returned err_msg.
+ */
+int
+rend_auth_decode_cookie(const char *cookie_in, uint8_t *cookie_out,
+ rend_auth_type_t *auth_type_out, char **err_msg_out)
+{
+ uint8_t descriptor_cookie_decoded[REND_DESC_COOKIE_LEN_EXT + 1] = { 0 };
+ char descriptor_cookie_base64ext[REND_DESC_COOKIE_LEN_EXT_BASE64 + 1];
+ const char *descriptor_cookie = cookie_in;
+ char *err_msg = NULL;
+ int auth_type_val = 0;
+ int res = -1;
+ int decoded_len;
+
+ size_t len = strlen(descriptor_cookie);
+ if (len == REND_DESC_COOKIE_LEN_BASE64) {
+ /* Add a trailing zero byte to make base64-decoding happy. */
+ tor_snprintf(descriptor_cookie_base64ext,
+ sizeof(descriptor_cookie_base64ext),
+ "%sA=", descriptor_cookie);
+ descriptor_cookie = descriptor_cookie_base64ext;
+ } else if (len != REND_DESC_COOKIE_LEN_EXT_BASE64) {
+ tor_asprintf(&err_msg, "Authorization cookie has wrong length: %s",
+ escaped(cookie_in));
+ goto err;
+ }
+
+ decoded_len = base64_decode((char *) descriptor_cookie_decoded,
+ sizeof(descriptor_cookie_decoded),
+ descriptor_cookie,
+ REND_DESC_COOKIE_LEN_EXT_BASE64);
+ if (decoded_len != REND_DESC_COOKIE_LEN &&
+ decoded_len != REND_DESC_COOKIE_LEN_EXT) {
+ tor_asprintf(&err_msg, "Authorization cookie has invalid characters: %s",
+ escaped(cookie_in));
+ goto err;
+ }
+
+ if (auth_type_out) {
+ auth_type_val = (descriptor_cookie_decoded[REND_DESC_COOKIE_LEN] >> 4) + 1;
+ if (auth_type_val < 1 || auth_type_val > 2) {
+ tor_asprintf(&err_msg, "Authorization cookie type is unknown: %s",
+ escaped(cookie_in));
+ goto err;
+ }
+ *auth_type_out = auth_type_val == 1 ? REND_BASIC_AUTH : REND_STEALTH_AUTH;
+ }
+
+ memcpy(cookie_out, descriptor_cookie_decoded, REND_DESC_COOKIE_LEN);
+ res = 0;
+ err:
+ if (err_msg_out) {
+ *err_msg_out = err_msg;
+ } else {
+ tor_free(err_msg);
+ }
+ memwipe(descriptor_cookie_decoded, 0, sizeof(descriptor_cookie_decoded));
+ memwipe(descriptor_cookie_base64ext, 0, sizeof(descriptor_cookie_base64ext));
+ return res;
+}
+
+/* Is this a rend client or server that allows direct (non-anonymous)
+ * connections?
+ * Clients must be specifically compiled and configured in this mode.
+ * Onion services can be configured to start in this mode.
+ * Prefer rend_client_allow_non_anonymous_connection() or
+ * rend_service_allow_non_anonymous_connection() whenever possible, so that
+ * checks are specific to Single Onion Services or Tor2web. */
+int
+rend_allow_non_anonymous_connection(const or_options_t* options)
+{
+ return (rend_client_allow_non_anonymous_connection(options)
+ || rend_service_allow_non_anonymous_connection(options));
+}
+
+/* Is this a rend client or server in non-anonymous mode?
+ * Clients must be specifically compiled in this mode.
+ * Onion services can be configured to start in this mode.
+ * Prefer rend_client_non_anonymous_mode_enabled() or
+ * rend_service_non_anonymous_mode_enabled() whenever possible, so that checks
+ * are specific to Single Onion Services or Tor2web. */
+int
+rend_non_anonymous_mode_enabled(const or_options_t *options)
+{
+ return (rend_client_non_anonymous_mode_enabled(options)
+ || rend_service_non_anonymous_mode_enabled(options));
+}
+
+/* Make sure that tor only builds one-hop circuits when they would not
+ * compromise user anonymity.
+ *
+ * One-hop circuits are permitted in Tor2web or Single Onion modes.
+ *
+ * Tor2web or Single Onion modes are also allowed to make multi-hop circuits.
+ * For example, single onion HSDir circuits are 3-hop to prevent denial of
+ * service.
+ */
+void
+assert_circ_anonymity_ok(origin_circuit_t *circ,
+ const or_options_t *options)
+{
+ tor_assert(options);
+ tor_assert(circ);
+ tor_assert(circ->build_state);
+
+ if (circ->build_state->onehop_tunnel) {
+ tor_assert(rend_allow_non_anonymous_connection(options));
+ }
+}
+
diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h
index d67552e405..090e6f25e0 100644
--- a/src/or/rendcommon.h
+++ b/src/or/rendcommon.h
@@ -45,6 +45,7 @@ void rend_intro_point_free(rend_intro_point_t *intro);
int rend_valid_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,
rend_service_descriptor_t *desc, time_t now,
uint8_t period, rend_auth_type_t auth_type,
@@ -68,5 +69,19 @@ rend_data_t *rend_data_service_create(const char *onion_address,
const char *pk_digest,
const uint8_t *cookie,
rend_auth_type_t auth_type);
+
+char *rend_auth_encode_cookie(const uint8_t *cookie_in,
+ rend_auth_type_t auth_type);
+int rend_auth_decode_cookie(const char *cookie_in,
+ uint8_t *cookie_out,
+ rend_auth_type_t *auth_type_out,
+ char **err_msg_out);
+
+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,
+ const or_options_t *options);
+
#endif
diff --git a/src/or/rendmid.c b/src/or/rendmid.c
index a33ad92966..ca0ad7b0d4 100644
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@ -309,7 +309,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
goto err;
}
- if (request_len != REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN) {
+ if (request_len < REND_COOKIE_LEN) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with bad length (%d) on circuit %u.",
(int)request_len, (unsigned)circ->p_circ_id);
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index 037c5fadd9..a8c383444d 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -20,6 +20,7 @@
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
+#include "policies.h"
#include "rendclient.h"
#include "rendcommon.h"
#include "rendservice.h"
@@ -71,6 +72,13 @@ static ssize_t rend_service_parse_intro_for_v3(
size_t plaintext_len,
char **err_msg_out);
+static int rend_service_check_private_dir(const or_options_t *options,
+ const rend_service_t *s,
+ int create);
+static int rend_service_check_private_dir_impl(const or_options_t *options,
+ const rend_service_t *s,
+ int create);
+
/** Represents the mapping from a virtual port of a rendezvous service to
* a real port on some IP.
*/
@@ -107,59 +115,13 @@ struct rend_service_port_config_s {
* rendezvous point before giving up? */
#define MAX_REND_TIMEOUT 30
-/** Represents a single hidden service running at this OP. */
-typedef struct rend_service_t {
- /* Fields specified in config file */
- char *directory; /**< where in the filesystem it stores it. Will be NULL if
- * this service is ephemeral. */
- int dir_group_readable; /**< if 1, allow group read
- permissions on directory */
- smartlist_t *ports; /**< List of rend_service_port_config_t */
- rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
- * authorization is performed. */
- smartlist_t *clients; /**< List of rend_authorized_client_t's of
- * clients that may access our service. Can be NULL
- * if no client authorization is performed. */
- /* Other fields */
- crypto_pk_t *private_key; /**< Permanent hidden-service key. */
- char service_id[REND_SERVICE_ID_LEN_BASE32+1]; /**< Onion address without
- * '.onion' */
- char pk_digest[DIGEST_LEN]; /**< Hash of permanent hidden-service key. */
- smartlist_t *intro_nodes; /**< List of rend_intro_point_t's we have,
- * or are trying to establish. */
- /** List of rend_intro_point_t that are expiring. They are removed once
- * the new descriptor is successfully uploaded. A node in this list CAN
- * NOT appear in the intro_nodes list. */
- smartlist_t *expiring_nodes;
- time_t intro_period_started; /**< Start of the current period to build
- * introduction points. */
- int n_intro_circuits_launched; /**< Count of intro circuits we have
- * established in this period. */
- unsigned int n_intro_points_wanted; /**< Number of intro points this
- * service wants to have open. */
- rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
- time_t desc_is_dirty; /**< Time at which changes to the hidden service
- * descriptor content occurred, or 0 if it's
- * up-to-date. */
- time_t next_upload_time; /**< Scheduled next hidden service descriptor
- * upload time. */
- /** Replay cache for Diffie-Hellman values of INTRODUCE2 cells, 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 *accepted_intro_dh_parts;
- /** If true, we don't close circuits for making requests to unsupported
- * ports. */
- int allow_unknown_ports;
- /** The maximum number of simultanious streams-per-circuit that are allowed
- * to be established, or 0 if no limit is set.
- */
- int max_streams_per_circuit;
- /** If true, we close circuits that exceed the max_streams_per_circuit
- * limit. */
- int max_streams_close_circuit;
-} rend_service_t;
+/* Hidden service directory file names:
+ * new file names should be added to rend_service_add_filenames_to_list()
+ * for sandboxing purposes. */
+static const char *private_key_fname = "private_key";
+static const char *hostname_fname = "hostname";
+static const char *client_keys_fname = "client_keys";
+static const char *sos_poison_fname = "onion_service_non_anonymous";
/** Returns a escaped string representation of the service, <b>s</b>.
*/
@@ -183,14 +145,15 @@ num_rend_services(void)
}
/** Helper: free storage held by a single service authorized client entry. */
-static void
+void
rend_authorized_client_free(rend_authorized_client_t *client)
{
if (!client)
return;
if (client->client_key)
crypto_pk_free(client->client_key);
- memwipe(client->client_name, 0, strlen(client->client_name));
+ if (client->client_name)
+ memwipe(client->client_name, 0, strlen(client->client_name));
tor_free(client->client_name);
memwipe(client->descriptor_cookie, 0, sizeof(client->descriptor_cookie));
tor_free(client);
@@ -205,16 +168,18 @@ rend_authorized_client_strmap_item_free(void *authorized_client)
/** Release the storage held by <b>service</b>.
*/
-static void
+STATIC void
rend_service_free(rend_service_t *service)
{
if (!service)
return;
tor_free(service->directory);
- SMARTLIST_FOREACH(service->ports, rend_service_port_config_t*, p,
- rend_service_port_config_free(p));
- smartlist_free(service->ports);
+ if (service->ports) {
+ SMARTLIST_FOREACH(service->ports, rend_service_port_config_t*, p,
+ rend_service_port_config_free(p));
+ smartlist_free(service->ports);
+ }
if (service->private_key)
crypto_pk_free(service->private_key);
if (service->intro_nodes) {
@@ -254,15 +219,30 @@ rend_service_free_all(void)
rend_service_list = NULL;
}
-/** Validate <b>service</b> and add it to rend_service_list if possible.
+/** Validate <b>service</b> and add it to <b>service_list</b>, or to
+ * the global rend_service_list if <b>service_list</b> is NULL.
* Return 0 on success. On failure, free <b>service</b> and return -1.
+ * Takes ownership of <b>service</b>.
*/
static int
-rend_add_service(rend_service_t *service)
+rend_add_service(smartlist_t *service_list, rend_service_t *service)
{
int i;
rend_service_port_config_t *p;
+ smartlist_t *s_list;
+ /* If no special service list is provided, then just use the global one. */
+ if (!service_list) {
+ if (BUG(!rend_service_list)) {
+ /* No global HS list, which is a failure. */
+ return -1;
+ }
+
+ s_list = rend_service_list;
+ } else {
+ s_list = service_list;
+ }
+
service->intro_nodes = smartlist_new();
service->expiring_nodes = smartlist_new();
@@ -284,7 +264,8 @@ rend_add_service(rend_service_t *service)
}
if (service->auth_type != REND_NO_AUTH &&
- smartlist_len(service->clients) == 0) {
+ (!service->clients ||
+ smartlist_len(service->clients) == 0)) {
log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no "
"clients; ignoring.",
rend_service_escaped_dir(service));
@@ -292,7 +273,7 @@ rend_add_service(rend_service_t *service)
return -1;
}
- if (!smartlist_len(service->ports)) {
+ if (!service->ports || !smartlist_len(service->ports)) {
log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; "
"ignoring.",
rend_service_escaped_dir(service));
@@ -315,8 +296,9 @@ rend_add_service(rend_service_t *service)
* lock file. But this is enough to detect a simple mistake that
* at least one person has actually made.
*/
- if (service->directory != NULL) { /* Skip dupe for ephemeral services. */
- SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
+ if (service->directory != NULL) {
+ /* Skip dupe for ephemeral services. */
+ SMARTLIST_FOREACH(s_list, rend_service_t*, ptr,
dupe = dupe ||
!strcmp(ptr->directory, service->directory));
if (dupe) {
@@ -327,7 +309,7 @@ rend_add_service(rend_service_t *service)
return -1;
}
}
- smartlist_add(rend_service_list, service);
+ smartlist_add(s_list, service);
log_debug(LD_REND,"Configuring service with directory \"%s\"",
service->directory);
for (i = 0; i < smartlist_len(service->ports); ++i) {
@@ -389,22 +371,20 @@ rend_service_parse_port_config(const char *string, const char *sep,
int realport = 0;
uint16_t p;
tor_addr_t addr;
- const char *addrport;
rend_service_port_config_t *result = NULL;
unsigned int is_unix_addr = 0;
- char *socket_path = NULL;
+ const char *socket_path = NULL;
char *err_msg = NULL;
+ char *addrport = NULL;
sl = smartlist_new();
smartlist_split_string(sl, string, sep,
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
- if (smartlist_len(sl) < 1 || smartlist_len(sl) > 2) {
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
+ if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) {
if (err_msg_out)
err_msg = tor_strdup("Bad syntax in hidden service port configuration.");
-
goto err;
}
-
virtport = (int)tor_parse_long(smartlist_get(sl,0), 10, 1, 65535, NULL,NULL);
if (!virtport) {
if (err_msg_out)
@@ -413,7 +393,6 @@ rend_service_parse_port_config(const char *string, const char *sep,
goto err;
}
-
if (smartlist_len(sl) == 1) {
/* No addr:port part; use default. */
realport = virtport;
@@ -421,17 +400,18 @@ rend_service_parse_port_config(const char *string, const char *sep,
} else {
int ret;
- addrport = smartlist_get(sl,1);
- ret = config_parse_unix_port(addrport, &socket_path);
- if (ret < 0 && ret != -ENOENT) {
- if (ret == -EINVAL)
- if (err_msg_out)
- err_msg = tor_strdup("Empty socket path in hidden service port "
- "configuration.");
-
+ const char *addrport_element = smartlist_get(sl,1);
+ const char *rest = NULL;
+ int is_unix;
+ ret = port_cfg_line_extract_addrport(addrport_element, &addrport,
+ &is_unix, &rest);
+ if (ret < 0) {
+ tor_asprintf(&err_msg, "Couldn't process address <%s> from hidden "
+ "service configuration", addrport_element);
goto err;
}
- if (socket_path) {
+ if (is_unix) {
+ socket_path = addrport;
is_unix_addr = 1;
} else if (strchr(addrport, ':') || strchr(addrport, '.')) {
/* else try it as an IP:port pair if it has a : or . in it */
@@ -469,10 +449,10 @@ rend_service_parse_port_config(const char *string, const char *sep,
}
err:
+ tor_free(addrport);
if (err_msg_out) *err_msg_out = err_msg;
SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
smartlist_free(sl);
- if (socket_path) tor_free(socket_path);
return result;
}
@@ -484,6 +464,61 @@ 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)
+{
+ 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;
+ }
+
+ if (validate_only) {
+ rend_service_free(service);
+ return 0;
+ } else {
+ /* Use service_list for unit tests */
+ smartlist_t *s_list = NULL;
+ /* If no special service list is provided, then just use the global one. */
+ if (!service_list) {
+ if (BUG(!rend_service_list)) {
+ /* No global HS list, which is a failure, because we plan on adding to
+ * it */
+ return -1;
+ }
+ s_list = rend_service_list;
+ } else {
+ s_list = service_list;
+ }
+ /* s_list can not be NULL here - if both service_list and rend_service_list
+ * are NULL, and validate_only is false, we exit earlier in the function
+ */
+ if (BUG(!s_list)) {
+ return -1;
+ }
+ /* Ignore service failures until 030 */
+ rend_add_service(s_list, service);
+ return 0;
+ }
+}
+
/** 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
@@ -505,11 +540,12 @@ rend_config_services(const or_options_t *options, int validate_only)
for (line = options->RendConfigLines; line; line = line->next) {
if (!strcasecmp(line->key, "HiddenServiceDir")) {
- if (service) { /* register the one we just finished parsing */
- if (validate_only)
- rend_service_free(service);
- else
- rend_add_service(service);
+ /* register the service we just finished parsing
+ * this code registers every service except the last one parsed,
+ * which is registered below the loop */
+ if (rend_service_check_dir_and_add(NULL, options, service,
+ validate_only) < 0) {
+ return -1;
}
service = tor_malloc_zero(sizeof(rend_service_t));
service->directory = tor_strdup(line->value);
@@ -671,27 +707,17 @@ rend_config_services(const or_options_t *options, int validate_only)
SMARTLIST_FOREACH_BEGIN(clients, const char *, client_name)
{
rend_authorized_client_t *client;
- size_t len = strlen(client_name);
- if (len < 1 || len > REND_CLIENTNAME_MAX_LEN) {
+ if (!rend_valid_client_name(client_name)) {
log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an "
- "illegal client name: '%s'. Length must be "
- "between 1 and %d characters.",
+ "illegal client name: '%s'. Names must be "
+ "between 1 and %d characters and contain "
+ "only [A-Za-z0-9+_-].",
client_name, REND_CLIENTNAME_MAX_LEN);
SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
smartlist_free(clients);
rend_service_free(service);
return -1;
}
- if (strspn(client_name, REND_LEGAL_CLIENTNAME_CHARACTERS) != len) {
- log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains an "
- "illegal client name: '%s'. Valid "
- "characters are [A-Za-z0-9+_-].",
- client_name);
- SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
- smartlist_free(clients);
- rend_service_free(service);
- return -1;
- }
client = tor_malloc_zero(sizeof(rend_authorized_client_t));
client->client_name = tor_strdup(client_name);
smartlist_add(service->clients, client);
@@ -725,22 +751,12 @@ rend_config_services(const or_options_t *options, int validate_only)
}
}
}
- if (service) {
- cpd_check_t check_opts = CPD_CHECK_MODE_ONLY|CPD_CHECK;
- if (service->dir_group_readable) {
- check_opts |= CPD_GROUP_READ;
- }
-
- if (check_private_dir(service->directory, check_opts, options->User) < 0) {
- rend_service_free(service);
- return -1;
- }
-
- if (validate_only) {
- rend_service_free(service);
- } else {
- rend_add_service(service);
- }
+ /* 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(NULL, options, service,
+ validate_only) < 0) {
+ return -1;
}
/* If this is a reload and there were hidden services configured before,
@@ -827,14 +843,17 @@ rend_config_services(const or_options_t *options, int validate_only)
return 0;
}
-/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, with
+/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using
+ * client authorization <b>auth_type</b> and an optional list of
+ * rend_authorized_client_t in <b>auth_clients</b>, with
* <b>max_streams_per_circuit</b> streams allowed per rendezvous circuit,
* and circuit closure on max streams being exceeded set by
* <b>max_streams_close_circuit</b>.
*
- * Regardless of sucess/failure, callers should not touch pk/ports after
- * calling this routine, and may assume that correct cleanup has been done
- * on failure.
+ * Ownership of pk, ports, and auth_clients is passed to this routine.
+ * Regardless of success/failure, callers should not touch these values
+ * after calling this routine, and may assume that correct cleanup has
+ * been done on failure.
*
* Return an appropriate rend_service_add_ephemeral_status_t.
*/
@@ -843,6 +862,8 @@ rend_service_add_ephemeral(crypto_pk_t *pk,
smartlist_t *ports,
int max_streams_per_circuit,
int max_streams_close_circuit,
+ rend_auth_type_t auth_type,
+ smartlist_t *auth_clients,
char **service_id_out)
{
*service_id_out = NULL;
@@ -852,7 +873,8 @@ rend_service_add_ephemeral(crypto_pk_t *pk,
rend_service_t *s = tor_malloc_zero(sizeof(rend_service_t));
s->directory = NULL; /* This indicates the service is ephemeral. */
s->private_key = pk;
- s->auth_type = REND_NO_AUTH;
+ s->auth_type = auth_type;
+ s->clients = auth_clients;
s->ports = ports;
s->intro_period_started = time(NULL);
s->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
@@ -868,6 +890,12 @@ rend_service_add_ephemeral(crypto_pk_t *pk,
rend_service_free(s);
return RSAE_BADVIRTPORT;
}
+ if (s->auth_type != REND_NO_AUTH &&
+ (!s->clients || smartlist_len(s->clients) == 0)) {
+ log_warn(LD_CONFIG, "At least one authorized client must be specified.");
+ rend_service_free(s);
+ return RSAE_BADAUTH;
+ }
/* Enforcing pk/id uniqueness should be done by rend_service_load_keys(), but
* it's not, see #14828.
@@ -885,7 +913,7 @@ rend_service_add_ephemeral(crypto_pk_t *pk,
}
/* Initialize the service. */
- if (rend_add_service(s)) {
+ if (rend_add_service(NULL, s)) {
return RSAE_INTERNAL;
}
*service_id_out = tor_strdup(s->service_id);
@@ -923,7 +951,6 @@ rend_service_del_ephemeral(const char *service_id)
*/
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
if (!circ->marked_for_close &&
- circ->state == CIRCUIT_STATE_OPEN &&
(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
@@ -999,13 +1026,241 @@ rend_service_update_descriptor(rend_service_t *service)
}
}
+/* Allocate and return a string containing the path to file_name in
+ * service->directory. Asserts that service has a directory.
+ * This function will never return NULL.
+ * The caller must free this path. */
+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;
+}
+
+/* Allocate and return a string containing the path to the single onion
+ * service poison file in service->directory. Asserts that service has a
+ * directory.
+ * The caller must free this path. */
+STATIC char *
+rend_service_sos_poison_path(const rend_service_t *service)
+{
+ return rend_service_path(service, sos_poison_fname);
+}
+
+/** Return True if hidden services <b>service> has been poisoned by single
+ * onion mode. */
+static int
+service_is_single_onion_poisoned(const rend_service_t *service)
+{
+ char *poison_fname = NULL;
+ file_status_t fstatus;
+
+ /* Passing a NULL service is a bug */
+ if (BUG(!service)) {
+ return 0;
+ }
+
+ if (!service->directory) {
+ return 0;
+ }
+
+ poison_fname = rend_service_sos_poison_path(service);
+
+ fstatus = file_status(poison_fname);
+ tor_free(poison_fname);
+
+ /* If this fname is occupied, the hidden service has been poisoned.
+ * fstatus can be FN_ERROR if the service directory does not exist, in that
+ * case, there is obviously no private key. */
+ if (fstatus == FN_FILE || fstatus == FN_EMPTY) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Return 1 if the private key file for service exists and has a non-zero size,
+ * and 0 otherwise. */
+static int
+rend_service_private_key_exists(const rend_service_t *service)
+{
+ char *private_key_path = rend_service_path(service, private_key_fname);
+ const file_status_t private_key_status = file_status(private_key_path);
+ tor_free(private_key_path);
+ /* Only non-empty regular private key files could have been used before.
+ * fstatus can be FN_ERROR if the service directory does not exist, in that
+ * case, there is obviously no private key. */
+ return private_key_status == FN_FILE;
+}
+
+/** Check the single onion service poison state of the directory for s:
+ * - If the service is poisoned, and we are in Single Onion Mode,
+ * return 0,
+ * - If the service is not poisoned, and we are not in Single Onion Mode,
+ * return 0,
+ * - Otherwise, the poison state is invalid: the service was created in one
+ * mode, and is being used in the other, return -1.
+ * Hidden service directories without keys are always considered consistent.
+ * They will be poisoned after their directory is created (if needed). */
+STATIC int
+rend_service_verify_single_onion_poison(const rend_service_t* s,
+ const or_options_t* options)
+{
+ /* Passing a NULL service is a bug */
+ if (BUG(!s)) {
+ return -1;
+ }
+
+ /* Ephemeral services are checked at ADD_ONION time */
+ if (!s->directory) {
+ return 0;
+ }
+
+ /* Services without keys are always ok - their keys will only ever be used
+ * in the current mode */
+ if (!rend_service_private_key_exists(s)) {
+ return 0;
+ }
+
+ /* The key has been used before in a different mode */
+ if (service_is_single_onion_poisoned(s) !=
+ rend_service_non_anonymous_mode_enabled(options)) {
+ return -1;
+ }
+
+ /* The key exists and is consistent with the current mode */
+ return 0;
+}
+
+/*** Helper for rend_service_poison_new_single_onion_dir(). Add a file to
+ * the hidden service directory for s that marks it as a single onion service.
+ * Tor must be in single onion mode before calling this function, and the
+ * service directory must already have been created.
+ * Returns 0 when a directory is successfully poisoned, or if it is already
+ * poisoned. Returns -1 on a failure to read the directory or write the poison
+ * file, or if there is an existing private key file in the directory. (The
+ * service should have been poisoned when the key was created.) */
+static int
+poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service,
+ const or_options_t* options)
+{
+ /* Passing a NULL service is a bug */
+ if (BUG(!service)) {
+ return -1;
+ }
+
+ /* We must only poison directories if we're in Single Onion mode */
+ tor_assert(rend_service_non_anonymous_mode_enabled(options));
+
+ int fd;
+ int retval = -1;
+ char *poison_fname = NULL;
+
+ if (!service->directory) {
+ log_info(LD_REND, "Ephemeral HS started in non-anonymous mode.");
+ return 0;
+ }
+
+ /* Make sure we're only poisoning new hidden service directories */
+ if (rend_service_private_key_exists(service)) {
+ log_warn(LD_BUG, "Tried to single onion poison a service directory after "
+ "the private key was created.");
+ return -1;
+ }
+
+ /* Make sure the directory was created before calling this function. */
+ if (BUG(rend_service_check_private_dir_impl(options, service, 0) < 0))
+ return -1;
+
+ poison_fname = rend_service_sos_poison_path(service);
+
+ switch (file_status(poison_fname)) {
+ case FN_DIR:
+ case FN_ERROR:
+ log_warn(LD_FS, "Can't read single onion poison file \"%s\"",
+ poison_fname);
+ goto done;
+ case FN_FILE: /* single onion poison file already exists. NOP. */
+ case FN_EMPTY: /* single onion poison file already exists. NOP. */
+ log_debug(LD_FS, "Tried to re-poison a single onion poisoned file \"%s\"",
+ poison_fname);
+ break;
+ case FN_NOENT:
+ fd = tor_open_cloexec(poison_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (fd < 0) {
+ log_warn(LD_FS, "Could not create single onion poison file %s",
+ poison_fname);
+ goto done;
+ }
+ close(fd);
+ break;
+ default:
+ tor_assert(0);
+ }
+
+ retval = 0;
+
+ done:
+ tor_free(poison_fname);
+
+ return retval;
+}
+
+/** We just got launched in Single Onion Mode. That's a non-anoymous mode for
+ * hidden services. If s is new, we should mark its hidden service
+ * directory appropriately so that it is never launched as a location-private
+ * hidden service. (New directories don't have private key files.)
+ * Return 0 on success, -1 on fail. */
+STATIC int
+rend_service_poison_new_single_onion_dir(const rend_service_t *s,
+ const or_options_t* options)
+{
+ /* Passing a NULL service is a bug */
+ if (BUG(!s)) {
+ return -1;
+ }
+
+ /* We must only poison directories if we're in Single Onion mode */
+ tor_assert(rend_service_non_anonymous_mode_enabled(options));
+
+ if (!rend_service_private_key_exists(s)) {
+ if (poison_new_single_onion_hidden_service_dir_impl(s, options)
+ < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
/** Load and/or generate private keys for all hidden services, possibly
- * including keys for client authorization. Return 0 on success, -1 on
- * failure. */
+ * including keys for client authorization.
+ * If a <b>service_list</b> is provided, treat it as the list of hidden
+ * services (used in unittests). Otherwise, require that rend_service_list is
+ * not NULL.
+ * Return 0 on success, -1 on failure. */
int
-rend_service_load_all_keys(void)
+rend_service_load_all_keys(const smartlist_t *service_list)
{
- SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) {
+ const smartlist_t *s_list = NULL;
+ /* If no special service list is provided, then just use the global one. */
+ if (!service_list) {
+ if (BUG(!rend_service_list)) {
+ return -1;
+ }
+ s_list = rend_service_list;
+ } else {
+ s_list = service_list;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(s_list, rend_service_t *, s) {
if (s->private_key)
continue;
log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
@@ -1025,12 +1280,10 @@ rend_service_add_filenames_to_list(smartlist_t *lst, const rend_service_t *s)
tor_assert(lst);
tor_assert(s);
tor_assert(s->directory);
- smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"private_key",
- s->directory);
- smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"hostname",
- s->directory);
- smartlist_add_asprintf(lst, "%s"PATH_SEPARATOR"client_keys",
- s->directory);
+ smartlist_add(lst, rend_service_path(s, private_key_fname));
+ smartlist_add(lst, rend_service_path(s, hostname_fname));
+ smartlist_add(lst, rend_service_path(s, client_keys_fname));
+ smartlist_add(lst, rend_service_sos_poison_path(s));
}
/** Add to <b>open_lst</b> every filename used by a configured hidden service,
@@ -1068,61 +1321,133 @@ rend_service_derive_key_digests(struct rend_service_t *s)
return 0;
}
-/** Load and/or generate private keys for the hidden service <b>s</b>,
- * possibly including keys for client authorization. Return 0 on success, -1
- * on failure. */
+/* Implements the directory check from rend_service_check_private_dir,
+ * without doing the single onion poison checks. */
static int
-rend_service_load_keys(rend_service_t *s)
+rend_service_check_private_dir_impl(const or_options_t *options,
+ const rend_service_t *s,
+ int create)
{
- char fname[512];
- char buf[128];
- cpd_check_t check_opts = CPD_CREATE;
-
+ cpd_check_t check_opts = CPD_NONE;
+ if (create) {
+ check_opts |= CPD_CREATE;
+ } else {
+ check_opts |= CPD_CHECK_MODE_ONLY;
+ check_opts |= CPD_CHECK;
+ }
if (s->dir_group_readable) {
check_opts |= CPD_GROUP_READ;
}
/* Check/create directory */
- if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) {
+ if (check_private_dir(s->directory, check_opts, options->User) < 0) {
+ log_warn(LD_REND, "Checking service directory %s failed.", s->directory);
return -1;
}
-#ifndef _WIN32
- if (s->dir_group_readable) {
- /* Only new dirs created get new opts, also enforce group read. */
- if (chmod(s->directory, 0750)) {
- log_warn(LD_FS,"Unable to make %s group-readable.", s->directory);
- }
+
+ return 0;
+}
+
+/** Make sure that the directory for <b>s</b> is private, using the config in
+ * <b>options</b>.
+ * If <b>create</b> is true:
+ * - if the directory exists, change permissions if needed,
+ * - if the directory does not exist, create it with the correct permissions.
+ * If <b>create</b> is false:
+ * - if the directory exists, check permissions,
+ * - if the directory does not exist, check if we think we can create it.
+ * Return 0 on success, -1 on failure. */
+static int
+rend_service_check_private_dir(const or_options_t *options,
+ const rend_service_t *s,
+ int create)
+{
+ /* Passing a NULL service is a bug */
+ if (BUG(!s)) {
+ return -1;
}
-#endif
- /* Load key */
- if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) ||
- strlcat(fname,PATH_SEPARATOR"private_key",sizeof(fname))
- >= sizeof(fname)) {
- log_warn(LD_CONFIG, "Directory name too long to store key file: \"%s\".",
- s->directory);
+ /* Check/create directory */
+ if (rend_service_check_private_dir_impl(options, s, create) < 0) {
+ return -1;
+ }
+
+ /* Check if the hidden service key exists, and was created in a different
+ * single onion service mode, and refuse to launch if it has.
+ * This is safe to call even when create is false, as it ignores missing
+ * keys and directories: they are always valid.
+ */
+ if (rend_service_verify_single_onion_poison(s, options) < 0) {
+ /* We can't use s->service_id here, as the key may not have been loaded */
+ log_warn(LD_GENERAL, "We are configured with "
+ "HiddenServiceNonAnonymousMode %d, but the hidden "
+ "service key in directory %s was created in %s mode. "
+ "This is not allowed.",
+ rend_service_non_anonymous_mode_enabled(options) ? 1 : 0,
+ rend_service_escaped_dir(s),
+ rend_service_non_anonymous_mode_enabled(options) ?
+ "an anonymous" : "a non-anonymous"
+ );
return -1;
}
+
+ /* Poison new single onion directories immediately after they are created,
+ * so that we never accidentally launch non-anonymous hidden services
+ * thinking they are anonymous. Any keys created later will end up with the
+ * correct poisoning state.
+ */
+ if (create && rend_service_non_anonymous_mode_enabled(options)) {
+ static int logged_warning = 0;
+
+ if (rend_service_poison_new_single_onion_dir(s, options) < 0) {
+ log_warn(LD_GENERAL,"Failed to mark new hidden services as non-anonymous"
+ ".");
+ return -1;
+ }
+
+ if (!logged_warning) {
+ /* The keys for these services are linked to the server IP address */
+ log_notice(LD_REND, "The configured onion service directories have been "
+ "used in single onion mode. They can not be used for "
+ "anonymous hidden services.");
+ logged_warning = 1;
+ }
+ }
+
+ return 0;
+}
+
+/** Load and/or generate private keys for the hidden service <b>s</b>,
+ * possibly including keys for client authorization. Return 0 on success, -1
+ * on failure. */
+static int
+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))
+ goto err;
+
+ /* Load key */
+ fname = rend_service_path(s, private_key_fname);
s->private_key = init_key_from_file(fname, 1, LOG_ERR, 0);
+
if (!s->private_key)
- return -1;
+ goto err;
if (rend_service_derive_key_digests(s) < 0)
- return -1;
+ goto err;
+ tor_free(fname);
/* Create service file */
- if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) ||
- strlcat(fname,PATH_SEPARATOR"hostname",sizeof(fname))
- >= sizeof(fname)) {
- log_warn(LD_CONFIG, "Directory name too long to store hostname file:"
- " \"%s\".", s->directory);
- return -1;
- }
+ fname = rend_service_path(s, hostname_fname);
tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id);
if (write_str_to_file(fname,buf,0)<0) {
log_warn(LD_CONFIG, "Could not write onion address to hostname file.");
- memwipe(buf, 0, sizeof(buf));
- return -1;
+ goto err;
}
#ifndef _WIN32
if (s->dir_group_readable) {
@@ -1133,15 +1458,21 @@ rend_service_load_keys(rend_service_t *s)
}
#endif
- memwipe(buf, 0, sizeof(buf));
-
/* If client authorization is configured, load or generate keys. */
if (s->auth_type != REND_NO_AUTH) {
- if (rend_service_load_auth_keys(s, fname) < 0)
- return -1;
+ if (rend_service_load_auth_keys(s, fname) < 0) {
+ goto err;
+ }
}
- return 0;
+ int r = 0;
+ goto done;
+ err:
+ r = -1;
+ done:
+ memwipe(buf, 0, sizeof(buf));
+ tor_free(fname);
+ return r;
}
/** Load and/or generate client authorization keys for the hidden service
@@ -1151,23 +1482,17 @@ static int
rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
{
int r = 0;
- char cfname[512];
+ char *cfname = NULL;
char *client_keys_str = NULL;
strmap_t *parsed_clients = strmap_new();
FILE *cfile, *hfile;
open_file_t *open_cfile = NULL, *open_hfile = NULL;
- char extended_desc_cookie[REND_DESC_COOKIE_LEN+1];
char desc_cook_out[3*REND_DESC_COOKIE_LEN_BASE64+1];
char service_id[16+1];
char buf[1500];
/* Load client keys and descriptor cookies, if available. */
- if (tor_snprintf(cfname, sizeof(cfname), "%s"PATH_SEPARATOR"client_keys",
- s->directory)<0) {
- log_warn(LD_CONFIG, "Directory name too long to store client keys "
- "file: \"%s\".", s->directory);
- goto err;
- }
+ cfname = rend_service_path(s, client_keys_fname);
client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL);
if (client_keys_str) {
if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) {
@@ -1208,10 +1533,12 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
memcpy(client->descriptor_cookie, parsed->descriptor_cookie,
REND_DESC_COOKIE_LEN);
} else {
- crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN);
+ crypto_rand((char *) client->descriptor_cookie, REND_DESC_COOKIE_LEN);
}
+ /* For compatibility with older tor clients, this does not
+ * truncate the padding characters, unlike rend_auth_encode_cookie. */
if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1,
- client->descriptor_cookie,
+ (char *) client->descriptor_cookie,
REND_DESC_COOKIE_LEN, 0) < 0) {
log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
goto err;
@@ -1272,6 +1599,8 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
log_warn(LD_BUG, "Could not write client entry.");
goto err;
}
+ } else {
+ strlcpy(service_id, s->service_id, sizeof(service_id));
}
if (fputs(buf, cfile) < 0) {
@@ -1280,27 +1609,18 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
goto err;
}
- /* Add line to hostname file. */
- if (s->auth_type == REND_BASIC_AUTH) {
- /* Remove == signs (newline has been removed above). */
- desc_cook_out[strlen(desc_cook_out)-2] = '\0';
- tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n",
- s->service_id, desc_cook_out, client->client_name);
- } else {
- memcpy(extended_desc_cookie, client->descriptor_cookie,
- REND_DESC_COOKIE_LEN);
- extended_desc_cookie[REND_DESC_COOKIE_LEN] =
- ((int)s->auth_type - 1) << 4;
- if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1,
- extended_desc_cookie,
- REND_DESC_COOKIE_LEN+1, 0) < 0) {
- log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
- goto err;
- }
- desc_cook_out[strlen(desc_cook_out)-2] = '\0'; /* Remove A=. */
- tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n",
- service_id, desc_cook_out, client->client_name);
+ /* Add line to hostname file. This is not the same encoding as in
+ * client_keys. */
+ char *encoded_cookie = rend_auth_encode_cookie(client->descriptor_cookie,
+ s->auth_type);
+ if (!encoded_cookie) {
+ log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
+ goto err;
}
+ tor_snprintf(buf, sizeof(buf), "%s.onion %s # client: %s\n",
+ service_id, encoded_cookie, client->client_name);
+ memwipe(encoded_cookie, 0, strlen(encoded_cookie));
+ tor_free(encoded_cookie);
if (fputs(buf, hfile)<0) {
log_warn(LD_FS, "Could not append host entry to file: %s",
@@ -1326,13 +1646,15 @@ rend_service_load_auth_keys(rend_service_t *s, const char *hfname)
}
strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
- memwipe(cfname, 0, sizeof(cfname));
+ if (cfname) {
+ memwipe(cfname, 0, strlen(cfname));
+ tor_free(cfname);
+ }
/* Clear stack buffers that held key-derived material. */
memwipe(buf, 0, sizeof(buf));
memwipe(desc_cook_out, 0, sizeof(desc_cook_out));
memwipe(service_id, 0, sizeof(service_id));
- memwipe(extended_desc_cookie, 0, sizeof(extended_desc_cookie));
return r;
}
@@ -1429,6 +1751,31 @@ rend_check_authorization(rend_service_t *service,
return 1;
}
+/* Can this service make a direct connection to ei?
+ * It must be a single onion service, and the firewall rules must allow ei. */
+static int
+rend_service_use_direct_connection(const or_options_t* options,
+ const extend_info_t* ei)
+{
+ /* We'll connect directly all reachable addresses, whether preferred or not.
+ * The prefer_ipv6 argument to fascist_firewall_allows_address_addr is
+ * ignored, because pref_only is 0. */
+ return (rend_service_allow_non_anonymous_connection(options) &&
+ fascist_firewall_allows_address_addr(&ei->addr, ei->port,
+ FIREWALL_OR_CONNECTION, 0, 0));
+}
+
+/* Like rend_service_use_direct_connection, but to a node. */
+static int
+rend_service_use_direct_connection_node(const or_options_t* options,
+ const node_t* node)
+{
+ /* We'll connect directly all reachable addresses, whether preferred or not.
+ */
+ return (rend_service_allow_non_anonymous_connection(options) &&
+ fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0));
+}
+
/******
* Handle cells
******/
@@ -1479,9 +1826,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
goto err;
}
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(circuit, options);
tor_assert(circuit->rend_data);
/* We'll use this in a bazillion log messages */
@@ -1686,6 +2031,11 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
for (i=0;i<MAX_REND_FAILURES;i++) {
int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
+ /* A Single Onion Service only uses a direct connection if its
+ * firewall rules permit direct connections to the address. */
+ if (rend_service_use_direct_connection(options, rp)) {
+ flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
launched = circuit_launch_by_extend_info(
CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
@@ -1798,7 +2148,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
goto err;
}
- rp = extend_info_from_node(node, 0);
+ /* Are we in single onion mode? */
+ const int allow_direct = rend_service_allow_non_anonymous_connection(
+ get_options());
+ rp = extend_info_from_node(node, allow_direct);
if (!rp) {
if (err_msg_out) {
tor_asprintf(&err_msg,
@@ -1823,6 +2176,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
goto err;
}
+ /* rp is always set here: extend_info_dup guarantees a non-NULL result, and
+ * the other cases goto err. */
+ tor_assert(rp);
+
/* Make sure the RP we are being asked to connect to is _not_ a private
* address unless it's allowed. Let's avoid to build a circuit to our
* second middle node and fail right after when extending to the RP. */
@@ -2597,6 +2954,10 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
log_info(LD_REND,"Reattempting rendezvous circuit to '%s'",
safe_str(extend_info_describe(oldstate->chosen_exit)));
+ /* You'd think Single Onion Services would want to retry the rendezvous
+ * using a direct connection. But if it's blocked by a firewall, or the
+ * service is IPv6-only, or the rend point avoiding becoming a one-hop
+ * proxy, we need a 3-hop connection. */
newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
oldstate->chosen_exit,
CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL);
@@ -2625,26 +2986,72 @@ rend_service_launch_establish_intro(rend_service_t *service,
rend_intro_point_t *intro)
{
origin_circuit_t *launched;
+ int flags = CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL;
+ const or_options_t *options = get_options();
+ extend_info_t *launch_ei = intro->extend_info;
+ extend_info_t *direct_ei = NULL;
+
+ /* Are we in single onion mode? */
+ if (rend_service_allow_non_anonymous_connection(options)) {
+ /* Do we have a descriptor for the node?
+ * We've either just chosen it from the consensus, or we've just reviewed
+ * our intro points to see which ones are still valid, and deleted the ones
+ * that aren't in the consensus any more. */
+ const node_t *node = node_get_by_id(launch_ei->identity_digest);
+ if (BUG(!node)) {
+ /* The service has kept an intro point after it went missing from the
+ * consensus. If we did anything else here, it would be a consensus
+ * distinguisher. Which are less of an issue for single onion services,
+ * but still a bug. */
+ return -1;
+ }
+ /* Can we connect to the node directly? If so, replace launch_ei
+ * (a multi-hop extend_info) with one suitable for direct connection. */
+ if (rend_service_use_direct_connection_node(options, node)) {
+ direct_ei = extend_info_from_node(node, 1);
+ if (BUG(!direct_ei)) {
+ /* rend_service_use_direct_connection_node and extend_info_from_node
+ * disagree about which addresses on this node are permitted. This
+ * should never happen. Avoiding the connection is a safe response. */
+ return -1;
+ }
+ flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL;
+ launch_ei = direct_ei;
+ }
+ }
+ /* launch_ei is either intro->extend_info, or has been replaced with a valid
+ * extend_info for single onion service direct connection. */
+ tor_assert(launch_ei);
+ /* We must have the same intro when making a direct connection. */
+ tor_assert(tor_memeq(intro->extend_info->identity_digest,
+ launch_ei->identity_digest,
+ DIGEST_LEN));
log_info(LD_REND,
- "Launching circuit to introduction point %s for service %s",
+ "Launching circuit to introduction point %s%s%s for service %s",
safe_str_client(extend_info_describe(intro->extend_info)),
+ direct_ei ? " via direct address " : "",
+ direct_ei ? safe_str_client(extend_info_describe(direct_ei)) : "",
service->service_id);
rep_hist_note_used_internal(time(NULL), 1, 0);
++service->n_intro_circuits_launched;
launched = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
- intro->extend_info,
- CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL);
+ launch_ei, flags);
if (!launched) {
log_info(LD_REND,
- "Can't launch circuit to establish introduction at %s.",
- safe_str_client(extend_info_describe(intro->extend_info)));
+ "Can't launch circuit to establish introduction at %s%s%s.",
+ safe_str_client(extend_info_describe(intro->extend_info)),
+ direct_ei ? " via direct address " : "",
+ direct_ei ? safe_str_client(extend_info_describe(direct_ei)) : ""
+ );
+ extend_info_free(direct_ei);
return -1;
}
- /* We must have the same exit node even if cannibalized. */
+ /* We must have the same exit node even if cannibalized or direct connection.
+ */
tor_assert(tor_memeq(intro->extend_info->identity_digest,
launched->build_state->chosen_exit->identity_digest,
DIGEST_LEN));
@@ -2655,6 +3062,7 @@ rend_service_launch_establish_intro(rend_service_t *service,
launched->intro_key = crypto_pk_dup_key(intro->intro_key);
if (launched->base_.state == CIRCUIT_STATE_OPEN)
rend_service_intro_has_opened(launched);
+ extend_info_free(direct_ei);
return 0;
}
@@ -2708,12 +3116,9 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
char auth[DIGEST_LEN + 9];
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
int reason = END_CIRC_REASON_TORPROTOCOL;
- crypto_pk_t *intro_key;
tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(circuit, get_options());
tor_assert(circuit->cpath);
tor_assert(circuit->rend_data);
@@ -2780,9 +3185,10 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
log_info(LD_REND,
"Established circuit %u as introduction point for service %s",
(unsigned)circuit->base_.n_circ_id, serviceid);
+ circuit_log_path(LOG_INFO, LD_REND, circuit);
/* Use the intro key instead of the service key in ESTABLISH_INTRO. */
- intro_key = circuit->intro_key;
+ crypto_pk_t *intro_key = circuit->intro_key;
/* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
r = crypto_pk_asn1_encode(intro_key, buf+2,
RELAY_PAYLOAD_SIZE-2);
@@ -2909,9 +3315,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
tor_assert(circuit->cpath);
tor_assert(circuit->build_state);
-#ifndef NON_ANONYMOUS_MODE_ENABLED
- tor_assert(!(circuit->build_state->onehop_tunnel));
-#endif
+ assert_circ_anonymity_ok(circuit, get_options());
tor_assert(circuit->rend_data);
/* Declare the circuit dirty to avoid reuse, and for path-bias */
@@ -2931,6 +3335,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
"Done building circuit %u to rendezvous with "
"cookie %s for service %s",
(unsigned)circuit->base_.n_circ_id, hexcookie, serviceid);
+ circuit_log_path(LOG_INFO, LD_REND, circuit);
/* Clear the 'in-progress HS circ has timed out' flag for
* consistency with what happens on the client side; this line has
@@ -3501,6 +3906,9 @@ rend_consider_services_intro_points(void)
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(
+ get_options());
/* List of nodes we need to _exclude_ when choosing a new node to
* establish an intro point to. */
smartlist_t *exclude_nodes;
@@ -3596,8 +4004,24 @@ rend_consider_services_intro_points(void)
router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC;
if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION)
flags |= CRN_ALLOW_INVALID;
+ router_crn_flags_t direct_flags = flags;
+ direct_flags |= CRN_PREF_ADDR;
+ direct_flags |= CRN_DIRECT_CONN;
+
node = router_choose_random_node(exclude_nodes,
- options->ExcludeNodes, flags);
+ options->ExcludeNodes,
+ allow_direct ? direct_flags : flags);
+ /* If we are in single onion mode, retry node selection for a 3-hop
+ * path */
+ if (allow_direct && !node) {
+ log_info(LD_REND,
+ "Unable to find an intro point that we can connect to "
+ "directly for %s, falling back to a 3-hop path.",
+ safe_str_client(service->service_id));
+ node = router_choose_random_node(exclude_nodes,
+ options->ExcludeNodes, flags);
+ }
+
if (!node) {
log_warn(LD_REND,
"We only have %d introduction points established for %s; "
@@ -3607,10 +4031,13 @@ rend_consider_services_intro_points(void)
n_intro_points_to_open);
break;
}
- /* Add the choosen node to the exclusion list in order to avoid to
- * pick it again in the next iteration. */
+ /* Add the choosen node to the exclusion list in order to avoid picking
+ * it again in the next iteration. */
smartlist_add(exclude_nodes, (void*)node);
intro = tor_malloc_zero(sizeof(rend_intro_point_t));
+ /* extend_info is for clients, so we want the multi-hop primary ORPort,
+ * even if we are a single onion service and intend to connect to it
+ * directly ourselves. */
intro->extend_info = extend_info_from_node(node, 0);
intro->intro_key = crypto_pk_new();
const int fail = crypto_pk_generate_key(intro->intro_key);
@@ -3656,8 +4083,9 @@ rend_consider_services_upload(time_t now)
{
int i;
rend_service_t *service;
- int rendpostperiod = get_options()->RendPostPeriod;
- int rendinitialpostdelay = (get_options()->TestingTorNetwork ?
+ const or_options_t *options = get_options();
+ int rendpostperiod = options->RendPostPeriod;
+ int rendinitialpostdelay = (options->TestingTorNetwork ?
MIN_REND_INITIAL_POST_DELAY_TESTING :
MIN_REND_INITIAL_POST_DELAY);
@@ -3668,6 +4096,12 @@ rend_consider_services_upload(time_t now)
* the descriptor is stable before being published. See comment below. */
service->next_upload_time =
now + rendinitialpostdelay + crypto_rand_int(2*rendpostperiod);
+ /* Single Onion Services prioritise availability over hiding their
+ * startup time, as their IP address is publicly discoverable anyway.
+ */
+ if (rend_service_reveal_startup_time(options)) {
+ service->next_upload_time = now + rendinitialpostdelay;
+ }
}
/* Does every introduction points have been established? */
unsigned int intro_points_ready =
@@ -3908,3 +4342,51 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
return -2;
}
+/* Are HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode consistent?
+ */
+static int
+rend_service_non_anonymous_mode_consistent(const or_options_t *options)
+{
+ /* !! is used to make these options boolean */
+ return (!! options->HiddenServiceSingleHopMode ==
+ !! options->HiddenServiceNonAnonymousMode);
+}
+
+/* Do the options allow onion services to make direct (non-anonymous)
+ * connections to introduction or rendezvous points?
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ * Returns true if tor is in HiddenServiceSingleHopMode. */
+int
+rend_service_allow_non_anonymous_connection(const or_options_t *options)
+{
+ tor_assert(rend_service_non_anonymous_mode_consistent(options));
+ return options->HiddenServiceSingleHopMode ? 1 : 0;
+}
+
+/* Do the options allow us to reveal the exact startup time of the onion
+ * service?
+ * Single Onion Services prioritise availability over hiding their
+ * startup time, as their IP address is publicly discoverable anyway.
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ * Returns true if tor is in non-anonymous hidden service mode. */
+int
+rend_service_reveal_startup_time(const or_options_t *options)
+{
+ tor_assert(rend_service_non_anonymous_mode_consistent(options));
+ return rend_service_non_anonymous_mode_enabled(options);
+}
+
+/* Is non-anonymous mode enabled using the HiddenServiceNonAnonymousMode
+ * config option?
+ * Must only be called after options_validate_single_onion() has successfully
+ * checked onion service option consistency.
+ */
+int
+rend_service_non_anonymous_mode_enabled(const or_options_t *options)
+{
+ tor_assert(rend_service_non_anonymous_mode_consistent(options));
+ return options->HiddenServiceNonAnonymousMode ? 1 : 0;
+}
+
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index 101b37e18d..3b185672f6 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -63,11 +63,77 @@ struct rend_intro_cell_s {
uint8_t dh[DH_KEY_LEN];
};
+/** Represents a single hidden service running at this OP. */
+typedef struct rend_service_t {
+ /* Fields specified in config file */
+ char *directory; /**< where in the filesystem it stores it. Will be NULL if
+ * this service is ephemeral. */
+ int dir_group_readable; /**< if 1, allow group read
+ permissions on directory */
+ smartlist_t *ports; /**< List of rend_service_port_config_t */
+ rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
+ * authorization is performed. */
+ smartlist_t *clients; /**< List of rend_authorized_client_t's of
+ * clients that may access our service. Can be NULL
+ * if no client authorization is performed. */
+ /* Other fields */
+ crypto_pk_t *private_key; /**< Permanent hidden-service key. */
+ char service_id[REND_SERVICE_ID_LEN_BASE32+1]; /**< Onion address without
+ * '.onion' */
+ char pk_digest[DIGEST_LEN]; /**< Hash of permanent hidden-service key. */
+ smartlist_t *intro_nodes; /**< List of rend_intro_point_t's we have,
+ * or are trying to establish. */
+ /** List of rend_intro_point_t that are expiring. They are removed once
+ * the new descriptor is successfully uploaded. A node in this list CAN
+ * NOT appear in the intro_nodes list. */
+ smartlist_t *expiring_nodes;
+ time_t intro_period_started; /**< Start of the current period to build
+ * introduction points. */
+ int n_intro_circuits_launched; /**< Count of intro circuits we have
+ * established in this period. */
+ unsigned int n_intro_points_wanted; /**< Number of intro points this
+ * service wants to have open. */
+ rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
+ time_t desc_is_dirty; /**< Time at which changes to the hidden service
+ * descriptor content occurred, or 0 if it's
+ * up-to-date. */
+ time_t next_upload_time; /**< Scheduled next hidden service descriptor
+ * upload time. */
+ /** Replay cache for Diffie-Hellman values of INTRODUCE2 cells, 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 *accepted_intro_dh_parts;
+ /** If true, we don't close circuits for making requests to unsupported
+ * ports. */
+ int allow_unknown_ports;
+ /** The maximum number of simultanious streams-per-circuit that are allowed
+ * to be established, or 0 if no limit is set.
+ */
+ int max_streams_per_circuit;
+ /** If true, we close circuits that exceed the max_streams_per_circuit
+ * limit. */
+ int max_streams_close_circuit;
+} 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);
#endif
int num_rend_services(void);
int rend_config_services(const or_options_t *options, int validate_only);
-int rend_service_load_all_keys(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);
@@ -106,8 +172,11 @@ rend_service_port_config_t *rend_service_parse_port_config(const char *string,
char **err_msg_out);
void rend_service_port_config_free(rend_service_port_config_t *p);
+void rend_authorized_client_free(rend_authorized_client_t *client);
+
/** Return value from rend_service_add_ephemeral. */
typedef enum {
+ RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */
RSAE_BADVIRTPORT = -4, /**< Invalid VIRTPORT/TARGET(s) */
RSAE_ADDREXISTS = -3, /**< Onion address collision */
RSAE_BADPRIVKEY = -2, /**< Invalid public key */
@@ -118,6 +187,8 @@ rend_service_add_ephemeral_status_t rend_service_add_ephemeral(crypto_pk_t *pk,
smartlist_t *ports,
int max_streams_per_circuit,
int max_streams_close_circuit,
+ rend_auth_type_t auth_type,
+ smartlist_t *auth_clients,
char **service_id_out);
int rend_service_del_ephemeral(const char *service_id);
@@ -126,5 +197,9 @@ void directory_post_to_hs_dir(rend_service_descriptor_t *renddesc,
const char *service_id, int seconds_valid);
void rend_service_desc_has_uploaded(const rend_data_t *rend_data);
+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
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 651fd43ade..f0bac57898 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -4,10 +4,74 @@
/**
* \file rephist.c
- * \brief Basic history and "reputation" functionality to remember
+ * \brief Basic history and performance-tracking functionality.
+ *
+ * Basic history and performance-tracking functionality to remember
* which servers have worked in the past, how much bandwidth we've
* been using, which ports we tend to want, and so on; further,
* exit port statistics, cell statistics, and connection statistics.
+ *
+ * The history and information tracked in this module could sensibly be
+ * divided into several categories:
+ *
+ * <ul><li>Statistics used by authorities to remember the uptime and
+ * stability information about various relays, including "uptime",
+ * "weighted fractional uptime" and "mean time between failures".
+ *
+ * <li>Bandwidth usage history, used by relays to self-report how much
+ * bandwidth they've used for different purposes over last day or so,
+ * in order to generate the {dirreq-,}{read,write}-history lines in
+ * that they publish.
+ *
+ * <li>Predicted ports, used by clients to remember how long it's been
+ * since they opened an exit connection to each given target
+ * port. Clients use this information in order to try to keep circuits
+ * open to exit nodes that can connect to the ports that they care
+ * about. (The predicted ports mechanism also handles predicted circuit
+ * usage that _isn't_ port-specific, such as resolves, internal circuits,
+ * and so on.)
+ *
+ * <li>Public key operation counters, for tracking how many times we've
+ * done each public key operation. (This is unmaintained and we should
+ * remove it.)
+ *
+ * <li>Exit statistics by port, used by exits to keep track of the
+ * number of streams and bytes they've served at each exit port, so they
+ * can generate their exit-kibibytes-{read,written} and
+ * exit-streams-opened statistics.
+ *
+ * <li>Circuit stats, used by relays instances to tract circuit
+ * queue fullness and delay over time, and generate cell-processed-cells,
+ * cell-queued-cells, cell-time-in-queue, and cell-circuits-per-decile
+ * statistics.
+ *
+ * <li>Descriptor serving statistics, used by directory caches to track
+ * how many descriptors they've served.
+ *
+ * <li>Connection statistics, used by relays to track one-way and
+ * bidirectional connections.
+ *
+ * <li>Onion handshake statistics, used by relays to count how many
+ * TAP and ntor handshakes they've handled.
+ *
+ * <li>Hidden service statistics, used by relays to count rendezvous
+ * traffic and HSDir-stored descriptors.
+ *
+ * <li>Link protocol statistics, used by relays to count how many times
+ * each link protocol has been used.
+ *
+ * </ul>
+ *
+ * The entry points for this module are scattered throughout the
+ * codebase. Sending data, receiving data, connecting to a relay,
+ * losing a connection to a relay, and so on can all trigger a change in
+ * our current stats. Relays also invoke this module in order to
+ * extract their statistics when building routerinfo and extrainfo
+ * objects in router.c.
+ *
+ * TODO: This module should be broken up.
+ *
+ * (The "rephist" name originally stood for "reputation and history". )
**/
#include "or.h"
@@ -604,7 +668,7 @@ rep_hist_get_weighted_time_known(const char *id, time_t when)
int
rep_hist_have_measured_enough_stability(void)
{
- /* XXXX023 This doesn't do so well when we change our opinion
+ /* XXXX++ This doesn't do so well when we change our opinion
* as to whether we're tracking router stability. */
return started_tracking_stability < time(NULL) - 4*60*60;
}
@@ -743,14 +807,15 @@ rep_history_clean(time_t before)
orhist_it = digestmap_iter_init(history_map);
while (!digestmap_iter_done(orhist_it)) {
- int remove;
+ int should_remove;
digestmap_iter_get(orhist_it, &d1, &or_history_p);
or_history = or_history_p;
- remove = authority ? (or_history->total_run_weights < STABILITY_EPSILON &&
+ should_remove = authority ?
+ (or_history->total_run_weights < STABILITY_EPSILON &&
!or_history->start_of_run)
: (or_history->changed < before);
- if (remove) {
+ if (should_remove) {
orhist_it = digestmap_iter_next_rmv(history_map, orhist_it);
free_or_history(or_history);
continue;
@@ -1074,7 +1139,8 @@ rep_hist_load_mtbf_data(time_t now)
if (mtbf_idx > i)
i = mtbf_idx;
}
- if (base16_decode(digest, DIGEST_LEN, hexbuf, HEX_DIGEST_LEN) < 0) {
+ if (base16_decode(digest, DIGEST_LEN,
+ hexbuf, HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_HIST, "Couldn't hex string %s", escaped(hexbuf));
continue;
}
@@ -2293,16 +2359,16 @@ void
rep_hist_add_buffer_stats(double mean_num_cells_in_queue,
double mean_time_cells_in_queue, uint32_t processed_cells)
{
- circ_buffer_stats_t *stat;
+ circ_buffer_stats_t *stats;
if (!start_of_buffer_stats_interval)
return; /* Not initialized. */
- stat = tor_malloc_zero(sizeof(circ_buffer_stats_t));
- stat->mean_num_cells_in_queue = mean_num_cells_in_queue;
- stat->mean_time_cells_in_queue = mean_time_cells_in_queue;
- stat->processed_cells = processed_cells;
+ stats = tor_malloc_zero(sizeof(circ_buffer_stats_t));
+ stats->mean_num_cells_in_queue = mean_num_cells_in_queue;
+ stats->mean_time_cells_in_queue = mean_time_cells_in_queue;
+ stats->processed_cells = processed_cells;
if (!circuits_for_buffer_stats)
circuits_for_buffer_stats = smartlist_new();
- smartlist_add(circuits_for_buffer_stats, stat);
+ smartlist_add(circuits_for_buffer_stats, stats);
}
/** Remember cell statistics for circuit <b>circ</b> at time
@@ -2372,7 +2438,7 @@ rep_hist_reset_buffer_stats(time_t now)
if (!circuits_for_buffer_stats)
circuits_for_buffer_stats = smartlist_new();
SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *,
- stat, tor_free(stat));
+ stats, tor_free(stats));
smartlist_clear(circuits_for_buffer_stats);
start_of_buffer_stats_interval = now;
}
@@ -2413,15 +2479,15 @@ rep_hist_format_buffer_stats(time_t now)
buffer_stats_compare_entries_);
i = 0;
SMARTLIST_FOREACH_BEGIN(circuits_for_buffer_stats,
- circ_buffer_stats_t *, stat)
+ circ_buffer_stats_t *, stats)
{
int share = i++ * SHARES / number_of_circuits;
- processed_cells[share] += stat->processed_cells;
- queued_cells[share] += stat->mean_num_cells_in_queue;
- time_in_queue[share] += stat->mean_time_cells_in_queue;
+ processed_cells[share] += stats->processed_cells;
+ queued_cells[share] += stats->mean_num_cells_in_queue;
+ time_in_queue[share] += stats->mean_time_cells_in_queue;
circs_in_share[share]++;
}
- SMARTLIST_FOREACH_END(stat);
+ SMARTLIST_FOREACH_END(stats);
}
/* Write deciles to strings. */
@@ -2648,7 +2714,9 @@ rep_hist_desc_stats_write(time_t now)
return start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL;
}
-/* DOCDOC rep_hist_note_desc_served */
+/** Called to note that we've served a given descriptor (by
+ * digest). Incrememnts the count of descriptors served, and the number
+ * of times we've served this descriptor. */
void
rep_hist_note_desc_served(const char * desc)
{
@@ -2738,7 +2806,7 @@ bidi_map_ent_hash(const bidi_map_entry_t *entry)
}
HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
- bidi_map_ent_eq);
+ bidi_map_ent_eq)
HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_)
@@ -2933,7 +3001,7 @@ static time_t start_of_hs_stats_interval;
* information needed. */
typedef struct hs_stats_t {
/** How many relay cells have we seen as rendezvous points? */
- int64_t rp_relay_cells_seen;
+ uint64_t rp_relay_cells_seen;
/** Set of unique public key digests we've seen this stat period
* (could also be implemented as sorted smartlist). */
@@ -2947,22 +3015,22 @@ static hs_stats_t *hs_stats = NULL;
static hs_stats_t *
hs_stats_new(void)
{
- hs_stats_t * hs_stats = tor_malloc_zero(sizeof(hs_stats_t));
- hs_stats->onions_seen_this_period = digestmap_new();
+ hs_stats_t *new_hs_stats = tor_malloc_zero(sizeof(hs_stats_t));
+ new_hs_stats->onions_seen_this_period = digestmap_new();
- return hs_stats;
+ return new_hs_stats;
}
/** Free an hs_stats_t structure. */
static void
-hs_stats_free(hs_stats_t *hs_stats)
+hs_stats_free(hs_stats_t *victim_hs_stats)
{
- if (!hs_stats) {
+ if (!victim_hs_stats) {
return;
}
- digestmap_free(hs_stats->onions_seen_this_period, NULL);
- tor_free(hs_stats);
+ digestmap_free(victim_hs_stats->onions_seen_this_period, NULL);
+ tor_free(victim_hs_stats);
}
/** Initialize hidden service statistics. */
@@ -3074,16 +3142,20 @@ rep_hist_format_hs_stats(time_t now)
int64_t obfuscated_cells_seen;
int64_t obfuscated_onions_seen;
- obfuscated_cells_seen = round_int64_to_next_multiple_of(
- hs_stats->rp_relay_cells_seen,
- REND_CELLS_BIN_SIZE);
- obfuscated_cells_seen = add_laplace_noise(obfuscated_cells_seen,
+ uint64_t rounded_cells_seen
+ = round_uint64_to_next_multiple_of(hs_stats->rp_relay_cells_seen,
+ REND_CELLS_BIN_SIZE);
+ rounded_cells_seen = MIN(rounded_cells_seen, INT64_MAX);
+ obfuscated_cells_seen = add_laplace_noise((int64_t)rounded_cells_seen,
crypto_rand_double(),
REND_CELLS_DELTA_F, REND_CELLS_EPSILON);
- obfuscated_onions_seen = round_int64_to_next_multiple_of(digestmap_size(
- hs_stats->onions_seen_this_period),
- ONIONS_SEEN_BIN_SIZE);
- obfuscated_onions_seen = add_laplace_noise(obfuscated_onions_seen,
+
+ uint64_t rounded_onions_seen =
+ round_uint64_to_next_multiple_of((size_t)digestmap_size(
+ hs_stats->onions_seen_this_period),
+ ONIONS_SEEN_BIN_SIZE);
+ rounded_onions_seen = MIN(rounded_onions_seen, INT64_MAX);
+ obfuscated_onions_seen = add_laplace_noise((int64_t)rounded_onions_seen,
crypto_rand_double(), ONIONS_SEEN_DELTA_F,
ONIONS_SEEN_EPSILON);
@@ -3217,7 +3289,7 @@ rep_hist_free_all(void)
rep_hist_desc_stats_term();
total_descriptor_downloads = 0;
- tor_assert(rephist_total_alloc == 0);
- tor_assert(rephist_total_num == 0);
+ tor_assert_nonfatal(rephist_total_alloc == 0);
+ tor_assert_nonfatal_once(rephist_total_num == 0);
}
diff --git a/src/or/rephist.h b/src/or/rephist.h
index 145da97d02..ff4810a56d 100644
--- a/src/or/rephist.h
+++ b/src/or/rephist.h
@@ -112,5 +112,12 @@ void rep_hist_note_negotiated_link_proto(unsigned link_proto,
int started_here);
void rep_hist_log_link_protocol_counts(void);
+extern uint64_t rephist_total_alloc;
+extern uint32_t rephist_total_num;
+#ifdef TOR_UNIT_TESTS
+extern int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1];
+extern int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1];
+#endif
+
#endif
diff --git a/src/or/replaycache.c b/src/or/replaycache.c
index 23a1737b18..8290fa6964 100644
--- a/src/or/replaycache.c
+++ b/src/or/replaycache.c
@@ -1,10 +1,22 @@
/* Copyright (c) 2012-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
-/*
+/**
* \file replaycache.c
*
* \brief Self-scrubbing replay cache for rendservice.c
+ *
+ * To prevent replay attacks, hidden services need to recognize INTRODUCE2
+ * cells that they've already seen, and drop them. If they didn't, then
+ * sending the same INTRODUCE2 cell over and over would force the hidden
+ * service to make a huge number of circuits to the same rendezvous
+ * point, aiding traffic analysis.
+ *
+ * (It's not that simple, actually. We only check for replays in the
+ * RSA-encrypted portion of the handshake, since the rest of the handshake is
+ * malleable.)
+ *
+ * This module is used from rendservice.c.
*/
#define REPLAYCACHE_PRIVATE
diff --git a/src/or/router.c b/src/or/router.c
index b134f2e315..31f2ff00d2 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -23,6 +23,7 @@
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
+#include "protover.h"
#include "relay.h"
#include "rephist.h"
#include "router.h"
@@ -36,12 +37,25 @@
/**
* \file router.c
- * \brief OR functionality, including key maintenance, generating
- * and uploading server descriptors, retrying OR connections.
+ * \brief Miscellaneous relay functionality, including RSA key maintenance,
+ * generating and uploading server descriptors, picking an address to
+ * advertise, and so on.
+ *
+ * This module handles the job of deciding whether we are a Tor relay, and if
+ * so what kind. (Mostly through functions like server_mode() that inspect an
+ * or_options_t, but in some cases based on our own capabilities, such as when
+ * we are deciding whether to be a directory cache in
+ * router_has_bandwidth_to_be_dirserver().)
+ *
+ * Also in this module are the functions to generate our own routerinfo_t and
+ * extrainfo_t, and to encode those to signed strings for upload to the
+ * directory authorities.
+ *
+ * This module also handles key maintenance for RSA and Curve25519-ntor keys,
+ * and for our TLS context. (These functions should eventually move to
+ * routerkeys.c along with the code that handles Ed25519 keys now.)
**/
-extern long stats_n_seconds_working;
-
/************************************************************/
/*****
@@ -454,7 +468,8 @@ init_key_from_file(const char *fname, int generate, int severity,
goto error;
}
} else {
- log_info(LD_GENERAL, "No key found in \"%s\"", fname);
+ tor_log(severity, LD_GENERAL, "No key found in \"%s\"", fname);
+ goto error;
}
return prkey;
case FN_FILE:
@@ -562,7 +577,7 @@ load_authority_keyset(int legacy, crypto_pk_t **key_out,
fname = get_datadir_fname2("keys",
legacy ? "legacy_signing_key" : "authority_signing_key");
- signing_key = init_key_from_file(fname, 0, LOG_INFO, 0);
+ signing_key = init_key_from_file(fname, 0, LOG_ERR, 0);
if (!signing_key) {
log_warn(LD_DIR, "No version 3 directory key found in %s", fname);
goto done;
@@ -1054,7 +1069,8 @@ init_keys(void)
log_info(LD_DIR, "adding my own v3 cert");
if (trusted_dirs_load_certs_from_string(
cert->cache_info.signed_descriptor_body,
- TRUSTED_DIRS_CERTS_SRC_SELF, 0)<0) {
+ TRUSTED_DIRS_CERTS_SRC_SELF, 0,
+ NULL)<0) {
log_warn(LD_DIR, "Unable to parse my own v3 cert! Failing.");
return -1;
}
@@ -1285,15 +1301,17 @@ decide_to_advertise_begindir(const or_options_t *options,
}
/** Allocate and return a new extend_info_t that can be used to build
- * a circuit to or through the router <b>r</b>. Use the primary
- * address of the router unless <b>for_direct_connect</b> is true, in
- * which case the preferred address is used instead. */
+ * a circuit to or through the router <b>r</b>. Uses the primary
+ * address of the router, so should only be called on a server. */
static extend_info_t *
extend_info_from_router(const routerinfo_t *r)
{
tor_addr_port_t ap;
tor_assert(r);
+ /* Make sure we don't need to check address reachability */
+ tor_assert_nonfatal(router_skip_or_reachability(get_options(), 0));
+
router_get_prim_orport(r, &ap);
return extend_info_new(r->nickname, r->cache_info.identity_digest,
r->onion_pkey, r->onion_curve25519_pkey,
@@ -1537,7 +1555,7 @@ MOCK_IMPL(int,
server_mode,(const or_options_t *options))
{
if (options->ClientOnly) return 0;
- /* XXXX024 I believe we can kill off ORListenAddress here.*/
+ /* XXXX I believe we can kill off ORListenAddress here.*/
return (options->ORPort_set || options->ORListenAddress);
}
@@ -1943,23 +1961,111 @@ static int router_guess_address_from_dir_headers(uint32_t *guess);
/** Make a current best guess at our address, either because
* it's configured in torrc, or because we've learned it from
* dirserver headers. Place the answer in *<b>addr</b> and return
- * 0 on success, else return -1 if we have no guess. */
+ * 0 on success, else return -1 if we have no guess.
+ *
+ * If <b>cache_only</b> is true, just return any cached answers, and
+ * don't try to get any new answers.
+ */
MOCK_IMPL(int,
-router_pick_published_address,(const or_options_t *options, uint32_t *addr))
+router_pick_published_address,(const or_options_t *options, uint32_t *addr,
+ int cache_only))
{
+ /* First, check the cached output from resolve_my_address(). */
*addr = get_last_resolved_addr();
- if (!*addr &&
- resolve_my_address(LOG_INFO, options, addr, NULL, NULL) < 0) {
- log_info(LD_CONFIG, "Could not determine our address locally. "
- "Checking if directory headers provide any hints.");
- if (router_guess_address_from_dir_headers(addr) < 0) {
- log_info(LD_CONFIG, "No hints from directory headers either. "
- "Will try again later.");
- return -1;
+ if (*addr)
+ return 0;
+
+ /* Second, consider doing a resolve attempt right here. */
+ if (!cache_only) {
+ if (resolve_my_address(LOG_INFO, options, addr, NULL, NULL) >= 0) {
+ log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr));
+ return 0;
}
}
- log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr));
- return 0;
+
+ /* Third, check the cached output from router_new_address_suggestion(). */
+ if (router_guess_address_from_dir_headers(addr) >= 0)
+ return 0;
+
+ /* We have no useful cached answers. Return failure. */
+ return -1;
+}
+
+/* Like router_check_descriptor_address_consistency, but specifically for the
+ * ORPort or DirPort.
+ * listener_type is either CONN_TYPE_OR_LISTENER or CONN_TYPE_DIR_LISTENER. */
+static void
+router_check_descriptor_address_port_consistency(uint32_t ipv4h_desc_addr,
+ int listener_type)
+{
+ tor_assert(listener_type == CONN_TYPE_OR_LISTENER ||
+ listener_type == CONN_TYPE_DIR_LISTENER);
+
+ /* The first advertised Port may be the magic constant CFG_AUTO_PORT.
+ */
+ int port_v4_cfg = get_first_advertised_port_by_type_af(listener_type,
+ AF_INET);
+ if (port_v4_cfg != 0 &&
+ !port_exists_by_type_addr32h_port(listener_type,
+ ipv4h_desc_addr, port_v4_cfg, 1)) {
+ const tor_addr_t *port_addr = get_first_advertised_addr_by_type_af(
+ listener_type,
+ AF_INET);
+ /* If we're building a descriptor with no advertised address,
+ * something is terribly wrong. */
+ tor_assert(port_addr);
+
+ tor_addr_t desc_addr;
+ char port_addr_str[TOR_ADDR_BUF_LEN];
+ char desc_addr_str[TOR_ADDR_BUF_LEN];
+
+ tor_addr_to_str(port_addr_str, port_addr, TOR_ADDR_BUF_LEN, 0);
+
+ tor_addr_from_ipv4h(&desc_addr, ipv4h_desc_addr);
+ tor_addr_to_str(desc_addr_str, &desc_addr, TOR_ADDR_BUF_LEN, 0);
+
+ const char *listener_str = (listener_type == CONN_TYPE_OR_LISTENER ?
+ "OR" : "Dir");
+ log_warn(LD_CONFIG, "The IPv4 %sPort address %s does not match the "
+ "descriptor address %s. If you have a static public IPv4 "
+ "address, use 'Address <IPv4>' and 'OutboundBindAddress "
+ "<IPv4>'. If you are behind a NAT, use two %sPort lines: "
+ "'%sPort <PublicPort> NoListen' and '%sPort <InternalPort> "
+ "NoAdvertise'.",
+ listener_str, port_addr_str, desc_addr_str, listener_str,
+ listener_str, listener_str);
+ }
+}
+
+/* Tor relays only have one IPv4 address in the descriptor, which is derived
+ * from the Address torrc option, or guessed using various methods in
+ * router_pick_published_address().
+ * Warn the operator if there is no ORPort on the descriptor address
+ * ipv4h_desc_addr.
+ * Warn the operator if there is no DirPort on the descriptor address.
+ * This catches a few common config errors:
+ * - operators who expect ORPorts and DirPorts to be advertised on the
+ * ports' listen addresses, rather than the torrc Address (or guessed
+ * addresses in the absence of an Address config). This includes
+ * operators who attempt to put their ORPort and DirPort on different
+ * addresses;
+ * - discrepancies between guessed addresses and configured listen
+ * addresses (when the Address option isn't set).
+ * If a listener is listening on all IPv4 addresses, it is assumed that it
+ * is listening on the configured Address, and no messages are logged.
+ * If an operators has specified NoAdvertise ORPorts in a NAT setting,
+ * no messages are logged, unless they have specified other advertised
+ * addresses.
+ * The message tells operators to configure an ORPort and DirPort that match
+ * the Address (using NoListen if needed).
+ */
+static void
+router_check_descriptor_address_consistency(uint32_t ipv4h_desc_addr)
+{
+ router_check_descriptor_address_port_consistency(ipv4h_desc_addr,
+ CONN_TYPE_OR_LISTENER);
+ router_check_descriptor_address_port_consistency(ipv4h_desc_addr,
+ CONN_TYPE_DIR_LISTENER);
}
/** Build a fresh routerinfo, signed server descriptor, and extra-info document
@@ -1979,11 +2085,15 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
int hibernating = we_are_hibernating();
const or_options_t *options = get_options();
- if (router_pick_published_address(options, &addr) < 0) {
+ if (router_pick_published_address(options, &addr, 0) < 0) {
log_warn(LD_CONFIG, "Don't know my address while generating descriptor");
return -1;
}
+ /* Log a message if the address in the descriptor doesn't match the ORPort
+ * and DirPort addresses configured by the operator. */
+ router_check_descriptor_address_consistency(addr);
+
ri = tor_malloc_zero(sizeof(routerinfo_t));
ri->cache_info.routerlist_index = -1;
ri->nickname = tor_strdup(options->Nickname);
@@ -2009,8 +2119,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
tor_addr_family(&p->addr) == AF_INET6) {
/* Like IPv4, if the relay is configured using the default
* authorities, disallow internal IPs. Otherwise, allow them. */
- const int default_auth = (!options->DirAuthorities &&
- !options->AlternateDirAuthority);
+ const int default_auth = using_default_dir_authorities(options);
if (! tor_addr_is_internal(&p->addr, 0) || ! default_auth) {
ipv6_orport = p;
break;
@@ -2042,6 +2151,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
get_platform_str(platform, sizeof(platform));
ri->platform = tor_strdup(platform);
+ ri->protocol_list = tor_strdup(protover_get_supported_protocols());
+
/* compute ri->bandwidthrate as the min of various options */
ri->bandwidthrate = get_effective_bwrate(options);
@@ -2058,8 +2169,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
&ri->exit_policy);
}
ri->policy_is_reject_star =
- policy_is_reject_star(ri->exit_policy, AF_INET) &&
- policy_is_reject_star(ri->exit_policy, AF_INET6);
+ policy_is_reject_star(ri->exit_policy, AF_INET, 1) &&
+ policy_is_reject_star(ri->exit_policy, AF_INET6, 1);
if (options->IPv6Exit) {
char *p_tmp = policy_summarize(ri->exit_policy, AF_INET6);
@@ -2223,7 +2334,7 @@ router_rebuild_descriptor(int force)
if (desc_clean_since && !force)
return 0;
- if (router_pick_published_address(options, &addr) < 0 ||
+ if (router_pick_published_address(options, &addr, 0) < 0 ||
router_get_advertised_or_port(options) == 0) {
/* Stop trying to rebuild our descriptor every second. We'll
* learn that it's time to try again when ip_address_changed()
@@ -2534,6 +2645,7 @@ router_dump_router_to_string(routerinfo_t *router,
char *ed_cert_line = NULL;
char *rsa_tap_cc_line = NULL;
char *ntor_cc_line = NULL;
+ char *proto_line = NULL;
/* Make sure the identity key matches the one in the routerinfo. */
if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) {
@@ -2698,6 +2810,12 @@ router_dump_router_to_string(routerinfo_t *router,
}
}
+ if (router->protocol_list) {
+ tor_asprintf(&proto_line, "proto %s\n", router->protocol_list);
+ } else {
+ proto_line = tor_strdup("");
+ }
+
address = tor_dup_ip(router->addr);
chunks = smartlist_new();
@@ -2707,7 +2825,7 @@ router_dump_router_to_string(routerinfo_t *router,
"%s"
"%s"
"platform %s\n"
- "protocols Link 1 2 Circuit 1\n"
+ "%s"
"published %s\n"
"fingerprint %s\n"
"uptime %ld\n"
@@ -2724,6 +2842,7 @@ router_dump_router_to_string(routerinfo_t *router,
ed_cert_line ? ed_cert_line : "",
extra_or_address ? extra_or_address : "",
router->platform,
+ proto_line,
published,
fingerprint,
stats_n_seconds_working,
@@ -2763,6 +2882,10 @@ router_dump_router_to_string(routerinfo_t *router,
(const char *)router->onion_curve25519_pkey->public_key,
CURVE25519_PUBKEY_LEN, BASE64_ENCODE_MULTILINE);
smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf);
+ } else {
+ /* Authorities will start rejecting relays without ntor keys in 0.2.9 */
+ log_err(LD_BUG, "A relay must have an ntor onion key");
+ goto err;
}
/* Write the exit policy to the end of 's'. */
@@ -2865,6 +2988,7 @@ router_dump_router_to_string(routerinfo_t *router,
tor_free(rsa_tap_cc_line);
tor_free(ntor_cc_line);
tor_free(extra_info_line);
+ tor_free(proto_line);
return output;
}
@@ -3087,17 +3211,17 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
}
if (emit_ed_sigs) {
- char digest[DIGEST256_LEN];
+ char sha256_digest[DIGEST256_LEN];
smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
- crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
+ crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN,
ED_DESC_SIGNATURE_PREFIX,
chunks, "", DIGEST_SHA256);
- ed25519_signature_t sig;
+ ed25519_signature_t ed_sig;
char buf[ED25519_SIG_BASE64_LEN+1];
- if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN,
+ if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN,
signing_keypair) < 0)
goto err;
- if (ed25519_signature_to_base64(buf, &sig) < 0)
+ if (ed25519_signature_to_base64(buf, &ed_sig) < 0)
goto err;
smartlist_add_asprintf(chunks, "%s\n", buf);
@@ -3171,7 +3295,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
done:
tor_free(s);
- SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(chunks, char *, chunk, tor_free(chunk));
smartlist_free(chunks);
tor_free(s_dup);
tor_free(ed_cert_line);
diff --git a/src/or/router.h b/src/or/router.h
index 73bfea1faa..c30a0301b7 100644
--- a/src/or/router.h
+++ b/src/or/router.h
@@ -91,7 +91,8 @@ const uint8_t *router_get_my_id_digest(void);
int router_extrainfo_digest_is_me(const char *digest);
int router_is_me(const routerinfo_t *router);
MOCK_DECL(int,router_pick_published_address,(const or_options_t *options,
- uint32_t *addr));
+ uint32_t *addr,
+ int cache_only));
int router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e);
int router_rebuild_descriptor(int force);
char *router_dump_router_to_string(routerinfo_t *router,
diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c
index fba3491f2b..ca32228fc7 100644
--- a/src/or/routerkeys.c
+++ b/src/or/routerkeys.c
@@ -48,7 +48,7 @@ do_getpass(const char *prompt, char *buf, size_t buflen,
size_t p2len = strlen(prompt) + 1;
if (p2len < sizeof(msg))
p2len = sizeof(msg);
- prompt2 = tor_malloc(strlen(prompt)+1);
+ prompt2 = tor_malloc(p2len);
memset(prompt2, ' ', p2len);
memcpy(prompt2 + p2len - sizeof(msg), msg, sizeof(msg));
@@ -115,20 +115,20 @@ read_encrypted_secret_key(ed25519_secret_key_t *out,
while (1) {
ssize_t pwlen =
- do_getpass("Enter pasphrase for master key:", pwbuf, sizeof(pwbuf), 0,
+ do_getpass("Enter passphrase for master key:", pwbuf, sizeof(pwbuf), 0,
get_options());
if (pwlen < 0) {
saved_errno = EINVAL;
goto done;
}
- const int r = crypto_unpwbox(&secret, &secret_len,
- encrypted_key, encrypted_len,
- pwbuf, pwlen);
- if (r == UNPWBOX_CORRUPTED) {
+ const int r_unbox = crypto_unpwbox(&secret, &secret_len,
+ encrypted_key, encrypted_len,
+ pwbuf, pwlen);
+ if (r_unbox == UNPWBOX_CORRUPTED) {
log_err(LD_OR, "%s is corrupted.", fname);
saved_errno = EINVAL;
goto done;
- } else if (r == UNPWBOX_OKAY) {
+ } else if (r_unbox == UNPWBOX_OKAY) {
break;
}
@@ -931,15 +931,15 @@ load_ed_keys(const or_options_t *options, time_t now)
int
generate_ed_link_cert(const or_options_t *options, time_t now)
{
- const tor_x509_cert_t *link = NULL, *id = NULL;
+ const tor_x509_cert_t *link_ = NULL, *id = NULL;
tor_cert_t *link_cert = NULL;
- if (tor_tls_get_my_certs(1, &link, &id) < 0 || link == NULL) {
+ if (tor_tls_get_my_certs(1, &link_, &id) < 0 || link_ == NULL) {
log_warn(LD_OR, "Can't get my x509 link cert.");
return -1;
}
- const common_digests_t *digests = tor_x509_cert_get_cert_digests(link);
+ const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_);
if (link_cert_cert &&
! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) &&
@@ -979,12 +979,12 @@ should_make_new_ed_keys(const or_options_t *options, const time_t now)
EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop))
return 1;
- const tor_x509_cert_t *link = NULL, *id = NULL;
+ const tor_x509_cert_t *link_ = NULL, *id = NULL;
- if (tor_tls_get_my_certs(1, &link, &id) < 0 || link == NULL)
+ if (tor_tls_get_my_certs(1, &link_, &id) < 0 || link_ == NULL)
return 1;
- const common_digests_t *digests = tor_x509_cert_get_cert_digests(link);
+ const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_);
if (!fast_memeq(digests->d[DIGEST_SHA256],
link_cert_cert->signed_key.pubkey,
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 0c7c5e98f1..1ad03b6cda 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -9,6 +9,85 @@
* \brief Code to
* maintain and access the global list of routerinfos for known
* servers.
+ *
+ * A "routerinfo_t" object represents a single self-signed router
+ * descriptor, as generated by a Tor relay in order to tell the rest of
+ * the world about its keys, address, and capabilities. An
+ * "extrainfo_t" object represents an adjunct "extra-info" object,
+ * certified by a corresponding router descriptor, reporting more
+ * information about the relay that nearly all users will not need.
+ *
+ * Most users will not use router descriptors for most relays. Instead,
+ * they use the information in microdescriptors and in the consensus
+ * networkstatus.
+ *
+ * Right now, routerinfo_t objects are used in these ways:
+ * <ul>
+ * <li>By clients, in order to learn about bridge keys and capabilities.
+ * (Bridges aren't listed in the consensus networkstatus, so they
+ * can't have microdescriptors.)
+ * <li>By relays, since relays want more information about other relays
+ * than they can learn from microdescriptors. (TODO: Is this still true?)
+ * <li>By authorities, which receive them and use them to generate the
+ * consensus and the microdescriptors.
+ * <li>By all directory caches, which download them in case somebody
+ * else wants them.
+ * </ul>
+ *
+ * Routerinfos are mostly created by parsing them from a string, in
+ * routerparse.c. We store them to disk on receiving them, and
+ * periodically discard the ones we don't need. On restarting, we
+ * re-read them from disk. (This also applies to extrainfo documents, if
+ * we are configured to fetch them.)
+ *
+ * In order to keep our list of routerinfos up-to-date, we periodically
+ * check whether there are any listed in the latest consensus (or in the
+ * votes from other authorities, if we are an authority) that we don't
+ * have. (This also applies to extrainfo documents, if we are
+ * configured to fetch them.)
+ *
+ * Almost nothing in Tor should use a routerinfo_t to refer directly to
+ * a relay; instead, almost everything should use node_t (implemented in
+ * nodelist.c), which provides a common interface to routerinfo_t,
+ * routerstatus_t, and microdescriptor_t.
+ *
+ * <br>
+ *
+ * This module also has some of the functions used for choosing random
+ * nodes according to different rules and weights. Historically, they
+ * were all in this module. Now, they are spread across this module,
+ * nodelist.c, and networkstatus.c. (TODO: Fix that.)
+ *
+ * <br>
+ *
+ * (For historical reasons) this module also contains code for handling
+ * the list of fallback directories, the list of directory authorities,
+ * and the list of authority certificates.
+ *
+ * For the directory authorities, we have a list containing the public
+ * identity key, and contact points, for each authority. The
+ * authorities receive descriptors from relays, and publish consensuses,
+ * descriptors, and microdescriptors. This list is pre-configured.
+ *
+ * Fallback directories are well-known, stable, but untrusted directory
+ * caches that clients which have not yet bootstrapped can use to get
+ * their first networkstatus consensus, in order to find out where the
+ * Tor network really is. This list is pre-configured in
+ * fallback_dirs.inc. Every authority also serves as a fallback.
+ *
+ * Both fallback directories and directory authorities are are
+ * represented by a dir_server_t.
+ *
+ * Authority certificates are signed with authority identity keys; they
+ * are used to authenticate shorter-term authority signing keys. We
+ * fetch them when we find a consensus or a vote that has been signed
+ * with a signing key we don't recognize. We cache them on disk and
+ * load them on startup. Authority operators generate them with the
+ * "tor-gencert" utility.
+ *
+ * TODO: Authority certificates should be a separate module.
+ *
+ * TODO: dir_server_t stuff should be in a separate module.
**/
#define ROUTERLIST_PRIVATE
@@ -46,6 +125,9 @@
/****************************************************************************/
+/* Typed wrappers for different digestmap types; used to avoid type
+ * confusion. */
+
DECLARE_TYPED_DIGESTMAP_FNS(sdmap_, digest_sd_map_t, signed_descriptor_t)
DECLARE_TYPED_DIGESTMAP_FNS(rimap_, digest_ri_map_t, routerinfo_t)
DECLARE_TYPED_DIGESTMAP_FNS(eimap_, digest_ei_map_t, extrainfo_t)
@@ -67,7 +149,7 @@ 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,
- u64_dbl_t **bandwidths_out);
+ double **bandwidths_out);
static const routerstatus_t *router_pick_trusteddirserver_impl(
const smartlist_t *sourcelist, dirinfo_type_t auth,
int flags, int *n_busy_out);
@@ -159,6 +241,9 @@ download_status_cert_init(download_status_t *dlstatus)
dlstatus->schedule = DL_SCHED_CONSENSUS;
dlstatus->want_authority = DL_WANT_ANY_DIRSERVER;
dlstatus->increment_on = DL_SCHED_INCREMENT_FAILURE;
+ dlstatus->backoff = DL_SCHED_RANDOM_EXPONENTIAL;
+ dlstatus->last_backoff_position = 0;
+ dlstatus->last_delay_used = 0;
/* Use the new schedule to set next_attempt_at */
download_status_reset(dlstatus);
@@ -250,6 +335,112 @@ get_cert_list(const char *id_digest)
return cl;
}
+/** Return a list of authority ID digests with potentially enumerable lists
+ * of download_status_t objects; used by controller GETINFO queries.
+ */
+
+MOCK_IMPL(smartlist_t *,
+list_authority_ids_with_downloads, (void))
+{
+ smartlist_t *ids = smartlist_new();
+ digestmap_iter_t *i;
+ const char *digest;
+ char *tmp;
+ void *cl;
+
+ if (trusted_dir_certs) {
+ for (i = digestmap_iter_init(trusted_dir_certs);
+ !(digestmap_iter_done(i));
+ i = digestmap_iter_next(trusted_dir_certs, i)) {
+ /*
+ * We always have at least dl_status_by_id to query, so no need to
+ * probe deeper than the existence of a cert_list_t.
+ */
+ digestmap_iter_get(i, &digest, &cl);
+ tmp = tor_malloc(DIGEST_LEN);
+ memcpy(tmp, digest, DIGEST_LEN);
+ smartlist_add(ids, tmp);
+ }
+ }
+ /* else definitely no downlaods going since nothing even has a cert list */
+
+ return ids;
+}
+
+/** Given an authority ID digest, return a pointer to the default download
+ * status, or NULL if there is no such entry in trusted_dir_certs */
+
+MOCK_IMPL(download_status_t *,
+id_only_download_status_for_authority_id, (const char *digest))
+{
+ download_status_t *dl = NULL;
+ cert_list_t *cl;
+
+ if (trusted_dir_certs) {
+ cl = digestmap_get(trusted_dir_certs, digest);
+ if (cl) {
+ dl = &(cl->dl_status_by_id);
+ }
+ }
+
+ return dl;
+}
+
+/** Given an authority ID digest, return a smartlist of signing key digests
+ * for which download_status_t is potentially queryable, or NULL if no such
+ * authority ID digest is known. */
+
+MOCK_IMPL(smartlist_t *,
+list_sk_digests_for_authority_id, (const char *digest))
+{
+ smartlist_t *sks = NULL;
+ cert_list_t *cl;
+ dsmap_iter_t *i;
+ const char *sk_digest;
+ char *tmp;
+ download_status_t *dl;
+
+ if (trusted_dir_certs) {
+ cl = digestmap_get(trusted_dir_certs, digest);
+ if (cl) {
+ sks = smartlist_new();
+ if (cl->dl_status_map) {
+ for (i = dsmap_iter_init(cl->dl_status_map);
+ !(dsmap_iter_done(i));
+ i = dsmap_iter_next(cl->dl_status_map, i)) {
+ /* Pull the digest out and add it to the list */
+ dsmap_iter_get(i, &sk_digest, &dl);
+ tmp = tor_malloc(DIGEST_LEN);
+ memcpy(tmp, sk_digest, DIGEST_LEN);
+ smartlist_add(sks, tmp);
+ }
+ }
+ }
+ }
+
+ return sks;
+}
+
+/** Given an authority ID digest and a signing key digest, return the
+ * download_status_t or NULL if none exists. */
+
+MOCK_IMPL(download_status_t *,
+ download_status_for_authority_id_and_sk,
+ (const char *id_digest, const char *sk_digest))
+{
+ download_status_t *dl = NULL;
+ cert_list_t *cl = NULL;
+
+ if (trusted_dir_certs) {
+ cl = digestmap_get(trusted_dir_certs, id_digest);
+ if (cl && cl->dl_status_map) {
+ dl = dsmap_get(cl->dl_status_map, sk_digest);
+ }
+ }
+
+ return dl;
+}
+
/** Release all space held by a cert_list_t */
static void
cert_list_free(cert_list_t *cl)
@@ -287,7 +478,7 @@ trusted_dirs_reload_certs(void)
return 0;
r = trusted_dirs_load_certs_from_string(
contents,
- TRUSTED_DIRS_CERTS_SRC_FROM_STORE, 1);
+ TRUSTED_DIRS_CERTS_SRC_FROM_STORE, 1, NULL);
tor_free(contents);
return r;
}
@@ -317,16 +508,20 @@ already_have_cert(authority_cert_t *cert)
* or TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST. If <b>flush</b> is true, we
* need to flush any changed certificates to disk now. Return 0 on success,
* -1 if any certs fail to parse.
+ *
+ * If source_dir is non-NULL, it's the identity digest for a directory that
+ * we've just successfully retrieved certificates from, so try it first to
+ * fetch any missing certificates.
*/
-
int
trusted_dirs_load_certs_from_string(const char *contents, int source,
- int flush)
+ int flush, const char *source_dir)
{
dir_server_t *ds;
const char *s, *eos;
int failure_code = 0;
int from_store = (source == TRUSTED_DIRS_CERTS_SRC_FROM_STORE);
+ int added_trusted_cert = 0;
for (s = contents; *s; s = eos) {
authority_cert_t *cert = authority_cert_parse_from_string(s, &eos);
@@ -386,6 +581,7 @@ trusted_dirs_load_certs_from_string(const char *contents, int source,
}
if (ds) {
+ added_trusted_cert = 1;
log_info(LD_DIR, "Adding %s certificate for directory authority %s with "
"signing key %s", from_store ? "cached" : "downloaded",
ds->nickname, hex_str(cert->signing_key_digest,DIGEST_LEN));
@@ -430,8 +626,15 @@ trusted_dirs_load_certs_from_string(const char *contents, int source,
trusted_dirs_flush_certs_to_disk();
/* call this even if failure_code is <0, since some certs might have
- * succeeded. */
- networkstatus_note_certs_arrived();
+ * succeeded, but only pass source_dir if there were no failures,
+ * and at least one more authority certificate was added to the store.
+ * This avoids retrying a directory that's serving bad or entirely duplicate
+ * certificates. */
+ if (failure_code == 0 && added_trusted_cert) {
+ networkstatus_note_certs_arrived(source_dir);
+ } else {
+ networkstatus_note_certs_arrived(NULL);
+ }
return failure_code;
}
@@ -679,7 +882,9 @@ static const char *BAD_SIGNING_KEYS[] = {
NULL,
};
-/* DOCDOC */
+/** Return true iff <b>cert</b> authenticates some atuhority signing key
+ * which, because of the old openssl heartbleed vulnerability, should
+ * never be trusted. */
int
authority_cert_is_blacklisted(const authority_cert_t *cert)
{
@@ -713,14 +918,81 @@ authority_cert_dl_looks_uncertain(const char *id_digest)
return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER;
}
+/* Fetch the authority certificates specified in resource.
+ * If we are a bridge client, and node is a configured bridge, fetch from node
+ * using dir_hint as the fingerprint. Otherwise, if rs is not NULL, fetch from
+ * rs. Otherwise, fetch from a random directory mirror. */
+static void
+authority_certs_fetch_resource_impl(const char *resource,
+ const char *dir_hint,
+ const node_t *node,
+ const routerstatus_t *rs)
+{
+ const or_options_t *options = get_options();
+ int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0);
+
+ /* Make sure bridge clients never connect to anything but a bridge */
+ if (options->UseBridges) {
+ if (node && !node_is_a_configured_bridge(node)) {
+ /* If we're using bridges, and node is not a bridge, use a 3-hop path. */
+ get_via_tor = 1;
+ } else if (!node) {
+ /* If we're using bridges, and there's no node, use a 3-hop path. */
+ get_via_tor = 1;
+ }
+ }
+
+ const dir_indirection_t indirection = get_via_tor ? DIRIND_ANONYMOUS
+ : DIRIND_ONEHOP;
+
+ /* If we've just downloaded a consensus from a bridge, re-use that
+ * bridge */
+ if (options->UseBridges && node && !get_via_tor) {
+ /* clients always make OR connections to bridges */
+ tor_addr_port_t or_ap;
+ /* we are willing to use a non-preferred address if we need to */
+ fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0,
+ &or_ap);
+ directory_initiate_command(&or_ap.addr, or_ap.port,
+ NULL, 0, /*no dirport*/
+ dir_hint,
+ DIR_PURPOSE_FETCH_CERTIFICATE,
+ 0,
+ indirection,
+ resource, NULL, 0, 0);
+ return;
+ }
+
+ if (rs) {
+ /* If we've just downloaded a consensus from a directory, re-use that
+ * directory */
+ directory_initiate_command_routerstatus(rs,
+ DIR_PURPOSE_FETCH_CERTIFICATE,
+ 0, indirection, resource, NULL,
+ 0, 0);
+ return;
+ }
+
+ /* Otherwise, we want certs from a random fallback or directory
+ * mirror, because they will almost always succeed. */
+ directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0,
+ resource, PDS_RETRY_IF_NO_SERVERS,
+ DL_WANT_ANY_DIRSERVER);
+}
+
/** Try to download any v3 authority certificates that we may be missing. If
* <b>status</b> is provided, try to get all the ones that were used to sign
* <b>status</b>. Additionally, try to have a non-expired certificate for
* every V3 authority in trusted_dir_servers. Don't fetch certificates we
* already have.
+ *
+ * If dir_hint is non-NULL, it's the identity digest for a directory that
+ * we've just successfully retrieved a consensus or certificates from, so try
+ * it first to fetch any missing certificates.
**/
void
-authority_certs_fetch_missing(networkstatus_t *status, time_t now)
+authority_certs_fetch_missing(networkstatus_t *status, time_t now,
+ const char *dir_hint)
{
/*
* The pending_id digestmap tracks pending certificate downloads by
@@ -729,7 +1001,6 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
*/
digestmap_t *pending_id;
fp_pair_map_t *pending_cert;
- authority_cert_t *cert;
/*
* The missing_id_digests smartlist will hold a list of id digests
* we want to fetch the newest cert for; the missing_cert_digests
@@ -739,12 +1010,13 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
smartlist_t *missing_cert_digests, *missing_id_digests;
char *resource = NULL;
cert_list_t *cl;
- const int cache = directory_caches_unknown_auth_certs(get_options());
+ const or_options_t *options = get_options();
+ const int cache = directory_caches_unknown_auth_certs(options);
fp_pair_t *fp_tmp = NULL;
char id_digest_str[2*DIGEST_LEN+1];
char sk_digest_str[2*DIGEST_LEN+1];
- if (should_delay_dir_fetches(get_options(), NULL))
+ if (should_delay_dir_fetches(options, NULL))
return;
pending_cert = fp_pair_map_new();
@@ -785,7 +1057,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
} SMARTLIST_FOREACH_END(cert);
if (!found &&
download_status_is_ready(&(cl->dl_status_by_id), now,
- get_options()->TestingCertMaxDownloadTries) &&
+ options->TestingCertMaxDownloadTries) &&
!digestmap_get(pending_id, ds->v3_identity_digest)) {
log_info(LD_DIR,
"No current certificate known for authority %s "
@@ -838,8 +1110,9 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
}
SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
- cert = authority_cert_get_by_digests(voter->identity_digest,
- sig->signing_key_digest);
+ authority_cert_t *cert =
+ authority_cert_get_by_digests(voter->identity_digest,
+ sig->signing_key_digest);
if (cert) {
if (now < cert->expires)
download_status_reset_by_sk_in_cl(cl, sig->signing_key_digest);
@@ -847,7 +1120,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
}
if (download_status_is_ready_by_sk_in_cl(
cl, sig->signing_key_digest,
- now, get_options()->TestingCertMaxDownloadTries) &&
+ now, options->TestingCertMaxDownloadTries) &&
!fp_pair_map_get_by_digests(pending_cert,
voter->identity_digest,
sig->signing_key_digest)) {
@@ -884,6 +1157,46 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
} SMARTLIST_FOREACH_END(voter);
}
+ /* Bridge clients look up the node for the dir_hint */
+ const node_t *node = NULL;
+ /* All clients, including bridge clients, look up the routerstatus for the
+ * dir_hint */
+ const routerstatus_t *rs = NULL;
+
+ /* If we still need certificates, try the directory that just successfully
+ * served us a consensus or certificates.
+ * As soon as the directory fails to provide additional certificates, we try
+ * another, randomly selected directory. This avoids continual retries.
+ * (We only ever have one outstanding request per certificate.)
+ */
+ if (dir_hint) {
+ if (options->UseBridges) {
+ /* Bridge clients try the nodelist. If the dir_hint is from an authority,
+ * or something else fetched over tor, we won't find the node here, but
+ * we will find the rs. */
+ node = node_get_by_id(dir_hint);
+ }
+
+ /* All clients try the consensus routerstatus, then the fallback
+ * routerstatus */
+ rs = router_get_consensus_status_by_id(dir_hint);
+ if (!rs) {
+ /* This will also find authorities */
+ const dir_server_t *ds = router_get_fallback_dirserver_by_digest(
+ dir_hint);
+ if (ds) {
+ rs = &ds->fake_status;
+ }
+ }
+
+ if (!node && !rs) {
+ log_warn(LD_BUG, "Directory %s delivered a consensus, but %s"
+ "no routerstatus could be found for it.",
+ options->UseBridges ? "no node and " : "",
+ hex_str(dir_hint, DIGEST_LEN));
+ }
+ }
+
/* Do downloads by identity digest */
if (smartlist_len(missing_id_digests) > 0) {
int need_plus = 0;
@@ -913,11 +1226,9 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
if (smartlist_len(fps) > 1) {
resource = smartlist_join_strings(fps, "", 0, NULL);
- /* We want certs from mirrors, because they will almost always succeed.
- */
- directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0,
- resource, PDS_RETRY_IF_NO_SERVERS,
- DL_WANT_ANY_DIRSERVER);
+ /* node and rs are directories that just gave us a consensus or
+ * certificates */
+ authority_certs_fetch_resource_impl(resource, dir_hint, node, rs);
tor_free(resource);
}
/* else we didn't add any: they were all pending */
@@ -960,11 +1271,9 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
if (smartlist_len(fp_pairs) > 1) {
resource = smartlist_join_strings(fp_pairs, "", 0, NULL);
- /* We want certs from mirrors, because they will almost always succeed.
- */
- directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0,
- resource, PDS_RETRY_IF_NO_SERVERS,
- DL_WANT_ANY_DIRSERVER);
+ /* node and rs are directories that just gave us a consensus or
+ * certificates */
+ authority_certs_fetch_resource_impl(resource, dir_hint, node, rs);
tor_free(resource);
}
/* else they were all pending */
@@ -1420,8 +1729,8 @@ router_digest_is_fallback_dir(const char *digest)
* v3 identity key hashes to <b>digest</b>, or NULL if no such authority
* is known.
*/
-dir_server_t *
-trusteddirserver_get_by_v3_auth_digest(const char *digest)
+MOCK_IMPL(dir_server_t *,
+trusteddirserver_get_by_v3_auth_digest, (const char *digest))
{
if (!trusted_dir_servers)
return NULL;
@@ -1645,7 +1954,7 @@ router_picked_poor_directory_log(const routerstatus_t *rs)
/* When iterating through the routerlist, can OR address/port preference
* and reachability checks be skipped?
*/
-static int
+int
router_skip_or_reachability(const or_options_t *options, int try_ip_pref)
{
/* Servers always have and prefer IPv4.
@@ -1815,20 +2124,23 @@ dirserver_choose_by_weight(const smartlist_t *servers, double authority_weight)
{
int n = smartlist_len(servers);
int i;
- u64_dbl_t *weights;
+ double *weights_dbl;
+ uint64_t *weights_u64;
const dir_server_t *ds;
- weights = tor_calloc(n, sizeof(u64_dbl_t));
+ weights_dbl = tor_calloc(n, sizeof(double));
+ weights_u64 = tor_calloc(n, sizeof(uint64_t));
for (i = 0; i < n; ++i) {
ds = smartlist_get(servers, i);
- weights[i].dbl = ds->weight;
+ weights_dbl[i] = ds->weight;
if (ds->is_authority)
- weights[i].dbl *= authority_weight;
+ weights_dbl[i] *= authority_weight;
}
- scale_array_elements_to_u64(weights, n, NULL);
- i = choose_array_element_by_weight(weights, n);
- tor_free(weights);
+ scale_array_elements_to_u64(weights_u64, weights_dbl, n, NULL);
+ i = choose_array_element_by_weight(weights_u64, n);
+ tor_free(weights_dbl);
+ tor_free(weights_u64);
return (i < 0) ? NULL : smartlist_get(servers, i);
}
@@ -2032,10 +2344,17 @@ router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid,
continue;
if (node_is_unreliable(node, need_uptime, need_capacity, need_guard))
continue;
- /* Choose a node with an OR address that matches the firewall rules,
- * if we are making a direct connection */
+ /* Don't choose nodes if we are certain they can't do EXTEND2 cells */
+ if (node->rs && !routerstatus_version_supports_extend2_cells(node->rs, 1))
+ continue;
+ /* Don't choose nodes if we are certain they can't do ntor. */
+ if ((node->ri || node->md) && !node_has_curve25519_onion_key(node))
+ continue;
+ /* Choose a node with an OR address that matches the firewall rules */
if (direct_conn && check_reach &&
- !fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, pref_addr))
+ !fascist_firewall_allows_node(node,
+ FIREWALL_OR_CONNECTION,
+ pref_addr))
continue;
smartlist_add(sl, (void *)node);
@@ -2090,59 +2409,43 @@ router_get_advertised_bandwidth_capped(const routerinfo_t *router)
* much of the range of uint64_t. If <b>total_out</b> is provided, set it to
* the sum of all elements in the array _before_ scaling. */
STATIC void
-scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries,
+scale_array_elements_to_u64(uint64_t *entries_out, const double *entries_in,
+ int n_entries,
uint64_t *total_out)
{
double total = 0.0;
double scale_factor = 0.0;
int i;
- /* big, but far away from overflowing an int64_t */
-#define SCALE_TO_U64_MAX ((int64_t) (INT64_MAX / 4))
for (i = 0; i < n_entries; ++i)
- total += entries[i].dbl;
+ total += entries_in[i];
- if (total > 0.0)
- scale_factor = SCALE_TO_U64_MAX / total;
+ if (total > 0.0) {
+ scale_factor = ((double)INT64_MAX) / total;
+ scale_factor /= 4.0; /* make sure we're very far away from overflowing */
+ }
for (i = 0; i < n_entries; ++i)
- entries[i].u64 = tor_llround(entries[i].dbl * scale_factor);
+ entries_out[i] = tor_llround(entries_in[i] * scale_factor);
if (total_out)
*total_out = (uint64_t) total;
-
-#undef SCALE_TO_U64_MAX
}
-/** Time-invariant 64-bit greater-than; works on two integers in the range
- * (0,INT64_MAX). */
-#if SIZEOF_VOID_P == 8
-#define gt_i64_timei(a,b) ((a) > (b))
-#else
-static inline int
-gt_i64_timei(uint64_t a, uint64_t b)
-{
- int64_t diff = (int64_t) (b - a);
- int res = diff >> 63;
- return res & 1;
-}
-#endif
-
/** Pick a random element of <b>n_entries</b>-element array <b>entries</b>,
* choosing each element with a probability proportional to its (uint64_t)
* value, and return the index of that element. If all elements are 0, choose
* an index at random. Return -1 on error.
*/
STATIC int
-choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries)
+choose_array_element_by_weight(const uint64_t *entries, int n_entries)
{
- int i, i_chosen=-1, n_chosen=0;
- uint64_t total_so_far = 0;
+ int i;
uint64_t rand_val;
uint64_t total = 0;
for (i = 0; i < n_entries; ++i)
- total += entries[i].u64;
+ total += entries[i];
if (n_entries < 1)
return -1;
@@ -2154,22 +2457,8 @@ choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries)
rand_val = crypto_rand_uint64(total);
- for (i = 0; i < n_entries; ++i) {
- total_so_far += entries[i].u64;
- if (gt_i64_timei(total_so_far, rand_val)) {
- i_chosen = i;
- n_chosen++;
- /* Set rand_val to INT64_MAX rather than stopping the loop. This way,
- * the time we spend in the loop does not leak which element we chose. */
- rand_val = INT64_MAX;
- }
- }
- tor_assert(total_so_far == total);
- tor_assert(n_chosen == 1);
- tor_assert(i_chosen >= 0);
- tor_assert(i_chosen < n_entries);
-
- return i_chosen;
+ return select_array_member_cumulative_timei(
+ entries, n_entries, total, rand_val);
}
/** When weighting bridges, enforce these values as lower and upper
@@ -2221,17 +2510,21 @@ static const node_t *
smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl,
bandwidth_weight_rule_t rule)
{
- u64_dbl_t *bandwidths=NULL;
+ double *bandwidths_dbl=NULL;
+ uint64_t *bandwidths_u64=NULL;
- if (compute_weighted_bandwidths(sl, rule, &bandwidths) < 0)
+ if (compute_weighted_bandwidths(sl, rule, &bandwidths_dbl) < 0)
return NULL;
- scale_array_elements_to_u64(bandwidths, smartlist_len(sl), NULL);
+ bandwidths_u64 = tor_calloc(smartlist_len(sl), sizeof(uint64_t));
+ scale_array_elements_to_u64(bandwidths_u64, bandwidths_dbl,
+ smartlist_len(sl), NULL);
{
- int idx = choose_array_element_by_weight(bandwidths,
+ int idx = choose_array_element_by_weight(bandwidths_u64,
smartlist_len(sl));
- tor_free(bandwidths);
+ tor_free(bandwidths_dbl);
+ tor_free(bandwidths_u64);
return idx < 0 ? NULL : smartlist_get(sl, idx);
}
}
@@ -2244,14 +2537,14 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl,
static int
compute_weighted_bandwidths(const smartlist_t *sl,
bandwidth_weight_rule_t rule,
- u64_dbl_t **bandwidths_out)
+ double **bandwidths_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;
- u64_dbl_t *bandwidths;
+ double *bandwidths;
/* Can't choose exit and guard at same time */
tor_assert(rule == NO_WEIGHTING ||
@@ -2333,7 +2626,7 @@ compute_weighted_bandwidths(const smartlist_t *sl,
Web /= weight_scale;
Wdb /= weight_scale;
- bandwidths = tor_calloc(smartlist_len(sl), sizeof(u64_dbl_t));
+ bandwidths = tor_calloc(smartlist_len(sl), sizeof(double));
// Cycle through smartlist and total the bandwidth.
static int warned_missing_bw = 0;
@@ -2420,7 +2713,7 @@ compute_weighted_bandwidths(const smartlist_t *sl,
final_weight = weight*this_bw;
}
- bandwidths[node_sl_idx].dbl = final_weight + 0.5;
+ bandwidths[node_sl_idx] = final_weight + 0.5;
} SMARTLIST_FOREACH_END(node);
log_debug(LD_CIRC, "Generated weighted bandwidths for rule %s based "
@@ -2441,7 +2734,7 @@ double
frac_nodes_with_descriptors(const smartlist_t *sl,
bandwidth_weight_rule_t rule)
{
- u64_dbl_t *bandwidths = NULL;
+ double *bandwidths = NULL;
double total, present;
if (smartlist_len(sl) == 0)
@@ -2458,7 +2751,7 @@ frac_nodes_with_descriptors(const smartlist_t *sl,
total = present = 0.0;
SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) {
- const double bw = bandwidths[node_sl_idx].dbl;
+ const double bw = bandwidths[node_sl_idx];
total += bw;
if (node_has_descriptor(node))
present += bw;
@@ -2637,7 +2930,8 @@ hex_digest_nickname_decode(const char *hexdigest,
return -1;
}
- if (base16_decode(digest_out, DIGEST_LEN, hexdigest, HEX_DIGEST_LEN)<0)
+ if (base16_decode(digest_out, DIGEST_LEN,
+ hexdigest, HEX_DIGEST_LEN) != DIGEST_LEN)
return -1;
return 0;
}
@@ -2722,7 +3016,7 @@ hexdigest_to_digest(const char *hexdigest, char *digest)
if (hexdigest[0]=='$')
++hexdigest;
if (strlen(hexdigest) < HEX_DIGEST_LEN ||
- base16_decode(digest,DIGEST_LEN,hexdigest,HEX_DIGEST_LEN) < 0)
+ base16_decode(digest,DIGEST_LEN,hexdigest,HEX_DIGEST_LEN) != DIGEST_LEN)
return -1;
return 0;
}
@@ -2896,6 +3190,7 @@ routerinfo_free(routerinfo_t *router)
tor_free(router->cache_info.signed_descriptor_body);
tor_free(router->nickname);
tor_free(router->platform);
+ tor_free(router->protocol_list);
tor_free(router->contact_info);
if (router->onion_pkey)
crypto_pk_free(router->onion_pkey);
@@ -2943,6 +3238,17 @@ signed_descriptor_free(signed_descriptor_t *sd)
tor_free(sd);
}
+/** Reset the given signed descriptor <b>sd</b> by freeing the allocated
+ * memory inside the object and by zeroing its content. */
+static void
+signed_descriptor_reset(signed_descriptor_t *sd)
+{
+ tor_assert(sd);
+ tor_free(sd->signed_descriptor_body);
+ tor_cert_free(sd->signing_key_cert);
+ memset(sd, 0, sizeof(*sd));
+}
+
/** Copy src into dest, and steal all references inside src so that when
* we free src, we don't mess up dest. */
static void
@@ -2950,6 +3256,8 @@ signed_descriptor_move(signed_descriptor_t *dest,
signed_descriptor_t *src)
{
tor_assert(dest != src);
+ /* Cleanup destination object before overwriting it.*/
+ signed_descriptor_reset(dest);
memcpy(dest, src, sizeof(signed_descriptor_t));
src->signed_descriptor_body = NULL;
src->signing_key_cert = NULL;
@@ -3707,7 +4015,7 @@ router_add_extrainfo_to_routerlist(extrainfo_t *ei, const char **msg,
was_router_added_t inserted;
(void)from_fetch;
if (msg) *msg = NULL;
- /*XXXX023 Do something with msg */
+ /*XXXX Do something with msg */
inserted = extrainfo_insert(router_get_routerlist(), ei, !from_cache);
@@ -4267,6 +4575,10 @@ void
routerlist_retry_directory_downloads(time_t now)
{
(void)now;
+
+ log_debug(LD_GENERAL,
+ "In routerlist_retry_directory_downloads()");
+
router_reset_status_download_failures();
router_reset_descriptor_download_failures();
reschedule_directory_downloads();
@@ -4310,7 +4622,7 @@ dir_server_new(int is_authority,
return NULL;
if (!hostname)
- hostname_ = tor_dup_addr(addr);
+ hostname_ = tor_addr_to_str_dup(addr);
else
hostname_ = tor_strdup(hostname);
@@ -4726,6 +5038,11 @@ launch_descriptor_downloads(int purpose,
log_debug(LD_DIR,
"There are enough downloadable %ss to launch requests.",
descname);
+ } else if (! router_have_minimum_dir_info()) {
+ log_debug(LD_DIR,
+ "We are only missing %d %ss, but we'll fetch anyway, since "
+ "we don't yet have enough directory info.",
+ n_downloadable, descname);
} else {
/* should delay */
@@ -4920,7 +5237,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
/** How often should we launch a server/authority request to be sure of getting
* a guess for our IP? */
-/*XXXX024 this info should come from netinfo cells or something, or we should
+/*XXXX+ this info should come from netinfo cells or something, or we should
* do this only when we aren't seeing incoming data. see bug 652. */
#define DUMMY_DOWNLOAD_INTERVAL (20*60)
@@ -4931,7 +5248,7 @@ launch_dummy_descriptor_download_as_needed(time_t now,
const or_options_t *options)
{
static time_t last_dummy_download = 0;
- /* XXXX024 we could be smarter here; see notes on bug 652. */
+ /* XXXX+ we could be smarter here; see notes on bug 652. */
/* If we're a server that doesn't have a configured address, we rely on
* directory fetches to learn when our address changes. So if we haven't
* tried to get any routerdescs in a long time, try a dummy fetch now. */
@@ -5079,6 +5396,9 @@ update_extrainfo_downloads(time_t now)
void
router_reset_descriptor_download_failures(void)
{
+ log_debug(LD_GENERAL,
+ "In router_reset_descriptor_download_failures()");
+
networkstatus_reset_download_failures();
last_descriptor_download_attempted = 0;
if (!routerlist)
@@ -5133,7 +5453,7 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2)
(r1->contact_info && r2->contact_info &&
strcasecmp(r1->contact_info, r2->contact_info)) ||
r1->is_hibernating != r2->is_hibernating ||
- cmp_addr_policies(r1->exit_policy, r2->exit_policy) ||
+ ! addr_policies_eq(r1->exit_policy, r2->exit_policy) ||
(r1->supports_tunnelled_dir_requests !=
r2->supports_tunnelled_dir_requests))
return 0;
@@ -5290,6 +5610,47 @@ routerinfo_incompatible_with_extrainfo(const crypto_pk_t *identity_pkey,
return r;
}
+/* Does ri have a valid ntor onion key?
+ * Valid ntor onion keys exist and have at least one non-zero byte. */
+int
+routerinfo_has_curve25519_onion_key(const routerinfo_t *ri)
+{
+ if (!ri) {
+ return 0;
+ }
+
+ if (!ri->onion_curve25519_pkey) {
+ return 0;
+ }
+
+ if (tor_mem_is_zero((const char*)ri->onion_curve25519_pkey->public_key,
+ CURVE25519_PUBKEY_LEN)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Is rs running a tor version known to support EXTEND2 cells?
+ * If allow_unknown_versions is true, return true if we can't tell
+ * (from a versions line or a protocols line) whether it supports extend2
+ * cells.
+ * Otherwise, return false if the version is unknown. */
+int
+routerstatus_version_supports_extend2_cells(const routerstatus_t *rs,
+ int allow_unknown_versions)
+{
+ if (!rs) {
+ return allow_unknown_versions;
+ }
+
+ if (!rs->protocols_known) {
+ return allow_unknown_versions;
+ }
+
+ return rs->supports_extend2_cells;
+}
+
/** Assert that the internal representation of <b>rl</b> is
* self-consistent. */
void
diff --git a/src/or/routerlist.h b/src/or/routerlist.h
index cb5b42a3b8..606e9085ce 100644
--- a/src/or/routerlist.h
+++ b/src/or/routerlist.h
@@ -29,7 +29,7 @@ int trusted_dirs_reload_certs(void);
#define TRUSTED_DIRS_CERTS_SRC_FROM_VOTE 4
int trusted_dirs_load_certs_from_string(const char *contents, int source,
- int flush);
+ int flush, const char *source_dir);
void trusted_dirs_flush_certs_to_disk(void);
authority_cert_t *authority_cert_get_newest_by_id(const char *id_digest);
authority_cert_t *authority_cert_get_by_sk_digest(const char *sk_digest);
@@ -38,7 +38,8 @@ authority_cert_t *authority_cert_get_by_digests(const char *id_digest,
void authority_cert_get_all(smartlist_t *certs_out);
void authority_cert_dl_failed(const char *id_digest,
const char *signing_key_digest, int status);
-void authority_certs_fetch_missing(networkstatus_t *status, time_t now);
+void authority_certs_fetch_missing(networkstatus_t *status, time_t now,
+ const char *dir_hint);
int router_reload_router_list(void);
int authority_cert_dl_looks_uncertain(const char *id_digest);
const smartlist_t *router_get_trusted_dir_servers(void);
@@ -51,11 +52,13 @@ dir_server_t *router_get_trusteddirserver_by_digest(const char *d);
dir_server_t *router_get_fallback_dirserver_by_digest(
const char *digest);
int router_digest_is_fallback_dir(const char *digest);
-dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d);
+MOCK_DECL(dir_server_t *, trusteddirserver_get_by_v3_auth_digest,
+ (const char *d));
const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type,
int flags);
const routerstatus_t *router_pick_fallback_dirserver(dirinfo_type_t type,
int flags);
+int router_skip_or_reachability(const or_options_t *options, int try_ip_pref);
int router_get_my_share_of_directory_requests(double *v3_share_out);
void router_reset_status_download_failures(void);
int routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2);
@@ -103,6 +106,14 @@ void routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old,
void routerlist_free_all(void);
void routerlist_reset_warnings(void);
+MOCK_DECL(smartlist_t *, list_authority_ids_with_downloads, (void));
+MOCK_DECL(download_status_t *, id_only_download_status_for_authority_id,
+ (const char *digest));
+MOCK_DECL(smartlist_t *, list_sk_digests_for_authority_id,
+ (const char *digest));
+MOCK_DECL(download_status_t *, download_status_for_authority_id_and_sk,
+ (const char *id_digest, const char *sk_digest));
+
static int WRA_WAS_ADDED(was_router_added_t s);
static int WRA_WAS_OUTDATED(was_router_added_t s);
static int WRA_WAS_REJECTED(was_router_added_t s);
@@ -195,6 +206,9 @@ int routerinfo_incompatible_with_extrainfo(const crypto_pk_t *ri,
extrainfo_t *ei,
signed_descriptor_t *sd,
const char **msg);
+int routerinfo_has_curve25519_onion_key(const routerinfo_t *ri);
+int routerstatus_version_supports_extend2_cells(const routerstatus_t *rs,
+ int allow_unknown_versions);
void routerlist_assert_ok(const routerlist_t *rl);
const char *esc_router_info(const routerinfo_t *router);
@@ -217,17 +231,11 @@ int hex_digest_nickname_matches(const char *hexdigest,
const char *nickname, int is_named);
#ifdef ROUTERLIST_PRIVATE
-/** Helper type for choosing routers by bandwidth: contains a union of
- * double and uint64_t. Before we call scale_array_elements_to_u64, it holds
- * a double; after, it holds a uint64_t. */
-typedef union u64_dbl_t {
- uint64_t u64;
- double dbl;
-} u64_dbl_t;
-
-STATIC int choose_array_element_by_weight(const u64_dbl_t *entries,
+STATIC int choose_array_element_by_weight(const uint64_t *entries,
int n_entries);
-STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries,
+STATIC void scale_array_elements_to_u64(uint64_t *entries_out,
+ const double *entries_in,
+ int n_entries,
uint64_t *total_out);
STATIC const routerstatus_t *router_pick_directory_server_impl(
dirinfo_type_t auth, int flags,
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index b6a90431a7..521e237be2 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -6,7 +6,51 @@
/**
* \file routerparse.c
- * \brief Code to parse and validate router descriptors and directories.
+ * \brief Code to parse and validate router descriptors, consenus directories,
+ * and similar objects.
+ *
+ * The objects parsed by this module use a common text-based metaformat,
+ * documented in dir-spec.txt in torspec.git. This module is itself divided
+ * into two major kinds of function: code to handle the metaformat, and code
+ * to convert from particular instances of the metaformat into the
+ * objects that Tor uses.
+ *
+ * The generic parsing code works by calling a table-based tokenizer on the
+ * input string. Each token corresponds to a single line with a token, plus
+ * optional arguments on that line, plus an optional base-64 encoded object
+ * after that line. Each token has a definition in a table of token_rule_t
+ * entries that describes how many arguments it can take, whether it takes an
+ * object, how many times it may appear, whether it must appear first, and so
+ * on.
+ *
+ * The tokenizer function tokenize_string() converts its string input into a
+ * smartlist full of instances of directory_token_t, according to a provided
+ * table of token_rule_t.
+ *
+ * The generic parts of this module additionally include functions for
+ * finding the start and end of signed information inside a signed object, and
+ * computing the digest that will be signed.
+ *
+ * There are also functions for saving objects to disk that have caused
+ * parsing to fail.
+ *
+ * The specific parts of this module describe conversions between
+ * particular lists of directory_token_t and particular objects. The
+ * kinds of objects that can be parsed here are:
+ * <ul>
+ * <li>router descriptors (managed from routerlist.c)
+ * <li>extra-info documents (managed from routerlist.c)
+ * <li>microdescriptors (managed from microdesc.c)
+ * <li>vote and consensus networkstatus documents, and the routerstatus_t
+ * objects that they comprise (managed from networkstatus.c)
+ * <li>detached-signature objects used by authorities for gathering
+ * signatures on the networkstatus consensus (managed from dirvote.c)
+ * <li>authority key certificates (managed from routerlist.c)
+ * <li>hidden service descriptors (managed from rendcommon.c and rendcache.c)
+ * </ul>
+ *
+ * For no terribly good reason, the functions to <i>generate</i> signatures on
+ * the above directory objects are also in this module.
**/
#define ROUTERPARSE_PRIVATE
@@ -17,6 +61,7 @@
#include "dirserv.h"
#include "dirvote.h"
#include "policies.h"
+#include "protover.h"
#include "rendcommon.h"
#include "router.h"
#include "routerlist.h"
@@ -28,6 +73,8 @@
#include "routerparse.h"
#include "entrynodes.h"
#include "torcert.h"
+#include "sandbox.h"
+#include "shared_random.h"
#undef log
#include <math.h>
@@ -56,6 +103,7 @@ typedef enum {
K_RUNNING_ROUTERS,
K_ROUTER_STATUS,
K_PLATFORM,
+ K_PROTO,
K_OPT,
K_BANDWIDTH,
K_CONTACT,
@@ -72,6 +120,10 @@ typedef enum {
K_DIR_OPTIONS,
K_CLIENT_VERSIONS,
K_SERVER_VERSIONS,
+ K_RECOMMENDED_CLIENT_PROTOCOLS,
+ K_RECOMMENDED_RELAY_PROTOCOLS,
+ K_REQUIRED_CLIENT_PROTOCOLS,
+ K_REQUIRED_RELAY_PROTOCOLS,
K_OR_ADDRESS,
K_ID,
K_P,
@@ -145,6 +197,11 @@ typedef enum {
K_CONSENSUS_METHOD,
K_LEGACY_DIR_KEY,
K_DIRECTORY_FOOTER,
+ K_SIGNING_CERT_ED,
+ K_SR_FLAG,
+ K_COMMIT,
+ K_PREVIOUS_SRV,
+ K_CURRENT_SRV,
K_PACKAGE,
A_PURPOSE,
@@ -245,12 +302,14 @@ typedef struct token_rule_t {
int is_annotation;
} token_rule_t;
-/*
+/**
+ * @name macros for defining token rules
+ *
* Helper macros to define token tables. 's' is a string, 't' is a
* directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an
* object syntax.
- *
*/
+/**@{*/
/** Appears to indicate the end of a table. */
#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 }
@@ -271,16 +330,17 @@ typedef struct token_rule_t {
/** An annotation that must appear no more than once */
#define A01(s,t,a,o) { s, t, a, o, 0, 1, 0, 1 }
-/* Argument multiplicity: any number of arguments. */
+/** Argument multiplicity: any number of arguments. */
#define ARGS 0,INT_MAX,0
-/* Argument multiplicity: no arguments. */
+/** Argument multiplicity: no arguments. */
#define NO_ARGS 0,0,0
-/* Argument multiplicity: concatenate all arguments. */
+/** Argument multiplicity: concatenate all arguments. */
#define CONCAT_ARGS 1,1,1
-/* Argument multiplicity: at least <b>n</b> arguments. */
+/** Argument multiplicity: at least <b>n</b> arguments. */
#define GE(n) n,INT_MAX,0
-/* Argument multiplicity: exactly <b>n</b> arguments. */
+/** Argument multiplicity: exactly <b>n</b> arguments. */
#define EQ(n) n,n,0
+/**@}*/
/** List of tokens recognized in router descriptors */
static token_rule_t routerdesc_token_table[] = {
@@ -299,6 +359,7 @@ static token_rule_t routerdesc_token_table[] = {
T01("fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ),
T01("hibernating", K_HIBERNATING, GE(1), NO_OBJ ),
T01("platform", K_PLATFORM, CONCAT_ARGS, NO_OBJ ),
+ T01("proto", K_PROTO, CONCAT_ARGS, NO_OBJ ),
T01("contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ),
T01("read-history", K_READ_HISTORY, ARGS, NO_OBJ ),
T01("write-history", K_WRITE_HISTORY, ARGS, NO_OBJ ),
@@ -375,6 +436,7 @@ static token_rule_t rtrstatus_token_table[] = {
T01("w", K_W, ARGS, NO_OBJ ),
T0N("m", K_M, CONCAT_ARGS, NO_OBJ ),
T0N("id", K_ID, GE(2), NO_OBJ ),
+ T01("pr", K_PROTO, CONCAT_ARGS, NO_OBJ ),
T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ),
END_OF_TABLE
};
@@ -446,7 +508,20 @@ static token_rule_t networkstatus_token_table[] = {
T1("known-flags", K_KNOWN_FLAGS, ARGS, NO_OBJ ),
T01("params", K_PARAMS, ARGS, NO_OBJ ),
T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ),
+ T01("signing-ed25519", K_SIGNING_CERT_ED, NO_ARGS , NEED_OBJ ),
+ T01("shared-rand-participate",K_SR_FLAG, NO_ARGS, NO_OBJ ),
+ T0N("shared-rand-commit", K_COMMIT, GE(3), NO_OBJ ),
+ T01("shared-rand-previous-value", K_PREVIOUS_SRV,EQ(2), NO_OBJ ),
+ T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ),
T0N("package", K_PACKAGE, CONCAT_ARGS, NO_OBJ ),
+ T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("required-client-protocols", K_REQUIRED_CLIENT_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("required-relay-protocols", K_REQUIRED_RELAY_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
CERTIFICATE_MEMBERS
@@ -485,6 +560,18 @@ static token_rule_t networkstatus_consensus_token_table[] = {
T01("consensus-method", K_CONSENSUS_METHOD, EQ(1), NO_OBJ),
T01("params", K_PARAMS, ARGS, NO_OBJ ),
+ T01("shared-rand-previous-value", K_PREVIOUS_SRV, EQ(2), NO_OBJ ),
+ T01("shared-rand-current-value", K_CURRENT_SRV, EQ(2), NO_OBJ ),
+
+ T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("required-client-protocols", K_REQUIRED_CLIENT_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+ T01("required-relay-protocols", K_REQUIRED_RELAY_PROTOCOLS,
+ CONCAT_ARGS, NO_OBJ ),
+
END_OF_TABLE
};
@@ -585,32 +672,579 @@ static int check_signature_token(const char *digest,
#define DUMP_AREA(a,name) STMT_NIL
#endif
-/** Last time we dumped a descriptor to disk. */
-static time_t last_desc_dumped = 0;
+/* Dump mechanism for unparseable descriptors */
+
+/** List of dumped descriptors for FIFO cleanup purposes */
+STATIC smartlist_t *descs_dumped = NULL;
+/** Total size of dumped descriptors for FIFO cleanup */
+STATIC uint64_t len_descs_dumped = 0;
+/** Directory to stash dumps in */
+static int have_dump_desc_dir = 0;
+static int problem_with_dump_desc_dir = 0;
+
+#define DESC_DUMP_DATADIR_SUBDIR "unparseable-descs"
+#define DESC_DUMP_BASE_FILENAME "unparseable-desc"
+
+/** Find the dump directory and check if we'll be able to create it */
+static void
+dump_desc_init(void)
+{
+ char *dump_desc_dir;
+
+ dump_desc_dir = get_datadir_fname(DESC_DUMP_DATADIR_SUBDIR);
+
+ /*
+ * We just check for it, don't create it at this point; we'll
+ * create it when we need it if it isn't already there.
+ */
+ if (check_private_dir(dump_desc_dir, CPD_CHECK, get_options()->User) < 0) {
+ /* Error, log and flag it as having a problem */
+ log_notice(LD_DIR,
+ "Doesn't look like we'll be able to create descriptor dump "
+ "directory %s; dumps will be disabled.",
+ dump_desc_dir);
+ problem_with_dump_desc_dir = 1;
+ tor_free(dump_desc_dir);
+ return;
+ }
+
+ /* Check if it exists */
+ switch (file_status(dump_desc_dir)) {
+ case FN_DIR:
+ /* We already have a directory */
+ have_dump_desc_dir = 1;
+ break;
+ case FN_NOENT:
+ /* Nothing, we'll need to create it later */
+ have_dump_desc_dir = 0;
+ break;
+ case FN_ERROR:
+ /* Log and flag having a problem */
+ log_notice(LD_DIR,
+ "Couldn't check whether descriptor dump directory %s already"
+ " exists: %s",
+ dump_desc_dir, strerror(errno));
+ problem_with_dump_desc_dir = 1;
+ break;
+ case FN_FILE:
+ case FN_EMPTY:
+ default:
+ /* Something else was here! */
+ log_notice(LD_DIR,
+ "Descriptor dump directory %s already exists and isn't a "
+ "directory",
+ dump_desc_dir);
+ problem_with_dump_desc_dir = 1;
+ }
+
+ if (have_dump_desc_dir && !problem_with_dump_desc_dir) {
+ dump_desc_populate_fifo_from_directory(dump_desc_dir);
+ }
+
+ tor_free(dump_desc_dir);
+}
+
+/** Create the dump directory if needed and possible */
+static void
+dump_desc_create_dir(void)
+{
+ char *dump_desc_dir;
+
+ /* If the problem flag is set, skip it */
+ if (problem_with_dump_desc_dir) return;
+
+ /* Do we need it? */
+ if (!have_dump_desc_dir) {
+ dump_desc_dir = get_datadir_fname(DESC_DUMP_DATADIR_SUBDIR);
+
+ if (check_private_dir(dump_desc_dir, CPD_CREATE,
+ get_options()->User) < 0) {
+ log_notice(LD_DIR,
+ "Failed to create descriptor dump directory %s",
+ dump_desc_dir);
+ problem_with_dump_desc_dir = 1;
+ }
+
+ /* Okay, we created it */
+ have_dump_desc_dir = 1;
+
+ tor_free(dump_desc_dir);
+ }
+}
+
+/** Dump desc FIFO/cleanup; take ownership of the given filename, add it to
+ * the FIFO, and clean up the oldest entries to the extent they exceed the
+ * configured cap. If any old entries with a matching hash existed, they
+ * just got overwritten right before this was called and we should adjust
+ * the total size counter without deleting them.
+ */
+static void
+dump_desc_fifo_add_and_clean(char *filename, const uint8_t *digest_sha256,
+ size_t len)
+{
+ dumped_desc_t *ent = NULL, *tmp;
+ uint64_t max_len;
+
+ tor_assert(filename != NULL);
+ tor_assert(digest_sha256 != NULL);
+
+ if (descs_dumped == NULL) {
+ /* We better have no length, then */
+ tor_assert(len_descs_dumped == 0);
+ /* Make a smartlist */
+ descs_dumped = smartlist_new();
+ }
+
+ /* Make a new entry to put this one in */
+ ent = tor_malloc_zero(sizeof(*ent));
+ ent->filename = filename;
+ ent->len = len;
+ ent->when = time(NULL);
+ memcpy(ent->digest_sha256, digest_sha256, DIGEST256_LEN);
+
+ /* Do we need to do some cleanup? */
+ max_len = get_options()->MaxUnparseableDescSizeToLog;
+ /* Iterate over the list until we've freed enough space */
+ while (len > max_len - len_descs_dumped &&
+ smartlist_len(descs_dumped) > 0) {
+ /* Get the oldest thing on the list */
+ tmp = (dumped_desc_t *)(smartlist_get(descs_dumped, 0));
+
+ /*
+ * Check if it matches the filename we just added, so we don't delete
+ * something we just emitted if we get repeated identical descriptors.
+ */
+ if (strcmp(tmp->filename, filename) != 0) {
+ /* Delete it and adjust the length counter */
+ tor_unlink(tmp->filename);
+ tor_assert(len_descs_dumped >= tmp->len);
+ len_descs_dumped -= tmp->len;
+ log_info(LD_DIR,
+ "Deleting old unparseable descriptor dump %s due to "
+ "space limits",
+ tmp->filename);
+ } else {
+ /*
+ * Don't delete, but do adjust the counter since we will bump it
+ * later
+ */
+ tor_assert(len_descs_dumped >= tmp->len);
+ len_descs_dumped -= tmp->len;
+ log_info(LD_DIR,
+ "Replacing old descriptor dump %s with new identical one",
+ tmp->filename);
+ }
+
+ /* Free it and remove it from the list */
+ smartlist_del_keeporder(descs_dumped, 0);
+ tor_free(tmp->filename);
+ tor_free(tmp);
+ }
+
+ /* Append our entry to the end of the list and bump the counter */
+ smartlist_add(descs_dumped, ent);
+ len_descs_dumped += len;
+}
+
+/** Check if we already have a descriptor for this hash and move it to the
+ * head of the queue if so. Return 1 if one existed and 0 otherwise.
+ */
+static int
+dump_desc_fifo_bump_hash(const uint8_t *digest_sha256)
+{
+ dumped_desc_t *match = NULL;
+
+ tor_assert(digest_sha256);
+
+ if (descs_dumped) {
+ /* Find a match if one exists */
+ SMARTLIST_FOREACH_BEGIN(descs_dumped, dumped_desc_t *, ent) {
+ if (ent &&
+ tor_memeq(ent->digest_sha256, digest_sha256, DIGEST256_LEN)) {
+ /*
+ * Save a pointer to the match and remove it from its current
+ * position.
+ */
+ match = ent;
+ SMARTLIST_DEL_CURRENT_KEEPORDER(descs_dumped, ent);
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ent);
+
+ if (match) {
+ /* Update the timestamp */
+ match->when = time(NULL);
+ /* Add it back at the end of the list */
+ smartlist_add(descs_dumped, match);
+
+ /* Indicate we found one */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/** Clean up on exit; just memory, leave the dumps behind
+ */
+STATIC void
+dump_desc_fifo_cleanup(void)
+{
+ if (descs_dumped) {
+ /* Free each descriptor */
+ SMARTLIST_FOREACH_BEGIN(descs_dumped, dumped_desc_t *, ent) {
+ tor_assert(ent);
+ tor_free(ent->filename);
+ tor_free(ent);
+ } SMARTLIST_FOREACH_END(ent);
+ /* Free the list */
+ smartlist_free(descs_dumped);
+ descs_dumped = NULL;
+ len_descs_dumped = 0;
+ }
+}
+
+/** Handle one file for dump_desc_populate_fifo_from_directory(); make sure
+ * the filename is sensibly formed and matches the file content, and either
+ * return a dumped_desc_t for it or remove the file and return NULL.
+ */
+MOCK_IMPL(STATIC dumped_desc_t *,
+dump_desc_populate_one_file, (const char *dirname, const char *f))
+{
+ dumped_desc_t *ent = NULL;
+ char *path = NULL, *desc = NULL;
+ const char *digest_str;
+ char digest[DIGEST256_LEN], content_digest[DIGEST256_LEN];
+ /* Expected prefix before digest in filenames */
+ const char *f_pfx = DESC_DUMP_BASE_FILENAME ".";
+ /*
+ * Stat while reading; this is important in case the file
+ * contains a NUL character.
+ */
+ struct stat st;
+
+ /* Sanity-check args */
+ tor_assert(dirname != NULL);
+ tor_assert(f != NULL);
+
+ /* Form the full path */
+ tor_asprintf(&path, "%s" PATH_SEPARATOR "%s", dirname, f);
+
+ /* Check that f has the form DESC_DUMP_BASE_FILENAME.<digest256> */
+
+ if (!strcmpstart(f, f_pfx)) {
+ /* It matches the form, but is the digest parseable as such? */
+ digest_str = f + strlen(f_pfx);
+ if (base16_decode(digest, DIGEST256_LEN,
+ digest_str, strlen(digest_str)) != DIGEST256_LEN) {
+ /* We failed to decode it */
+ digest_str = NULL;
+ }
+ } else {
+ /* No match */
+ digest_str = NULL;
+ }
+
+ if (!digest_str) {
+ /* We couldn't get a sensible digest */
+ log_notice(LD_DIR,
+ "Removing unrecognized filename %s from unparseable "
+ "descriptors directory", f);
+ tor_unlink(path);
+ /* We're done */
+ goto done;
+ }
+
+ /*
+ * The filename has the form DESC_DUMP_BASE_FILENAME "." <digest256> and
+ * we've decoded the digest. Next, check that we can read it and the
+ * content matches this digest. We are relying on the fact that if the
+ * file contains a '\0', read_file_to_str() will allocate space for and
+ * read the entire file and return the correct size in st.
+ */
+ desc = read_file_to_str(path, RFTS_IGNORE_MISSING|RFTS_BIN, &st);
+ if (!desc) {
+ /* We couldn't read it */
+ log_notice(LD_DIR,
+ "Failed to read %s from unparseable descriptors directory; "
+ "attempting to remove it.", f);
+ tor_unlink(path);
+ /* We're done */
+ goto done;
+ }
+
+#if SIZE_MAX > UINT64_MAX
+ if (BUG((uint64_t)st.st_size > (uint64_t)SIZE_MAX)) {
+ /* LCOV_EXCL_START
+ * Should be impossible since RFTS above should have failed to read the
+ * huge file into RAM. */
+ goto done;
+ /* LCOV_EXCL_STOP */
+ }
+#endif
+ if (BUG(st.st_size < 0)) {
+ /* LCOV_EXCL_START
+ * Should be impossible, since the OS isn't supposed to be b0rken. */
+ goto done;
+ /* LCOV_EXCL_STOP */
+ }
+ /* (Now we can be sure that st.st_size is safe to cast to a size_t.) */
+
+ /*
+ * We got one; now compute its digest and check that it matches the
+ * filename.
+ */
+ if (crypto_digest256((char *)content_digest, desc, (size_t) st.st_size,
+ DIGEST_SHA256) != 0) {
+ /* Weird, but okay */
+ log_info(LD_DIR,
+ "Unable to hash content of %s from unparseable descriptors "
+ "directory", f);
+ tor_unlink(path);
+ /* We're done */
+ goto done;
+ }
+
+ /* Compare the digests */
+ if (tor_memneq(digest, content_digest, DIGEST256_LEN)) {
+ /* No match */
+ log_info(LD_DIR,
+ "Hash of %s from unparseable descriptors directory didn't "
+ "match its filename; removing it", f);
+ tor_unlink(path);
+ /* We're done */
+ goto done;
+ }
+
+ /* Okay, it's a match, we should prepare ent */
+ ent = tor_malloc_zero(sizeof(dumped_desc_t));
+ ent->filename = path;
+ memcpy(ent->digest_sha256, digest, DIGEST256_LEN);
+ ent->len = (size_t) st.st_size;
+ ent->when = st.st_mtime;
+ /* Null out path so we don't free it out from under ent */
+ path = NULL;
+
+ done:
+ /* Free allocations if we had them */
+ tor_free(desc);
+ tor_free(path);
+
+ return ent;
+}
+
+/** Sort helper for dump_desc_populate_fifo_from_directory(); compares
+ * the when field of dumped_desc_ts in a smartlist to put the FIFO in
+ * the correct order after reconstructing it from the directory.
+ */
+static int
+dump_desc_compare_fifo_entries(const void **a_v, const void **b_v)
+{
+ const dumped_desc_t **a = (const dumped_desc_t **)a_v;
+ const dumped_desc_t **b = (const dumped_desc_t **)b_v;
+
+ if ((a != NULL) && (*a != NULL)) {
+ if ((b != NULL) && (*b != NULL)) {
+ /* We have sensible dumped_desc_ts to compare */
+ if ((*a)->when < (*b)->when) {
+ return -1;
+ } else if ((*a)->when == (*b)->when) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ /*
+ * We shouldn't see this, but what the hell, NULLs precede everythin
+ * else
+ */
+ return 1;
+ }
+ } else {
+ return -1;
+ }
+}
+
+/** Scan the contents of the directory, and update FIFO/counters; this will
+ * consistency-check descriptor dump filenames against hashes of descriptor
+ * dump file content, and remove any inconsistent/unreadable dumps, and then
+ * reconstruct the dump FIFO as closely as possible for the last time the
+ * tor process shut down. If a previous dump was repeated more than once and
+ * moved ahead in the FIFO, the mtime will not have been updated and the
+ * reconstructed order will be wrong, but will always be a permutation of
+ * the original.
+ */
+STATIC void
+dump_desc_populate_fifo_from_directory(const char *dirname)
+{
+ smartlist_t *files = NULL;
+ dumped_desc_t *ent = NULL;
+
+ tor_assert(dirname != NULL);
+
+ /* Get a list of files */
+ files = tor_listdir(dirname);
+ if (!files) {
+ log_notice(LD_DIR,
+ "Unable to get contents of unparseable descriptor dump "
+ "directory %s",
+ dirname);
+ return;
+ }
+
+ /*
+ * Iterate through the list and decide which files should go in the
+ * FIFO and which should be purged.
+ */
+
+ SMARTLIST_FOREACH_BEGIN(files, char *, f) {
+ /* Try to get a FIFO entry */
+ ent = dump_desc_populate_one_file(dirname, f);
+ if (ent) {
+ /*
+ * We got one; add it to the FIFO. No need for duplicate checking
+ * here since we just verified the name and digest match.
+ */
+
+ /* Make sure we have a list to add it to */
+ if (!descs_dumped) {
+ descs_dumped = smartlist_new();
+ len_descs_dumped = 0;
+ }
+
+ /* Add it and adjust the counter */
+ smartlist_add(descs_dumped, ent);
+ len_descs_dumped += ent->len;
+ }
+ /*
+ * If we didn't, we will have unlinked the file if necessary and
+ * possible, and emitted a log message about it, so just go on to
+ * the next.
+ */
+ } SMARTLIST_FOREACH_END(f);
+
+ /* Did we get anything? */
+ if (descs_dumped != NULL) {
+ /* Sort the FIFO in order of increasing timestamp */
+ smartlist_sort(descs_dumped, dump_desc_compare_fifo_entries);
+
+ /* Log some stats */
+ log_info(LD_DIR,
+ "Reloaded unparseable descriptor dump FIFO with %d dump(s) "
+ "totaling " U64_FORMAT " bytes",
+ smartlist_len(descs_dumped), U64_PRINTF_ARG(len_descs_dumped));
+ }
+
+ /* Free the original list */
+ SMARTLIST_FOREACH(files, char *, f, tor_free(f));
+ smartlist_free(files);
+}
/** For debugging purposes, dump unparseable descriptor *<b>desc</b> of
* type *<b>type</b> to file $DATADIR/unparseable-desc. Do not write more
* than one descriptor to disk per minute. If there is already such a
* file in the data directory, overwrite it. */
-static void
+STATIC void
dump_desc(const char *desc, const char *type)
{
- time_t now = time(NULL);
tor_assert(desc);
tor_assert(type);
- if (!last_desc_dumped || last_desc_dumped + 60 < now) {
- char *debugfile = get_datadir_fname("unparseable-desc");
- size_t filelen = 50 + strlen(type) + strlen(desc);
- char *content = tor_malloc_zero(filelen);
- tor_snprintf(content, filelen, "Unable to parse descriptor of type "
- "%s:\n%s", type, desc);
- write_str_to_file(debugfile, content, 1);
- log_info(LD_DIR, "Unable to parse descriptor of type %s. See file "
- "unparseable-desc in data directory for details.", type);
- tor_free(content);
- tor_free(debugfile);
- last_desc_dumped = now;
+ size_t len;
+ /* The SHA256 of the string */
+ uint8_t digest_sha256[DIGEST256_LEN];
+ char digest_sha256_hex[HEX_DIGEST256_LEN+1];
+ /* Filename to log it to */
+ char *debugfile, *debugfile_base;
+
+ /* Get the hash for logging purposes anyway */
+ len = strlen(desc);
+ if (crypto_digest256((char *)digest_sha256, desc, len,
+ DIGEST_SHA256) != 0) {
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s, and unable to even hash"
+ " it!", type);
+ goto err;
+ }
+
+ base16_encode(digest_sha256_hex, sizeof(digest_sha256_hex),
+ (const char *)digest_sha256, sizeof(digest_sha256));
+
+ /*
+ * We mention type and hash in the main log; don't clutter up the files
+ * with anything but the exact dump.
+ */
+ tor_asprintf(&debugfile_base,
+ DESC_DUMP_BASE_FILENAME ".%s", digest_sha256_hex);
+ debugfile = get_datadir_fname2(DESC_DUMP_DATADIR_SUBDIR, debugfile_base);
+
+ /*
+ * Check if the sandbox is active or will become active; see comment
+ * below at the log message for why.
+ */
+ if (!(sandbox_is_active() || get_options()->Sandbox)) {
+ if (len <= get_options()->MaxUnparseableDescSizeToLog) {
+ if (!dump_desc_fifo_bump_hash(digest_sha256)) {
+ /* Create the directory if needed */
+ dump_desc_create_dir();
+ /* Make sure we've got it */
+ if (have_dump_desc_dir && !problem_with_dump_desc_dir) {
+ /* Write it, and tell the main log about it */
+ write_str_to_file(debugfile, desc, 1);
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s with hash %s and "
+ "length %lu. See file %s in data directory for details.",
+ type, digest_sha256_hex, (unsigned long)len,
+ debugfile_base);
+ dump_desc_fifo_add_and_clean(debugfile, digest_sha256, len);
+ /* Since we handed ownership over, don't free debugfile later */
+ debugfile = NULL;
+ } else {
+ /* Problem with the subdirectory */
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s with hash %s and "
+ "length %lu. Descriptor not dumped because we had a "
+ "problem creating the " DESC_DUMP_DATADIR_SUBDIR
+ " subdirectory",
+ type, digest_sha256_hex, (unsigned long)len);
+ /* We do have to free debugfile in this case */
+ }
+ } else {
+ /* We already had one with this hash dumped */
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s with hash %s and "
+ "length %lu. Descriptor not dumped because one with that "
+ "hash has already been dumped.",
+ type, digest_sha256_hex, (unsigned long)len);
+ /* We do have to free debugfile in this case */
+ }
+ } else {
+ /* Just log that it happened without dumping */
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s with hash %s and "
+ "length %lu. Descriptor not dumped because it exceeds maximum"
+ " log size all by itself.",
+ type, digest_sha256_hex, (unsigned long)len);
+ /* We do have to free debugfile in this case */
+ }
+ } else {
+ /*
+ * Not logging because the sandbox is active and seccomp2 apparently
+ * doesn't have a sensible way to allow filenames according to a pattern
+ * match. (If we ever figure out how to say "allow writes to /regex/",
+ * remove this checK).
+ */
+ log_info(LD_DIR,
+ "Unable to parse descriptor of type %s with hash %s and "
+ "length %lu. Descriptor not dumped because the sandbox is "
+ "configured",
+ type, digest_sha256_hex, (unsigned long)len);
}
+
+ tor_free(debugfile_base);
+ tor_free(debugfile);
+
+ err:
+ return;
}
/** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
@@ -1217,11 +1851,11 @@ router_parse_entry_from_string(const char *s, const char *end,
if (cache_copy) {
size_t len = router->cache_info.signed_descriptor_len +
router->cache_info.annotations_len;
- char *cp =
+ char *signed_body =
router->cache_info.signed_descriptor_body = tor_malloc(len+1);
if (prepend_annotations) {
- memcpy(cp, prepend_annotations, prepend_len);
- cp += prepend_len;
+ memcpy(signed_body, prepend_annotations, prepend_len);
+ signed_body += prepend_len;
}
/* This assertion will always succeed.
* len == signed_desc_len + annotations_len
@@ -1229,9 +1863,9 @@ router_parse_entry_from_string(const char *s, const char *end,
* == end-start_of_annotations + prepend_len
* We already wrote prepend_len bytes into the buffer; now we're
* writing end-start_of_annotations -NM. */
- tor_assert(cp+(end-start_of_annotations) ==
+ tor_assert(signed_body+(end-start_of_annotations) ==
router->cache_info.signed_descriptor_body+len);
- memcpy(cp, start_of_annotations, end-start_of_annotations);
+ memcpy(signed_body, start_of_annotations, end-start_of_annotations);
router->cache_info.signed_descriptor_body[len] = '\0';
tor_assert(strlen(router->cache_info.signed_descriptor_body) == len);
}
@@ -1513,7 +2147,8 @@ router_parse_entry_from_string(const char *s, const char *end,
char d[DIGEST_LEN];
tor_assert(tok->n_args == 1);
tor_strstrip(tok->args[0], " ");
- if (base16_decode(d, DIGEST_LEN, tok->args[0], strlen(tok->args[0]))) {
+ if (base16_decode(d, DIGEST_LEN,
+ tok->args[0], strlen(tok->args[0])) != DIGEST_LEN) {
log_warn(LD_DIR, "Couldn't decode router fingerprint %s",
escaped(tok->args[0]));
goto err;
@@ -1529,6 +2164,10 @@ router_parse_entry_from_string(const char *s, const char *end,
router->platform = tor_strdup(tok->args[0]);
}
+ if ((tok = find_opt_by_keyword(tokens, K_PROTO))) {
+ router->protocol_list = tor_strdup(tok->args[0]);
+ }
+
if ((tok = find_opt_by_keyword(tokens, K_CONTACT))) {
router->contact_info = tor_strdup(tok->args[0]);
}
@@ -1567,7 +2206,7 @@ router_parse_entry_from_string(const char *s, const char *end,
}
}
- if (policy_is_reject_star(router->exit_policy, AF_INET) &&
+ if (policy_is_reject_star(router->exit_policy, AF_INET, 1) &&
(!router->ipv6_exit_policy ||
short_policy_is_reject_star(router->ipv6_exit_policy)))
router->policy_is_reject_star = 1;
@@ -1594,8 +2233,10 @@ router_parse_entry_from_string(const char *s, const char *end,
if ((tok = find_opt_by_keyword(tokens, K_EXTRA_INFO_DIGEST))) {
tor_assert(tok->n_args >= 1);
if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
- base16_decode(router->cache_info.extra_info_digest,
- DIGEST_LEN, tok->args[0], HEX_DIGEST_LEN);
+ if (base16_decode(router->cache_info.extra_info_digest, DIGEST_LEN,
+ tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN) {
+ log_warn(LD_DIR,"Invalid extra info digest");
+ }
} else {
log_warn(LD_DIR, "Invalid extra info digest %s", escaped(tok->args[0]));
}
@@ -1738,7 +2379,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
strlcpy(extrainfo->nickname, tok->args[0], sizeof(extrainfo->nickname));
if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
base16_decode(extrainfo->cache_info.identity_digest, DIGEST_LEN,
- tok->args[1], HEX_DIGEST_LEN)) {
+ tok->args[1], HEX_DIGEST_LEN) != DIGEST_LEN) {
log_warn(LD_DIR,"Invalid fingerprint %s on \"extra-info\"",
escaped(tok->args[1]));
goto err;
@@ -1960,7 +2601,7 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string)
tok = find_by_keyword(tokens, K_FINGERPRINT);
tor_assert(tok->n_args);
if (base16_decode(fp_declared, DIGEST_LEN, tok->args[0],
- strlen(tok->args[0]))) {
+ strlen(tok->args[0])) != DIGEST_LEN) {
log_warn(LD_DIR, "Couldn't decode key certificate fingerprint %s",
escaped(tok->args[0]));
goto err;
@@ -1981,7 +2622,7 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string)
struct in_addr in;
char *address = NULL;
tor_assert(tok->n_args);
- /* XXX024 use some tor_addr parse function below instead. -RD */
+ /* XXX++ use some tor_addr parse function below instead. -RD */
if (tor_addr_port_split(LOG_WARN, tok->args[0], &address,
&cert->dir_port) < 0 ||
tor_inet_aton(address, &in) == 0) {
@@ -2166,7 +2807,7 @@ routerstatus_parse_guardfraction(const char *guardfraction_str,
*
* Parse according to the syntax used by the consensus flavor <b>flav</b>.
**/
-static routerstatus_t *
+STATIC routerstatus_t *
routerstatus_parse_entry_from_string(memarea_t *area,
const char **s, smartlist_t *tokens,
networkstatus_t *vote,
@@ -2280,6 +2921,7 @@ routerstatus_parse_entry_from_string(memarea_t *area,
}
}
} else if (tok) {
+ /* This is a consensus, not a vote. */
int i;
for (i=0; i < tok->n_args; ++i) {
if (!strcmp(tok->args[i], "Exit"))
@@ -2310,14 +2952,28 @@ routerstatus_parse_entry_from_string(memarea_t *area,
rs->is_v2_dir = 1;
}
}
+ /* These are implied true by having been included in a consensus made
+ * with a given method */
+ rs->is_flagged_running = 1; /* Starting with consensus method 4. */
+ if (consensus_method >= MIN_METHOD_FOR_EXCLUDING_INVALID_NODES)
+ rs->is_valid = 1;
+ }
+ int found_protocol_list = 0;
+ if ((tok = find_opt_by_keyword(tokens, K_PROTO))) {
+ found_protocol_list = 1;
+ rs->protocols_known = 1;
+ rs->supports_extend2_cells =
+ protocol_list_supports_protocol(tok->args[0], PRT_RELAY, 2);
}
if ((tok = find_opt_by_keyword(tokens, K_V))) {
tor_assert(tok->n_args == 1);
- rs->version_known = 1;
- if (strcmpstart(tok->args[0], "Tor ")) {
- } else {
- rs->version_supports_extend2_cells =
+ if (!strcmpstart(tok->args[0], "Tor ") && !found_protocol_list) {
+ /* We only do version checks like this in the case where
+ * the version is a "Tor" version, and where there is no
+ * list of protocol versions that we should be looking at instead. */
+ rs->supports_extend2_cells =
tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha");
+ rs->protocols_known = 1;
}
if (vote_rs) {
vote_rs->version = tor_strdup(tok->args[0]);
@@ -2400,6 +3056,10 @@ routerstatus_parse_entry_from_string(memarea_t *area,
}
}
}
+ if (t->tp == K_PROTO) {
+ tor_assert(t->n_args == 1);
+ vote_rs->protocols = tor_strdup(t->args[0]);
+ }
} SMARTLIST_FOREACH_END(t);
} else if (flav == FLAV_MICRODESC) {
tok = find_opt_by_keyword(tokens, K_M);
@@ -2840,6 +3500,134 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
return valid;
}
+/** Parse and extract all SR commits from <b>tokens</b> and place them in
+ * <b>ns</b>. */
+static void
+extract_shared_random_commits(networkstatus_t *ns, smartlist_t *tokens)
+{
+ smartlist_t *chunks = NULL;
+
+ tor_assert(ns);
+ tor_assert(tokens);
+ /* Commits are only present in a vote. */
+ tor_assert(ns->type == NS_TYPE_VOTE);
+
+ ns->sr_info.commits = smartlist_new();
+
+ smartlist_t *commits = find_all_by_keyword(tokens, K_COMMIT);
+ /* It's normal that a vote might contain no commits even if it participates
+ * in the SR protocol. Don't treat it as an error. */
+ if (commits == NULL) {
+ goto end;
+ }
+
+ /* Parse the commit. We do NO validation of number of arguments or ordering
+ * for forward compatibility, it's the parse commit job to inform us if it's
+ * supported or not. */
+ chunks = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(commits, directory_token_t *, tok) {
+ /* Extract all arguments and put them in the chunks list. */
+ for (int i = 0; i < tok->n_args; i++) {
+ smartlist_add(chunks, tok->args[i]);
+ }
+ sr_commit_t *commit = sr_parse_commit(chunks);
+ smartlist_clear(chunks);
+ if (commit == NULL) {
+ /* Get voter identity so we can warn that this dirauth vote contains
+ * commit we can't parse. */
+ networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0);
+ tor_assert(voter);
+ log_warn(LD_DIR, "SR: Unable to parse commit %s from vote of voter %s.",
+ escaped(tok->object_body),
+ hex_str(voter->identity_digest,
+ sizeof(voter->identity_digest)));
+ /* Commitment couldn't be parsed. Continue onto the next commit because
+ * this one could be unsupported for instance. */
+ continue;
+ }
+ /* Add newly created commit object to the vote. */
+ smartlist_add(ns->sr_info.commits, commit);
+ } SMARTLIST_FOREACH_END(tok);
+
+ end:
+ smartlist_free(chunks);
+ smartlist_free(commits);
+}
+
+/** Check if a shared random value of type <b>srv_type</b> is in
+ * <b>tokens</b>. If there is, parse it and set it to <b>srv_out</b>. Return
+ * -1 on failure, 0 on success. The resulting srv is allocated on the heap and
+ * it's the responsibility of the caller to free it. */
+static int
+extract_one_srv(smartlist_t *tokens, directory_keyword srv_type,
+ sr_srv_t **srv_out)
+{
+ int ret = -1;
+ directory_token_t *tok;
+ sr_srv_t *srv = NULL;
+ smartlist_t *chunks;
+
+ tor_assert(tokens);
+
+ chunks = smartlist_new();
+ tok = find_opt_by_keyword(tokens, srv_type);
+ if (!tok) {
+ /* That's fine, no SRV is allowed. */
+ ret = 0;
+ goto end;
+ }
+ for (int i = 0; i < tok->n_args; i++) {
+ smartlist_add(chunks, tok->args[i]);
+ }
+ srv = sr_parse_srv(chunks);
+ if (srv == NULL) {
+ log_warn(LD_DIR, "SR: Unparseable SRV %s", escaped(tok->object_body));
+ goto end;
+ }
+ /* All is good. */
+ *srv_out = srv;
+ ret = 0;
+ end:
+ smartlist_free(chunks);
+ return ret;
+}
+
+/** Extract any shared random values found in <b>tokens</b> and place them in
+ * the networkstatus <b>ns</b>. */
+static void
+extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens)
+{
+ const char *voter_identity;
+ networkstatus_voter_info_t *voter;
+
+ tor_assert(ns);
+ tor_assert(tokens);
+ /* Can be only one of them else code flow. */
+ tor_assert(ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS);
+
+ if (ns->type == NS_TYPE_VOTE) {
+ voter = smartlist_get(ns->voters, 0);
+ tor_assert(voter);
+ voter_identity = hex_str(voter->identity_digest,
+ sizeof(voter->identity_digest));
+ } else {
+ /* Consensus has multiple voters so no specific voter. */
+ 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). */
+ 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);
+ /* Maybe we have a chance with the current SRV so let's try it anyway. */
+ }
+ if (extract_one_srv(tokens, K_CURRENT_SRV, &ns->sr_info.current_srv) < 0) {
+ log_warn(LD_DIR, "SR: Unable to parse current SRV from %s",
+ voter_identity);
+ }
+}
+
/** Parse a v3 networkstatus vote, opinion, or consensus (depending on
* ns_type), from <b>s</b>, and return the result. Return NULL on failure. */
networkstatus_t *
@@ -2853,7 +3641,6 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
common_digests_t ns_digests;
const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
directory_token_t *tok;
- int ok;
struct in_addr in;
int i, inorder, n_signatures = 0;
memarea_t *area = NULL, *rs_area = NULL;
@@ -2943,15 +3730,25 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
} else {
tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD);
if (tok) {
+ int num_ok;
ns->consensus_method = (int)tor_parse_long(tok->args[0], 10, 1, INT_MAX,
- &ok, NULL);
- if (!ok)
+ &num_ok, NULL);
+ if (!num_ok)
goto err;
} else {
ns->consensus_method = 1;
}
}
+ if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_CLIENT_PROTOCOLS)))
+ ns->recommended_client_protocols = tor_strdup(tok->args[0]);
+ if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_RELAY_PROTOCOLS)))
+ ns->recommended_relay_protocols = tor_strdup(tok->args[0]);
+ if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_CLIENT_PROTOCOLS)))
+ ns->required_client_protocols = tor_strdup(tok->args[0]);
+ if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_RELAY_PROTOCOLS)))
+ ns->required_relay_protocols = tor_strdup(tok->args[0]);
+
tok = find_by_keyword(tokens, K_VALID_AFTER);
if (parse_iso_time(tok->args[0], &ns->valid_after))
goto err;
@@ -2966,14 +3763,17 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
tok = find_by_keyword(tokens, K_VOTING_DELAY);
tor_assert(tok->n_args >= 2);
- ns->vote_seconds =
- (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL);
- if (!ok)
- goto err;
- ns->dist_seconds =
- (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL);
- if (!ok)
- goto err;
+ {
+ int ok;
+ ns->vote_seconds =
+ (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL);
+ if (!ok)
+ goto err;
+ ns->dist_seconds =
+ (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL);
+ if (!ok)
+ goto err;
+ }
if (ns->valid_after +
(get_options()->TestingTorNetwork ?
MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) > ns->fresh_until) {
@@ -3097,7 +3897,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
voter->nickname = tor_strdup(tok->args[0]);
if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
base16_decode(voter->identity_digest, sizeof(voter->identity_digest),
- tok->args[1], HEX_DIGEST_LEN) < 0) {
+ tok->args[1], HEX_DIGEST_LEN)
+ != sizeof(voter->identity_digest)) {
log_warn(LD_DIR, "Error decoding identity digest %s in "
"network-status document.", escaped(tok->args[1]));
goto err;
@@ -3123,6 +3924,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
goto err;
}
voter->addr = ntohl(in.s_addr);
+ int ok;
voter->dir_port = (uint16_t)
tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL);
if (!ok)
@@ -3146,7 +3948,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
}
if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
base16_decode(voter->vote_digest, sizeof(voter->vote_digest),
- tok->args[0], HEX_DIGEST_LEN) < 0) {
+ tok->args[0], HEX_DIGEST_LEN)
+ != sizeof(voter->vote_digest)) {
log_warn(LD_DIR, "Error decoding vote digest %s in "
"network-status consensus.", escaped(tok->args[0]));
goto err;
@@ -3169,9 +3972,9 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
(tok = find_opt_by_keyword(tokens, K_LEGACY_DIR_KEY))) {
int bad = 1;
if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
- networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0);
- if (base16_decode(voter->legacy_id_digest, DIGEST_LEN,
- tok->args[0], HEX_DIGEST_LEN)<0)
+ networkstatus_voter_info_t *voter_0 = smartlist_get(ns->voters, 0);
+ if (base16_decode(voter_0->legacy_id_digest, DIGEST_LEN,
+ tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN)
bad = 1;
else
bad = 0;
@@ -3182,6 +3985,22 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
}
}
+ /* If this is a vote document, check if information about the shared
+ randomness protocol is included, and extract it. */
+ if (ns->type == NS_TYPE_VOTE) {
+ /* Does this authority participates in the SR protocol? */
+ tok = find_opt_by_keyword(tokens, K_SR_FLAG);
+ if (tok) {
+ ns->sr_info.participate = 1;
+ /* Get the SR commitments and reveals from the vote. */
+ extract_shared_random_commits(ns, tokens);
+ }
+ }
+ /* For both a vote and consensus, extract the shared random values. */
+ if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS) {
+ extract_shared_random_srvs(ns, tokens);
+ }
+
/* Parse routerstatus lines. */
rs_tokens = smartlist_new();
rs_area = memarea_new();
@@ -3203,8 +4022,11 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
NULL, NULL,
ns->consensus_method,
- flav)))
+ flav))) {
+ /* Use exponential-backoff scheduling when downloading microdescs */
+ rs->dl_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL;
smartlist_add(ns->routerstatus_list, rs);
+ }
}
}
for (i = 1; i < smartlist_len(ns->routerstatus_list); ++i) {
@@ -3330,7 +4152,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
base16_decode(declared_identity, sizeof(declared_identity),
- id_hexdigest, HEX_DIGEST_LEN) < 0) {
+ id_hexdigest, HEX_DIGEST_LEN)
+ != sizeof(declared_identity)) {
log_warn(LD_DIR, "Error decoding declared identity %s in "
"network-status document.", escaped(id_hexdigest));
goto err;
@@ -3345,7 +4168,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
sig->alg = alg;
if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
- sk_hexdigest, HEX_DIGEST_LEN) < 0) {
+ sk_hexdigest, HEX_DIGEST_LEN)
+ != sizeof(sig->signing_key_digest)) {
log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
"network-status document.", escaped(sk_hexdigest));
tor_free(sig);
@@ -3508,7 +4332,7 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
digest_algorithm_t alg;
const char *flavor;
const char *hexdigest;
- size_t expected_length;
+ size_t expected_length, digest_length;
tok = _tok;
@@ -3531,8 +4355,8 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
continue;
}
- expected_length =
- (alg == DIGEST_SHA1) ? HEX_DIGEST_LEN : HEX_DIGEST256_LEN;
+ digest_length = crypto_digest_algorithm_get_length(alg);
+ expected_length = digest_length * 2; /* hex encoding */
if (strlen(hexdigest) != expected_length) {
log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
@@ -3541,13 +4365,13 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
}
digests = detached_get_digests(sigs, flavor);
tor_assert(digests);
- if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
+ if (!tor_mem_is_zero(digests->d[alg], digest_length)) {
log_warn(LD_DIR, "Multiple digests for %s with %s on detached "
"signatures document", flavor, algname);
continue;
}
- if (base16_decode(digests->d[alg], DIGEST256_LEN,
- hexdigest, strlen(hexdigest)) < 0) {
+ if (base16_decode(digests->d[alg], digest_length,
+ hexdigest, strlen(hexdigest)) != (int) digest_length) {
log_warn(LD_DIR, "Bad encoding on consensus-digest in detached "
"networkstatus signatures");
goto err;
@@ -3620,14 +4444,14 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos)
if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
base16_decode(id_digest, sizeof(id_digest),
- id_hexdigest, HEX_DIGEST_LEN) < 0) {
+ id_hexdigest, HEX_DIGEST_LEN) != sizeof(id_digest)) {
log_warn(LD_DIR, "Error decoding declared identity %s in "
"network-status vote.", escaped(id_hexdigest));
goto err;
}
if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
base16_decode(sk_digest, sizeof(sk_digest),
- sk_hexdigest, HEX_DIGEST_LEN) < 0) {
+ sk_hexdigest, HEX_DIGEST_LEN) != sizeof(sk_digest)) {
log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
"network-status vote.", escaped(sk_hexdigest));
goto err;
@@ -4688,40 +5512,78 @@ microdescs_parse_from_string(const char *s, const char *eos,
return result;
}
-/** Parse the Tor version of the platform string <b>platform</b>,
- * and compare it to the version in <b>cutoff</b>. Return 1 if
- * the router is at least as new as the cutoff, else return 0.
+/** Extract a Tor version from a <b>platform</b> line from a router
+ * descriptor, and place the result in <b>router_version</b>.
+ *
+ * Return 1 on success, -1 on parsing failure, and 0 if the
+ * platform line does not indicate some version of Tor.
+ *
+ * If <b>strict</b> is non-zero, finding any weird version components
+ * (like negative numbers) counts as a parsing failure.
*/
int
-tor_version_as_new_as(const char *platform, const char *cutoff)
+tor_version_parse_platform(const char *platform,
+ tor_version_t *router_version,
+ int strict)
{
- tor_version_t cutoff_version, router_version;
- char *s, *s2, *start;
char tmp[128];
+ char *s, *s2, *start;
- tor_assert(platform);
-
- if (tor_version_parse(cutoff, &cutoff_version)<0) {
- log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff);
+ if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; say 0. */
return 0;
- }
- if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; be safe and say yes */
- return 1;
start = (char *)eat_whitespace(platform+3);
- if (!*start) return 0;
+ if (!*start) return -1;
s = (char *)find_whitespace(start); /* also finds '\0', which is fine */
s2 = (char*)eat_whitespace(s);
if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-"))
s = (char*)find_whitespace(s2);
if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */
- return 0;
+ return -1;
strlcpy(tmp, start, s-start+1);
- if (tor_version_parse(tmp, &router_version)<0) {
+ if (tor_version_parse(tmp, router_version)<0) {
log_info(LD_DIR,"Router version '%s' unparseable.",tmp);
- return 1; /* be safe and say yes */
+ return -1;
+ }
+
+ if (strict) {
+ if (router_version->major < 0 ||
+ router_version->minor < 0 ||
+ router_version->micro < 0 ||
+ router_version->patchlevel < 0 ||
+ router_version->svn_revision < 0) {
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+/** Parse the Tor version of the platform string <b>platform</b>,
+ * and compare it to the version in <b>cutoff</b>. Return 1 if
+ * the router is at least as new as the cutoff, else return 0.
+ */
+int
+tor_version_as_new_as(const char *platform, const char *cutoff)
+{
+ tor_version_t cutoff_version, router_version;
+ int r;
+ tor_assert(platform);
+
+ if (tor_version_parse(cutoff, &cutoff_version)<0) {
+ log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff);
+ return 0;
+ }
+
+ r = tor_version_parse_platform(platform, &router_version, 0);
+ if (r == 0) {
+ /* nonstandard Tor; be safe and say yes */
+ return 1;
+ } else if (r < 0) {
+ /* unparseable version; be safe and say yes. */
+ return 1;
}
/* Here's why we don't need to do any special handling for svn revisions:
@@ -4743,6 +5605,7 @@ tor_version_parse(const char *s, tor_version_t *out)
{
char *eos=NULL;
const char *cp=NULL;
+ int ok = 1;
/* Format is:
* "Tor " ? NUM dot NUM [ dot NUM [ ( pre | rc | dot ) NUM ] ] [ - tag ]
*/
@@ -4758,7 +5621,11 @@ tor_version_parse(const char *s, tor_version_t *out)
#define NUMBER(m) \
do { \
- out->m = (int)strtol(cp, &eos, 10); \
+ if (!cp || *cp < '0' || *cp > '9') \
+ return -1; \
+ out->m = (int)tor_parse_uint64(cp, 10, 0, INT32_MAX, &ok, &eos); \
+ if (!ok) \
+ return -1; \
if (!eos || eos == cp) \
return -1; \
cp = eos; \
@@ -4829,7 +5696,7 @@ tor_version_parse(const char *s, tor_version_t *out)
memwipe(digest, 0, sizeof(digest));
if ( hexlen == 0 || (hexlen % 2) == 1)
return -1;
- if (base16_decode(digest, hexlen/2, cp, hexlen))
+ if (base16_decode(digest, hexlen/2, cp, hexlen) != hexlen/2)
return -1;
memcpy(out->git_tag, digest, hexlen/2);
out->git_tag_len = hexlen/2;
@@ -4985,7 +5852,7 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
eos = eos + 1;
/* Check length. */
if (eos-desc > REND_DESC_MAX_SIZE) {
- /* XXX023 If we are parsing this descriptor as a server, this
+ /* XXXX+ If we are parsing this descriptor as a server, this
* should be a protocol warning. */
log_warn(LD_REND, "Descriptor length is %d which exceeds "
"maximum rendezvous descriptor size of %d bytes.",
@@ -5385,6 +6252,7 @@ rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
directory_token_t *tok;
const char *current_entry = NULL;
memarea_t *area = NULL;
+ char *err_msg = NULL;
if (!ckstr || strlen(ckstr) == 0)
return -1;
tokens = smartlist_new();
@@ -5394,8 +6262,6 @@ rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
current_entry = eat_whitespace(ckstr);
while (!strcmpstart(current_entry, "client-name ")) {
rend_authorized_client_t *parsed_entry;
- size_t len;
- char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2];
/* Determine end of string. */
const char *eos = strstr(current_entry, "\nclient-name ");
if (!eos)
@@ -5424,12 +6290,10 @@ rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
tor_assert(tok == smartlist_get(tokens, 0));
tor_assert(tok->n_args == 1);
- len = strlen(tok->args[0]);
- if (len < 1 || len > 19 ||
- strspn(tok->args[0], REND_LEGAL_CLIENTNAME_CHARACTERS) != len) {
+ if (!rend_valid_client_name(tok->args[0])) {
log_warn(LD_CONFIG, "Illegal client name: %s. (Length must be "
- "between 1 and 19, and valid characters are "
- "[A-Za-z0-9+-_].)", tok->args[0]);
+ "between 1 and %d, and valid characters are "
+ "[A-Za-z0-9+-_].)", tok->args[0], REND_CLIENTNAME_MAX_LEN);
goto err;
}
/* Check if client name is duplicate. */
@@ -5451,23 +6315,13 @@ rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
/* Parse descriptor cookie. */
tok = find_by_keyword(tokens, C_DESCRIPTOR_COOKIE);
tor_assert(tok->n_args == 1);
- if (strlen(tok->args[0]) != REND_DESC_COOKIE_LEN_BASE64 + 2) {
- log_warn(LD_REND, "Descriptor cookie has illegal length: %s",
- escaped(tok->args[0]));
- goto err;
- }
- /* The size of descriptor_cookie_tmp needs to be REND_DESC_COOKIE_LEN+2,
- * because a base64 encoding of length 24 does not fit into 16 bytes in all
- * cases. */
- if (base64_decode(descriptor_cookie_tmp, sizeof(descriptor_cookie_tmp),
- tok->args[0], strlen(tok->args[0]))
- != REND_DESC_COOKIE_LEN) {
- log_warn(LD_REND, "Descriptor cookie contains illegal characters: "
- "%s", escaped(tok->args[0]));
+ if (rend_auth_decode_cookie(tok->args[0], parsed_entry->descriptor_cookie,
+ NULL, &err_msg) < 0) {
+ tor_assert(err_msg);
+ log_warn(LD_REND, "%s", err_msg);
+ tor_free(err_msg);
goto err;
}
- memcpy(parsed_entry->descriptor_cookie, descriptor_cookie_tmp,
- REND_DESC_COOKIE_LEN);
}
result = strmap_size(parsed_clients);
goto done;
@@ -5482,3 +6336,27 @@ rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
return result;
}
+/** Called on startup; right now we just handle scanning the unparseable
+ * descriptor dumps, but hang anything else we might need to do in the
+ * future here as well.
+ */
+void
+routerparse_init(void)
+{
+ /*
+ * Check both if the sandbox is active and whether it's configured; no
+ * point in loading all that if we won't be able to use it after the
+ * sandbox becomes active.
+ */
+ if (!(sandbox_is_active() || get_options()->Sandbox)) {
+ dump_desc_init();
+ }
+}
+
+/** Clean up all data structures used by routerparse.c at exit */
+void
+routerparse_free_all(void)
+{
+ dump_desc_fifo_cleanup();
+}
+
diff --git a/src/or/routerparse.h b/src/or/routerparse.h
index c46eb1c0ae..01a5de88e8 100644
--- a/src/or/routerparse.h
+++ b/src/or/routerparse.h
@@ -45,6 +45,9 @@ MOCK_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string,
(const char *s, int assume_action, int *malformed_list));
version_status_t tor_version_is_obsolete(const char *myversion,
const char *versionlist);
+int tor_version_parse_platform(const char *platform,
+ tor_version_t *version_out,
+ int strict);
int tor_version_as_new_as(const char *platform, const char *cutoff);
int tor_version_parse(const char *s, tor_version_t *out);
int tor_version_compare(tor_version_t *a, tor_version_t *b);
@@ -85,11 +88,41 @@ int rend_parse_introduction_points(rend_service_descriptor_t *parsed,
size_t intro_points_encoded_size);
int rend_parse_client_keys(strmap_t *parsed_clients, const char *str);
+void routerparse_init(void);
+void routerparse_free_all(void);
+
#ifdef ROUTERPARSE_PRIVATE
+/*
+ * One entry in the list of dumped descriptors; filename dumped to, length,
+ * SHA-256 and timestamp.
+ */
+
+typedef struct {
+ char *filename;
+ size_t len;
+ uint8_t digest_sha256[DIGEST256_LEN];
+ time_t when;
+} dumped_desc_t;
+
+EXTERN(uint64_t, len_descs_dumped)
+EXTERN(smartlist_t *, descs_dumped)
STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
networkstatus_t *vote,
vote_routerstatus_t *vote_rs,
routerstatus_t *rs);
+MOCK_DECL(STATIC dumped_desc_t *, dump_desc_populate_one_file,
+ (const char *dirname, const char *f));
+STATIC void dump_desc_populate_fifo_from_directory(const char *dirname);
+STATIC void dump_desc(const char *desc, const char *type);
+STATIC void dump_desc_fifo_cleanup(void);
+struct memarea_t;
+STATIC routerstatus_t *routerstatus_parse_entry_from_string(
+ struct memarea_t *area,
+ const char **s, smartlist_t *tokens,
+ networkstatus_t *vote,
+ vote_routerstatus_t *vote_rs,
+ int consensus_method,
+ consensus_flavor_t flav);
#endif
#define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1"
diff --git a/src/or/routerset.c b/src/or/routerset.c
index f260914f4b..58b66ea777 100644
--- a/src/or/routerset.c
+++ b/src/or/routerset.c
@@ -9,6 +9,20 @@
*
* \brief Functions and structures to handle set-type selection of routers
* by name, ID, address, etc.
+ *
+ * This module implements the routerset_t data structure, whose purpose
+ * is to specify a set of relays based on a list of their identities or
+ * properties. Routersets can restrict relays by IP address mask,
+ * identity fingerprint, country codes, and nicknames (deprecated).
+ *
+ * Routersets are typically used for user-specified restrictions, and
+ * are created by invoking routerset_new and routerset_parse from
+ * config.c and confparse.c. To use a routerset, invoke one of
+ * routerset_contains_...() functions , or use
+ * routerstatus_get_all_nodes() / routerstatus_subtract_nodes() to
+ * manipulate a smartlist of node_t pointers.
+ *
+ * Country-code restrictions are implemented in geoip.c.
*/
#define ROUTERSET_PRIVATE
diff --git a/src/or/scheduler.c b/src/or/scheduler.c
index 8e4810b199..49ac1b939a 100644
--- a/src/or/scheduler.c
+++ b/src/or/scheduler.c
@@ -15,11 +15,7 @@
#define SCHEDULER_PRIVATE_
#include "scheduler.h"
-#ifdef HAVE_EVENT2_EVENT_H
#include <event2/event.h>
-#else
-#include <event.h>
-#endif
/*
* Scheduler high/low watermarks
@@ -500,13 +496,13 @@ scheduler_run, (void))
/* Readd any channels we need to */
if (to_readd) {
- SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, chan) {
- chan->scheduler_state = SCHED_CHAN_PENDING;
+ 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),
- chan);
- } SMARTLIST_FOREACH_END(chan);
+ readd_chan);
+ } SMARTLIST_FOREACH_END(readd_chan);
smartlist_free(to_readd);
}
diff --git a/src/or/scheduler.h b/src/or/scheduler.h
index 94a44a0aa3..3dcfd2faca 100644
--- a/src/or/scheduler.h
+++ b/src/or/scheduler.h
@@ -44,6 +44,13 @@ MOCK_DECL(STATIC int, scheduler_compare_channels,
(const void *c1_v, const void *c2_v));
STATIC uint64_t scheduler_get_queue_heuristic(void);
STATIC void scheduler_update_queue_heuristic(time_t now);
+
+#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
#endif /* !defined(TOR_SCHEDULER_H) */
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
new file mode 100644
index 0000000000..5f6b03f1ba
--- /dev/null
+++ b/src/or/shared_random.c
@@ -0,0 +1,1363 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file shared_random.c
+ *
+ * \brief Functions and data structure needed to accomplish the shared
+ * random protocol as defined in proposal #250.
+ *
+ * \details
+ *
+ * This file implements the dirauth-only commit-and-reveal protocol specified
+ * by proposal #250. The protocol has two phases (sr_phase_t): the commitment
+ * phase and the reveal phase (see get_sr_protocol_phase()).
+ *
+ * During the protocol, directory authorities keep state in memory (using
+ * sr_state_t) and in disk (using sr_disk_state_t). The synchronization between
+ * these two data structures happens in disk_state_update() and
+ * disk_state_parse().
+ *
+ * Here is a rough protocol outline:
+ *
+ * 1) In the beginning of the commitment phase, dirauths generate a
+ * commitment/reveal value for the current protocol run (see
+ * new_protocol_run() and sr_generate_our_commit()).
+ *
+ * 2) During voting, dirauths publish their commits in their votes
+ * depending on the current phase. Dirauths also include the two
+ * latest shared random values (SRV) in their votes.
+ * (see sr_get_string_for_vote())
+ *
+ * 3) Upon receiving a commit from a vote, authorities parse it, verify
+ * it, and attempt to save any new commitment or reveal information in
+ * their state file (see extract_shared_random_commits() and
+ * sr_handle_received_commits()). They also parse SRVs from votes to
+ * decide which SRV should be included in the final consensus (see
+ * extract_shared_random_srvs()).
+ *
+ * 3) After voting is done, we count the SRVs we extracted from the votes,
+ * to find the one voted by the majority of dirauths which should be
+ * included in the final consensus (see get_majority_srv_from_votes()).
+ * If an appropriate SRV is found, it is embedded in the consensus (see
+ * sr_get_string_for_consensus()).
+ *
+ * 4) At the end of the reveal phase, dirauths compute a fresh SRV for the
+ * day using the active commits (see sr_compute_srv()). This new SRV
+ * is embedded in the votes as described above.
+ *
+ * Some more notes:
+ *
+ * - To support rebooting authorities and to avoid double voting, each dirauth
+ * saves the current state of the protocol on disk so that it can resume
+ * normally in case of reboot. The disk state (sr_disk_state_t) is managed by
+ * shared_random_state.c:state_query() and we go to extra lengths to ensure
+ * that the state is flushed on disk everytime we receive any useful
+ * information like commits or SRVs.
+ *
+ * - When we receive a commit from a vote, we examine it to see if it's useful
+ * to us and whether it's appropriate to receive it according to the current
+ * phase of the protocol (see should_keep_commit()). If the commit is useful
+ * to us, we save it in our disk state using save_commit_to_state(). When we
+ * receive the reveal information corresponding to a commitment, we verify
+ * that they indeed match using verify_commit_and_reveal().
+ *
+ * - We treat consensuses as the ground truth, so everytime we generate a new
+ * consensus we update our SR state accordingly even if our local view was
+ * different (see sr_act_post_consensus()).
+ *
+ * - After a consensus has been composed, the SR protocol state gets prepared
+ * for the next voting session using sr_state_update(). That function takes
+ * care of housekeeping and also rotates the SRVs and commits in case a new
+ * protocol run is coming up. We also call sr_state_update() on bootup (in
+ * sr_state_init()), to prepare the state for the very first voting session.
+ *
+ * Terminology:
+ *
+ * - "Commitment" is the commitment value of the commit-and-reveal protocol.
+ *
+ * - "Reveal" is the reveal value of the commit-and-reveal protocol.
+ *
+ * - "Commit" is a struct (sr_commit_t) that contains a commitment value and
+ * optionally also a corresponding reveal value.
+ *
+ * - "SRV" is the Shared Random Value that gets generated as the result of the
+ * commit-and-reveal protocol.
+ **/
+
+#define SHARED_RANDOM_PRIVATE
+
+#include "or.h"
+#include "shared_random.h"
+#include "config.h"
+#include "confparse.h"
+#include "dirvote.h"
+#include "networkstatus.h"
+#include "routerkeys.h"
+#include "router.h"
+#include "routerlist.h"
+#include "shared_random_state.h"
+#include "util.h"
+
+/* String prefix of shared random values in votes/consensuses. */
+static const char previous_srv_str[] = "shared-rand-previous-value";
+static const char current_srv_str[] = "shared-rand-current-value";
+static const char commit_ns_str[] = "shared-rand-commit";
+static const char sr_flag_ns_str[] = "shared-rand-participate";
+
+/* The value of the consensus param AuthDirNumSRVAgreements found in the
+ * vote. This is set once the consensus creation subsystem requests the
+ * SRV(s) that should be put in the consensus. We use this value to decide
+ * if we keep or not an SRV. */
+static int32_t num_srv_agreements_from_vote;
+
+/* Return a heap allocated copy of the SRV <b>orig</b>. */
+STATIC sr_srv_t *
+srv_dup(const sr_srv_t *orig)
+{
+ sr_srv_t *duplicate = NULL;
+
+ if (!orig) {
+ return NULL;
+ }
+
+ duplicate = tor_malloc_zero(sizeof(sr_srv_t));
+ duplicate->num_reveals = orig->num_reveals;
+ memcpy(duplicate->value, orig->value, sizeof(duplicate->value));
+ return duplicate;
+}
+
+/* Allocate a new commit object and initializing it with <b>rsa_identity</b>
+ * that MUST be provided. The digest algorithm is set to the default one
+ * that is supported. The rest is uninitialized. This never returns NULL. */
+static sr_commit_t *
+commit_new(const char *rsa_identity)
+{
+ sr_commit_t *commit;
+
+ tor_assert(rsa_identity);
+
+ commit = tor_malloc_zero(sizeof(*commit));
+ commit->alg = SR_DIGEST_ALG;
+ memcpy(commit->rsa_identity, rsa_identity, sizeof(commit->rsa_identity));
+ base16_encode(commit->rsa_identity_hex, sizeof(commit->rsa_identity_hex),
+ commit->rsa_identity, sizeof(commit->rsa_identity));
+ return commit;
+}
+
+/* Issue a log message describing <b>commit</b>. */
+static void
+commit_log(const sr_commit_t *commit)
+{
+ tor_assert(commit);
+
+ log_debug(LD_DIR, "SR: Commit from %s", sr_commit_get_rsa_fpr(commit));
+ log_debug(LD_DIR, "SR: Commit: [TS: %" PRIu64 "] [Encoded: %s]",
+ commit->commit_ts, commit->encoded_commit);
+ log_debug(LD_DIR, "SR: Reveal: [TS: %" PRIu64 "] [Encoded: %s]",
+ commit->reveal_ts, safe_str(commit->encoded_reveal));
+}
+
+/* Make sure that the commitment and reveal information in <b>commit</b>
+ * match. If they match return 0, return -1 otherwise. This function MUST be
+ * used everytime we receive a new reveal value. Furthermore, the commit
+ * object MUST have a reveal value and the hash of the reveal value. */
+STATIC int
+verify_commit_and_reveal(const sr_commit_t *commit)
+{
+ tor_assert(commit);
+
+ log_debug(LD_DIR, "SR: Validating commit from authority %s",
+ sr_commit_get_rsa_fpr(commit));
+
+ /* Check that the timestamps match. */
+ if (commit->commit_ts != commit->reveal_ts) {
+ log_warn(LD_BUG, "SR: Commit timestamp %" PRIu64 " doesn't match reveal "
+ "timestamp %" PRIu64, commit->commit_ts,
+ commit->reveal_ts);
+ goto invalid;
+ }
+
+ /* Verify that the hashed_reveal received in the COMMIT message, matches
+ * the reveal we just received. */
+ {
+ /* We first hash the reveal we just received. */
+ char received_hashed_reveal[sizeof(commit->hashed_reveal)];
+
+ /* Only sha3-256 is supported. */
+ if (commit->alg != SR_DIGEST_ALG) {
+ goto invalid;
+ }
+
+ /* Use the invariant length since the encoded reveal variable has an
+ * extra byte for the NUL terminated byte. */
+ if (crypto_digest256(received_hashed_reveal, commit->encoded_reveal,
+ SR_REVEAL_BASE64_LEN, commit->alg)) {
+ /* Unable to digest the reveal blob, this is unlikely. */
+ goto invalid;
+ }
+
+ /* Now compare that with the hashed_reveal we received in COMMIT. */
+ if (fast_memneq(received_hashed_reveal, commit->hashed_reveal,
+ sizeof(received_hashed_reveal))) {
+ log_warn(LD_BUG, "SR: Received reveal value from authority %s "
+ "doesn't match the commit value.",
+ sr_commit_get_rsa_fpr(commit));
+ goto invalid;
+ }
+ }
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Return true iff the commit contains an encoded reveal value. */
+STATIC int
+commit_has_reveal_value(const sr_commit_t *commit)
+{
+ return !tor_mem_is_zero(commit->encoded_reveal,
+ sizeof(commit->encoded_reveal));
+}
+
+/* Parse the encoded commit. The format is:
+ * base64-encode( TIMESTAMP || H(REVEAL) )
+ *
+ * If successfully decoded and parsed, commit is updated and 0 is returned.
+ * On error, return -1. */
+STATIC int
+commit_decode(const char *encoded, sr_commit_t *commit)
+{
+ int decoded_len = 0;
+ size_t offset = 0;
+ /* XXX: Needs two extra bytes for the base64 decode calculation matches
+ * the binary length once decoded. #17868. */
+ char b64_decoded[SR_COMMIT_LEN + 2];
+
+ tor_assert(encoded);
+ tor_assert(commit);
+
+ if (strlen(encoded) > SR_COMMIT_BASE64_LEN) {
+ /* This means that if we base64 decode successfully the reveiced commit,
+ * we'll end up with a bigger decoded commit thus unusable. */
+ goto error;
+ }
+
+ /* Decode our encoded commit. Let's be careful here since _encoded_ is
+ * coming from the network in a dirauth vote so we expect nothing more
+ * than the base64 encoded length of a commit. */
+ decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded),
+ encoded, strlen(encoded));
+ if (decoded_len < 0) {
+ log_warn(LD_BUG, "SR: Commit from authority %s can't be decoded.",
+ sr_commit_get_rsa_fpr(commit));
+ goto error;
+ }
+
+ if (decoded_len != SR_COMMIT_LEN) {
+ log_warn(LD_BUG, "SR: Commit from authority %s decoded length doesn't "
+ "match the expected length (%d vs %u).",
+ sr_commit_get_rsa_fpr(commit), decoded_len,
+ (unsigned)SR_COMMIT_LEN);
+ goto error;
+ }
+
+ /* First is the timestamp (8 bytes). */
+ commit->commit_ts = tor_ntohll(get_uint64(b64_decoded));
+ offset += sizeof(uint64_t);
+ /* Next is hashed reveal. */
+ memcpy(commit->hashed_reveal, b64_decoded + offset,
+ sizeof(commit->hashed_reveal));
+ /* Copy the base64 blob to the commit. Useful for voting. */
+ strlcpy(commit->encoded_commit, encoded, sizeof(commit->encoded_commit));
+
+ return 0;
+
+ error:
+ return -1;
+}
+
+/* Parse the b64 blob at <b>encoded</b> containing reveal information and
+ * store the information in-place in <b>commit</b>. Return 0 on success else
+ * a negative value. */
+STATIC int
+reveal_decode(const char *encoded, sr_commit_t *commit)
+{
+ int decoded_len = 0;
+ /* XXX: Needs two extra bytes for the base64 decode calculation matches
+ * the binary length once decoded. #17868. */
+ char b64_decoded[SR_REVEAL_LEN + 2];
+
+ tor_assert(encoded);
+ tor_assert(commit);
+
+ if (strlen(encoded) > SR_REVEAL_BASE64_LEN) {
+ /* This means that if we base64 decode successfully the received reveal
+ * value, we'll end up with a bigger decoded value thus unusable. */
+ goto error;
+ }
+
+ /* Decode our encoded reveal. Let's be careful here since _encoded_ is
+ * coming from the network in a dirauth vote so we expect nothing more
+ * than the base64 encoded length of our reveal. */
+ decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded),
+ encoded, strlen(encoded));
+ if (decoded_len < 0) {
+ log_warn(LD_BUG, "SR: Reveal from authority %s can't be decoded.",
+ sr_commit_get_rsa_fpr(commit));
+ goto error;
+ }
+
+ if (decoded_len != SR_REVEAL_LEN) {
+ log_warn(LD_BUG, "SR: Reveal from authority %s decoded length is "
+ "doesn't match the expected length (%d vs %u)",
+ sr_commit_get_rsa_fpr(commit), decoded_len,
+ (unsigned)SR_REVEAL_LEN);
+ goto error;
+ }
+
+ commit->reveal_ts = tor_ntohll(get_uint64(b64_decoded));
+ /* Copy the last part, the random value. */
+ memcpy(commit->random_number, b64_decoded + 8,
+ sizeof(commit->random_number));
+ /* Also copy the whole message to use during verification */
+ strlcpy(commit->encoded_reveal, encoded, sizeof(commit->encoded_reveal));
+
+ return 0;
+
+ error:
+ return -1;
+}
+
+/* Encode a reveal element using a given commit object to dst which is a
+ * buffer large enough to put the base64-encoded reveal construction. The
+ * format is as follow:
+ * REVEAL = base64-encode( TIMESTAMP || H(RN) )
+ * Return base64 encoded length on success else a negative value.
+ */
+STATIC int
+reveal_encode(const sr_commit_t *commit, char *dst, size_t len)
+{
+ int ret;
+ size_t offset = 0;
+ char buf[SR_REVEAL_LEN] = {0};
+
+ tor_assert(commit);
+ tor_assert(dst);
+
+ set_uint64(buf, tor_htonll(commit->reveal_ts));
+ offset += sizeof(uint64_t);
+ memcpy(buf + offset, commit->random_number,
+ sizeof(commit->random_number));
+
+ /* Let's clean the buffer and then b64 encode it. */
+ memset(dst, 0, len);
+ ret = base64_encode(dst, len, buf, sizeof(buf), 0);
+ /* Wipe this buffer because it contains our random value. */
+ memwipe(buf, 0, sizeof(buf));
+ return ret;
+}
+
+/* Encode the given commit object to dst which is a buffer large enough to
+ * put the base64-encoded commit. The format is as follow:
+ * COMMIT = base64-encode( TIMESTAMP || H(H(RN)) )
+ * Return base64 encoded length on success else a negative value.
+ */
+STATIC int
+commit_encode(const sr_commit_t *commit, char *dst, size_t len)
+{
+ size_t offset = 0;
+ char buf[SR_COMMIT_LEN] = {0};
+
+ tor_assert(commit);
+ tor_assert(dst);
+
+ /* First is the timestamp (8 bytes). */
+ set_uint64(buf, tor_htonll(commit->commit_ts));
+ offset += sizeof(uint64_t);
+ /* and then the hashed reveal. */
+ memcpy(buf + offset, commit->hashed_reveal,
+ sizeof(commit->hashed_reveal));
+
+ /* Clean the buffer and then b64 encode it. */
+ memset(dst, 0, len);
+ return base64_encode(dst, len, buf, sizeof(buf), 0);
+}
+
+/* Cleanup both our global state and disk state. */
+static void
+sr_cleanup(void)
+{
+ sr_state_free();
+}
+
+/* Using <b>commit</b>, return a newly allocated string containing the commit
+ * information that should be used during SRV calculation. It's the caller
+ * responsibility to free the memory. Return NULL if this is not a commit to be
+ * used for SRV calculation. */
+static char *
+get_srv_element_from_commit(const sr_commit_t *commit)
+{
+ char *element;
+ tor_assert(commit);
+
+ if (!commit_has_reveal_value(commit)) {
+ return NULL;
+ }
+
+ tor_asprintf(&element, "%s%s", sr_commit_get_rsa_fpr(commit),
+ commit->encoded_reveal);
+ return element;
+}
+
+/* Return a srv object that is built with the construction:
+ * SRV = SHA3-256("shared-random" | INT_8(reveal_num) |
+ * INT_4(version) | HASHED_REVEALS | previous_SRV)
+ * This function cannot fail. */
+static sr_srv_t *
+generate_srv(const char *hashed_reveals, uint64_t reveal_num,
+ const sr_srv_t *previous_srv)
+{
+ char msg[DIGEST256_LEN + SR_SRV_MSG_LEN] = {0};
+ size_t offset = 0;
+ sr_srv_t *srv;
+
+ tor_assert(hashed_reveals);
+
+ /* Add the invariant token. */
+ memcpy(msg, SR_SRV_TOKEN, SR_SRV_TOKEN_LEN);
+ offset += SR_SRV_TOKEN_LEN;
+ set_uint64(msg + offset, tor_htonll(reveal_num));
+ offset += sizeof(uint64_t);
+ set_uint32(msg + offset, htonl(SR_PROTO_VERSION));
+ offset += sizeof(uint32_t);
+ memcpy(msg + offset, hashed_reveals, DIGEST256_LEN);
+ offset += DIGEST256_LEN;
+ if (previous_srv != NULL) {
+ memcpy(msg + offset, previous_srv->value, sizeof(previous_srv->value));
+ }
+
+ /* Ok we have our message and key for the HMAC computation, allocate our
+ * srv object and do the last step. */
+ srv = tor_malloc_zero(sizeof(*srv));
+ crypto_digest256((char *) srv->value, msg, sizeof(msg), SR_DIGEST_ALG);
+ srv->num_reveals = reveal_num;
+
+ {
+ /* Debugging. */
+ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+ sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv);
+ log_info(LD_DIR, "SR: Generated SRV: %s", srv_hash_encoded);
+ }
+ return srv;
+}
+
+/* Compare reveal values and return the result. This should exclusively be
+ * used by smartlist_sort(). */
+static int
+compare_reveal_(const void **_a, const void **_b)
+{
+ const sr_commit_t *a = *_a, *b = *_b;
+ return fast_memcmp(a->hashed_reveal, b->hashed_reveal,
+ sizeof(a->hashed_reveal));
+}
+
+/* Given <b>commit</b> give the line that we should place in our votes.
+ * It's the responsibility of the caller to free the string. */
+static char *
+get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase)
+{
+ char *vote_line = NULL;
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ tor_asprintf(&vote_line, "%s %u %s %s %s\n",
+ commit_ns_str,
+ SR_PROTO_VERSION,
+ crypto_digest_algorithm_get_name(commit->alg),
+ sr_commit_get_rsa_fpr(commit),
+ commit->encoded_commit);
+ break;
+ case SR_PHASE_REVEAL:
+ {
+ /* Send a reveal value for this commit if we have one. */
+ const char *reveal_str = commit->encoded_reveal;
+ if (tor_mem_is_zero(commit->encoded_reveal,
+ sizeof(commit->encoded_reveal))) {
+ reveal_str = "";
+ }
+ tor_asprintf(&vote_line, "%s %u %s %s %s %s\n",
+ commit_ns_str,
+ SR_PROTO_VERSION,
+ crypto_digest_algorithm_get_name(commit->alg),
+ sr_commit_get_rsa_fpr(commit),
+ commit->encoded_commit, reveal_str);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ log_debug(LD_DIR, "SR: Commit vote line: %s", vote_line);
+ return vote_line;
+}
+
+/* Return a heap allocated string that contains the given <b>srv</b> string
+ * representation formatted for a networkstatus document using the
+ * <b>key</b> as the start of the line. This doesn't return NULL. */
+static char *
+srv_to_ns_string(const sr_srv_t *srv, const char *key)
+{
+ char *srv_str;
+ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+ tor_assert(srv);
+ tor_assert(key);
+
+ sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv);
+ tor_asprintf(&srv_str, "%s %" PRIu64 " %s\n", key,
+ srv->num_reveals, srv_hash_encoded);
+ log_debug(LD_DIR, "SR: Consensus SRV line: %s", srv_str);
+ return srv_str;
+}
+
+/* Given the previous SRV and the current SRV, return a heap allocated
+ * string with their data that could be put in a vote or a consensus. Caller
+ * must free the returned string. Return NULL if no SRVs were provided. */
+static char *
+get_ns_str_from_sr_values(const sr_srv_t *prev_srv, const sr_srv_t *cur_srv)
+{
+ smartlist_t *chunks = NULL;
+ char *srv_str;
+
+ if (!prev_srv && !cur_srv) {
+ return NULL;
+ }
+
+ chunks = smartlist_new();
+
+ if (prev_srv) {
+ char *srv_line = srv_to_ns_string(prev_srv, previous_srv_str);
+ smartlist_add(chunks, srv_line);
+ }
+
+ if (cur_srv) {
+ char *srv_line = srv_to_ns_string(cur_srv, current_srv_str);
+ smartlist_add(chunks, srv_line);
+ }
+
+ /* Join the line(s) here in one string to return. */
+ srv_str = smartlist_join_strings(chunks, "", 0, NULL);
+ SMARTLIST_FOREACH(chunks, char *, s, tor_free(s));
+ smartlist_free(chunks);
+
+ return srv_str;
+}
+
+/* Return 1 iff the two commits have the same commitment values. This
+ * function does not care about reveal values. */
+STATIC int
+commitments_are_the_same(const sr_commit_t *commit_one,
+ const sr_commit_t *commit_two)
+{
+ tor_assert(commit_one);
+ tor_assert(commit_two);
+
+ if (strcmp(commit_one->encoded_commit, commit_two->encoded_commit)) {
+ return 0;
+ }
+ return 1;
+}
+
+/* We just received a commit from the vote of authority with
+ * <b>identity_digest</b>. Return 1 if this commit is authorititative that
+ * is, it belongs to the authority that voted it. Else return 0 if not. */
+STATIC int
+commit_is_authoritative(const sr_commit_t *commit,
+ const char *voter_key)
+{
+ tor_assert(commit);
+ tor_assert(voter_key);
+
+ return fast_memeq(commit->rsa_identity, voter_key,
+ sizeof(commit->rsa_identity));
+}
+
+/* Decide if the newly received <b>commit</b> should be kept depending on
+ * the current phase and state of the protocol. The <b>voter_key</b> is the
+ * RSA identity key fingerprint of the authority's vote from which the
+ * commit comes from. The <b>phase</b> is the phase we should be validating
+ * the commit for. Return 1 if the commit should be added to our state or 0
+ * if not. */
+STATIC int
+should_keep_commit(const sr_commit_t *commit, const char *voter_key,
+ sr_phase_t phase)
+{
+ const sr_commit_t *saved_commit;
+
+ tor_assert(commit);
+ tor_assert(voter_key);
+
+ log_debug(LD_DIR, "SR: Inspecting commit from %s (voter: %s)?",
+ sr_commit_get_rsa_fpr(commit),
+ hex_str(voter_key, DIGEST_LEN));
+
+ /* For a commit to be considered, it needs to be authoritative (it should
+ * be the voter's own commit). */
+ if (!commit_is_authoritative(commit, voter_key)) {
+ log_debug(LD_DIR, "SR: Ignoring non-authoritative commit.");
+ goto ignore;
+ }
+
+ /* Let's make sure, for extra safety, that this fingerprint is known to
+ * us. Even though this comes from a vote, doesn't hurt to be
+ * extracareful. */
+ if (trusteddirserver_get_by_v3_auth_digest(commit->rsa_identity) == NULL) {
+ log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized "
+ "authority. Discarding commit.",
+ escaped(commit->rsa_identity));
+ goto ignore;
+ }
+
+ /* Check if the authority that voted for <b>commit</b> has already posted
+ * a commit before. */
+ saved_commit = sr_state_get_commit(commit->rsa_identity);
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ /* Already having a commit for an authority so ignore this one. */
+ if (saved_commit) {
+ /* Receiving known commits should happen naturally since commit phase
+ lasts multiple rounds. However if the commitment value changes
+ during commit phase, it might be a bug so log more loudly. */
+ if (!commitments_are_the_same(commit, saved_commit)) {
+ log_info(LD_DIR,
+ "SR: Received altered commit from %s in commit phase.",
+ sr_commit_get_rsa_fpr(commit));
+ } else {
+ log_debug(LD_DIR, "SR: Ignoring known commit during commit phase.");
+ }
+ goto ignore;
+ }
+
+ /* A commit with a reveal value during commitment phase is very wrong. */
+ if (commit_has_reveal_value(commit)) {
+ log_warn(LD_DIR, "SR: Commit from authority %s has a reveal value "
+ "during COMMIT phase. (voter: %s)",
+ sr_commit_get_rsa_fpr(commit),
+ hex_str(voter_key, DIGEST_LEN));
+ goto ignore;
+ }
+ break;
+ case SR_PHASE_REVEAL:
+ /* We are now in reveal phase. We keep a commit if and only if:
+ *
+ * - We have already seen a commit by this auth, AND
+ * - the saved commit has the same commitment value as this one, AND
+ * - the saved commit has no reveal information, AND
+ * - this commit does have reveal information, AND
+ * - the reveal & commit information are matching.
+ *
+ * If all the above are true, then we are interested in this new commit
+ * for its reveal information. */
+
+ if (!saved_commit) {
+ log_debug(LD_DIR, "SR: Ignoring commit first seen in reveal phase.");
+ goto ignore;
+ }
+
+ if (!commitments_are_the_same(commit, saved_commit)) {
+ log_warn(LD_DIR, "SR: Commit from authority %s is different from "
+ "previous commit in our state (voter: %s)",
+ sr_commit_get_rsa_fpr(commit),
+ hex_str(voter_key, DIGEST_LEN));
+ goto ignore;
+ }
+
+ if (commit_has_reveal_value(saved_commit)) {
+ log_debug(LD_DIR, "SR: Ignoring commit with known reveal info.");
+ goto ignore;
+ }
+
+ if (!commit_has_reveal_value(commit)) {
+ log_debug(LD_DIR, "SR: Ignoring commit without reveal value.");
+ goto ignore;
+ }
+
+ if (verify_commit_and_reveal(commit) < 0) {
+ log_warn(LD_BUG, "SR: Commit from authority %s has an invalid "
+ "reveal value. (voter: %s)",
+ sr_commit_get_rsa_fpr(commit),
+ hex_str(voter_key, DIGEST_LEN));
+ goto ignore;
+ }
+ break;
+ default:
+ tor_assert(0);
+ }
+
+ return 1;
+
+ ignore:
+ return 0;
+}
+
+/* We are in reveal phase and we found a valid and verified <b>commit</b> in
+ * a vote that contains reveal values that we could use. Update the commit
+ * we have in our state. Never call this with an unverified commit. */
+STATIC void
+save_commit_during_reveal_phase(const sr_commit_t *commit)
+{
+ sr_commit_t *saved_commit;
+
+ tor_assert(commit);
+
+ /* Get the commit from our state. */
+ saved_commit = sr_state_get_commit(commit->rsa_identity);
+ tor_assert(saved_commit);
+ /* Safety net. They can not be different commitments at this point. */
+ int same_commits = commitments_are_the_same(commit, saved_commit);
+ tor_assert(same_commits);
+
+ /* Copy reveal information to our saved commit. */
+ sr_state_copy_reveal_info(saved_commit, commit);
+}
+
+/* Save <b>commit</b> to our persistent state. Depending on the current
+ * phase, different actions are taken. Steals reference of <b>commit</b>.
+ * The commit object MUST be valid and verified before adding it to the
+ * state. */
+STATIC void
+save_commit_to_state(sr_commit_t *commit)
+{
+ sr_phase_t phase = sr_state_get_phase();
+
+ ASSERT_COMMIT_VALID(commit);
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ /* During commit phase, just save any new authoritative commit */
+ sr_state_add_commit(commit);
+ break;
+ case SR_PHASE_REVEAL:
+ save_commit_during_reveal_phase(commit);
+ sr_commit_free(commit);
+ break;
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Return 1 if we should we keep an SRV voted by <b>n_agreements</b> auths.
+ * Return 0 if we should ignore it. */
+static int
+should_keep_srv(int n_agreements)
+{
+ /* Check if the most popular SRV has reached majority. */
+ int n_voters = get_n_authorities(V3_DIRINFO);
+ int votes_required_for_majority = (n_voters / 2) + 1;
+
+ /* We need at the very least majority to keep a value. */
+ if (n_agreements < votes_required_for_majority) {
+ log_notice(LD_DIR, "SR: SRV didn't reach majority [%d/%d]!",
+ n_agreements, votes_required_for_majority);
+ return 0;
+ }
+
+ /* When we just computed a new SRV, we need to have super majority in order
+ * to keep it. */
+ if (sr_state_srv_is_fresh()) {
+ /* Check if we have super majority for this new SRV value. */
+ if (n_agreements < num_srv_agreements_from_vote) {
+ log_notice(LD_DIR, "SR: New SRV didn't reach agreement [%d/%d]!",
+ n_agreements, num_srv_agreements_from_vote);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Helper: compare two DIGEST256_LEN digests. */
+static int
+compare_srvs_(const void **_a, const void **_b)
+{
+ const sr_srv_t *a = *_a, *b = *_b;
+ return tor_memcmp(a->value, b->value, sizeof(a->value));
+}
+
+/* Return the most frequent member of the sorted list of DIGEST256_LEN
+ * digests in <b>sl</b> with the count of that most frequent element. */
+static sr_srv_t *
+smartlist_get_most_frequent_srv(const smartlist_t *sl, int *count_out)
+{
+ return smartlist_get_most_frequent_(sl, compare_srvs_, count_out);
+}
+
+/** Compare two SRVs. Used in smartlist sorting. */
+static int
+compare_srv_(const void **_a, const void **_b)
+{
+ const sr_srv_t *a = *_a, *b = *_b;
+ return fast_memcmp(a->value, b->value,
+ sizeof(a->value));
+}
+
+/* Using a list of <b>votes</b>, return the SRV object from them that has
+ * been voted by the majority of dirauths. If <b>current</b> is set, we look
+ * for the current SRV value else the previous one. The returned pointer is
+ * an object located inside a vote. NULL is returned if no appropriate value
+ * could be found. */
+STATIC sr_srv_t *
+get_majority_srv_from_votes(const smartlist_t *votes, int current)
+{
+ int count = 0;
+ sr_srv_t *most_frequent_srv = NULL;
+ sr_srv_t *the_srv = NULL;
+ smartlist_t *srv_list;
+
+ tor_assert(votes);
+
+ srv_list = smartlist_new();
+
+ /* Walk over votes and register any SRVs found. */
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ sr_srv_t *srv_tmp = NULL;
+
+ if (!v->sr_info.participate) {
+ /* Ignore vote that do not participate. */
+ continue;
+ }
+ /* Do we want previous or current SRV? */
+ srv_tmp = current ? v->sr_info.current_srv : v->sr_info.previous_srv;
+ if (!srv_tmp) {
+ continue;
+ }
+
+ smartlist_add(srv_list, srv_tmp);
+ } SMARTLIST_FOREACH_END(v);
+
+ smartlist_sort(srv_list, compare_srv_);
+ most_frequent_srv = smartlist_get_most_frequent_srv(srv_list, &count);
+ if (!most_frequent_srv) {
+ goto end;
+ }
+
+ /* Was this SRV voted by enough auths for us to keep it? */
+ if (!should_keep_srv(count)) {
+ goto end;
+ }
+
+ /* We found an SRV that we can use! Habemus SRV! */
+ the_srv = most_frequent_srv;
+
+ {
+ /* Debugging */
+ char encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+ sr_srv_encode(encoded, sizeof(encoded), the_srv);
+ log_debug(LD_DIR, "SR: Chosen SRV by majority: %s (%d votes)", encoded,
+ count);
+ }
+
+ end:
+ /* We do not free any sr_srv_t values, we don't have the ownership. */
+ smartlist_free(srv_list);
+ return the_srv;
+}
+
+/* Encode the given shared random value and put it in dst. Destination
+ * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */
+void
+sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv)
+{
+ int ret;
+ /* Extra byte for the NULL terminated char. */
+ char buf[SR_SRV_VALUE_BASE64_LEN + 1];
+
+ tor_assert(dst);
+ tor_assert(srv);
+ tor_assert(dst_len >= sizeof(buf));
+
+ ret = base64_encode(buf, sizeof(buf), (const char *) srv->value,
+ sizeof(srv->value), 0);
+ /* Always expect the full length without the NULL byte. */
+ tor_assert(ret == (sizeof(buf) - 1));
+ tor_assert(ret <= (int) dst_len);
+ strlcpy(dst, buf, dst_len);
+}
+
+/* Free a commit object. */
+void
+sr_commit_free(sr_commit_t *commit)
+{
+ if (commit == NULL) {
+ return;
+ }
+ /* Make sure we do not leave OUR random number in memory. */
+ memwipe(commit->random_number, 0, sizeof(commit->random_number));
+ tor_free(commit);
+}
+
+/* Generate the commitment/reveal value for the protocol run starting at
+ * <b>timestamp</b>. <b>my_rsa_cert</b> is our authority RSA certificate. */
+sr_commit_t *
+sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert)
+{
+ sr_commit_t *commit = NULL;
+ char digest[DIGEST_LEN];
+
+ tor_assert(my_rsa_cert);
+
+ /* Get our RSA identity fingerprint */
+ if (crypto_pk_get_digest(my_rsa_cert->identity_key, digest) < 0) {
+ goto error;
+ }
+
+ /* New commit with our identity key. */
+ commit = commit_new(digest);
+
+ /* Generate the reveal random value */
+ crypto_strongest_rand(commit->random_number,
+ sizeof(commit->random_number));
+ commit->commit_ts = commit->reveal_ts = timestamp;
+
+ /* Now get the base64 blob that corresponds to our reveal */
+ if (reveal_encode(commit, commit->encoded_reveal,
+ sizeof(commit->encoded_reveal)) < 0) {
+ log_err(LD_DIR, "SR: Unable to encode our reveal value!");
+ goto error;
+ }
+
+ /* Now let's create the commitment */
+ tor_assert(commit->alg == SR_DIGEST_ALG);
+ /* The invariant length is used here since the encoded reveal variable
+ * has an extra byte added for the NULL terminated byte. */
+ if (crypto_digest256(commit->hashed_reveal, commit->encoded_reveal,
+ SR_REVEAL_BASE64_LEN, commit->alg)) {
+ goto error;
+ }
+
+ /* Now get the base64 blob that corresponds to our commit. */
+ if (commit_encode(commit, commit->encoded_commit,
+ sizeof(commit->encoded_commit)) < 0) {
+ log_err(LD_DIR, "SR: Unable to encode our commit value!");
+ goto error;
+ }
+
+ log_debug(LD_DIR, "SR: Generated our commitment:");
+ commit_log(commit);
+ /* Our commit better be valid :). */
+ commit->valid = 1;
+ return commit;
+
+ error:
+ sr_commit_free(commit);
+ return NULL;
+}
+
+/* Compute the shared random value based on the active commits in our state. */
+void
+sr_compute_srv(void)
+{
+ uint64_t reveal_num = 0;
+ char *reveals = NULL;
+ smartlist_t *chunks, *commits;
+ digestmap_t *state_commits;
+
+ /* Computing a shared random value in the commit phase is very wrong. This
+ * should only happen at the very end of the reveal phase when a new
+ * protocol run is about to start. */
+ tor_assert(sr_state_get_phase() == SR_PHASE_REVEAL);
+ state_commits = sr_state_get_commits();
+
+ commits = smartlist_new();
+ chunks = smartlist_new();
+
+ /* We must make a list of commit ordered by authority fingerprint in
+ * ascending order as specified by proposal 250. */
+ DIGESTMAP_FOREACH(state_commits, key, sr_commit_t *, c) {
+ /* Extra safety net, make sure we have valid commit before using it. */
+ ASSERT_COMMIT_VALID(c);
+ /* Let's not use a commit from an authority that we don't know. It's
+ * possible that an authority could be removed during a protocol run so
+ * that commit value should never be used in the SRV computation. */
+ if (trusteddirserver_get_by_v3_auth_digest(c->rsa_identity) == NULL) {
+ log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized "
+ "authority. Discarding commit for the SRV computation.",
+ sr_commit_get_rsa_fpr(c));
+ continue;
+ }
+ /* We consider this commit valid. */
+ smartlist_add(commits, c);
+ } DIGESTMAP_FOREACH_END;
+ smartlist_sort(commits, compare_reveal_);
+
+ /* Now for each commit for that sorted list in ascending order, we'll
+ * build the element for each authority that needs to go into the srv
+ * computation. */
+ SMARTLIST_FOREACH_BEGIN(commits, const sr_commit_t *, c) {
+ char *element = get_srv_element_from_commit(c);
+ if (element) {
+ smartlist_add(chunks, element);
+ reveal_num++;
+ }
+ } SMARTLIST_FOREACH_END(c);
+ smartlist_free(commits);
+
+ {
+ /* Join all reveal values into one giant string that we'll hash so we
+ * can generated our shared random value. */
+ sr_srv_t *current_srv;
+ char hashed_reveals[DIGEST256_LEN];
+ reveals = smartlist_join_strings(chunks, "", 0, NULL);
+ SMARTLIST_FOREACH(chunks, char *, s, tor_free(s));
+ smartlist_free(chunks);
+ if (crypto_digest256(hashed_reveals, reveals, strlen(reveals),
+ SR_DIGEST_ALG)) {
+ goto end;
+ }
+ current_srv = generate_srv(hashed_reveals, reveal_num,
+ sr_state_get_previous_srv());
+ sr_state_set_current_srv(current_srv);
+ /* We have a fresh SRV, flag our state. */
+ sr_state_set_fresh_srv();
+ }
+
+ end:
+ tor_free(reveals);
+}
+
+/* Parse a list of arguments from a SRV value either from a vote, consensus
+ * or from our disk state and return a newly allocated srv object. NULL is
+ * returned on error.
+ *
+ * The arguments' order:
+ * num_reveals, value
+ */
+sr_srv_t *
+sr_parse_srv(const smartlist_t *args)
+{
+ char *value;
+ int ok, ret;
+ uint64_t num_reveals;
+ sr_srv_t *srv = NULL;
+
+ tor_assert(args);
+
+ if (smartlist_len(args) < 2) {
+ goto end;
+ }
+
+ /* First argument is the number of reveal values */
+ num_reveals = tor_parse_uint64(smartlist_get(args, 0),
+ 10, 0, UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ goto end;
+ }
+ /* Second and last argument is the shared random value it self. */
+ value = smartlist_get(args, 1);
+ if (strlen(value) != SR_SRV_VALUE_BASE64_LEN) {
+ goto end;
+ }
+
+ srv = tor_malloc_zero(sizeof(*srv));
+ srv->num_reveals = num_reveals;
+ /* We substract one byte from the srclen because the function ignores the
+ * '=' character in the given buffer. This is broken but it's a documented
+ * behavior of the implementation. */
+ ret = base64_decode((char *) srv->value, sizeof(srv->value), value,
+ SR_SRV_VALUE_BASE64_LEN - 1);
+ if (ret != sizeof(srv->value)) {
+ tor_free(srv);
+ srv = NULL;
+ goto end;
+ }
+ end:
+ return srv;
+}
+
+/* Parse a commit from a vote or from our disk state and return a newly
+ * allocated commit object. NULL is returned on error.
+ *
+ * The commit's data is in <b>args</b> and the order matters very much:
+ * version, algname, RSA fingerprint, commit value[, reveal value]
+ */
+sr_commit_t *
+sr_parse_commit(const smartlist_t *args)
+{
+ uint32_t version;
+ char *value, digest[DIGEST_LEN];
+ digest_algorithm_t alg;
+ const char *rsa_identity_fpr;
+ sr_commit_t *commit = NULL;
+
+ if (smartlist_len(args) < 4) {
+ goto error;
+ }
+
+ /* First is the version number of the SR protocol which indicates at which
+ * version that commit was created. */
+ value = smartlist_get(args, 0);
+ version = (uint32_t) tor_parse_ulong(value, 10, 1, UINT32_MAX, NULL, NULL);
+ if (version > SR_PROTO_VERSION) {
+ log_info(LD_DIR, "SR: Commit version %" PRIu32 " (%s) is not supported.",
+ version, escaped(value));
+ goto error;
+ }
+
+ /* Second is the algorithm. */
+ value = smartlist_get(args, 1);
+ alg = crypto_digest_algorithm_parse_name(value);
+ if (alg != SR_DIGEST_ALG) {
+ log_warn(LD_BUG, "SR: Commit algorithm %s is not recognized.",
+ escaped(value));
+ goto error;
+ }
+
+ /* Third argument is the RSA fingerprint of the auth and turn it into a
+ * digest value. */
+ rsa_identity_fpr = smartlist_get(args, 2);
+ if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr,
+ HEX_DIGEST_LEN) < 0) {
+ log_warn(LD_DIR, "SR: RSA fingerprint %s not decodable",
+ escaped(rsa_identity_fpr));
+ goto error;
+ }
+
+ /* Allocate commit since we have a valid identity now. */
+ commit = commit_new(digest);
+
+ /* Fourth argument is the commitment value base64-encoded. */
+ value = smartlist_get(args, 3);
+ if (commit_decode(value, commit) < 0) {
+ goto error;
+ }
+
+ /* (Optional) Fifth argument is the revealed value. */
+ if (smartlist_len(args) > 4) {
+ value = smartlist_get(args, 4);
+ if (reveal_decode(value, commit) < 0) {
+ goto error;
+ }
+ }
+
+ return commit;
+
+ error:
+ sr_commit_free(commit);
+ return NULL;
+}
+
+/* Called when we are done parsing a vote by <b>voter_key</b> that might
+ * contain some useful <b>commits</b>. Find if any of them should be kept
+ * and update our state accordingly. Once done, the list of commitments will
+ * be empty. */
+void
+sr_handle_received_commits(smartlist_t *commits, crypto_pk_t *voter_key)
+{
+ char rsa_identity[DIGEST_LEN];
+
+ tor_assert(voter_key);
+
+ /* It's possible that the vote has _NO_ commits. */
+ if (commits == NULL) {
+ return;
+ }
+
+ /* Get the RSA identity fingerprint of this voter */
+ if (crypto_pk_get_digest(voter_key, rsa_identity) < 0) {
+ return;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(commits, sr_commit_t *, commit) {
+ /* We won't need the commit in this list anymore, kept or not. */
+ SMARTLIST_DEL_CURRENT(commits, commit);
+ /* Check if this commit is valid and should be stored in our state. */
+ if (!should_keep_commit(commit, rsa_identity,
+ sr_state_get_phase())) {
+ sr_commit_free(commit);
+ continue;
+ }
+ /* Ok, we have a valid commit now that we are about to put in our state.
+ * so flag it valid from now on. */
+ commit->valid = 1;
+ /* Everything lines up: save this commit to state then! */
+ save_commit_to_state(commit);
+ } SMARTLIST_FOREACH_END(commit);
+}
+
+/* Return a heap-allocated string containing commits that should be put in
+ * the votes. It's the responsibility of the caller to free the string.
+ * This always return a valid string, either empty or with line(s). */
+char *
+sr_get_string_for_vote(void)
+{
+ char *vote_str = NULL;
+ digestmap_t *state_commits;
+ smartlist_t *chunks = smartlist_new();
+ const or_options_t *options = get_options();
+
+ /* Are we participating in the protocol? */
+ if (!options->AuthDirSharedRandomness) {
+ goto end;
+ }
+
+ log_debug(LD_DIR, "SR: Preparing our vote info:");
+
+ /* First line, put in the vote the participation flag. */
+ {
+ char *sr_flag_line;
+ tor_asprintf(&sr_flag_line, "%s\n", sr_flag_ns_str);
+ smartlist_add(chunks, sr_flag_line);
+ }
+
+ /* In our vote we include every commitment in our permanent state. */
+ state_commits = sr_state_get_commits();
+ smartlist_t *state_commit_vote_lines = smartlist_new();
+ DIGESTMAP_FOREACH(state_commits, key, const sr_commit_t *, commit) {
+ char *line = get_vote_line_from_commit(commit, sr_state_get_phase());
+ smartlist_add(state_commit_vote_lines, line);
+ } DIGESTMAP_FOREACH_END;
+
+ /* Sort the commit strings by version (string, not numeric), algorithm,
+ * and fingerprint. This makes sure the commit lines in votes are in a
+ * recognisable, stable order. */
+ smartlist_sort_strings(state_commit_vote_lines);
+
+ /* Now add the sorted list of commits to the vote */
+ smartlist_add_all(chunks, state_commit_vote_lines);
+ smartlist_free(state_commit_vote_lines);
+
+ /* Add the SRV value(s) if any. */
+ {
+ char *srv_lines = get_ns_str_from_sr_values(sr_state_get_previous_srv(),
+ sr_state_get_current_srv());
+ if (srv_lines) {
+ smartlist_add(chunks, srv_lines);
+ }
+ }
+
+ end:
+ vote_str = smartlist_join_strings(chunks, "", 0, NULL);
+ SMARTLIST_FOREACH(chunks, char *, s, tor_free(s));
+ smartlist_free(chunks);
+ return vote_str;
+}
+
+/* Return a heap-allocated string that should be put in the consensus and
+ * contains the shared randomness values. It's the responsibility of the
+ * caller to free the string. NULL is returned if no SRV(s) available.
+ *
+ * This is called when a consensus (any flavor) is bring created thus it
+ * should NEVER change the state nor the state should be changed in between
+ * consensus creation.
+ *
+ * <b>num_srv_agreements</b> is taken from the votes thus the voted value
+ * that should be used.
+ * */
+char *
+sr_get_string_for_consensus(const smartlist_t *votes,
+ int32_t num_srv_agreements)
+{
+ char *srv_str;
+ const or_options_t *options = get_options();
+
+ tor_assert(votes);
+
+ /* Not participating, avoid returning anything. */
+ if (!options->AuthDirSharedRandomness) {
+ log_info(LD_DIR, "SR: Support disabled (AuthDirSharedRandomness %d)",
+ options->AuthDirSharedRandomness);
+ goto end;
+ }
+
+ /* Set the global value of AuthDirNumSRVAgreements found in the votes. */
+ num_srv_agreements_from_vote = num_srv_agreements;
+
+ /* Check the votes and figure out if SRVs should be included in the final
+ * consensus. */
+ sr_srv_t *prev_srv = get_majority_srv_from_votes(votes, 0);
+ sr_srv_t *cur_srv = get_majority_srv_from_votes(votes, 1);
+ srv_str = get_ns_str_from_sr_values(prev_srv, cur_srv);
+ if (!srv_str) {
+ goto end;
+ }
+
+ return srv_str;
+ end:
+ return NULL;
+}
+
+/* We just computed a new <b>consensus</b>. Update our state with the SRVs
+ * from the consensus (might be NULL as well). Register the SRVs in our SR
+ * state and prepare for the upcoming protocol round. */
+void
+sr_act_post_consensus(const networkstatus_t *consensus)
+{
+ const or_options_t *options = get_options();
+
+ /* Don't act if our state hasn't been initialized. We can be called during
+ * boot time when loading consensus from disk which is prior to the
+ * initialization of the SR subsystem. We also should not be doing
+ * anything if we are _not_ a directory authority and if we are a bridge
+ * authority. */
+ if (!sr_state_is_initialized() || !authdir_mode_v3(options) ||
+ authdir_mode_bridge(options)) {
+ return;
+ }
+
+ /* Set the majority voted SRVs in our state even if both are NULL. It
+ * doesn't matter this is what the majority has decided. Obviously, we can
+ * only do that if we have a consensus. */
+ if (consensus) {
+ /* Start by freeing the current SRVs since the SRVs we believed during
+ * voting do not really matter. Now that all the votes are in, we use the
+ * majority's opinion on which are the active SRVs. */
+ sr_state_clean_srvs();
+ /* Reset the fresh flag of the SRV so we know that from now on we don't
+ * have a new SRV to vote for. We just used the one from the consensus
+ * decided by the majority. */
+ sr_state_unset_fresh_srv();
+ /* Set the SR values from the given consensus. */
+ sr_state_set_previous_srv(srv_dup(consensus->sr_info.previous_srv));
+ sr_state_set_current_srv(srv_dup(consensus->sr_info.current_srv));
+ }
+
+ /* 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);
+ }
+}
+
+/* Initialize shared random subsystem. This MUST be called early in the boot
+ * process of tor. Return 0 on success else -1 on error. */
+int
+sr_init(int save_to_disk)
+{
+ return sr_state_init(save_to_disk, 1);
+}
+
+/* Save our state to disk and cleanup everything. */
+void
+sr_save_and_cleanup(void)
+{
+ sr_state_save();
+ sr_cleanup();
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Set the global value of number of SRV agreements so the test can play
+ * along by calling specific functions that don't parse the votes prior for
+ * the AuthDirNumSRVAgreements value. */
+void
+set_num_srv_agreements(int32_t value)
+{
+ num_srv_agreements_from_vote = value;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/shared_random.h b/src/or/shared_random.h
new file mode 100644
index 0000000000..9885934cc7
--- /dev/null
+++ b/src/or/shared_random.h
@@ -0,0 +1,168 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SHARED_RANDOM_H
+#define TOR_SHARED_RANDOM_H
+
+/*
+ * This file contains ABI/API of the shared random protocol defined in
+ * proposal #250. Every public functions and data structure are namespaced
+ * with "sr_" which stands for shared random.
+ */
+
+#include "or.h"
+
+/* Protocol version */
+#define SR_PROTO_VERSION 1
+/* Default digest algorithm. */
+#define SR_DIGEST_ALG DIGEST_SHA3_256
+/* Invariant token in the SRV calculation. */
+#define SR_SRV_TOKEN "shared-random"
+/* Don't count the NUL terminated byte even though the TOKEN has it. */
+#define SR_SRV_TOKEN_LEN (sizeof(SR_SRV_TOKEN) - 1)
+
+/* Length of the random number (in bytes). */
+#define SR_RANDOM_NUMBER_LEN 32
+/* Size of a decoded commit value in a vote or state. It's a hash and a
+ * timestamp. It adds up to 40 bytes. */
+#define SR_COMMIT_LEN (sizeof(uint64_t) + DIGEST256_LEN)
+/* Size of a decoded reveal value from a vote or state. It's a 64 bit
+ * timestamp and the hashed random number. This adds up to 40 bytes. */
+#define SR_REVEAL_LEN (sizeof(uint64_t) + DIGEST256_LEN)
+/* Size of SRV message length. The construction is has follow:
+ * "shared-random" | INT_8(reveal_num) | INT_4(version) | PREV_SRV */
+#define SR_SRV_MSG_LEN \
+ (SR_SRV_TOKEN_LEN + sizeof(uint64_t) + sizeof(uint32_t) + DIGEST256_LEN)
+
+/* Length of base64 encoded commit NOT including the NUL terminated byte.
+ * Formula is taken from base64_encode_size. This adds up to 56 bytes. */
+#define SR_COMMIT_BASE64_LEN \
+ (((SR_COMMIT_LEN - 1) / 3) * 4 + 4)
+/* Length of base64 encoded reveal NOT including the NUL terminated byte.
+ * Formula is taken from base64_encode_size. This adds up to 56 bytes. */
+#define SR_REVEAL_BASE64_LEN \
+ (((SR_REVEAL_LEN - 1) / 3) * 4 + 4)
+/* Length of base64 encoded shared random value. It's 32 bytes long so 44
+ * bytes from the base64_encode_size formula. That includes the '='
+ * character at the end. */
+#define SR_SRV_VALUE_BASE64_LEN \
+ (((DIGEST256_LEN - 1) / 3) * 4 + 4)
+
+/* Assert if commit valid flag is not set. */
+#define ASSERT_COMMIT_VALID(c) tor_assert((c)->valid)
+
+/* Protocol phase. */
+typedef enum {
+ /* Commitment phase */
+ SR_PHASE_COMMIT = 1,
+ /* Reveal phase */
+ SR_PHASE_REVEAL = 2,
+} sr_phase_t;
+
+/* A shared random value (SRV). */
+typedef struct sr_srv_t {
+ /* The number of reveal values used to derive this SRV. */
+ uint64_t num_reveals;
+ /* The actual value. This is the stored result of SHA3-256. */
+ uint8_t value[DIGEST256_LEN];
+} sr_srv_t;
+
+/* A commit (either ours or from another authority). */
+typedef struct sr_commit_t {
+ /* Hashing algorithm used. */
+ digest_algorithm_t alg;
+ /* Indicate if this commit has been verified thus valid. */
+ unsigned int valid:1;
+
+ /* Commit owner info */
+
+ /* The RSA identity key of the authority and its base16 representation,
+ * which includes the NUL terminated byte. */
+ char rsa_identity[DIGEST_LEN];
+ char rsa_identity_hex[HEX_DIGEST_LEN + 1];
+
+ /* Commitment information */
+
+ /* Timestamp of reveal. Correspond to TIMESTAMP. */
+ uint64_t reveal_ts;
+ /* H(REVEAL) as found in COMMIT message. */
+ char hashed_reveal[DIGEST256_LEN];
+ /* Base64 encoded COMMIT. We use this to put it in our vote. */
+ char encoded_commit[SR_COMMIT_BASE64_LEN + 1];
+
+ /* Reveal information */
+
+ /* H(RN) which is what we used as the random value for this commit. We
+ * don't use the raw bytes since those are sent on the network thus
+ * avoiding possible information leaks of our PRNG. */
+ uint8_t random_number[SR_RANDOM_NUMBER_LEN];
+ /* Timestamp of commit. Correspond to TIMESTAMP. */
+ uint64_t commit_ts;
+ /* This is the whole reveal message. We use it during verification */
+ char encoded_reveal[SR_REVEAL_BASE64_LEN + 1];
+} sr_commit_t;
+
+/* API */
+
+/* Public methods: */
+
+int sr_init(int save_to_disk);
+void sr_save_and_cleanup(void);
+void sr_act_post_consensus(const networkstatus_t *consensus);
+void sr_handle_received_commits(smartlist_t *commits,
+ crypto_pk_t *voter_key);
+sr_commit_t *sr_parse_commit(const smartlist_t *args);
+sr_srv_t *sr_parse_srv(const smartlist_t *args);
+char *sr_get_string_for_vote(void);
+char *sr_get_string_for_consensus(const smartlist_t *votes,
+ int32_t num_srv_agreements);
+void sr_commit_free(sr_commit_t *commit);
+void sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv);
+
+/* Private methods (only used by shared_random_state.c): */
+static inline
+const char *sr_commit_get_rsa_fpr(const sr_commit_t *commit)
+{
+ return commit->rsa_identity_hex;
+}
+
+void sr_compute_srv(void);
+sr_commit_t *sr_generate_our_commit(time_t timestamp,
+ const authority_cert_t *my_rsa_cert);
+#ifdef SHARED_RANDOM_PRIVATE
+
+/* Encode */
+STATIC int reveal_encode(const sr_commit_t *commit, char *dst, size_t len);
+STATIC int commit_encode(const sr_commit_t *commit, char *dst, size_t len);
+/* Decode. */
+STATIC int commit_decode(const char *encoded, sr_commit_t *commit);
+STATIC int reveal_decode(const char *encoded, sr_commit_t *commit);
+
+STATIC int commit_has_reveal_value(const sr_commit_t *commit);
+
+STATIC int verify_commit_and_reveal(const sr_commit_t *commit);
+
+STATIC sr_srv_t *get_majority_srv_from_votes(const smartlist_t *votes,
+ int current);
+
+STATIC void save_commit_to_state(sr_commit_t *commit);
+STATIC sr_srv_t *srv_dup(const sr_srv_t *orig);
+STATIC int commitments_are_the_same(const sr_commit_t *commit_one,
+ const sr_commit_t *commit_two);
+STATIC int commit_is_authoritative(const sr_commit_t *commit,
+ const char *voter_key);
+STATIC int should_keep_commit(const sr_commit_t *commit,
+ const char *voter_key,
+ sr_phase_t phase);
+STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit);
+
+#endif /* SHARED_RANDOM_PRIVATE */
+
+#ifdef TOR_UNIT_TESTS
+
+void set_num_srv_agreements(int32_t value);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* TOR_SHARED_RANDOM_H */
+
diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c
new file mode 100644
index 0000000000..87db9031ee
--- /dev/null
+++ b/src/or/shared_random_state.c
@@ -0,0 +1,1359 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file shared_random_state.c
+ *
+ * \brief Functions and data structures for the state of the random protocol
+ * as defined in proposal #250.
+ **/
+
+#define SHARED_RANDOM_STATE_PRIVATE
+
+#include "or.h"
+#include "shared_random.h"
+#include "config.h"
+#include "confparse.h"
+#include "dirvote.h"
+#include "networkstatus.h"
+#include "router.h"
+#include "shared_random_state.h"
+
+/* Default filename of the shared random state on disk. */
+static const char default_fname[] = "sr-state";
+
+/* String representation of a protocol phase. */
+static const char *phase_str[] = { "unknown", "commit", "reveal" };
+
+/* Our shared random protocol state. There is only one possible state per
+ * protocol run so this is the global state which is reset at every run once
+ * the shared random value has been computed. */
+static sr_state_t *sr_state = NULL;
+
+/* Representation of our persistent state on disk. The sr_state above
+ * contains the data parsed from this state. When we save to disk, we
+ * translate the sr_state to this sr_disk_state. */
+static sr_disk_state_t *sr_disk_state = NULL;
+
+/* Disk state file keys. */
+static const char dstate_commit_key[] = "Commit";
+static const char dstate_prev_srv_key[] = "SharedRandPreviousValue";
+static const char dstate_cur_srv_key[] = "SharedRandCurrentValue";
+
+/* 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 }
+/* As VAR, but the option name and member name are the same. */
+#define V(member, conftype, initvalue) \
+ VAR(#member, conftype, member, initvalue)
+/* Our persistent state magic number. */
+#define SR_DISK_STATE_MAGIC 0x98AB1254
+/* Each protocol phase has 12 rounds */
+#define SHARED_RANDOM_N_ROUNDS 12
+/* Number of phase we have in a protocol. */
+#define SHARED_RANDOM_N_PHASES 2
+
+static int
+disk_state_validate_cb(void *old_state, void *state, void *default_state,
+ int from_setconf, char **msg);
+
+/* Array of variables that are saved to disk as a persistent state. */
+static config_var_t state_vars[] = {
+ V(Version, UINT, "0"),
+ V(TorVersion, STRING, NULL),
+ V(ValidAfter, ISOTIME, NULL),
+ V(ValidUntil, ISOTIME, NULL),
+
+ V(Commit, LINELIST, NULL),
+
+ V(SharedRandValues, LINELIST_V, NULL),
+ VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL),
+ VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL),
+ { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
+};
+
+/* "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
+};
+
+/* 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_),
+ NULL,
+ NULL,
+ state_vars,
+ disk_state_validate_cb,
+ &state_extra_var,
+};
+
+/* Return a string representation of a protocol phase. */
+STATIC const char *
+get_phase_str(sr_phase_t phase)
+{
+ const char *the_string = NULL;
+
+ switch (phase) {
+ case SR_PHASE_COMMIT:
+ case SR_PHASE_REVEAL:
+ the_string = phase_str[phase];
+ break;
+ default:
+ /* Unknown phase shouldn't be possible. */
+ tor_assert(0);
+ }
+
+ return the_string;
+}
+
+/* Return the voting interval of the tor vote subsystem. */
+static int
+get_voting_interval(void)
+{
+ int interval;
+ networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL));
+
+ if (consensus) {
+ interval = (int)(consensus->fresh_until - consensus->valid_after);
+ } else {
+ /* Same for both a testing and real network. We voluntarily ignore the
+ * InitialVotingInterval since it complexifies things and it doesn't
+ * affect the SR protocol. */
+ interval = get_options()->V3AuthVotingInterval;
+ }
+ tor_assert(interval > 0);
+ return interval;
+}
+
+/* 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)
+{
+ 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;
+ /* 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);
+
+ voting_schedule_free(new_voting_schedule);
+
+ return curr_start;
+}
+
+/* Return the time we should expire the state file created at <b>now</b>.
+ * We expire the state file in the beginning of the next protocol run. */
+STATIC time_t
+get_state_valid_until_time(time_t now)
+{
+ int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ int current_round, voting_interval, rounds_left;
+ time_t valid_until, beginning_of_current_round;
+
+ voting_interval = get_voting_interval();
+ /* Find the time the current round started. */
+ beginning_of_current_round = get_start_time_of_current_round(now);
+
+ /* Find how many rounds are left till the end of the protocol run */
+ current_round = (now / voting_interval) % total_rounds;
+ rounds_left = total_rounds - current_round;
+
+ /* To find the valid-until time now, take the start time of the current
+ * round and add to it the time it takes for the leftover rounds to
+ * complete. */
+ valid_until = beginning_of_current_round + (rounds_left * voting_interval);
+
+ { /* Logging */
+ char tbuf[ISO_TIME_LEN + 1];
+ format_iso_time(tbuf, valid_until);
+ log_debug(LD_DIR, "SR: Valid until time for state set to %s.", tbuf);
+ }
+
+ return valid_until;
+}
+
+/* Given the consensus 'valid-after' time, return the protocol phase we should
+ * be in. */
+STATIC sr_phase_t
+get_sr_protocol_phase(time_t valid_after)
+{
+ /* Shared random protocol has two phases, commit and reveal. */
+ int total_periods = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ int current_slot;
+
+ /* Split time into slots of size 'voting_interval'. See which slot we are
+ * currently into, and find which phase it corresponds to. */
+ current_slot = (valid_after / get_voting_interval()) % total_periods;
+
+ if (current_slot < SHARED_RANDOM_N_ROUNDS) {
+ return SR_PHASE_COMMIT;
+ } else {
+ return SR_PHASE_REVEAL;
+ }
+}
+
+/* Add the given <b>commit</b> to <b>state</b>. It MUST be a valid commit
+ * and there shouldn't be a commit from the same authority in the state
+ * already else verification hasn't been done prior. This takes ownership of
+ * the commit once in our state. */
+static void
+commit_add_to_state(sr_commit_t *commit, sr_state_t *state)
+{
+ sr_commit_t *saved_commit;
+
+ tor_assert(commit);
+ tor_assert(state);
+
+ saved_commit = digestmap_set(state->commits, commit->rsa_identity,
+ commit);
+ if (saved_commit != NULL) {
+ /* This means we already have that commit in our state so adding twice
+ * the same commit is either a code flow error, a corrupted disk state
+ * or some new unknown issue. */
+ log_warn(LD_DIR, "SR: Commit from %s exists in our state while "
+ "adding it: '%s'", sr_commit_get_rsa_fpr(commit),
+ commit->encoded_commit);
+ sr_commit_free(saved_commit);
+ }
+}
+
+/* Helper: deallocate a commit object. (Used with digestmap_free(), which
+ * requires a function pointer whose argument is void *). */
+static void
+commit_free_(void *p)
+{
+ sr_commit_free(p);
+}
+
+/* Free a state that was allocated with state_new(). */
+static void
+state_free(sr_state_t *state)
+{
+ if (state == NULL) {
+ return;
+ }
+ tor_free(state->fname);
+ digestmap_free(state->commits, commit_free_);
+ tor_free(state->current_srv);
+ tor_free(state->previous_srv);
+ tor_free(state);
+}
+
+/* Allocate an sr_state_t object and returns it. If no <b>fname</b>, the
+ * default file name is used. This function does NOT initialize the state
+ * timestamp, phase or shared random value. NULL is never returned. */
+static sr_state_t *
+state_new(const char *fname, time_t now)
+{
+ sr_state_t *new_state = tor_malloc_zero(sizeof(*new_state));
+ /* If file name is not provided, use default. */
+ if (fname == NULL) {
+ fname = default_fname;
+ }
+ new_state->fname = tor_strdup(fname);
+ new_state->version = SR_PROTO_VERSION;
+ new_state->commits = digestmap_new();
+ new_state->phase = get_sr_protocol_phase(now);
+ new_state->valid_until = get_state_valid_until_time(now);
+ return new_state;
+}
+
+/* Set our global state pointer with the one given. */
+static void
+state_set(sr_state_t *state)
+{
+ tor_assert(state);
+ if (sr_state != NULL) {
+ state_free(sr_state);
+ }
+ sr_state = state;
+}
+
+/* Free an allocated disk state. */
+static void
+disk_state_free(sr_disk_state_t *state)
+{
+ if (state == NULL) {
+ return;
+ }
+ config_free(&state_format, state);
+}
+
+/* Allocate a new disk state, initialize it and return it. */
+static sr_disk_state_t *
+disk_state_new(time_t now)
+{
+ sr_disk_state_t *new_state = tor_malloc_zero(sizeof(*new_state));
+
+ new_state->magic_ = SR_DISK_STATE_MAGIC;
+ new_state->Version = SR_PROTO_VERSION;
+ new_state->TorVersion = tor_strdup(get_version());
+ new_state->ValidUntil = get_state_valid_until_time(now);
+ new_state->ValidAfter = now;
+
+ /* Init config format. */
+ config_init(&state_format, new_state);
+ return new_state;
+}
+
+/* Set our global disk state with the given state. */
+static void
+disk_state_set(sr_disk_state_t *state)
+{
+ tor_assert(state);
+ if (sr_disk_state != NULL) {
+ disk_state_free(sr_disk_state);
+ }
+ sr_disk_state = state;
+}
+
+/* Return -1 if the disk state is invalid (something in there that we can't or
+ * shouldn't use). Return 0 if everything checks out. */
+static int
+disk_state_validate(const sr_disk_state_t *state)
+{
+ time_t now;
+
+ tor_assert(state);
+
+ /* Do we support the protocol version in the state or is it 0 meaning
+ * Version wasn't found in the state file or bad anyway ? */
+ if (state->Version == 0 || state->Version > SR_PROTO_VERSION) {
+ goto invalid;
+ }
+
+ /* If the valid until time is before now, we shouldn't use that state. */
+ now = time(NULL);
+ if (state->ValidUntil < now) {
+ log_info(LD_DIR, "SR: Disk state has expired. Ignoring it.");
+ goto invalid;
+ }
+
+ /* Make sure we don't have a valid after time that is earlier than a valid
+ * until time which would make things not work well. */
+ if (state->ValidAfter >= state->ValidUntil) {
+ log_info(LD_DIR, "SR: Disk state valid after/until times are invalid.");
+ goto invalid;
+ }
+
+ return 0;
+
+ invalid:
+ return -1;
+}
+
+/* Validate the disk state (NOP for now). */
+static int
+disk_state_validate_cb(void *old_state, void *state, void *default_state,
+ int from_setconf, char **msg)
+{
+ /* We don't use these; only options do. */
+ (void) from_setconf;
+ (void) default_state;
+ (void) old_state;
+
+ /* This is called by config_dump which is just before we are about to
+ * write it to disk. At that point, our global memory state has been
+ * copied to the disk state so it's fair to assume it's trustable. */
+ (void) state;
+ (void) msg;
+ return 0;
+}
+
+/* Parse the Commit line(s) in the disk state and translate them to the
+ * the memory state. Return 0 on success else -1 on error. */
+static int
+disk_state_parse_commits(sr_state_t *state,
+ const sr_disk_state_t *disk_state)
+{
+ config_line_t *line;
+ smartlist_t *args = NULL;
+
+ tor_assert(state);
+ tor_assert(disk_state);
+
+ for (line = disk_state->Commit; line; line = line->next) {
+ sr_commit_t *commit = NULL;
+
+ /* Extra safety. */
+ if (strcasecmp(line->key, dstate_commit_key) ||
+ line->value == NULL) {
+ /* Ignore any lines that are not commits. */
+ tor_fragile_assert();
+ continue;
+ }
+ args = smartlist_new();
+ smartlist_split_string(args, line->value, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ if (smartlist_len(args) < 3) {
+ log_warn(LD_BUG, "SR: Too few arguments in Commit Line: %s",
+ escaped(line->value));
+ goto error;
+ }
+ commit = sr_parse_commit(args);
+ if (commit == NULL) {
+ /* Ignore badly formed commit. It could also be a authority
+ * fingerprint that we don't know about so it shouldn't be used. */
+ continue;
+ }
+ /* We consider parseable commit from our disk state to be valid because
+ * they need to be in the first place to get in there. */
+ commit->valid = 1;
+ /* Add commit to our state pointer. */
+ commit_add_to_state(commit, state);
+
+ SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
+ smartlist_free(args);
+ }
+
+ return 0;
+
+ error:
+ SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
+ smartlist_free(args);
+ return -1;
+}
+
+/* Parse a share random value line from the disk state and save it to dst
+ * which is an allocated srv object. Return 0 on success else -1. */
+static int
+disk_state_parse_srv(const char *value, sr_srv_t *dst)
+{
+ int ret = -1;
+ smartlist_t *args;
+ sr_srv_t *srv;
+
+ tor_assert(value);
+ tor_assert(dst);
+
+ args = smartlist_new();
+ smartlist_split_string(args, value, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ if (smartlist_len(args) < 2) {
+ log_warn(LD_BUG, "SR: Too few arguments in shared random value. "
+ "Line: %s", escaped(value));
+ goto error;
+ }
+ srv = sr_parse_srv(args);
+ if (srv == NULL) {
+ goto error;
+ }
+ dst->num_reveals = srv->num_reveals;
+ memcpy(dst->value, srv->value, sizeof(dst->value));
+ tor_free(srv);
+ ret = 0;
+
+ error:
+ SMARTLIST_FOREACH(args, char *, s, tor_free(s));
+ smartlist_free(args);
+ return ret;
+}
+
+/* Parse both SharedRandCurrentValue and SharedRandPreviousValue line from
+ * the state. Return 0 on success else -1. */
+static int
+disk_state_parse_sr_values(sr_state_t *state,
+ const sr_disk_state_t *disk_state)
+{
+ /* Only one value per type (current or previous) is allowed so we keep
+ * track of it with these flag. */
+ unsigned int seen_previous = 0, seen_current = 0;
+ config_line_t *line;
+ sr_srv_t *srv = NULL;
+
+ tor_assert(state);
+ tor_assert(disk_state);
+
+ for (line = disk_state->SharedRandValues; line; line = line->next) {
+ if (line->value == NULL) {
+ continue;
+ }
+ srv = tor_malloc_zero(sizeof(*srv));
+ if (disk_state_parse_srv(line->value, srv) < 0) {
+ log_warn(LD_BUG, "SR: Broken current SRV line in state %s",
+ escaped(line->value));
+ goto bad;
+ }
+ if (!strcasecmp(line->key, dstate_prev_srv_key)) {
+ if (seen_previous) {
+ log_warn(LD_DIR, "SR: Second previous SRV value seen. Bad state");
+ goto bad;
+ }
+ state->previous_srv = srv;
+ seen_previous = 1;
+ } else if (!strcasecmp(line->key, dstate_cur_srv_key)) {
+ if (seen_current) {
+ log_warn(LD_DIR, "SR: Second current SRV value seen. Bad state");
+ goto bad;
+ }
+ state->current_srv = srv;
+ seen_current = 1;
+ } else {
+ /* Unknown key. Ignoring. */
+ tor_free(srv);
+ }
+ }
+
+ return 0;
+ bad:
+ tor_free(srv);
+ return -1;
+}
+
+/* Parse the given disk state and set a newly allocated state. On success,
+ * return that state else NULL. */
+static sr_state_t *
+disk_state_parse(const sr_disk_state_t *new_disk_state)
+{
+ sr_state_t *new_state = state_new(default_fname, time(NULL));
+
+ tor_assert(new_disk_state);
+
+ new_state->version = new_disk_state->Version;
+ new_state->valid_until = new_disk_state->ValidUntil;
+ new_state->valid_after = new_disk_state->ValidAfter;
+
+ /* Set our current phase according to the valid-after time in our disk
+ * state. The disk state we are parsing contains everything for the phase
+ * starting at valid_after so make sure our phase reflects that. */
+ new_state->phase = get_sr_protocol_phase(new_state->valid_after);
+
+ /* Parse the shared random values. */
+ if (disk_state_parse_sr_values(new_state, new_disk_state) < 0) {
+ goto error;
+ }
+ /* Parse the commits. */
+ if (disk_state_parse_commits(new_state, new_disk_state) < 0) {
+ goto error;
+ }
+ /* Great! This new state contains everything we had on disk. */
+ return new_state;
+
+ error:
+ state_free(new_state);
+ return NULL;
+}
+
+/* From a valid commit object and an allocated config line, set the line's
+ * value to the state string representation of a commit. */
+static void
+disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line)
+{
+ char *reveal_str = NULL;
+
+ tor_assert(commit);
+ tor_assert(line);
+
+ if (!tor_mem_is_zero(commit->encoded_reveal,
+ sizeof(commit->encoded_reveal))) {
+ /* Add extra whitespace so we can format the line correctly. */
+ tor_asprintf(&reveal_str, " %s", commit->encoded_reveal);
+ }
+ tor_asprintf(&line->value, "%u %s %s %s%s",
+ SR_PROTO_VERSION,
+ crypto_digest_algorithm_get_name(commit->alg),
+ sr_commit_get_rsa_fpr(commit),
+ commit->encoded_commit,
+ reveal_str != NULL ? reveal_str : "");
+ if (reveal_str != NULL) {
+ memwipe(reveal_str, 0, strlen(reveal_str));
+ tor_free(reveal_str);
+ }
+}
+
+/* From a valid srv object and an allocated config line, set the line's
+ * value to the state string representation of a shared random value. */
+static void
+disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line)
+{
+ char encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+
+ tor_assert(line);
+
+ /* No SRV value thus don't add the line. This is possible since we might
+ * not have a current or previous SRV value in our state. */
+ if (srv == NULL) {
+ return;
+ }
+ sr_srv_encode(encoded, sizeof(encoded), srv);
+ tor_asprintf(&line->value, "%" PRIu64 " %s", srv->num_reveals, encoded);
+}
+
+/* Reset disk state that is free allocated memory and zeroed the object. */
+static void
+disk_state_reset(void)
+{
+ /* Free allocated memory */
+ config_free_lines(sr_disk_state->Commit);
+ config_free_lines(sr_disk_state->SharedRandValues);
+ config_free_lines(sr_disk_state->ExtraLines);
+ tor_free(sr_disk_state->TorVersion);
+
+ /* Clean up the struct */
+ memset(sr_disk_state, 0, sizeof(*sr_disk_state));
+
+ /* Reset it with useful data */
+ sr_disk_state->magic_ = SR_DISK_STATE_MAGIC;
+ sr_disk_state->TorVersion = tor_strdup(get_version());
+}
+
+/* Update our disk state based on our global SR state. */
+static void
+disk_state_update(void)
+{
+ config_line_t **next, *line;
+
+ tor_assert(sr_disk_state);
+ tor_assert(sr_state);
+
+ /* Reset current disk state. */
+ disk_state_reset();
+
+ /* First, update elements that we don't need to do a construction. */
+ sr_disk_state->Version = sr_state->version;
+ sr_disk_state->ValidUntil = sr_state->valid_until;
+ sr_disk_state->ValidAfter = sr_state->valid_after;
+
+ /* Shared random values. */
+ next = &sr_disk_state->SharedRandValues;
+ if (sr_state->previous_srv != NULL) {
+ *next = line = tor_malloc_zero(sizeof(config_line_t));
+ line->key = tor_strdup(dstate_prev_srv_key);
+ disk_state_put_srv_line(sr_state->previous_srv, line);
+ /* Go to the next shared random value. */
+ next = &(line->next);
+ }
+ if (sr_state->current_srv != NULL) {
+ *next = line = tor_malloc_zero(sizeof(*line));
+ line->key = tor_strdup(dstate_cur_srv_key);
+ disk_state_put_srv_line(sr_state->current_srv, line);
+ }
+
+ /* Parse the commits and construct config line(s). */
+ next = &sr_disk_state->Commit;
+ DIGESTMAP_FOREACH(sr_state->commits, key, sr_commit_t *, commit) {
+ *next = line = tor_malloc_zero(sizeof(*line));
+ line->key = tor_strdup(dstate_commit_key);
+ disk_state_put_commit_line(commit, line);
+ next = &(line->next);
+ } DIGESTMAP_FOREACH_END;
+}
+
+/* Load state from disk and put it into our disk state. If the state passes
+ * validation, our global state will be updated with it. Return 0 on
+ * success. On error, -EINVAL is returned if the state on disk did contained
+ * something malformed or is unreadable. -ENOENT is returned indicating that
+ * the state file is either empty of non existing. */
+static int
+disk_state_load_from_disk(void)
+{
+ int ret;
+ char *fname;
+
+ fname = get_datadir_fname(default_fname);
+ ret = disk_state_load_from_disk_impl(fname);
+ tor_free(fname);
+
+ return ret;
+}
+
+/* Helper for disk_state_load_from_disk(). */
+STATIC int
+disk_state_load_from_disk_impl(const char *fname)
+{
+ int ret;
+ char *content = NULL;
+ sr_state_t *parsed_state = NULL;
+ sr_disk_state_t *disk_state = NULL;
+
+ /* Read content of file so we can parse it. */
+ if ((content = read_file_to_str(fname, 0, NULL)) == NULL) {
+ log_warn(LD_FS, "SR: Unable to read SR state file %s",
+ escaped(fname));
+ ret = -errno;
+ goto error;
+ }
+
+ {
+ config_line_t *lines = NULL;
+ char *errmsg = NULL;
+
+ /* Every error in this code path will return EINVAL. */
+ ret = -EINVAL;
+ if (config_get_lines(content, &lines, 0) < 0) {
+ config_free_lines(lines);
+ goto error;
+ }
+
+ disk_state = disk_state_new(time(NULL));
+ config_assign(&state_format, disk_state, lines, 0, &errmsg);
+ config_free_lines(lines);
+ if (errmsg) {
+ log_warn(LD_DIR, "SR: Reading state error: %s", errmsg);
+ tor_free(errmsg);
+ goto error;
+ }
+ }
+
+ /* So far so good, we've loaded our state file into our disk state. Let's
+ * validate it and then parse it. */
+ if (disk_state_validate(disk_state) < 0) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ parsed_state = disk_state_parse(disk_state);
+ if (parsed_state == NULL) {
+ ret = -EINVAL;
+ goto error;
+ }
+ state_set(parsed_state);
+ disk_state_set(disk_state);
+ tor_free(content);
+ log_info(LD_DIR, "SR: State loaded successfully from file %s", fname);
+ return 0;
+
+ error:
+ disk_state_free(disk_state);
+ tor_free(content);
+ return ret;
+}
+
+/* Save the disk state to disk but before that update it from the current
+ * state so we always have the latest. Return 0 on success else -1. */
+static int
+disk_state_save_to_disk(void)
+{
+ int ret;
+ char *state, *content = NULL, *fname = NULL;
+ char tbuf[ISO_TIME_LEN + 1];
+ time_t now = time(NULL);
+
+ /* If we didn't have the opportunity to setup an internal disk state,
+ * don't bother saving something to disk. */
+ if (sr_disk_state == NULL) {
+ ret = 0;
+ goto done;
+ }
+
+ /* Make sure that our disk state is up to date with our memory state
+ * before saving it to disk. */
+ disk_state_update();
+ state = config_dump(&state_format, NULL, sr_disk_state, 0, 0);
+ format_local_iso_time(tbuf, now);
+ tor_asprintf(&content,
+ "# Tor shared random state file last generated on %s "
+ "local time\n"
+ "# Other times below are in UTC\n"
+ "# Please *do not* edit this file.\n\n%s",
+ tbuf, state);
+ tor_free(state);
+ fname = get_datadir_fname(default_fname);
+ if (write_str_to_file(fname, content, 0) < 0) {
+ log_warn(LD_FS, "SR: Unable to write SR state to file %s", fname);
+ ret = -1;
+ goto done;
+ }
+ ret = 0;
+ log_debug(LD_DIR, "SR: Saved state to file %s", fname);
+
+ done:
+ tor_free(fname);
+ tor_free(content);
+ return ret;
+}
+
+/* Reset our state to prepare for a new protocol run. Once this returns, all
+ * commits in the state will be removed and freed. */
+STATIC void
+reset_state_for_new_protocol_run(time_t valid_after)
+{
+ tor_assert(sr_state);
+
+ /* Keep counters in track */
+ sr_state->n_reveal_rounds = 0;
+ sr_state->n_commit_rounds = 0;
+ sr_state->n_protocol_runs++;
+
+ /* Reset valid-until */
+ sr_state->valid_until = get_state_valid_until_time(valid_after);
+ sr_state->valid_after = valid_after;
+
+ /* We are in a new protocol run so cleanup commits. */
+ sr_state_delete_commits();
+}
+
+/* This is the first round of the new protocol run starting at
+ * <b>valid_after</b>. Do the necessary housekeeping. */
+STATIC void
+new_protocol_run(time_t valid_after)
+{
+ sr_commit_t *our_commitment = NULL;
+
+ /* Only compute the srv at the end of the reveal phase. */
+ if (sr_state->phase == SR_PHASE_REVEAL) {
+ /* We are about to compute a new shared random value that will be set in
+ * our state as the current value so rotate values. */
+ state_rotate_srv();
+ /* Compute the shared randomness value of the day. */
+ sr_compute_srv();
+ }
+
+ /* Prepare for the new protocol run by reseting the state */
+ reset_state_for_new_protocol_run(valid_after);
+
+ /* Do some logging */
+ log_info(LD_DIR, "SR: Protocol run #%" PRIu64 " starting!",
+ sr_state->n_protocol_runs);
+
+ /* Generate fresh commitments for this protocol run */
+ our_commitment = sr_generate_our_commit(valid_after,
+ get_my_v3_authority_cert());
+ if (our_commitment) {
+ /* Add our commitment to our state. In case we are unable to create one
+ * (highly unlikely), we won't vote for this protocol run since our
+ * commitment won't be in our state. */
+ sr_state_add_commit(our_commitment);
+ }
+}
+
+/* Return 1 iff the <b>next_phase</b> is a phase transition from the current
+ * phase that is it's different. */
+STATIC int
+is_phase_transition(sr_phase_t next_phase)
+{
+ return sr_state->phase != next_phase;
+}
+
+/* Helper function: return a commit using the RSA fingerprint of the
+ * authority or NULL if no such commit is known. */
+static sr_commit_t *
+state_query_get_commit(const char *rsa_fpr)
+{
+ tor_assert(rsa_fpr);
+ return digestmap_get(sr_state->commits, rsa_fpr);
+}
+
+/* Helper function: This handles the GET state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void *
+state_query_get_(sr_state_object_t obj_type, const void *data)
+{
+ void *obj = NULL;
+
+ switch (obj_type) {
+ case SR_STATE_OBJ_COMMIT:
+ {
+ obj = state_query_get_commit(data);
+ break;
+ }
+ case SR_STATE_OBJ_COMMITS:
+ obj = sr_state->commits;
+ break;
+ case SR_STATE_OBJ_CURSRV:
+ obj = sr_state->current_srv;
+ break;
+ case SR_STATE_OBJ_PREVSRV:
+ obj = sr_state->previous_srv;
+ break;
+ case SR_STATE_OBJ_PHASE:
+ obj = &sr_state->phase;
+ break;
+ case SR_STATE_OBJ_VALID_AFTER:
+ default:
+ tor_assert(0);
+ }
+ return obj;
+}
+
+/* Helper function: This handles the PUT state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void
+state_query_put_(sr_state_object_t obj_type, void *data)
+{
+ switch (obj_type) {
+ case SR_STATE_OBJ_COMMIT:
+ {
+ sr_commit_t *commit = data;
+ tor_assert(commit);
+ commit_add_to_state(commit, sr_state);
+ break;
+ }
+ case SR_STATE_OBJ_CURSRV:
+ sr_state->current_srv = (sr_srv_t *) data;
+ break;
+ case SR_STATE_OBJ_PREVSRV:
+ sr_state->previous_srv = (sr_srv_t *) data;
+ break;
+ case SR_STATE_OBJ_VALID_AFTER:
+ sr_state->valid_after = *((time_t *) data);
+ break;
+ /* It's not allowed to change the phase nor the full commitments map from
+ * the state. The phase is decided during a strict process post voting and
+ * the commits should be put individually. */
+ case SR_STATE_OBJ_PHASE:
+ case SR_STATE_OBJ_COMMITS:
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Helper function: This handles the DEL_ALL state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void
+state_query_del_all_(sr_state_object_t obj_type)
+{
+ switch (obj_type) {
+ case SR_STATE_OBJ_COMMIT:
+ {
+ /* We are in a new protocol run so cleanup commitments. */
+ DIGESTMAP_FOREACH_MODIFY(sr_state->commits, key, sr_commit_t *, c) {
+ sr_commit_free(c);
+ MAP_DEL_CURRENT(key);
+ } DIGESTMAP_FOREACH_END;
+ break;
+ }
+ /* The following object are _NOT_ suppose to be removed. */
+ case SR_STATE_OBJ_CURSRV:
+ case SR_STATE_OBJ_PREVSRV:
+ case SR_STATE_OBJ_PHASE:
+ case SR_STATE_OBJ_COMMITS:
+ case SR_STATE_OBJ_VALID_AFTER:
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Helper function: This handles the DEL state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void
+state_query_del_(sr_state_object_t obj_type, void *data)
+{
+ (void) data;
+
+ switch (obj_type) {
+ case SR_STATE_OBJ_PREVSRV:
+ tor_free(sr_state->previous_srv);
+ break;
+ case SR_STATE_OBJ_CURSRV:
+ tor_free(sr_state->current_srv);
+ break;
+ case SR_STATE_OBJ_COMMIT:
+ case SR_STATE_OBJ_COMMITS:
+ case SR_STATE_OBJ_PHASE:
+ case SR_STATE_OBJ_VALID_AFTER:
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Query state using an <b>action</b> for an object type <b>obj_type</b>.
+ * The <b>data</b> pointer needs to point to an object that the action needs
+ * to use and if anything is required to be returned, it is stored in
+ * <b>out</b>.
+ *
+ * This mechanism exists so we have one single point where we synchronized
+ * our memory state with our disk state for every actions that changes it.
+ * We then trigger a write on disk immediately.
+ *
+ * This should be the only entry point to our memory state. It's used by all
+ * our state accessors and should be in the future. */
+static void
+state_query(sr_state_action_t action, sr_state_object_t obj_type,
+ void *data, void **out)
+{
+ switch (action) {
+ case SR_STATE_ACTION_GET:
+ *out = state_query_get_(obj_type, data);
+ break;
+ case SR_STATE_ACTION_PUT:
+ state_query_put_(obj_type, data);
+ break;
+ case SR_STATE_ACTION_DEL:
+ state_query_del_(obj_type, data);
+ break;
+ case SR_STATE_ACTION_DEL_ALL:
+ state_query_del_all_(obj_type);
+ break;
+ case SR_STATE_ACTION_SAVE:
+ /* Only trigger a disk state save. */
+ break;
+ default:
+ tor_assert(0);
+ }
+
+ /* If the action actually changes the state, immediately save it to disk.
+ * The following will sync the state -> disk state and then save it. */
+ if (action != SR_STATE_ACTION_GET) {
+ disk_state_save_to_disk();
+ }
+}
+
+/* Delete the current SRV value from the state freeing it and the value is set
+ * to NULL meaning empty. */
+static void
+state_del_current_srv(void)
+{
+ state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_CURSRV, NULL, NULL);
+}
+
+/* Delete the previous SRV value from the state freeing it and the value is
+ * set to NULL meaning empty. */
+static void
+state_del_previous_srv(void)
+{
+ state_query(SR_STATE_ACTION_DEL, SR_STATE_OBJ_PREVSRV, NULL, NULL);
+}
+
+/* Rotate SRV value by freeing the previous value, assigning the current
+ * value to the previous one and nullifying the current one. */
+STATIC void
+state_rotate_srv(void)
+{
+ /* First delete previous SRV from the state. Object will be freed. */
+ state_del_previous_srv();
+ /* Set previous SRV with the current one. */
+ sr_state_set_previous_srv(sr_state_get_current_srv());
+ /* Nullify the current srv. */
+ sr_state_set_current_srv(NULL);
+}
+
+/* Set valid after time in the our state. */
+void
+sr_state_set_valid_after(time_t valid_after)
+{
+ state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_VALID_AFTER,
+ (void *) &valid_after, NULL);
+}
+
+/* Return the phase we are currently in according to our state. */
+sr_phase_t
+sr_state_get_phase(void)
+{
+ void *ptr;
+ state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PHASE, NULL, &ptr);
+ return *(sr_phase_t *) ptr;
+}
+
+/* Return the previous SRV value from our state. Value CAN be NULL. */
+const sr_srv_t *
+sr_state_get_previous_srv(void)
+{
+ const sr_srv_t *srv;
+ state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PREVSRV, NULL,
+ (void *) &srv);
+ return srv;
+}
+
+/* Set the current SRV value from our state. Value CAN be NULL. The srv
+ * object ownership is transfered to the state object. */
+void
+sr_state_set_previous_srv(const sr_srv_t *srv)
+{
+ state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_PREVSRV, (void *) srv,
+ NULL);
+}
+
+/* Return the current SRV value from our state. Value CAN be NULL. */
+const sr_srv_t *
+sr_state_get_current_srv(void)
+{
+ const sr_srv_t *srv;
+ state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_CURSRV, NULL,
+ (void *) &srv);
+ return srv;
+}
+
+/* Set the current SRV value from our state. Value CAN be NULL. The srv
+ * object ownership is transfered to the state object. */
+void
+sr_state_set_current_srv(const sr_srv_t *srv)
+{
+ state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_CURSRV, (void *) srv,
+ NULL);
+}
+
+/* Clean all the SRVs in our state. */
+void
+sr_state_clean_srvs(void)
+{
+ /* Remove SRVs from state. They will be set to NULL as "empty". */
+ state_del_previous_srv();
+ state_del_current_srv();
+}
+
+/* Return a pointer to the commits map from our state. CANNOT be NULL. */
+digestmap_t *
+sr_state_get_commits(void)
+{
+ digestmap_t *commits;
+ state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMITS,
+ NULL, (void *) &commits);
+ tor_assert(commits);
+ return commits;
+}
+
+/* Update the current SR state as needed for the upcoming voting round at
+ * <b>valid_after</b>. */
+void
+sr_state_update(time_t valid_after)
+{
+ sr_phase_t next_phase;
+
+ tor_assert(sr_state);
+
+ /* Don't call this function twice in the same voting period. */
+ if (valid_after <= sr_state->valid_after) {
+ log_info(LD_DIR, "SR: Asked to update state twice. Ignoring.");
+ return;
+ }
+
+ /* Get phase of upcoming round. */
+ next_phase = get_sr_protocol_phase(valid_after);
+
+ /* If we are transitioning to a new protocol phase, prepare the stage. */
+ if (is_phase_transition(next_phase)) {
+ if (next_phase == SR_PHASE_COMMIT) {
+ /* Going into commit phase means we are starting a new protocol run. */
+ new_protocol_run(valid_after);
+ }
+ /* Set the new phase for this round */
+ sr_state->phase = next_phase;
+ } else if (sr_state->phase == SR_PHASE_COMMIT &&
+ digestmap_size(sr_state->commits) == 0) {
+ /* We are _NOT_ in a transition phase so if we are in the commit phase
+ * and have no commit, generate one. Chances are that we are booting up
+ * so let's have a commit in our state for the next voting period. */
+ sr_commit_t *our_commit =
+ sr_generate_our_commit(valid_after, get_my_v3_authority_cert());
+ if (our_commit) {
+ /* Add our commitment to our state. In case we are unable to create one
+ * (highly unlikely), we won't vote for this protocol run since our
+ * commitment won't be in our state. */
+ sr_state_add_commit(our_commit);
+ }
+ }
+
+ sr_state_set_valid_after(valid_after);
+
+ /* Count the current round */
+ if (sr_state->phase == SR_PHASE_COMMIT) {
+ /* invariant check: we've not entered reveal phase yet */
+ tor_assert(sr_state->n_reveal_rounds == 0);
+ sr_state->n_commit_rounds++;
+ } else {
+ sr_state->n_reveal_rounds++;
+ }
+
+ { /* Debugging. */
+ char tbuf[ISO_TIME_LEN + 1];
+ format_iso_time(tbuf, valid_after);
+ log_info(LD_DIR, "SR: State prepared for upcoming voting period (%s). "
+ "Upcoming phase is %s (counters: %d commit & %d reveal rounds).",
+ tbuf, get_phase_str(sr_state->phase),
+ sr_state->n_commit_rounds, sr_state->n_reveal_rounds);
+ }
+}
+
+/* Return commit object from the given authority digest <b>rsa_identity</b>.
+ * Return NULL if not found. */
+sr_commit_t *
+sr_state_get_commit(const char *rsa_identity)
+{
+ sr_commit_t *commit;
+
+ tor_assert(rsa_identity);
+
+ state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMIT,
+ (void *) rsa_identity, (void *) &commit);
+ return commit;
+}
+
+/* Add <b>commit</b> to the permanent state. The commit object ownership is
+ * transfered to the state so the caller MUST not free it. */
+void
+sr_state_add_commit(sr_commit_t *commit)
+{
+ tor_assert(commit);
+
+ /* Put the commit to the global state. */
+ state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_COMMIT,
+ (void *) commit, NULL);
+
+ log_debug(LD_DIR, "SR: Commit from %s has been added to our state.",
+ sr_commit_get_rsa_fpr(commit));
+}
+
+/* Remove all commits from our state. */
+void
+sr_state_delete_commits(void)
+{
+ state_query(SR_STATE_ACTION_DEL_ALL, SR_STATE_OBJ_COMMIT, NULL, NULL);
+}
+
+/* Copy the reveal information from <b>commit</b> into <b>saved_commit</b>.
+ * This <b>saved_commit</b> MUST come from our current SR state. Once modified,
+ * the disk state is updated. */
+void
+sr_state_copy_reveal_info(sr_commit_t *saved_commit, const sr_commit_t *commit)
+{
+ tor_assert(saved_commit);
+ tor_assert(commit);
+
+ saved_commit->reveal_ts = commit->reveal_ts;
+ memcpy(saved_commit->random_number, commit->random_number,
+ sizeof(saved_commit->random_number));
+
+ strlcpy(saved_commit->encoded_reveal, commit->encoded_reveal,
+ sizeof(saved_commit->encoded_reveal));
+ state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL);
+ log_debug(LD_DIR, "SR: Reveal value learned %s (for commit %s) from %s",
+ saved_commit->encoded_reveal, saved_commit->encoded_commit,
+ sr_commit_get_rsa_fpr(saved_commit));
+}
+
+/* Set the fresh SRV flag from our state. This doesn't need to trigger a
+ * disk state synchronization so we directly change the state. */
+void
+sr_state_set_fresh_srv(void)
+{
+ sr_state->is_srv_fresh = 1;
+}
+
+/* Unset the fresh SRV flag from our state. This doesn't need to trigger a
+ * disk state synchronization so we directly change the state. */
+void
+sr_state_unset_fresh_srv(void)
+{
+ sr_state->is_srv_fresh = 0;
+}
+
+/* Return the value of the fresh SRV flag. */
+unsigned int
+sr_state_srv_is_fresh(void)
+{
+ return sr_state->is_srv_fresh;
+}
+
+/* Cleanup and free our disk and memory state. */
+void
+sr_state_free(void)
+{
+ state_free(sr_state);
+ disk_state_free(sr_disk_state);
+ /* Nullify our global state. */
+ sr_state = NULL;
+ sr_disk_state = NULL;
+}
+
+/* Save our current state in memory to disk. */
+void
+sr_state_save(void)
+{
+ /* Query a SAVE action on our current state so it's synced and saved. */
+ state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL);
+}
+
+/* Return 1 iff the state has been initialized that is it exists in memory.
+ * Return 0 otherwise. */
+int
+sr_state_is_initialized(void)
+{
+ return sr_state == NULL ? 0 : 1;
+}
+
+/* Initialize the disk and memory state.
+ *
+ * If save_to_disk is set to 1, the state is immediately saved to disk after
+ * creation else it's not thus only kept in memory.
+ * If read_from_disk is set to 1, we try to load the state from the disk and
+ * if not found, a new state is created.
+ *
+ * Return 0 on success else a negative value on error. */
+int
+sr_state_init(int save_to_disk, int read_from_disk)
+{
+ int ret = -ENOENT;
+ time_t now = time(NULL);
+
+ /* We shouldn't have those assigned. */
+ tor_assert(sr_disk_state == NULL);
+ tor_assert(sr_state == NULL);
+
+ /* First, try to load the state from disk. */
+ if (read_from_disk) {
+ ret = disk_state_load_from_disk();
+ }
+
+ if (ret < 0) {
+ switch (-ret) {
+ case EINVAL:
+ /* We have a state on disk but it contains something we couldn't parse
+ * or an invalid entry in the state file. Let's remove it since it's
+ * obviously unusable and replace it by an new fresh state below. */
+ case ENOENT:
+ {
+ /* No state on disk so allocate our states for the first time. */
+ sr_state_t *new_state = state_new(default_fname, now);
+ sr_disk_state_t *new_disk_state = disk_state_new(now);
+ state_set(new_state);
+ /* It's important to set our disk state pointer since the save call
+ * below uses it to synchronized it with our memory state. */
+ disk_state_set(new_disk_state);
+ /* No entry, let's save our new state to disk. */
+ if (save_to_disk && disk_state_save_to_disk() < 0) {
+ goto error;
+ }
+ break;
+ }
+ default:
+ /* Big problem. Not possible. */
+ tor_assert(0);
+ }
+ }
+ /* 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);
+ sr_state_update(valid_after);
+ }
+ return 0;
+
+ error:
+ return -1;
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Set the current phase of the protocol. Used only by unit tests. */
+void
+set_sr_phase(sr_phase_t phase)
+{
+ tor_assert(sr_state);
+ sr_state->phase = phase;
+}
+
+/* Get the SR state. Used only by unit tests */
+sr_state_t *
+get_sr_state(void)
+{
+ return sr_state;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h
new file mode 100644
index 0000000000..43a7f1d284
--- /dev/null
+++ b/src/or/shared_random_state.h
@@ -0,0 +1,147 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SHARED_RANDOM_STATE_H
+#define TOR_SHARED_RANDOM_STATE_H
+
+#include "shared_random.h"
+
+/* Action that can be performed on the state for any objects. */
+typedef enum {
+ SR_STATE_ACTION_GET = 1,
+ SR_STATE_ACTION_PUT = 2,
+ SR_STATE_ACTION_DEL = 3,
+ SR_STATE_ACTION_DEL_ALL = 4,
+ SR_STATE_ACTION_SAVE = 5,
+} sr_state_action_t;
+
+/* Object in the state that can be queried through the state API. */
+typedef enum {
+ /* Will return a single commit using an authority identity key. */
+ SR_STATE_OBJ_COMMIT,
+ /* Returns the entire list of commits from the state. */
+ SR_STATE_OBJ_COMMITS,
+ /* Return the current SRV object pointer. */
+ SR_STATE_OBJ_CURSRV,
+ /* Return the previous SRV object pointer. */
+ SR_STATE_OBJ_PREVSRV,
+ /* Return the phase. */
+ SR_STATE_OBJ_PHASE,
+ /* Get or Put the valid after time. */
+ SR_STATE_OBJ_VALID_AFTER,
+} sr_state_object_t;
+
+/* State of the protocol. It's also saved on disk in fname. This data
+ * structure MUST be synchronized at all time with the one on disk. */
+typedef struct sr_state_t {
+ /* Filename of the state file on disk. */
+ char *fname;
+ /* Version of the protocol. */
+ uint32_t version;
+ /* The valid-after of the voting period we have prepared the state for. */
+ time_t valid_after;
+ /* Until when is this state valid? */
+ time_t valid_until;
+ /* Protocol phase. */
+ sr_phase_t phase;
+
+ /* Number of runs completed. */
+ uint64_t n_protocol_runs;
+ /* The number of commitment rounds we've performed in this protocol run. */
+ unsigned int n_commit_rounds;
+ /* The number of reveal rounds we've performed in this protocol run. */
+ unsigned int n_reveal_rounds;
+
+ /* A map of all the received commitments for this protocol run. This is
+ * indexed by authority RSA identity digest. */
+ digestmap_t *commits;
+
+ /* Current and previous shared random value. */
+ sr_srv_t *previous_srv;
+ sr_srv_t *current_srv;
+
+ /* Indicate if the state contains an SRV that was _just_ generated. This is
+ * used during voting so that we know whether to use the super majority rule
+ * or not when deciding on keeping it for the consensus. It is _always_ set
+ * to 0 post consensus.
+ *
+ * EDGE CASE: if an authority computes a new SRV then immediately reboots
+ * and, once back up, votes for the current round, it won't know if the
+ * SRV is fresh or not ultimately making it _NOT_ use the super majority
+ * when deciding to put or not the SRV in the consensus. This is for now
+ * an acceptable very rare edge case. */
+ unsigned int is_srv_fresh:1;
+} sr_state_t;
+
+/* Persistent state of the protocol, as saved to disk. */
+typedef struct sr_disk_state_t {
+ uint32_t magic_;
+ /* Version of the protocol. */
+ uint32_t Version;
+ /* Version of our running tor. */
+ char *TorVersion;
+ /* Creation time of this state */
+ time_t ValidAfter;
+ /* State valid until? */
+ time_t ValidUntil;
+ /* All commits seen that are valid. */
+ config_line_t *Commit;
+ /* Previous and current shared random value. */
+ config_line_t *SharedRandValues;
+ /* Extra Lines for configuration we might not know. */
+ config_line_t *ExtraLines;
+} sr_disk_state_t;
+
+/* API */
+
+/* Public methods: */
+
+void sr_state_update(time_t valid_after);
+
+/* Private methods (only used by shared-random.c): */
+
+void sr_state_set_valid_after(time_t valid_after);
+sr_phase_t sr_state_get_phase(void);
+const sr_srv_t *sr_state_get_previous_srv(void);
+const sr_srv_t *sr_state_get_current_srv(void);
+void sr_state_set_previous_srv(const sr_srv_t *srv);
+void sr_state_set_current_srv(const sr_srv_t *srv);
+void sr_state_clean_srvs(void);
+digestmap_t *sr_state_get_commits(void);
+sr_commit_t *sr_state_get_commit(const char *rsa_fpr);
+void sr_state_add_commit(sr_commit_t *commit);
+void sr_state_delete_commits(void);
+void sr_state_copy_reveal_info(sr_commit_t *saved_commit,
+ const sr_commit_t *commit);
+unsigned int sr_state_srv_is_fresh(void);
+void sr_state_set_fresh_srv(void);
+void sr_state_unset_fresh_srv(void);
+int sr_state_init(int save_to_disk, int read_from_disk);
+int sr_state_is_initialized(void);
+void sr_state_save(void);
+void sr_state_free(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_state_valid_until_time(time_t now);
+STATIC const char *get_phase_str(sr_phase_t phase);
+STATIC void reset_state_for_new_protocol_run(time_t valid_after);
+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 */
+
+#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 /* TOR_SHARED_RANDOM_STATE_H */
+
diff --git a/src/or/statefile.c b/src/or/statefile.c
index 9594d9cec3..8fa4324b25 100644
--- a/src/or/statefile.c
+++ b/src/or/statefile.c
@@ -9,6 +9,23 @@
*
* \brief Handles parsing and encoding the persistent 'state' file that carries
* miscellaneous persistent state between Tor invocations.
+ *
+ * This 'state' file is a typed key-value store that allows multiple
+ * entries for the same key. It follows the same metaformat as described
+ * in confparse.c, and uses the same code to read and write itself.
+ *
+ * The state file is most suitable for small values that don't change too
+ * frequently. For values that become very large, we typically use a separate
+ * file -- for example, see how we handle microdescriptors, by storing them in
+ * a separate file with a journal.
+ *
+ * The current state is accessed via get_or_state(), which returns a singleton
+ * or_state_t object. Functions that change it should call
+ * or_state_mark_dirty() to ensure that it will get written to disk.
+ *
+ * The or_state_save() function additionally calls various functioens
+ * throughout Tor that might want to flush more state to the the disk,
+ * including some in rephist.c, entrynodes.c, circuitstats.c, hibernate.c.
*/
#define STATEFILE_PRIVATE
@@ -121,6 +138,7 @@ static const config_format_t state_format = {
OR_STATE_MAGIC,
STRUCT_OFFSET(or_state_t, magic_),
state_abbrevs_,
+ NULL,
state_vars_,
or_state_validate_cb,
&state_extra_var,
@@ -349,7 +367,7 @@ or_state_load(void)
if (config_get_lines(contents, &lines, 0)<0)
goto done;
assign_retval = config_assign(&state_format, new_state,
- lines, 0, 0, &errmsg);
+ lines, 0, &errmsg);
config_free_lines(lines);
if (assign_retval<0)
badstate = 1;
diff --git a/src/or/status.c b/src/or/status.c
index 749cee4edf..fce6a10157 100644
--- a/src/or/status.c
+++ b/src/or/status.c
@@ -3,7 +3,13 @@
/**
* \file status.c
- * \brief Keep status information and log the heartbeat messages.
+ * \brief Collect status information and log heartbeat messages.
+ *
+ * This module is responsible for implementing the heartbeat log messages,
+ * which periodically inform users and operators about basic facts to
+ * do with their Tor instance. The log_heartbeat() function, invoked from
+ * main.c, is the principle entry point. It collects data from elsewhere
+ * in Tor, and logs it in a human-readable format.
**/
#define STATUS_PRIVATE
diff --git a/src/or/tor_main.c b/src/or/tor_main.c
index ac32eef559..d67eda2ac9 100644
--- a/src/or/tor_main.c
+++ b/src/or/tor_main.c
@@ -3,6 +3,8 @@
* Copyright (c) 2007-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
+extern const char tor_git_revision[];
+
/** String describing which Tor Git repository version the source was
* built from. This string is generated by a bit of shell kludging in
* src/or/include.am, and is usually right.
@@ -15,8 +17,10 @@ const char tor_git_revision[] =
/**
* \file tor_main.c
- * \brief Stub module containing a main() function. Allows unit
- * test binary to link against main.c.
+ * \brief Stub module containing a main() function.
+ *
+ * We keep the main function in a separate module so that the unit
+ * tests, which have their own main()s, can link against main.c.
**/
int tor_main(int argc, char *argv[]);
diff --git a/src/or/transports.c b/src/or/transports.c
index 1b8b1e678c..7a52b737e4 100644
--- a/src/or/transports.c
+++ b/src/or/transports.c
@@ -1270,7 +1270,7 @@ get_transport_options_for_server_proxy(const managed_proxy_t *mp)
/** Return the string that tor should place in TOR_PT_SERVER_BINDADDR
* while configuring the server managed proxy in <b>mp</b>. The
- * string is stored in the heap, and it's the the responsibility of
+ * string is stored in the heap, and it's the responsibility of
* the caller to deallocate it after its use. */
static char *
get_bindaddr_for_server_proxy(const managed_proxy_t *mp)
@@ -1363,7 +1363,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
}
}
- /* XXX024 Remove the '=' here once versions of obfsproxy which
+ /* XXXX Remove the '=' here once versions of obfsproxy which
* assert that this env var exists are sufficiently dead.
*
* (If we remove this line entirely, some joker will stick this
@@ -1425,7 +1425,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
*
* Requires that proxy_argv have at least one element. */
STATIC managed_proxy_t *
-managed_proxy_create(const smartlist_t *transport_list,
+managed_proxy_create(const smartlist_t *with_transport_list,
char **proxy_argv, int is_server)
{
managed_proxy_t *mp = tor_malloc_zero(sizeof(managed_proxy_t));
@@ -1436,7 +1436,7 @@ managed_proxy_create(const smartlist_t *transport_list,
mp->proxy_uri = get_pt_proxy_uri();
mp->transports_to_launch = smartlist_new();
- SMARTLIST_FOREACH(transport_list, const char *, transport,
+ SMARTLIST_FOREACH(with_transport_list, const char *, transport,
add_transport_to_proxy(transport, mp));
/* register the managed proxy */
@@ -1460,7 +1460,7 @@ managed_proxy_create(const smartlist_t *transport_list,
* elements, containing at least one element.
**/
MOCK_IMPL(void,
-pt_kickstart_proxy, (const smartlist_t *transport_list,
+pt_kickstart_proxy, (const smartlist_t *with_transport_list,
char **proxy_argv, int is_server))
{
managed_proxy_t *mp=NULL;
@@ -1473,7 +1473,7 @@ pt_kickstart_proxy, (const smartlist_t *transport_list,
mp = get_managed_proxy_by_argv_and_type(proxy_argv, is_server);
if (!mp) { /* we haven't seen this proxy before */
- managed_proxy_create(transport_list, proxy_argv, is_server);
+ managed_proxy_create(with_transport_list, proxy_argv, is_server);
} else { /* known proxy. add its transport to its transport list */
if (mp->was_around_before_config_read) {
@@ -1490,14 +1490,14 @@ pt_kickstart_proxy, (const smartlist_t *transport_list,
/* For each new transport, check if the managed proxy used to
support it before the SIGHUP. If that was the case, make sure
it doesn't get removed because we might reuse it. */
- SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport) {
+ SMARTLIST_FOREACH_BEGIN(with_transport_list, const char *, transport) {
old_transport = transport_get_by_name(transport);
if (old_transport)
old_transport->marked_for_removal = 0;
} SMARTLIST_FOREACH_END(transport);
}
- SMARTLIST_FOREACH(transport_list, const char *, transport,
+ SMARTLIST_FOREACH(with_transport_list, const char *, transport,
add_transport_to_proxy(transport, mp));
free_execve_args(proxy_argv);
}
@@ -1611,7 +1611,7 @@ pt_get_extra_info_descriptor_string(void)
uint32_t external_ip_address = 0;
if (tor_addr_is_null(&t->addr) &&
router_pick_published_address(get_options(),
- &external_ip_address) >= 0) {
+ &external_ip_address, 0) >= 0) {
tor_addr_t addr;
tor_addr_from_ipv4h(&addr, external_ip_address);
addrport = fmt_addrport(&addr, t->port);