summaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
Diffstat (limited to 'src/or')
-rw-r--r--src/or/Makefile.nmake1
-rw-r--r--src/or/addressmap.c50
-rw-r--r--src/or/addressmap.h2
-rw-r--r--src/or/bridges.c889
-rw-r--r--src/or/bridges.h70
-rw-r--r--src/or/buffers.c785
-rw-r--r--src/or/buffers.h60
-rw-r--r--src/or/channel.c802
-rw-r--r--src/or/channel.h155
-rw-r--r--src/or/channelpadding.c759
-rw-r--r--src/or/channelpadding.h40
-rw-r--r--src/or/channeltls.c543
-rw-r--r--src/or/channeltls.h13
-rw-r--r--src/or/circpathbias.c337
-rw-r--r--src/or/circpathbias.h2
-rw-r--r--src/or/circuitbuild.c1195
-rw-r--r--src/or/circuitbuild.h39
-rw-r--r--src/or/circuitlist.c559
-rw-r--r--src/or/circuitlist.h14
-rw-r--r--src/or/circuitmux.c93
-rw-r--r--src/or/circuitmux.h2
-rw-r--r--src/or/circuitmux_ewma.c39
-rw-r--r--src/or/circuitmux_ewma.h7
-rw-r--r--src/or/circuitstats.c86
-rw-r--r--src/or/circuitstats.h7
-rw-r--r--src/or/circuituse.c557
-rw-r--r--src/or/circuituse.h24
-rw-r--r--src/or/command.c48
-rw-r--r--src/or/command.h2
-rw-r--r--src/or/config.c1600
-rw-r--r--src/or/config.h39
-rw-r--r--src/or/confparse.c238
-rw-r--r--src/or/confparse.h34
-rw-r--r--src/or/connection.c948
-rw-r--r--src/or/connection.h76
-rw-r--r--src/or/connection_edge.c598
-rw-r--r--src/or/connection_edge.h7
-rw-r--r--src/or/connection_or.c1009
-rw-r--r--src/or/connection_or.h37
-rw-r--r--src/or/conscache.c541
-rw-r--r--src/or/conscache.h61
-rw-r--r--src/or/consdiff.c1412
-rw-r--r--src/or/consdiff.h98
-rw-r--r--src/or/consdiffmgr.c1878
-rw-r--r--src/or/consdiffmgr.h74
-rw-r--r--src/or/control.c1039
-rw-r--r--src/or/control.h38
-rw-r--r--src/or/cpuworker.c50
-rw-r--r--src/or/cpuworker.h10
-rw-r--r--src/or/dircollate.c44
-rw-r--r--src/or/dircollate.h2
-rw-r--r--src/or/directory.c4570
-rw-r--r--src/or/directory.h166
-rw-r--r--src/or/dirserv.c1327
-rw-r--r--src/or/dirserv.h98
-rw-r--r--src/or/dirvote.c608
-rw-r--r--src/or/dirvote.h69
-rw-r--r--src/or/dns.c194
-rw-r--r--src/or/dns.h16
-rw-r--r--src/or/dns_structs.h12
-rw-r--r--src/or/dnsserv.c34
-rw-r--r--src/or/dnsserv.h2
-rw-r--r--src/or/entrynodes.c5221
-rw-r--r--src/or/entrynodes.h604
-rw-r--r--src/or/eventdns_tor.h22
-rw-r--r--src/or/ext_orport.c23
-rw-r--r--src/or/ext_orport.h2
-rw-r--r--src/or/fallback_dirs.inc274
-rw-r--r--src/or/fp_pair.c10
-rw-r--r--src/or/fp_pair.h2
-rw-r--r--src/or/geoip.c53
-rw-r--r--src/or/geoip.h2
-rw-r--r--src/or/hibernate.c38
-rw-r--r--src/or/hibernate.h2
-rw-r--r--src/or/hs_cache.c400
-rw-r--r--src/or/hs_cache.h63
-rw-r--r--src/or/hs_cell.c584
-rw-r--r--src/or/hs_cell.h75
-rw-r--r--src/or/hs_circuit.c1056
-rw-r--r--src/or/hs_circuit.h58
-rw-r--r--src/or/hs_circuitmap.c490
-rw-r--r--src/or/hs_circuitmap.h103
-rw-r--r--src/or/hs_client.c48
-rw-r--r--src/or/hs_client.h16
-rw-r--r--src/or/hs_common.c1369
-rw-r--r--src/or/hs_common.h244
-rw-r--r--src/or/hs_config.c582
-rw-r--r--src/or/hs_config.h24
-rw-r--r--src/or/hs_descriptor.c2546
-rw-r--r--src/or/hs_descriptor.h254
-rw-r--r--src/or/hs_ident.c88
-rw-r--r--src/or/hs_ident.h130
-rw-r--r--src/or/hs_intropoint.c609
-rw-r--r--src/or/hs_intropoint.h79
-rw-r--r--src/or/hs_ntor.c618
-rw-r--r--src/or/hs_ntor.h67
-rw-r--r--src/or/hs_service.c3087
-rw-r--r--src/or/hs_service.h346
-rw-r--r--src/or/include.am63
-rw-r--r--src/or/keypin.c24
-rw-r--r--src/or/keypin.h2
-rw-r--r--src/or/main.c759
-rw-r--r--src/or/main.h19
-rw-r--r--src/or/microdesc.c40
-rw-r--r--src/or/microdesc.h6
-rw-r--r--src/or/networkstatus.c839
-rw-r--r--src/or/networkstatus.h54
-rw-r--r--src/or/nodelist.c348
-rw-r--r--src/or/nodelist.h15
-rw-r--r--src/or/ntmain.c22
-rw-r--r--src/or/ntmain.h2
-rw-r--r--src/or/onion.c399
-rw-r--r--src/or/onion.h4
-rw-r--r--src/or/onion_fast.c28
-rw-r--r--src/or/onion_fast.h2
-rw-r--r--src/or/onion_ntor.c20
-rw-r--r--src/or/onion_ntor.h2
-rw-r--r--src/or/onion_tap.c33
-rw-r--r--src/or/onion_tap.h2
-rw-r--r--src/or/or.h762
-rw-r--r--src/or/parsecommon.c450
-rw-r--r--src/or/parsecommon.h322
-rw-r--r--src/or/periodic.c10
-rw-r--r--src/or/periodic.h2
-rw-r--r--src/or/policies.c436
-rw-r--r--src/or/policies.h17
-rw-r--r--src/or/protover.c737
-rw-r--r--src/or/protover.h79
-rw-r--r--src/or/reasons.c8
-rw-r--r--src/or/reasons.h2
-rw-r--r--src/or/relay.c266
-rw-r--r--src/or/relay.h9
-rw-r--r--src/or/rendcache.c97
-rw-r--r--src/or/rendcache.h22
-rw-r--r--src/or/rendclient.c407
-rw-r--r--src/or/rendclient.h7
-rw-r--r--src/or/rendcommon.c338
-rw-r--r--src/or/rendcommon.h48
-rw-r--r--src/or/rendmid.c86
-rw-r--r--src/or/rendmid.h10
-rw-r--r--src/or/rendservice.c2095
-rw-r--r--src/or/rendservice.h106
-rw-r--r--src/or/rephist.c549
-rw-r--r--src/or/rephist.h39
-rw-r--r--src/or/replaycache.c16
-rw-r--r--src/or/replaycache.h2
-rw-r--r--src/or/router.c644
-rw-r--r--src/or/router.h14
-rw-r--r--src/or/routerkeys.c338
-rw-r--r--src/or/routerkeys.h16
-rw-r--r--src/or/routerlist.c912
-rw-r--r--src/or/routerlist.h52
-rw-r--r--src/or/routerparse.c2030
-rw-r--r--src/or/routerparse.h45
-rw-r--r--src/or/routerset.c37
-rw-r--r--src/or/routerset.h7
-rw-r--r--src/or/scheduler.c143
-rw-r--r--src/or/scheduler.h9
-rw-r--r--src/or/shared_random.c1451
-rw-r--r--src/or/shared_random.h172
-rw-r--r--src/or/shared_random_state.c1395
-rw-r--r--src/or/shared_random_state.h152
-rw-r--r--src/or/statefile.c32
-rw-r--r--src/or/statefile.h2
-rw-r--r--src/or/status.c10
-rw-r--r--src/or/status.h2
-rw-r--r--src/or/tor_main.c10
-rw-r--r--src/or/torcert.c416
-rw-r--r--src/or/torcert.h44
-rw-r--r--src/or/transports.c27
-rw-r--r--src/or/transports.h2
171 files changed, 47662 insertions, 13203 deletions
diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake
index 2ac98cd372..429ae67858 100644
--- a/src/or/Makefile.nmake
+++ b/src/or/Makefile.nmake
@@ -14,6 +14,7 @@ LIBTOR_OBJECTS = \
addressmap.obj \
buffers.obj \
channel.obj \
+ channelpadding.obj \
channeltls.obj \
circpathbias.obj \
circuitbuild.obj \
diff --git a/src/or/addressmap.c b/src/or/addressmap.c
index 047a863ef5..c92af38254 100644
--- a/src/or/addressmap.c
+++ b/src/or/addressmap.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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);
}
@@ -376,29 +376,38 @@ addressmap_rewrite(char *address, size_t maxlen,
char *addr_orig = tor_strdup(address);
char *log_addr_orig = NULL;
+ /* We use a loop here to limit the total number of rewrites we do,
+ * so that we can't hit an infinite loop. */
for (rewrites = 0; rewrites < 16; rewrites++) {
int exact_match = 0;
log_addr_orig = tor_strdup(escaped_safe_str_client(address));
+ /* First check to see if there's an exact match for this address */
ent = strmap_get(addressmap, address);
if (!ent || !ent->new_address) {
+ /* And if we don't have an exact match, try to check whether
+ * we have a pattern-based match.
+ */
ent = addressmap_match_superdomains(address);
} else {
if (ent->src_wildcard && !ent->dst_wildcard &&
!strcasecmp(address, ent->new_address)) {
- /* This is a rule like *.example.com example.com, and we just got
- * "example.com" */
+ /* This is a rule like "rewrite *.example.com to example.com", and we
+ * just got "example.com". Instead of calling it an infinite loop,
+ * call it complete. */
goto done;
}
-
exact_match = 1;
}
if (!ent || !ent->new_address) {
+ /* We still have no match at all. We're done! */
goto done;
}
+ /* Check wither the flags we were passed tell us not to use this
+ * mapping. */
switch (ent->source) {
case ADDRMAPSRC_DNS:
{
@@ -431,6 +440,8 @@ addressmap_rewrite(char *address, size_t maxlen,
goto done;
}
+ /* Now fill in the address with the new address. That might be via
+ * appending some new stuff to the end, or via just replacing it. */
if (ent->dst_wildcard && !exact_match) {
strlcat(address, ".", maxlen);
strlcat(address, ent->new_address, maxlen);
@@ -438,6 +449,7 @@ addressmap_rewrite(char *address, size_t maxlen,
strlcpy(address, ent->new_address, maxlen);
}
+ /* Is this now a .exit address? If so, remember where we got it.*/
if (!strcmpend(address, ".exit") &&
strcmpend(addr_orig, ".exit") &&
exit_source == ADDRMAPSRC_NONE) {
@@ -774,7 +786,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 +816,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 +908,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 +1119,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/addressmap.h b/src/or/addressmap.h
index 67648d0518..80f453b4c1 100644
--- a/src/or/addressmap.h
+++ b/src/or/addressmap.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_ADDRESSMAP_H
diff --git a/src/or/bridges.c b/src/or/bridges.c
new file mode 100644
index 0000000000..0818fb0812
--- /dev/null
+++ b/src/or/bridges.c
@@ -0,0 +1,889 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bridges.c
+ * \brief Code to manage bridges and bridge selection.
+ *
+ * Bridges are fixed entry nodes, used for censorship circumvention.
+ **/
+
+#include "or.h"
+#include "bridges.h"
+#include "circuitbuild.h"
+#include "config.h"
+#include "connection.h"
+#include "directory.h"
+#include "entrynodes.h"
+#include "nodelist.h"
+#include "policies.h"
+#include "router.h"
+#include "routerlist.h"
+#include "routerset.h"
+#include "transports.h"
+
+/** Information about a configured bridge. Currently this just matches the
+ * ones in the torrc file, but one day we may be able to learn about new
+ * bridges on our own, and remember them in the state file. */
+struct bridge_info_t {
+ /** Address and port of the bridge, as configured by the user.*/
+ tor_addr_port_t addrport_configured;
+ /** Address of the bridge. */
+ tor_addr_t addr;
+ /** TLS port for the bridge. */
+ uint16_t port;
+ /** Boolean: We are re-parsing our bridge list, and we are going to remove
+ * this one if we don't find it in the list of configured bridges. */
+ unsigned marked_for_removal : 1;
+ /** Expected identity digest, or all zero bytes if we don't know what the
+ * digest should be. */
+ char identity[DIGEST_LEN];
+
+ /** Name of pluggable transport protocol taken from its config line. */
+ char *transport_name;
+
+ /** When should we next try to fetch a descriptor for this bridge? */
+ download_status_t fetch_status;
+
+ /** A smartlist of k=v values to be passed to the SOCKS proxy, if
+ transports are used for this bridge. */
+ smartlist_t *socks_args;
+};
+
+static void bridge_free(bridge_info_t *bridge);
+
+/** A list of configured bridges. Whenever we actually get a descriptor
+ * for one, we add it as an entry guard. Note that the order of bridges
+ * in this list does not necessarily correspond to the order of bridges
+ * in the torrc. */
+static smartlist_t *bridge_list = NULL;
+
+/** Mark every entry of the bridge list to be removed on our next call to
+ * sweep_bridge_list unless it has first been un-marked. */
+void
+mark_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b,
+ b->marked_for_removal = 1);
+}
+
+/** Remove every entry of the bridge list that was marked with
+ * mark_bridge_list if it has not subsequently been un-marked. */
+void
+sweep_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ if (b->marked_for_removal) {
+ SMARTLIST_DEL_CURRENT(bridge_list, b);
+ bridge_free(b);
+ }
+ } SMARTLIST_FOREACH_END(b);
+}
+
+/** Initialize the bridge list to empty, creating it if needed. */
+static void
+clear_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b));
+ smartlist_clear(bridge_list);
+}
+
+/** Free the bridge <b>bridge</b>. */
+static void
+bridge_free(bridge_info_t *bridge)
+{
+ if (!bridge)
+ return;
+
+ tor_free(bridge->transport_name);
+ if (bridge->socks_args) {
+ SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s));
+ smartlist_free(bridge->socks_args);
+ }
+
+ tor_free(bridge);
+}
+
+/** Return a list of all the configured bridges, as bridge_info_t pointers. */
+const smartlist_t *
+bridge_list_get(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ return bridge_list;
+}
+
+/**
+ * Given a <b>bridge</b>, return a pointer to its RSA identity digest, or
+ * NULL if we don't know one for it.
+ */
+const uint8_t *
+bridge_get_rsa_id_digest(const bridge_info_t *bridge)
+{
+ tor_assert(bridge);
+ if (tor_digest_is_zero(bridge->identity))
+ return NULL;
+ else
+ return (const uint8_t *) bridge->identity;
+}
+
+/**
+ * Given a <b>bridge</b>, return a pointer to its configured addr:port
+ * combination.
+ */
+const tor_addr_port_t *
+bridge_get_addr_port(const bridge_info_t *bridge)
+{
+ tor_assert(bridge);
+ return &bridge->addrport_configured;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches any of the
+ * tor_addr_port_t's in <b>orports</b>, return that bridge. Else return
+ * NULL. */
+static bridge_info_t *
+get_configured_bridge_by_orports_digest(const char *digest,
+ const smartlist_t *orports)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if (tor_digest_is_zero(bridge->identity)) {
+ SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap)
+ {
+ if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 &&
+ bridge->port == ap->port)
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(ap);
+ }
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
+ * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for
+ * address/port matches only. */
+bridge_info_t *
+get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if ((tor_digest_is_zero(bridge->identity) || digest == NULL) &&
+ !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
+ bridge->port == port)
+ return bridge;
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/**
+ * As get_configured_bridge_by_addr_port, but require that the
+ * address match <b>addr</b>:<b>port</b>, and that the ID digest match
+ * <b>digest</b>. (The other function will ignore the address if the
+ * digest matches.)
+ */
+bridge_info_t *
+get_configured_bridge_by_exact_addr_port_digest(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
+ if (!tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
+ bridge->port == port) {
+
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ else if (!digest || tor_digest_is_zero(bridge->identity))
+ return bridge;
+ }
+
+ } SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
+ * return 1. Else return 0. If <b>digest</b> is NULL, check for
+ * address/port matches only. */
+int
+addr_is_a_configured_bridge(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ tor_assert(addr);
+ return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0;
+}
+
+/** If we have a bridge configured whose digest matches
+ * <b>ei->identity_digest</b>, or a bridge with no known digest whose address
+ * matches <b>ei->addr</b>:<b>ei->port</b>, return 1. Else return 0.
+ * If <b>ei->onion_key</b> is NULL, check for address/port matches only. */
+int
+extend_info_is_a_configured_bridge(const extend_info_t *ei)
+{
+ const char *digest = ei->onion_key ? ei->identity_digest : NULL;
+ return addr_is_a_configured_bridge(&ei->addr, ei->port, digest);
+}
+
+/** Wrapper around get_configured_bridge_by_addr_port_digest() to look
+ * it up via router descriptor <b>ri</b>. */
+static bridge_info_t *
+get_configured_bridge_by_routerinfo(const routerinfo_t *ri)
+{
+ bridge_info_t *bi = NULL;
+ smartlist_t *orports = router_get_all_orports(ri);
+ bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest,
+ orports);
+ SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
+ smartlist_free(orports);
+ return bi;
+}
+
+/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */
+int
+routerinfo_is_a_configured_bridge(const routerinfo_t *ri)
+{
+ return get_configured_bridge_by_routerinfo(ri) ? 1 : 0;
+}
+
+/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */
+int
+node_is_a_configured_bridge(const node_t *node)
+{
+ int retval = 0;
+ smartlist_t *orports = node_get_all_orports(node);
+ retval = get_configured_bridge_by_orports_digest(node->identity,
+ orports) != NULL;
+ SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
+ smartlist_free(orports);
+ return retval;
+}
+
+/** We made a connection to a router at <b>addr</b>:<b>port</b>
+ * without knowing its digest. Its digest turned out to be <b>digest</b>.
+ * If it was a bridge, and we still don't know its digest, record it.
+ */
+void
+learned_router_identity(const tor_addr_t *addr, uint16_t port,
+ const char *digest,
+ const ed25519_public_key_t *ed_id)
+{
+ // XXXX prop220 use ed_id here, once there is some way to specify
+ (void)ed_id;
+ int learned = 0;
+ bridge_info_t *bridge =
+ get_configured_bridge_by_exact_addr_port_digest(addr, port, digest);
+ if (bridge && tor_digest_is_zero(bridge->identity)) {
+ memcpy(bridge->identity, digest, DIGEST_LEN);
+ learned = 1;
+ }
+ /* XXXX prop220 remember bridge ed25519 identities -- add a field */
+#if 0
+ if (bridge && ed_id &&
+ ed25519_public_key_is_zero(&bridge->ed25519_identity) &&
+ !ed25519_public_key_is_zero(ed_id)) {
+ memcpy(&bridge->ed25519_identity, ed_id, sizeof(*ed_id));
+ learned = 1;
+ }
+#endif
+ if (learned) {
+ char *transport_info = NULL;
+ const char *transport_name =
+ find_transport_name_by_bridge_addrport(addr, port);
+ if (transport_name)
+ tor_asprintf(&transport_info, " (with transport '%s')", transport_name);
+
+ // XXXX prop220 log both fingerprints.
+ log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.",
+ hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port),
+ transport_info ? transport_info : "");
+ tor_free(transport_info);
+ entry_guard_learned_bridge_identity(&bridge->addrport_configured,
+ (const uint8_t *)digest);
+ }
+}
+
+/** Return true if <b>bridge</b> has the same identity digest as
+ * <b>digest</b>. If <b>digest</b> is NULL, it matches
+ * bridges with unspecified identity digests. */
+static int
+bridge_has_digest(const bridge_info_t *bridge, const char *digest)
+{
+ if (digest)
+ return tor_memeq(digest, bridge->identity, DIGEST_LEN);
+ else
+ return tor_digest_is_zero(bridge->identity);
+}
+
+/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional
+ * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously
+ * existing bridge with the same address and port, and warn the user as
+ * appropriate.
+ */
+static void
+bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port,
+ const char *digest, const char *transport_name)
+{
+ /* Iterate the already-registered bridge list:
+
+ If you find a bridge with the same adress and port, mark it for
+ removal. It doesn't make sense to have two active bridges with
+ the same IP:PORT. If the bridge in question has a different
+ digest or transport than <b>digest</b>/<b>transport_name</b>,
+ it's probably a misconfiguration and we should warn the user.
+ */
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
+ if (bridge->marked_for_removal)
+ continue;
+
+ if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) {
+
+ bridge->marked_for_removal = 1;
+
+ if (!bridge_has_digest(bridge, digest) ||
+ strcmp_opt(bridge->transport_name, transport_name)) {
+ /* warn the user */
+ char *bridge_description_new, *bridge_description_old;
+ tor_asprintf(&bridge_description_new, "%s:%s:%s",
+ fmt_addrport(addr, port),
+ digest ? hex_str(digest, DIGEST_LEN) : "",
+ transport_name ? transport_name : "");
+ tor_asprintf(&bridge_description_old, "%s:%s:%s",
+ fmt_addrport(&bridge->addr, bridge->port),
+ tor_digest_is_zero(bridge->identity) ?
+ "" : hex_str(bridge->identity,DIGEST_LEN),
+ bridge->transport_name ? bridge->transport_name : "");
+
+ log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict"
+ " with the already registered bridge '%s'. We will discard"
+ " the old bridge and keep '%s'. If this is not what you"
+ " wanted, please change your configuration file accordingly.",
+ bridge_description_new, bridge_description_old,
+ bridge_description_new);
+
+ tor_free(bridge_description_new);
+ tor_free(bridge_description_old);
+ }
+ }
+ } SMARTLIST_FOREACH_END(bridge);
+}
+
+/** Return True if we have a bridge that uses a transport with name
+ * <b>transport_name</b>. */
+MOCK_IMPL(int,
+transport_is_needed, (const char *transport_name))
+{
+ if (!bridge_list)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (bridge->transport_name &&
+ !strcmp(bridge->transport_name, transport_name))
+ return 1;
+ } SMARTLIST_FOREACH_END(bridge);
+
+ return 0;
+}
+
+/** Register the bridge information in <b>bridge_line</b> to the
+ * bridge subsystem. Steals reference of <b>bridge_line</b>. */
+void
+bridge_add_from_config(bridge_line_t *bridge_line)
+{
+ bridge_info_t *b;
+
+ // XXXX prop220 add a way to specify ed25519 ID to bridge_line_t.
+
+ { /* Log the bridge we are about to register: */
+ log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)",
+ fmt_addrport(&bridge_line->addr, bridge_line->port),
+ bridge_line->transport_name ?
+ bridge_line->transport_name : "no transport",
+ tor_digest_is_zero(bridge_line->digest) ?
+ "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN));
+
+ if (bridge_line->socks_args) { /* print socks arguments */
+ int i = 0;
+
+ tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+
+ log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:",
+ smartlist_len(bridge_line->socks_args));
+ SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg,
+ log_debug(LD_CONFIG, "%d: %s", ++i, arg));
+ }
+ }
+
+ bridge_resolve_conflicts(&bridge_line->addr,
+ bridge_line->port,
+ bridge_line->digest,
+ bridge_line->transport_name);
+
+ b = tor_malloc_zero(sizeof(bridge_info_t));
+ tor_addr_copy(&b->addrport_configured.addr, &bridge_line->addr);
+ b->addrport_configured.port = bridge_line->port;
+ tor_addr_copy(&b->addr, &bridge_line->addr);
+ b->port = bridge_line->port;
+ memcpy(b->identity, bridge_line->digest, DIGEST_LEN);
+ if (bridge_line->transport_name)
+ b->transport_name = bridge_line->transport_name;
+ b->fetch_status.schedule = DL_SCHED_BRIDGE;
+ b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL;
+ b->socks_args = bridge_line->socks_args;
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+
+ tor_free(bridge_line); /* Deallocate bridge_line now. */
+
+ smartlist_add(bridge_list, b);
+}
+
+/** If <b>digest</b> is one of our known bridges, return it. */
+bridge_info_t *
+find_bridge_by_digest(const char *digest)
+{
+ if (! bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge,
+ {
+ if (tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ });
+ return NULL;
+}
+
+/** Given the <b>addr</b> and <b>port</b> of a bridge, if that bridge
+ * supports a pluggable transport, return its name. Otherwise, return
+ * NULL. */
+const char *
+find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+{
+ if (!bridge_list)
+ return NULL;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (tor_addr_eq(&bridge->addr, addr) &&
+ (bridge->port == port))
+ return bridge->transport_name;
+ } SMARTLIST_FOREACH_END(bridge);
+
+ return NULL;
+}
+
+/** If <b>addr</b> and <b>port</b> match the address and port of a
+ * bridge of ours that uses pluggable transports, place its transport
+ * in <b>transport</b>.
+ *
+ * Return 0 on success (found a transport, or found a bridge with no
+ * transport, or found no bridge); return -1 if we should be using a
+ * transport, but the transport could not be found.
+ */
+int
+get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+ const transport_t **transport)
+{
+ *transport = NULL;
+ if (!bridge_list)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (tor_addr_eq(&bridge->addr, addr) &&
+ (bridge->port == port)) { /* bridge matched */
+ if (bridge->transport_name) { /* it also uses pluggable transports */
+ *transport = transport_get_by_name(bridge->transport_name);
+ if (*transport == NULL) { /* it uses pluggable transports, but
+ the transport could not be found! */
+ return -1;
+ }
+ return 0;
+ } else { /* bridge matched, but it doesn't use transports. */
+ break;
+ }
+ }
+ } SMARTLIST_FOREACH_END(bridge);
+
+ *transport = NULL;
+ return 0;
+}
+
+/** Return a smartlist containing all the SOCKS arguments that we
+ * should pass to the SOCKS proxy. */
+const smartlist_t *
+get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+{
+ bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr,
+ port,
+ NULL);
+ return bridge ? bridge->socks_args : NULL;
+}
+
+/** We need to ask <b>bridge</b> for its server descriptor. */
+static void
+launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
+{
+ const or_options_t *options = get_options();
+ circuit_guard_state_t *guard_state = NULL;
+
+ if (connection_get_by_type_addr_port_purpose(
+ CONN_TYPE_DIR, &bridge->addr, bridge->port,
+ DIR_PURPOSE_FETCH_SERVERDESC))
+ return; /* it's already on the way */
+
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+ download_status_mark_impossible(&bridge->fetch_status);
+ log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+ safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
+ return;
+ }
+
+ /* Until we get a descriptor for the bridge, we only know one address for
+ * it. */
+ if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
+ FIREWALL_OR_CONNECTION, 0, 0)) {
+ log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a "
+ "bridge, but that bridge is not reachable through our "
+ "firewall.");
+ return;
+ }
+
+ tor_addr_port_t bridge_addrport;
+ memcpy(&bridge_addrport.addr, &bridge->addr, sizeof(tor_addr_t));
+ bridge_addrport.port = bridge->port;
+
+ guard_state = get_guard_state_for_bridge_desc_fetch(bridge->identity);
+
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC);
+ directory_request_set_or_addr_port(req, &bridge_addrport);
+ directory_request_set_directory_id_digest(req, bridge->identity);
+ directory_request_set_router_purpose(req, ROUTER_PURPOSE_BRIDGE);
+ directory_request_set_resource(req, "authority.z");
+ if (guard_state) {
+ directory_request_set_guard_state(req, guard_state);
+ }
+ directory_initiate_request(req);
+ directory_request_free(req);
+}
+
+/** Fetching the bridge descriptor from the bridge authority returned a
+ * "not found". Fall back to trying a direct fetch. */
+void
+retry_bridge_descriptor_fetch_directly(const char *digest)
+{
+ bridge_info_t *bridge = find_bridge_by_digest(digest);
+ if (!bridge)
+ return; /* not found? oh well. */
+
+ launch_direct_bridge_descriptor_fetch(bridge);
+}
+
+/** For each bridge in our list for which we don't currently have a
+ * descriptor, fetch a new copy of its descriptor -- either directly
+ * from the bridge or via a bridge authority. */
+void
+fetch_bridge_descriptors(const or_options_t *options, time_t now)
+{
+ int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO);
+ int ask_bridge_directly;
+ int can_use_bridge_authority;
+
+ if (!bridge_list)
+ return;
+
+ /* If we still have unconfigured managed proxies, don't go and
+ connect to a bridge. */
+ if (pt_proxies_configuration_pending())
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if (!download_status_is_ready(&bridge->fetch_status, now,
+ IMPOSSIBLE_TO_DOWNLOAD))
+ continue; /* don't bother, no need to retry yet */
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+ download_status_mark_impossible(&bridge->fetch_status);
+ log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+ safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
+ continue;
+ }
+
+ /* schedule another fetch as if this one will fail, in case it does */
+ download_status_failed(&bridge->fetch_status, 0);
+
+ can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) &&
+ num_bridge_auths;
+ ask_bridge_directly = !can_use_bridge_authority ||
+ !options->UpdateBridgesFromAuthority;
+ log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)",
+ ask_bridge_directly, tor_digest_is_zero(bridge->identity),
+ !options->UpdateBridgesFromAuthority, !num_bridge_auths);
+
+ if (ask_bridge_directly &&
+ !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
+ FIREWALL_OR_CONNECTION, 0,
+ 0)) {
+ log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
+ "firewall policy. %s.",
+ fmt_addrport(&bridge->addr, bridge->port),
+ can_use_bridge_authority ?
+ "Asking bridge authority instead" : "Skipping");
+ if (can_use_bridge_authority)
+ ask_bridge_directly = 0;
+ else
+ continue;
+ }
+
+ if (ask_bridge_directly) {
+ /* we need to ask the bridge itself for its descriptor. */
+ launch_direct_bridge_descriptor_fetch(bridge);
+ } else {
+ /* We have a digest and we want to ask an authority. We could
+ * combine all the requests into one, but that may give more
+ * hints to the bridge authority than we want to give. */
+ char resource[10 + HEX_DIGEST_LEN];
+ memcpy(resource, "fp/", 3);
+ base16_encode(resource+3, HEX_DIGEST_LEN+1,
+ bridge->identity, DIGEST_LEN);
+ memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3);
+ log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.",
+ resource);
+ directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC,
+ ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY);
+ }
+ }
+ SMARTLIST_FOREACH_END(bridge);
+}
+
+/** If our <b>bridge</b> is configured to be a different address than
+ * the bridge gives in <b>node</b>, rewrite the routerinfo
+ * we received to use the address we meant to use. Now we handle
+ * multihomed bridges better.
+ */
+static void
+rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
+{
+ /* XXXX move this function. */
+ /* XXXX overridden addresses should really live in the node_t, so that the
+ * routerinfo_t and the microdesc_t can be immutable. But we can only
+ * do that safely if we know that no function that connects to an OR
+ * does so through an address from any source other than node_get_addr().
+ */
+ tor_addr_t addr;
+ const or_options_t *options = get_options();
+
+ if (node->ri) {
+ routerinfo_t *ri = node->ri;
+ tor_addr_from_ipv4h(&addr, ri->addr);
+
+ if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
+ bridge->port == ri->or_port) ||
+ (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) &&
+ bridge->port == ri->ipv6_orport)) {
+ /* they match, so no need to do anything */
+ } else {
+ if (tor_addr_family(&bridge->addr) == AF_INET) {
+ ri->addr = tor_addr_to_ipv4h(&bridge->addr);
+ ri->or_port = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerinfo for '%s' to match configured "
+ "address %s:%d.",
+ ri->nickname, fmt_addr32(ri->addr), ri->or_port);
+ } else if (tor_addr_family(&bridge->addr) == AF_INET6) {
+ tor_addr_copy(&ri->ipv6_addr, &bridge->addr);
+ ri->ipv6_orport = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerinfo for '%s' to match configured "
+ "address %s.",
+ ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport));
+ } else {
+ log_err(LD_BUG, "Address family not supported: %d.",
+ tor_addr_family(&bridge->addr));
+ return;
+ }
+ }
+
+ if (options->ClientPreferIPv6ORPort == -1) {
+ /* Mark which address to use based on which bridge_t we got. */
+ node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 &&
+ !tor_addr_is_null(&node->ri->ipv6_addr));
+ } else {
+ /* Mark which address to use based on user preference */
+ node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) &&
+ !tor_addr_is_null(&node->ri->ipv6_addr));
+ }
+
+ /* XXXipv6 we lack support for falling back to another address for
+ the same relay, warn the user */
+ if (!tor_addr_is_null(&ri->ipv6_addr)) {
+ tor_addr_port_t ap;
+ node_get_pref_orport(node, &ap);
+ log_notice(LD_CONFIG,
+ "Bridge '%s' has both an IPv4 and an IPv6 address. "
+ "Will prefer using its %s address (%s) based on %s.",
+ ri->nickname,
+ node->ipv6_preferred ? "IPv6" : "IPv4",
+ fmt_addrport(&ap.addr, ap.port),
+ options->ClientPreferIPv6ORPort == -1 ?
+ "the configured Bridge address" :
+ "ClientPreferIPv6ORPort");
+ }
+ }
+ if (node->rs) {
+ routerstatus_t *rs = node->rs;
+ tor_addr_from_ipv4h(&addr, rs->addr);
+
+ if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
+ bridge->port == rs->or_port) {
+ /* they match, so no need to do anything */
+ } else {
+ rs->addr = tor_addr_to_ipv4h(&bridge->addr);
+ rs->or_port = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerstatus for '%s' to match "
+ "configured address %s.",
+ rs->nickname, fmt_addrport(&bridge->addr, rs->or_port));
+ }
+ }
+}
+
+/** We just learned a descriptor for a bridge. See if that
+ * digest is in our entry guard list, and add it if not. */
+void
+learned_bridge_descriptor(routerinfo_t *ri, int from_cache)
+{
+ tor_assert(ri);
+ tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE);
+ if (get_options()->UseBridges) {
+ int first = num_bridges_usable() <= 1;
+ bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri);
+ time_t now = time(NULL);
+ router_set_status(ri->cache_info.identity_digest, 1);
+
+ if (bridge) { /* if we actually want to use this one */
+ node_t *node;
+ /* it's here; schedule its re-fetch for a long time from now. */
+ if (!from_cache)
+ download_status_reset(&bridge->fetch_status);
+
+ node = node_get_mutable_by_id(ri->cache_info.identity_digest);
+ tor_assert(node);
+ rewrite_node_address_for_bridge(bridge, node);
+ if (tor_digest_is_zero(bridge->identity)) {
+ memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN);
+ log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d",
+ hex_str(bridge->identity, DIGEST_LEN),
+ fmt_and_decorate_addr(&bridge->addr),
+ (int) bridge->port);
+ }
+ entry_guard_learned_bridge_identity(&bridge->addrport_configured,
+ (const uint8_t*)ri->cache_info.identity_digest);
+
+ log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname,
+ from_cache ? "cached" : "fresh", router_describe(ri));
+ /* set entry->made_contact so if it goes down we don't drop it from
+ * our entry node list */
+ if (first) {
+ routerlist_retry_directory_downloads(now);
+ }
+ }
+ }
+}
+
+/** Return the number of bridges that have descriptors that
+ * are marked with purpose 'bridge' and are running.
+ *
+ * We use this function to decide if we're ready to start building
+ * circuits through our bridges, or if we need to wait until the
+ * directory "server/authority" requests finish. */
+int
+any_bridge_descriptors_known(void)
+{
+ tor_assert(get_options()->UseBridges);
+
+ if (!bridge_list)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
+ const node_t *node;
+ if (!tor_digest_is_zero(bridge->identity) &&
+ (node = node_get_by_id(bridge->identity)) != NULL &&
+ node->ri) {
+ return 1;
+ }
+ } SMARTLIST_FOREACH_END(bridge);
+
+ return 0;
+}
+
+/** Return a smartlist containing all bridge identity digests */
+MOCK_IMPL(smartlist_t *,
+list_bridge_identities, (void))
+{
+ 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;
+}
+
+/** Release all storage held in bridges.c */
+void
+bridges_free_all(void)
+{
+ clear_bridge_list();
+ smartlist_free(bridge_list);
+ bridge_list = NULL;
+}
+
diff --git a/src/or/bridges.h b/src/or/bridges.h
new file mode 100644
index 0000000000..3bfc782f9a
--- /dev/null
+++ b/src/or/bridges.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bridges.h
+ * \brief Header file for circuitbuild.c.
+ **/
+
+#ifndef TOR_BRIDGES_H
+#define TOR_BRIDGES_H
+
+struct bridge_line_t;
+
+/* Opaque handle to a configured bridge */
+typedef struct bridge_info_t bridge_info_t;
+
+void mark_bridge_list(void);
+void sweep_bridge_list(void);
+const smartlist_t *bridge_list_get(void);
+bridge_info_t *find_bridge_by_digest(const char *digest);
+const uint8_t *bridge_get_rsa_id_digest(const bridge_info_t *bridge);
+const tor_addr_port_t * bridge_get_addr_port(const bridge_info_t *bridge);
+bridge_info_t *get_configured_bridge_by_addr_port_digest(
+ const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest);
+bridge_info_t *get_configured_bridge_by_exact_addr_port_digest(
+ const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest);
+
+int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port,
+ const char *digest);
+int extend_info_is_a_configured_bridge(const extend_info_t *ei);
+int routerinfo_is_a_configured_bridge(const routerinfo_t *ri);
+int node_is_a_configured_bridge(const node_t *node);
+void learned_router_identity(const tor_addr_t *addr, uint16_t port,
+ const char *digest,
+ const ed25519_public_key_t *ed_id);
+
+void bridge_add_from_config(struct bridge_line_t *bridge_line);
+void retry_bridge_descriptor_fetch_directly(const char *digest);
+void fetch_bridge_descriptors(const or_options_t *options, time_t now);
+void learned_bridge_descriptor(routerinfo_t *ri, int from_cache);
+int any_bridge_descriptors_known(void);
+const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr,
+ uint16_t port);
+
+int any_bridges_dont_support_microdescriptors(void);
+
+const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr,
+ uint16_t port);
+struct transport_t;
+int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+ const struct transport_t **transport);
+
+MOCK_DECL(int, transport_is_needed, (const char *transport_name));
+int validate_pluggable_transports_config(void);
+
+MOCK_DECL(smartlist_t *, list_bridge_identities, (void));
+MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id,
+ (const char *digest));
+
+void bridges_free_all(void);
+
+#endif
+
diff --git a/src/or/buffers.c b/src/or/buffers.c
index cdc71ab9db..bd84103c37 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -1,15 +1,27 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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"
@@ -68,14 +80,43 @@ static int parse_socks_client(const uint8_t *data, size_t datalen,
/* Chunk manipulation functions */
-#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0])
+#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0])
+
+/* We leave this many NUL bytes at the end of the buffer. */
+#ifdef DISABLE_MEMORY_SENTINELS
+#define SENTINEL_LEN 0
+#else
+#define SENTINEL_LEN 4
+#endif
+
+/* Header size plus NUL bytes at the end */
+#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN)
/** Return the number of bytes needed to allocate a chunk to hold
* <b>memlen</b> bytes. */
-#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_HEADER_LEN + (memlen))
+#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen))
/** Return the number of usable bytes in a chunk allocated with
* malloc(<b>memlen</b>). */
-#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_HEADER_LEN)
+#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD)
+
+#define DEBUG_SENTINEL
+
+#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS)
+#define DBG_S(s) s
+#else
+#define DBG_S(s) (void)0
+#endif
+
+#ifdef DISABLE_MEMORY_SENTINELS
+#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL
+#else
+#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \
+ uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \
+ DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \
+ DBG_S(tor_assert(a == b)); \
+ memset(a,0,SENTINEL_LEN); \
+ } while (0)
+#endif
/** Return the next character in <b>chunk</b> onto which data can be appended.
* If the chunk is full, this might be off the end of chunk->mem. */
@@ -132,6 +173,7 @@ chunk_new_with_alloc_size(size_t alloc)
ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc);
total_bytes_allocated_in_chunks += alloc;
ch->data = &ch->mem[0];
+ CHUNK_SET_SENTINEL(ch, alloc);
return ch;
}
@@ -141,18 +183,20 @@ static inline chunk_t *
chunk_grow(chunk_t *chunk, size_t sz)
{
off_t offset;
- size_t memlen_orig = chunk->memlen;
+ const size_t memlen_orig = chunk->memlen;
+ const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig);
+ const size_t new_alloc = CHUNK_ALLOC_SIZE(sz);
tor_assert(sz > chunk->memlen);
offset = chunk->data - chunk->mem;
- chunk = tor_realloc(chunk, CHUNK_ALLOC_SIZE(sz));
+ chunk = tor_realloc(chunk, new_alloc);
chunk->memlen = sz;
chunk->data = chunk->mem + offset;
#ifdef DEBUG_CHUNK_ALLOC
- tor_assert(chunk->DBG_alloc == CHUNK_ALLOC_SIZE(memlen_orig));
- chunk->DBG_alloc = CHUNK_ALLOC_SIZE(sz);
+ tor_assert(chunk->DBG_alloc == orig_alloc);
+ chunk->DBG_alloc = new_alloc;
#endif
- total_bytes_allocated_in_chunks +=
- CHUNK_ALLOC_SIZE(sz) - CHUNK_ALLOC_SIZE(memlen_orig);
+ total_bytes_allocated_in_chunks += new_alloc - orig_alloc;
+ CHUNK_SET_SENTINEL(chunk, new_alloc);
return chunk;
}
@@ -166,9 +210,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;
@@ -242,6 +289,7 @@ buf_pullup(buf_t *buf, size_t bytes)
}
#ifdef TOR_UNIT_TESTS
+/* Return the data from the first chunk of buf in cp, and its length in sz. */
void
buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz)
{
@@ -253,6 +301,53 @@ buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz)
*sz = buf->head->datalen;
}
}
+
+/* Write sz bytes from cp into a newly allocated buffer buf.
+ * Returns NULL when passed a NULL cp or zero sz.
+ * Asserts on failure: only for use in unit tests.
+ * buf must be freed using buf_free(). */
+buf_t *
+buf_new_with_data(const char *cp, size_t sz)
+{
+ /* Validate arguments */
+ if (!cp || sz <= 0) {
+ return NULL;
+ }
+
+ tor_assert(sz < SSIZE_T_CEILING);
+
+ /* Allocate a buffer */
+ buf_t *buf = buf_new_with_capacity(sz);
+ tor_assert(buf);
+ assert_buf_ok(buf);
+ tor_assert(!buf->head);
+
+ /* Allocate a chunk that is sz bytes long */
+ buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz));
+ buf->tail = buf->head;
+ tor_assert(buf->head);
+ assert_buf_ok(buf);
+ tor_assert(buf_allocation(buf) >= sz);
+
+ /* Copy the data and size the buffers */
+ tor_assert(sz <= buf_slack(buf));
+ tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head));
+ memcpy(&buf->head->mem[0], cp, sz);
+ buf->datalen = sz;
+ buf->head->datalen = sz;
+ buf->head->data = &buf->head->mem[0];
+ assert_buf_ok(buf);
+
+ /* Make sure everything is large enough */
+ tor_assert(buf_allocation(buf) >= sz);
+ tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf));
+ /* Does the buffer implementation allocate more than the requested size?
+ * (for example, by rounding up). If so, these checks will fail. */
+ tor_assert(buf_datalen(buf) == sz);
+ tor_assert(buf_slack(buf) == 0);
+
+ return buf;
+}
#endif
/** Remove the first <b>n</b> bytes from buf. */
@@ -405,7 +500,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) {
@@ -414,8 +509,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);
@@ -430,8 +524,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)
@@ -509,12 +603,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;
@@ -524,6 +618,11 @@ read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof,
tor_assert(reached_eof);
tor_assert(SOCKET_OK(s));
+ if (BUG(buf->datalen >= INT_MAX))
+ return -1;
+ if (BUG(buf->datalen >= INT_MAX - at_most))
+ return -1;
+
while (at_most > total_read) {
size_t readlen = at_most - total_read;
chunk_t *chunk;
@@ -581,6 +680,11 @@ read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf)
check();
+ if (BUG(buf->datalen >= INT_MAX))
+ return -1;
+ if (BUG(buf->datalen >= INT_MAX - at_most))
+ return -1;
+
while (at_most > total_read) {
size_t readlen = at_most - total_read;
chunk_t *chunk;
@@ -687,7 +791,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;
@@ -775,6 +879,11 @@ write_to_buf(const char *string, size_t string_len, buf_t *buf)
return (int)buf->datalen;
check();
+ if (BUG(buf->datalen >= INT_MAX))
+ return -1;
+ if (BUG(buf->datalen >= INT_MAX - string_len))
+ return -1;
+
while (string_len) {
size_t copy;
if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail))
@@ -914,97 +1023,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.
@@ -1015,6 +1033,12 @@ move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen)
/* We can do way better here, but this doesn't turn up in any profiles. */
char b[4096];
size_t cp, len;
+
+ if (BUG(buf_out->datalen >= INT_MAX))
+ return -1;
+ if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen))
+ return -1;
+
len = *buf_flushlen;
if (len > buf_in->datalen)
len = buf_in->datalen;
@@ -1143,6 +1167,52 @@ buf_find_string_offset(const buf_t *buf, const char *s, size_t n)
return -1;
}
+/**
+ * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at
+ * <b>headers</b>, looking for a "Content-Length" header. Try to set
+ * *<b>result_out</b> to the numeric value of that header if possible.
+ * Return -1 if the header was malformed, 0 if it was missing, and 1 if
+ * it was present and well-formed.
+ */
+STATIC int
+buf_http_find_content_length(const char *headers, size_t headerlen,
+ size_t *result_out)
+{
+ const char *p, *newline;
+ char *len_str, *eos=NULL;
+ size_t remaining, result;
+ int ok;
+ *result_out = 0; /* The caller shouldn't look at this unless the
+ * return value is 1, but let's prevent confusion */
+
+#define CONTENT_LENGTH "\r\nContent-Length: "
+ p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
+ if (p == NULL)
+ return 0;
+
+ tor_assert(p >= headers && p < headers+headerlen);
+ remaining = (headers+headerlen)-p;
+ p += strlen(CONTENT_LENGTH);
+ remaining -= strlen(CONTENT_LENGTH);
+
+ newline = memchr(p, '\n', remaining);
+ if (newline == NULL)
+ return -1;
+
+ len_str = tor_memdup_nulterm(p, newline-p);
+ /* We limit the size to INT_MAX because other parts of the buffer.c
+ * code don't like buffers to be any bigger than that. */
+ result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos);
+ if (eos && !tor_strisspace(eos)) {
+ ok = 0;
+ } else {
+ *result_out = result;
+ }
+ tor_free(len_str);
+
+ return ok ? 1 : -1;
+}
+
/** There is a (possibly incomplete) http statement on <b>buf</b>, of the
* form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.)
* If a) the headers include a Content-Length field and all bytes in
@@ -1168,9 +1238,10 @@ fetch_from_buf_http(buf_t *buf,
char **body_out, size_t *body_used, size_t max_bodylen,
int force_complete)
{
- char *headers, *p;
- size_t headerlen, bodylen, contentlen;
+ char *headers;
+ size_t headerlen, bodylen, contentlen=0;
int crlf_offset;
+ int r;
check();
if (!buf->head)
@@ -1206,17 +1277,12 @@ fetch_from_buf_http(buf_t *buf,
return -1;
}
-#define CONTENT_LENGTH "\r\nContent-Length: "
- p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
- if (p) {
- int i;
- i = atoi(p+strlen(CONTENT_LENGTH));
- 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;
+ r = buf_http_find_content_length(headers, headerlen, &contentlen);
+ if (r == -1) {
+ log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe "
+ "someone is trying to crash us.");
+ return -1;
+ } else if (r == 1) {
/* if content-length is malformed, then our body length is 0. fine. */
log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen);
if (bodylen < contentlen) {
@@ -1229,7 +1295,11 @@ fetch_from_buf_http(buf_t *buf,
bodylen = contentlen;
log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen);
}
+ } else {
+ tor_assert(r == 0);
+ /* Leave bodylen alone */
}
+
/* all happy. copy into the appropriate places, and return 1 */
if (headers_out) {
*headers_out = tor_malloc(headerlen+1);
@@ -1247,97 +1317,9 @@ 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). */
+ * again. */
#define SOCKS_WARN_INTERVAL 5
/** Warn that the user application has made an unsafe socks request using
@@ -1349,9 +1331,6 @@ log_unsafe_socks_warning(int socks_protocol, const char *address,
{
static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL);
- const or_options_t *options = get_options();
- if (! options->WarnUnsafeSocks)
- return;
if (safe_socks) {
log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP,
"Your application (using socks%d to port %d) is giving "
@@ -1454,86 +1433,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
@@ -1564,34 +1463,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>.
*/
@@ -1607,6 +1478,32 @@ socks_request_set_socks5_error(socks_request_t *req,
req->reply[3] = 0x01; // ATYP field.
}
+static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor is not an HTTP Proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor is not an HTTP Proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor as "
+ "an HTTP proxy.\n\n"
+ "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Implementation helper to implement fetch_from_*_socks. Instead of looking
* at a buffer's contents, we look at the <b>datalen</b> bytes of data in
* <b>data</b>. Instead of removing data from the buffer, we set
@@ -1813,15 +1710,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
req->port = ntohs(get_uint16(data+5+len));
*drain_out = 5+len+2;
- if (string_is_valid_ipv4_address(req->address) ||
- string_is_valid_ipv6_address(req->address)) {
- log_unsafe_socks_warning(5,req->address,req->port,safe_socks);
-
- if (safe_socks) {
- socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED);
- return -1;
- }
- } else if (!string_is_valid_hostname(req->address)) {
+ if (!string_is_valid_hostname(req->address)) {
socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR);
log_warn(LD_PROTOCOL,
@@ -1844,6 +1733,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;
@@ -1942,7 +1832,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
log_debug(LD_APP,"socks4: Everything is here. Success.");
strlcpy(req->address, startaddr ? startaddr : tmpbuf,
sizeof(req->address));
- if (!tor_strisprint(req->address) || strchr(req->address,'\"')) {
+ if (!string_is_valid_hostname(req->address)) {
log_warn(LD_PROTOCOL,
"Your application (using socks4 to port %d) gave Tor "
"a malformed hostname: %s. Rejecting the connection.",
@@ -1962,32 +1852,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
case 'H': /* head */
case 'P': /* put/post */
case 'C': /* connect */
- strlcpy((char*)req->reply,
-"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
-"Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
-"<html>\n"
-"<head>\n"
-"<title>Tor is not an HTTP Proxy</title>\n"
-"</head>\n"
-"<body>\n"
-"<h1>Tor is not an HTTP Proxy</h1>\n"
-"<p>\n"
-"It appears you have configured your web browser to use Tor as an HTTP proxy."
-"\n"
-"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
-"Please configure your client accordingly.\n"
-"</p>\n"
-"<p>\n"
-"See <a href=\"https://www.torproject.org/documentation.html\">"
- "https://www.torproject.org/documentation.html</a> for more "
- "information.\n"
-"<!-- Plus this comment, to make the body response more than 512 bytes, so "
-" IE will be willing to display it. Comment comment comment comment "
-" comment comment comment comment comment comment comment comment.-->\n"
-"</p>\n"
-"</body>\n"
-"</html>\n"
- , MAX_SOCKS_REPLY_LEN);
+ strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG,
+ MAX_SOCKS_REPLY_LEN);
req->replylen = strlen((char*)req->reply)+1;
/* fall through */
default: /* version is not socks4 or socks5 */
@@ -2036,34 +1902,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,
@@ -2178,6 +2016,34 @@ parse_socks_client(const uint8_t *data, size_t datalen,
return -1;
}
+/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */
+int
+peek_buf_has_http_command(const buf_t *buf)
+{
+ if (peek_buf_startswith(buf, "CONNECT ") ||
+ peek_buf_startswith(buf, "DELETE ") ||
+ peek_buf_startswith(buf, "GET ") ||
+ peek_buf_startswith(buf, "POST ") ||
+ peek_buf_startswith(buf, "PUT " ))
+ return 1;
+ return 0;
+}
+
+/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null
+ * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */
+int
+peek_buf_startswith(const buf_t *buf, const char *cmd)
+{
+ char tmp[PEEK_BUF_STARTSWITH_MAX];
+ size_t clen = strlen(cmd);
+ if (BUG(clen > sizeof(tmp)))
+ return 0;
+ if (buf->datalen < clen)
+ return 0;
+ peek_from_buf(tmp, clen, buf);
+ return fast_memeq(tmp, cmd, clen);
+}
+
/** Return 1 iff buf looks more like it has an (obsolete) v0 controller
* command on it than any valid v1 controller command. */
int
@@ -2194,27 +2060,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
@@ -2263,13 +2108,13 @@ fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len)
}
/** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the
- * zlib state <b>state</b>, appending the result to <b>buf</b>. If
+ * compression state <b>state</b>, appending the result to <b>buf</b>. If
* <b>done</b> is true, flush the data in the state and finish the
* compression/uncompression. Return -1 on failure, 0 on success. */
int
-write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state,
- const char *data, size_t data_len,
- int done)
+write_to_buf_compress(buf_t *buf, tor_compress_state_t *state,
+ const char *data, size_t data_len,
+ const int done)
{
char *next;
size_t old_avail, avail;
@@ -2283,22 +2128,31 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state,
}
next = CHUNK_WRITE_PTR(buf->tail);
avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail);
- switch (tor_zlib_process(state, &next, &avail, &data, &data_len, done)) {
- case TOR_ZLIB_DONE:
+ switch (tor_compress_process(state, &next, &avail,
+ &data, &data_len, done)) {
+ case TOR_COMPRESS_DONE:
over = 1;
break;
- case TOR_ZLIB_ERR:
+ case TOR_COMPRESS_ERROR:
return -1;
- case TOR_ZLIB_OK:
- if (data_len == 0)
+ case TOR_COMPRESS_OK:
+ if (data_len == 0) {
+ tor_assert_nonfatal(!done);
over = 1;
+ }
break;
- case TOR_ZLIB_BUF_FULL:
+ case TOR_COMPRESS_BUFFER_FULL:
if (avail) {
- /* Zlib says we need more room (ZLIB_BUF_FULL). Start a new chunk
- * automatically, whether were going to or not. */
+ /* The compression module says we need more room
+ * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically,
+ * whether were going to or not. */
need_new_chunk = 1;
}
+ if (data_len == 0 && !done) {
+ /* We've consumed all the input data, though, so there's no
+ * point in forging ahead right now. */
+ over = 1;
+ }
break;
}
buf->datalen += old_avail - avail;
@@ -2312,93 +2166,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..d884084385 100644
--- a/src/or/buffers.h
+++ b/src/or/buffers.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -36,8 +36,8 @@ int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen);
int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen);
int write_to_buf(const char *string, size_t string_len, buf_t *buf);
-int write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state,
- const char *data, size_t data_len, int done);
+int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state,
+ const char *data, size_t data_len, int done);
int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen);
int fetch_from_buf(char *string, size_t string_len, buf_t *buf);
int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto);
@@ -53,56 +53,25 @@ int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason);
int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len);
int peek_buf_has_control0_command(buf_t *buf);
+#define PEEK_BUF_STARTSWITH_MAX 16
+int peek_buf_startswith(const buf_t *buf, const char *cmd);
+int peek_buf_has_http_command(const 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);
#ifdef BUFFERS_PRIVATE
STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n);
STATIC void buf_pullup(buf_t *buf, size_t bytes);
+#ifdef TOR_UNIT_TESTS
void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz);
+buf_t *buf_new_with_data(const char *cp, size_t sz);
+#endif
+STATIC size_t preferred_chunk_size(size_t target);
#define DEBUG_CHUNK_ALLOC
/** A single chunk on a buffer. */
@@ -134,5 +103,10 @@ struct buf_t {
};
#endif
+#ifdef BUFFERS_PRIVATE
+STATIC int buf_http_find_content_length(const char *headers, size_t headerlen,
+ size_t *result_out);
+#endif
+
#endif
diff --git a/src/or/channel.c b/src/or/channel.c
index f3939399b0..9f8a03683f 100644
--- a/src/or/channel.c
+++ b/src/or/channel.c
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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().
**/
/*
@@ -23,6 +49,7 @@
#include "or.h"
#include "channel.h"
#include "channeltls.h"
+#include "channelpadding.h"
#include "circuitbuild.h"
#include "circuitlist.h"
#include "circuitstats.h"
@@ -37,6 +64,7 @@
#include "router.h"
#include "routerlist.h"
#include "scheduler.h"
+#include "compat_time.h"
/* Global lists of channels */
@@ -58,6 +86,28 @@ static smartlist_t *active_listeners = NULL;
/* All channel_listener_t instances in LISTENING state */
static smartlist_t *finished_listeners = NULL;
+/** Map from channel->global_identifier to channel. Contains the same
+ * elements as all_channels. */
+static HT_HEAD(channel_gid_map, channel_s) channel_gid_map = HT_INITIALIZER();
+
+static unsigned
+channel_id_hash(const channel_t *chan)
+{
+ return (unsigned) chan->global_identifier;
+}
+static int
+channel_id_eq(const channel_t *a, const channel_t *b)
+{
+ return a->global_identifier == b->global_identifier;
+}
+HT_PROTOTYPE(channel_gid_map, channel_s, gidmap_node,
+ channel_id_hash, channel_id_eq)
+HT_GENERATE2(channel_gid_map, channel_s, gidmap_node,
+ channel_id_hash, channel_id_eq,
+ 0.6, tor_reallocarray_, tor_free_)
+
+HANDLE_IMPL(channel, channel_s,)
+
/* Counter for ID numbers */
static uint64_t n_channels_allocated = 0;
/*
@@ -122,7 +172,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 +195,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
@@ -403,6 +453,7 @@ void
channel_register(channel_t *chan)
{
tor_assert(chan);
+ tor_assert(chan->global_identifier);
/* No-op if already registered */
if (chan->registered) return;
@@ -417,6 +468,8 @@ channel_register(channel_t *chan)
/* Make sure we have all_channels, then add it */
if (!all_channels) all_channels = smartlist_new();
smartlist_add(all_channels, chan);
+ channel_t *oldval = HT_REPLACE(channel_gid_map, &channel_gid_map, chan);
+ tor_assert(! oldval);
/* Is it finished? */
if (CHANNEL_FINISHED(chan)) {
@@ -472,7 +525,9 @@ channel_unregister(channel_t *chan)
}
/* Get it out of all_channels */
- if (all_channels) smartlist_remove(all_channels, chan);
+ if (all_channels) smartlist_remove(all_channels, chan);
+ channel_t *oldval = HT_REMOVE(channel_gid_map, &channel_gid_map, chan);
+ tor_assert(oldval == NULL || oldval == chan);
/* Mark it as unregistered */
chan->registered = 0;
@@ -507,7 +562,7 @@ channel_listener_register(channel_listener_t *chan_l)
channel_listener_state_to_string(chan_l->state),
chan_l->state);
- /* Make sure we have all_channels, then add it */
+ /* Make sure we have all_listeners, then add it */
if (!all_listeners) all_listeners = smartlist_new();
smartlist_add(all_listeners, chan_l);
@@ -552,7 +607,7 @@ channel_listener_unregister(channel_listener_t *chan_l)
if (active_listeners) smartlist_remove(active_listeners, chan_l);
}
- /* Get it out of all_channels */
+ /* Get it out of all_listeners */
if (all_listeners) smartlist_remove(all_listeners, chan_l);
/* Mark it as unregistered */
@@ -693,41 +748,74 @@ channel_remove_from_digest_map(channel_t *chan)
channel_t *
channel_find_by_global_id(uint64_t global_identifier)
{
+ channel_t lookup;
channel_t *rv = NULL;
- if (all_channels && smartlist_len(all_channels) > 0) {
- SMARTLIST_FOREACH_BEGIN(all_channels, channel_t *, curr) {
- if (curr->global_identifier == global_identifier) {
- rv = curr;
- break;
- }
- } SMARTLIST_FOREACH_END(curr);
+ lookup.global_identifier = global_identifier;
+ rv = HT_FIND(channel_gid_map, &channel_gid_map, &lookup);
+ if (rv) {
+ tor_assert(rv->global_identifier == global_identifier);
}
return rv;
}
+/** Return true iff <b>chan</b> matches <b>rsa_id_digest</b> and <b>ed_id</b>.
+ * as its identity keys. If either is NULL, do not check for a match. */
+static int
+channel_remote_identity_matches(const channel_t *chan,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id)
+{
+ if (BUG(!chan))
+ return 0;
+ if (rsa_id_digest) {
+ if (tor_memneq(rsa_id_digest, chan->identity_digest, DIGEST_LEN))
+ return 0;
+ }
+ if (ed_id) {
+ if (tor_memneq(ed_id->pubkey, chan->ed25519_identity.pubkey,
+ ED25519_PUBKEY_LEN))
+ return 0;
+ }
+ return 1;
+}
+
/**
- * Find channel by digest of the remote endpoint
+ * Find channel by RSA/Ed25519 identity of of the remote endpoint
+ *
+ * This function looks up a channel by the digest of its remote endpoint's RSA
+ * identity key. If <b>ed_id</b> is provided and nonzero, only a channel
+ * matching the <b>ed_id</b> will be returned.
*
- * This function looks up a channel by the digest of its remote endpoint in
- * the channel digest map. It's possible that more than one channel to a
- * given endpoint exists. Use channel_next_with_digest() to walk the list.
+ * It's possible that more than one channel to a given endpoint exists. Use
+ * channel_next_with_rsa_identity() to walk the list of channels; make sure
+ * to test for Ed25519 identity match too (as appropriate)
*/
-
channel_t *
-channel_find_by_remote_digest(const char *identity_digest)
+channel_find_by_remote_identity(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_t *rv = NULL;
channel_idmap_entry_t *ent, search;
- tor_assert(identity_digest);
+ tor_assert(rsa_id_digest); /* For now, we require that every channel have
+ * an RSA identity, and that every lookup
+ * contain an RSA identity */
+ if (ed_id && ed25519_public_key_is_zero(ed_id)) {
+ /* Treat zero as meaning "We don't care about the presence or absence of
+ * an Ed key", not "There must be no Ed key". */
+ ed_id = NULL;
+ }
- memcpy(search.digest, identity_digest, DIGEST_LEN);
+ memcpy(search.digest, rsa_id_digest, DIGEST_LEN);
ent = HT_FIND(channel_idmap, &channel_identity_map, &search);
if (ent) {
rv = TOR_LIST_FIRST(&ent->channel_list);
}
+ while (rv && ! channel_remote_identity_matches(rv, rsa_id_digest, ed_id)) {
+ rv = channel_next_with_rsa_identity(rv);
+ }
return rv;
}
@@ -740,7 +828,7 @@ channel_find_by_remote_digest(const char *identity_digest)
*/
channel_t *
-channel_next_with_digest(channel_t *chan)
+channel_next_with_rsa_identity(channel_t *chan)
{
tor_assert(chan);
@@ -748,6 +836,83 @@ channel_next_with_digest(channel_t *chan)
}
/**
+ * Relays run this once an hour to look over our list of channels to other
+ * relays. It prints out some statistics if there are multiple connections
+ * to many relays.
+ *
+ * This function is similar to connection_or_set_bad_connections(),
+ * and probably could be adapted to replace it, if it was modified to actually
+ * take action on any of these connections.
+ */
+void
+channel_check_for_duplicates(void)
+{
+ channel_idmap_entry_t **iter;
+ channel_t *chan;
+ int total_relay_connections = 0, total_relays = 0, total_canonical = 0;
+ int total_half_canonical = 0;
+ int total_gt_one_connection = 0, total_gt_two_connections = 0;
+ int total_gt_four_connections = 0;
+
+ HT_FOREACH(iter, channel_idmap, &channel_identity_map) {
+ int connections_to_relay = 0;
+
+ /* Only consider relay connections */
+ if (!connection_or_digest_is_known_relay((char*)(*iter)->digest))
+ continue;
+
+ total_relays++;
+
+ for (chan = TOR_LIST_FIRST(&(*iter)->channel_list); chan;
+ chan = channel_next_with_rsa_identity(chan)) {
+
+ if (CHANNEL_CONDEMNED(chan) || !CHANNEL_IS_OPEN(chan))
+ continue;
+
+ connections_to_relay++;
+ total_relay_connections++;
+
+ if (chan->is_canonical(chan, 0)) total_canonical++;
+
+ if (!chan->is_canonical_to_peer && chan->is_canonical(chan, 0)
+ && chan->is_canonical(chan, 1)) {
+ total_half_canonical++;
+ }
+ }
+
+ if (connections_to_relay > 1) total_gt_one_connection++;
+ if (connections_to_relay > 2) total_gt_two_connections++;
+ if (connections_to_relay > 4) total_gt_four_connections++;
+ }
+
+#define MIN_RELAY_CONNECTIONS_TO_WARN 5
+
+ /* If we average 1.5 or more connections per relay, something is wrong */
+ if (total_relays > MIN_RELAY_CONNECTIONS_TO_WARN &&
+ total_relay_connections >= 1.5*total_relays) {
+ log_notice(LD_OR,
+ "Your relay has a very large number of connections to other relays. "
+ "Is your outbound address the same as your relay address? "
+ "Found %d connections to %d relays. Found %d current canonical "
+ "connections, in %d of which we were a non-canonical peer. "
+ "%d relays had more than 1 connection, %d had more than 2, and "
+ "%d had more than 4 connections.",
+ total_relay_connections, total_relays, total_canonical,
+ total_half_canonical, total_gt_one_connection,
+ total_gt_two_connections, total_gt_four_connections);
+ } else {
+ log_info(LD_OR, "Performed connection pruning. "
+ "Found %d connections to %d relays. Found %d current canonical "
+ "connections, in %d of which we were a non-canonical peer. "
+ "%d relays had more than 1 connection, %d had more than 2, and "
+ "%d had more than 4 connections.",
+ total_relay_connections, total_relays, total_canonical,
+ total_half_canonical, total_gt_one_connection,
+ total_gt_two_connections, total_gt_four_connections);
+ }
+}
+
+/**
* Initialize a channel
*
* This function should be called by subclasses to set up some per-channel
@@ -761,7 +926,7 @@ channel_init(channel_t *chan)
tor_assert(chan);
/* Assign an ID and bump the counter */
- chan->global_identifier = n_channels_allocated++;
+ chan->global_identifier = ++n_channels_allocated;
/* Init timestamp */
chan->timestamp_last_had_circuits = time(NULL);
@@ -800,7 +965,7 @@ channel_init_listener(channel_listener_t *chan_l)
tor_assert(chan_l);
/* Assign an ID and bump the counter */
- chan_l->global_identifier = n_channels_allocated++;
+ chan_l->global_identifier = ++n_channels_allocated;
/* Timestamp it */
channel_listener_timestamp_created(chan_l);
@@ -837,8 +1002,13 @@ channel_free(channel_t *chan)
circuitmux_set_policy(chan->cmux, NULL);
}
+ /* Remove all timers and associated handle entries now */
+ timer_free(chan->padding_timer);
+ channel_handle_free(chan->timer_handle);
+ channel_handles_clear(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 +1048,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
@@ -915,8 +1085,13 @@ channel_force_free(channel_t *chan)
circuitmux_set_policy(chan->cmux, NULL);
}
+ /* Remove all timers and associated handle entries now */
+ timer_free(chan->padding_timer);
+ channel_handle_free(chan->timer_handle);
+ channel_handles_clear(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 +1133,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
@@ -1407,10 +1582,10 @@ channel_clear_identity_digest(channel_t *chan)
* This function sets the identity digest of the remote endpoint for a
* channel; this is intended for use by the lower layer.
*/
-
void
channel_set_identity_digest(channel_t *chan,
- const char *identity_digest)
+ const char *identity_digest,
+ const ed25519_public_key_t *ed_identity)
{
int was_in_digest_map, should_be_in_digest_map, state_not_in_map;
@@ -1449,6 +1624,11 @@ channel_set_identity_digest(channel_t *chan,
memset(chan->identity_digest, 0,
sizeof(chan->identity_digest));
}
+ if (ed_identity) {
+ memcpy(&chan->ed25519_identity, ed_identity, sizeof(*ed_identity));
+ } else {
+ memset(&chan->ed25519_identity, 0, sizeof(*ed_identity));
+ }
/* Put it in the digest map if we should */
if (should_be_in_digest_map)
@@ -1712,7 +1892,7 @@ channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q)
rv = get_cell_network_size(chan->wide_circ_ids);
break;
default:
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
return rv;
@@ -1812,45 +1992,58 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q)
}
}
-/**
- * Write a cell to a channel
+/** Write a generic cell type to a channel
*
- * Write a fixed-length cell to a channel using the write_cell() method.
- * This is equivalent to the pre-channels connection_or_write_cell_to_buf();
- * it is called by the transport-independent code to deliver a cell to a
- * channel for transmission.
+ * Write a generic cell to a channel. It is called by channel_write_cell(),
+ * channel_write_var_cell() and channel_write_packed_cell() in order to reduce
+ * code duplication. Notice that it takes cell as pointer of type void,
+ * this can be dangerous because no type check is performed.
*/
void
-channel_write_cell(channel_t *chan, cell_t *cell)
+channel_write_cell_generic_(channel_t *chan, const char *cell_type,
+ void *cell, cell_queue_entry_t *q)
{
- cell_queue_entry_t q;
tor_assert(chan);
tor_assert(cell);
if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding cell_t %p on closing channel %p with "
- "global ID "U64_FORMAT, cell, chan,
+ log_debug(LD_CHANNEL, "Discarding %c %p on closing channel %p with "
+ "global ID "U64_FORMAT, *cell_type, cell, chan,
U64_PRINTF_ARG(chan->global_identifier));
tor_free(cell);
return;
}
-
log_debug(LD_CHANNEL,
- "Writing cell_t %p to channel %p with global ID "
- U64_FORMAT,
+ "Writing %c %p to channel %p with global ID "
+ U64_FORMAT, *cell_type,
cell, chan, U64_PRINTF_ARG(chan->global_identifier));
- q.type = CELL_QUEUE_FIXED;
- q.u.fixed.cell = cell;
- channel_write_cell_queue_entry(chan, &q);
-
+ channel_write_cell_queue_entry(chan, q);
/* Update the queue size estimate */
channel_update_xmit_queue_size(chan);
}
/**
+ * Write a cell to a channel
+ *
+ * Write a fixed-length cell to a channel using the write_cell() method.
+ * This is equivalent to the pre-channels connection_or_write_cell_to_buf();
+ * it is called by the transport-independent code to deliver a cell to a
+ * channel for transmission.
+ */
+
+void
+channel_write_cell(channel_t *chan, cell_t *cell)
+{
+ cell_queue_entry_t q;
+ q.type = CELL_QUEUE_FIXED;
+ q.u.fixed.cell = cell;
+ channel_write_cell_generic_(chan, "cell_t", cell, &q);
+}
+
+/**
* Write a packed cell to a channel
*
* Write a packed cell to a channel using the write_cell() method. This is
@@ -1862,30 +2055,9 @@ void
channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell)
{
cell_queue_entry_t q;
-
- tor_assert(chan);
- tor_assert(packed_cell);
-
- if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding packed_cell_t %p on closing channel %p "
- "with global ID "U64_FORMAT, packed_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
- packed_cell_free(packed_cell);
- return;
- }
-
- log_debug(LD_CHANNEL,
- "Writing packed_cell_t %p to channel %p with global ID "
- U64_FORMAT,
- packed_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
-
q.type = CELL_QUEUE_PACKED;
q.u.packed.packed_cell = packed_cell;
- channel_write_cell_queue_entry(chan, &q);
-
- /* Update the queue size estimate */
- channel_update_xmit_queue_size(chan);
+ channel_write_cell_generic_(chan, "packed_cell_t", packed_cell, &q);
}
/**
@@ -1901,30 +2073,9 @@ void
channel_write_var_cell(channel_t *chan, var_cell_t *var_cell)
{
cell_queue_entry_t q;
-
- tor_assert(chan);
- tor_assert(var_cell);
-
- if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding var_cell_t %p on closing channel %p "
- "with global ID "U64_FORMAT, var_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
- var_cell_free(var_cell);
- return;
- }
-
- log_debug(LD_CHANNEL,
- "Writing var_cell_t %p to channel %p with global ID "
- U64_FORMAT,
- var_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
-
q.type = CELL_QUEUE_VAR;
q.u.var.var_cell = var_cell;
- channel_write_cell_queue_entry(chan, &q);
-
- /* Update the queue size estimate */
- channel_update_xmit_queue_size(chan);
+ channel_write_cell_generic_(chan, "var_cell_t", var_cell, &q);
}
/**
@@ -1935,8 +2086,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell)
* are appropriate to the state transition in question.
*/
-void
-channel_change_state(channel_t *chan, channel_state_t to_state)
+static void
+channel_change_state_(channel_t *chan, channel_state_t to_state)
{
channel_state_t from_state;
unsigned char was_active, is_active;
@@ -2055,18 +2206,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
estimated_total_queue_size += chan->bytes_in_queue;
}
- /* Tell circuits if we opened and stuff */
- if (to_state == CHANNEL_STATE_OPEN) {
- channel_do_open_actions(chan);
- chan->has_been_open = 1;
-
- /* Check for queued cells to process */
- if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
- channel_process_cells(chan);
- if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
- channel_flush_cells(chan);
- } else if (to_state == CHANNEL_STATE_CLOSED ||
- to_state == CHANNEL_STATE_ERROR) {
+ if (to_state == CHANNEL_STATE_CLOSED ||
+ to_state == CHANNEL_STATE_ERROR) {
/* Assert that all queues are empty */
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue));
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue));
@@ -2074,6 +2215,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
}
/**
+ * As channel_change_state_, but change the state to any state but open.
+ */
+void
+channel_change_state(channel_t *chan, channel_state_t to_state)
+{
+ tor_assert(to_state != CHANNEL_STATE_OPEN);
+ channel_change_state_(chan, to_state);
+}
+
+/**
+ * As channel_change_state, but change the state to open.
+ */
+void
+channel_change_state_open(channel_t *chan)
+{
+ channel_change_state_(chan, CHANNEL_STATE_OPEN);
+
+ /* Tell circuits if we opened and stuff */
+ channel_do_open_actions(chan);
+ chan->has_been_open = 1;
+
+ /* Check for queued cells to process */
+ if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
+ channel_process_cells(chan);
+ if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
+ channel_flush_cells(chan);
+}
+
+/**
* Change channel listener state
*
* This internal and subclass use only function is used to change channel
@@ -2281,121 +2451,120 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan,
free_q = 0;
handed_off = 0;
- if (1) {
- /* Figure out how big it is for statistical purposes */
- cell_size = channel_get_cell_queue_entry_size(chan, q);
- /*
- * Okay, we have a good queue entry, try to give it to the lower
- * layer.
- */
- switch (q->type) {
- case CELL_QUEUE_FIXED:
- if (q->u.fixed.cell) {
- if (chan->write_cell(chan,
- q->u.fixed.cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_FIXED "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- case CELL_QUEUE_PACKED:
- if (q->u.packed.packed_cell) {
- if (chan->write_packed_cell(chan,
- q->u.packed.packed_cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_PACKED "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- case CELL_QUEUE_VAR:
- if (q->u.var.var_cell) {
- if (chan->write_var_cell(chan,
- q->u.var.var_cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_VAR "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- default:
- /* Unknown type, log and free it */
- log_info(LD_CHANNEL,
- "Saw an unknown cell queue entry type %d on channel %p "
- "(global ID " U64_FORMAT "; ignoring it."
- " Someone should fix this.",
- q->type, chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Figure out how big it is for statistical purposes */
+ cell_size = channel_get_cell_queue_entry_size(chan, q);
+ /*
+ * Okay, we have a good queue entry, try to give it to the lower
+ * layer.
+ */
+ switch (q->type) {
+ case CELL_QUEUE_FIXED:
+ if (q->u.fixed.cell) {
+ if (chan->write_cell(chan,
+ q->u.fixed.cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
free_q = 1;
- handed_off = 0;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_FIXED "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
}
+ break;
+ case CELL_QUEUE_PACKED:
+ if (q->u.packed.packed_cell) {
+ if (chan->write_packed_cell(chan,
+ q->u.packed.packed_cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
+ free_q = 1;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_PACKED "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
+ }
+ break;
+ case CELL_QUEUE_VAR:
+ if (q->u.var.var_cell) {
+ if (chan->write_var_cell(chan,
+ q->u.var.var_cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
+ free_q = 1;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_VAR "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
+ }
+ break;
+ default:
+ /* Unknown type, log and free it */
+ log_info(LD_CHANNEL,
+ "Saw an unknown cell queue entry type %d on channel %p "
+ "(global ID " U64_FORMAT "; ignoring it."
+ " Someone should fix this.",
+ q->type, chan, U64_PRINTF_ARG(chan->global_identifier));
+ free_q = 1;
+ handed_off = 0;
+ }
+ /*
+ * if free_q is set, we used it and should remove the queue entry;
+ * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't
+ * accessing freed memory
+ */
+ if (free_q) {
+ TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next);
/*
- * if free_q is set, we used it and should remove the queue entry;
- * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't
- * accessing freed memory
+ * ...and we handed a cell off to the lower layer, so we should
+ * update the counters.
*/
- if (free_q) {
- TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next);
- /*
- * ...and we handed a cell off to the lower layer, so we should
- * update the counters.
- */
- ++n_channel_cells_passed_to_lower_layer;
- --n_channel_cells_in_queues;
- n_channel_bytes_passed_to_lower_layer += cell_size;
- n_channel_bytes_in_queues -= cell_size;
- channel_assert_counter_consistency();
- /* Update the channel's queue size too */
- chan->bytes_in_queue -= cell_size;
- /* Finally, free q */
- cell_queue_entry_free(q, handed_off);
- q = NULL;
- }
+ ++n_channel_cells_passed_to_lower_layer;
+ --n_channel_cells_in_queues;
+ n_channel_bytes_passed_to_lower_layer += cell_size;
+ n_channel_bytes_in_queues -= cell_size;
+ channel_assert_counter_consistency();
+ /* Update the channel's queue size too */
+ chan->bytes_in_queue -= cell_size;
+ /* Finally, free q */
+ cell_queue_entry_free(q, handed_off);
+ q = NULL;
+ } else {
/* No cell removed from list, so we can't go on any further */
- else break;
+ break;
}
}
}
@@ -2541,16 +2710,6 @@ channel_do_open_actions(channel_t *chan)
if (started_here) {
circuit_build_times_network_is_live(get_circuit_build_times_mutable());
rep_hist_note_connect_succeeded(chan->identity_digest, now);
- if (entry_guard_register_connect_status(
- chan->identity_digest, 1, 0, now) < 0) {
- /* Close any circuits pending on this channel. We leave it in state
- * 'open' though, because it didn't actually *fail* -- we just
- * chose not to use it. */
- log_debug(LD_OR,
- "New entry guard was reachable, but closing this "
- "connection so we can retry the earlier entry guards.");
- close_origin_circuits = 1;
- }
router_set_status(chan->identity_digest, 1);
} else {
/* only report it to the geoip module if it's not a known router */
@@ -2569,6 +2728,19 @@ channel_do_open_actions(channel_t *chan)
}
}
+ /* Disable or reduce padding according to user prefs. */
+ if (chan->padding_enabled || get_options()->ConnectionPadding == 1) {
+ if (!get_options()->ConnectionPadding) {
+ channelpadding_disable_padding_on_channel(chan);
+ }
+
+ /* Padding can be forced and/or reduced by clients, regardless of if
+ * the channel supports it */
+ if (get_options()->ReducedConnectionPadding) {
+ channelpadding_reduce_padding_on_channel(chan);
+ }
+ }
+
circuit_n_chan_done(chan, 1, close_origin_circuits);
}
@@ -3206,6 +3378,11 @@ channel_free_all(void)
/* Geez, anything still left over just won't die ... let it leak then */
HT_CLEAR(channel_idmap, &channel_identity_map);
+ /* Same with channel_gid_map */
+ log_debug(LD_CHANNEL,
+ "Freeing channel_gid_map");
+ HT_CLEAR(channel_gid_map, &channel_gid_map);
+
log_debug(LD_CHANNEL,
"Done cleaning up after channels");
}
@@ -3223,9 +3400,10 @@ channel_free_all(void)
channel_t *
channel_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
- return channel_tls_connect(addr, port, id_digest);
+ return channel_tls_connect(addr, port, id_digest, ed_id);
}
/**
@@ -3240,22 +3418,20 @@ channel_connect(const tor_addr_t *addr, uint16_t port,
*/
int
-channel_is_better(time_t now, channel_t *a, channel_t *b,
- int forgive_new_connections)
+channel_is_better(channel_t *a, channel_t *b)
{
- int a_grace, b_grace;
int a_is_canonical, b_is_canonical;
- int a_has_circs, b_has_circs;
-
- /*
- * Do not definitively deprecate a new channel with no circuits on it
- * until this much time has passed.
- */
-#define NEW_CHAN_GRACE_PERIOD (15*60)
tor_assert(a);
tor_assert(b);
+ /* If one channel is bad for new circuits, and the other isn't,
+ * use the one that is still good. */
+ if (!channel_is_bad_for_new_circs(a) && channel_is_bad_for_new_circs(b))
+ return 1;
+ if (channel_is_bad_for_new_circs(a) && !channel_is_bad_for_new_circs(b))
+ return 0;
+
/* Check if one is canonical and the other isn't first */
a_is_canonical = channel_is_canonical(a);
b_is_canonical = channel_is_canonical(b);
@@ -3263,26 +3439,31 @@ channel_is_better(time_t now, channel_t *a, channel_t *b,
if (a_is_canonical && !b_is_canonical) return 1;
if (!a_is_canonical && b_is_canonical) return 0;
+ /* Check if we suspect that one of the channels will be preferred
+ * by the peer */
+ if (a->is_canonical_to_peer && !b->is_canonical_to_peer) return 1;
+ if (!a->is_canonical_to_peer && b->is_canonical_to_peer) return 0;
+
/*
- * Okay, if we're here they tied on canonicity. Next we check if
- * they have any circuits, and if one does and the other doesn't,
- * we prefer the one that does, unless we are forgiving and the
- * one that has no circuits is in its grace period.
+ * Okay, if we're here they tied on canonicity, the prefer the older
+ * connection, so that the adversary can't create a new connection
+ * and try to switch us over to it (which will leak information
+ * about long-lived circuits). Additionally, switching connections
+ * too often makes us more vulnerable to attacks like Torscan and
+ * passive netflow-based equivalents.
+ *
+ * Connections will still only live for at most a week, due to
+ * the check in connection_or_group_set_badness() against
+ * TIME_BEFORE_OR_CONN_IS_TOO_OLD, which marks old connections as
+ * unusable for new circuits after 1 week. That check sets
+ * is_bad_for_new_circs, which is checked in channel_get_for_extend().
+ *
+ * We check channel_is_bad_for_new_circs() above here anyway, for safety.
*/
+ if (channel_when_created(a) < channel_when_created(b)) return 1;
+ else if (channel_when_created(a) > channel_when_created(b)) return 0;
- a_has_circs = (channel_num_circuits(a) > 0);
- b_has_circs = (channel_num_circuits(b) > 0);
- a_grace = (forgive_new_connections &&
- (now < channel_when_created(a) + NEW_CHAN_GRACE_PERIOD));
- b_grace = (forgive_new_connections &&
- (now < channel_when_created(b) + NEW_CHAN_GRACE_PERIOD));
-
- if (a_has_circs && !b_has_circs && !b_grace) return 1;
- if (!a_has_circs && b_has_circs && !a_grace) return 0;
-
- /* They tied on circuits too; just prefer whichever is newer */
-
- if (channel_when_created(a) > channel_when_created(b)) return 1;
+ if (channel_num_circuits(a) > channel_num_circuits(b)) return 1;
else return 0;
}
@@ -3298,7 +3479,8 @@ channel_is_better(time_t now, channel_t *a, channel_t *b,
*/
channel_t *
-channel_get_for_extend(const char *digest,
+channel_get_for_extend(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out)
@@ -3306,19 +3488,18 @@ channel_get_for_extend(const char *digest,
channel_t *chan, *best = NULL;
int n_inprogress_goodaddr = 0, n_old = 0;
int n_noncanonical = 0, n_possible = 0;
- time_t now = approx_time();
tor_assert(msg_out);
tor_assert(launch_out);
- chan = channel_find_by_remote_digest(digest);
+ chan = channel_find_by_remote_identity(rsa_id_digest, ed_id);
/* Walk the list, unrefing the old one and refing the new at each
* iteration.
*/
- for (; chan; chan = channel_next_with_digest(chan)) {
+ for (; chan; chan = channel_next_with_rsa_identity(chan)) {
tor_assert(tor_memeq(chan->identity_digest,
- digest, DIGEST_LEN));
+ rsa_id_digest, DIGEST_LEN));
if (CHANNEL_CONDEMNED(chan))
continue;
@@ -3329,6 +3510,11 @@ channel_get_for_extend(const char *digest,
continue;
}
+ /* The Ed25519 key has to match too */
+ if (!channel_remote_identity_matches(chan, rsa_id_digest, ed_id)) {
+ continue;
+ }
+
/* Never return a non-open connection. */
if (!CHANNEL_IS_OPEN(chan)) {
/* If the address matches, don't launch a new connection for this
@@ -3371,7 +3557,7 @@ channel_get_for_extend(const char *digest,
continue;
}
- if (channel_is_better(now, chan, best, 0))
+ if (channel_is_better(chan, best))
best = chan;
}
@@ -4153,8 +4339,12 @@ channel_timestamp_active(channel_t *chan)
time_t now = time(NULL);
tor_assert(chan);
+ chan->timestamp_xfer_ms = monotime_coarse_absolute_msec();
chan->timestamp_active = now;
+
+ /* Clear any potential netflow padding timer. We're active */
+ chan->next_padding_time_ms = 0;
}
/**
@@ -4237,11 +4427,14 @@ void
channel_timestamp_recv(channel_t *chan)
{
time_t now = time(NULL);
-
tor_assert(chan);
+ chan->timestamp_xfer_ms = monotime_coarse_absolute_msec();
chan->timestamp_active = now;
chan->timestamp_recv = now;
+
+ /* Clear any potential netflow padding timer. We're active */
+ chan->next_padding_time_ms = 0;
}
/**
@@ -4254,11 +4447,15 @@ void
channel_timestamp_xmit(channel_t *chan)
{
time_t now = time(NULL);
-
tor_assert(chan);
+ chan->timestamp_xfer_ms = monotime_coarse_absolute_msec();
+
chan->timestamp_active = now;
chan->timestamp_xmit = now;
+
+ /* Clear any potential netflow padding timer. We're active */
+ chan->next_padding_time_ms = 0;
}
/***************************************************************
@@ -4500,6 +4697,81 @@ channel_set_circid_type,(channel_t *chan,
}
}
+/** Helper for channel_update_bad_for_new_circs(): Perform the
+ * channel_update_bad_for_new_circs operation on all channels in <b>lst</b>,
+ * all of which MUST have the same RSA ID. (They MAY have different
+ * Ed25519 IDs.) */
+static void
+channel_rsa_id_group_set_badness(struct channel_list_s *lst, int force)
+{
+ /*XXXX This function should really be about channels. 15056 */
+ channel_t *chan;
+
+ /* First, get a minimal list of the ed25519 identites */
+ smartlist_t *ed_identities = smartlist_new();
+ TOR_LIST_FOREACH(chan, lst, next_with_same_id) {
+ uint8_t *id_copy =
+ tor_memdup(&chan->ed25519_identity.pubkey, DIGEST256_LEN);
+ smartlist_add(ed_identities, id_copy);
+ }
+ smartlist_sort_digests256(ed_identities);
+ smartlist_uniq_digests256(ed_identities);
+
+ /* Now, for each Ed identity, build a smartlist and find the best entry on
+ * it. */
+ smartlist_t *or_conns = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(ed_identities, const uint8_t *, ed_id) {
+ TOR_LIST_FOREACH(chan, lst, next_with_same_id) {
+ channel_tls_t *chantls = BASE_CHAN_TO_TLS(chan);
+ if (tor_memneq(ed_id, &chan->ed25519_identity.pubkey, DIGEST256_LEN))
+ continue;
+ or_connection_t *orconn = chantls->conn;
+ if (orconn) {
+ tor_assert(orconn->chan == chantls);
+ smartlist_add(or_conns, orconn);
+ }
+ }
+
+ connection_or_group_set_badness_(or_conns, force);
+ smartlist_clear(or_conns);
+ } SMARTLIST_FOREACH_END(ed_id);
+
+ /* XXXX 15056 we may want to do something special with connections that have
+ * no set Ed25519 identity! */
+
+ smartlist_free(or_conns);
+
+ SMARTLIST_FOREACH(ed_identities, uint8_t *, ed_id, tor_free(ed_id));
+ smartlist_free(ed_identities);
+}
+
+/** Go through all the channels (or if <b>digest</b> is non-NULL, just
+ * the OR connections with that digest), and set the is_bad_for_new_circs
+ * flag based on the rules in connection_or_group_set_badness() (or just
+ * always set it if <b>force</b> is true).
+ */
+void
+channel_update_bad_for_new_circs(const char *digest, int force)
+{
+ if (digest) {
+ channel_idmap_entry_t *ent;
+ channel_idmap_entry_t search;
+ memset(&search, 0, sizeof(search));
+ memcpy(search.digest, digest, DIGEST_LEN);
+ ent = HT_FIND(channel_idmap, &channel_identity_map, &search);
+ if (ent) {
+ channel_rsa_id_group_set_badness(&ent->channel_list, force);
+ }
+ return;
+ }
+
+ /* no digest; just look at everything. */
+ channel_idmap_entry_t **iter;
+ HT_FOREACH(iter, channel_idmap, &channel_identity_map) {
+ channel_rsa_id_group_set_badness(&(*iter)->channel_list, force);
+ }
+}
+
/**
* Update the estimated number of bytes queued to transmit for this channel,
* and notify the scheduler. The estimate includes both the channel queue and
@@ -4524,8 +4796,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 a8c337e107..2d0ec39924 100644
--- a/src/or/channel.h
+++ b/src/or/channel.h
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -11,6 +11,8 @@
#include "or.h"
#include "circuitmux.h"
+#include "timers.h"
+#include "handles.h"
/* Channel handler function pointer typedefs */
typedef void (*channel_listener_fn_ptr)(channel_listener_t *, channel_t *);
@@ -22,6 +24,17 @@ TOR_SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s);
typedef struct chan_cell_queue chan_cell_queue_t;
/**
+ * This enum is used by channelpadding to decide when to pad channels.
+ * Don't add values to it without updating the checks in
+ * channelpadding_decide_to_pad_channel().
+ */
+typedef enum {
+ CHANNEL_USED_NOT_USED_FOR_FULL_CIRCS = 0,
+ CHANNEL_USED_FOR_FULL_CIRCS,
+ CHANNEL_USED_FOR_USER_TRAFFIC,
+} channel_usage_info_t;
+
+/**
* Channel struct; see the channel_t typedef in or.h. A channel is an
* abstract interface for the OR-to-OR connection, similar to connection_or_t,
* but without the strong coupling to the underlying TLS implementation. They
@@ -34,11 +47,17 @@ struct channel_s {
/** Magic number for type-checking cast macros */
uint32_t magic;
+ /** List entry for hashtable for global-identifier lookup. */
+ HT_ENTRY(channel_s) gidmap_node;
+
+ /** Handle entry for handle-based lookup */
+ HANDLE_ENTRY(channel, channel_s);
+
/** Current channel state */
channel_state_t state;
/** Globally unique ID number for a channel over the lifetime of a Tor
- * process.
+ * process. This may not be 0.
*/
uint64_t global_identifier;
@@ -48,6 +67,61 @@ struct channel_s {
/** has this channel ever been open? */
unsigned int has_been_open:1;
+ /**
+ * This field indicates if the other side has enabled or disabled
+ * padding via either the link protocol version or
+ * channelpadding_negotiate cells.
+ *
+ * Clients can override this with ConnectionPadding in torrc to
+ * disable or force padding to relays, but relays cannot override the
+ * client's request.
+ */
+ unsigned int padding_enabled:1;
+
+ /** Cached value of our decision to pad (to avoid expensive
+ * checks during critical path statistics counting). */
+ unsigned int currently_padding:1;
+
+ /** Is there a pending netflow padding callback? */
+ unsigned int pending_padding_callback:1;
+
+ /** Is our peer likely to consider this channel canonical? */
+ unsigned int is_canonical_to_peer:1;
+
+ /** Has this channel ever been used for non-directory traffic?
+ * Used to decide what channels to pad, and when. */
+ channel_usage_info_t channel_usage;
+
+ /** When should we send a cell for netflow padding, in absolute
+ * milliseconds since monotime system start. 0 means no padding
+ * is scheduled. */
+ uint64_t next_padding_time_ms;
+
+ /** The callback pointer for the padding callbacks */
+ tor_timer_t *padding_timer;
+ /** The handle to this channel (to free on canceled timers) */
+ struct channel_handle_t *timer_handle;
+
+ /**
+ * These two fields specify the minimum and maximum negotiated timeout
+ * values for inactivity (send or receive) before we decide to pad a
+ * channel. These fields can be set either via a PADDING_NEGOTIATE cell,
+ * or the torrc option ReducedConnectionPadding. The consensus parameters
+ * nf_ito_low and nf_ito_high are used to ensure that padding can only be
+ * negotiated to be less frequent than what is specified in the consensus.
+ * (This is done to prevent wingnut clients from requesting excessive
+ * padding).
+ *
+ * The actual timeout value is randomly chosen between these two values
+ * as per the table in channelpadding_get_netflow_inactive_timeout_ms(),
+ * after ensuring that these values do not specify lower timeouts than
+ * the consensus parameters.
+ *
+ * If these are 0, we have not negotiated or specified custom padding
+ * times, and instead use consensus defaults. */
+ uint16_t padding_timeout_low_ms;
+ uint16_t padding_timeout_high_ms;
+
/** Why did we close?
*/
enum {
@@ -87,10 +161,22 @@ struct channel_s {
time_t timestamp_created; /* Channel created */
time_t timestamp_active; /* Any activity */
+ /**
+ * This is a high-resolution monotonic timestamp that marks when we
+ * believe the channel has actually sent or received data to/from
+ * the wire. Right now, it is used to determine when we should send
+ * a padding cell for channelpadding.
+ *
+ * XXX: Are we setting timestamp_xfer_ms in the right places to
+ * accurately reflect actual network data transfer? Or might this be
+ * very wrong wrt when bytes actually go on the wire?
+ */
+ uint64_t timestamp_xfer_ms;
+
/* 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 */
@@ -153,16 +239,32 @@ struct channel_s {
int (*write_var_cell)(channel_t *, var_cell_t *);
/**
- * Hash of the public RSA key for the other side's identity key, or
- * zeroes if the other side hasn't shown us a valid identity key.
+ * Hash of the public RSA key for the other side's RSA identity key -- or
+ * zeroes if we don't have an RSA identity in mind for the other side, and
+ * it hasn't shown us one.
+ *
+ * Note that this is the RSA identity that we hope the other side has -- not
+ * necessarily its true identity. Don't believe this identity unless
+ * authentication has happened.
*/
char identity_digest[DIGEST_LEN];
+ /**
+ * Ed25519 key for the other side of this channel -- or zeroes if we don't
+ * have an Ed25519 identity in mind for the other side, and it hasn't shown
+ * us one.
+ *
+ * Note that this is the identity that we hope the other side has -- not
+ * necessarily its true identity. Don't believe this identity unless
+ * authentication has happened.
+ */
+ ed25519_public_key_t ed25519_identity;
+
/** Nickname of the OR on the other side, or NULL if none. */
char *nickname;
/**
- * Linked list of channels with the same identity digest, for the
- * digest->channel map
+ * Linked list of channels with the same RSA identity digest, for use with
+ * the digest->channel map
*/
TOR_LIST_ENTRY(channel_s) next_with_same_id;
@@ -198,8 +300,8 @@ struct channel_s {
unsigned int is_bad_for_new_circs:1;
/** True iff we have decided that the other end of this connection
- * is a client. Channels with this flag set should never be used
- * to satisfy an EXTEND request. */
+ * is a client or bridge relay. Connections with this flag set should never
+ * be used to satisfy an EXTEND request. */
unsigned int is_client:1;
/** Set if the channel was initiated remotely (came from a listener) */
@@ -273,7 +375,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 */
@@ -382,6 +484,9 @@ struct cell_queue_entry_s {
STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue);
STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off);
+
+void channel_write_cell_generic_(channel_t *chan, const char *cell_type,
+ void *cell, cell_queue_entry_t *q);
#endif
/* Channel operations for subclasses and internal use only */
@@ -417,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l);
/* State/metadata setters */
void channel_change_state(channel_t *chan, channel_state_t to_state);
+void channel_change_state_open(channel_t *chan);
void channel_clear_identity_digest(channel_t *chan);
void channel_clear_remote_end(channel_t *chan);
void channel_mark_local(channel_t *chan);
@@ -424,7 +530,8 @@ void channel_mark_incoming(channel_t *chan);
void channel_mark_outgoing(channel_t *chan);
void channel_mark_remote(channel_t *chan);
void channel_set_identity_digest(channel_t *chan,
- const char *identity_digest);
+ const char *identity_digest,
+ const ed25519_public_key_t *ed_identity);
void channel_set_remote_end(channel_t *chan,
const char *identity_digest,
const char *nickname);
@@ -469,6 +576,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 */
@@ -482,27 +593,29 @@ int channel_send_destroy(circid_t circ_id, channel_t *chan,
*/
channel_t * channel_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest);
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id);
-channel_t * channel_get_for_extend(const char *digest,
+channel_t * channel_get_for_extend(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out);
/* Ask which of two channels is better for circuit-extension purposes */
-int channel_is_better(time_t now,
- channel_t *a, channel_t *b,
- int forgive_new_connections);
+int channel_is_better(channel_t *a, channel_t *b);
/** Channel lookups
*/
channel_t * channel_find_by_global_id(uint64_t global_identifier);
-channel_t * channel_find_by_remote_digest(const char *identity_digest);
+channel_t * channel_find_by_remote_identity(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id);
/** For things returned by channel_find_by_remote_digest(), walk the list.
+ * The RSA key will match for all returned elements; the Ed25519 key might not.
*/
-channel_t * channel_next_with_digest(channel_t *chan);
+channel_t * channel_next_with_rsa_identity(channel_t *chan);
/*
* Helper macros to lookup state of given channel.
@@ -573,6 +686,9 @@ void channel_listener_dump_statistics(channel_listener_t *chan_l,
int severity);
void channel_listener_dump_transport_statistics(channel_listener_t *chan_l,
int severity);
+void channel_check_for_duplicates(void);
+
+void channel_update_bad_for_new_circs(const char *digest, int force);
/* Flow control queries */
uint64_t channel_get_global_queue_estimate(void);
@@ -600,5 +716,8 @@ int packed_cell_is_destroy(channel_t *chan,
const packed_cell_t *packed_cell,
circid_t *circid_out);
+/* Declare the handle helpers */
+HANDLE_DECL(channel, channel_s,)
+
#endif
diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c
new file mode 100644
index 0000000000..bed2489837
--- /dev/null
+++ b/src/or/channelpadding.c
@@ -0,0 +1,759 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2015, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/* TOR_CHANNEL_INTERNAL_ define needed for an O(1) implementation of
+ * channelpadding_channel_to_channelinfo() */
+#define TOR_CHANNEL_INTERNAL_
+
+#include "or.h"
+#include "channel.h"
+#include "channelpadding.h"
+#include "channeltls.h"
+#include "config.h"
+#include "networkstatus.h"
+#include "connection.h"
+#include "connection_or.h"
+#include "main.h"
+#include "rephist.h"
+#include "router.h"
+#include "compat_time.h"
+#include <event2/event.h>
+
+STATIC int channelpadding_get_netflow_inactive_timeout_ms(const channel_t *);
+STATIC int channelpadding_send_disable_command(channel_t *);
+STATIC int64_t channelpadding_compute_time_until_pad_for_netflow(channel_t *);
+
+/** The total number of pending channelpadding timers */
+static uint64_t total_timers_pending;
+
+/** These are cached consensus parameters for netflow */
+/** The timeout lower bound that is allowed before sending padding */
+static int consensus_nf_ito_low;
+/** The timeout upper bound that is allowed before sending padding */
+static int consensus_nf_ito_high;
+/** The timeout lower bound that is allowed before sending reduced padding */
+static int consensus_nf_ito_low_reduced;
+/** The timeout upper bound that is allowed before sending reduced padding */
+static int consensus_nf_ito_high_reduced;
+/** The connection timeout between relays */
+static int consensus_nf_conntimeout_relays;
+/** The connection timeout for client connections */
+static int consensus_nf_conntimeout_clients;
+/** Should we pad before circuits are actually used for client data? */
+static int consensus_nf_pad_before_usage;
+/** Should we pad relay-to-relay connections? */
+static int consensus_nf_pad_relays;
+
+#define TOR_MSEC_PER_SEC 1000
+#define TOR_USEC_PER_MSEC 1000
+
+/**
+ * How often do we get called by the connection housekeeping (ie: once
+ * per second) */
+#define TOR_HOUSEKEEPING_CALLBACK_MSEC 1000
+/**
+ * Additional extra time buffer on the housekeeping callback, since
+ * it can be delayed. This extra slack is used to decide if we should
+ * schedule a timer or wait for the next callback. */
+#define TOR_HOUSEKEEPING_CALLBACK_SLACK_MSEC 100
+
+/**
+ * This macro tells us if either end of the channel is connected to a client.
+ * (If we're not a server, we're definitely a client. If the channel thinks
+ * its a client, use that. Then finally verify in the consensus).
+ */
+#define CHANNEL_IS_CLIENT(chan, options) \
+ (!public_server_mode((options)) || (chan)->is_client || \
+ !connection_or_digest_is_known_relay((chan)->identity_digest))
+
+/**
+ * This function is called to update cached consensus parameters every time
+ * there is a consensus update. This allows us to move the consensus param
+ * search off of the critical path, so it does not need to be evaluated
+ * for every single connection, every second.
+ */
+void
+channelpadding_new_consensus_params(networkstatus_t *ns)
+{
+#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW 1500
+#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH 9500
+#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN 0
+#define DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX 60000
+ consensus_nf_ito_low = networkstatus_get_param(ns, "nf_ito_low",
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_LOW,
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_MIN,
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX);
+ consensus_nf_ito_high = networkstatus_get_param(ns, "nf_ito_high",
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH,
+ consensus_nf_ito_low,
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX);
+
+#define DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW 9000
+#define DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH 14000
+#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN 0
+#define DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX 60000
+ consensus_nf_ito_low_reduced =
+ networkstatus_get_param(ns, "nf_ito_low_reduced",
+ DFLT_NETFLOW_REDUCED_KEEPALIVE_LOW,
+ DFLT_NETFLOW_REDUCED_KEEPALIVE_MIN,
+ DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX);
+
+ consensus_nf_ito_high_reduced =
+ networkstatus_get_param(ns, "nf_ito_high_reduced",
+ DFLT_NETFLOW_REDUCED_KEEPALIVE_HIGH,
+ consensus_nf_ito_low_reduced,
+ DFLT_NETFLOW_REDUCED_KEEPALIVE_MAX);
+
+#define CONNTIMEOUT_RELAYS_DFLT (60*60) // 1 hour
+#define CONNTIMEOUT_RELAYS_MIN 60
+#define CONNTIMEOUT_RELAYS_MAX (7*24*60*60) // 1 week
+ consensus_nf_conntimeout_relays =
+ networkstatus_get_param(ns, "nf_conntimeout_relays",
+ CONNTIMEOUT_RELAYS_DFLT,
+ CONNTIMEOUT_RELAYS_MIN,
+ CONNTIMEOUT_RELAYS_MAX);
+
+#define CIRCTIMEOUT_CLIENTS_DFLT (30*60) // 30 minutes
+#define CIRCTIMEOUT_CLIENTS_MIN 60
+#define CIRCTIMEOUT_CLIENTS_MAX (24*60*60) // 24 hours
+ consensus_nf_conntimeout_clients =
+ networkstatus_get_param(ns, "nf_conntimeout_clients",
+ CIRCTIMEOUT_CLIENTS_DFLT,
+ CIRCTIMEOUT_CLIENTS_MIN,
+ CIRCTIMEOUT_CLIENTS_MAX);
+
+ consensus_nf_pad_before_usage =
+ networkstatus_get_param(ns, "nf_pad_before_usage", 1, 0, 1);
+
+ consensus_nf_pad_relays =
+ networkstatus_get_param(ns, "nf_pad_relays", 0, 0, 1);
+}
+
+/**
+ * Get a random netflow inactive timeout keepalive period in milliseconds,
+ * the range for which is determined by consensus parameters, negotiation,
+ * configuration, or default values. The consensus parameters enforce the
+ * minimum possible value, to avoid excessively frequent padding.
+ *
+ * The ranges for this value were chosen to be low enough to ensure that
+ * routers do not emit a new netflow record for a connection due to it
+ * being idle.
+ *
+ * Specific timeout values for major routers are listed in Proposal 251.
+ * No major router appeared capable of setting an inactive timeout below 10
+ * seconds, so we set the defaults below that value, since we can always
+ * scale back if it ends up being too much padding.
+ *
+ * Returns the next timeout period (in milliseconds) after which we should
+ * send a padding packet, or 0 if padding is disabled.
+ */
+STATIC int
+channelpadding_get_netflow_inactive_timeout_ms(const channel_t *chan)
+{
+ int low_timeout = consensus_nf_ito_low;
+ int high_timeout = consensus_nf_ito_high;
+ int X1, X2;
+
+ if (low_timeout == 0 && low_timeout == high_timeout)
+ return 0; // No padding
+
+ /* If we have negotiated different timeout values, use those, but
+ * don't allow them to be lower than the consensus ones */
+ if (chan->padding_timeout_low_ms && chan->padding_timeout_high_ms) {
+ low_timeout = MAX(low_timeout, chan->padding_timeout_low_ms);
+ high_timeout = MAX(high_timeout, chan->padding_timeout_high_ms);
+ }
+
+ if (low_timeout == high_timeout)
+ return low_timeout; // No randomization
+
+ /*
+ * This MAX() hack is here because we apply the timeout on both the client
+ * and the server. This creates the situation where the total time before
+ * sending a packet in either direction is actually
+ * min(client_timeout,server_timeout).
+ *
+ * If X is a random variable uniform from 0..R-1 (where R=high-low),
+ * then Y=max(X,X) has Prob(Y == i) = (2.0*i + 1)/(R*R).
+ *
+ * If we create a third random variable Z=min(Y,Y), then it turns out that
+ * Exp[Z] ~= Exp[X]. Here's a table:
+ *
+ * R Exp[X] Exp[Z] Exp[min(X,X)] Exp[max(X,X)]
+ * 2000 999.5 1066 666.2 1332.8
+ * 3000 1499.5 1599.5 999.5 1999.5
+ * 5000 2499.5 2666 1666.2 3332.8
+ * 6000 2999.5 3199.5 1999.5 3999.5
+ * 7000 3499.5 3732.8 2332.8 4666.2
+ * 8000 3999.5 4266.2 2666.2 5332.8
+ * 10000 4999.5 5328 3332.8 6666.2
+ * 15000 7499.5 7995 4999.5 9999.5
+ * 20000 9900.5 10661 6666.2 13332.8
+ *
+ * In other words, this hack makes it so that when both the client and
+ * the guard are sending this padding, then the averages work out closer
+ * to the midpoint of the range, making the overhead easier to tune.
+ * If only one endpoint is padding (for example: if the relay does not
+ * support padding, but the client has set ConnectionPadding 1; or
+ * if the relay does support padding, but the client has set
+ * ReducedConnectionPadding 1), then the defense will still prevent
+ * record splitting, but with less overhead than the midpoint
+ * (as seen by the Exp[max(X,X)] column).
+ *
+ * To calculate average padding packet frequency (and thus overhead),
+ * index into the table by picking a row based on R = high-low. Then,
+ * use the appropriate column (Exp[Z] for two-sided padding, and
+ * Exp[max(X,X)] for one-sided padding). Finally, take this value
+ * and add it to the low timeout value. This value is the average
+ * frequency which padding packets will be sent.
+ */
+
+ X1 = crypto_rand_int(high_timeout - low_timeout);
+ X2 = crypto_rand_int(high_timeout - low_timeout);
+ return low_timeout + MAX(X1, X2);
+}
+
+/**
+ * Update this channel's padding settings based on the PADDING_NEGOTIATE
+ * contents.
+ *
+ * Returns -1 on error; 1 on success.
+ */
+int
+channelpadding_update_padding_for_channel(channel_t *chan,
+ const channelpadding_negotiate_t *pad_vars)
+{
+ if (pad_vars->version != 0) {
+ static ratelim_t version_limit = RATELIM_INIT(600);
+
+ log_fn_ratelim(&version_limit,LOG_PROTOCOL_WARN,LD_PROTOCOL,
+ "Got a PADDING_NEGOTIATE cell with an unknown version. Ignoring.");
+ return -1;
+ }
+
+ // We should not allow malicious relays to disable or reduce padding for
+ // us as clients. In fact, we should only accept this cell at all if we're
+ // operating as a relay. Bridges should not accept it from relays, either
+ // (only from their clients).
+ if ((get_options()->BridgeRelay &&
+ connection_or_digest_is_known_relay(chan->identity_digest)) ||
+ !get_options()->ORPort_set) {
+ static ratelim_t relay_limit = RATELIM_INIT(600);
+
+ log_fn_ratelim(&relay_limit,LOG_PROTOCOL_WARN,LD_PROTOCOL,
+ "Got a PADDING_NEGOTIATE from relay at %s (%s). "
+ "This should not happen.",
+ chan->get_remote_descr(chan, 0),
+ hex_str(chan->identity_digest, DIGEST_LEN));
+ return -1;
+ }
+
+ chan->padding_enabled = (pad_vars->command == CHANNELPADDING_COMMAND_START);
+
+ /* Min must not be lower than the current consensus parameter
+ nf_ito_low. */
+ chan->padding_timeout_low_ms = MAX(consensus_nf_ito_low,
+ pad_vars->ito_low_ms);
+
+ /* Max must not be lower than ito_low_ms */
+ chan->padding_timeout_high_ms = MAX(chan->padding_timeout_low_ms,
+ pad_vars->ito_high_ms);
+
+ log_fn(LOG_INFO,LD_OR,
+ "Negotiated padding=%d, lo=%d, hi=%d on "U64_FORMAT,
+ chan->padding_enabled, chan->padding_timeout_low_ms,
+ chan->padding_timeout_high_ms,
+ U64_PRINTF_ARG(chan->global_identifier));
+
+ return 1;
+}
+
+/**
+ * Sends a CELL_PADDING_NEGOTIATE on the channel to tell the other side not
+ * to send padding.
+ *
+ * Returns -1 on error, 0 on success.
+ */
+STATIC int
+channelpadding_send_disable_command(channel_t *chan)
+{
+ channelpadding_negotiate_t disable;
+ cell_t cell;
+
+ tor_assert(BASE_CHAN_TO_TLS(chan)->conn->link_proto >=
+ MIN_LINK_PROTO_FOR_CHANNEL_PADDING);
+
+ memset(&cell, 0, sizeof(cell_t));
+ memset(&disable, 0, sizeof(channelpadding_negotiate_t));
+ cell.command = CELL_PADDING_NEGOTIATE;
+
+ channelpadding_negotiate_set_command(&disable, CHANNELPADDING_COMMAND_STOP);
+
+ if (channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE,
+ &disable) < 0)
+ return -1;
+
+ if (chan->write_cell(chan, &cell) == 1)
+ return 0;
+ else
+ return -1;
+}
+
+/**
+ * Sends a CELL_PADDING_NEGOTIATE on the channel to tell the other side to
+ * resume sending padding at some rate.
+ *
+ * Returns -1 on error, 0 on success.
+ */
+int
+channelpadding_send_enable_command(channel_t *chan, uint16_t low_timeout,
+ uint16_t high_timeout)
+{
+ channelpadding_negotiate_t enable;
+ cell_t cell;
+
+ tor_assert(BASE_CHAN_TO_TLS(chan)->conn->link_proto >=
+ MIN_LINK_PROTO_FOR_CHANNEL_PADDING);
+
+ memset(&cell, 0, sizeof(cell_t));
+ memset(&enable, 0, sizeof(channelpadding_negotiate_t));
+ cell.command = CELL_PADDING_NEGOTIATE;
+
+ channelpadding_negotiate_set_command(&enable, CHANNELPADDING_COMMAND_START);
+ channelpadding_negotiate_set_ito_low_ms(&enable, low_timeout);
+ channelpadding_negotiate_set_ito_high_ms(&enable, high_timeout);
+
+ if (channelpadding_negotiate_encode(cell.payload, CELL_PAYLOAD_SIZE,
+ &enable) < 0)
+ return -1;
+
+ if (chan->write_cell(chan, &cell) == 1)
+ return 0;
+ else
+ return -1;
+}
+
+/**
+ * Sends a CELL_PADDING cell on a channel if it has been idle since
+ * our callback was scheduled.
+ *
+ * This function also clears the pending padding timer and the callback
+ * flags.
+ */
+static void
+channelpadding_send_padding_cell_for_callback(channel_t *chan)
+{
+ cell_t cell;
+
+ /* Check that the channel is still valid and open */
+ if (!chan || chan->state != CHANNEL_STATE_OPEN) {
+ if (chan) chan->pending_padding_callback = 0;
+ log_fn(LOG_INFO,LD_OR,
+ "Scheduled a netflow padding cell, but connection already closed.");
+ return;
+ }
+
+ /* We should have a pending callback flag set. */
+ if (BUG(chan->pending_padding_callback == 0))
+ return;
+
+ chan->pending_padding_callback = 0;
+
+ if (!chan->next_padding_time_ms ||
+ chan->has_queued_writes(chan)) {
+ /* We must have been active before the timer fired */
+ chan->next_padding_time_ms = 0;
+ return;
+ }
+
+ {
+ uint64_t now = monotime_coarse_absolute_msec();
+
+ log_fn(LOG_INFO,LD_OR,
+ "Sending netflow keepalive on "U64_FORMAT" to %s (%s) after "
+ I64_FORMAT" ms. Delta "I64_FORMAT"ms",
+ U64_PRINTF_ARG(chan->global_identifier),
+ safe_str_client(chan->get_remote_descr(chan, 0)),
+ safe_str_client(hex_str(chan->identity_digest, DIGEST_LEN)),
+ U64_PRINTF_ARG(now - chan->timestamp_xfer_ms),
+ U64_PRINTF_ARG(now - chan->next_padding_time_ms));
+ }
+
+ /* Clear the timer */
+ chan->next_padding_time_ms = 0;
+
+ /* Send the padding cell. This will cause the channel to get a
+ * fresh timestamp_active */
+ memset(&cell, 0, sizeof(cell));
+ cell.command = CELL_PADDING;
+ chan->write_cell(chan, &cell);
+}
+
+/**
+ * tor_timer callback function for us to send padding on an idle channel.
+ *
+ * This function just obtains the channel from the callback handle, ensures
+ * it is still valid, and then hands it off to
+ * channelpadding_send_padding_cell_for_callback(), which checks if
+ * the channel is still idle before sending padding.
+ */
+static void
+channelpadding_send_padding_callback(tor_timer_t *timer, void *args,
+ const struct monotime_t *when)
+{
+ channel_t *chan = channel_handle_get((struct channel_handle_t*)args);
+ (void)timer; (void)when;
+
+ if (chan && CHANNEL_CAN_HANDLE_CELLS(chan)) {
+ /* Hrmm.. It might be nice to have an equivalent to assert_connection_ok
+ * for channels. Then we could get rid of the channeltls dependency */
+ tor_assert(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn)->magic ==
+ OR_CONNECTION_MAGIC);
+ assert_connection_ok(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn), approx_time());
+
+ channelpadding_send_padding_cell_for_callback(chan);
+ } else {
+ log_fn(LOG_INFO,LD_OR,
+ "Channel closed while waiting for timer.");
+ }
+
+ total_timers_pending--;
+}
+
+/**
+ * Schedules a callback to send padding on a channel in_ms milliseconds from
+ * now.
+ *
+ * Returns CHANNELPADDING_WONTPAD on error, CHANNELPADDING_PADDING_SENT if we
+ * sent the packet immediately without a timer, and
+ * CHANNELPADDING_PADDING_SCHEDULED if we decided to schedule a timer.
+ */
+static channelpadding_decision_t
+channelpadding_schedule_padding(channel_t *chan, int in_ms)
+{
+ struct timeval timeout;
+ tor_assert(!chan->pending_padding_callback);
+
+ if (in_ms <= 0) {
+ chan->pending_padding_callback = 1;
+ channelpadding_send_padding_cell_for_callback(chan);
+ return CHANNELPADDING_PADDING_SENT;
+ }
+
+ timeout.tv_sec = in_ms/TOR_MSEC_PER_SEC;
+ timeout.tv_usec = (in_ms%TOR_USEC_PER_MSEC)*TOR_USEC_PER_MSEC;
+
+ if (!chan->timer_handle) {
+ chan->timer_handle = channel_handle_new(chan);
+ }
+
+ if (chan->padding_timer) {
+ timer_set_cb(chan->padding_timer,
+ channelpadding_send_padding_callback,
+ chan->timer_handle);
+ } else {
+ chan->padding_timer = timer_new(channelpadding_send_padding_callback,
+ chan->timer_handle);
+ }
+ timer_schedule(chan->padding_timer, &timeout);
+
+ rep_hist_padding_count_timers(++total_timers_pending);
+
+ chan->pending_padding_callback = 1;
+ return CHANNELPADDING_PADDING_SCHEDULED;
+}
+
+/**
+ * Calculates the number of milliseconds from now to schedule a padding cell.
+ *
+ * Returns the number of milliseconds from now (relative) to schedule the
+ * padding callback. If the padding timer is more than 1.1 seconds in the
+ * future, we return -1, to avoid scheduling excessive callbacks. If padding
+ * is disabled in the consensus, we return -2.
+ *
+ * Side-effects: Updates chan->next_padding_time_ms, storing an (absolute, not
+ * relative) millisecond representation of when we should send padding, unless
+ * other activity happens first. This side-effect allows us to avoid
+ * scheduling a libevent callback until we're within 1.1 seconds of the padding
+ * time.
+ */
+#define CHANNELPADDING_TIME_LATER -1
+#define CHANNELPADDING_TIME_DISABLED -2
+STATIC int64_t
+channelpadding_compute_time_until_pad_for_netflow(channel_t *chan)
+{
+ uint64_t long_now = monotime_coarse_absolute_msec();
+
+ if (!chan->next_padding_time_ms) {
+ /* If the below line or crypto_rand_int() shows up on a profile,
+ * we can avoid getting a timeout until we're at least nf_ito_lo
+ * from a timeout window. That will prevent us from setting timers
+ * on connections that were active up to 1.5 seconds ago.
+ * Idle connections should only call this once every 5.5s on average
+ * though, so that might be a micro-optimization for little gain. */
+ int64_t padding_timeout =
+ channelpadding_get_netflow_inactive_timeout_ms(chan);
+
+ if (!padding_timeout)
+ return CHANNELPADDING_TIME_DISABLED;
+
+ chan->next_padding_time_ms = padding_timeout
+ + chan->timestamp_xfer_ms;
+ }
+
+ /* If the next padding time is beyond the maximum possible consensus value,
+ * then this indicates a clock jump, so just send padding now. This is
+ * better than using monotonic time because we want to avoid the situation
+ * where we wait around forever for monotonic time to move forward after
+ * a clock jump far into the past.
+ */
+ if (chan->next_padding_time_ms > long_now +
+ DFLT_NETFLOW_INACTIVE_KEEPALIVE_MAX) {
+ tor_fragile_assert();
+ log_warn(LD_BUG,
+ "Channel padding timeout scheduled "I64_FORMAT"ms in the future. "
+ "Did the monotonic clock just jump?",
+ I64_PRINTF_ARG(chan->next_padding_time_ms - long_now));
+ return 0; /* Clock jumped: Send padding now */
+ }
+
+ /* If the timeout will expire before the next time we're called (1000ms
+ from now, plus some slack), then calculate the number of milliseconds
+ from now which we should send padding, so we can schedule a callback
+ then.
+ */
+ if (long_now +
+ (TOR_HOUSEKEEPING_CALLBACK_MSEC + TOR_HOUSEKEEPING_CALLBACK_SLACK_MSEC)
+ >= chan->next_padding_time_ms) {
+ int64_t ms_until_pad_for_netflow = chan->next_padding_time_ms -
+ long_now;
+ /* If the padding time is in the past, that means that libevent delayed
+ * calling the once-per-second callback due to other work taking too long.
+ * See https://bugs.torproject.org/22212 and
+ * https://bugs.torproject.org/16585. This is a systemic problem
+ * with being single-threaded, but let's emit a notice if this
+ * is long enough in the past that we might have missed a netflow window,
+ * and allowed a router to emit a netflow frame, just so we don't forget
+ * about it entirely.. */
+#define NETFLOW_MISSED_WINDOW (150000 - DFLT_NETFLOW_INACTIVE_KEEPALIVE_HIGH)
+ if (ms_until_pad_for_netflow < 0) {
+ int severity = (ms_until_pad_for_netflow < -NETFLOW_MISSED_WINDOW)
+ ? LOG_NOTICE : LOG_INFO;
+ log_fn(severity, LD_OR,
+ "Channel padding timeout scheduled "I64_FORMAT"ms in the past. ",
+ I64_PRINTF_ARG(-ms_until_pad_for_netflow));
+ return 0; /* Clock jumped: Send padding now */
+ }
+
+ return ms_until_pad_for_netflow;
+ }
+ return CHANNELPADDING_TIME_LATER;
+}
+
+/**
+ * Returns a randomized value for channel idle timeout in seconds.
+ * The channel idle timeout governs how quickly we close a channel
+ * after its last circuit has disappeared.
+ *
+ * There are three classes of channels:
+ * 1. Client+non-canonical. These live for 3-4.5 minutes
+ * 2. relay to relay. These live for 45-75 min by default
+ * 3. Reduced padding clients. These live for 1.5-2.25 minutes.
+ *
+ * Also allows the default relay-to-relay value to be controlled by the
+ * consensus.
+ */
+unsigned int
+channelpadding_get_channel_idle_timeout(const channel_t *chan,
+ int is_canonical)
+{
+ const or_options_t *options = get_options();
+ unsigned int timeout;
+
+ /* Non-canonical and client channels only last for 3-4.5 min when idle */
+ if (!is_canonical || CHANNEL_IS_CLIENT(chan, options)) {
+#define CONNTIMEOUT_CLIENTS_BASE 180 // 3 to 4.5 min
+ timeout = CONNTIMEOUT_CLIENTS_BASE
+ + crypto_rand_int(CONNTIMEOUT_CLIENTS_BASE/2);
+ } else { // Canonical relay-to-relay channels
+ // 45..75min or consensus +/- 25%
+ timeout = consensus_nf_conntimeout_relays;
+ timeout = 3*timeout/4 + crypto_rand_int(timeout/2);
+ }
+
+ /* If ReducedConnectionPadding is set, we want to halve the duration of
+ * the channel idle timeout, since reducing the additional time that
+ * a channel stays open will reduce the total overhead for making
+ * new channels. This reduction in overhead/channel expense
+ * is important for mobile users. The option cannot be set by relays.
+ *
+ * We also don't reduce any values for timeout that the user explicitly
+ * set.
+ */
+ if (options->ReducedConnectionPadding
+ && !options->CircuitsAvailableTimeout) {
+ timeout /= 2;
+ }
+
+ return timeout;
+}
+
+/**
+ * This function controls how long we keep idle circuits open,
+ * and how long we build predicted circuits. This behavior is under
+ * the control of channelpadding because circuit availability is the
+ * dominant factor in channel lifespan, which influences total padding
+ * overhead.
+ *
+ * Returns a randomized number of seconds in a range from
+ * CircuitsAvailableTimeout to 2*CircuitsAvailableTimeout. This value is halved
+ * if ReducedConnectionPadding is set. The default value of
+ * CircuitsAvailableTimeout can be controlled by the consensus.
+ */
+int
+channelpadding_get_circuits_available_timeout(void)
+{
+ const or_options_t *options = get_options();
+ int timeout = options->CircuitsAvailableTimeout;
+
+ if (!timeout) {
+ timeout = consensus_nf_conntimeout_clients;
+
+ /* If ReducedConnectionPadding is set, we want to halve the duration of
+ * the channel idle timeout, since reducing the additional time that
+ * a channel stays open will reduce the total overhead for making
+ * new connections. This reduction in overhead/connection expense
+ * is important for mobile users. The option cannot be set by relays.
+ *
+ * We also don't reduce any values for timeout that the user explicitly
+ * set.
+ */
+ if (options->ReducedConnectionPadding) {
+ // half the value to 15..30min by default
+ timeout /= 2;
+ }
+ }
+
+ // 30..60min by default
+ timeout = timeout + crypto_rand_int(timeout);
+
+ return timeout;
+}
+
+/**
+ * Calling this function on a channel causes it to tell the other side
+ * not to send padding, and disables sending padding from this side as well.
+ */
+void
+channelpadding_disable_padding_on_channel(channel_t *chan)
+{
+ chan->padding_enabled = 0;
+
+ // Send cell to disable padding on the other end
+ channelpadding_send_disable_command(chan);
+}
+
+/**
+ * Calling this function on a channel causes it to tell the other side
+ * not to send padding, and reduces the rate that padding is sent from
+ * this side.
+ */
+void
+channelpadding_reduce_padding_on_channel(channel_t *chan)
+{
+ /* Padding can be forced and reduced by clients, regardless of if
+ * the channel supports it. So we check for support here before
+ * sending any commands. */
+ if (chan->padding_enabled) {
+ channelpadding_send_disable_command(chan);
+ }
+
+ chan->padding_timeout_low_ms = consensus_nf_ito_low_reduced;
+ chan->padding_timeout_high_ms = consensus_nf_ito_high_reduced;
+
+ log_fn(LOG_INFO,LD_OR,
+ "Reduced padding on channel "U64_FORMAT": lo=%d, hi=%d",
+ U64_PRINTF_ARG(chan->global_identifier),
+ chan->padding_timeout_low_ms, chan->padding_timeout_high_ms);
+}
+
+/**
+ * This function is called once per second by run_connection_housekeeping(),
+ * but only if the channel is still open, valid, and non-wedged.
+ *
+ * It decides if and when we should send a padding cell, and if needed,
+ * schedules a callback to send that cell at the appropriate time.
+ *
+ * Returns an enum that represents the current padding decision state.
+ * Return value is currently used only by unit tests.
+ */
+channelpadding_decision_t
+channelpadding_decide_to_pad_channel(channel_t *chan)
+{
+ const or_options_t *options = get_options();
+
+ /* Only pad open channels */
+ if (chan->state != CHANNEL_STATE_OPEN)
+ return CHANNELPADDING_WONTPAD;
+
+ if (chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS) {
+ if (!consensus_nf_pad_before_usage)
+ return CHANNELPADDING_WONTPAD;
+ } else if (chan->channel_usage != CHANNEL_USED_FOR_USER_TRAFFIC) {
+ return CHANNELPADDING_WONTPAD;
+ }
+
+ if (chan->pending_padding_callback)
+ return CHANNELPADDING_PADDING_ALREADY_SCHEDULED;
+
+ /* Don't pad the channel if we didn't negotiate it, but still
+ * allow clients to force padding if options->ChannelPadding is
+ * explicitly set to 1.
+ */
+ if (!chan->padding_enabled && options->ConnectionPadding != 1) {
+ return CHANNELPADDING_WONTPAD;
+ }
+
+ if (!chan->has_queued_writes(chan)) {
+ int is_client_channel = 0;
+
+ if (CHANNEL_IS_CLIENT(chan, options)) {
+ is_client_channel = 1;
+ }
+
+ /* If nf_pad_relays=1 is set in the consensus, we pad
+ * on *all* idle connections, relay-relay or relay-client.
+ * Otherwise pad only for client+bridge cons */
+ if (is_client_channel || consensus_nf_pad_relays) {
+ int64_t pad_time_ms =
+ channelpadding_compute_time_until_pad_for_netflow(chan);
+
+ if (pad_time_ms == CHANNELPADDING_TIME_DISABLED) {
+ return CHANNELPADDING_WONTPAD;
+ } else if (pad_time_ms == CHANNELPADDING_TIME_LATER) {
+ chan->currently_padding = 1;
+ return CHANNELPADDING_PADLATER;
+ } else {
+ if (BUG(pad_time_ms > INT_MAX)) {
+ pad_time_ms = INT_MAX;
+ }
+ /* We have to schedule a callback because we're called exactly once per
+ * second, but we don't want padding packets to go out exactly on an
+ * integer multiple of seconds. This callback will only be scheduled
+ * if we're within 1.1 seconds of the padding time.
+ */
+ chan->currently_padding = 1;
+ return channelpadding_schedule_padding(chan, (int)pad_time_ms);
+ }
+ } else {
+ chan->currently_padding = 0;
+ return CHANNELPADDING_WONTPAD;
+ }
+ } else {
+ return CHANNELPADDING_PADLATER;
+ }
+}
+
diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h
new file mode 100644
index 0000000000..2708ee9739
--- /dev/null
+++ b/src/or/channelpadding.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2015, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file circuitbuild.h
+ * \brief Header file for circuitbuild.c.
+ **/
+#ifndef TOR_CHANNELPADDING_H
+#define TOR_CHANNELPADDING_H
+
+#include "channelpadding_negotiation.h"
+
+typedef enum {
+ CHANNELPADDING_WONTPAD,
+ CHANNELPADDING_PADLATER,
+ CHANNELPADDING_PADDING_SCHEDULED,
+ CHANNELPADDING_PADDING_ALREADY_SCHEDULED,
+ CHANNELPADDING_PADDING_SENT,
+} channelpadding_decision_t;
+
+channelpadding_decision_t channelpadding_decide_to_pad_channel(channel_t
+ *chan);
+int channelpadding_update_padding_for_channel(channel_t *,
+ const channelpadding_negotiate_t
+ *chan);
+
+void channelpadding_disable_padding_on_channel(channel_t *chan);
+void channelpadding_reduce_padding_on_channel(channel_t *chan);
+int channelpadding_send_enable_command(channel_t *chan, uint16_t low_timeout,
+ uint16_t high_timeout);
+
+int channelpadding_get_circuits_available_timeout(void);
+unsigned int channelpadding_get_channel_idle_timeout(const channel_t *, int);
+void channelpadding_new_consensus_params(networkstatus_t *ns);
+
+#endif
+
diff --git a/src/or/channeltls.c b/src/or/channeltls.c
index 2128b0924d..6547451181 100644
--- a/src/or/channeltls.c
+++ b/src/or/channeltls.c
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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,16 +44,22 @@
#include "channeltls.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
+#include "command.h"
#include "config.h"
#include "connection.h"
#include "connection_or.h"
#include "control.h"
+#include "entrynodes.h"
#include "link_handshake.h"
#include "relay.h"
#include "rephist.h"
#include "router.h"
#include "routerlist.h"
#include "scheduler.h"
+#include "torcert.h"
+#include "networkstatus.h"
+#include "channelpadding_negotiation.h"
+#include "channelpadding.h"
/** How many CELL_PADDING cells have we received, ever? */
uint64_t stats_n_padding_cells_processed = 0;
@@ -51,7 +79,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 */
@@ -97,6 +125,8 @@ static void channel_tls_process_netinfo_cell(cell_t *cell,
static int command_allowed_before_handshake(uint8_t command);
static int enter_v3_handshake_with_cell(var_cell_t *cell,
channel_tls_t *tlschan);
+static void channel_tls_process_padding_negotiate_cell(cell_t *cell,
+ channel_tls_t *chan);
/**
* Do parts of channel_tls_t initialization common to channel_tls_connect()
@@ -116,7 +146,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;
@@ -147,7 +177,8 @@ channel_tls_common_init(channel_tls_t *tlschan)
channel_t *
channel_tls_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_tls_t *tlschan = tor_malloc_zero(sizeof(*tlschan));
channel_t *chan = &(tlschan->base_);
@@ -175,7 +206,7 @@ channel_tls_connect(const tor_addr_t *addr, uint16_t port,
channel_mark_outgoing(chan);
/* Set up or_connection stuff */
- tlschan->conn = connection_or_connect(addr, port, id_digest, tlschan);
+ tlschan->conn = connection_or_connect(addr, port, id_digest, ed_id, tlschan);
/* connection_or_connect() will fill in tlschan->conn */
if (!(tlschan->conn)) {
chan->reason_for_closing = CHANNEL_CLOSE_FOR_ERROR;
@@ -445,7 +476,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 +493,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,
@@ -574,7 +606,7 @@ channel_tls_get_remote_descr_method(channel_t *chan, int flags)
break;
default:
/* Something's broken in channel.c */
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
} else {
strlcpy(buf, "(No connection)", sizeof(buf));
@@ -643,7 +675,7 @@ channel_tls_is_canonical_method(channel_t *chan, int req)
break;
default:
/* This shouldn't happen; channel.c is broken if it does */
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
}
/* else return 0 for tlschan->conn == NULL */
@@ -707,6 +739,15 @@ channel_tls_matches_target_method(channel_t *chan,
return 0;
}
+ /* real_addr is the address this connection came from.
+ * base_.addr is updated by connection_or_init_conn_from_address()
+ * to be the address in the descriptor. It may be tempting to
+ * allow either address to be allowed, but if we did so, it would
+ * enable someone who steals a relay's keys to impersonate/MITM it
+ * from anywhere on the Internet! (Because they could make long-lived
+ * TLS connections from anywhere to all relays, and wait for them to
+ * be used for extends).
+ */
return tor_addr_eq(&(tlschan->conn->real_addr), target);
}
@@ -797,6 +838,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;
@@ -951,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan,
* We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or
* CHANNEL_STATE_MAINT on this.
*/
- channel_change_state(base_chan, CHANNEL_STATE_OPEN);
+ channel_change_state_open(base_chan);
/* We might have just become writeable; check and tell the scheduler */
if (connection_or_num_cells_writeable(conn) > 0) {
scheduler_channel_wants_writes(base_chan);
@@ -1067,8 +1109,19 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn)
if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3)
or_handshake_state_record_cell(conn, conn->handshake_state, cell, 1);
+ /* We note that we're on the internet whenever we read a cell. This is
+ * a fast operation. */
+ entry_guards_note_internet_connectivity(get_guard_selection_info());
+ rep_hist_padding_count_read(PADDING_TYPE_TOTAL);
+
+ if (TLS_CHAN_TO_BASE(chan)->currently_padding)
+ rep_hist_padding_count_read(PADDING_TYPE_ENABLED_TOTAL);
+
switch (cell->command) {
case CELL_PADDING:
+ rep_hist_padding_count_read(PADDING_TYPE_CELL);
+ if (TLS_CHAN_TO_BASE(chan)->currently_padding)
+ rep_hist_padding_count_read(PADDING_TYPE_ENABLED_CELL);
++stats_n_padding_cells_processed;
/* do nothing */
break;
@@ -1079,6 +1132,10 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn)
++stats_n_netinfo_cells_processed;
PROCESS_CELL(netinfo, cell, chan);
break;
+ case CELL_PADDING_NEGOTIATE:
+ ++stats_n_netinfo_cells_processed;
+ PROCESS_CELL(padding_negotiate, cell, chan);
+ break;
case CELL_CREATE:
case CELL_CREATE_FAST:
case CELL_CREATED:
@@ -1189,6 +1246,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:
@@ -1243,6 +1302,10 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn)
return;
}
+ /* We note that we're on the internet whenever we read a cell. This is
+ * a fast operation. */
+ entry_guards_note_internet_connectivity(get_guard_selection_info());
+
/* Now handle the cell */
switch (var_cell->command) {
@@ -1528,9 +1591,12 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan)
/* We set this after sending the verions cell. */
/*XXXXX symbolic const.*/
- chan->base_.wide_circ_ids =
+ TLS_CHAN_TO_BASE(chan)->wide_circ_ids =
chan->conn->link_proto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS;
- chan->conn->wide_circ_ids = chan->base_.wide_circ_ids;
+ chan->conn->wide_circ_ids = TLS_CHAN_TO_BASE(chan)->wide_circ_ids;
+
+ TLS_CHAN_TO_BASE(chan)->padding_enabled =
+ chan->conn->link_proto >= MIN_LINK_PROTO_FOR_CHANNEL_PADDING;
if (send_certs) {
if (connection_or_send_certs_cell(chan->conn) < 0) {
@@ -1557,6 +1623,43 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan)
}
/**
+ * Process a 'padding_negotiate' cell
+ *
+ * This function is called to handle an incoming PADDING_NEGOTIATE cell;
+ * enable or disable padding accordingly, and read and act on its timeout
+ * value contents.
+ */
+static void
+channel_tls_process_padding_negotiate_cell(cell_t *cell, channel_tls_t *chan)
+{
+ channelpadding_negotiate_t *negotiation;
+ tor_assert(cell);
+ tor_assert(chan);
+ tor_assert(chan->conn);
+
+ if (chan->conn->link_proto < MIN_LINK_PROTO_FOR_CHANNEL_PADDING) {
+ log_fn(LOG_PROTOCOL_WARN, LD_OR,
+ "Received a PADDING_NEGOTIATE cell on v%d connection; dropping.",
+ chan->conn->link_proto);
+ return;
+ }
+
+ if (channelpadding_negotiate_parse(&negotiation, cell->payload,
+ CELL_PAYLOAD_SIZE) < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_OR,
+ "Received malformed PADDING_NEGOTIATE cell on v%d connection; "
+ "dropping.", chan->conn->link_proto);
+
+ return;
+ }
+
+ channelpadding_update_padding_for_channel(TLS_CHAN_TO_BASE(chan),
+ negotiation);
+
+ channelpadding_negotiate_free(negotiation);
+}
+
+/**
* Process a 'netinfo' cell
*
* This function is called to handle an incoming NETINFO cell; read and act
@@ -1573,6 +1676,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
const uint8_t *cp, *end;
uint8_t n_other_addrs;
time_t now = time(NULL);
+ const routerinfo_t *me = router_get_my_routerinfo();
long apparent_skew = 0;
tor_addr_t my_apparent_addr = TOR_ADDR_NULL;
@@ -1612,15 +1716,24 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
if (!(chan->conn->handshake_state->authenticated)) {
tor_assert(tor_digest_is_zero(
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id)));
+ authenticated_rsa_peer_id)));
+ tor_assert(tor_mem_is_zero(
+ (const char*)(chan->conn->handshake_state->
+ authenticated_ed25519_peer_id.pubkey), 32));
+ /* If the client never authenticated, it's a tor client or bridge
+ * relay, and we must not use it for EXTEND requests (nor could we, as
+ * there are no authenticated peer IDs) */
+ channel_mark_client(TLS_CHAN_TO_BASE(chan));
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), NULL,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
connection_or_init_conn_from_address(chan->conn,
&(chan->conn->base_.addr),
chan->conn->base_.port,
+ /* zero, checked above */
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id),
+ authenticated_rsa_peer_id),
+ NULL, /* Ed25519 ID: Also checked as zero */
0);
}
}
@@ -1646,8 +1759,20 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) {
tor_addr_from_ipv4n(&my_apparent_addr, get_uint32(my_addr_ptr));
+
+ if (!get_options()->BridgeRelay && me &&
+ get_uint32(my_addr_ptr) == htonl(me->addr)) {
+ TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer = 1;
+ }
+
} else if (my_addr_type == RESOLVED_TYPE_IPV6 && my_addr_len == 16) {
tor_addr_from_ipv6_bytes(&my_apparent_addr, (const char *) my_addr_ptr);
+
+ if (!get_options()->BridgeRelay && me &&
+ !tor_addr_is_null(&me->ipv6_addr) &&
+ tor_addr_eq(&my_apparent_addr, &me->ipv6_addr)) {
+ TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer = 1;
+ }
}
n_other_addrs = (uint8_t) *cp++;
@@ -1663,6 +1788,14 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
connection_or_close_for_error(chan->conn, 0);
return;
}
+ /* A relay can connect from anywhere and be canonical, so
+ * long as it tells you from where it came. This may be a bit
+ * concerning.. Luckily we have another check in
+ * channel_tls_matches_target_method() to ensure that extends
+ * only go to the IP they ask for.
+ *
+ * XXX: Bleh. That check is not used if the connection is canonical.
+ */
if (tor_addr_eq(&addr, &(chan->conn->real_addr))) {
connection_or_set_canonical(chan->conn, 1);
break;
@@ -1671,6 +1804,21 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
--n_other_addrs;
}
+ if (me && !TLS_CHAN_TO_BASE(chan)->is_canonical_to_peer &&
+ channel_is_canonical(TLS_CHAN_TO_BASE(chan))) {
+ const char *descr =
+ TLS_CHAN_TO_BASE(chan)->get_remote_descr(TLS_CHAN_TO_BASE(chan), 0);
+ log_info(LD_OR,
+ "We made a connection to a relay at %s (fp=%s) but we think "
+ "they will not consider this connection canonical. They "
+ "think we are at %s, but we think its %s.",
+ safe_str(descr),
+ safe_str(hex_str(chan->conn->identity_digest, DIGEST_LEN)),
+ safe_str(tor_addr_is_null(&my_apparent_addr) ?
+ "<none>" : fmt_and_decorate_addr(&my_apparent_addr)),
+ safe_str(fmt_addr32(me->addr)));
+ }
+
/* Act on apparent skew. */
/** Warn when we get a netinfo skew with at least this value. */
#define NETINFO_NOTICE_SKEW 3600
@@ -1717,6 +1865,41 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
assert_connection_ok(TO_CONN(chan->conn),time(NULL));
}
+/** Types of certificates that we know how to parse from CERTS cells. Each
+ * type corresponds to a different encoding format. */
+typedef enum cert_encoding_t {
+ CERT_ENCODING_UNKNOWN, /**< We don't recognize this. */
+ CERT_ENCODING_X509, /**< It's an RSA key, signed with RSA, encoded in x509.
+ * (Actually, it might not be RSA. We test that later.) */
+ CERT_ENCODING_ED25519, /**< It's something signed with an Ed25519 key,
+ * encoded asa a tor_cert_t.*/
+ CERT_ENCODING_RSA_CROSSCERT, /**< It's an Ed key signed with an RSA key. */
+} cert_encoding_t;
+
+/**
+ * Given one of the certificate type codes used in a CERTS cell,
+ * return the corresponding cert_encoding_t that we should use to parse
+ * the certificate.
+ */
+static cert_encoding_t
+certs_cell_typenum_to_cert_type(int typenum)
+{
+ switch (typenum) {
+ case CERTTYPE_RSA1024_ID_LINK:
+ case CERTTYPE_RSA1024_ID_ID:
+ case CERTTYPE_RSA1024_ID_AUTH:
+ return CERT_ENCODING_X509;
+ case CERTTYPE_ED_ID_SIGN:
+ case CERTTYPE_ED_SIGN_LINK:
+ case CERTTYPE_ED_SIGN_AUTH:
+ return CERT_ENCODING_ED25519;
+ case CERTTYPE_RSA1024_ID_EDID:
+ return CERT_ENCODING_RSA_CROSSCERT;
+ default:
+ return CERT_ENCODING_UNKNOWN;
+ }
+}
+
/**
* Process a CERTS cell from a channel.
*
@@ -1732,18 +1915,24 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
* of the connection, we then authenticate the server or mark the connection.
* If it's the server side, wait for an AUTHENTICATE cell.
*/
-
STATIC void
channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
{
-#define MAX_CERT_TYPE_WANTED OR_CERT_TYPE_AUTH_1024
- tor_x509_cert_t *certs[MAX_CERT_TYPE_WANTED + 1];
+#define MAX_CERT_TYPE_WANTED CERTTYPE_RSA1024_ID_EDID
+ /* These arrays will be sparse, since a cert type can be at most one
+ * of ed/x509 */
+ tor_x509_cert_t *x509_certs[MAX_CERT_TYPE_WANTED + 1];
+ tor_cert_t *ed_certs[MAX_CERT_TYPE_WANTED + 1];
+ uint8_t *rsa_ed_cc_cert = NULL;
+ size_t rsa_ed_cc_cert_len = 0;
+
int n_certs, i;
certs_cell_t *cc = NULL;
int send_netinfo = 0;
- memset(certs, 0, sizeof(certs));
+ memset(x509_certs, 0, sizeof(x509_certs));
+ memset(ed_certs, 0, sizeof(ed_certs));
tor_assert(cell);
tor_assert(chan);
tor_assert(chan->conn);
@@ -1787,77 +1976,149 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
if (cert_type > MAX_CERT_TYPE_WANTED)
continue;
+ const cert_encoding_t ct = certs_cell_typenum_to_cert_type(cert_type);
+ switch (ct) {
+ default:
+ case CERT_ENCODING_UNKNOWN:
+ break;
+ case CERT_ENCODING_X509: {
+ tor_x509_cert_t *x509_cert = tor_x509_cert_decode(cert_body, cert_len);
+ if (!x509_cert) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Received undecodable certificate in CERTS cell from %s:%d",
+ safe_str(chan->conn->base_.address),
+ chan->conn->base_.port);
+ } else {
+ if (x509_certs[cert_type]) {
+ tor_x509_cert_free(x509_cert);
+ ERR("Duplicate x509 certificate");
+ } else {
+ x509_certs[cert_type] = x509_cert;
+ }
+ }
+ break;
+ }
+ case CERT_ENCODING_ED25519: {
+ tor_cert_t *ed_cert = tor_cert_parse(cert_body, cert_len);
+ if (!ed_cert) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Received undecodable Ed certificate "
+ "in CERTS cell from %s:%d",
+ safe_str(chan->conn->base_.address),
+ chan->conn->base_.port);
+ } else {
+ if (ed_certs[cert_type]) {
+ tor_cert_free(ed_cert);
+ ERR("Duplicate Ed25519 certificate");
+ } else {
+ ed_certs[cert_type] = ed_cert;
+ }
+ }
+ break;
+ }
- tor_x509_cert_t *cert = tor_x509_cert_decode(cert_body, cert_len);
- if (!cert) {
- log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Received undecodable certificate in CERTS cell from %s:%d",
- safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
- } else {
- if (certs[cert_type]) {
- tor_x509_cert_free(cert);
- ERR("Duplicate x509 certificate");
- } else {
- certs[cert_type] = cert;
+ case CERT_ENCODING_RSA_CROSSCERT: {
+ if (rsa_ed_cc_cert) {
+ ERR("Duplicate RSA->Ed25519 crosscert");
+ } else {
+ rsa_ed_cc_cert = tor_memdup(cert_body, cert_len);
+ rsa_ed_cc_cert_len = cert_len;
+ }
+ break;
}
}
}
- tor_x509_cert_t *id_cert = certs[OR_CERT_TYPE_ID_1024];
- tor_x509_cert_t *auth_cert = certs[OR_CERT_TYPE_AUTH_1024];
- tor_x509_cert_t *link_cert = certs[OR_CERT_TYPE_TLS_LINK];
+ /* Move the certificates we (might) want into the handshake_state->certs
+ * structure. */
+ tor_x509_cert_t *id_cert = x509_certs[CERTTYPE_RSA1024_ID_ID];
+ tor_x509_cert_t *auth_cert = x509_certs[CERTTYPE_RSA1024_ID_AUTH];
+ tor_x509_cert_t *link_cert = x509_certs[CERTTYPE_RSA1024_ID_LINK];
+ chan->conn->handshake_state->certs->auth_cert = auth_cert;
+ chan->conn->handshake_state->certs->link_cert = link_cert;
+ chan->conn->handshake_state->certs->id_cert = id_cert;
+ x509_certs[CERTTYPE_RSA1024_ID_ID] =
+ x509_certs[CERTTYPE_RSA1024_ID_AUTH] =
+ x509_certs[CERTTYPE_RSA1024_ID_LINK] = NULL;
+
+ tor_cert_t *ed_id_sign = ed_certs[CERTTYPE_ED_ID_SIGN];
+ tor_cert_t *ed_sign_link = ed_certs[CERTTYPE_ED_SIGN_LINK];
+ tor_cert_t *ed_sign_auth = ed_certs[CERTTYPE_ED_SIGN_AUTH];
+ chan->conn->handshake_state->certs->ed_id_sign = ed_id_sign;
+ chan->conn->handshake_state->certs->ed_sign_link = ed_sign_link;
+ chan->conn->handshake_state->certs->ed_sign_auth = ed_sign_auth;
+ ed_certs[CERTTYPE_ED_ID_SIGN] =
+ ed_certs[CERTTYPE_ED_SIGN_LINK] =
+ ed_certs[CERTTYPE_ED_SIGN_AUTH] = NULL;
+
+ chan->conn->handshake_state->certs->ed_rsa_crosscert = rsa_ed_cc_cert;
+ chan->conn->handshake_state->certs->ed_rsa_crosscert_len =
+ rsa_ed_cc_cert_len;
+ rsa_ed_cc_cert = NULL;
+
+ int severity;
+ /* Note that this warns more loudly about time and validity if we were
+ * _trying_ to connect to an authority, not necessarily if we _did_ connect
+ * to one. */
+ if (chan->conn->handshake_state->started_here &&
+ router_digest_is_trusted_dir(TLS_CHAN_TO_BASE(chan)->identity_digest))
+ severity = LOG_WARN;
+ else
+ severity = LOG_PROTOCOL_WARN;
+
+ const ed25519_public_key_t *checked_ed_id = NULL;
+ const common_digests_t *checked_rsa_id = NULL;
+ or_handshake_certs_check_both(severity,
+ chan->conn->handshake_state->certs,
+ chan->conn->tls,
+ time(NULL),
+ &checked_ed_id,
+ &checked_rsa_id);
+
+ if (!checked_rsa_id)
+ ERR("Invalid certificate chain!");
if (chan->conn->handshake_state->started_here) {
- int severity;
- if (! (id_cert && link_cert))
- ERR("The certs we wanted were missing");
- /* Okay. We should be able to check the certificates now. */
- if (! tor_tls_cert_matches_key(chan->conn->tls, link_cert)) {
- ERR("The link certificate didn't match the TLS public key");
- }
- /* Note that this warns more loudly about time and validity if we were
- * _trying_ to connect to an authority, not necessarily if we _did_ connect
- * to one. */
- if (router_digest_is_trusted_dir(
- TLS_CHAN_TO_BASE(chan)->identity_digest))
- severity = LOG_WARN;
- else
- severity = LOG_PROTOCOL_WARN;
-
- if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, 0))
- ERR("The link certificate was not valid");
- if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, 1))
- ERR("The ID certificate was not valid");
+ /* No more information is needed. */
chan->conn->handshake_state->authenticated = 1;
+ chan->conn->handshake_state->authenticated_rsa = 1;
{
- const common_digests_t *id_digests =
- tor_x509_cert_get_id_digests(id_cert);
+ const common_digests_t *id_digests = checked_rsa_id;
crypto_pk_t *identity_rcvd;
if (!id_digests)
ERR("Couldn't compute digests for key in ID cert");
identity_rcvd = tor_tls_cert_get_key(id_cert);
- if (!identity_rcvd)
- ERR("Internal error: Couldn't get RSA key from ID cert.");
- memcpy(chan->conn->handshake_state->authenticated_peer_id,
+ if (!identity_rcvd) {
+ ERR("Couldn't get RSA key from ID cert.");
+ }
+ memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id,
id_digests->d[DIGEST_SHA1], DIGEST_LEN);
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
crypto_pk_free(identity_rcvd);
}
+ if (checked_ed_id) {
+ chan->conn->handshake_state->authenticated_ed25519 = 1;
+ memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id,
+ checked_ed_id, sizeof(ed25519_public_key_t));
+ }
+
+ log_debug(LD_HANDSHAKE, "calling client_learned_peer_id from "
+ "process_certs_cell");
+
if (connection_or_client_learned_peer_id(chan->conn,
- chan->conn->handshake_state->authenticated_peer_id) < 0)
+ chan->conn->handshake_state->authenticated_rsa_peer_id,
+ checked_ed_id) < 0)
ERR("Problem setting or checking peer id");
- log_info(LD_OR,
- "Got some good certificates from %s:%d: Authenticated it.",
- safe_str(chan->conn->base_.address), chan->conn->base_.port);
-
- chan->conn->handshake_state->id_cert = id_cert;
- certs[OR_CERT_TYPE_ID_1024] = NULL;
+ log_info(LD_HANDSHAKE,
+ "Got some good certificates from %s:%d: Authenticated it with "
+ "RSA%s",
+ safe_str(chan->conn->base_.address), chan->conn->base_.port,
+ checked_ed_id ? " and Ed25519" : "");
if (!public_server_mode(get_options())) {
/* If we initiated the connection and we are not a public server, we
@@ -1866,25 +2127,14 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
send_netinfo = 1;
}
} else {
- if (! (id_cert && auth_cert))
- ERR("The certs we wanted were missing");
-
- /* Remember these certificates so we can check an AUTHENTICATE cell */
- if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, 1))
- ERR("The authentication certificate was not valid");
- if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, 1))
- ERR("The ID certificate was not valid");
-
+ /* We can't call it authenticated till we see an AUTHENTICATE cell. */
log_info(LD_OR,
- "Got some good certificates from %s:%d: "
+ "Got some good RSA%s certificates from %s:%d. "
"Waiting for AUTHENTICATE.",
+ checked_ed_id ? " and Ed25519" : "",
safe_str(chan->conn->base_.address),
chan->conn->base_.port);
/* XXXX check more stuff? */
-
- chan->conn->handshake_state->id_cert = id_cert;
- chan->conn->handshake_state->auth_cert = auth_cert;
- certs[OR_CERT_TYPE_ID_1024] = certs[OR_CERT_TYPE_AUTH_1024] = NULL;
}
chan->conn->handshake_state->received_certs_cell = 1;
@@ -1898,9 +2148,13 @@ 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(x509_certs); ++u) {
+ tor_x509_cert_free(x509_certs[u]);
+ }
+ for (unsigned u = 0; u < ARRAY_LENGTH(ed_certs); ++u) {
+ tor_cert_free(ed_certs[u]);
}
+ tor_free(rsa_ed_cc_cert);
certs_cell_free(cc);
#undef ERR
}
@@ -1957,8 +2211,12 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
/* Now see if there is an authentication type we can use */
for (i = 0; i < n_types; ++i) {
uint16_t authtype = auth_challenge_cell_get_methods(ac, i);
- if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET)
- use_type = authtype;
+ if (authchallenge_type_is_supported(authtype)) {
+ if (use_type == -1 ||
+ authchallenge_type_is_better(authtype, use_type)) {
+ use_type = authtype;
+ }
+ }
}
chan->conn->handshake_state->received_auth_challenge = 1;
@@ -1973,9 +2231,10 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
if (use_type >= 0) {
log_info(LD_OR,
"Got an AUTH_CHALLENGE cell from %s:%d: Sending "
- "authentication",
+ "authentication type %d",
safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
+ chan->conn->base_.port,
+ use_type);
if (connection_or_send_authenticate_cell(chan->conn, use_type) < 0) {
log_warn(LD_OR,
@@ -2016,9 +2275,11 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
STATIC void
channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
{
- uint8_t expected[V3_AUTH_FIXED_PART_LEN+256];
+ var_cell_t *expected_cell = NULL;
const uint8_t *auth;
int authlen;
+ int authtype;
+ int bodylen;
tor_assert(cell);
tor_assert(chan);
@@ -2031,6 +2292,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
safe_str(chan->conn->base_.address), \
chan->conn->base_.port, (s)); \
connection_or_close_for_error(chan->conn, 0); \
+ var_cell_free(expected_cell); \
return; \
} while (0)
@@ -2048,9 +2310,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
}
if (!(chan->conn->handshake_state->received_certs_cell))
ERR("We never got a certs cell");
- if (chan->conn->handshake_state->auth_cert == NULL)
- ERR("We never got an authentication certificate");
- if (chan->conn->handshake_state->id_cert == NULL)
+ if (chan->conn->handshake_state->certs->id_cert == NULL)
ERR("We never got an identity certificate");
if (cell->payload_len < 4)
ERR("Cell was way too short");
@@ -2062,8 +2322,9 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
if (4 + len > cell->payload_len)
ERR("Authenticator was truncated");
- if (type != AUTHTYPE_RSA_SHA256_TLSSECRET)
+ if (! authchallenge_type_is_supported(type))
ERR("Authenticator type was not recognized");
+ authtype = type;
auth += 4;
authlen = len;
@@ -2072,25 +2333,55 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
if (authlen < V3_AUTH_BODY_LEN + 1)
ERR("Authenticator was too short");
- ssize_t bodylen =
- connection_or_compute_authenticate_cell_body(
- chan->conn, expected, sizeof(expected), NULL, 1);
- if (bodylen < 0 || bodylen != V3_AUTH_FIXED_PART_LEN)
+ expected_cell = connection_or_compute_authenticate_cell_body(
+ chan->conn, authtype, NULL, NULL, 1);
+ if (! expected_cell)
ERR("Couldn't compute expected AUTHENTICATE cell body");
- if (tor_memneq(expected, auth, bodylen))
+ int sig_is_rsa;
+ if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET ||
+ authtype == AUTHTYPE_RSA_SHA256_RFC5705) {
+ bodylen = V3_AUTH_BODY_LEN;
+ sig_is_rsa = 1;
+ } else {
+ tor_assert(authtype == AUTHTYPE_ED25519_SHA256_RFC5705);
+ /* Our earlier check had better have made sure we had room
+ * for an ed25519 sig (inadvertently) */
+ tor_assert(V3_AUTH_BODY_LEN > ED25519_SIG_LEN);
+ bodylen = authlen - ED25519_SIG_LEN;
+ sig_is_rsa = 0;
+ }
+ if (expected_cell->payload_len != bodylen+4) {
+ ERR("Expected AUTHENTICATE cell body len not as expected.");
+ }
+
+ /* Length of random part. */
+ if (BUG(bodylen < 24)) {
+ // LCOV_EXCL_START
+ ERR("Bodylen is somehow less than 24, which should really be impossible");
+ // LCOV_EXCL_STOP
+ }
+
+ if (tor_memneq(expected_cell->payload+4, auth, bodylen-24))
ERR("Some field in the AUTHENTICATE cell body was not as expected");
- {
+ if (sig_is_rsa) {
+ if (chan->conn->handshake_state->certs->ed_id_sign != NULL)
+ ERR("RSA-signed AUTHENTICATE response provided with an ED25519 cert");
+
+ if (chan->conn->handshake_state->certs->auth_cert == NULL)
+ ERR("We never got an RSA authentication certificate");
+
crypto_pk_t *pk = tor_tls_cert_get_key(
- chan->conn->handshake_state->auth_cert);
+ chan->conn->handshake_state->certs->auth_cert);
char d[DIGEST256_LEN];
char *signed_data;
size_t keysize;
int signed_len;
- if (!pk)
- ERR("Internal error: couldn't get RSA key from AUTH cert.");
+ if (! pk) {
+ ERR("Couldn't get RSA key from AUTH cert.");
+ }
crypto_digest256(d, (char*)auth, V3_AUTH_BODY_LEN, DIGEST_SHA256);
keysize = crypto_pk_keysize(pk);
@@ -2101,7 +2392,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
crypto_pk_free(pk);
if (signed_len < 0) {
tor_free(signed_data);
- ERR("Signature wasn't valid");
+ ERR("RSA signature wasn't valid");
}
if (signed_len < DIGEST256_LEN) {
tor_free(signed_data);
@@ -2114,41 +2405,75 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
ERR("Signature did not match data to be signed.");
}
tor_free(signed_data);
+ } else {
+ if (chan->conn->handshake_state->certs->ed_id_sign == NULL)
+ ERR("We never got an Ed25519 identity certificate.");
+ if (chan->conn->handshake_state->certs->ed_sign_auth == NULL)
+ ERR("We never got an Ed25519 authentication certificate.");
+
+ const ed25519_public_key_t *authkey =
+ &chan->conn->handshake_state->certs->ed_sign_auth->signed_key;
+ ed25519_signature_t sig;
+ tor_assert(authlen > ED25519_SIG_LEN);
+ memcpy(&sig.sig, auth + authlen - ED25519_SIG_LEN, ED25519_SIG_LEN);
+ if (ed25519_checksig(&sig, auth, authlen - ED25519_SIG_LEN, authkey)<0) {
+ ERR("Ed25519 signature wasn't valid.");
+ }
}
/* Okay, we are authenticated. */
chan->conn->handshake_state->received_authenticate = 1;
chan->conn->handshake_state->authenticated = 1;
+ chan->conn->handshake_state->authenticated_rsa = 1;
chan->conn->handshake_state->digest_received_data = 0;
{
- crypto_pk_t *identity_rcvd =
- tor_tls_cert_get_key(chan->conn->handshake_state->id_cert);
- const common_digests_t *id_digests =
- tor_x509_cert_get_id_digests(chan->conn->handshake_state->id_cert);
+ tor_x509_cert_t *id_cert = chan->conn->handshake_state->certs->id_cert;
+ crypto_pk_t *identity_rcvd = tor_tls_cert_get_key(id_cert);
+ const common_digests_t *id_digests = tor_x509_cert_get_id_digests(id_cert);
+ const ed25519_public_key_t *ed_identity_received = NULL;
+
+ if (! sig_is_rsa) {
+ chan->conn->handshake_state->authenticated_ed25519 = 1;
+ ed_identity_received =
+ &chan->conn->handshake_state->certs->ed_id_sign->signing_key;
+ memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id,
+ ed_identity_received, sizeof(ed25519_public_key_t));
+ }
/* This must exist; we checked key type when reading the cert. */
tor_assert(id_digests);
- memcpy(chan->conn->handshake_state->authenticated_peer_id,
+ memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id,
id_digests->d[DIGEST_SHA1], DIGEST_LEN);
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
crypto_pk_free(identity_rcvd);
+ log_debug(LD_HANDSHAKE,
+ "Calling connection_or_init_conn_from_address for %s "
+ " from %s, with%s ed25519 id.",
+ safe_str(chan->conn->base_.address),
+ __func__,
+ ed_identity_received ? "" : "out");
+
connection_or_init_conn_from_address(chan->conn,
&(chan->conn->base_.addr),
chan->conn->base_.port,
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id),
+ authenticated_rsa_peer_id),
+ ed_identity_received,
0);
- log_info(LD_OR,
- "Got an AUTHENTICATE cell from %s:%d: Looks good.",
+ log_debug(LD_HANDSHAKE,
+ "Got an AUTHENTICATE cell from %s:%d, type %d: Looks good.",
safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
+ chan->conn->base_.port,
+ authtype);
}
+ var_cell_free(expected_cell);
+
#undef ERR
}
diff --git a/src/or/channeltls.h b/src/or/channeltls.h
index a4d9c7a095..1f9a39d8a4 100644
--- a/src/or/channeltls.h
+++ b/src/or/channeltls.h
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -29,7 +29,8 @@ struct channel_tls_s {
#endif /* TOR_CHANNEL_INTERNAL_ */
channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest);
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id);
channel_listener_t * channel_tls_get_listener(void);
channel_listener_t * channel_tls_start_listener(void);
channel_t * channel_tls_handle_incoming(or_connection_t *orconn);
@@ -52,6 +53,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..4c0bd9e455 100644
--- a/src/or/circpathbias.c
+++ b/src/or/circpathbias.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -11,6 +11,14 @@
* different tor nodes, in an attempt to detect attacks where
* an attacker deliberately causes circuits to fail until the client
* choses a path they like.
+ *
+ * This code is currently configured in a warning-only mode, though false
+ * positives appear to be rare in practice. There is also support for
+ * disabling really bad guards, but it's quite experimental and may have bad
+ * anonymity effects.
+ *
+ * The information here is associated with the entry_guard_t object for
+ * each guard, and stored persistently in the state file.
*/
#include "or.h"
@@ -43,19 +51,21 @@ static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard);
static int
entry_guard_inc_circ_attempt_count(entry_guard_t *guard)
{
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
entry_guards_changed();
pathbias_measure_close_rate(guard);
- if (guard->path_bias_disabled)
+ if (pb->path_bias_disabled)
return -1;
pathbias_scale_close_rates(guard);
- guard->circ_attempts++;
+ pb->circ_attempts++;
- log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ log_info(LD_CIRC, "Got success count %f/%f for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
return 0;
}
@@ -85,7 +95,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 +107,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 +123,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.
@@ -506,14 +515,16 @@ pathbias_count_build_success(origin_circuit_t *circ)
}
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) {
circ->path_state = PATH_STATE_BUILD_SUCCEEDED;
- guard->circ_successes++;
+ pb->circ_successes++;
entry_guards_changed();
- log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ log_info(LD_CIRC, "Got success count %f/%f for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
} else {
if ((rate_msg = rate_limit_log(&success_notice_limit,
approx_time()))) {
@@ -528,11 +539,11 @@ pathbias_count_build_success(origin_circuit_t *circ)
}
}
- if (guard->circ_attempts < guard->circ_successes) {
+ if (pb->circ_attempts < pb->circ_successes) {
log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) "
- "for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
}
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
* CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here.
@@ -575,8 +586,6 @@ pathbias_count_build_success(origin_circuit_t *circ)
void
pathbias_count_use_attempt(origin_circuit_t *circ)
{
- entry_guard_t *guard;
-
if (!pathbias_should_count(circ)) {
return;
}
@@ -589,19 +598,21 @@ pathbias_count_use_attempt(origin_circuit_t *circ)
circuit_purpose_to_string(circ->base_.purpose),
circuit_state_to_string(circ->base_.state));
} else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) {
- guard = entry_guard_get_by_id_digest(
+ entry_guard_t *guard = entry_guard_get_by_id_digest(
circ->cpath->extend_info->identity_digest);
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
pathbias_measure_use_rate(guard);
pathbias_scale_use_rates(guard);
- guard->use_attempts++;
+ pb->use_attempts++;
entry_guards_changed();
log_debug(LD_CIRC,
- "Marked circuit %d (%f/%f) as used for guard %s ($%s).",
+ "Marked circuit %d (%f/%f) as used for guard %s.",
circ->global_identifier,
- guard->use_successes, guard->use_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ pb->use_successes, pb->use_attempts,
+ entry_guard_describe(guard));
}
circ->path_state = PATH_STATE_USE_ATTEMPTED;
@@ -703,22 +714,23 @@ pathbias_count_use_success(origin_circuit_t *circ)
guard = entry_guard_get_by_id_digest(
circ->cpath->extend_info->identity_digest);
if (guard) {
- guard->use_successes++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->use_successes++;
entry_guards_changed();
- if (guard->use_attempts < guard->use_successes) {
+ if (pb->use_attempts < pb->use_successes) {
log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) "
- "for guard %s=%s",
- guard->use_successes, guard->use_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->use_successes, pb->use_attempts,
+ entry_guard_describe(guard));
}
log_debug(LD_CIRC,
- "Marked circuit %d (%f/%f) as used successfully for guard "
- "%s ($%s).",
- circ->global_identifier, guard->use_successes,
- guard->use_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "Marked circuit %d (%f/%f) as used successfully for guard %s",
+ circ->global_identifier, pb->use_successes,
+ pb->use_attempts,
+ entry_guard_describe(guard));
}
}
@@ -1019,9 +1031,11 @@ pathbias_count_successful_close(origin_circuit_t *circ)
}
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
/* In the long run: circuit_success ~= successful_circuit_close +
* circ_failure + stream_failure */
- guard->successful_circuits_closed++;
+ pb->successful_circuits_closed++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1058,7 +1072,9 @@ pathbias_count_collapse(origin_circuit_t *circ)
}
if (guard) {
- guard->collapsed_circuits++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->collapsed_circuits++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1091,7 +1107,9 @@ pathbias_count_use_failed(origin_circuit_t *circ)
}
if (guard) {
- guard->unusable_circuits++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->unusable_circuits++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1134,7 +1152,9 @@ pathbias_count_timeout(origin_circuit_t *circ)
}
if (guard) {
- guard->timeouts++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->timeouts++;
entry_guards_changed();
}
}
@@ -1166,7 +1186,7 @@ pathbias_count_circs_in_states(entry_guard_t *guard,
if (ocirc->path_state >= from &&
ocirc->path_state <= to &&
pathbias_should_count(ocirc) &&
- fast_memeq(guard->identity,
+ fast_memeq(entry_guard_get_rsa_id_digest(guard),
ocirc->cpath->extend_info->identity_digest,
DIGEST_LEN)) {
log_debug(LD_CIRC, "Found opened circuit %d in path_state %s",
@@ -1190,7 +1210,9 @@ pathbias_count_circs_in_states(entry_guard_t *guard,
double
pathbias_get_close_success_count(entry_guard_t *guard)
{
- return guard->successful_circuits_closed +
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ return pb->successful_circuits_closed +
pathbias_count_circs_in_states(guard,
PATH_STATE_BUILD_SUCCEEDED,
PATH_STATE_USE_SUCCEEDED);
@@ -1206,7 +1228,9 @@ pathbias_get_close_success_count(entry_guard_t *guard)
double
pathbias_get_use_success_count(entry_guard_t *guard)
{
- return guard->use_successes +
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ return pb->use_successes +
pathbias_count_circs_in_states(guard,
PATH_STATE_USE_ATTEMPTED,
PATH_STATE_USE_SUCCEEDED);
@@ -1224,18 +1248,19 @@ static void
pathbias_measure_use_rate(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
- if (guard->use_attempts > pathbias_get_min_use(options)) {
+ if (pb->use_attempts > pathbias_get_min_use(options)) {
/* Note: We rely on the < comparison here to allow us to set a 0
* rate and disable the feature entirely. If refactoring, don't
* change to <= */
- if (pathbias_get_use_success_count(guard)/guard->use_attempts
+ if (pathbias_get_use_success_count(guard)/pb->use_attempts
< pathbias_get_extreme_use_rate(options)) {
/* Dropping is currently disabled by default. */
if (pathbias_get_dropguards(options)) {
- if (!guard->path_bias_disabled) {
+ if (!pb->path_bias_disabled) {
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry an extremely large "
+ "Your Guard %s is failing to carry an extremely large "
"amount of stream on its circuits. "
"To avoid potential route manipulation attacks, Tor has "
"disabled use of this guard. "
@@ -1243,25 +1268,23 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
- guard->path_bias_disabled = 1;
- guard->bad_since = approx_time();
- entry_guards_changed();
+ pb->path_bias_disabled = 1;
return;
}
- } else if (!guard->path_bias_use_extreme) {
- guard->path_bias_use_extreme = 1;
+ } else if (!pb->path_bias_use_extreme) {
+ pb->path_bias_use_extreme = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry an extremely large "
+ "Your Guard %s is failing to carry an extremely large "
"amount of streams on its circuits. "
"This could indicate a route manipulation attack, network "
"overload, bad local network connectivity, or a bug. "
@@ -1269,23 +1292,23 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_use_success_count(guard)/guard->use_attempts
+ } else if (pathbias_get_use_success_count(guard)/pb->use_attempts
< pathbias_get_notice_use_rate(options)) {
- if (!guard->path_bias_use_noticed) {
- guard->path_bias_use_noticed = 1;
+ if (!pb->path_bias_use_noticed) {
+ pb->path_bias_use_noticed = 1;
log_notice(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry more streams on its "
+ "Your Guard %s is failing to carry more streams on its "
"circuits than usual. "
"Most likely this means the Tor network is overloaded "
"or your network connection is poor. "
@@ -1293,15 +1316,15 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
}
@@ -1330,18 +1353,19 @@ static void
pathbias_measure_close_rate(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
- if (guard->circ_attempts > pathbias_get_min_circs(options)) {
+ if (pb->circ_attempts > pathbias_get_min_circs(options)) {
/* Note: We rely on the < comparison here to allow us to set a 0
* rate and disable the feature entirely. If refactoring, don't
* change to <= */
- if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_extreme_rate(options)) {
/* Dropping is currently disabled by default. */
if (pathbias_get_dropguards(options)) {
- if (!guard->path_bias_disabled) {
+ if (!pb->path_bias_disabled) {
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing an extremely large "
+ "Your Guard %s is failing an extremely large "
"amount of circuits. "
"To avoid potential route manipulation attacks, Tor has "
"disabled use of this guard. "
@@ -1349,25 +1373,23 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
- guard->path_bias_disabled = 1;
- guard->bad_since = approx_time();
- entry_guards_changed();
+ pb->path_bias_disabled = 1;
return;
}
- } else if (!guard->path_bias_extreme) {
- guard->path_bias_extreme = 1;
+ } else if (!pb->path_bias_extreme) {
+ pb->path_bias_extreme = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing an extremely large "
+ "Your Guard %s is failing an extremely large "
"amount of circuits. "
"This could indicate a route manipulation attack, "
"extreme network overload, or a bug. "
@@ -1375,23 +1397,23 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_warn_rate(options)) {
- if (!guard->path_bias_warned) {
- guard->path_bias_warned = 1;
+ if (!pb->path_bias_warned) {
+ pb->path_bias_warned = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing a very large "
+ "Your Guard %s is failing a very large "
"amount of circuits. "
"Most likely this means the Tor network is "
"overloaded, but it could also mean an attack against "
@@ -1400,38 +1422,38 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_notice_rate(options)) {
- if (!guard->path_bias_noticed) {
- guard->path_bias_noticed = 1;
+ if (!pb->path_bias_noticed) {
+ pb->path_bias_noticed = 1;
log_notice(LD_CIRC,
- "Your Guard %s ($%s) is failing more circuits than "
+ "Your Guard %s is failing more circuits than "
"usual. "
"Most likely this means the Tor network is overloaded. "
"Success counts are %ld/%ld. Use counts are %ld/%ld. "
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
}
@@ -1451,9 +1473,10 @@ static void
pathbias_scale_close_rates(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
/* If we get a ton of circuits, just scale everything down */
- if (guard->circ_attempts > pathbias_get_scale_threshold(options)) {
+ if (pb->circ_attempts > pathbias_get_scale_threshold(options)) {
double scale_ratio = pathbias_get_scale_ratio(options);
int opened_attempts = pathbias_count_circs_in_states(guard,
PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED);
@@ -1461,38 +1484,38 @@ pathbias_scale_close_rates(entry_guard_t *guard)
PATH_STATE_BUILD_SUCCEEDED,
PATH_STATE_USE_FAILED);
/* Verify that the counts are sane before and after scaling */
- int counts_are_sane = (guard->circ_attempts >= guard->circ_successes);
+ int counts_are_sane = (pb->circ_attempts >= pb->circ_successes);
- guard->circ_attempts -= (opened_attempts+opened_built);
- guard->circ_successes -= opened_built;
+ pb->circ_attempts -= (opened_attempts+opened_built);
+ pb->circ_successes -= opened_built;
- guard->circ_attempts *= scale_ratio;
- guard->circ_successes *= scale_ratio;
- guard->timeouts *= scale_ratio;
- guard->successful_circuits_closed *= scale_ratio;
- guard->collapsed_circuits *= scale_ratio;
- guard->unusable_circuits *= scale_ratio;
+ pb->circ_attempts *= scale_ratio;
+ pb->circ_successes *= scale_ratio;
+ pb->timeouts *= scale_ratio;
+ pb->successful_circuits_closed *= scale_ratio;
+ pb->collapsed_circuits *= scale_ratio;
+ pb->unusable_circuits *= scale_ratio;
- guard->circ_attempts += (opened_attempts+opened_built);
- guard->circ_successes += opened_built;
+ pb->circ_attempts += (opened_attempts+opened_built);
+ pb->circ_successes += opened_built;
entry_guards_changed();
log_info(LD_CIRC,
"Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard "
- "%s ($%s)",
- guard->circ_successes, guard->successful_circuits_closed,
- guard->circ_attempts, opened_built, opened_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "%s",
+ pb->circ_successes, pb->successful_circuits_closed,
+ pb->circ_attempts, opened_built, opened_attempts,
+ entry_guard_describe(guard));
/* Have the counts just become invalid by this scaling attempt? */
- if (counts_are_sane && guard->circ_attempts < guard->circ_successes) {
+ if (counts_are_sane && pb->circ_attempts < pb->circ_successes) {
log_notice(LD_BUG,
"Scaling has mangled pathbias counts to %f/%f (%d/%d open) "
- "for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts, opened_built,
- opened_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->circ_successes, pb->circ_attempts, opened_built,
+ opened_attempts,
+ entry_guard_describe(guard));
}
}
}
@@ -1510,35 +1533,35 @@ void
pathbias_scale_use_rates(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
/* If we get a ton of circuits, just scale everything down */
- if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) {
+ if (pb->use_attempts > pathbias_get_scale_use_threshold(options)) {
double scale_ratio = pathbias_get_scale_ratio(options);
int opened_attempts = pathbias_count_circs_in_states(guard,
PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED);
/* Verify that the counts are sane before and after scaling */
- int counts_are_sane = (guard->use_attempts >= guard->use_successes);
+ int counts_are_sane = (pb->use_attempts >= pb->use_successes);
- guard->use_attempts -= opened_attempts;
+ pb->use_attempts -= opened_attempts;
- guard->use_attempts *= scale_ratio;
- guard->use_successes *= scale_ratio;
+ pb->use_attempts *= scale_ratio;
+ pb->use_successes *= scale_ratio;
- guard->use_attempts += opened_attempts;
+ pb->use_attempts += opened_attempts;
log_info(LD_CIRC,
- "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)",
- guard->use_successes, guard->use_attempts, opened_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "Scaled pathbias use counts to %f/%f (%d open) for guard %s",
+ pb->use_successes, pb->use_attempts, opened_attempts,
+ entry_guard_describe(guard));
/* Have the counts just become invalid by this scaling attempt? */
- if (counts_are_sane && guard->use_attempts < guard->use_successes) {
+ if (counts_are_sane && pb->use_attempts < pb->use_successes) {
log_notice(LD_BUG,
"Scaling has mangled pathbias usage counts to %f/%f "
- "(%d open) for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- opened_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "(%d open) for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ opened_attempts, entry_guard_describe(guard));
}
entry_guards_changed();
diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h
index ce76689d5f..2a4c316807 100644
--- a/src/or/circpathbias.h
+++ b/src/or/circpathbias.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 0c57b0b255..9a95015005 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -9,11 +9,26 @@
*
* \brief Implements the details of building circuits (by chosing paths,
* constructing/sending create/extend cells, and so on).
+ *
+ * On the client side, this module handles launching circuits. Circuit
+ * launches are srtarted from circuit_establish_circuit(), called from
+ * circuit_launch_by_extend_info()). To choose the path the circuit will
+ * take, onion_extend_cpath() calls into a maze of node selection functions.
+ *
+ * Once the circuit is ready to be launched, the first hop is treated as a
+ * special case with circuit_handle_first_hop(), since it might need to open a
+ * channel. As the channel opens, and later as CREATED and RELAY_EXTENDED
+ * cells arrive, the client will invoke circuit_send_next_onion_skin() to send
+ * CREATE or RELAY_EXTEND cells.
+ *
+ * On the server side, this module also handles the logic of responding to
+ * RELAY_EXTEND requests, using circuit_extend().
**/
#define CIRCUITBUILD_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "channel.h"
#include "circpathbias.h"
#define CIRCUITBUILD_PRIVATE
@@ -28,8 +43,10 @@
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
+#include "crypto.h"
#include "directory.h"
#include "entrynodes.h"
+#include "hs_ntor.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -38,27 +55,30 @@
#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"
+#include "transports.h"
static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
- uint16_t port,
- const char *id_digest);
+ uint16_t port,
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id);
static int circuit_deliver_create_cell(circuit_t *circ,
const create_cell_t *create_cell,
int relayed);
static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit);
static 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);
+static int circuit_send_first_onion_skin(origin_circuit_t *circ);
+static int circuit_build_no_more_hops(origin_circuit_t *circ);
+static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop);
/** This function tries to get a channel to the specified endpoint,
* and then calls command_setup_channel() to give it the right
@@ -66,11 +86,12 @@ static int circuits_can_use_ntor(void);
*/
static channel_t *
channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_t *chan;
- chan = channel_connect(addr, port, id_digest);
+ chan = channel_connect(addr, port, id_digest, ed_id);
if (chan) command_setup_channel(chan);
return chan;
@@ -365,7 +386,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)
{
@@ -373,16 +394,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
@@ -390,41 +414,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);
+
+ /* 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;
+ }
- /* Clear the path and retry */
- circuit_clear_cpath(circ);
+ 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
@@ -457,14 +501,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;
@@ -479,6 +523,13 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags)
return circ;
}
+/** Return the guard state associated with <b>circ</b>, which may be NULL. */
+circuit_guard_state_t *
+origin_circuit_get_guard_state(origin_circuit_t *circ)
+{
+ return circ->guard_state;
+}
+
/** Start establishing the first hop of our circuit. Figure out what
* OR we should connect to, and if necessary start the connection to
* it. If we're already connected, then send the 'create' cell.
@@ -517,6 +568,7 @@ circuit_handle_first_hop(origin_circuit_t *circ)
firsthop->extend_info->port));
n_chan = channel_get_for_extend(firsthop->extend_info->identity_digest,
+ &firsthop->extend_info->ed_identity,
&firsthop->extend_info->addr,
&msg,
&should_launch);
@@ -534,7 +586,8 @@ circuit_handle_first_hop(origin_circuit_t *circ)
n_chan = channel_connect_for_circuit(
&firsthop->extend_info->addr,
firsthop->extend_info->port,
- firsthop->extend_info->identity_digest);
+ firsthop->extend_info->identity_digest,
+ &firsthop->extend_info->ed_identity);
if (!n_chan) { /* connect failed, forget the whole thing */
log_info(LD_CIRC,"connect to firsthop failed. Closing.");
return -END_CIRC_REASON_CONNECTFAILED;
@@ -757,20 +810,18 @@ 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;
}
- if (options->FastFirstHopPK == -1) {
- /* option is "auto", so look at the consensus. */
- return networkstatus_get_param(NULL, "usecreatefast", 1, 0, 1);
- }
-
- return options->FastFirstHopPK;
+ return networkstatus_get_param(NULL, "usecreatefast", 0, 0, 1);
}
/** Return true if <b>circ</b> is the type of circuit we want to count
@@ -785,265 +836,356 @@ 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;
}
}
+/**
+ * Return true iff <b>purpose</b> is a purpose for a circuit which is
+ * allowed to have no guard configured, even if the circuit is multihop
+ * and guards are enabled.
+ */
+static int
+circuit_purpose_may_omit_guard(int purpose)
+{
+ switch (purpose) {
+ case CIRCUIT_PURPOSE_TESTING:
+ case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT:
+ /* Testing circuits may omit guards because they're measuring
+ * liveness or performance, and don't want guards to interfere. */
+ return 1;
+ default:
+ /* All other multihop circuits should use guards if guards are
+ * enabled. */
+ return 0;
+ }
+}
+
/** This is the backbone function for building circuits.
*
* If circ's first hop is closed, then we need to build a create
* cell and send it forward.
*
- * Otherwise, we need to build a relay extend cell and send it
- * forward.
+ * Otherwise, if circ's cpath still has any non-open hops, we need to
+ * build a relay extend cell and send it forward to the next non-open hop.
+ *
+ * If all hops on the cpath are open, we're done building the circuit
+ * and we should do housekeeping for the newly opened circuit.
*
* Return -reason if we want to tear down circ, else return 0.
*/
int
circuit_send_next_onion_skin(origin_circuit_t *circ)
{
- crypt_path_t *hop;
- const node_t *node;
-
tor_assert(circ);
if (circ->cpath->state == CPATH_STATE_CLOSED) {
- /* This is the first hop. */
- create_cell_t cc;
- int fast;
- int len;
- log_debug(LD_CIRC,"First skin; sending create cell.");
- memset(&cc, 0, sizeof(cc));
- if (circ->build_state->onehop_tunnel)
- control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
- else
- control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
+ /* Case one: we're on the first hop. */
+ return circuit_send_first_onion_skin(circ);
+ }
- node = node_get_by_id(circ->base_.n_chan->identity_digest);
- fast = should_use_create_fast_for_circuit(circ);
- if (!fast) {
- /* We are an OR and we know the right onion key: we should
- * send a create cell.
- */
- circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
- circ->cpath->extend_info);
- 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);
- }
+ tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
+ tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
+ crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath);
- len = onion_skin_create(cc.handshake_type,
- circ->cpath->extend_info,
- &circ->cpath->handshake_state,
- cc.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
- return - END_CIRC_REASON_INTERNAL;
- }
- cc.handshake_len = len;
+ if (hop) {
+ /* Case two: we're on a hop after the first. */
+ return circuit_send_intermediate_onion_skin(circ, hop);
+ }
- if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
- return - END_CIRC_REASON_RESOURCELIMIT;
+ /* Case three: the circuit is finished. Do housekeeping tasks on it. */
+ return circuit_build_no_more_hops(circ);
+}
- circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
- log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
- fast ? "CREATE_FAST" : "CREATE",
- node ? node_describe(node) : "<unnamed>");
+/**
+ * Called from circuit_send_next_onion_skin() when we find ourselves connected
+ * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST
+ * cell to that hop. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_send_first_onion_skin(origin_circuit_t *circ)
+{
+ int fast;
+ int len;
+ const node_t *node;
+ create_cell_t cc;
+ memset(&cc, 0, sizeof(cc));
+
+ log_debug(LD_CIRC,"First skin; sending create cell.");
+
+ if (circ->build_state->onehop_tunnel) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
} else {
- extend_cell_t ec;
- int len;
- tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
- tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
- log_debug(LD_CIRC,"starting to send subsequent skin.");
- hop = onion_next_hop_in_cpath(circ->cpath);
- memset(&ec, 0, sizeof(ec));
- if (!hop) {
- /* done building the circuit. whew. */
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
- if (circuit_timeout_want_to_count_circ(circ)) {
- struct timeval end;
- long timediff;
- tor_gettimeofday(&end);
- timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
-
- /*
- * If the circuit build time is much greater than we would have cut
- * it off at, we probably had a suspend event along this codepath,
- * and we should discard the value.
- */
- if (timediff < 0 ||
- timediff > 2*get_circuit_build_close_time_ms()+1000) {
- log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
- "Assuming clock jump. Purpose %d (%s)", timediff,
- circ->base_.purpose,
- circuit_purpose_to_string(circ->base_.purpose));
- } else if (!circuit_build_times_disabled()) {
- /* Only count circuit times if the network is live */
- if (circuit_build_times_network_check_live(
- get_circuit_build_times())) {
- circuit_build_times_add_time(get_circuit_build_times_mutable(),
- (build_time_t)timediff);
- circuit_build_times_set_timeout(get_circuit_build_times_mutable());
- }
-
- if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_build_times_network_circ_success(
- get_circuit_build_times_mutable());
- }
- }
- }
- log_info(LD_CIRC,"circuit built!");
- circuit_reset_failure_count(0);
+ control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
- if (circ->build_state->onehop_tunnel || circ->has_opened) {
- control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
- }
+ /* If this is not a one-hop tunnel, the channel is being used
+ * for traffic that wants anonymity and protection from traffic
+ * analysis (such as netflow record retention). That means we want
+ * to pad it.
+ */
+ if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS)
+ circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
+ }
- pathbias_count_build_success(circ);
- circuit_rep_hist_note_result(circ);
- circuit_has_opened(circ); /* do other actions as necessary */
-
- if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
- const or_options_t *options = get_options();
- note_that_we_completed_a_circuit();
- /* FFFF Log a count of known routers here */
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- }
- control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
- clear_broken_connection_map(1);
- if (server_mode(options) && !check_whether_orport_reachable()) {
- inform_testing_reachability();
- consider_testing_reachability(1, 1);
- }
+ node = node_get_by_id(circ->base_.n_chan->identity_digest);
+ fast = should_use_create_fast_for_circuit(circ);
+ if (!fast) {
+ /* We know the right onion key: we should send a create cell. */
+ circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
+ circ->cpath->extend_info);
+ } else {
+ /* We don't know an onion key, so we need to fall back to CREATE_FAST. */
+ cc.cell_type = CELL_CREATE_FAST;
+ cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST;
+ }
+
+ len = onion_skin_create(cc.handshake_type,
+ circ->cpath->extend_info,
+ &circ->cpath->handshake_state,
+ cc.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ cc.handshake_len = len;
+
+ if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
+ return - END_CIRC_REASON_RESOURCELIMIT;
+
+ circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
+ log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
+ fast ? "CREATE_FAST" : "CREATE",
+ node ? node_describe(node) : "<unnamed>");
+ return 0;
+}
+
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have no
+ * more hops: mark the circuit as finished, and perform the necessary
+ * bookkeeping. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_build_no_more_hops(origin_circuit_t *circ)
+{
+ guard_usable_t r;
+ if (! circ->guard_state) {
+ if (circuit_get_cpath_len(circ) != 1 &&
+ ! circuit_purpose_may_omit_guard(circ->base_.purpose) &&
+ get_options()->UseEntryGuards) {
+ log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no "
+ "guard state",
+ circuit_get_cpath_len(circ), circ, circ->base_.purpose);
+ }
+ r = GUARD_USABLE_NOW;
+ } else {
+ r = entry_guard_succeeded(&circ->guard_state);
+ }
+ const int is_usable_for_streams = (r == GUARD_USABLE_NOW);
+ if (r == GUARD_USABLE_NOW) {
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ } else if (r == GUARD_MAYBE_USABLE_LATER) {
+ // Wait till either a better guard succeeds, or till
+ // all better guards fail.
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT);
+ } else {
+ tor_assert_nonfatal(r == GUARD_USABLE_NEVER);
+ return - END_CIRC_REASON_INTERNAL;
+ }
+
+ /* XXXX #21422 -- the rest of this branch needs careful thought!
+ * Some of the things here need to happen when a circuit becomes
+ * mechanically open; some need to happen when it is actually usable.
+ * I think I got them right, but more checking would be wise. -NM
+ */
+
+ if (circuit_timeout_want_to_count_circ(circ)) {
+ struct timeval end;
+ long timediff;
+ tor_gettimeofday(&end);
+ timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
+
+ /*
+ * If the circuit build time is much greater than we would have cut
+ * it off at, we probably had a suspend event along this codepath,
+ * and we should discard the value.
+ */
+ if (timediff < 0 ||
+ timediff > 2*get_circuit_build_close_time_ms()+1000) {
+ log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
+ "Assuming clock jump. Purpose %d (%s)", timediff,
+ circ->base_.purpose,
+ circuit_purpose_to_string(circ->base_.purpose));
+ } else if (!circuit_build_times_disabled(get_options())) {
+ /* Only count circuit times if the network is live */
+ if (circuit_build_times_network_check_live(
+ get_circuit_build_times())) {
+ circuit_build_times_add_time(get_circuit_build_times_mutable(),
+ (build_time_t)timediff);
+ circuit_build_times_set_timeout(get_circuit_build_times_mutable());
}
- /* We're done with measurement circuits here. Just close them */
- if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_build_times_network_circ_success(
+ get_circuit_build_times_mutable());
}
- return 0;
}
+ }
+ log_info(LD_CIRC,"circuit built!");
+ circuit_reset_failure_count(0);
- if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
- log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
- return - END_CIRC_REASON_INTERNAL;
- }
+ if (circ->build_state->onehop_tunnel || circ->has_opened) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
+ }
- {
- 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);
+ pathbias_count_build_success(circ);
+ circuit_rep_hist_note_result(circ);
+ if (is_usable_for_streams)
+ circuit_has_opened(circ); /* do other actions as necessary */
+
+ if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
+ const or_options_t *options = get_options();
+ note_that_we_completed_a_circuit();
+ /* FFFF Log a count of known routers here */
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
+ if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
}
-
- tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
- ec.orport_ipv4.port = hop->extend_info->port;
- tor_addr_make_unspec(&ec.orport_ipv6.addr);
- memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
-
- len = onion_skin_create(ec.create_cell.handshake_type,
- hop->extend_info,
- &hop->handshake_state,
- ec.create_cell.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create failed.");
- return - END_CIRC_REASON_INTERNAL;
+ control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
+ clear_broken_connection_map(1);
+ if (server_mode(options) && !check_whether_orport_reachable(options)) {
+ inform_testing_reachability();
+ consider_testing_reachability(1, 1);
}
- ec.create_cell.handshake_len = len;
+ }
- log_info(LD_CIRC,"Sending extend relay cell.");
- note_request("cell: extend", 1);
- {
- uint8_t command = 0;
- uint16_t payload_len=0;
- uint8_t payload[RELAY_PAYLOAD_SIZE];
- if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
- log_warn(LD_CIRC,"Couldn't format extend cell");
- return -END_CIRC_REASON_INTERNAL;
- }
+ /* We're done with measurement circuits here. Just close them */
+ if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ }
+ return 0;
+}
+
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have a hop
+ * other than the first that we need to extend to: use <b>hop</b>'s
+ * information to extend the circuit another step. Return 0 on success;
+ * -reason on failure (if the circuit should be torn down).
+ */
+static int
+circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop)
+{
+ int len;
+ extend_cell_t ec;
+ memset(&ec, 0, sizeof(ec));
+
+ log_debug(LD_CIRC,"starting to send subsequent skin.");
- /* send it to hop->prev, because it will transfer
- * it to a create cell and then send to hop */
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
- command,
- (char*)payload, payload_len,
- hop->prev) < 0)
- return 0; /* circuit is closed */
+ if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
+ log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+
+ circuit_pick_extend_handshake(&ec.cell_type,
+ &ec.create_cell.cell_type,
+ &ec.create_cell.handshake_type,
+ hop->extend_info);
+
+ tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
+ ec.orport_ipv4.port = hop->extend_info->port;
+ tor_addr_make_unspec(&ec.orport_ipv6.addr);
+ memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
+ /* Set the ED25519 identity too -- it will only get included
+ * in the extend2 cell if we're configured to use it, though. */
+ ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity);
+
+ len = onion_skin_create(ec.create_cell.handshake_type,
+ hop->extend_info,
+ &hop->handshake_state,
+ ec.create_cell.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ ec.create_cell.handshake_len = len;
+
+ log_info(LD_CIRC,"Sending extend relay cell.");
+ {
+ uint8_t command = 0;
+ uint16_t payload_len=0;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+ if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
+ log_warn(LD_CIRC,"Couldn't format extend cell");
+ return -END_CIRC_REASON_INTERNAL;
}
- hop->state = CPATH_STATE_AWAITING_KEYS;
+
+ /* send it to hop->prev, because that relay will transfer
+ * it to a create cell and then send to hop */
+ if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ command,
+ (char*)payload, payload_len,
+ hop->prev) < 0)
+ return 0; /* circuit is closed */
}
+ hop->state = CPATH_STATE_AWAITING_KEYS;
return 0;
}
@@ -1131,7 +1273,7 @@ circuit_extend(cell_t *cell, circuit_t *circ)
/* Check if they asked us for 0000..0000. We support using
* an empty fingerprint for the first hop (e.g. for a bridge relay),
- * but we don't want to let people send us extend cells for empty
+ * but we don't want to let clients send us extend cells for empty
* fingerprints -- a) because it opens the user up to a mitm attack,
* and b) because it lets an attacker force the relay to hold open a
* new TLS connection for each extend request. */
@@ -1141,6 +1283,18 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return -1;
}
+ /* Fill in ed_pubkey if it was not provided and we can infer it from
+ * our networkstatus */
+ if (ed25519_public_key_is_zero(&ec.ed_pubkey)) {
+ const node_t *node = node_get_by_id((const char*)ec.node_id);
+ const ed25519_public_key_t *node_ed_id = NULL;
+ if (node &&
+ node_supports_ed25519_link_authentication(node) &&
+ (node_ed_id = node_get_ed25519_id(node))) {
+ ed25519_pubkey_copy(&ec.ed_pubkey, node_ed_id);
+ }
+ }
+
/* Next, check if we're being asked to connect to the hop that the
* extend cell came from. There isn't any reason for that, and it can
* assist circular-path attacks. */
@@ -1152,7 +1306,17 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return -1;
}
+ /* Check the previous hop Ed25519 ID too */
+ if (! ed25519_public_key_is_zero(&ec.ed_pubkey) &&
+ ed25519_pubkey_eq(&ec.ed_pubkey,
+ &TO_OR_CIRCUIT(circ)->p_chan->ed25519_identity)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Client asked me to extend back to the previous hop "
+ "(by Ed25519 ID).");
+ }
+
n_chan = channel_get_for_extend((const char*)ec.node_id,
+ &ec.ed_pubkey,
&ec.orport_ipv4.addr,
&msg,
&should_launch);
@@ -1164,8 +1328,9 @@ circuit_extend(cell_t *cell, circuit_t *circ)
circ->n_hop = extend_info_new(NULL /*nickname*/,
(const char*)ec.node_id,
- NULL /*onion_key*/,
- NULL /*curve25519_key*/,
+ &ec.ed_pubkey,
+ NULL, /*onion_key*/
+ NULL, /*curve25519_key*/
&ec.orport_ipv4.addr,
ec.orport_ipv4.port);
@@ -1178,7 +1343,8 @@ circuit_extend(cell_t *cell, circuit_t *circ)
/* we should try to open a connection */
n_chan = channel_connect_for_circuit(&ec.orport_ipv4.addr,
ec.orport_ipv4.port,
- (const char*)ec.node_id);
+ (const char*)ec.node_id,
+ &ec.ed_pubkey);
if (!n_chan) {
log_info(LD_CIRC,"Launching n_chan failed. Closing circuit.");
circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED);
@@ -1205,40 +1371,77 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return 0;
}
-/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in
- * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are
- * used as follows:
+/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data.
+ *
+ * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
+ * service circuits and <b>key_data</b> must be at least
+ * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
+ *
+ * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
+ * bytes, which are used as follows:
* - 20 to initialize f_digest
* - 20 to initialize b_digest
* - 16 to key f_crypto
* - 16 to key b_crypto
*
* (If 'reverse' is true, then f_XX and b_XX are swapped.)
+ *
+ * Return 0 if init was successful, else -1 if it failed.
*/
int
-circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse)
+circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3)
{
crypto_digest_t *tmp_digest;
crypto_cipher_t *tmp_crypto;
+ size_t digest_len = 0;
+ size_t cipher_key_len = 0;
tor_assert(cpath);
tor_assert(key_data);
tor_assert(!(cpath->f_crypto || cpath->b_crypto ||
cpath->f_digest || cpath->b_digest));
- cpath->f_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN);
- cpath->b_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN);
+ /* Basic key size validation */
+ if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) {
+ return -1;
+ }
+
+ /* If we are using this cpath for next gen onion services use SHA3-256,
+ otherwise use good ol' SHA1 */
+ if (is_hs_v3) {
+ digest_len = DIGEST256_LEN;
+ cipher_key_len = CIPHER256_KEY_LEN;
+ cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ } else {
+ digest_len = DIGEST_LEN;
+ cipher_key_len = CIPHER_KEY_LEN;
+ cpath->f_digest = crypto_digest_new();
+ cpath->b_digest = crypto_digest_new();
+ }
- if (!(cpath->f_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)))) {
+ tor_assert(digest_len != 0);
+ tor_assert(cipher_key_len != 0);
+ const int cipher_key_bits = (int) cipher_key_len * 8;
+
+ crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len);
+ crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len);
+
+ cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len),
+ cipher_key_bits);
+ if (!cpath->f_crypto) {
log_warn(LD_BUG,"Forward cipher initialization failed.");
return -1;
}
- if (!(cpath->b_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) {
+
+ cpath->b_crypto = crypto_cipher_new_with_bits(
+ key_data+(2*digest_len)+cipher_key_len,
+ cipher_key_bits);
+ if (!cpath->b_crypto) {
log_warn(LD_BUG,"Backward cipher initialization failed.");
return -1;
}
@@ -1304,7 +1507,7 @@ circuit_finish_handshake(origin_circuit_t *circ,
onion_handshake_state_release(&hop->handshake_state);
- if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) {
return -END_CIRC_REASON_TORPROTOCOL;
}
@@ -1371,12 +1574,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason)
int
onionskin_answer(or_circuit_t *circ,
const created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce)
{
cell_t cell;
crypt_path_t *tmp_cpath;
+ tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN);
+
if (created_cell_format(&cell, created_cell) < 0) {
log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)",
(int)created_cell->cell_type, (int)created_cell->handshake_len);
@@ -1392,7 +1597,7 @@ onionskin_answer(or_circuit_t *circ,
log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
(unsigned int)get_uint32(keys),
(unsigned int)get_uint32(keys+20));
- if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) {
log_warn(LD_BUG,"Circuit initialization failed");
tor_free(tmp_cpath);
return -1;
@@ -1429,25 +1634,106 @@ onionskin_answer(or_circuit_t *circ,
return 0;
}
-/** Choose a length for a circuit of purpose <b>purpose</b>: three + the
- * number of endpoints that would give something away about our destination.
+/** Helper for new_route_len(). Choose a circuit length for purpose
+ * <b>purpose</b>: DEFAULT_ROUTE_LEN (+ 1 if someone else chose the
+ * exit). If someone else chose the exit, they could be colluding
+ * with the exit, so add a randomly selected node to preserve
+ * anonymity.
+ *
+ * Here, "exit node" sometimes means an OR acting as an internal
+ * endpoint, rather than as a relay to an external endpoint. This
+ * means there need to be at least DEFAULT_ROUTE_LEN routers between
+ * us and the internal endpoint to preserve the same anonymity
+ * properties that we would get when connecting to an external
+ * endpoint. These internal endpoints can include:
+ *
+ * - Connections to a directory of hidden services
+ * (CIRCUIT_PURPOSE_C_GENERAL)
+ *
+ * - A client connecting to an introduction point, which the hidden
+ * service picked (CIRCUIT_PURPOSE_C_INTRODUCING, via
+ * circuit_get_open_circ_or_launch() which rewrites it from
+ * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
+ *
+ * - A hidden service connecting to a rendezvous point, which the
+ * client picked (CIRCUIT_PURPOSE_S_CONNECT_REND, via
+ * rend_service_receive_introduction() and
+ * rend_service_relaunch_rendezvous)
+ *
+ * There are currently two situations where we picked the exit node
+ * ourselves, making DEFAULT_ROUTE_LEN a safe circuit length:
+ *
+ * - We are a hidden service connecting to an introduction point
+ * (CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, via
+ * rend_service_launch_establish_intro())
+ *
+ * - We are a router testing its own reachabiity
+ * (CIRCUIT_PURPOSE_TESTING, via consider_testing_reachability())
+ *
+ * onion_pick_cpath_exit() bypasses us (by not calling
+ * new_route_len()) in the one-hop tunnel case, so we don't need to
+ * handle that.
+ */
+static int
+route_len_for_purpose(uint8_t purpose, extend_info_t *exit_ei)
+{
+ int routelen = DEFAULT_ROUTE_LEN;
+ int known_purpose = 0;
+
+ if (!exit_ei)
+ return routelen;
+
+ switch (purpose) {
+ /* These two purposes connect to a router that we chose, so
+ * DEFAULT_ROUTE_LEN is safe. */
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ /* hidden service connecting to introduction point */
+ case CIRCUIT_PURPOSE_TESTING:
+ /* router reachability testing */
+ known_purpose = 1;
+ break;
+
+ /* These three purposes connect to a router that someone else
+ * might have chosen, so add an extra hop to protect anonymity. */
+ case CIRCUIT_PURPOSE_C_GENERAL:
+ /* connecting to hidden service directory */
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ /* client connecting to introduction point */
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ /* hidden service connecting to rendezvous point */
+ known_purpose = 1;
+ routelen++;
+ break;
+
+ default:
+ /* Got a purpose not listed above along with a chosen exit.
+ * Increase the circuit length by one anyway for safety. */
+ routelen++;
+ break;
+ }
+
+ if (BUG(exit_ei && !known_purpose)) {
+ log_warn(LD_BUG, "Unhandled purpose %d with a chosen exit; "
+ "assuming routelen %d.", purpose, routelen);
+ }
+ return routelen;
+}
+
+/** Choose a length for a circuit of purpose <b>purpose</b> and check
+ * if enough routers are available.
*
* If the routerlist <b>nodes</b> doesn't have enough routers
* to handle the desired path length, return -1.
*/
-static int
-new_route_len(uint8_t purpose, extend_info_t *exit, smartlist_t *nodes)
+STATIC int
+new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes)
{
int num_acceptable_routers;
int routelen;
tor_assert(nodes);
- routelen = DEFAULT_ROUTE_LEN;
- if (exit &&
- purpose != CIRCUIT_PURPOSE_TESTING &&
- purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
- routelen++;
+ routelen = route_len_for_purpose(purpose, exit_ei);
num_acceptable_routers = count_acceptable_nodes(nodes);
@@ -1480,9 +1766,9 @@ circuit_get_unhandled_ports(time_t now)
* If we're returning 0, set need_uptime and need_capacity to
* indicate any requirements that the unhandled ports have.
*/
-int
-circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
- int *need_capacity)
+MOCK_IMPL(int,
+circuit_all_predicted_ports_handled, (time_t now, int *need_uptime,
+ int *need_capacity))
{
int i, enough;
uint16_t *port;
@@ -1568,7 +1854,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();
@@ -1631,15 +1917,16 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
* we'll retry later in this function with need_update and
* need_capacity set to 0. */
}
- if (!(node->is_valid || options->AllowInvalid_ & ALLOW_INVALID_EXIT)) {
+ if (!(node->is_valid)) {
/* if it's invalid and we don't want it */
n_supported[i] = -1;
// log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- invalid router.",
// router->nickname, i);
continue; /* skip invalid routers */
}
- if (options->ExcludeSingleHopRelays &&
- node_allows_single_hop_exits(node)) {
+ /* We do not allow relays that allow single hop exits by default. Option
+ * was deprecated in 0.2.9.2-alpha and removed in 0.3.1.0-alpha. */
+ if (node_allows_single_hop_exits(node)) {
n_supported[i] = -1;
continue;
}
@@ -1695,7 +1982,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
@@ -1733,8 +2020,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
@@ -1748,15 +2035,16 @@ 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,
- "No specified %sexit routers seem to be running: "
+ "No exits in ExitNodes%s seem to be running: "
"can't choose an exit.",
- options->ExcludeExitNodesUnion_ ? "non-excluded " : "");
+ options->ExcludeExitNodesUnion_ ?
+ ", except possibly those excluded by your configuration, " : "");
}
return NULL;
}
@@ -1771,8 +2059,9 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags,
const or_options_t *options)
{
const node_t *rp_node = NULL;
- const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0;
const int need_desc = (flags & CRN_NEED_DESC) != 0;
+ const int pref_addr = (flags & CRN_PREF_ADDR) != 0;
+ const int direct_conn = (flags & CRN_DIRECT_CONN) != 0;
smartlist_t *whitelisted_live_rps = smartlist_new();
smartlist_t *all_live_nodes = smartlist_new();
@@ -1781,9 +2070,10 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags,
/* Add all running nodes to all_live_nodes */
router_add_running_nodes_to_smartlist(all_live_nodes,
- allow_invalid,
0, 0, 0,
- need_desc, 0);
+ need_desc,
+ pref_addr,
+ direct_conn);
/* Filter all_live_nodes to only add live *and* whitelisted RPs to
* the list whitelisted_live_rps. */
@@ -1821,17 +2111,33 @@ pick_rendezvous_node(router_crn_flags_t flags)
{
const or_options_t *options = get_options();
- if (options->AllowInvalid_ & ALLOW_INVALID_RENDEZVOUS)
- flags |= CRN_ALLOW_INVALID;
-
#ifdef ENABLE_TOR2WEB_MODE
+ /* 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
@@ -1861,8 +2167,6 @@ choose_good_exit_server(uint8_t purpose,
switch (purpose) {
case CIRCUIT_PURPOSE_C_GENERAL:
- if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE)
- flags |= CRN_ALLOW_INVALID;
if (is_internal) /* pick it like a middle hop */
return router_choose_random_node(NULL, options->ExcludeNodes, flags);
else
@@ -1884,7 +2188,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;
@@ -1931,13 +2236,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 {
@@ -1946,7 +2251,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));
}
@@ -1960,25 +2265,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,
@@ -1987,10 +2294,11 @@ 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);
+ if (BUG(exit_ei == NULL))
+ return -1;
}
- state->chosen_exit = exit;
+ state->chosen_exit = exit_ei;
return 0;
}
@@ -1999,19 +2307,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;
}
@@ -2020,18 +2328,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;
}
@@ -2044,8 +2352,8 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit)
/** Return the number of routers in <b>routers</b> that are currently up
* and available for building circuits through.
*/
-static int
-count_acceptable_nodes(smartlist_t *nodes)
+MOCK_IMPL(STATIC int,
+count_acceptable_nodes, (smartlist_t *nodes))
{
int num=0;
@@ -2061,10 +2369,9 @@ count_acceptable_nodes(smartlist_t *nodes)
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);
@@ -2090,6 +2397,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop)
}
}
+#ifdef TOR_UNIT_TESTS
+
+/** Unittest helper function: Count number of hops in cpath linked list. */
+unsigned int
+cpath_get_n_hops(crypt_path_t **head_ptr)
+{
+ unsigned int n_hops = 0;
+ crypt_path_t *tmp;
+
+ if (!*head_ptr) {
+ return 0;
+ }
+
+ tmp = *head_ptr;
+ if (tmp) {
+ n_hops++;
+ tmp = (*head_ptr)->next;
+ }
+
+ return n_hops;
+}
+
+#endif
+
/** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
* and <b>state</b> and the cpath <b>head</b> (currently populated only
* to length <b>cur_len</b> to decide a suitable middle hop for a
@@ -2127,8 +2458,6 @@ choose_good_middle_server(uint8_t purpose,
flags |= CRN_NEED_UPTIME;
if (state->need_capacity)
flags |= CRN_NEED_CAPACITY;
- if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE)
- flags |= CRN_ALLOW_INVALID;
choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
smartlist_free(excluded);
return choice;
@@ -2141,24 +2470,30 @@ 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.
+ *
+ * Set *<b>guard_state_out</b> to information about the guard that
+ * we're selecting, which we'll use later to remember whether the
+ * guard worked or not.
*/
-/* 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)
+choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out)
{
const node_t *choice;
smartlist_t *excluded;
const or_options_t *options = get_options();
/* If possible, choose an entry server with a preferred address,
* otherwise, choose one with an allowed address */
- router_crn_flags_t flags = CRN_NEED_GUARD|CRN_NEED_DESC|CRN_PREF_ADDR;
+ router_crn_flags_t flags = (CRN_NEED_GUARD|CRN_NEED_DESC|CRN_PREF_ADDR|
+ CRN_DIRECT_CONN);
const node_t *node;
if (state && options->UseEntryGuards &&
(purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) {
/* This request is for an entry server to use for a regular circuit,
* and we use entry guard nodes. Just return one of the guard nodes. */
- return choose_random_entry(state);
+ tor_assert(guard_state_out);
+ return guards_choose_guard(state, guard_state_out);
}
excluded = smartlist_new();
@@ -2168,25 +2503,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
* family. */
nodelist_add_node_and_family(excluded, node);
}
- /* and exclude current entry guards and their families,
- * unless we're in a test network, and excluding guards
- * would exclude all nodes (i.e. we're in an incredibly small tor network,
- * or we're using TestingAuthVoteGuard *).
- * This is an incomplete fix, but is no worse than the previous behaviour,
- * and only applies to minimal, testing tor networks
- * (so it's no less secure) */
- /*XXXX025 use the using_as_guard flag to accomplish this.*/
- if (options->UseEntryGuards
- && (!options->TestingTorNetwork ||
- smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards())
- )) {
- SMARTLIST_FOREACH(get_entry_guards(), const entry_guard_t *, entry,
- {
- if ((node = node_get_by_id(entry->identity))) {
- nodelist_add_node_and_family(excluded, node);
- }
- });
- }
if (state) {
if (state->need_uptime)
@@ -2194,8 +2510,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
if (state->need_capacity)
flags |= CRN_NEED_CAPACITY;
}
- if (options->AllowInvalid_ & ALLOW_INVALID_ENTRY)
- flags |= CRN_ALLOW_INVALID;
choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
smartlist_free(excluded);
@@ -2242,7 +2556,8 @@ onion_extend_cpath(origin_circuit_t *circ)
if (cur_len == state->desired_path_len - 1) { /* Picking last node */
info = extend_info_dup(state->chosen_exit);
} else if (cur_len == 0) { /* picking first node */
- const node_t *r = choose_good_entry_server(purpose, state);
+ const node_t *r = choose_good_entry_server(purpose, state,
+ &circ->guard_state);
if (r) {
/* If we're a client, use the preferred address rather than the
primary address, for potentially connecting to an IPv6 OR
@@ -2250,14 +2565,14 @@ onion_extend_cpath(origin_circuit_t *circ)
int client = (server_mode(get_options()) == 0);
info = extend_info_from_node(r, client);
/* Clients can fail to find an allowed address */
- tor_assert(info || client);
+ tor_assert_nonfatal(info || client);
}
} else {
const node_t *r =
choose_good_middle_server(purpose, state, circ->cpath, cur_len);
if (r) {
info = extend_info_from_node(r, 0);
- tor_assert(info);
+ tor_assert_nonfatal(info);
}
}
@@ -2300,19 +2615,23 @@ onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice)
/** Allocate a new extend_info object based on the various arguments. */
extend_info_t *
-extend_info_new(const char *nickname, const char *digest,
+extend_info_new(const char *nickname,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
crypto_pk_t *onion_key,
- const curve25519_public_key_t *curve25519_key,
+ const curve25519_public_key_t *ntor_key,
const tor_addr_t *addr, uint16_t port)
{
extend_info_t *info = tor_malloc_zero(sizeof(extend_info_t));
- memcpy(info->identity_digest, digest, DIGEST_LEN);
+ memcpy(info->identity_digest, rsa_id_digest, DIGEST_LEN);
+ if (ed_id && !ed25519_public_key_is_zero(ed_id))
+ memcpy(&info->ed_identity, ed_id, sizeof(ed25519_public_key_t));
if (nickname)
strlcpy(info->nickname, nickname, sizeof(info->nickname));
if (onion_key)
info->onion_key = crypto_pk_dup_key(onion_key);
- if (curve25519_key)
- memcpy(&info->curve25519_onion_key, curve25519_key,
+ if (ntor_key)
+ memcpy(&info->curve25519_onion_key, ntor_key,
sizeof(curve25519_public_key_t));
tor_addr_copy(&info->addr, addr);
info->port = port;
@@ -2354,20 +2673,43 @@ 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;
+ }
+
+ const ed25519_public_key_t *ed_pubkey = NULL;
+
+ /* Don't send the ed25519 pubkey unless the target node actually supports
+ * authenticating with it. */
+ if (node_supports_ed25519_link_authentication(node)) {
+ log_info(LD_CIRC, "Including Ed25519 ID for %s", node_describe(node));
+ ed_pubkey = node_get_ed25519_id(node);
+ } else if (node_get_ed25519_id(node)) {
+ log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't "
+ "be able to authenticate it.",
+ node_describe(node));
+ }
+
if (valid_addr && node->ri)
return extend_info_new(node->ri->nickname,
- node->identity,
- node->ri->onion_pkey,
- node->ri->onion_curve25519_pkey,
- &ap.addr,
- ap.port);
+ node->identity,
+ ed_pubkey,
+ node->ri->onion_pkey,
+ node->ri->onion_curve25519_pkey,
+ &ap.addr,
+ ap.port);
else if (valid_addr && node->rs && node->md)
return extend_info_new(node->rs->nickname,
- node->identity,
- node->md->onion_pkey,
- node->md->onion_curve25519_pkey,
- &ap.addr,
- ap.port);
+ node->identity,
+ ed_pubkey,
+ node->md->onion_pkey,
+ node->md->onion_curve25519_pkey,
+ &ap.addr,
+ ap.port);
else
return NULL;
}
@@ -2398,8 +2740,8 @@ extend_info_dup(extend_info_t *info)
return newinfo;
}
-/** Return the routerinfo_t for the chosen exit router in <b>state</b>.
- * If there is no chosen exit, or if we don't know the routerinfo_t for
+/** Return the node_t for the chosen exit router in <b>state</b>.
+ * If there is no chosen exit, or if we don't know the node_t for
* the chosen exit, return NULL.
*/
const node_t *
@@ -2410,6 +2752,17 @@ build_state_get_exit_node(cpath_build_state_t *state)
return node_get_by_id(state->chosen_exit->identity_digest);
}
+/** Return the RSA ID digest for the chosen exit router in <b>state</b>.
+ * If there is no chosen exit, return NULL.
+ */
+const uint8_t *
+build_state_get_exit_rsa_id(cpath_build_state_t *state)
+{
+ if (!state || !state->chosen_exit)
+ return NULL;
+ return (const uint8_t *) state->chosen_exit->identity_digest;
+}
+
/** Return the nickname for the chosen exit router in <b>state</b>. If
* there is no chosen exit, or if we don't know the routerinfo_t for the
* chosen exit, return NULL.
@@ -2439,3 +2792,89 @@ 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);
+}
+
+/** Find the circuits that are waiting to find out whether their guards are
+ * usable, and if any are ready to become usable, mark them open and try
+ * attaching streams as appropriate. */
+void
+circuit_upgrade_circuits_from_guard_wait(void)
+{
+ smartlist_t *to_upgrade =
+ circuit_find_circuits_to_upgrade_from_guard_wait();
+
+ if (to_upgrade == NULL)
+ return;
+
+ log_info(LD_GUARD, "Upgrading %d circuits from 'waiting for better guard' "
+ "to 'open'.", smartlist_len(to_upgrade));
+
+ SMARTLIST_FOREACH_BEGIN(to_upgrade, origin_circuit_t *, circ) {
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ circuit_has_opened(circ);
+ } SMARTLIST_FOREACH_END(circ);
+
+ smartlist_free(to_upgrade);
+}
+
diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h
index 7f5fd511a9..62a6367ed2 100644
--- a/src/or/circuitbuild.h
+++ b/src/or/circuitbuild.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -21,6 +21,8 @@ origin_circuit_t *origin_circuit_init(uint8_t purpose, int flags);
origin_circuit_t *circuit_establish_circuit(uint8_t purpose,
extend_info_t *exit,
int flags);
+struct circuit_guard_state_t *origin_circuit_get_guard_state(
+ origin_circuit_t *circ);
int circuit_handle_first_hop(origin_circuit_t *circ);
void circuit_n_chan_done(channel_t *chan, int status,
int close_origin_circuits);
@@ -29,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ);
int circuit_send_next_onion_skin(origin_circuit_t *circ);
void circuit_note_clock_jumped(int seconds_elapsed);
int circuit_extend(cell_t *cell, circuit_t *circ);
-int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse);
+int circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3);
struct created_cell_t;
int circuit_finish_handshake(origin_circuit_t *circ,
const struct created_cell_t *created_cell);
@@ -38,33 +41,51 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer,
int reason);
int onionskin_answer(or_circuit_t *circ,
const struct created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce);
-int circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
- int *need_capacity);
+MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
+ int *need_uptime,
+ int *need_capacity));
int circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *info);
int circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *info);
void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
-extend_info_t *extend_info_new(const char *nickname, const char *digest,
+extend_info_t *extend_info_new(const char *nickname,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
crypto_pk_t *onion_key,
- const curve25519_public_key_t *curve25519_key,
+ const curve25519_public_key_t *ntor_key,
const tor_addr_t *addr, uint16_t port);
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 uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state);
const node_t *build_state_get_exit_node(cpath_build_state_t *state);
const char *build_state_get_exit_nickname(cpath_build_state_t *state);
+struct circuit_guard_state_t;
+
const node_t *choose_good_entry_server(uint8_t purpose,
- cpath_build_state_t *state);
+ cpath_build_state_t *state,
+ struct circuit_guard_state_t **guard_state_out);
+void circuit_upgrade_circuits_from_guard_wait(void);
#ifdef CIRCUITBUILD_PRIVATE
STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan);
+STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei,
+ smartlist_t *nodes);
+MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes));
#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS)
STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags,
const or_options_t *options);
+unsigned int cpath_get_n_hops(crypt_path_t **head_ptr);
+
#endif
#endif
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 1efb7ef4d0..d891c89f38 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -1,13 +1,54 @@
/* Copyright 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file circuitlist.c
*
- * \brief Manage the global circuit list, and looking up circuits within it.
+ * \brief Manage global structures that list and index circuits, and
+ * look up circuits within them.
+ *
+ * One of the most frequent operations in Tor occurs every time that
+ * a relay cell arrives on a channel. When that happens, we need to
+ * find which circuit it is associated with, based on the channel and the
+ * circuit ID in the relay cell.
+ *
+ * To handle that, we maintain a global list of circuits, and a hashtable
+ * mapping [channel,circID] pairs to circuits. Circuits are added to and
+ * removed from this mapping using circuit_set_p_circid_chan() and
+ * circuit_set_n_circid_chan(). To look up a circuit from this map, most
+ * callers should use circuit_get_by_circid_channel(), though
+ * circuit_get_by_circid_channel_even_if_marked() is appropriate under some
+ * circumstances.
+ *
+ * We also need to allow for the possibility that we have blocked use of a
+ * circuit ID (because we are waiting to send a DESTROY cell), but the
+ * circuit is not there any more. For that case, we allow placeholder
+ * entries in the table, using channel_mark_circid_unusable().
+ *
+ * To efficiently handle a channel that has just opened, we also maintain a
+ * list of the circuits waiting for channels, so we can attach them as
+ * needed without iterating through the whole list of circuits, using
+ * circuit_get_all_pending_on_channel().
+ *
+ * In this module, we also handle the list of circuits that have been
+ * marked for close elsewhere, and close them as needed. (We use this
+ * "mark now, close later" pattern here and elsewhere to avoid
+ * unpredictable recursion if we closed every circuit immediately upon
+ * realizing it needed to close.) See circuit_mark_for_close() for the
+ * mark function, and circuit_close_all_marked() for the close function.
+ *
+ * For hidden services, we need to be able to look up introduction point
+ * circuits and rendezvous circuits by cookie, key, etc. These are
+ * currently handled with linear searches in
+ * circuit_get_ready_rend_circuit_by_rend_data(),
+ * circuit_get_next_by_pk_and_purpose(), and with hash lookups in
+ * circuit_get_rendezvous() and circuit_get_intro_point().
+ *
+ * This module is also the entry point for our out-of-memory handler
+ * logic, which was originally circuit-focused.
**/
#define CIRCUITLIST_PRIVATE
#include "or.h"
@@ -22,7 +63,12 @@
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
+#include "entrynodes.h"
#include "main.h"
+#include "hs_circuit.h"
+#include "hs_circuitmap.h"
+#include "hs_common.h"
+#include "hs_ident.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "onion.h"
@@ -34,6 +80,7 @@
#include "rephist.h"
#include "routerlist.h"
#include "routerset.h"
+#include "channelpadding.h"
#include "ht.h"
@@ -42,18 +89,23 @@
/** A global list of all circuits at this hop. */
static smartlist_t *global_circuitlist = NULL;
+/** A global list of all origin circuits. Every element of this is also
+ * an element of global_circuitlist. */
+static smartlist_t *global_origin_circuit_list = NULL;
+
/** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */
static smartlist_t *circuits_pending_chans = NULL;
+/** List of all the (origin) circuits whose state is
+ * CIRCUIT_STATE_GUARD_WAIT. */
+static smartlist_t *circuits_pending_other_guards = NULL;
+
/** A list of all the circuits that have been marked with
* circuit_mark_for_close and which are waiting for circuit_about_to_free. */
static smartlist_t *circuits_pending_close = NULL;
static void circuit_free_cpath_node(crypt_path_t *victim);
static void cpath_ref_decref(crypt_path_reference_t *cpath_ref);
-//static void circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ,
-// const uint8_t *token);
-static void circuit_clear_rend_token(or_circuit_t *circ);
static void circuit_about_to_free_atexit(circuit_t *circ);
static void circuit_about_to_free(circuit_t *circ);
@@ -109,7 +161,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>
@@ -309,8 +361,8 @@ channel_note_destroy_pending(channel_t *chan, circid_t id)
/** Called to indicate that a DESTROY is no longer pending on <b>chan</b> with
* circuit ID <b>id</b> -- typically, because it has been sent. */
-MOCK_IMPL(void, channel_note_destroy_not_pending,
- (channel_t *chan, circid_t id))
+MOCK_IMPL(void,
+channel_note_destroy_not_pending,(channel_t *chan, circid_t id))
{
circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan);
if (circ) {
@@ -386,8 +438,10 @@ circuit_set_state(circuit_t *circ, uint8_t state)
tor_assert(circ);
if (state == circ->state)
return;
- if (!circuits_pending_chans)
+ if (PREDICT_UNLIKELY(!circuits_pending_chans))
circuits_pending_chans = smartlist_new();
+ if (PREDICT_UNLIKELY(!circuits_pending_other_guards))
+ circuits_pending_other_guards = smartlist_new();
if (circ->state == CIRCUIT_STATE_CHAN_WAIT) {
/* remove from waiting-circuit list. */
smartlist_remove(circuits_pending_chans, circ);
@@ -396,7 +450,13 @@ circuit_set_state(circuit_t *circ, uint8_t state)
/* add to waiting-circuit list. */
smartlist_add(circuits_pending_chans, circ);
}
- if (state == CIRCUIT_STATE_OPEN)
+ if (circ->state == CIRCUIT_STATE_GUARD_WAIT) {
+ smartlist_remove(circuits_pending_other_guards, circ);
+ }
+ if (state == CIRCUIT_STATE_GUARD_WAIT) {
+ smartlist_add(circuits_pending_other_guards, circ);
+ }
+ if (state == CIRCUIT_STATE_GUARD_WAIT || state == CIRCUIT_STATE_OPEN)
tor_assert(!circ->n_chan_create_cell);
circ->state = state;
}
@@ -452,6 +512,39 @@ circuit_count_pending_on_channel(channel_t *chan)
return cnt;
}
+/** Remove <b>origin_circ</b> from the global list of origin circuits.
+ * Called when we are freeing a circuit.
+ */
+static void
+circuit_remove_from_origin_circuit_list(origin_circuit_t *origin_circ)
+{
+ int origin_idx = origin_circ->global_origin_circuit_list_idx;
+ if (origin_idx < 0)
+ return;
+ origin_circuit_t *c2;
+ tor_assert(origin_idx <= smartlist_len(global_origin_circuit_list));
+ c2 = smartlist_get(global_origin_circuit_list, origin_idx);
+ tor_assert(origin_circ == c2);
+ smartlist_del(global_origin_circuit_list, origin_idx);
+ if (origin_idx < smartlist_len(global_origin_circuit_list)) {
+ origin_circuit_t *replacement =
+ smartlist_get(global_origin_circuit_list, origin_idx);
+ replacement->global_origin_circuit_list_idx = origin_idx;
+ }
+ origin_circ->global_origin_circuit_list_idx = -1;
+}
+
+/** Add <b>origin_circ</b> to the global list of origin circuits. Called
+ * when creating the circuit. */
+static void
+circuit_add_to_origin_circuit_list(origin_circuit_t *origin_circ)
+{
+ tor_assert(origin_circ->global_origin_circuit_list_idx == -1);
+ smartlist_t *lst = circuit_get_global_origin_circuit_list();
+ smartlist_add(lst, origin_circ);
+ origin_circ->global_origin_circuit_list_idx = smartlist_len(lst) - 1;
+}
+
/** Detach from the global circuit list, and deallocate, all
* circuits that have been marked for close.
*/
@@ -474,6 +567,11 @@ circuit_close_all_marked(void)
}
circ->global_circuitlist_idx = -1;
+ /* Remove it from the origin circuit list, if appropriate. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ circuit_remove_from_origin_circuit_list(TO_ORIGIN_CIRCUIT(circ));
+ }
+
circuit_about_to_free(circ);
circuit_free(circ);
} SMARTLIST_FOREACH_END(circ);
@@ -481,7 +579,7 @@ circuit_close_all_marked(void)
smartlist_clear(circuits_pending_close);
}
-/** Return the head of the global linked list of circuits. */
+/** Return a pointer to the global list of circuits. */
MOCK_IMPL(smartlist_t *,
circuit_get_global_list,(void))
{
@@ -490,6 +588,15 @@ circuit_get_global_list,(void))
return global_circuitlist;
}
+/** Return a pointer to the global list of origin circuits. */
+smartlist_t *
+circuit_get_global_origin_circuit_list(void)
+{
+ if (NULL == global_origin_circuit_list)
+ global_origin_circuit_list = smartlist_new();
+ return global_origin_circuit_list;
+}
+
/** Function to make circ-\>state human-readable */
const char *
circuit_state_to_string(int state)
@@ -499,6 +606,8 @@ circuit_state_to_string(int state)
case CIRCUIT_STATE_BUILDING: return "doing handshakes";
case CIRCUIT_STATE_ONIONSKIN_PENDING: return "processing the onion";
case CIRCUIT_STATE_CHAN_WAIT: return "connecting to server";
+ case CIRCUIT_STATE_GUARD_WAIT: return "waiting to see how other "
+ "guards perform";
case CIRCUIT_STATE_OPEN: return "open";
default:
log_warn(LD_BUG, "Unknown circuit state %d", state);
@@ -708,6 +817,11 @@ init_circuit_base(circuit_t *circ)
circ->global_circuitlist_idx = smartlist_len(circuit_get_global_list()) - 1;
}
+/** If we haven't yet decided on a good timeout value for circuit
+ * building, we close idle circuits aggressively so we can get more
+ * data points. */
+#define IDLE_TIMEOUT_WHILE_LEARNING (1*60)
+
/** Allocate space for a new circuit, initializing with <b>p_circ_id</b>
* and <b>p_conn</b>. Add it to the global circuit list.
*/
@@ -729,8 +843,47 @@ origin_circuit_new(void)
init_circuit_base(TO_CIRCUIT(circ));
+ /* Add to origin-list. */
+ circ->global_origin_circuit_list_idx = -1;
+ circuit_add_to_origin_circuit_list(circ);
+
circuit_build_times_update_last_circ(get_circuit_build_times_mutable());
+ if (! circuit_build_times_disabled(get_options()) &&
+ circuit_build_times_needs_circuits(get_circuit_build_times())) {
+ /* Circuits should be shorter lived if we need more of them
+ * for learning a good build timeout */
+ circ->circuit_idle_timeout = IDLE_TIMEOUT_WHILE_LEARNING;
+ } else {
+ // This should always be larger than the current port prediction time
+ // remaining, or else we'll end up with the case where a circuit times out
+ // and another one is built, effectively doubling the timeout window.
+ //
+ // We also randomize it by up to 5% more (ie 5% of 0 to 3600 seconds,
+ // depending on how much circuit prediction time is remaining) so that
+ // we don't close a bunch of unused circuits all at the same time.
+ int prediction_time_remaining =
+ predicted_ports_prediction_time_remaining(time(NULL));
+ circ->circuit_idle_timeout = prediction_time_remaining+1+
+ crypto_rand_int(1+prediction_time_remaining/20);
+
+ if (circ->circuit_idle_timeout <= 0) {
+ log_warn(LD_BUG,
+ "Circuit chose a negative idle timeout of %d based on "
+ "%d seconds of predictive building remaining.",
+ circ->circuit_idle_timeout,
+ prediction_time_remaining);
+ circ->circuit_idle_timeout = IDLE_TIMEOUT_WHILE_LEARNING;
+ }
+
+ log_info(LD_CIRC,
+ "Circuit " U64_FORMAT " chose an idle timeout of %d based on "
+ "%d seconds of predictive building remaining.",
+ U64_PRINTF_ARG(circ->global_identifier),
+ circ->circuit_idle_timeout,
+ prediction_time_remaining);
+ }
+
return circ;
}
@@ -786,6 +939,9 @@ circuit_free(circuit_t *circ)
mem = ocirc;
memlen = sizeof(origin_circuit_t);
tor_assert(circ->magic == ORIGIN_CIRCUIT_MAGIC);
+
+ circuit_remove_from_origin_circuit_list(ocirc);
+
if (ocirc->build_state) {
extend_info_free(ocirc->build_state->chosen_exit);
circuit_free_cpath_node(ocirc->build_state->pending_final_cpath);
@@ -793,10 +949,17 @@ circuit_free(circuit_t *circ)
}
tor_free(ocirc->build_state);
+ /* Cancel before freeing, if we haven't already succeeded or failed. */
+ if (ocirc->guard_state) {
+ entry_guard_cancel(&ocirc->guard_state);
+ }
+ circuit_guard_state_free(ocirc->guard_state);
+
circuit_clear_cpath(ocirc);
crypto_pk_free(ocirc->intro_key);
rend_data_free(ocirc->rend_data);
+ hs_ident_circuit_free(ocirc->hs_ident);
tor_free(ocirc->dest_address);
if (ocirc->socks_username) {
@@ -824,8 +987,6 @@ circuit_free(circuit_t *circ)
crypto_cipher_free(ocirc->n_crypto);
crypto_digest_free(ocirc->n_digest);
- circuit_clear_rend_token(ocirc);
-
if (ocirc->rend_splice) {
or_circuit_t *other = ocirc->rend_splice;
tor_assert(other->base_.magic == OR_CIRCUIT_MAGIC);
@@ -857,6 +1018,11 @@ circuit_free(circuit_t *circ)
/* Remove from map. */
circuit_set_n_circid_chan(circ, 0, NULL);
+ /* Clear HS circuitmap token from this circ (if any) */
+ if (circ->hs_token) {
+ hs_circuitmap_remove_circuit(circ);
+ }
+
/* Clear cell queue _after_ removing it from the map. Otherwise our
* "active" checks will be violated. */
cell_queue_clear(&circ->n_chan_cells);
@@ -925,12 +1091,18 @@ circuit_free_all(void)
smartlist_free(lst);
global_circuitlist = NULL;
+ smartlist_free(global_origin_circuit_list);
+ global_origin_circuit_list = NULL;
+
smartlist_free(circuits_pending_chans);
circuits_pending_chans = NULL;
smartlist_free(circuits_pending_close);
circuits_pending_close = NULL;
+ smartlist_free(circuits_pending_other_guards);
+ circuits_pending_other_guards = NULL;
+
{
chan_circid_circuit_map_t **elt, **next, *c;
for (elt = HT_START(chan_circid_map, &chan_circid_map);
@@ -1311,9 +1483,11 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data)
if (!circ->marked_for_close &&
circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
- if (ocirc->rend_data &&
- !rend_cmp_service_ids(rend_data->onion_address,
- ocirc->rend_data->onion_address) &&
+ if (ocirc->rend_data == NULL) {
+ continue;
+ }
+ if (!rend_cmp_service_ids(rend_data_get_address(rend_data),
+ rend_data_get_address(ocirc->rend_data)) &&
tor_memeq(ocirc->rend_data->rend_cookie,
rend_data->rend_cookie,
REND_COOKIE_LEN))
@@ -1324,209 +1498,115 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data)
return NULL;
}
-/** Return the first circuit originating here in global_circuitlist after
- * <b>start</b> whose purpose is <b>purpose</b>, and where
- * <b>digest</b> (if set) matches the rend_pk_digest field. Return NULL if no
- * circuit is found. If <b>start</b> is NULL, begin at the start of the list.
- */
+/** Return the first service introduction circuit originating from the global
+ * circuit list after <b>start</b> or at the start of the list if <b>start</b>
+ * is NULL. Return NULL if no circuit is found.
+ *
+ * A service introduction point circuit has a purpose of either
+ * CIRCUIT_PURPOSE_S_ESTABLISH_INTRO or CIRCUIT_PURPOSE_S_INTRO. This does not
+ * return a circuit marked for close and its state must be open. */
origin_circuit_t *
-circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
- const char *digest, uint8_t purpose)
+circuit_get_next_service_intro_circ(origin_circuit_t *start)
{
- int idx;
+ int idx = 0;
smartlist_t *lst = circuit_get_global_list();
- tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(purpose));
- if (start == NULL)
- idx = 0;
- else
+
+ if (start) {
idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
+ }
for ( ; idx < smartlist_len(lst); ++idx) {
circuit_t *circ = smartlist_get(lst, idx);
- if (circ->marked_for_close)
- continue;
- if (circ->purpose != purpose)
+ /* Ignore a marked for close circuit or purpose not matching a service
+ * intro point or if the state is not open. */
+ if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN ||
+ (circ->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO &&
+ circ->purpose != CIRCUIT_PURPOSE_S_INTRO)) {
continue;
- if (!digest)
- return TO_ORIGIN_CIRCUIT(circ);
- else if (TO_ORIGIN_CIRCUIT(circ)->rend_data &&
- tor_memeq(TO_ORIGIN_CIRCUIT(circ)->rend_data->rend_pk_digest,
- digest, DIGEST_LEN))
- return TO_ORIGIN_CIRCUIT(circ);
+ }
+ /* The purposes we are looking for are only for origin circuits so the
+ * following is valid. */
+ return TO_ORIGIN_CIRCUIT(circ);
}
+ /* Not found. */
return NULL;
}
-/** Map from rendezvous cookie to or_circuit_t */
-static digestmap_t *rend_cookie_map = NULL;
-
-/** Map from introduction point digest to or_circuit_t */
-static digestmap_t *intro_digest_map = NULL;
-
-/** Return the OR circuit whose purpose is <b>purpose</b>, and whose
- * rend_token is the REND_TOKEN_LEN-byte <b>token</b>. If <b>is_rend_circ</b>,
- * look for rendezvous point circuits; otherwise look for introduction point
- * circuits. */
-static or_circuit_t *
-circuit_get_by_rend_token_and_purpose(uint8_t purpose, int is_rend_circ,
- const char *token)
-{
- or_circuit_t *circ;
- digestmap_t *map = is_rend_circ ? rend_cookie_map : intro_digest_map;
-
- if (!map)
- return NULL;
-
- circ = digestmap_get(map, token);
- if (!circ ||
- circ->base_.purpose != purpose ||
- circ->base_.marked_for_close)
- return NULL;
-
- if (!circ->rendinfo) {
- char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN));
- log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned a "
- "circuit with no rendinfo set.",
- safe_str(t), is_rend_circ);
- tor_free(t);
- return NULL;
- }
-
- if (! bool_eq(circ->rendinfo->is_rend_circ, is_rend_circ) ||
- tor_memneq(circ->rendinfo->rend_token, token, REND_TOKEN_LEN)) {
- char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN));
- log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned %s:%d",
- safe_str(t), is_rend_circ,
- safe_str(hex_str(circ->rendinfo->rend_token, REND_TOKEN_LEN)),
- (int)circ->rendinfo->is_rend_circ);
- tor_free(t);
- return NULL;
- }
-
- return circ;
-}
-
-/** Clear the rendezvous cookie or introduction point key digest that's
- * configured on <b>circ</b>, if any, and remove it from any such maps. */
-static void
-circuit_clear_rend_token(or_circuit_t *circ)
+/** Return the first service rendezvous circuit originating from the global
+ * circuit list after <b>start</b> or at the start of the list if <b>start</b>
+ * is NULL. Return NULL if no circuit is found.
+ *
+ * A service rendezvous point circuit has a purpose of either
+ * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does
+ * not return a circuit marked for close and its state must be open. */
+origin_circuit_t *
+circuit_get_next_service_rp_circ(origin_circuit_t *start)
{
- or_circuit_t *found_circ;
- digestmap_t *map;
-
- if (!circ || !circ->rendinfo)
- return;
-
- map = circ->rendinfo->is_rend_circ ? rend_cookie_map : intro_digest_map;
-
- if (!map) {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but found no map");
- return;
- }
+ int idx = 0;
+ smartlist_t *lst = circuit_get_global_list();
- found_circ = digestmap_get(map, circ->rendinfo->rend_token);
- if (found_circ == circ) {
- /* Great, this is the right one. */
- digestmap_remove(map, circ->rendinfo->rend_token);
- } else if (found_circ) {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but "
- "it was already replaced in the map.");
- } else {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but "
- "it not in the map at all.");
+ if (start) {
+ idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
}
- tor_free(circ->rendinfo); /* Sets it to NULL too */
-}
-
-/** Set the rendezvous cookie (if is_rend_circ), or the introduction point
- * digest (if ! is_rend_circ) of <b>circ</b> to the REND_TOKEN_LEN-byte value
- * in <b>token</b>, and add it to the appropriate map. If it previously had a
- * token, clear it. If another circuit previously had the same
- * cookie/intro-digest, mark that circuit and remove it from the map. */
-static void
-circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ,
- const uint8_t *token)
-{
- digestmap_t **map_p, *map;
- or_circuit_t *found_circ;
-
- /* Find the right map, creating it as needed */
- map_p = is_rend_circ ? &rend_cookie_map : &intro_digest_map;
-
- if (!*map_p)
- *map_p = digestmap_new();
-
- map = *map_p;
-
- /* If this circuit already has a token, we need to remove that. */
- if (circ->rendinfo)
- circuit_clear_rend_token(circ);
-
- if (token == NULL) {
- /* We were only trying to remove this token, not set a new one. */
- return;
- }
+ for ( ; idx < smartlist_len(lst); ++idx) {
+ circuit_t *circ = smartlist_get(lst, idx);
- found_circ = digestmap_get(map, (const char *)token);
- if (found_circ) {
- tor_assert(found_circ != circ);
- circuit_clear_rend_token(found_circ);
- if (! found_circ->base_.marked_for_close) {
- circuit_mark_for_close(TO_CIRCUIT(found_circ), END_CIRC_REASON_FINISHED);
- if (is_rend_circ) {
- log_fn(LOG_PROTOCOL_WARN, LD_REND,
- "Duplicate rendezvous cookie (%s...) used on two circuits",
- hex_str((const char*)token, 4)); /* only log first 4 chars */
- }
+ /* Ignore a marked for close circuit or purpose not matching a service
+ * intro point or if the state is not open. */
+ if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN ||
+ (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND &&
+ circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) {
+ continue;
}
+ /* The purposes we are looking for are only for origin circuits so the
+ * following is valid. */
+ return TO_ORIGIN_CIRCUIT(circ);
}
-
- /* Now set up the rendinfo */
- circ->rendinfo = tor_malloc(sizeof(*circ->rendinfo));
- memcpy(circ->rendinfo->rend_token, token, REND_TOKEN_LEN);
- circ->rendinfo->is_rend_circ = is_rend_circ ? 1 : 0;
-
- digestmap_set(map, (const char *)token, circ);
-}
-
-/** Return the circuit waiting for a rendezvous with the provided cookie.
- * Return NULL if no such circuit is found.
- */
-or_circuit_t *
-circuit_get_rendezvous(const uint8_t *cookie)
-{
- return circuit_get_by_rend_token_and_purpose(
- CIRCUIT_PURPOSE_REND_POINT_WAITING,
- 1, (const char*)cookie);
+ /* Not found. */
+ return NULL;
}
-/** Return the circuit waiting for intro cells of the given digest.
- * Return NULL if no such circuit is found.
+/** Return the first circuit originating here in global_circuitlist after
+ * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if
+ * set) matches the private key digest of the rend data associated with the
+ * circuit. Return NULL if no circuit is found. If <b>start</b> is NULL,
+ * begin at the start of the list.
*/
-or_circuit_t *
-circuit_get_intro_point(const uint8_t *digest)
+origin_circuit_t *
+circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
+ const uint8_t *digest, uint8_t purpose)
{
- return circuit_get_by_rend_token_and_purpose(
- CIRCUIT_PURPOSE_INTRO_POINT, 0,
- (const char *)digest);
-}
+ int idx;
+ smartlist_t *lst = circuit_get_global_list();
+ tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(purpose));
+ if (start == NULL)
+ idx = 0;
+ else
+ idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
-/** Set the rendezvous cookie of <b>circ</b> to <b>cookie</b>. If another
- * circuit previously had that cookie, mark it. */
-void
-circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie)
-{
- circuit_set_rend_token(circ, 1, cookie);
-}
+ for ( ; idx < smartlist_len(lst); ++idx) {
+ circuit_t *circ = smartlist_get(lst, idx);
+ origin_circuit_t *ocirc;
-/** Set the intro point key digest of <b>circ</b> to <b>cookie</b>. If another
- * circuit previously had that intro point digest, mark it. */
-void
-circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest)
-{
- circuit_set_rend_token(circ, 0, digest);
+ if (circ->marked_for_close)
+ continue;
+ if (circ->purpose != purpose)
+ continue;
+ /* At this point we should be able to get a valid origin circuit because
+ * the origin purpose we are looking for matches this circuit. */
+ if (BUG(!CIRCUIT_PURPOSE_IS_ORIGIN(circ->purpose))) {
+ break;
+ }
+ ocirc = TO_ORIGIN_CIRCUIT(circ);
+ if (!digest)
+ return ocirc;
+ if (rend_circuit_pk_digest_eq(ocirc, digest)) {
+ return ocirc;
+ }
+ }
+ return NULL;
}
/** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL,
@@ -1539,6 +1619,14 @@ circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest)
* cannibalize.
*
* If !CIRCLAUNCH_NEED_UPTIME, prefer returning non-uptime circuits.
+ *
+ * To "cannibalize" a circuit means to extend it an extra hop, and use it
+ * for some other purpose than we had originally intended. We do this when
+ * we want to perform some low-bandwidth task at a specific relay, and we
+ * would like the circuit to complete as soon as possible. (If we were going
+ * to use a lot of bandwidth, we wouldn't want a circuit with an extra hop.
+ * If we didn't care about circuit completion latency, we would just build
+ * a new circuit.)
*/
origin_circuit_t *
circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
@@ -1613,7 +1701,39 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
return best;
}
-/** Return the number of hops in circuit's path. */
+/**
+ * Check whether any of the origin circuits that are waiting to see if
+ * their guard is good enough to use can be upgraded to "ready". If so,
+ * return a new smartlist containing them. Otherwise return NULL.
+ */
+smartlist_t *
+circuit_find_circuits_to_upgrade_from_guard_wait(void)
+{
+ /* Only if some circuit is actually waiting on an upgrade should we
+ * run the algorithm. */
+ if (! circuits_pending_other_guards ||
+ smartlist_len(circuits_pending_other_guards)==0)
+ return NULL;
+ /* Only if we have some origin circuits should we run the algorithm. */
+ if (!global_origin_circuit_list)
+ return NULL;
+
+ /* Okay; we can pass our circuit list to entrynodes.c.*/
+ smartlist_t *result = smartlist_new();
+ int circuits_upgraded = entry_guards_upgrade_waiting_circuits(
+ get_guard_selection_info(),
+ global_origin_circuit_list,
+ result);
+ if (circuits_upgraded && smartlist_len(result)) {
+ return result;
+ } else {
+ smartlist_free(result);
+ return NULL;
+ }
+}
+
+/** 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 +1749,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)
{
@@ -1805,7 +1926,8 @@ circuit_about_to_free(circuit_t *circ)
* module then. If it isn't OPEN, we send it there now to remember which
* links worked and which didn't.
*/
- if (circ->state != CIRCUIT_STATE_OPEN) {
+ if (circ->state != CIRCUIT_STATE_OPEN &&
+ circ->state != CIRCUIT_STATE_GUARD_WAIT) {
if (CIRCUIT_IS_ORIGIN(circ)) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
circuit_build_failed(ocirc); /* take actions if necessary */
@@ -1816,12 +1938,24 @@ circuit_about_to_free(circuit_t *circ)
if (circuits_pending_chans)
smartlist_remove(circuits_pending_chans, circ);
}
+ if (circuits_pending_other_guards) {
+ smartlist_remove(circuits_pending_other_guards, circ);
+ }
if (CIRCUIT_IS_ORIGIN(circ)) {
control_event_circuit_status(TO_ORIGIN_CIRCUIT(circ),
- (circ->state == CIRCUIT_STATE_OPEN)?CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED,
+ (circ->state == CIRCUIT_STATE_OPEN ||
+ circ->state == CIRCUIT_STATE_GUARD_WAIT) ?
+ CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED,
orig_reason);
}
+ /* Notify the HS subsystem for any intro point circuit closing so it can be
+ * dealt with cleanly. */
+ if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO) {
+ hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ));
+ }
+
if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
int timed_out = (reason == END_CIRC_REASON_TIMEOUT);
@@ -1831,7 +1965,7 @@ circuit_about_to_free(circuit_t *circ)
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
/* treat this like getting a nack from it */
log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s",
- safe_str_client(ocirc->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)),
safe_str_client(build_state_get_exit_nickname(ocirc->build_state)),
timed_out ? "Recording timeout." : "Removing from descriptor.");
rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit,
@@ -1848,7 +1982,7 @@ circuit_about_to_free(circuit_t *circ)
log_info(LD_REND, "Failed intro circ %s to %s "
"(building circuit to intro point). "
"Marking intro point as possibly unreachable.",
- safe_str_client(ocirc->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)),
safe_str_client(build_state_get_exit_nickname(
ocirc->build_state)));
rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit,
@@ -1918,8 +2052,14 @@ marked_circuit_free_cells(circuit_t *circ)
return;
}
cell_queue_clear(&circ->n_chan_cells);
- if (! CIRCUIT_IS_ORIGIN(circ))
- cell_queue_clear(& TO_OR_CIRCUIT(circ)->p_chan_cells);
+ if (circ->n_mux)
+ circuitmux_clear_num_cells(circ->n_mux, circ);
+ if (! CIRCUIT_IS_ORIGIN(circ)) {
+ or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
+ cell_queue_clear(&orcirc->p_chan_cells);
+ if (orcirc->p_mux)
+ circuitmux_clear_num_cells(orcirc->p_mux, circ);
+ }
}
static size_t
@@ -1936,10 +2076,10 @@ single_conn_free_bytes(connection_t *conn)
}
if (conn->type == CONN_TYPE_DIR) {
dir_connection_t *dir_conn = TO_DIR_CONN(conn);
- if (dir_conn->zlib_state) {
- result += tor_zlib_state_size(dir_conn->zlib_state);
- tor_zlib_free(dir_conn->zlib_state);
- dir_conn->zlib_state = NULL;
+ if (dir_conn->compress_state) {
+ result += tor_compress_state_size(dir_conn->compress_state);
+ tor_compress_free(dir_conn->compress_state);
+ dir_conn->compress_state = NULL;
}
}
return result;
@@ -2015,7 +2155,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
@@ -2138,7 +2278,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 "
@@ -2152,8 +2291,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) {
@@ -2336,7 +2474,8 @@ assert_circuit_ok(const circuit_t *c)
tor_assert(c->deliver_window >= 0);
tor_assert(c->package_window >= 0);
- if (c->state == CIRCUIT_STATE_OPEN) {
+ if (c->state == CIRCUIT_STATE_OPEN ||
+ c->state == CIRCUIT_STATE_GUARD_WAIT) {
tor_assert(!c->n_chan_create_cell);
if (or_circ) {
tor_assert(or_circ->n_crypto);
diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h
index 2707b426ab..048cd5f763 100644
--- a/src/or/circuitlist.h
+++ b/src/or/circuitlist.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -15,6 +15,7 @@
#include "testsupport.h"
MOCK_DECL(smartlist_t *, circuit_get_global_list, (void));
+smartlist_t *circuit_get_global_origin_circuit_list(void);
const char *circuit_state_to_string(int state);
const char *circuit_purpose_to_controller_string(uint8_t purpose);
const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose);
@@ -45,11 +46,10 @@ origin_circuit_t *circuit_get_by_global_id(uint32_t id);
origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data(
const rend_data_t *rend_data);
origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
- const char *digest, uint8_t purpose);
-or_circuit_t *circuit_get_rendezvous(const uint8_t *cookie);
-or_circuit_t *circuit_get_intro_point(const uint8_t *digest);
-void circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie);
-void circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest);
+ const uint8_t *digest, uint8_t purpose);
+origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start);
+origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start);
+origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start);
origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose,
extend_info_t *info, int flags);
void circuit_mark_all_unused_circs(void);
@@ -77,6 +77,8 @@ void channel_note_destroy_pending(channel_t *chan, circid_t id);
MOCK_DECL(void, channel_note_destroy_not_pending,
(channel_t *chan, circid_t id));
+smartlist_t *circuit_find_circuits_to_upgrade_from_guard_wait(void);
+
#ifdef CIRCUITLIST_PRIVATE
STATIC void circuit_free(circuit_t *circ);
STATIC size_t n_cells_in_circ_queues(const circuit_t *c);
diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c
index cc1c4cd401..ee0d5c718b 100644
--- a/src/or/circuitmux.c
+++ b/src/or/circuitmux.c
@@ -1,52 +1,23 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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.h b/src/or/circuitmux.h
index 00745ac4a1..42a46aaa47 100644
--- a/src/or/circuitmux.h
+++ b/src/or/circuitmux.h
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c
index b784a140ac..fde2d22a89 100644
--- a/src/or/circuitmux_ewma.c
+++ b/src/or/circuitmux_ewma.c
@@ -1,13 +1,37 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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 ***/
@@ -475,7 +500,7 @@ ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1,
tor_assert(pol_data_2);
p1 = TO_EWMA_POL_DATA(pol_data_1);
- p2 = TO_EWMA_POL_DATA(pol_data_1);
+ p2 = TO_EWMA_POL_DATA(pol_data_2);
if (p1 != p2) {
/* Get the head cell_ewma_t from each queue */
@@ -706,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_add(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -721,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_remove(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -735,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol)
return smartlist_pqueue_pop(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index));
+ offsetof(cell_ewma_t, heap_index));
}
diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h
index 58aac1e196..1f04408789 100644
--- a/src/or/circuitmux_ewma.h
+++ b/src/or/circuitmux_ewma.h
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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..51d580a1a4 100644
--- a/src/or/circuitstats.c
+++ b/src/or/circuitstats.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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,27 +95,53 @@ 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)
+circuit_build_times_disabled(const or_options_t *options)
+{
+ return circuit_build_times_disabled_(options, 0);
+}
+
+/** As circuit_build_times_disabled, but take options as an argument. */
+int
+circuit_build_times_disabled_(const or_options_t *options,
+ int ignore_consensus)
{
if (unit_tests) {
return 0;
} else {
- int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
+ int consensus_disabled =
+ ignore_consensus ? 0 : 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 +349,6 @@ circuit_build_times_min_timeout(void)
"circuit_build_times_min_timeout() called, cbtmintimeout is %d",
num);
}
-
return num;
}
@@ -386,7 +425,7 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt,
* update if we aren't.
*/
- if (!circuit_build_times_disabled()) {
+ if (!circuit_build_times_disabled(get_options())) {
num = circuit_build_times_recent_circuit_count(ns);
if (num > 0) {
@@ -462,14 +501,15 @@ static double
circuit_build_times_get_initial_timeout(void)
{
double timeout;
+ const or_options_t *options = get_options();
/*
* Check if we have LearnCircuitBuildTimeout, and if we don't,
* always use CircuitBuildTimeout, no questions asked.
*/
- if (!unit_tests && get_options()->CircuitBuildTimeout) {
- timeout = get_options()->CircuitBuildTimeout*1000;
- if (get_options()->LearnCircuitBuildTimeout &&
+ if (!unit_tests && options->CircuitBuildTimeout) {
+ timeout = options->CircuitBuildTimeout*1000;
+ if (!circuit_build_times_disabled(options) &&
timeout < circuit_build_times_min_timeout()) {
log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
circuit_build_times_min_timeout()/1000);
@@ -511,7 +551,7 @@ circuit_build_times_init(circuit_build_times_t *cbt)
* Check if we really are using adaptive timeouts, and don't keep
* track of this stuff if not.
*/
- if (!circuit_build_times_disabled()) {
+ if (!circuit_build_times_disabled(get_options())) {
cbt->liveness.num_recent_circs =
circuit_build_times_recent_circuit_count(NULL);
cbt->liveness.timeouts_after_firsthop =
@@ -578,18 +618,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++;
@@ -875,7 +915,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt,
int err = 0;
circuit_build_times_init(cbt);
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
return 0;
}
@@ -1400,7 +1440,7 @@ circuit_build_times_network_check_changed(circuit_build_times_t *cbt)
#define MAX_TIMEOUT ((int32_t) (INT32_MAX/2))
/* Check to see if this has happened before. If so, double the timeout
- * to give people on abysmally bad network connections a shot at access */
+ * to give clients on abysmally bad network connections a shot at access */
if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) {
if (cbt->timeout_ms > MAX_TIMEOUT || cbt->close_ms > MAX_TIMEOUT) {
log_warn(LD_CIRC, "Insanely large circuit build timeout value. "
@@ -1476,7 +1516,7 @@ circuit_build_times_count_close(circuit_build_times_t *cbt,
int did_onehop,
time_t start_time)
{
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
cbt->close_ms = cbt->timeout_ms
= circuit_build_times_get_initial_timeout();
return 0;
@@ -1507,7 +1547,7 @@ void
circuit_build_times_count_timeout(circuit_build_times_t *cbt,
int did_onehop)
{
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
cbt->close_ms = cbt->timeout_ms
= circuit_build_times_get_initial_timeout();
return;
@@ -1581,7 +1621,7 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt)
/*
* Just return if we aren't using adaptive timeouts
*/
- if (circuit_build_times_disabled())
+ if (circuit_build_times_disabled(get_options()))
return;
if (!circuit_build_times_set_timeout_worker(cbt))
diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h
index 72b160983f..8a1dec4bfd 100644
--- a/src/or/circuitstats.h
+++ b/src/or/circuitstats.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -17,7 +17,10 @@ circuit_build_times_t *get_circuit_build_times_mutable(void);
double get_circuit_build_close_time_ms(void);
double get_circuit_build_timeout_ms(void);
-int circuit_build_times_disabled(void);
+int circuit_build_times_disabled(const or_options_t *options);
+int circuit_build_times_disabled_(const or_options_t *options,
+ int ignore_consensus);
+
int circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt);
void circuit_build_times_update_state(const circuit_build_times_t *cbt,
or_state_t *state);
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index b5959944f1..21cc9c540f 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -1,16 +1,35 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file circuituse.c
- * \brief Launch the right sort of circuits and attach streams to them.
+ * \brief Launch the right sort of circuits and attach the right streams to
+ * them.
+ *
+ * As distinct from circuitlist.c, which manages lookups to find circuits, and
+ * circuitbuild.c, which handles the logistics of circuit construction, this
+ * module keeps track of which streams can be attached to which circuits (in
+ * circuit_get_best()), and attaches streams to circuits (with
+ * circuit_try_attaching_streams(), connection_ap_handshake_attach_circuit(),
+ * and connection_ap_handshake_attach_chosen_circuit() ).
+ *
+ * This module also makes sure that we are building circuits for all of the
+ * predicted ports, using circuit_remove_handled_ports(),
+ * circuit_stream_is_being_handled(), and circuit_build_needed_cirs(). It
+ * handles launching circuits for specific targets using
+ * circuit_launch_by_extend_info().
+ *
+ * This is also where we handle expiring circuits that have been around for
+ * too long without actually completing, along with the circuit_build_timeout
+ * logic in circuitstats.c.
**/
#include "or.h"
#include "addressmap.h"
+#include "bridges.h"
#include "channel.h"
#include "circpathbias.h"
#include "circuitbuild.h"
@@ -22,6 +41,10 @@
#include "connection_edge.h"
#include "control.h"
#include "entrynodes.h"
+#include "hs_common.h"
+#include "hs_client.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
#include "nodelist.h"
#include "networkstatus.h"
#include "policies.h"
@@ -35,6 +58,36 @@
static void circuit_expire_old_circuits_clientside(void);
static void circuit_increment_failure_count(void);
+/** Check whether the hidden service destination of the stream at
+ * <b>edge_conn</b> is the same as the destination of the circuit at
+ * <b>origin_circ</b>. */
+static int
+circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
+ const origin_circuit_t *origin_circ)
+{
+ /* Check if this is a v2 rendezvous circ/stream */
+ if ((edge_conn->rend_data && !origin_circ->rend_data) ||
+ (!edge_conn->rend_data && origin_circ->rend_data) ||
+ (edge_conn->rend_data && origin_circ->rend_data &&
+ rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
+ rend_data_get_address(origin_circ->rend_data)))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ /* Check if this is a v3 rendezvous circ/stream */
+ if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
+ (!edge_conn->hs_ident && origin_circ->hs_ident) ||
+ (edge_conn->hs_ident && origin_circ->hs_ident &&
+ !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ &origin_circ->hs_ident->identity_pk))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ return 1;
+}
+
/** Return 1 if <b>circ</b> could be returned by circuit_get_best().
* Else return 0.
*/
@@ -149,14 +202,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
/* can't exit from this router */
return 0;
}
- } else { /* not general */
+ } else { /* not general: this might be a rend circuit */
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
- if ((edge_conn->rend_data && !origin_circ->rend_data) ||
- (!edge_conn->rend_data && origin_circ->rend_data) ||
- (edge_conn->rend_data && origin_circ->rend_data &&
- rend_cmp_service_ids(edge_conn->rend_data->onion_address,
- origin_circ->rend_data->onion_address))) {
- /* this circ is not for this conn */
+ if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
return 0;
}
}
@@ -203,7 +251,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 +270,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,
@@ -530,16 +578,14 @@ circuit_expire_building(void)
== CPATH_STATE_OPEN;
log_info(LD_CIRC,
"No circuits are opened. Relaxing timeout for circuit %d "
- "(a %s %d-hop circuit in state %s with channel state %s). "
- "%d guards are live.",
+ "(a %s %d-hop circuit in state %s with channel state %s).",
TO_ORIGIN_CIRCUIT(victim)->global_identifier,
circuit_purpose_to_string(victim->purpose),
TO_ORIGIN_CIRCUIT(victim)->build_state ?
TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
-1,
circuit_state_to_string(victim->state),
- channel_state_to_string(victim->n_chan->state),
- num_live_entry_guards(0));
+ channel_state_to_string(victim->n_chan->state));
/* We count the timeout here for CBT, because technically this
* was a timeout, and the timeout value needs to reset if we
@@ -557,7 +603,7 @@ circuit_expire_building(void)
"No circuits are opened. Relaxed timeout for circuit %d "
"(a %s %d-hop circuit in state %s with channel state %s) to "
"%ldms. However, it appears the circuit has timed out "
- "anyway. %d guards are live.",
+ "anyway.",
TO_ORIGIN_CIRCUIT(victim)->global_identifier,
circuit_purpose_to_string(victim->purpose),
TO_ORIGIN_CIRCUIT(victim)->build_state ?
@@ -565,8 +611,7 @@ circuit_expire_building(void)
-1,
circuit_state_to_string(victim->state),
channel_state_to_string(victim->n_chan->state),
- (long)build_close_ms,
- num_live_entry_guards(0));
+ (long)build_close_ms);
}
}
@@ -688,18 +733,15 @@ circuit_expire_building(void)
}
}
- /* If this is a hidden service client circuit which is far enough
- * along in connecting to its destination, and we haven't already
- * flagged it as 'timed out', and the user has not told us to
- * close such circs immediately on timeout, flag it as 'timed out'
- * so we'll launch another intro or rend circ, but don't mark it
- * for close yet.
+ /* If this is a hidden service client circuit which is far enough along in
+ * connecting to its destination, and we haven't already flagged it as
+ * 'timed out', flag it so we'll launch another intro or rend circ, but
+ * don't mark it for close yet.
*
* (Circs flagged as 'timed out' are given a much longer timeout
* period above, so we won't close them in the next call to
* circuit_expire_building.) */
- if (!(options->CloseHSClientCircuitsImmediatelyOnTimeout) &&
- !(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)) {
+ if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)) {
switch (victim->purpose) {
case CIRCUIT_PURPOSE_C_REND_READY:
/* We only want to spare a rend circ if it has been specified in
@@ -733,8 +775,7 @@ circuit_expire_building(void)
/* If this is a service-side rendezvous circuit which is far
* enough along in connecting to its destination, consider sparing
* it. */
- if (!(options->CloseHSServiceRendCircuitsImmediatelyOnTimeout) &&
- !(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) &&
+ if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) &&
victim->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
log_info(LD_CIRC,"Marking circ %u (state %d:%s, purpose %d) "
"as timed-out HS circ; relaunching rendezvous attempt.",
@@ -742,7 +783,7 @@ circuit_expire_building(void)
victim->state, circuit_state_to_string(victim->state),
victim->purpose);
TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
- rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim));
+ hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim));
continue;
}
@@ -780,6 +821,25 @@ circuit_expire_building(void)
} SMARTLIST_FOREACH_END(victim);
}
+/**
+ * Mark for close all circuits that start here, that were built through a
+ * guard we weren't sure if we wanted to use, and that have been waiting
+ * around for way too long.
+ */
+void
+circuit_expire_waiting_for_better_guard(void)
+{
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_origin_circuit_list(),
+ origin_circuit_t *, circ) {
+ if (TO_CIRCUIT(circ)->marked_for_close)
+ continue;
+ if (circ->guard_state == NULL)
+ continue;
+ if (entry_guard_state_should_expire(circ->guard_state))
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NONE);
+ } SMARTLIST_FOREACH_END(circ);
+}
+
/** For debugging #8387: track when we last called
* circuit_expire_old_circuits_clientside. */
static time_t last_expired_clientside_circuits = 0;
@@ -788,6 +848,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 +859,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 +867,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 +915,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");
}
@@ -987,8 +1063,138 @@ circuit_stream_is_being_handled(entry_connection_t *conn,
/** Don't keep more than this many unused open circuits around. */
#define MAX_UNUSED_OPEN_CIRCUITS 14
-/** Figure out how many circuits we have open that are clean. Make
- * sure it's enough for all the upcoming behaviors we predict we'll have.
+/* Return true if a circuit is available for use, meaning that it is open,
+ * clean, usable for new multi-hop connections, and a general purpose origin
+ * circuit.
+ * Accept any kind of circuit, return false if the above conditions are not
+ * met. */
+STATIC int
+circuit_is_available_for_use(const circuit_t *circ)
+{
+ const origin_circuit_t *origin_circ;
+ cpath_build_state_t *build_state;
+
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ return 0; /* We first filter out only origin circuits before doing the
+ following checks. */
+ if (circ->marked_for_close)
+ return 0; /* Don't mess with marked circs */
+ if (circ->timestamp_dirty)
+ return 0; /* Only count clean circs */
+ if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL)
+ return 0; /* We only pay attention to general purpose circuits.
+ General purpose circuits are always origin circuits. */
+
+ origin_circ = CONST_TO_ORIGIN_CIRCUIT(circ);
+ if (origin_circ->unusable_for_new_conns)
+ return 0;
+
+ build_state = origin_circ->build_state;
+ if (build_state->onehop_tunnel)
+ return 0;
+
+ return 1;
+}
+
+/* Return true if we need any more exit circuits.
+ * needs_uptime and needs_capacity are set only if we need more exit circuits.
+ * Check if we know of a port that's been requested recently and no circuit
+ * is currently available that can handle it. */
+STATIC int
+needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
+{
+ return (!circuit_all_predicted_ports_handled(now, needs_uptime,
+ needs_capacity) &&
+ router_have_consensus_path() == CONSENSUS_PATH_EXIT);
+}
+
+/* Hidden services need at least this many internal circuits */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS 3
+
+/* Return true if we need any more hidden service server circuits.
+ * HS servers only need an internal circuit. */
+STATIC int
+needs_hs_server_circuits(time_t now, int num_uptime_internal)
+{
+ if (!rend_num_services() && !hs_service_get_num_services()) {
+ /* No services, we don't need anything. */
+ goto no_need;
+ }
+
+ if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) {
+ /* We have sufficient amount of internal circuit. */
+ goto no_need;
+ }
+
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
+ /* Consensus hasn't been checked or might be invalid so requesting
+ * internal circuits is not wise. */
+ goto no_need;
+ }
+
+ /* At this point, we need a certain amount of circuits and we will most
+ * likely use them for rendezvous so we note down the use of internal
+ * circuit for our prediction for circuit needing uptime and capacity. */
+ rep_hist_note_used_internal(now, 1, 1);
+
+ return 1;
+ no_need:
+ return 0;
+}
+
+/* We need at least this many internal circuits for hidden service clients */
+#define SUFFICIENT_INTERNAL_HS_CLIENTS 3
+
+/* We need at least this much uptime for internal circuits for hidden service
+ * clients */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS 2
+
+/* Return true if we need any more hidden service client circuits.
+ * HS clients only need an internal circuit. */
+STATIC int
+needs_hs_client_circuits(time_t now, int *needs_uptime, int *needs_capacity,
+ int num_internal, int num_uptime_internal)
+{
+ int used_internal_recently = rep_hist_get_predicted_internal(now,
+ needs_uptime,
+ needs_capacity);
+ int requires_uptime = num_uptime_internal <
+ SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS &&
+ needs_uptime;
+
+ return (used_internal_recently &&
+ (requires_uptime || num_internal < SUFFICIENT_INTERNAL_HS_CLIENTS) &&
+ router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+}
+
+/* The minimum number of open slots we should keep in order to preemptively
+ * build circuits. */
+#define CBT_MIN_REMAINING_PREEMPTIVE_CIRCUITS 2
+
+/* Check to see if we need more circuits to have a good build timeout. However,
+ * leave a couple slots open so that we can still build circuits preemptively
+ * as needed. */
+#define CBT_MAX_UNUSED_OPEN_CIRCUITS (MAX_UNUSED_OPEN_CIRCUITS - \
+ CBT_MIN_REMAINING_PREEMPTIVE_CIRCUITS)
+
+/* Return true if we need more circuits for a good build timeout.
+ * XXXX make the assumption that build timeout streams should be
+ * created whenever we can build internal circuits. */
+STATIC int
+needs_circuits_for_build(int num)
+{
+ if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (num < CBT_MAX_UNUSED_OPEN_CIRCUITS &&
+ !circuit_build_times_disabled(get_options()) &&
+ circuit_build_times_needs_circuits_now(get_circuit_build_times())) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/** Determine how many circuits we have open that are clean,
+ * Make sure it's enough for all the upcoming behaviors we predict we'll have.
* But put an upper bound on the total number of circuits.
*/
static void
@@ -1000,25 +1206,14 @@ circuit_predict_and_launch_new(void)
time_t now = time(NULL);
int flags = 0;
- /* First, count how many of each type of circuit we have already. */
+ /* Count how many of each type of circuit we currently have. */
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
- cpath_build_state_t *build_state;
- origin_circuit_t *origin_circ;
- if (!CIRCUIT_IS_ORIGIN(circ))
- continue;
- if (circ->marked_for_close)
- continue; /* don't mess with marked circs */
- if (circ->timestamp_dirty)
- continue; /* only count clean circs */
- if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL)
- continue; /* only pay attention to general-purpose circs */
- origin_circ = TO_ORIGIN_CIRCUIT(circ);
- if (origin_circ->unusable_for_new_conns)
- continue;
- build_state = origin_circ->build_state;
- if (build_state->onehop_tunnel)
+ if (!circuit_is_available_for_use(circ))
continue;
+
num++;
+
+ cpath_build_state_t *build_state = TO_ORIGIN_CIRCUIT(circ)->build_state;
if (build_state->is_internal)
num_internal++;
if (build_state->need_uptime && build_state->is_internal)
@@ -1028,19 +1223,14 @@ circuit_predict_and_launch_new(void)
/* If that's enough, then stop now. */
if (num >= MAX_UNUSED_OPEN_CIRCUITS)
- return; /* we already have many, making more probably will hurt */
-
- /* Second, see if we need any more exit circuits. */
- /* check if we know of a port that's been requested recently
- * and no circuit is currently available that can handle it.
- * Exits (obviously) require an exit circuit. */
- if (!circuit_all_predicted_ports_handled(now, &port_needs_uptime,
- &port_needs_capacity)
- && router_have_consensus_path() == CONSENSUS_PATH_EXIT) {
+ return;
+
+ if (needs_exit_circuits(now, &port_needs_uptime, &port_needs_capacity)) {
if (port_needs_uptime)
flags |= CIRCLAUNCH_NEED_UPTIME;
if (port_needs_capacity)
flags |= CIRCLAUNCH_NEED_CAPACITY;
+
log_info(LD_CIRC,
"Have %d clean circs (%d internal), need another exit circ.",
num, num_internal);
@@ -1048,12 +1238,10 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Third, see if we need any more hidden service (server) circuits.
- * HS servers only need an internal circuit. */
- if (num_rend_services() && num_uptime_internal < 3
- && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (needs_hs_server_circuits(now, num_uptime_internal)) {
flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
CIRCLAUNCH_IS_INTERNAL);
+
log_info(LD_CIRC,
"Have %d clean circs (%d internal), need another internal "
"circ for my hidden service.",
@@ -1062,18 +1250,16 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Fourth, see if we need any more hidden service (client) circuits.
- * HS clients only need an internal circuit. */
- if (rep_hist_get_predicted_internal(now, &hidserv_needs_uptime,
- &hidserv_needs_capacity) &&
- ((num_uptime_internal<2 && hidserv_needs_uptime) ||
- num_internal<3)
- && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (needs_hs_client_circuits(now, &hidserv_needs_uptime,
+ &hidserv_needs_capacity,
+ num_internal, num_uptime_internal))
+ {
if (hidserv_needs_uptime)
flags |= CIRCLAUNCH_NEED_UPTIME;
if (hidserv_needs_capacity)
flags |= CIRCLAUNCH_NEED_CAPACITY;
flags |= CIRCLAUNCH_IS_INTERNAL;
+
log_info(LD_CIRC,
"Have %d clean circs (%d uptime-internal, %d internal), need"
" another hidden service circ.",
@@ -1082,34 +1268,25 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Finally, check to see if we still need more circuits to learn
- * a good build timeout. But if we're close to our max number we
- * want, don't do another -- we want to leave a few slots open so
- * we can still build circuits preemptively as needed.
- * XXXX make the assumption that build timeout streams should be
- * created whenever we can build internal circuits. */
- if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
- if (num < MAX_UNUSED_OPEN_CIRCUITS-2 &&
- ! circuit_build_times_disabled() &&
- circuit_build_times_needs_circuits_now(get_circuit_build_times())) {
- flags = CIRCLAUNCH_NEED_CAPACITY;
- /* if there are no exits in the consensus, make timeout
- * circuits internal */
- if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL)
- flags |= CIRCLAUNCH_IS_INTERNAL;
+ if (needs_circuits_for_build(num)) {
+ flags = CIRCLAUNCH_NEED_CAPACITY;
+ /* if there are no exits in the consensus, make timeout
+ * circuits internal */
+ if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL)
+ flags |= CIRCLAUNCH_IS_INTERNAL;
+
log_info(LD_CIRC,
"Have %d clean circs need another buildtime test circ.", num);
circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags);
return;
- }
}
}
/** Build a new test circuit every 5 minutes */
#define TESTING_CIRCUIT_INTERVAL 300
-/** This function is called once a second, if router_have_min_dir_info() is
- * true. Its job is to make sure all services we offer have enough circuits
+/** This function is called once a second, if router_have_minimum_dir_info()
+ * is true. Its job is to make sure all services we offer have enough circuits
* available. Some services just want enough circuits for current tasks,
* whereas others want a minimum set of idle circuits hanging around.
*/
@@ -1125,11 +1302,6 @@ circuit_build_needed_circs(time_t now)
if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
connection_ap_rescan_and_attach_pending();
- /* make sure any hidden services have enough intro points
- * HS intro point streams only require an internal circuit */
- if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
- rend_consider_services_intro_points();
-
circuit_expire_old_circs_as_needed(now);
if (!options->DisablePredictedCircuits)
@@ -1211,8 +1383,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
* number of streams on the circuit associated with the rend service.
*/
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
- tor_assert(origin_circ->rend_data);
- origin_circ->rend_data->nr_streams--;
+ hs_dec_rdv_stream_counter(origin_circ);
}
return;
}
@@ -1251,11 +1422,6 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
tor_fragile_assert();
}
-/** If we haven't yet decided on a good timeout value for circuit
- * building, we close idles circuits aggressively so we can get more
- * data points. */
-#define IDLE_TIMEOUT_WHILE_LEARNING (10*60)
-
/** Find each circuit that has been unused for too long, or dirty
* for too long and has no streams on it: mark it for close.
*/
@@ -1265,21 +1431,15 @@ circuit_expire_old_circuits_clientside(void)
struct timeval cutoff, now;
tor_gettimeofday(&now);
- cutoff = now;
last_expired_clientside_circuits = now.tv_sec;
- if (! circuit_build_times_disabled() &&
- circuit_build_times_needs_circuits(get_circuit_build_times())) {
- /* Circuits should be shorter lived if we need more of them
- * for learning a good build timeout */
- cutoff.tv_sec -= IDLE_TIMEOUT_WHILE_LEARNING;
- } else {
- cutoff.tv_sec -= get_options()->CircuitIdleTimeout;
- }
-
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ))
continue;
+
+ cutoff = now;
+ cutoff.tv_sec -= TO_ORIGIN_CIRCUIT(circ)->circuit_idle_timeout;
+
/* If the circuit has been dirty for too long, and there are no streams
* on it, mark it for close.
*/
@@ -1305,8 +1465,10 @@ circuit_expire_old_circuits_clientside(void)
(circ->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
circ->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) ||
circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
- log_debug(LD_CIRC,
- "Closing circuit that has been unused for %ld msec.",
+ log_info(LD_CIRC,
+ "Closing circuit "U64_FORMAT
+ " that has been unused for %ld msec.",
+ U64_PRINTF_ARG(TO_ORIGIN_CIRCUIT(circ)->global_identifier),
tv_mdiff(&circ->timestamp_began, &now));
circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
} else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) {
@@ -1426,7 +1588,7 @@ static void
circuit_testing_opened(origin_circuit_t *circ)
{
if (have_performed_bandwidth_test ||
- !check_whether_orport_reachable()) {
+ !check_whether_orport_reachable(get_options())) {
/* either we've already done everything we want with testing circuits,
* or this testing circuit became open due to a fluke, e.g. we picked
* a last hop where we already had the connection open due to an
@@ -1443,7 +1605,8 @@ circuit_testing_opened(origin_circuit_t *circ)
static void
circuit_testing_failed(origin_circuit_t *circ, int at_last_hop)
{
- if (server_mode(get_options()) && check_whether_orport_reachable())
+ const or_options_t *options = get_options();
+ if (server_mode(options) && check_whether_orport_reachable(options))
return;
log_info(LD_GENERAL,
@@ -1494,11 +1657,11 @@ circuit_has_opened(origin_circuit_t *circ)
break;
case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
/* at the service, waiting for introductions */
- rend_service_intro_has_opened(circ);
+ hs_service_circuit_has_opened(circ);
break;
case CIRCUIT_PURPOSE_S_CONNECT_REND:
/* at the service, connecting to rend point */
- rend_service_rendezvous_has_opened(circ);
+ hs_service_circuit_has_opened(circ);
break;
case CIRCUIT_PURPOSE_TESTING:
circuit_testing_opened(circ);
@@ -1596,7 +1759,9 @@ circuit_build_failed(origin_circuit_t *circ)
"Our circuit died before the first hop with no connection");
}
if (n_chan_id && !already_marked) {
- entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL));
+ /* New guard API: we failed. */
+ if (circ->guard_state)
+ entry_guard_failed(&circ->guard_state);
/* if there are any one-hop streams waiting on this circuit, fail
* them now so they can retry elsewhere. */
connection_ap_fail_onehop(n_chan_id, circ->build_state);
@@ -1646,7 +1811,7 @@ circuit_build_failed(origin_circuit_t *circ)
"(%s hop failed).",
escaped(build_state_get_exit_nickname(circ->build_state)),
failed_at_last_hop?"last":"non-last");
- rend_service_relaunch_rendezvous(circ);
+ hs_circ_retry_service_rendezvous_point(circ);
break;
/* default:
* This won't happen in normal operation, but might happen if the
@@ -1674,7 +1839,11 @@ circuit_launch(uint8_t purpose, int flags)
return circuit_launch_by_extend_info(purpose, NULL, flags);
}
-/* DOCDOC */
+/* Do we have enough descriptors to build paths?
+ * If need_exit is true, return 1 if we can build exit paths.
+ * (We need at least one Exit in the consensus to build exit paths.)
+ * If need_exit is false, return 1 if we can build internal paths.
+ */
static int
have_enough_path_info(int need_exit)
{
@@ -1853,16 +2022,22 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
c->state, conn_state_to_string(c->type, c->state));
}
tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_CIRCUIT_WAIT);
+
+ /* Will the exit policy of the exit node apply to this stream? */
check_exit_policy =
conn->socks_request->command == SOCKS_COMMAND_CONNECT &&
!conn->use_begindir &&
!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn));
+
+ /* Does this connection want a one-hop circuit? */
want_onehop = conn->want_onehop;
+ /* Do we need a high-uptime circuit? */
need_uptime = !conn->want_onehop && !conn->use_begindir &&
smartlist_contains_int_as_string(options->LongLivedPorts,
conn->socks_request->port);
+ /* Do we need an "internal" circuit? */
if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL)
need_internal = 1;
else if (conn->use_begindir || conn->want_onehop)
@@ -1870,23 +2045,33 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
else
need_internal = 0;
- circ = circuit_get_best(conn, 1, desired_circuit_purpose,
+ /* We now know what kind of circuit we need. See if there is an
+ * open circuit that we can use for this stream */
+ circ = circuit_get_best(conn, 1 /* Insist on open circuits */,
+ desired_circuit_purpose,
need_uptime, need_internal);
if (circ) {
+ /* We got a circuit that will work for this stream! We can return it. */
*circp = circ;
return 1; /* we're happy */
}
+ /* Okay, there's no circuit open that will work for this stream. Let's
+ * see if there's an in-progress circuit or if we have to launch one */
+
+ /* Do we know enough directory info to build circuits at all? */
int have_path = have_enough_path_info(!need_internal);
if (!want_onehop && (!router_have_minimum_dir_info() || !have_path)) {
+ /* If we don't have enough directory information, we can't build
+ * multihop circuits.
+ */
if (!connection_get_by_type(CONN_TYPE_DIR)) {
int severity = LOG_NOTICE;
- /* FFFF if this is a tunneled directory fetch, don't yell
- * as loudly. the user doesn't even know it's happening. */
+ /* Retry some stuff that might help the connection work. */
if (entry_list_is_constrained(options) &&
- entries_known_but_down(options)) {
+ guards_retry_optimistic(options)) {
log_fn(severity, LD_APP|LD_DIR,
"Application request when we haven't %s. "
"Optimistically trying known %s again.",
@@ -1894,7 +2079,6 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
"used client functionality lately" :
"received a consensus with exits",
options->UseBridges ? "bridges" : "entrynodes");
- entries_retry_all(options);
} else if (!options->UseBridges || any_bridge_descriptors_known()) {
log_fn(severity, LD_APP|LD_DIR,
"Application request when we haven't %s. "
@@ -1905,14 +2089,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
routerlist_retry_directory_downloads(time(NULL));
}
}
- /* the stream will be dealt with when router_have_minimum_dir_info becomes
- * 1, or when all directory attempts fail and directory_all_unreachable()
+ /* Since we didn't have enough directory info, we can't attach now. The
+ * stream will be dealt with when router_have_minimum_dir_info becomes 1,
+ * or when all directory attempts fail and directory_all_unreachable()
* kills it.
*/
return 0;
}
- /* Do we need to check exit policy? */
+ /* Check whether the exit policy of the chosen exit, or the exit policies
+ * of _all_ nodes, would forbid this node. */
if (check_exit_policy) {
if (!conn->chosen_exit_name) {
struct in_addr in;
@@ -1931,8 +2117,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)) {
@@ -1953,16 +2139,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
- /* is one already on the way? */
- circ = circuit_get_best(conn, 0, desired_circuit_purpose,
+ /* Now, check whether there already a circuit on the way that could handle
+ * this stream. This check matches the one above, but this time we
+ * do not require that the circuit will work. */
+ circ = circuit_get_best(conn, 0 /* don't insist on open circuits */,
+ desired_circuit_purpose,
need_uptime, need_internal);
if (circ)
log_debug(LD_CIRC, "one on the way!");
+
if (!circ) {
+ /* No open or in-progress circuit could handle this stream! We
+ * will have to launch one!
+ */
+
+ /* The chosen exit node, if there is one. */
extend_info_t *extend_info=NULL;
- uint8_t new_circ_purpose;
const int n_pending = count_pending_general_client_circuits();
+ /* Do we have too many pending circuits? */
if (n_pending >= options->MaxClientCircuitsPending) {
static ratelim_t delay_limit = RATELIM_INIT(10*60);
char *m;
@@ -1976,6 +2171,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
return 0;
}
+ /* If this is a hidden service trying to start an introduction point,
+ * handle that case. */
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
/* need to pick an intro point */
rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
@@ -1984,7 +2181,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
if (!extend_info) {
log_info(LD_REND,
"No intro points for '%s': re-fetching service descriptor.",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(rend_data_get_address(rend_data)));
rend_client_refetch_v2_renddesc(rend_data);
connection_ap_mark_as_non_pending_circuit(conn);
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT;
@@ -1992,7 +2189,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
log_info(LD_REND,"Chose %s as intro point for '%s'.",
extend_info_describe(extend_info),
- safe_str_client(rend_data->onion_address));
+ safe_str_client(rend_data_get_address(rend_data)));
}
/* If we have specified a particular exit node for our
@@ -2013,17 +2210,22 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
"Discarding this circuit.", conn->chosen_exit_name);
return -1;
}
- } else {
+ } else { /* ! (r && node_has_descriptor(r)) */
log_debug(LD_DIR, "considering %d, %s",
want_onehop, conn->chosen_exit_name);
if (want_onehop && conn->chosen_exit_name[0] == '$') {
/* We're asking for a one-hop circuit to a router that
* we don't have a routerinfo about. Make up an extend_info. */
+ /* XXX prop220: we need to make chosen_exit_name able to
+ * encode both key formats. This is not absolutely critical
+ * since this is just for one-hop circuits, but we should
+ * still get it done */
char digest[DIGEST_LEN];
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;
}
@@ -2032,10 +2234,13 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
escaped_safe_str_client(conn->socks_request->address));
return -1;
}
+ /* XXXX prop220 add a workaround for ed25519 ID below*/
extend_info = extend_info_new(conn->chosen_exit_name+1,
- digest, NULL, NULL, &addr,
- conn->socks_request->port);
- } else {
+ digest,
+ NULL, /* Ed25519 ID */
+ NULL, NULL, /* onion keys */
+ &addr, conn->socks_request->port);
+ } else { /* ! (want_onehop && conn->chosen_exit_name[0] == '$') */
/* We will need an onion key for the router, and we
* don't have one. Refuse or relax requirements. */
log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
@@ -2053,8 +2258,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
}
- }
+ } /* Done checking for general circutis with chosen exits. */
+ /* What purpose do we need to launch this circuit with? */
+ uint8_t new_circ_purpose;
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED)
new_circ_purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
else if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
@@ -2063,6 +2270,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
new_circ_purpose = desired_circuit_purpose;
#ifdef ENABLE_TOR2WEB_MODE
+ /* If tor2Web is on, then hidden service requests should be one-hop.
+ */
if (options->Tor2webMode &&
(new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND ||
new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) {
@@ -2070,6 +2279,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
#endif
+ /* Determine what kind of a circuit to launch, and actually launch it. */
{
int flags = CIRCLAUNCH_NEED_CAPACITY;
if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
@@ -2081,6 +2291,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
extend_info_free(extend_info);
+ /* Now trigger things that need to happen when we launch circuits */
+
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) {
/* We just caused a circuit to get built because of this stream.
* If this stream has caused a _lot_ of circuits to be built, that's
@@ -2104,6 +2316,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
} /* endif (!circ) */
+
+ /* We either found a good circuit, or launched a new circuit, or failed to
+ * do so. Report success, and delay. */
+
if (circ) {
/* Mark the circuit with the isolation fields for this connection.
* When the circuit arrives, we'll clear these flags: this is
@@ -2141,10 +2357,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;
@@ -2174,8 +2391,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
/* We are attaching a stream to a rendezvous circuit. That means
* that an attempt to connect to a hidden service just
* succeeded. Tell rendclient.c. */
- rend_client_note_connection_attempt_ended(
- ENTRY_TO_EDGE_CONN(apconn)->rend_data);
+ hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
}
if (cpath) { /* we were given one; use it */
@@ -2302,7 +2518,9 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn,
pathbias_count_use_attempt(circ);
+ /* Now, actually link the connection. */
link_apconn_to_circ(conn, circ, cpath);
+
tor_assert(conn->socks_request);
if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
if (!conn->use_begindir)
@@ -2317,12 +2535,11 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn,
return 1;
}
-/** Try to find a safe live circuit for CONN_TYPE_AP connection conn. If
- * we don't find one: if conn cannot be handled by any known nodes,
- * warn and return -1 (conn needs to die, and is maybe already marked);
- * else launch new circuit (if necessary) and return 0.
- * Otherwise, associate conn with a safe live circuit, do the
- * right next step, and return 1.
+/** Try to find a safe live circuit for stream <b>conn</b>. If we find one,
+ * attach the stream, send appropriate cells, and return 1. Otherwise,
+ * try to launch new circuit(s) for the stream. If we can launch
+ * circuits, return 0. Otherwise, if we simply can't proceed with
+ * this stream, return -1. (conn needs to die, and is maybe already marked).
*/
/* XXXX this function should mark for close whenever it returns -1;
* its callers shouldn't have to worry about that. */
@@ -2341,6 +2558,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
conn_age = (int)(time(NULL) - base_conn->timestamp_created);
+ /* Is this connection so old that we should give up on it? */
if (conn_age >= get_options()->SocksTimeout) {
int severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ?
LOG_INFO : LOG_NOTICE;
@@ -2351,10 +2569,34 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
return -1;
}
+ /* We handle "general" (non-onion) connections much more straightforwardly.
+ */
if (!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn))) {
/* we're a general conn */
origin_circuit_t *circ=NULL;
+ /* Are we linked to a dir conn that aims to fetch a consensus?
+ * We check here because the conn might no longer be needed. */
+ if (base_conn->linked_conn &&
+ base_conn->linked_conn->type == CONN_TYPE_DIR &&
+ base_conn->linked_conn->purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
+
+ /* Yes we are. Is there a consensus fetch farther along than us? */
+ if (networkstatus_consensus_is_already_downloading(
+ TO_DIR_CONN(base_conn->linked_conn)->requested_resource)) {
+ /* We're doing the "multiple consensus fetch attempts" game from
+ * proposal 210, and we're late to the party. Just close this conn.
+ * The circuit and TLS conn that we made will time out after a while
+ * if nothing else wants to use them. */
+ log_info(LD_DIR, "Closing extra consensus fetch (to %s) since one "
+ "is already downloading.", base_conn->linked_conn->address);
+ return -1;
+ }
+ }
+
+ /* If we have a chosen exit, we need to use a circuit that's
+ * open to that exit. See what exit we meant, and whether we can use it.
+ */
if (conn->chosen_exit_name) {
const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1);
int opt = conn->chosen_exit_optional;
@@ -2368,6 +2610,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
"Requested exit point '%s' is not known. %s.",
conn->chosen_exit_name, opt ? "Trying others" : "Closing");
if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
conn->chosen_exit_optional = 0;
tor_free(conn->chosen_exit_name);
return 0;
@@ -2380,6 +2623,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
"would refuse request. %s.",
conn->chosen_exit_name, opt ? "Trying others" : "Closing");
if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
conn->chosen_exit_optional = 0;
tor_free(conn->chosen_exit_name);
return 0;
@@ -2388,20 +2632,25 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
}
}
- /* find the circuit that we should use, if there is one. */
+ /* Find the circuit that we should use, if there is one. Otherwise
+ * launch it. */
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) {
+ /* We were either told "-1" (complete failure) or 0 (circuit in
+ * progress); we can't attach this stream yet. */
return retval;
+ }
log_debug(LD_APP|LD_CIRC,
"Attaching apconn to circ %u (stream %d sec old).",
(unsigned)circ->base_.n_circ_id, conn_age);
- /* print the circ's path, so people can figure out which circs are
+ /* print the circ's path, so clients can figure out which circs are
* sucking. */
circuit_log_path(LOG_INFO,LD_APP|LD_CIRC,circ);
- /* We have found a suitable circuit for our conn. Hurray. */
+ /* We have found a suitable circuit for our conn. Hurray. Do
+ * the attachment. */
return connection_ap_handshake_attach_chosen_circuit(conn, circ, NULL);
} else { /* we're a rendezvous conn */
@@ -2566,7 +2815,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/circuituse.h b/src/or/circuituse.h
index 5973978c45..e66679586d 100644
--- a/src/or/circuituse.h
+++ b/src/or/circuituse.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -13,6 +13,7 @@
#define TOR_CIRCUITUSE_H
void circuit_expire_building(void);
+void circuit_expire_waiting_for_better_guard(void);
void circuit_remove_handled_ports(smartlist_t *needed_ports);
int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port,
int min);
@@ -59,5 +60,26 @@ int hostname_in_track_host_exits(const or_options_t *options,
const char *address);
void mark_circuit_unusable_for_new_conns(origin_circuit_t *circ);
+#ifdef TOR_UNIT_TESTS
+/* Used only by circuituse.c and test_circuituse.c */
+
+STATIC int circuit_is_available_for_use(const circuit_t *circ);
+
+STATIC int needs_exit_circuits(time_t now,
+ int *port_needs_uptime,
+ int *port_needs_capacity);
+STATIC int needs_hs_server_circuits(time_t now,
+ int num_uptime_internal);
+
+STATIC int needs_hs_client_circuits(time_t now,
+ int *needs_uptime,
+ int *needs_capacity,
+ int num_internal,
+ int num_uptime_internal);
+
+STATIC int needs_circuits_for_build(int num);
+
+#endif
+
#endif
diff --git a/src/or/command.c b/src/or/command.c
index 5ad92bed1e..2c82984901 100644
--- a/src/or/command.c
+++ b/src/or/command.c
@@ -1,12 +1,32 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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:
@@ -306,10 +326,19 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
return;
}
+ if (connection_or_digest_is_known_relay(chan->identity_digest)) {
+ rep_hist_note_circuit_handshake_requested(create_cell->handshake_type);
+ // Needed for chutney: Sometimes relays aren't in the consensus yet, and
+ // get marked as clients. This resets their channels once they appear.
+ // Probably useful for normal operation wrt relay flapping, too.
+ chan->is_client = 0;
+ } else {
+ channel_mark_client(chan);
+ }
+
if (create_cell->handshake_type != ONION_HANDSHAKE_TYPE_FAST) {
/* hand it off to the cpuworkers, and then return. */
- if (connection_or_digest_is_known_relay(chan->identity_digest))
- rep_hist_note_circuit_handshake_requested(create_cell->handshake_type);
+
if (assign_onionskin_to_cpuworker(circ, create_cell) < 0) {
log_debug(LD_GENERAL,"Failed to hand off onionskin. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT);
@@ -324,8 +353,14 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
int len;
created_cell_t created_cell;
- /* Make sure we never try to use the OR connection on which we
- * received this cell to satisfy an EXTEND request, */
+ /* If the client used CREATE_FAST, it's probably a tor client or bridge
+ * relay, and we must not use it for EXTEND requests (in most cases, we
+ * won't have an authenticated peer ID for the extend).
+ * Public relays on 0.2.9 and later will use CREATE_FAST if they have no
+ * ntor onion key for this relay, but that should be a rare occurrence.
+ * Clients on 0.3.1 and later avoid using CREATE_FAST as much as they can,
+ * even during bootstrap, so the CREATE_FAST check is most accurate for
+ * earlier tor client versions. */
channel_mark_client(chan);
memset(&created_cell, 0, sizeof(created_cell));
@@ -346,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
created_cell.handshake_len = len;
if (onionskin_answer(circ, &created_cell,
- (const char *)keys, rend_circ_nonce)<0) {
+ (const char *)keys, sizeof(keys),
+ rend_circ_nonce)<0) {
log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return;
diff --git a/src/or/command.h b/src/or/command.h
index 12cda6a463..5079d42e75 100644
--- a/src/or/command.h
+++ b/src/or/command.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/config.c b/src/or/config.c
index 2e14ba69dc..9b6bf40ebf 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -1,16 +1,66 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file config.c
- * \brief Code to parse and interpret configuration files.
+ * \brief Code to interpret the user's configuration of Tor.
+ *
+ * This module handles torrc configuration file, including parsing it,
+ * combining it with torrc.defaults and the command line, allowing
+ * user changes to it (via editing and SIGHUP or via the control port),
+ * writing it back to disk (because of SAVECONF from the control port),
+ * and -- most importantly, acting on it.
+ *
+ * The module additionally has some tools for manipulating and
+ * inspecting values that are calculated as a result of the
+ * configured options.
+ *
+ * <h3>How to add new options</h3>
+ *
+ * To add new items to the torrc, there are a minimum of three places to edit:
+ * <ul>
+ * <li>The or_options_t structure in or.h, where the options are stored.
+ * <li>The option_vars_ array below in this module, which configures
+ * the names of the torrc options, their types, their multiplicities,
+ * and their mappings to fields in or_options_t.
+ * <li>The manual in doc/tor.1.txt, to document what the new option
+ * is, and how it works.
+ * </ul>
+ *
+ * Additionally, you might need to edit these places too:
+ * <ul>
+ * <li>options_validate() below, in case you want to reject some possible
+ * values of the new configuration option.
+ * <li>options_transition_allowed() below, in case you need to
+ * forbid some or all changes in the option while Tor is
+ * running.
+ * <li>options_transition_affects_workers(), in case changes in the option
+ * might require Tor to relaunch or reconfigure its worker threads.
+ * <li>options_transition_affects_descriptor(), in case changes in the
+ * option might require a Tor relay to build and publish a new server
+ * descriptor.
+ * <li>options_act() and/or options_act_reversible(), in case there's some
+ * action that needs to be taken immediately based on the option's
+ * value.
+ * </ul>
+ *
+ * <h3>Changing the value of an option</h3>
+ *
+ * Because of the SAVECONF command from the control port, it's a bad
+ * idea to change the value of any user-configured option in the
+ * or_options_t. If you want to sometimes do this anyway, we recommend
+ * that you create a secondary field in or_options_t; that you have the
+ * user option linked only to the secondary field; that you use the
+ * secondary field to initialize the one that Tor actually looks at; and that
+ * you use the one Tor looks as the one that you modify.
**/
#define CONFIG_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "compat.h"
#include "addressmap.h"
#include "channel.h"
@@ -18,10 +68,13 @@
#include "circuitlist.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
+#include "circuitstats.h"
+#include "compress.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
#include "connection_or.h"
+#include "consdiffmgr.h"
#include "control.h"
#include "confparse.h"
#include "cpuworker.h"
@@ -38,6 +91,7 @@
#include "relay.h"
#include "rendclient.h"
#include "rendservice.h"
+#include "hs_config.h"
#include "rephist.h"
#include "router.h"
#include "sandbox.h"
@@ -48,7 +102,6 @@
#include "statefile.h"
#include "transports.h"
#include "ext_orport.h"
-#include "torgzip.h"
#ifdef _WIN32
#include <shlobj.h>
#endif
@@ -65,11 +118,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 +152,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 +169,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},
};
@@ -125,7 +177,7 @@ static config_abbrev_t option_abbrevs_[] = {
* or_options_t.<b>member</b>"
*/
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -133,8 +185,17 @@ static config_abbrev_t option_abbrevs_[] = {
/** An entry for config_vars: "The option <b>name</b> is obsolete." */
#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL }
-#define VPORT(member,conftype,initvalue) \
- VAR(#member, conftype, member ## _lines, initvalue)
+/**
+ * Macro to declare *Port options. Each one comes in three entries.
+ * For example, most users should use "SocksPort" to configure the
+ * socks port, but TorBrowser wants to use __SocksPort so that it
+ * isn't stored by SAVECONF. The SocksPortLines virtual option is
+ * used to query both options from the controller.
+ */
+#define VPORT(member) \
+ VAR(#member "Lines", LINELIST_V, member ## _lines, NULL), \
+ VAR(#member, LINELIST_S, member ## _lines, NULL), \
+ VAR("__" #member, LINELIST_S, member ## _lines, NULL)
/** Array of configuration options. Until we disallow nonstandard
* abbreviations, order is significant, since the first matching option will
@@ -146,10 +207,10 @@ static config_var_t option_vars_[] = {
V(AccountingStart, STRING, NULL),
V(Address, STRING, NULL),
V(AllowDotExit, BOOL, "0"),
- V(AllowInvalidNodes, CSV, "middle,rendezvous"),
+ OBSOLETE("AllowInvalidNodes"),
V(AllowNonRFC953Hostnames, BOOL, "0"),
- V(AllowSingleHopCircuits, BOOL, "0"),
- V(AllowSingleHopExits, BOOL, "0"),
+ OBSOLETE("AllowSingleHopCircuits"),
+ OBSOLETE("AllowSingleHopExits"),
V(AlternateBridgeAuthority, LINELIST, NULL),
V(AlternateDirAuthority, LINELIST, NULL),
OBSOLETE("AlternateHSAuthority"),
@@ -162,14 +223,14 @@ static config_var_t option_vars_[] = {
V(AuthDirInvalidCCs, CSV, ""),
V(AuthDirFastGuarantee, MEMUNIT, "100 KB"),
V(AuthDirGuardBWGuarantee, MEMUNIT, "2 MB"),
- V(AuthDirPinKeys, BOOL, "0"),
+ V(AuthDirPinKeys, BOOL, "1"),
V(AuthDirReject, LINELIST, NULL),
V(AuthDirRejectCCs, CSV, ""),
OBSOLETE("AuthDirRejectUnlisted"),
OBSOLETE("AuthDirListBadDirs"),
V(AuthDirListBadExits, BOOL, "0"),
V(AuthDirMaxServersPerAddr, UINT, "2"),
- V(AuthDirMaxServersPerAuthAddr,UINT, "5"),
+ OBSOLETE("AuthDirMaxServersPerAuthAddr"),
V(AuthDirHasIPv6Connectivity, BOOL, "0"),
VAR("AuthoritativeDirectory", BOOL, AuthoritativeDir, "0"),
V(AutomapHostsOnResolve, BOOL, "0"),
@@ -183,9 +244,11 @@ static config_var_t option_vars_[] = {
V(BridgeRecordUsageByCountry, BOOL, "1"),
V(BridgeRelay, BOOL, "0"),
V(CellStatistics, BOOL, "0"),
+ V(PaddingStatistics, BOOL, "1"),
V(LearnCircuitBuildTimeout, BOOL, "1"),
V(CircuitBuildTimeout, INTERVAL, "0"),
- V(CircuitIdleTimeout, INTERVAL, "1 hour"),
+ OBSOLETE("CircuitIdleTimeout"),
+ V(CircuitsAvailableTimeout, INTERVAL, "0"),
V(CircuitStreamTimeout, INTERVAL, "0"),
V(CircuitPriorityHalflife, DOUBLE, "-100.0"), /*negative:'Use default'*/
V(ClientDNSRejectInternalAddresses, BOOL,"1"),
@@ -202,8 +265,8 @@ static config_var_t option_vars_[] = {
V(ConstrainedSockets, BOOL, "0"),
V(ConstrainedSockSize, MEMUNIT, "8192"),
V(ContactInfo, STRING, NULL),
- V(ControlListenAddress, LINELIST, NULL),
- VPORT(ControlPort, LINELIST, NULL),
+ OBSOLETE("ControlListenAddress"),
+ VPORT(ControlPort),
V(ControlPortFileGroupReadable,BOOL, "0"),
V(ControlPortWriteToFile, FILENAME, NULL),
V(ControlSocket, LINELIST, NULL),
@@ -215,12 +278,13 @@ 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"),
- V(DirListenAddress, LINELIST, NULL),
+ OBSOLETE("DirListenAddress"),
V(DirPolicy, LINELIST, NULL),
- VPORT(DirPort, LINELIST, NULL),
+ VPORT(DirPort),
V(DirPortFrontPage, FILENAME, NULL),
VAR("DirReqStatistics", BOOL, DirReqStatistics_option, "1"),
VAR("DirAuthority", LINELIST, DirAuthorities, NULL),
@@ -228,11 +292,11 @@ 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),
- V(DNSListenAddress, LINELIST, NULL),
+ VPORT(DNSPort),
+ OBSOLETE("DNSListenAddress"),
V(DownloadExtraInfo, BOOL, "0"),
V(TestingEnableConnBwEvent, BOOL, "0"),
V(TestingEnableCellStatsEvent, BOOL, "0"),
@@ -243,24 +307,27 @@ static config_var_t option_vars_[] = {
V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
V(ExcludeNodes, ROUTERSET, NULL),
V(ExcludeExitNodes, ROUTERSET, NULL),
- V(ExcludeSingleHopRelays, BOOL, "1"),
+ OBSOLETE("ExcludeSingleHopRelays"),
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"),
- VPORT(ExtORPort, LINELIST, NULL),
+ VPORT(ExtORPort),
V(ExtORPortCookieAuthFile, STRING, NULL),
V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"),
V(ExtraInfoStatistics, BOOL, "1"),
+ V(ExtendByEd25519ID, AUTOBOOL, "auto"),
V(FallbackDir, LINELIST, NULL),
+
V(UseDefaultFallbackDirs, BOOL, "1"),
OBSOLETE("FallbackNetworkstatusFile"),
V(FascistFirewall, BOOL, "0"),
V(FirewallPorts, CSV, ""),
- V(FastFirstHopPK, AUTOBOOL, "auto"),
+ OBSOLETE("FastFirstHopPK"),
V(FetchDirInfoEarly, BOOL, "0"),
V(FetchDirInfoExtraEarly, BOOL, "0"),
V(FetchServerDescriptors, BOOL, "1"),
@@ -295,10 +362,12 @@ static config_var_t option_vars_[] = {
VAR("HiddenServiceMaxStreams",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceMaxStreamsCloseCircuit",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL),
- V(HiddenServiceStatistics, BOOL, "1"),
+ VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
V(HidServAuth, LINELIST, NULL),
- V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
- V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
+ OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"),
+ OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"),
+ V(HiddenServiceSingleHopMode, BOOL, "0"),
+ V(HiddenServiceNonAnonymousMode,BOOL, "0"),
V(HTTPProxy, STRING, NULL),
V(HTTPProxyAuthenticator, STRING, NULL),
V(HTTPSProxy, STRING, NULL),
@@ -325,26 +394,30 @@ static config_var_t option_vars_[] = {
V(MaxAdvertisedBandwidth, MEMUNIT, "1 GB"),
V(MaxCircuitDirtiness, INTERVAL, "10 minutes"),
V(MaxClientCircuitsPending, UINT, "32"),
+ V(MaxConsensusAgeForDiffs, INTERVAL, "0 seconds"),
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),
+ VAR("MyFamily", LINELIST, MyFamily_lines, NULL),
V(NewCircuitPeriod, INTERVAL, "30 seconds"),
OBSOLETE("NamingAuthoritativeDirectory"),
- V(NATDListenAddress, LINELIST, NULL),
- VPORT(NATDPort, LINELIST, NULL),
+ OBSOLETE("NATDListenAddress"),
+ VPORT(NATDPort),
V(Nickname, STRING, NULL),
- V(PredictedPortsRelevanceTime, INTERVAL, "1 hour"),
- V(WarnUnsafeSocks, BOOL, "1"),
+ OBSOLETE("PredictedPortsRelevanceTime"),
+ OBSOLETE("WarnUnsafeSocks"),
VAR("NodeFamily", LINELIST, NodeFamilies, NULL),
V(NumCPUs, UINT, "0"),
V(NumDirectoryGuards, UINT, "0"),
V(NumEntryGuards, UINT, "0"),
V(OfflineMasterKey, BOOL, "0"),
- V(ORListenAddress, LINELIST, NULL),
- VPORT(ORPort, LINELIST, NULL),
+ OBSOLETE("ORListenAddress"),
+ VPORT(ORPort),
V(OutboundBindAddress, LINELIST, NULL),
+ V(OutboundBindAddressOR, LINELIST, NULL),
+ V(OutboundBindAddressExit, LINELIST, NULL),
OBSOLETE("PathBiasDisableRate"),
V(PathBiasCircThreshold, INT, "-1"),
@@ -390,6 +463,8 @@ static config_var_t option_vars_[] = {
V(RecommendedClientVersions, LINELIST, NULL),
V(RecommendedServerVersions, LINELIST, NULL),
V(RecommendedPackages, LINELIST, NULL),
+ V(ReducedConnectionPadding, BOOL, "0"),
+ V(ConnectionPadding, AUTOBOOL, "auto"),
V(RefuseUnknownExits, AUTOBOOL, "auto"),
V(RejectPlaintextPorts, CSV, ""),
V(RelayBandwidthBurst, MEMUNIT, "0"),
@@ -413,9 +488,9 @@ static config_var_t option_vars_[] = {
V(SchedulerHighWaterMark__, MEMUNIT, "101 MB"),
V(SchedulerMaxFlushCells__, UINT, "1000"),
V(ShutdownWaitLength, INTERVAL, "30 seconds"),
- V(SocksListenAddress, LINELIST, NULL),
+ OBSOLETE("SocksListenAddress"),
V(SocksPolicy, LINELIST, NULL),
- VPORT(SocksPort, LINELIST, NULL),
+ VPORT(SocksPort),
V(SocksTimeout, INTERVAL, "2 minutes"),
V(SSLKeyLifetime, INTERVAL, "0"),
OBSOLETE("StrictEntryNodes"),
@@ -426,22 +501,24 @@ static config_var_t option_vars_[] = {
V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"),
V(Tor2webMode, BOOL, "0"),
V(Tor2webRendezvousPoints, ROUTERSET, NULL),
- V(TLSECGroup, STRING, NULL),
+ OBSOLETE("TLSECGroup"),
V(TrackHostExits, CSV, NULL),
V(TrackHostExitsExpire, INTERVAL, "30 minutes"),
- V(TransListenAddress, LINELIST, NULL),
- VPORT(TransPort, LINELIST, NULL),
+ OBSOLETE("TransListenAddress"),
+ VPORT(TransPort),
V(TransProxyType, STRING, "default"),
OBSOLETE("TunnelDirConns"),
V(UpdateBridgesFromAuthority, BOOL, "0"),
V(UseBridges, BOOL, "0"),
- V(UseEntryGuards, BOOL, "1"),
- V(UseEntryGuardsAsDirGuards, BOOL, "1"),
+ VAR("UseEntryGuards", BOOL, UseEntryGuards_option, "1"),
+ OBSOLETE("UseEntryGuardsAsDirGuards"),
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"),
+ V(AuthDirTestEd25519LinkKeys, BOOL, "1"),
OBSOLETE("V1AuthoritativeDirectory"),
OBSOLETE("V2AuthoritativeDirectory"),
VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"),
@@ -461,7 +538,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"),
@@ -482,11 +560,13 @@ static config_var_t option_vars_[] = {
"10800, 21600, 43200"),
/* With the ClientBootstrapConsensus*Download* below:
* Clients with only authorities will try:
- * - 3 authorities over 10 seconds, then wait 60 minutes.
+ * - at least 3 authorities over 10 seconds, then exponentially backoff,
+ * with the next attempt 3-21 seconds later,
* Clients with authorities and fallbacks will try:
- * - 2 authorities and 4 fallbacks over 21 seconds, then wait 60 minutes.
+ * - at least 2 authorities and 4 fallbacks over 21 seconds, then
+ * exponentially backoff, with the next attempts 4-33 seconds later,
* Clients will also retry when an application request arrives.
- * After a number of failed reqests, clients retry every 3 days + 1 hour.
+ * After a number of failed requests, clients retry every 3 days + 1 hour.
*
* Clients used to try 2 authorities over 10 seconds, then wait for
* 60 minutes or an application request.
@@ -494,7 +574,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 +585,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"),
@@ -536,7 +616,6 @@ static const config_var_t testing_tor_network_defaults[] = {
V(EnforceDistinctSubnets, BOOL, "0"),
V(AssumeReachable, BOOL, "1"),
V(AuthDirMaxServersPerAddr, UINT, "0"),
- V(AuthDirMaxServersPerAuthAddr,UINT, "0"),
V(ClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL,
"0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"),
V(ClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL,
@@ -545,7 +624,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 +667,24 @@ 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." },
+ { "ClientDNSRejectInternalAddresses", "Turning this on makes your client "
+ "easier to fingerprint, and may open you to esoteric attacks." },
+ /* End of options deprecated since 0.2.9.2-alpha. */
+
+ /* Deprecated since 0.3.2.0-alpha. */
+ { "HTTPProxy", "It only applies to direct unencrypted HTTP connections "
+ "to your directory server, which your Tor probably wasn't using." },
+ { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy "
+ "which should be used with HTTPSProxyAuthenticator." },
+ /* End of options deprecated since 0.3.2.0-alpha. */
+
+ { NULL, NULL }
+};
+
#ifdef _WIN32
static char *get_windows_conf_root(void);
#endif
@@ -599,7 +696,9 @@ static int options_transition_affects_workers(
const or_options_t *old_options, const or_options_t *new_options);
static int options_transition_affects_descriptor(
const or_options_t *old_options, const or_options_t *new_options);
-static int check_nickname_list(char **lst, const char *name, char **msg);
+static int normalize_nickname_list(config_line_t **normalized_out,
+ const config_line_t *lst, const char *name,
+ char **msg);
static char *get_bindaddr_from_transport_listen_line(const char *line,
const char *transport);
static int parse_ports(or_options_t *options, int validate_only,
@@ -634,8 +733,9 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val,
STATIC config_format_t options_format = {
sizeof(or_options_t),
OR_OPTIONS_MAGIC,
- STRUCT_OFFSET(or_options_t, magic_),
+ offsetof(or_options_t, magic_),
option_abbrevs_,
+ option_deprecation_notes_,
option_vars_,
options_validate_cb,
NULL
@@ -735,7 +835,7 @@ set_options(or_options_t *new_val, char **msg)
tor_free(line);
}
} else {
- smartlist_add(elements, tor_strdup(options_format.vars[i].name));
+ smartlist_add_strdup(elements, options_format.vars[i].name);
smartlist_add(elements, NULL);
}
}
@@ -746,7 +846,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;
}
@@ -806,6 +906,7 @@ or_options_free(or_options_t *options)
tor_free(options->BridgePassword_AuthDigest_);
tor_free(options->command_arg);
tor_free(options->master_key_fname);
+ config_free_lines(options->MyFamily);
config_free(&options_format, options);
}
@@ -910,8 +1011,8 @@ static const char *default_authorities[] = {
"dizum orport=443 "
"v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 "
"194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
- "Tonga orport=443 bridge "
- "82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D",
+ "Bifroest orport=443 bridge "
+ "37.218.247.217:80 1D8F 3A91 C37C 5D1C 4C19 B1AD 1D0C FBE8 BF72 D8E1",
"gabelmoo orport=443 "
"v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 "
"ipv6=[2001:638:a000:4140::ffff:189]:443 "
@@ -919,11 +1020,6 @@ static const char *default_authorities[] = {
"dannenberg orport=443 "
"v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E "
"193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
- "urras orport=80 "
- "v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C "
- "208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417"
- /* XX/teor - urras may have an IPv6 address, but it's not in urras'
- * descriptor as of 11 Dec 2015. See #17813. */,
"maatuska orport=80 "
"v3ident=49015F787433103580E3B66A1707A00E60F2D15B "
"ipv6=[2001:67c:289c::9]:80 "
@@ -1339,6 +1435,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:
@@ -1420,21 +1545,32 @@ get_effective_bwburst(const or_options_t *options)
return (uint32_t)bw;
}
-/** Return True if any changes from <b>old_options</b> to
- * <b>new_options</b> needs us to refresh our TLS context. */
+/**
+ * Return true if changing the configuration from <b>old</b> to <b>new</b>
+ * affects the guard susbsystem.
+ */
static int
-options_transition_requires_fresh_tls_context(const or_options_t *old_options,
- const or_options_t *new_options)
+options_transition_affects_guards(const or_options_t *old,
+ const or_options_t *new)
{
- tor_assert(new_options);
-
- if (!old_options)
- return 0;
-
- if (!opt_streq(old_options->TLSECGroup, new_options->TLSECGroup))
- return 1;
-
- return 0;
+ /* NOTE: Make sure this function stays in sync with
+ * entry_guards_set_filtered_flags */
+
+ tor_assert(old);
+ tor_assert(new);
+
+ return
+ (old->UseEntryGuards != new->UseEntryGuards ||
+ old->UseBridges != new->UseBridges ||
+ old->ClientUseIPv4 != new->ClientUseIPv4 ||
+ old->ClientUseIPv6 != new->ClientUseIPv6 ||
+ old->FascistFirewall != new->FascistFirewall ||
+ !routerset_equal(old->ExcludeNodes, new->ExcludeNodes) ||
+ !routerset_equal(old->EntryNodes, new->EntryNodes) ||
+ !smartlist_strings_eq(old->FirewallPorts, new->FirewallPorts) ||
+ !config_lines_eq(old->Bridges, new->Bridges) ||
+ !config_lines_eq(old->ReachableORAddresses, new->ReachableORAddresses) ||
+ !config_lines_eq(old->ReachableDirAddresses, new->ReachableDirAddresses));
}
/** Fetch the active option list, and take actions based on it. All of the
@@ -1456,6 +1592,8 @@ options_act(const or_options_t *old_options)
const int transition_affects_workers =
old_options && options_transition_affects_workers(old_options, options);
int old_ewma_enabled;
+ const int transition_affects_guards =
+ old_options && options_transition_affects_guards(old_options, options);
/* disable ptrace and later, other basic debugging techniques */
{
@@ -1494,10 +1632,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 */
@@ -1545,7 +1683,7 @@ options_act(const or_options_t *old_options)
sweep_bridge_list();
}
- if (running_tor && rend_config_services(options, 0)<0) {
+ if (running_tor && hs_config_service_all(options, 0)<0) {
log_warn(LD_BUG,
"Previously validated hidden services line could not be added!");
return -1;
@@ -1625,13 +1763,6 @@ options_act(const or_options_t *old_options)
log_warn(LD_BUG,"Error initializing keys; exiting");
return -1;
}
- } else if (old_options &&
- options_transition_requires_fresh_tls_context(old_options,
- options)) {
- if (router_initialize_tls_context() < 0) {
- log_warn(LD_BUG,"Error initializing TLS context.");
- return -1;
- }
}
/* Write our PID to the PID file. If we do not have write permissions we
@@ -1652,6 +1783,15 @@ options_act(const or_options_t *old_options)
return -1;
}
+ if (server_mode(options)) {
+ static int cdm_initialized = 0;
+ if (cdm_initialized == 0) {
+ cdm_initialized = 1;
+ consdiffmgr_configure(NULL);
+ consdiffmgr_validate();
+ }
+ }
+
if (init_control_cookie_authentication(options->CookieAuthentication) < 0) {
log_warn(LD_CONFIG,"Error creating control cookie authentication file.");
return -1;
@@ -1660,7 +1800,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 (hs_service_load_all_keys() < 0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
return -1;
}
@@ -1679,17 +1819,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());
@@ -1743,6 +1872,7 @@ options_act(const or_options_t *old_options)
if (old_options) {
int revise_trackexithosts = 0;
int revise_automap_entries = 0;
+ int abandon_circuits = 0;
if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
options->UseBridges != old_options->UseBridges ||
(options->UseBridges &&
@@ -1759,6 +1889,16 @@ options_act(const or_options_t *old_options)
"Changed to using entry guards or bridges, or changed "
"preferred or excluded node lists. "
"Abandoning previous circuits.");
+ abandon_circuits = 1;
+ }
+
+ if (transition_affects_guards) {
+ if (guards_update_all()) {
+ abandon_circuits = 1;
+ }
+ }
+
+ if (abandon_circuits) {
circuit_mark_all_unused_circs();
circuit_mark_all_dirty_circs_as_unusable();
revise_trackexithosts = 1;
@@ -1789,7 +1929,7 @@ options_act(const or_options_t *old_options)
addressmap_clear_invalid_automaps(options);
/* How long should we delay counting bridge stats after becoming a bridge?
- * We use this so we don't count people who used our bridge thinking it is
+ * We use this so we don't count clients who used our bridge thinking it is
* a relay. If you change this, don't forget to change the log message
* below. It's 4 hours (the time it takes to stop being used by clients)
* plus some extra time for clock skew. */
@@ -1840,6 +1980,8 @@ options_act(const or_options_t *old_options)
/* Only collect directory-request statistics on relays and bridges. */
options->DirReqStatistics = options->DirReqStatistics_option &&
server_mode(options);
+ options->HiddenServiceStatistics =
+ options->HiddenServiceStatistics_option && server_mode(options);
if (options->CellStatistics || options->DirReqStatistics ||
options->EntryStatistics || options->ExitPortStatistics ||
@@ -1854,7 +1996,6 @@ options_act(const or_options_t *old_options)
options->CellStatistics = 0;
options->EntryStatistics = 0;
options->ConnDirectionStatistics = 0;
- options->HiddenServiceStatistics = 0;
options->ExitPortStatistics = 0;
}
@@ -1940,13 +2081,6 @@ options_act(const or_options_t *old_options)
!options->BridgeAuthoritativeDir)
rep_hist_desc_stats_term();
- /* Check if we need to parse and add the EntryNodes config option. */
- if (options->EntryNodes &&
- (!old_options ||
- !routerset_equal(old_options->EntryNodes,options->EntryNodes) ||
- !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)))
- entry_nodes_should_be_added();
-
/* Since our options changed, we might need to regenerate and upload our
* server descriptor.
*/
@@ -2003,12 +2137,8 @@ static const struct {
{ "--dump-config", ARGUMENT_OPTIONAL },
{ "--list-fingerprint", TAKES_NO_ARGUMENT },
{ "--keygen", TAKES_NO_ARGUMENT },
+ { "--key-expiration", ARGUMENT_OPTIONAL },
{ "--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 },
@@ -2020,6 +2150,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 },
@@ -2082,7 +2213,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]);
@@ -2156,31 +2287,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;
}
@@ -2195,7 +2325,7 @@ print_usage(void)
printf(
"Copyright (c) 2001-2004, Roger Dingledine\n"
"Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n"
-"Copyright (c) 2007-2016, The Tor Project, Inc.\n\n"
+"Copyright (c) 2007-2017, The Tor Project, Inc.\n\n"
"tor -f <torrc> [args]\n"
"See man page for options, or https://www.torproject.org/ for "
"documentation.\n");
@@ -2206,7 +2336,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 ||
@@ -2214,7 +2343,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. */
@@ -2234,6 +2372,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
@@ -2260,8 +2406,8 @@ reset_last_resolved_addr(void)
* Fail if one or more of the following is true:
* - DNS name in <b>options-\>Address</b> cannot be resolved.
* - <b>options-\>Address</b> is a local host address.
- * - Attempt to getting local hostname fails.
- * - Attempt to getting network interface address fails.
+ * - Attempt at getting local hostname fails.
+ * - Attempt at getting network interface address fails.
*
* Return 0 if all is well, or -1 if we can't find a suitable
* public IP address.
@@ -2393,7 +2539,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,
@@ -2489,7 +2635,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
@@ -2642,13 +2787,13 @@ compute_publishserverdescriptor(or_options_t *options)
#define MIN_REND_POST_PERIOD (10*60)
#define MIN_REND_POST_PERIOD_TESTING (5)
-/** Higest allowable value for PredictedPortsRelevanceTime; if this is
- * too high, our selection of exits will decrease for an extended
- * period of time to an uncomfortable level .*/
-#define MAX_PREDICTED_CIRCS_RELEVANCE (60*60)
+/** Highest allowable value for CircuitsAvailableTimeout.
+ * If this is too large, client connections will stay open for too long,
+ * incurring extra padding overhead. */
+#define MAX_CIRCS_AVAILABLE_TIME (24*60*60)
/** Highest allowable value for RendPostPeriod. */
-#define MAX_DIR_PERIOD (MIN_ONION_KEY_LIFETIME/2)
+#define MAX_DIR_PERIOD ((7*24*60*60)/2)
/** Lowest allowable value for MaxCircuitDirtiness; if this is too low, Tor
* will generate too many circuits and potentially overload the network. */
@@ -2662,10 +2807,6 @@ compute_publishserverdescriptor(or_options_t *options)
* will generate too many circuits and potentially overload the network. */
#define MIN_CIRCUIT_STREAM_TIMEOUT 10
-/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
- * expose more information than we're comfortable with. */
-#define MIN_HEARTBEAT_PERIOD (30*60)
-
/** Lowest recommended value for CircuitBuildTimeout; if it is set too low
* and LearnCircuitBuildTimeout is off, the failure rate for circuit
* construction may be very high. In that case, if it is set below this
@@ -2683,7 +2824,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
@@ -2741,6 +2882,70 @@ 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 "
+ "revert HiddenServiceNonAnonymousMode to 0.");
+ }
+
+ /* 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
@@ -2767,6 +2972,16 @@ options_validate(or_options_t *old_options, or_options_t *options,
tor_assert(msg);
*msg = NULL;
+ if (parse_ports(options, 1, msg, &n_ports,
+ &world_writable_control_socket) < 0)
+ return -1;
+
+ /* Set UseEntryGuards from the configured value, before we check it below.
+ * We change UseEntryGuards when 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) &&
@@ -2779,10 +2994,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
"for details.", uname);
}
- if (parse_ports(options, 1, msg, &n_ports,
- &world_writable_control_socket) < 0)
- return -1;
-
if (parse_outbound_addresses(options, 1, msg) < 0)
return -1;
@@ -2848,7 +3059,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (!strcasecmp(options->TransProxyType, "default")) {
options->TransProxyType_parsed = TPT_DEFAULT;
} else if (!strcasecmp(options->TransProxyType, "pf-divert")) {
-#if !defined(__OpenBSD__) && !defined( DARWIN )
+#if !defined(OpenBSD) && !defined( DARWIN )
/* Later versions of OS X have pf */
REJECT("pf-divert is a OpenBSD-specific "
"and OS X/Darwin-specific feature.");
@@ -2864,7 +3075,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;
@@ -2875,14 +3086,12 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (strcasecmp(options->TransProxyType, "default") &&
!options->TransPort_set) {
- REJECT("Cannot use TransProxyType without any valid TransPort or "
- "TransListenAddress.");
+ REJECT("Cannot use TransProxyType without any valid TransPort.");
}
}
#else
if (options->TransPort_set)
- REJECT("TransPort and TransListenAddress are disabled "
- "in this build.");
+ REJECT("TransPort is disabled in this build.");
#endif
if (options->TokenBucketRefillInterval <= 0
@@ -2919,15 +3128,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
}
}
- if (options->TLSECGroup && (strcasecmp(options->TLSECGroup, "P256") &&
- strcasecmp(options->TLSECGroup, "P224"))) {
- COMPLAIN("Unrecognized TLSECGroup: Falling back to the default.");
- tor_free(options->TLSECGroup);
- }
- if (!evaluate_ecgroup_for_tls(options->TLSECGroup)) {
- REJECT("Unsupported TLSECGroup.");
- }
-
if (options->ExcludeNodes && options->StrictNodes) {
COMPLAIN("You have asked to exclude certain relays from all positions "
"in your circuits. Expect hidden services and other Tor "
@@ -2960,7 +3160,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
"UseEntryGuards. Disabling.");
options->UseEntryGuards = 0;
}
- if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) {
+ if (!options->DownloadExtraInfo && authdir_mode_v3(options)) {
log_info(LD_CONFIG, "Authoritative directories always try to download "
"extra-info documents. Setting DownloadExtraInfo.");
options->DownloadExtraInfo = 1;
@@ -3114,23 +3314,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
"of the Internet, so they must not set Reachable*Addresses "
"or FascistFirewall or FirewallPorts or ClientUseIPv4 0.");
- /* We check if Reachable*Addresses blocks all addresses in
- * parse_reachable_addresses(). */
-
-#define WARN_PLEASE_USE_IPV6_LOG_MSG \
- "ClientPreferIPv6%sPort 1 is ignored unless tor is using IPv6. " \
- "Please set ClientUseIPv6 1, ClientUseIPv4 0, or configure bridges."
-
- if (!fascist_firewall_use_ipv6(options)
- && options->ClientPreferIPv6ORPort == 1)
- log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "OR");
-
- if (!fascist_firewall_use_ipv6(options)
- && options->ClientPreferIPv6DirPort == 1)
- log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "Dir");
-
-#undef WARN_PLEASE_USE_IPV6_LOG_MSG
-
if (options->UseBridges &&
server_mode(options))
REJECT("Servers must be able to freely connect to the rest "
@@ -3142,37 +3325,16 @@ 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.");
- }
+ /* If we have UseBridges as 1 and UseEntryGuards as 0, we end up bypassing
+ * the use of bridges */
+ if (options->UseBridges && !options->UseEntryGuards)
+ REJECT("Setting UseBridges requires also setting UseEntryGuards.");
options->MaxMemInQueues =
compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
server_mode(options));
options->MaxMemInQueues_low_threshold = (options->MaxMemInQueues / 4) * 3;
- options->AllowInvalid_ = 0;
-
- if (options->AllowInvalidNodes) {
- SMARTLIST_FOREACH_BEGIN(options->AllowInvalidNodes, const char *, cp) {
- if (!strcasecmp(cp, "entry"))
- options->AllowInvalid_ |= ALLOW_INVALID_ENTRY;
- else if (!strcasecmp(cp, "exit"))
- options->AllowInvalid_ |= ALLOW_INVALID_EXIT;
- else if (!strcasecmp(cp, "middle"))
- options->AllowInvalid_ |= ALLOW_INVALID_MIDDLE;
- else if (!strcasecmp(cp, "introduction"))
- options->AllowInvalid_ |= ALLOW_INVALID_INTRODUCTION;
- else if (!strcasecmp(cp, "rendezvous"))
- options->AllowInvalid_ |= ALLOW_INVALID_RENDEZVOUS;
- else {
- tor_asprintf(msg,
- "Unrecognized value '%s' in AllowInvalidNodes", cp);
- return -1;
- }
- } SMARTLIST_FOREACH_END(cp);
- }
-
if (!options->SafeLogging ||
!strcasecmp(options->SafeLogging, "0")) {
options->SafeLogging_ = SAFELOG_SCRUB_NONE;
@@ -3208,6 +3370,14 @@ options_validate(or_options_t *old_options, or_options_t *options,
options->DirPort_set = 0;
}
+ if (server_mode(options) && options->ConnectionPadding != -1) {
+ REJECT("Relays must use 'auto' for the ConnectionPadding setting.");
+ }
+
+ if (server_mode(options) && options->ReducedConnectionPadding != 0) {
+ REJECT("Relays cannot set ReducedConnectionPadding. ");
+ }
+
if (options->MinUptimeHidServDirectoryV2 < 0) {
log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at "
"least 0 seconds. Changing to 0.");
@@ -3229,32 +3399,18 @@ options_validate(or_options_t *old_options, or_options_t *options,
options->RendPostPeriod = MAX_DIR_PERIOD;
}
- if (options->PredictedPortsRelevanceTime >
- MAX_PREDICTED_CIRCS_RELEVANCE) {
- log_warn(LD_CONFIG, "PredictedPortsRelevanceTime is too large; "
- "clipping to %ds.", MAX_PREDICTED_CIRCS_RELEVANCE);
- options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE;
- }
+ /* Check the Single Onion Service options */
+ if (options_validate_single_onion(options, msg) < 0)
+ return -1;
-#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;
+ if (options->CircuitsAvailableTimeout > MAX_CIRCS_AVAILABLE_TIME) {
+ // options_t is immutable for new code (the above code is older),
+ // so just make the user fix the value themselves rather than
+ // silently keep a shadow value lower than what they asked for.
+ REJECT("CircuitsAvailableTimeout is too large. Max is 24 hours.");
}
+#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
@@ -3274,8 +3430,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 "
@@ -3298,6 +3459,31 @@ options_validate(or_options_t *old_options, or_options_t *options,
return -1;
}
+ /* Inform the hidden service operator that pinning EntryNodes can possibly
+ * be harmful for the service anonymity. */
+ if (options->EntryNodes &&
+ routerset_is_list(options->EntryNodes) &&
+ (options->RendConfigLines != NULL)) {
+ log_warn(LD_CONFIG,
+ "EntryNodes is set with multiple entries and at least one "
+ "hidden service is configured. Pinning entry nodes can possibly "
+ "be harmful to the service anonymity. Because of this, we "
+ "recommend you either don't do that or make sure you know what "
+ "you are doing. For more details, please look at "
+ "https://trac.torproject.org/projects/tor/ticket/21155.");
+ }
+
+ /* 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,
@@ -3309,8 +3495,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_(options, 1)) {
+ 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) {
@@ -3500,10 +3693,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);
}
}
@@ -3635,13 +3828,14 @@ options_validate(or_options_t *old_options, or_options_t *options,
"have it group-readable.");
}
- if (options->MyFamily && options->BridgeRelay) {
+ if (options->MyFamily_lines && options->BridgeRelay) {
log_warn(LD_CONFIG, "Listing a family for a bridge relay is not "
"supported: it can reveal bridge fingerprints to censors. "
"You should also make sure you aren't listing this bridge's "
"fingerprint in any other MyFamily.");
}
- if (check_nickname_list(&options->MyFamily, "MyFamily", msg))
+ if (normalize_nickname_list(&options->MyFamily,
+ options->MyFamily_lines, "MyFamily", msg))
return -1;
for (cl = options->NodeFamilies; cl; cl = cl->next) {
routerset_t *rs = routerset_new();
@@ -3814,7 +4008,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours.");
}
- if (rend_config_services(options, 1) < 0)
+ if (hs_config_service_all(options, 1) < 0)
REJECT("Failed to configure rendezvous options. See logs for details.");
/* Parse client-side authorization for hidden services. */
@@ -3838,13 +4032,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
"AlternateDirAuthority and AlternateBridgeAuthority configured.");
}
- if (options->AllowSingleHopExits && !options->DirAuthorities) {
- COMPLAIN("You have set AllowSingleHopExits; now your relay will allow "
- "others to make one-hop exits. However, since by default most "
- "clients avoid relays that set this option, most clients will "
- "ignore you.");
- }
-
#define CHECK_DEFAULT(arg) \
STMT_BEGIN \
if (!options->TestingTorNetwork && \
@@ -4110,8 +4297,8 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess)
}
/* If we have less than 300 MB suggest disabling dircache */
-#define DIRCACHE_MIN_MB_BANDWIDTH 300
-#define DIRCACHE_MIN_BANDWIDTH (DIRCACHE_MIN_MB_BANDWIDTH*ONE_MEGABYTE)
+#define DIRCACHE_MIN_MEM_MB 300
+#define DIRCACHE_MIN_MEM_BYTES (DIRCACHE_MIN_MEM_MB*ONE_MEGABYTE)
#define STRINGIFY(val) #val
/** Create a warning message for emitting if we are a dircache but may not have
@@ -4131,22 +4318,22 @@ have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
}
}
if (options->DirCache) {
- if (total_mem < DIRCACHE_MIN_BANDWIDTH) {
+ if (total_mem < DIRCACHE_MIN_MEM_BYTES) {
if (options->BridgeRelay) {
- *msg = strdup("Running a Bridge with less than "
- STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
- "not recommended.");
+ *msg = tor_strdup("Running a Bridge with less than "
+ STRINGIFY(DIRCACHE_MIN_MEM_MB) " MB of memory is not "
+ "recommended.");
} else {
- *msg = 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 "
+ *msg = tor_strdup("Being a directory cache (default) with less than "
+ STRINGIFY(DIRCACHE_MIN_MEM_MB) " MB of memory is not "
+ "recommended and may consume most of the available "
"resources, consider disabling this functionality by "
"setting the DirCache option to 0.");
}
}
} else {
- if (total_mem >= DIRCACHE_MIN_BANDWIDTH) {
- *msg = strdup("DirCache is disabled and we are configured as a "
+ if (total_mem >= DIRCACHE_MIN_MEM_BYTES) {
+ *msg = tor_strdup("DirCache is disabled and we are configured as a "
"relay. This may disqualify us from becoming a guard in the "
"future.");
}
@@ -4240,9 +4427,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;
}
@@ -4263,7 +4457,6 @@ options_transition_allowed(const or_options_t *old,
} while (0)
SB_NOCHANGE_STR(Address);
- SB_NOCHANGE_STR(PidFile);
SB_NOCHANGE_STR(ServerDNSResolvConfFile);
SB_NOCHANGE_STR(DirPortFrontPage);
SB_NOCHANGE_STR(CookieAuthFile);
@@ -4328,6 +4521,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) ||
@@ -4341,11 +4536,13 @@ options_transition_affects_descriptor(const or_options_t *old_options,
get_effective_bwburst(old_options) !=
get_effective_bwburst(new_options) ||
!opt_streq(old_options->ContactInfo, new_options->ContactInfo) ||
- !opt_streq(old_options->MyFamily, new_options->MyFamily) ||
+ !config_lines_eq(old_options->MyFamily, new_options->MyFamily) ||
!opt_streq(old_options->AccountingStart, new_options->AccountingStart) ||
old_options->AccountingMax != new_options->AccountingMax ||
+ old_options->AccountingRule != new_options->AccountingRule ||
public_server_mode(old_options) != public_server_mode(new_options) ||
- old_options->DirCache != new_options->DirCache)
+ old_options->DirCache != new_options->DirCache ||
+ old_options->AssumeReachable != new_options->AssumeReachable)
return 1;
return 0;
@@ -4435,27 +4632,36 @@ get_default_conf_file(int defaults_file)
#endif
}
-/** Verify whether lst is a string containing valid-looking comma-separated
- * nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' to any nickname
- * or fingerprint that needs it. Return 0 on success.
+/** Verify whether lst is a list of strings containing valid-looking
+ * comma-separated nicknames, or NULL. Will normalise <b>lst</b> to prefix '$'
+ * to any nickname or fingerprint that needs it. Also splits comma-separated
+ * list elements into multiple elements. Return 0 on success.
* Warn and return -1 on failure.
*/
static int
-check_nickname_list(char **lst, const char *name, char **msg)
+normalize_nickname_list(config_line_t **normalized_out,
+ const config_line_t *lst, const char *name,
+ char **msg)
{
- int r = 0;
- smartlist_t *sl;
- int changes = 0;
-
- if (!*lst)
+ if (!lst)
return 0;
- sl = smartlist_new();
- smartlist_split_string(sl, *lst, ",",
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
+ config_line_t *new_nicknames = NULL;
+ config_line_t **new_nicknames_next = &new_nicknames;
- SMARTLIST_FOREACH_BEGIN(sl, char *, s)
+ const config_line_t *cl;
+ for (cl = lst; cl; cl = cl->next) {
+ const char *line = cl->value;
+ if (!line)
+ continue;
+
+ int valid_line = 1;
+ smartlist_t *sl = smartlist_new();
+ smartlist_split_string(sl, line, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
+ SMARTLIST_FOREACH_BEGIN(sl, char *, s)
{
+ char *normalized = NULL;
if (!is_legal_nickname_or_hexdigest(s)) {
// check if first char is dollar
if (s[0] != '$') {
@@ -4464,36 +4670,45 @@ check_nickname_list(char **lst, const char *name, char **msg)
tor_asprintf(&prepended, "$%s", s);
if (is_legal_nickname_or_hexdigest(prepended)) {
- // The nickname is valid when it's prepended, swap the current
- // version with a prepended one
- tor_free(s);
- SMARTLIST_REPLACE_CURRENT(sl, s, prepended);
- changes = 1;
- continue;
+ // The nickname is valid when it's prepended, set it as the
+ // normalized version
+ normalized = prepended;
+ } else {
+ // Still not valid, free and fallback to error message
+ tor_free(prepended);
}
-
- // Still not valid, free and fallback to error message
- tor_free(prepended);
}
- tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
- r = -1;
- break;
+ if (!normalized) {
+ tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
+ valid_line = 0;
+ break;
+ }
+ } else {
+ normalized = tor_strdup(s);
}
- }
- SMARTLIST_FOREACH_END(s);
- // Replace the caller's nickname list with a fixed one
- if (changes && r == 0) {
- char *newNicknames = smartlist_join_strings(sl, ", ", 0, NULL);
- tor_free(*lst);
- *lst = newNicknames;
+ config_line_t *next = tor_malloc_zero(sizeof(*next));
+ next->key = tor_strdup(cl->key);
+ next->value = normalized;
+ next->next = NULL;
+
+ *new_nicknames_next = next;
+ new_nicknames_next = &next->next;
+ } SMARTLIST_FOREACH_END(s);
+
+ SMARTLIST_FOREACH(sl, char *, s, tor_free(s));
+ smartlist_free(sl);
+
+ if (!valid_line) {
+ config_free_lines(new_nicknames);
+ return -1;
+ }
}
- SMARTLIST_FOREACH(sl, char *, s, tor_free(s));
- smartlist_free(sl);
+ *normalized_out = new_nicknames;
- return r;
+ return 0;
}
/** Learn config file name from command line arguments, or use the default.
@@ -4671,10 +4886,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());
@@ -4690,9 +4910,21 @@ options_init_from_torrc(int argc, char **argv)
printf("OpenSSL \t\t%-15s\t\t%s\n",
crypto_openssl_get_header_version_str(),
crypto_openssl_get_version_str());
- printf("Zlib \t\t%-15s\t\t%s\n",
- tor_zlib_get_header_version_str(),
- tor_zlib_get_version_str());
+ if (tor_compress_supports_method(ZLIB_METHOD)) {
+ printf("Zlib \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(ZLIB_METHOD),
+ tor_compress_header_version_str(ZLIB_METHOD));
+ }
+ if (tor_compress_supports_method(LZMA_METHOD)) {
+ printf("Liblzma \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(LZMA_METHOD),
+ tor_compress_header_version_str(LZMA_METHOD));
+ }
+ if (tor_compress_supports_method(ZSTD_METHOD)) {
+ printf("Libzstd \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(ZSTD_METHOD),
+ tor_compress_header_version_str(ZSTD_METHOD));
+ }
//TODO: Hex versions?
exit(0);
}
@@ -4701,6 +4933,9 @@ options_init_from_torrc(int argc, char **argv)
for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
if (!strcmp(p_index->key,"--keygen")) {
command = CMD_KEYGEN;
+ } else if (!strcmp(p_index->key, "--key-expiration")) {
+ command = CMD_KEY_EXPIRATION;
+ command_arg = p_index->value;
} else if (!strcmp(p_index->key,"--list-fingerprint")) {
command = CMD_LIST_FINGERPRINT;
} else if (!strcmp(p_index->key, "--hash-password")) {
@@ -4830,8 +5065,9 @@ 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;
+ int cf_has_include = 0;
tor_assert(msg);
oldoptions = global_options; /* get_options unfortunately asserts if
@@ -4843,17 +5079,19 @@ 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;
/* get config lines, assign them */
- retval = config_get_lines(body, &cl, 1);
+ retval = config_get_lines_include(body, &cl, 1,
+ body == cf ? &cf_has_include : NULL);
if (retval < 0) {
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;
@@ -4869,12 +5107,14 @@ 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;
}
+ newoptions->IncludeUsed = cf_has_include;
+
/* If this is a testing network configuration, change defaults
* for a list of dependent config options, re-initialize newoptions
* with the new defaults, and assign all options to it second time. */
@@ -4887,19 +5127,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;
@@ -4908,17 +5153,18 @@ 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;
/* get config lines, assign them */
- retval = config_get_lines(body, &cl, 1);
+ retval = config_get_lines_include(body, &cl, 1,
+ body == cf ? &cf_has_include : NULL);
if (retval < 0) {
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;
@@ -4929,13 +5175,15 @@ 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;
}
}
+ newoptions->IncludeUsed = cf_has_include;
+
/* Validate newoptions */
if (options_validate(oldoptions, newoptions, newdefaultoptions,
0, msg) < 0) {
@@ -4952,14 +5200,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);
@@ -5029,7 +5277,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,
@@ -5039,35 +5287,35 @@ addressmap_register_auto(const char *from, const char *to,
int from_wildcard = 0, to_wildcard = 0;
*msg = "whoops, forgot the error message";
- if (1) {
- if (!strcmp(to, "*") || !strcmp(from, "*")) {
- *msg = "can't remap from or to *";
- return -1;
- }
- /* Detect asterisks in expressions of type: '*.example.com' */
- if (!strncmp(from,"*.",2)) {
- from += 2;
- from_wildcard = 1;
- }
- if (!strncmp(to,"*.",2)) {
- to += 2;
- to_wildcard = 1;
- }
- if (to_wildcard && !from_wildcard) {
- *msg = "can only use wildcard (i.e. '*.') if 'from' address "
- "uses wildcard also";
- return -1;
- }
+ if (!strcmp(to, "*") || !strcmp(from, "*")) {
+ *msg = "can't remap from or to *";
+ return -1;
+ }
+ /* Detect asterisks in expressions of type: '*.example.com' */
+ if (!strncmp(from,"*.",2)) {
+ from += 2;
+ from_wildcard = 1;
+ }
+ if (!strncmp(to,"*.",2)) {
+ to += 2;
+ to_wildcard = 1;
+ }
- if (address_is_invalid_destination(to, 1)) {
- *msg = "destination is invalid";
- return -1;
- }
+ if (to_wildcard && !from_wildcard) {
+ *msg = "can only use wildcard (i.e. '*.') if 'from' address "
+ "uses wildcard also";
+ return -1;
+ }
- addressmap_register(from, tor_strdup(to), expires, addrmap_source,
- from_wildcard, to_wildcard);
+ if (address_is_invalid_destination(to, 1)) {
+ *msg = "destination is invalid";
+ return -1;
}
+
+ addressmap_register(from, tor_strdup(to), expires, addrmap_source,
+ from_wildcard, to_wildcard);
+
return 0;
}
@@ -5081,7 +5329,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
@@ -5134,7 +5382,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
if (smartlist_len(elts) == 0)
- smartlist_add(elts, tor_strdup("stdout"));
+ smartlist_add_strdup(elts, "stdout");
if (smartlist_len(elts) == 1 &&
(!strcasecmp(smartlist_get(elts,0), "stdout") ||
@@ -5142,7 +5390,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");
@@ -5171,19 +5419,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;
@@ -5261,10 +5509,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] ...
@@ -5337,7 +5589,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;
}
@@ -5665,7 +5917,7 @@ get_options_from_transport_options_line(const char *line,const char *transport)
}
/* add it to the options smartlist */
- smartlist_add(options, tor_strdup(option));
+ smartlist_add_strdup(options, option);
log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option));
} SMARTLIST_FOREACH_END(option);
@@ -5780,7 +6032,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;
@@ -5788,7 +6040,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 {
@@ -5837,7 +6090,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;
}
@@ -5905,8 +6159,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");
@@ -5922,10 +6176,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;
}
@@ -5988,6 +6242,9 @@ 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.ipv6_traffic = 1;
+ cfg->entry_cfg.dns_request = 1;
+ cfg->entry_cfg.onion_traffic = 1;
cfg->entry_cfg.cache_ipv4_answers = 1;
cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
return cfg;
@@ -6003,8 +6260,9 @@ port_cfg_free(port_cfg_t *port)
/** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is
* on a publicly routable address. */
static void
-warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname,
- int listener_type)
+warn_nonlocal_client_ports(const smartlist_t *ports,
+ const char *portname,
+ const int listener_type)
{
SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
if (port->type != listener_type)
@@ -6090,66 +6348,82 @@ 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.
*
- * Read entries of the "FooPort" type from the list <b>ports</b>, and
- * entries of the "FooListenAddress" type from the list
- * <b>listenaddrs</b>. Two syntaxes are supported: a legacy syntax
- * where FooPort is at most a single entry containing a port number and
- * where FooListenAddress has any number of address:port combinations;
- * and a new syntax where there are no FooListenAddress entries and
- * where FooPort can have any number of entries of the format
- * "[Address:][Port] IsolationOptions".
+ * Read entries of the "FooPort" type from the list <b>ports</b>. Syntax is
+ * that FooPort can have any number of entries of the format
+ * "[Address:][Port] IsolationOptions".
*
* In log messages, describe the port type as <b>portname</b>.
*
@@ -6163,9 +6437,6 @@ config_parse_unix_port(const char *addrport, char **path_out)
* ports are not on a local address. If CL_PORT_FORBID_NONLOCAL is set,
* this is a control port with no password set: don't even allow it.
*
- * Unless CL_PORT_ALLOW_EXTRA_LISTENADDR is set in <b>flags</b>, warn
- * if FooListenAddress is set but FooPort is 0.
- *
* If CL_PORT_SERVER_OPTIONS is set in <b>flags</b>, do not allow stream
* isolation options in the FooPort entries; instead allow the
* server-port option set.
@@ -6180,7 +6451,6 @@ config_parse_unix_port(const char *addrport, char **path_out)
STATIC int
parse_port_config(smartlist_t *out,
const config_line_t *ports,
- const config_line_t *listenaddrs,
const char *portname,
int listener_type,
const char *defaultaddr,
@@ -6197,91 +6467,12 @@ parse_port_config(smartlist_t *out,
const unsigned forbid_nonlocal = flags & CL_PORT_FORBID_NONLOCAL;
const unsigned default_to_group_writable =
flags & CL_PORT_DFLT_GROUP_WRITABLE;
- const unsigned allow_spurious_listenaddr =
- flags & CL_PORT_ALLOW_EXTRA_LISTENADDR;
const unsigned takes_hostnames = flags & CL_PORT_TAKES_HOSTNAMES;
const unsigned is_unix_socket = flags & CL_PORT_IS_UNIXSOCKET;
int got_zero_port=0, got_nonzero_port=0;
char *unix_socket_path = NULL;
- /* FooListenAddress is deprecated; let's make it work like it used to work,
- * though. */
- if (listenaddrs) {
- int mainport = defaultport;
-
- if (ports && ports->next) {
- log_warn(LD_CONFIG, "%sListenAddress can't be used when there are "
- "multiple %sPort lines", portname, portname);
- return -1;
- } else if (ports) {
- if (!strcmp(ports->value, "auto")) {
- mainport = CFG_AUTO_PORT;
- } else {
- int ok;
- mainport = (int)tor_parse_long(ports->value, 10, 0, 65535, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG, "%sListenAddress can only be used with a single "
- "%sPort with value \"auto\" or 1-65535 and no options set.",
- portname, portname);
- return -1;
- }
- }
- }
-
- if (mainport == 0) {
- if (allow_spurious_listenaddr)
- return 1; /*DOCDOC*/
- log_warn(LD_CONFIG, "%sPort must be defined if %sListenAddress is used",
- portname, portname);
- return -1;
- }
-
- if (use_server_options && out) {
- /* Add a no_listen port. */
- port_cfg_t *cfg = port_cfg_new(0);
- cfg->type = listener_type;
- cfg->port = mainport;
- tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */
- cfg->server_cfg.no_listen = 1;
- cfg->server_cfg.bind_ipv4_only = 1;
- cfg->entry_cfg.ipv4_traffic = 1;
- cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
- smartlist_add(out, cfg);
- }
-
- for (; listenaddrs; listenaddrs = listenaddrs->next) {
- tor_addr_t addr;
- uint16_t port = 0;
- if (tor_addr_port_lookup(listenaddrs->value, &addr, &port) < 0) {
- log_warn(LD_CONFIG, "Unable to parse %sListenAddress '%s'",
- portname, listenaddrs->value);
- return -1;
- }
- if (out) {
- port_cfg_t *cfg = port_cfg_new(0);
- cfg->type = listener_type;
- cfg->port = port ? port : mainport;
- tor_addr_copy(&cfg->addr, &addr);
- cfg->entry_cfg.session_group = SESSION_GROUP_UNSET;
- cfg->entry_cfg.isolation_flags = ISO_DEFAULT;
- cfg->server_cfg.no_advertise = 1;
- smartlist_add(out, cfg);
- }
- }
-
- if (warn_nonlocal && out) {
- if (is_control)
- warn_nonlocal_controller_ports(out, forbid_nonlocal);
- else if (is_ext_orport)
- warn_nonlocal_ext_orports(out, portname);
- else
- warn_nonlocal_client_ports(out, portname, listener_type);
- }
- return 0;
- } /* end if (listenaddrs) */
-
- /* No ListenAddress lines. If there's no FooPort, then maybe make a default
- * one. */
+ /* If there's no FooPort, then maybe make a default one. */
if (! ports) {
if (defaultport && defaultaddr && out) {
port_cfg_t *cfg = port_cfg_new(is_unix_socket ? strlen(defaultaddr) : 0);
@@ -6304,44 +6495,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 = 1, 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 &&
@@ -6353,6 +6554,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;
@@ -6399,9 +6602,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")) {
@@ -6449,8 +6649,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="),
@@ -6504,24 +6702,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")) {
@@ -6566,9 +6788,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;
}
@@ -6586,6 +6823,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);
@@ -6612,6 +6856,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;
@@ -6626,6 +6872,8 @@ parse_port_config(smartlist_t *out,
}
SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
smartlist_clear(elts);
+ tor_free(addrport);
+ tor_free(unix_socket_path);
}
if (warn_nonlocal && out) {
@@ -6649,18 +6897,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;
@@ -6669,9 +6921,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.
*
@@ -6693,36 +6944,36 @@ parse_ports(or_options_t *options, int validate_only,
const unsigned gw_flag = options->SocksSocketsGroupWritable ?
CL_PORT_DFLT_GROUP_WRITABLE : 0;
if (parse_port_config(ports,
- options->SocksPort_lines, options->SocksListenAddress,
+ options->SocksPort_lines,
"Socks", CONN_TYPE_AP_LISTENER,
"127.0.0.1", 9050,
- CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR|
- CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) {
- *msg = tor_strdup("Invalid SocksPort/SocksListenAddress configuration");
+ ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+ | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
+ *msg = tor_strdup("Invalid SocksPort configuration");
goto err;
}
if (parse_port_config(ports,
- options->DNSPort_lines, options->DNSListenAddress,
+ options->DNSPort_lines,
"DNS", CONN_TYPE_AP_DNS_LISTENER,
"127.0.0.1", 0,
CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES) < 0) {
- *msg = tor_strdup("Invalid DNSPort/DNSListenAddress configuration");
+ *msg = tor_strdup("Invalid DNSPort configuration");
goto err;
}
if (parse_port_config(ports,
- options->TransPort_lines, options->TransListenAddress,
+ options->TransPort_lines,
"Trans", CONN_TYPE_AP_TRANS_LISTENER,
"127.0.0.1", 0,
CL_PORT_WARN_NONLOCAL) < 0) {
- *msg = tor_strdup("Invalid TransPort/TransListenAddress configuration");
+ *msg = tor_strdup("Invalid TransPort configuration");
goto err;
}
if (parse_port_config(ports,
- options->NATDPort_lines, options->NATDListenAddress,
+ options->NATDPort_lines,
"NATD", CONN_TYPE_AP_NATD_LISTENER,
"127.0.0.1", 0,
CL_PORT_WARN_NONLOCAL) < 0) {
- *msg = tor_strdup("Invalid NatdPort/NatdListenAddress configuration");
+ *msg = tor_strdup("Invalid NatdPort configuration");
goto err;
}
{
@@ -6738,16 +6989,14 @@ parse_ports(or_options_t *options, int validate_only,
if (parse_port_config(ports,
options->ControlPort_lines,
- options->ControlListenAddress,
"Control", CONN_TYPE_CONTROL_LISTENER,
"127.0.0.1", 0,
control_port_flags) < 0) {
- *msg = tor_strdup("Invalid ControlPort/ControlListenAddress "
- "configuration");
+ *msg = tor_strdup("Invalid ControlPort configuration");
goto err;
}
- if (parse_port_config(ports, options->ControlSocket, NULL,
+ if (parse_port_config(ports, options->ControlSocket,
"ControlSocket",
CONN_TYPE_CONTROL_LISTENER, NULL, 0,
control_port_flags | CL_PORT_IS_UNIXSOCKET) < 0) {
@@ -6757,15 +7006,15 @@ parse_ports(or_options_t *options, int validate_only,
}
if (! options->ClientOnly) {
if (parse_port_config(ports,
- options->ORPort_lines, options->ORListenAddress,
+ options->ORPort_lines,
"OR", CONN_TYPE_OR_LISTENER,
"0.0.0.0", 0,
CL_PORT_SERVER_OPTIONS) < 0) {
- *msg = tor_strdup("Invalid ORPort/ORListenAddress configuration");
+ *msg = tor_strdup("Invalid ORPort configuration");
goto err;
}
if (parse_port_config(ports,
- options->ExtORPort_lines, NULL,
+ options->ExtORPort_lines,
"ExtOR", CONN_TYPE_EXT_OR_LISTENER,
"127.0.0.1", 0,
CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) {
@@ -6773,11 +7022,11 @@ parse_ports(or_options_t *options, int validate_only,
goto err;
}
if (parse_port_config(ports,
- options->DirPort_lines, options->DirListenAddress,
+ options->DirPort_lines,
"Dir", CONN_TYPE_DIR_LISTENER,
"0.0.0.0", 0,
CL_PORT_SERVER_OPTIONS) < 0) {
- *msg = tor_strdup("Invalid DirPort/DirListenAddress configuration");
+ *msg = tor_strdup("Invalid DirPort configuration");
goto err;
}
}
@@ -6797,21 +7046,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,
@@ -6841,6 +7091,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. */
@@ -6866,9 +7134,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)
@@ -7002,20 +7268,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 (!configured_ports)
+ if (address_family == AF_UNSPEC)
return 0;
- SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+
+ 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;
}
}
@@ -7023,6 +7289,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
@@ -7208,10 +7555,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;
@@ -7234,10 +7578,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;
@@ -7403,7 +7747,7 @@ getinfo_helper_config(control_connection_t *conn,
case CONFIG_TYPE_CSV: type = "CommaList"; break;
case CONFIG_TYPE_CSV_INTERVAL: type = "TimeIntervalCommaList"; break;
case CONFIG_TYPE_LINELIST: type = "LineList"; break;
- case CONFIG_TYPE_LINELIST_S: type = "Dependant"; break;
+ case CONFIG_TYPE_LINELIST_S: type = "Dependent"; break;
case CONFIG_TYPE_LINELIST_V: type = "Virtual"; break;
default:
case CONFIG_TYPE_OBSOLETE:
@@ -7418,8 +7762,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) {
@@ -7447,14 +7791,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);
@@ -7486,60 +7829,83 @@ getinfo_helper_config(control_connection_t *conn,
return 0;
}
-/** Parse outbound bind address option lines. If <b>validate_only</b>
- * is not 0 update OutboundBindAddressIPv4_ and
- * OutboundBindAddressIPv6_ in <b>options</b>. On failure, set
- * <b>msg</b> (if provided) to a newly allocated string containing a
- * description of the problem and return -1. */
+/* Check whether an address has already been set against the options
+ * depending on address family and destination type. Any exsting
+ * value will lead to a fail, even if it is the same value. If not
+ * set and not only validating, copy it into this location too.
+ * Returns 0 on success or -1 if this address is already set.
+ */
static int
-parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
+verify_and_store_outbound_address(sa_family_t family, tor_addr_t *addr,
+ outbound_addr_t type, or_options_t *options, int validate_only)
{
- const config_line_t *lines = options->OutboundBindAddress;
- int found_v4 = 0, found_v6 = 0;
-
+ if (type>=OUTBOUND_ADDR_MAX || (family!=AF_INET && family!=AF_INET6)) {
+ return -1;
+ }
+ int fam_index=0;
+ if (family==AF_INET6) {
+ fam_index=1;
+ }
+ tor_addr_t *dest=&options->OutboundBindAddresses[type][fam_index];
+ if (!tor_addr_is_null(dest)) {
+ return -1;
+ }
if (!validate_only) {
- memset(&options->OutboundBindAddressIPv4_, 0,
- sizeof(options->OutboundBindAddressIPv4_));
- memset(&options->OutboundBindAddressIPv6_, 0,
- sizeof(options->OutboundBindAddressIPv6_));
+ tor_addr_copy(dest, addr);
}
+ return 0;
+}
+
+/* Parse a list of address lines for a specific destination type.
+ * Will store them into the options if not validate_only. If a
+ * problem occurs, a suitable error message is store in msg.
+ * Returns 0 on success or -1 if any address is already set.
+ */
+static int
+parse_outbound_address_lines(const config_line_t *lines, outbound_addr_t type,
+ or_options_t *options, int validate_only, char **msg)
+{
+ tor_addr_t addr;
+ sa_family_t family;
while (lines) {
- tor_addr_t addr, *dst_addr = NULL;
- int af = tor_addr_parse(&addr, lines->value);
- switch (af) {
- case AF_INET:
- if (found_v4) {
- if (msg)
- tor_asprintf(msg, "Multiple IPv4 outbound bind addresses "
- "configured: %s", lines->value);
- return -1;
- }
- found_v4 = 1;
- dst_addr = &options->OutboundBindAddressIPv4_;
- break;
- case AF_INET6:
- if (found_v6) {
- if (msg)
- tor_asprintf(msg, "Multiple IPv6 outbound bind addresses "
- "configured: %s", lines->value);
- return -1;
- }
- found_v6 = 1;
- dst_addr = &options->OutboundBindAddressIPv6_;
- break;
- default:
+ family = tor_addr_parse(&addr, lines->value);
+ if (verify_and_store_outbound_address(family, &addr, type,
+ options, validate_only)) {
if (msg)
- tor_asprintf(msg, "Outbound bind address '%s' didn't parse.",
- lines->value);
+ tor_asprintf(msg, "Multiple%s%s outbound bind addresses "
+ "configured: %s",
+ family==AF_INET?" IPv4":(family==AF_INET6?" IPv6":""),
+ type==OUTBOUND_ADDR_OR?" OR":
+ (type==OUTBOUND_ADDR_EXIT?" exit":""), lines->value);
return -1;
}
- if (!validate_only)
- tor_addr_copy(dst_addr, &addr);
lines = lines->next;
}
return 0;
}
+/** Parse outbound bind address option lines. If <b>validate_only</b>
+ * is not 0 update OutboundBindAddresses in <b>options</b>.
+ * Only one address can be set for any of these values.
+ * On failure, set <b>msg</b> (if provided) to a newly allocated string
+ * containing a description of the problem and return -1.
+ */
+static int
+parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
+{
+ if (!validate_only) {
+ memset(&options->OutboundBindAddresses, 0,
+ sizeof(options->OutboundBindAddresses));
+ }
+ parse_outbound_address_lines(options->OutboundBindAddress,
+ OUTBOUND_ADDR_EXIT_AND_OR, options, validate_only, msg);
+ parse_outbound_address_lines(options->OutboundBindAddressOR,
+ OUTBOUND_ADDR_OR, options, validate_only, msg);
+ parse_outbound_address_lines(options->OutboundBindAddressExit,
+ OUTBOUND_ADDR_EXIT, options, validate_only, msg);
+ return 0;
+}
+
/** Load one of the geoip files, <a>family</a> determining which
* one. <a>default_fname</a> is used if on Windows and
* <a>fname</a> equals "<default>". */
@@ -7571,7 +7937,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..3cfa7c4e5b 100644
--- a/src/or/config.h
+++ b/src/or/config.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -18,6 +18,10 @@
#define KERNEL_MAY_SUPPORT_IPFW
#endif
+/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
+ * expose more information than we're comfortable with. */
+#define MIN_HEARTBEAT_PERIOD (30*60)
+
MOCK_DECL(const char*, get_dirportfrontpage, (void));
MOCK_DECL(const or_options_t *, get_options, (void));
MOCK_DECL(or_options_t *, get_options_mutable, (void));
@@ -29,8 +33,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 +57,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 +80,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 +95,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 +129,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 {
@@ -143,7 +161,7 @@ smartlist_t *get_options_for_server_transport(const char *transport);
#define CL_PORT_NO_STREAM_OPTIONS (1u<<0)
#define CL_PORT_WARN_NONLOCAL (1u<<1)
-#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2)
+/* Was CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2) */
#define CL_PORT_SERVER_OPTIONS (1u<<3)
#define CL_PORT_FORBID_NONLOCAL (1u<<4)
#define CL_PORT_TAKES_HOSTNAMES (1u<<5)
@@ -158,6 +176,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,
@@ -177,7 +197,6 @@ STATIC int have_enough_mem_for_dircache(const or_options_t *options,
size_t total_mem, char **msg);
STATIC int parse_port_config(smartlist_t *out,
const config_line_t *ports,
- const config_line_t *listenaddrs,
const char *portname,
int listener_type,
const char *defaultaddr,
diff --git a/src/or/confparse.c b/src/or/confparse.c
index 4f446d07c3..abae7e33dc 100644
--- a/src/or/confparse.c
+++ b/src/or/confparse.c
@@ -1,7 +1,8 @@
+
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -9,6 +10,16 @@
*
* \brief Back-end for parsing and generating key-value files, used to
* implement the torrc file format and the state file.
+ *
+ * This module is used by config.c to parse and encode torrc
+ * configuration files, and by statefile.c to parse and encode the
+ * $DATADIR/state file.
+ *
+ * To use this module, its callers provide an instance of
+ * config_format_t to describe the mappings from a set of configuration
+ * options to a number of fields in a C structure. With this mapping,
+ * the functions here can convert back and forth between the C structure
+ * specified, and a linked list of key-value pairs.
*/
#include "or.h"
@@ -67,118 +78,24 @@ config_expand_abbrev(const config_format_t *fmt, const char *option,
return option;
}
-/** Helper: allocate a new configuration option mapping 'key' to 'val',
- * append it to *<b>lst</b>. */
-void
-config_line_append(config_line_t **lst,
- const char *key,
- const char *val)
-{
- config_line_t *newline;
-
- newline = tor_malloc_zero(sizeof(config_line_t));
- newline->key = tor_strdup(key);
- newline->value = tor_strdup(val);
- newline->next = NULL;
- while (*lst)
- lst = &((*lst)->next);
-
- (*lst) = newline;
-}
-
-/** Return the line in <b>lines</b> whose key is exactly <b>key</b>, or NULL
- * if no such key exists. For handling commandline-only options only; other
- * options should be looked up in the appropriate data structure. */
-const config_line_t *
-config_line_find(const config_line_t *lines,
- const char *key)
+/** 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)
{
- const config_line_t *cl;
- for (cl = lines; cl; cl = cl->next) {
- if (!strcmp(cl->key, key))
- return cl;
- }
- return NULL;
-}
+ if (BUG(fmt == NULL) || BUG(key == NULL))
+ return NULL;
+ if (fmt->deprecations == NULL)
+ return NULL;
-/** Helper: parse the config string and strdup into key/value
- * strings. Set *result to the list, or NULL if parsing the string
- * failed. Return 0 on success, -1 on failure. Warn and ignore any
- * misformatted lines.
- *
- * If <b>extended</b> is set, then treat keys beginning with / and with + as
- * indicating "clear" and "append" respectively. */
-int
-config_get_lines(const char *string, config_line_t **result, int extended)
-{
- config_line_t *list = NULL, **next;
- char *k, *v;
- const char *parse_err;
-
- next = &list;
- do {
- k = v = NULL;
- string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
- if (!string) {
- log_warn(LD_CONFIG, "Error while parsing configuration: %s",
- parse_err?parse_err:"<unknown>");
- config_free_lines(list);
- tor_free(k);
- tor_free(v);
- return -1;
- }
- if (k && v) {
- unsigned command = CONFIG_LINE_NORMAL;
- if (extended) {
- if (k[0] == '+') {
- char *k_new = tor_strdup(k+1);
- tor_free(k);
- k = k_new;
- command = CONFIG_LINE_APPEND;
- } else if (k[0] == '/') {
- char *k_new = tor_strdup(k+1);
- tor_free(k);
- k = k_new;
- tor_free(v);
- v = tor_strdup("");
- command = CONFIG_LINE_CLEAR;
- }
- }
- /* This list can get long, so we keep a pointer to the end of it
- * rather than using config_line_append over and over and getting
- * n^2 performance. */
- *next = tor_malloc_zero(sizeof(config_line_t));
- (*next)->key = k;
- (*next)->value = v;
- (*next)->next = NULL;
- (*next)->command = command;
- next = &((*next)->next);
- } else {
- tor_free(k);
- tor_free(v);
+ const config_deprecation_t *d;
+ for (d = fmt->deprecations; d->name; ++d) {
+ if (!strcasecmp(d->name, key)) {
+ return d->why_deprecated ? d->why_deprecated : "";
}
- } while (*string);
-
- *result = list;
- return 0;
-}
-
-/**
- * Free all the configuration lines on the linked list <b>front</b>.
- */
-void
-config_free_lines(config_line_t *front)
-{
- config_line_t *tmp;
-
- while (front) {
- tmp = front;
- front = tmp->next;
-
- tor_free(tmp->key);
- tor_free(tmp->value);
- tor_free(tmp);
}
+ return NULL;
}
/** As config_find_option, but return a non-const pointer. */
@@ -463,6 +380,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 +401,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 +432,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) {
@@ -581,30 +517,13 @@ config_value_needs_escape(const char *value)
return 0;
}
-/** Return a newly allocated deep copy of the lines in <b>inp</b>. */
-config_line_t *
-config_lines_dup(const config_line_t *inp)
-{
- config_line_t *result = NULL;
- config_line_t **next_out = &result;
- while (inp) {
- *next_out = tor_malloc_zero(sizeof(config_line_t));
- (*next_out)->key = tor_strdup(inp->key);
- (*next_out)->value = tor_strdup(inp->value);
- inp = inp->next;
- next_out = &((*next_out)->next);
- }
- (*next_out) = NULL;
- return result;
-}
-
/** Return newly allocated line or lines corresponding to <b>key</b> in the
* configuration <b>options</b>. If <b>escape_val</b> is true and a
* value needs to be quoted before it's put in a config file, quote and
* 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;
@@ -714,11 +633,11 @@ config_get_assigned_option(const config_format_t *fmt, const void *options,
tor_free(result);
return NULL;
case CONFIG_TYPE_LINELIST_S:
- log_warn(LD_CONFIG,
- "Can't return context-sensitive '%s' on its own", key);
tor_free(result->key);
tor_free(result);
- return NULL;
+ result = config_lines_dup_and_filter(*(const config_line_t **)value,
+ key);
+ break;
case CONFIG_TYPE_LINELIST:
case CONFIG_TYPE_LINELIST_V:
tor_free(result->key);
@@ -804,11 +723,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 +753,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;
}
@@ -961,36 +882,6 @@ config_free(const config_format_t *fmt, void *options)
tor_free(options);
}
-/** Return true iff a and b contain identical keys and values in identical
- * order. */
-int
-config_lines_eq(config_line_t *a, config_line_t *b)
-{
- while (a && b) {
- if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
- return 0;
- a = a->next;
- b = b->next;
- }
- if (a || b)
- return 0;
- return 1;
-}
-
-/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
-int
-config_count_key(const config_line_t *a, const char *key)
-{
- int n = 0;
- while (a) {
- if (!strcasecmp(a->key, key)) {
- ++n;
- }
- a = a->next;
- }
- return n;
-}
-
/** Return true iff the option <b>name</b> has the same value in <b>o1</b>
* and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options.
*/
@@ -1029,7 +920,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);
@@ -1107,6 +998,11 @@ config_dump(const config_format_t *fmt, const void *default_options,
config_get_assigned_option(fmt, options, fmt->vars[i].name, 1);
for (; line; line = line->next) {
+ if (!strcmpstart(line->key, "__")) {
+ /* This check detects "hidden" variables inside LINELIST_V structures.
+ */
+ continue;
+ }
smartlist_add_asprintf(elements, "%s%s %s\n",
comment_option ? "# " : "",
line->key, line->value);
@@ -1172,6 +1068,8 @@ static struct unit_table_t memory_units[] = {
{ "gbits", 1<<27 },
{ "gbit", 1<<27 },
{ "tb", U64_LITERAL(1)<<40 },
+ { "tbyte", U64_LITERAL(1)<<40 },
+ { "tbytes", U64_LITERAL(1)<<40 },
{ "terabyte", U64_LITERAL(1)<<40 },
{ "terabytes", U64_LITERAL(1)<<40 },
{ "terabits", U64_LITERAL(1)<<37 },
@@ -1238,7 +1136,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 +1153,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;
@@ -1292,8 +1190,6 @@ config_parse_msec_interval(const char *s, int *ok)
{
uint64_t r;
r = config_parse_units(s, time_msec_units, ok);
- if (!ok)
- return -1;
if (r > INT_MAX) {
log_warn(LD_CONFIG, "Msec interval '%s' is too long", s);
*ok = 0;
@@ -1311,8 +1207,6 @@ config_parse_interval(const char *s, int *ok)
{
uint64_t r;
r = config_parse_units(s, time_units, ok);
- if (!ok)
- return -1;
if (r > INT_MAX) {
log_warn(LD_CONFIG, "Interval '%s' is too long", s);
*ok = 0;
diff --git a/src/or/confparse.h b/src/or/confparse.h
index 885c615202..9c4205d07c 100644
--- a/src/or/confparse.h
+++ b/src/or/confparse.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_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,15 +98,12 @@ 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);
-config_line_t *config_lines_dup(const config_line_t *inp);
-const config_line_t *config_line_find(const config_line_t *lines,
- const char *key);
void config_free(const config_format_t *fmt, void *options);
-int config_lines_eq(config_line_t *a, config_line_t *b);
-int config_count_key(const config_line_t *a, const char *key);
config_line_t *config_get_assigned_option(const config_format_t *fmt,
const void *options, const char *key,
int escape_val);
@@ -121,17 +117,17 @@ 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);
-
-int config_get_lines(const char *string, config_line_t **result, int extended);
-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 1bd1a92e39..5c65e886c0 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -1,17 +1,62 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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_stop_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
#include "or.h"
+#include "bridges.h"
#include "buffers.h"
/*
* Define this so we get channel internal functions, since we're implementing
@@ -38,6 +83,8 @@
#include "ext_orport.h"
#include "geoip.h"
#include "main.h"
+#include "hs_common.h"
+#include "hs_ident.h"
#include "nodelist.h"
#include "policies.h"
#include "reasons.h"
@@ -52,10 +99,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 +118,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);
@@ -94,11 +135,13 @@ static int connection_read_https_proxy_response(connection_t *conn);
static void connection_send_socks5_connect(connection_t *conn);
static const char *proxy_type_to_string(int proxy_type);
static int get_proxy_type(void);
+const tor_addr_t *conn_get_outbound_address(sa_family_t family,
+ const or_options_t *options, unsigned int conn_type);
/** 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 +279,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 +450,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,14 +598,15 @@ 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)) {
rend_data_free(TO_EDGE_CONN(conn)->rend_data);
+ hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident);
}
if (conn->type == CONN_TYPE_CONTROL) {
control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -603,28 +625,25 @@ 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);
tor_free(dir_conn->requested_resource);
- tor_zlib_free(dir_conn->zlib_state);
- if (dir_conn->fingerprint_stack) {
- SMARTLIST_FOREACH(dir_conn->fingerprint_stack, char *, cp, tor_free(cp));
- smartlist_free(dir_conn->fingerprint_stack);
+ tor_compress_free(dir_conn->compress_state);
+ if (dir_conn->spool) {
+ SMARTLIST_FOREACH(dir_conn->spool, spooled_resource_t *, spooled,
+ spooled_resource_free(spooled));
+ smartlist_free(dir_conn->spool);
}
- cached_dir_decref(dir_conn->cached_dir);
rend_data_free(dir_conn->rend_data);
+ hs_ident_dir_conn_free(dir_conn->hs_ident);
+ if (dir_conn->guard_state) {
+ /* Cancel before freeing, if it's still there. */
+ entry_guard_cancel(&dir_conn->guard_state);
+ }
+ circuit_guard_state_free(dir_conn->guard_state);
}
if (SOCKET_OK(conn->s)) {
@@ -636,7 +655,7 @@ connection_free_(connection_t *conn)
if (conn->type == CONN_TYPE_OR &&
!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest)) {
log_warn(LD_BUG, "called on OR conn with non-zeroed identity_digest");
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
if (conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR) {
connection_or_remove_from_ext_or_id_map(TO_OR_CONN(conn));
@@ -645,13 +664,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);
}
@@ -674,7 +686,7 @@ connection_free,(connection_t *conn))
}
if (connection_speaks_cells(conn)) {
if (!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest)) {
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
}
if (conn->type == CONN_TYPE_CONTROL) {
@@ -798,9 +810,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);
@@ -1134,6 +1146,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) {
@@ -1152,6 +1165,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));
@@ -1270,6 +1288,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));
}
@@ -1388,6 +1411,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:
@@ -1396,6 +1425,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;
}
@@ -1486,21 +1518,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. */
@@ -1561,16 +1606,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_addr_to_str_dup(&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) {
@@ -1705,12 +1753,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;
}
@@ -1720,6 +1774,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 configured 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",
@@ -1834,6 +1895,55 @@ connection_connect_log_client_use_ip_version(const connection_t *conn)
}
}
+/** Retrieve the outbound address depending on the protocol (IPv4 or IPv6)
+ * and the connection type (relay, exit, ...)
+ * Return a socket address or NULL in case nothing is configured.
+ **/
+const tor_addr_t *
+conn_get_outbound_address(sa_family_t family,
+ const or_options_t *options, unsigned int conn_type)
+{
+ const tor_addr_t *ext_addr = NULL;
+
+ int fam_index;
+ switch (family) {
+ case AF_INET:
+ fam_index = 0;
+ break;
+ case AF_INET6:
+ fam_index = 1;
+ break;
+ default:
+ return NULL;
+ }
+
+ // If an exit connection, use the exit address (if present)
+ if (conn_type == CONN_TYPE_EXIT) {
+ if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT][fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT]
+ [fam_index];
+ } else if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index];
+ }
+ } else { // All non-exit connections
+ if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_OR][fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_OR]
+ [fam_index];
+ } else if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index];
+ }
+ }
+ return ext_addr;
+}
+
/** Take conn, make a nonblocking socket; try to connect to
* addr:port (port arrives in *host order*). If fail, return -1 and if
* applicable put your best guess about errno into *<b>socket_error</b>.
@@ -1855,26 +1965,15 @@ connection_connect(connection_t *conn, const char *address,
struct sockaddr *bind_addr = NULL;
struct sockaddr *dest_addr;
int dest_addr_len, bind_addr_len = 0;
- const or_options_t *options = get_options();
- int protocol_family;
/* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort
*/
connection_connect_log_client_use_ip_version(conn);
- if (tor_addr_family(addr) == AF_INET6)
- protocol_family = PF_INET6;
- else
- protocol_family = PF_INET;
-
if (!tor_addr_is_loopback(addr)) {
const tor_addr_t *ext_addr = NULL;
- if (protocol_family == AF_INET &&
- !tor_addr_is_null(&options->OutboundBindAddressIPv4_))
- ext_addr = &options->OutboundBindAddressIPv4_;
- else if (protocol_family == AF_INET6 &&
- !tor_addr_is_null(&options->OutboundBindAddressIPv6_))
- ext_addr = &options->OutboundBindAddressIPv6_;
+ ext_addr = conn_get_outbound_address(tor_addr_family(addr), get_options(),
+ conn->type);
if (ext_addr) {
memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
@@ -2240,7 +2339,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);
}
@@ -2249,18 +2348,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
@@ -2694,23 +2788,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.
@@ -2822,20 +2908,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
@@ -2859,12 +2931,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 */
@@ -2874,10 +2942,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? */
@@ -2925,29 +2991,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
@@ -2992,7 +3035,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,
@@ -3323,92 +3365,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.
*
@@ -3739,171 +3695,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. */
@@ -3911,43 +3707,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
@@ -4139,7 +3911,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. */
@@ -4251,7 +4023,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.
@@ -4259,10 +4031,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);
}
@@ -4273,10 +4041,6 @@ connection_flush(connection_t *conn)
* its contents compressed or decompressed as they're written. If zlib is
* negative, this is the last data to be compressed, and the connection's zlib
* state should be flushed.
- *
- * If it's a local control connection and a 64k chunk is ready, try to flush
- * it all, so we don't end up with many megabytes of controller info queued at
- * once.
*/
MOCK_IMPL(void,
connection_write_to_buf_impl_,(const char *string, size_t len,
@@ -4291,29 +4055,13 @@ 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);
int done = zlib < 0;
- CONN_LOG_PROTECT(conn, r = write_to_buf_zlib(conn->outbuf,
- dir_conn->zlib_state,
- string, len, done));
+ CONN_LOG_PROTECT(conn, r = write_to_buf_compress(conn->outbuf,
+ dir_conn->compress_state,
+ string, len, done));
} else {
CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf));
}
@@ -4427,41 +4175,15 @@ connection_get_by_type_state_rendquery(int type, int state,
(type == CONN_TYPE_DIR &&
TO_DIR_CONN(conn)->rend_data &&
!rend_cmp_service_ids(rendquery,
- TO_DIR_CONN(conn)->rend_data->onion_address))
+ rend_data_get_address(TO_DIR_CONN(conn)->rend_data)))
||
(CONN_IS_EDGE(conn) &&
TO_EDGE_CONN(conn)->rend_data &&
!rend_cmp_service_ids(rendquery,
- TO_EDGE_CONN(conn)->rend_data->onion_address))
+ rend_data_get_address(TO_EDGE_CONN(conn)->rend_data)))
));
}
-#define CONN_FIRST_AND_FREE_TEMPLATE(sl) \
- STMT_BEGIN \
- if (smartlist_len(sl) > 0) { \
- void *first_item = smartlist_get(sl, 0); \
- smartlist_free(sl); \
- return first_item; \
- } else { \
- smartlist_free(sl); \
- return NULL; \
- } \
- STMT_END
-
-/** Return a directory connection (if any one exists) that is fetching
- * the item described by <b>purpose</b>/<b>resource</b>, otherwise return NULL.
- */
-dir_connection_t *
-connection_dir_get_by_purpose_and_resource(
- int purpose,
- const char *resource)
-{
- smartlist_t *conns = connection_dir_list_by_purpose_and_resource(
- purpose,
- resource);
- CONN_FIRST_AND_FREE_TEMPLATE(conns);
-}
-
/** Return a new smartlist of dir_connection_t * from get_connection_array()
* that satisfy conn_test on connection_t *conn_var, and dirconn_test on
* dir_connection_t *dirconn_var. conn_var must be of CONN_TYPE_DIR and not
@@ -4502,25 +4224,6 @@ connection_dir_list_by_purpose_and_resource(
dirconn->requested_resource));
}
-/** Return a directory connection (if any one exists) that is fetching
- * the item described by <b>purpose</b>/<b>resource</b>/<b>state</b>,
- * otherwise return NULL. */
-dir_connection_t *
-connection_dir_get_by_purpose_resource_and_state(
- int purpose,
- const char *resource,
- int state)
-{
- smartlist_t *conns =
- connection_dir_list_by_purpose_resource_and_state(
- purpose,
- resource,
- state);
- CONN_FIRST_AND_FREE_TEMPLATE(conns);
-}
-
-#undef CONN_FIRST_AND_FREE_TEMPLATE
-
/** Return a list of directory connections that are fetching the item
* described by <b>purpose</b>/<b>resource</b>/<b>state</b>. If there are
* none, return an empty list. This list must be freed using smartlist_free,
@@ -4797,7 +4500,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
@@ -4814,8 +4517,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:
@@ -4891,6 +4593,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)
@@ -4949,15 +4901,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:
@@ -5221,11 +5164,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 45175cd5a2..36e45aef38 100644
--- a/src/or/connection.h
+++ b/src/or/connection.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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) \
@@ -143,17 +141,17 @@ MOCK_DECL(void, connection_write_to_buf_impl_,
/* DOCDOC connection_write_to_buf */
static void connection_write_to_buf(const char *string, size_t len,
connection_t *conn);
-/* DOCDOC connection_write_to_buf_zlib */
-static void connection_write_to_buf_zlib(const char *string, size_t len,
- dir_connection_t *conn, int done);
+/* DOCDOC connection_write_to_buf_compress */
+static void connection_write_to_buf_compress(const char *string, size_t len,
+ dir_connection_t *conn, int done);
static inline void
connection_write_to_buf(const char *string, size_t len, connection_t *conn)
{
connection_write_to_buf_impl_(string, len, conn, 0);
}
static inline void
-connection_write_to_buf_zlib(const char *string, size_t len,
- dir_connection_t *conn, int done)
+connection_write_to_buf_compress(const char *string, size_t len,
+ dir_connection_t *conn, int done)
{
connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1);
}
@@ -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);
@@ -192,13 +182,6 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type,
connection_t *connection_get_by_type_state(int type, int state);
connection_t *connection_get_by_type_state_rendquery(int type, int state,
const char *rendquery);
-dir_connection_t *connection_dir_get_by_purpose_and_resource(
- int purpose,
- const char *resource);
-dir_connection_t *connection_dir_get_by_purpose_resource_and_state(
- int purpose,
- const char *resource,
- int state);
smartlist_t *connection_dir_list_by_purpose_and_resource(
int purpose,
const char *resource);
@@ -264,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);
@@ -296,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 0f523de1ee..12ddc7e829 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -1,12 +1,57 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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_BEGIN_DIR 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,8 +72,11 @@
#include "control.h"
#include "dns.h"
#include "dnsserv.h"
+#include "directory.h"
#include "dirserv.h"
#include "hibernate.h"
+#include "hs_common.h"
+#include "hs_circuit.h"
#include "main.h"
#include "nodelist.h"
#include "policies.h"
@@ -214,6 +262,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:
@@ -282,6 +331,33 @@ relay_send_end_cell_from_edge(streamid_t stream_id, circuit_t *circ,
payload, 1, cpath_layer);
}
+/* If the connection <b>conn</b> is attempting to connect to an external
+ * destination that is an hidden service and the reason is a connection
+ * refused or timeout, log it so the operator can take appropriate actions.
+ * The log statement is a rate limited warning. */
+static void
+warn_if_hs_unreachable(const edge_connection_t *conn, uint8_t reason)
+{
+ tor_assert(conn);
+
+ if (conn->base_.type == CONN_TYPE_EXIT &&
+ connection_edge_is_rendezvous_stream(conn) &&
+ (reason == END_STREAM_REASON_CONNECTREFUSED ||
+ reason == END_STREAM_REASON_TIMEOUT)) {
+#define WARN_FAILED_HS_CONNECTION 300
+ static ratelim_t warn_limit = RATELIM_INIT(WARN_FAILED_HS_CONNECTION);
+ char *m;
+ if ((m = rate_limit_log(&warn_limit, approx_time()))) {
+ log_warn(LD_EDGE, "Onion service connection to %s failed (%s)",
+ (conn->base_.socket_family == AF_UNIX) ?
+ safe_str(conn->base_.address) :
+ safe_str(fmt_addrport(&conn->base_.addr, conn->base_.port)),
+ stream_end_reason_to_string(reason));
+ tor_free(m);
+ }
+ }
+}
+
/** Send a relay end cell from stream <b>conn</b> down conn's circuit, and
* remember that we've done so. If this is not a client connection, set the
* relay end cell's reason for closing as <b>reason</b>.
@@ -339,6 +415,9 @@ connection_edge_end(edge_connection_t *conn, uint8_t reason)
conn->base_.s);
connection_edge_send_command(conn, RELAY_COMMAND_END,
payload, payload_len);
+ /* We'll log warn if the connection was an hidden service and couldn't be
+ * made because the service wasn't available. */
+ warn_if_hs_unreachable(conn, control_reason);
} else {
log_debug(LD_EDGE,"No circ to send end on conn "
"(fd "TOR_SOCKET_T_FORMAT").",
@@ -478,8 +557,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);
@@ -785,7 +863,8 @@ connection_ap_rescan_and_attach_pending(void)
#endif
/** Tell any AP streams that are listed as waiting for a new circuit to try
- * again, either attaching to an available circ or launching a new one.
+ * again. If there is an available circuit for a stream, attach it. Otherwise,
+ * launch a new circuit.
*
* If <b>retry</b> is false, only check the list if it contains at least one
* streams that we have not yet tried to attach to a circuit.
@@ -800,8 +879,9 @@ connection_ap_attach_pending(int retry)
if (untried_pending_connections == 0 && !retry)
return;
- /* Don't allow modifications to pending_entry_connections while we are
- * iterating over it. */
+ /* Don't allow any modifications to list while we are iterating over
+ * it. We'll put streams back on this list if we can't attach them
+ * immediately. */
smartlist_t *pending = pending_entry_connections;
pending_entry_connections = smartlist_new();
@@ -828,6 +908,7 @@ connection_ap_attach_pending(int retry)
continue;
}
+ /* Okay, we're through the sanity checks. Try to handle this stream. */
if (connection_ap_handshake_attach_circuit(entry_conn) < 0) {
if (!conn->marked_for_close)
connection_mark_unattached_ap(entry_conn,
@@ -837,12 +918,17 @@ connection_ap_attach_pending(int retry)
if (! conn->marked_for_close &&
conn->type == CONN_TYPE_AP &&
conn->state == AP_CONN_STATE_CIRCUIT_WAIT) {
+ /* Is it still waiting for a circuit? If so, we didn't attach it,
+ * so it's still pending. Put it back on the list.
+ */
if (!smartlist_contains(pending_entry_connections, entry_conn)) {
smartlist_add(pending_entry_connections, entry_conn);
continue;
}
}
+ /* If we got here, then we either closed the connection, or
+ * we attached it. */
UNMARK();
} SMARTLIST_FOREACH_END(entry_conn);
@@ -892,6 +978,15 @@ connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn,
untried_pending_connections = 1;
smartlist_add(pending_entry_connections, entry_conn);
+
+ /* Work-around for bug 19969: we handle pending_entry_connections at
+ * the end of run_main_loop_once(), but in many cases that function will
+ * take a very long time, if ever, to finish its call to event_base_loop().
+ *
+ * So the fix is to tell it right now that it ought to finish its loop at
+ * its next available opportunity.
+ */
+ tell_event_loop_to_finish();
}
/** Mark <b>entry_conn</b> as no longer waiting for a circuit. */
@@ -919,7 +1014,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
@@ -1026,8 +1121,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) {
@@ -1141,6 +1236,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
/* Remember the original address so we can tell the user about what
* they actually said, not just what it turned into. */
+ /* XXX yes, this is the same as out->orig_address above. One is
+ * in the output, and one is in the connection. */
if (! conn->original_dest_address) {
/* Is the 'if' necessary here? XXXX */
conn->original_dest_address = tor_strdup(conn->socks_request->address);
@@ -1148,7 +1245,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
/* First, apply MapAddress and MAPADDRESS mappings. We need to do
* these only for non-reverse lookups, since they don't exist for those.
- * We need to do this before we consider automapping, since we might
+ * We also need to do this before we consider automapping, since we might
* e.g. resolve irc.oftc.net into irconionaddress.onion, at which point
* we'd need to automap it. */
if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) {
@@ -1160,9 +1257,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
}
- /* Now, handle automapping. Automapping happens when we're asked to
- * resolve a hostname, and AutomapHostsOnResolve is set, and
- * the hostname has a suffix listed in AutomapHostsSuffixes.
+ /* Now see if we need to create or return an existing Hostname->IP
+ * automapping. Automapping happens when we're asked to resolve a
+ * hostname, and AutomapHostsOnResolve is set, and the hostname has a
+ * suffix listed in AutomapHostsSuffixes. It's a handy feature
+ * that lets you have Tor assign e.g. IPv6 addresses for .onion
+ * names, and return them safely from DNSPort.
*/
if (socks->command == SOCKS_COMMAND_RESOLVE &&
tor_addr_parse(&addr_tmp, socks->address)<0 &&
@@ -1202,7 +1302,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
/* Now handle reverse lookups, if they're in the cache. This doesn't
- * happen too often, since client-side DNS caching is off by default. */
+ * happen too often, since client-side DNS caching is off by default,
+ * and very deprecated. */
if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
unsigned rewrite_flags = 0;
if (conn->entry_cfg.use_cached_ipv4_answers)
@@ -1228,10 +1329,10 @@ 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. */
+ /* Don't let clients try to do a reverse lookup on 10.0.0.1. */
tor_addr_t addr;
int ok;
ok = tor_addr_parse_PTR_name(
@@ -1247,11 +1348,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
}
- /* If we didn't automap it before, then this is still the address
- * that came straight from the user, mapped according to any
- * MapAddress/MAPADDRESS commands. Now other mappings, including
- * previously registered Automap entries, TrackHostExits entries,
- * and client-side DNS cache entries (not recommended).
+ /* If we didn't automap it before, then this is still the address that
+ * came straight from the user, mapped according to any
+ * MapAddress/MAPADDRESS commands. Now apply other mappings,
+ * including previously registered Automap entries (IP back to
+ * hostname), TrackHostExits entries, and client-side DNS cache
+ * entries (if they're turned on).
*/
if (socks->command != SOCKS_COMMAND_RESOLVE_PTR &&
!out->automap) {
@@ -1316,11 +1418,14 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
time_t now = time(NULL);
rewrite_result_t rr;
+ /* First we'll do the rewrite part. Let's see if we get a reasonable
+ * answer.
+ */
memset(&rr, 0, sizeof(rr));
connection_ap_handshake_rewrite(conn,&rr);
if (rr.should_close) {
- /* connection_ap_handshake_rewrite told us to close the connection,
+ /* connection_ap_handshake_rewrite told us to close the connection:
* either because it sent back an answer, or because it sent back an
* error */
connection_mark_unattached_ap(conn, rr.end_reason);
@@ -1334,8 +1439,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
const int automap = rr.automap;
const addressmap_entry_source_t exit_source = rr.exit_source;
- /* Parse the address provided by SOCKS. Modify it in-place if it
- * specifies a hidden-service (.onion) or particular exit node (.exit).
+ /* Now, we parse the address to see if it's an .onion or .exit or
+ * other special address.
*/
const hostname_type_t addresstype = parse_extended_hostname(socks->address);
@@ -1349,8 +1454,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
/* If this is a .exit hostname, strip off the .name.exit part, and
- * see whether we're going to connect there, and otherwise handle it.
- * (The ".exit" part got stripped off by "parse_extended_hostname").
+ * see whether we're willing to connect there, and and otherwise handle the
+ * .exit address.
*
* We'll set chosen_exit_name and/or close the connection as appropriate.
*/
@@ -1362,7 +1467,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
const node_t *node = NULL;
/* If this .exit was added by an AUTOMAP, then it came straight from
- * a user. Make sure that options->AllowDotExit permits that. */
+ * a user. Make sure that options->AllowDotExit permits that! */
if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) {
/* Whoops; this one is stale. It must have gotten added earlier,
* when AllowDotExit was on. */
@@ -1391,7 +1496,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
tor_assert(!automap);
- /* Now, find the character before the .(name) part. */
+
+ /* Now, find the character before the .(name) part.
+ * (The ".exit" part got stripped off by "parse_extended_hostname").
+ *
+ * We're going to put the exit name into conn->chosen_exit_name, and
+ * look up a node correspondingly. */
char *s = strrchr(socks->address,'.');
if (s) {
/* The address was of the form "(stuff).(name).exit */
@@ -1442,15 +1552,17 @@ 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. */
}
- /* Now, handle everything that isn't a .onion address. */
+ /* Now, we handle everything that isn't a .onion address. */
if (addresstype != ONION_HOSTNAME) {
/* Not a hidden-service request. It's either a hostname or an IP,
- * possibly with a .exit that we stripped off. */
+ * possibly with a .exit that we stripped off. We're going to check
+ * if we're allowed to connect/resolve there, and then launch the
+ * appropriate request. */
/* Check for funny characters in the address. */
if (address_is_invalid_destination(socks->address, 1)) {
@@ -1467,14 +1579,68 @@ 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. Reject if not. */
+ 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) {
+ if (!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) {
+ if (!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) {
+ if (!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;
+ }
+ } else {
+ tor_assert_nonfatal_unreached_once();
+ }
+
/* 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.)
*/
@@ -1494,7 +1660,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
tor_assert(!automap);
rep_hist_note_used_resolve(now); /* help predict this next time */
} else if (socks->command == SOCKS_COMMAND_CONNECT) {
- /* Special handling for attempts to connect */
+ /* Now see if this is a connect request that we can reject immediately */
+
tor_assert(!automap);
/* Don't allow connections to port 0. */
if (socks->port == 0) {
@@ -1503,7 +1670,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
return -1;
}
/* You can't make connections to internal addresses, by default.
- * Exceptions are begindir requests (where the address is meaningless,
+ * Exceptions are begindir requests (where the address is meaningless),
* or cases where you've hand-configured a particular exit, thereby
* making the local address meaningful. */
if (options->ClientRejectInternalAddresses &&
@@ -1547,7 +1714,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
} /* end "if we should check for internal addresses" */
/* Okay. We're still doing a CONNECT, and it wasn't a private
- * address. Do special handling for literal IP addresses */
+ * address. Here we do special handling for literal IP addresses,
+ * to see if we should reject this preemptively, and to set up
+ * fields in conn->entry_cfg to tell the exit what AF we want. */
{
tor_addr_t addr;
/* XXX Duplicate call to tor_addr_parse. */
@@ -1590,11 +1759,15 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
}
+ /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */
if (socks->socks_version == 4)
conn->entry_cfg.ipv6_traffic = 0;
/* Still handling CONNECT. Now, check for exit enclaves. (Which we
- * don't do on BEGINDIR, or there is a chosen exit.)
+ * don't do on BEGIN_DIR, or when there is a chosen exit.)
+ *
+ * TODO: Should we remove this? Exit enclaves are nutty and don't
+ * work very well
*/
if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
/* see if we can find a suitable enclave exit */
@@ -1618,7 +1791,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
if (consider_plaintext_ports(conn, socks->port) < 0)
return -1;
- /* Remember the port so that we do predicted requests there. */
+ /* Remember the port so that we will predict that more requests
+ there will happen in the future. */
if (!conn->use_begindir) {
/* help predict this next time */
rep_hist_note_used_port(now, socks->port);
@@ -1627,7 +1801,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
rep_hist_note_used_resolve(now); /* help predict this next time */
/* no extra processing needed */
} else {
- /* We should only be doing CONNECT or RESOLVE! */
+ /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */
tor_fragile_assert();
}
@@ -1643,6 +1817,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
if (circ) {
rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
} else {
+ /* We'll try to attach it at the next event loop, or whenever
+ * we call connection_ap_attach_pending() */
connection_ap_mark_as_pending_circuit(conn);
rv = 0;
}
@@ -1662,6 +1838,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)) {
@@ -1691,7 +1875,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 "
@@ -1703,28 +1887,31 @@ 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;
}
+ const char *onion_address = rend_data_get_address(rend_data);
log_info(LD_REND,"Got a hidden service request for ID '%s'",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(onion_address));
- /* Lookup the given onion address. If invalid, stop right now else we
- * might have it in the cache or not, it will be tested later on. */
+ /* Lookup the given onion address. If invalid, stop right now.
+ * Otherwise, we might have it in the cache or not. */
unsigned int refetch_desc = 0;
rend_cache_entry_t *entry = NULL;
const int rend_cache_lookup_result =
- rend_cache_lookup_entry(rend_data->onion_address, -1, &entry);
+ rend_cache_lookup_entry(onion_address, -1, &entry);
if (rend_cache_lookup_result < 0) {
switch (-rend_cache_lookup_result) {
case EINVAL:
/* We should already have rejected this address! */
log_warn(LD_BUG,"Invalid service name '%s'",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(onion_address));
connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
return -1;
case ENOENT:
+ /* We didn't have this; we should look it up. */
refetch_desc = 1;
break;
default:
@@ -1734,8 +1921,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
}
- /* Help predict this next time. We're not sure if it will need
- * a stable circuit yet, but we know we'll need *something*. */
+ /* Help predict that we'll want to do hidden service circuits in the
+ * future. We're not sure if it will need a stable circuit yet, but
+ * we know we'll need *something*. */
rep_hist_note_used_internal(now, 0, 1);
/* Now we have a descriptor but is it usable or not? If not, refetch.
@@ -1745,14 +1933,17 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
connection_ap_mark_as_non_pending_circuit(conn);
base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
log_info(LD_REND, "Unknown descriptor %s. Fetching.",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(onion_address));
rend_client_refetch_v2_renddesc(rend_data);
return 0;
}
- /* We have the descriptor so launch a connection to the HS. */
+ /* We have the descriptor! So launch a connection to the HS. */
base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
log_info(LD_REND, "Descriptor is here. Great.");
+
+ /* We'll try to attach it at the next event loop, or whenever
+ * we call connection_ap_attach_pending() */
connection_ap_mark_as_pending_circuit(conn);
return 0;
}
@@ -1770,7 +1961,7 @@ get_pf_socket(void)
if (pf_socket >= 0)
return pf_socket;
-#ifdef OPENBSD
+#if defined(OpenBSD)
/* only works on OpenBSD */
pf = tor_open_cloexec("/dev/pf", O_RDONLY, 0);
#else
@@ -1798,8 +1989,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);
@@ -2007,14 +2198,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;
@@ -2271,12 +2456,13 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn)
*
* If ap_conn is broken, mark it for close and return -1. Else return 0.
*/
-int
-connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+MOCK_IMPL(int,
+connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
{
char payload[CELL_PAYLOAD_SIZE];
int payload_len;
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);
@@ -2290,7 +2476,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);
@@ -2320,10 +2506,39 @@ 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_request(). */
+ if (purpose_needs_anonymity(linked_dir_conn_base->purpose,
+ TO_DIR_CONN(linked_dir_conn_base)->router_purpose,
+ TO_DIR_CONN(linked_dir_conn_base)->requested_resource)) {
+ 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,
@@ -2346,7 +2561,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);
}
@@ -2382,7 +2597,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);
@@ -2788,12 +3003,12 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply,
return;
}
-/** Read a RELAY_BEGIN or RELAY_BEGINDIR cell from <b>cell</b>, decode it, and
+/** Read a RELAY_BEGIN or RELAY_BEGIN_DIR cell from <b>cell</b>, decode it, and
* place the result in <b>bcell</b>. On success return 0; on failure return
* <0 and set *<b>end_reason_out</b> to the end reason we should send back to
* the client.
*
- * Return -1 in the case where want to send a RELAY_END cell, and < -1 when
+ * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when
* we don't.
**/
STATIC int
@@ -2852,6 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
return 0;
}
+/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the
+ * connection, attach it to the circ and connect it. Return 0 on success
+ * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port
+ * where the caller should close the circuit. */
+static int
+handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
+{
+ int ret;
+ origin_circuit_t *origin_circ;
+
+ assert_circuit_ok(circ);
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ tor_assert(conn);
+
+ log_debug(LD_REND, "Connecting the hidden service rendezvous circuit "
+ "to the service destination.");
+
+ origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ conn->base_.address = tor_strdup("(rendezvous)");
+ conn->base_.state = EXIT_CONN_STATE_CONNECTING;
+
+ /* The circuit either has an hs identifier for v3+ or a rend_data for legacy
+ * service. */
+ if (origin_circ->rend_data) {
+ conn->rend_data = rend_data_dup(origin_circ->rend_data);
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+ ret = rend_service_set_connection_addr_port(conn, origin_circ);
+ } else if (origin_circ->hs_ident) {
+ /* Setup the identifier to be the one for the circuit service. */
+ conn->hs_ident =
+ hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk);
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+ ret = hs_service_set_conn_addr_port(origin_circ, conn);
+ } else {
+ /* We should never get here if the circuit's purpose is rendezvous. */
+ tor_assert_nonfatal_unreached();
+ return -1;
+ }
+ if (ret < 0) {
+ log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)",
+ fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port);
+ /* Send back reason DONE because we want to make hidden service port
+ * scanning harder thus instead of returning that the exit policy
+ * didn't match, which makes it obvious that the port is closed,
+ * return DONE and kill the circuit. That way, a user (malicious or
+ * not) needs one circuit per bad port unless it matches the policy of
+ * the hidden service. */
+ relay_send_end_cell_from_edge(conn->stream_id, circ,
+ END_STREAM_REASON_DONE,
+ origin_circ->cpath->prev);
+ connection_free(TO_CONN(conn));
+
+ /* Drop the circuit here since it might be someone deliberately
+ * scanning the hidden service ports. Note that this mitigates port
+ * scanning by adding more work on the attacker side to successfully
+ * scan but does not fully solve it. */
+ if (ret < -1) {
+ return END_CIRC_AT_ORIGIN;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Link the circuit and the connection crypt path. */
+ conn->cpath_layer = origin_circ->cpath->prev;
+
+ /* Add it into the linked list of p_streams on this circuit */
+ conn->next_stream = origin_circ->p_streams;
+ origin_circ->p_streams = conn;
+ conn->on_circuit = circ;
+ assert_circuit_ok(circ);
+
+ hs_inc_rdv_stream_counter(origin_circ);
+
+ /* Connect tor to the hidden service destination. */
+ connection_exit_connect(conn);
+
+ /* For path bias: This circuit was used successfully */
+ pathbias_mark_use_success(origin_circ);
+ return 0;
+}
+
/** A relay 'begin' or 'begin_dir' cell has arrived, and either we are
* an exit hop for the circuit, or we are the origin and it is a
* rendezvous begin.
@@ -2878,14 +3175,21 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
char *address = NULL;
uint16_t port = 0;
or_circuit_t *or_circ = NULL;
+ origin_circuit_t *origin_circ = NULL;
+ crypt_path_t *layer_hint = 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);
- if (!CIRCUIT_IS_ORIGIN(circ))
+ if (!CIRCUIT_IS_ORIGIN(circ)) {
or_circ = TO_OR_CIRCUIT(circ);
+ } else {
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ layer_hint = origin_circ->cpath->prev;
+ }
relay_header_unpack(&rh, cell->payload);
if (rh.length > RELAY_PAYLOAD_SIZE)
@@ -2905,12 +3209,12 @@ 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);
+ relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, layer_hint);
return 0;
}
@@ -2920,15 +3224,13 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
port = bcell.port;
if (or_circ && or_circ->p_chan) {
- if (!options->AllowSingleHopExits &&
- (or_circ->is_first_hop ||
- (!connection_or_digest_is_known_relay(
+ if ((or_circ->is_first_hop ||
+ (!connection_or_digest_is_known_relay(
or_circ->p_chan->identity_digest) &&
should_refuse_unknown_exits(options)))) {
- /* Don't let clients use us as a single-hop proxy, unless the user
- * has explicitly allowed that in the config. It attracts attackers
- * and users who'd be better off with, well, single-hop proxies.
- */
+ /* Don't let clients use us as a single-hop proxy. It attracts
+ * attackers and users who'd be better off with, well, single-hop
+ * proxies. */
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Attempt by %s to open a stream %s. Closing.",
safe_str(channel_get_canonical_remote_descr(or_circ->p_chan)),
@@ -2947,7 +3249,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
if (!directory_permits_begindir_requests(options) ||
circ->purpose != CIRCUIT_PURPOSE_OR) {
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_NOTDIRECTORY, NULL);
+ END_STREAM_REASON_NOTDIRECTORY, layer_hint);
return 0;
}
/* Make sure to get the 'real' address of the previous hop: the
@@ -2964,7 +3266,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
} else {
log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command);
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_INTERNAL, NULL);
+ END_STREAM_REASON_INTERNAL, layer_hint);
return 0;
}
@@ -2975,7 +3277,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
if (bcell.flags & BEGIN_FLAG_IPV4_NOT_OK) {
tor_free(address);
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_EXITPOLICY, NULL);
+ END_STREAM_REASON_EXITPOLICY, layer_hint);
return 0;
}
}
@@ -2998,58 +3300,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
n_stream->deliver_window = STREAMWINDOW_START;
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
- origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
- log_info(LD_REND,"begin is for rendezvous. configuring stream.");
- n_stream->base_.address = tor_strdup("(rendezvous)");
- n_stream->base_.state = EXIT_CONN_STATE_CONNECTING;
- n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
- tor_assert(connection_edge_is_rendezvous_stream(n_stream));
- assert_circuit_ok(circ);
-
- const int r = rend_service_set_connection_addr_port(n_stream, origin_circ);
- if (r < 0) {
- log_info(LD_REND,"Didn't find rendezvous service (port %d)",
- n_stream->base_.port);
- /* Send back reason DONE because we want to make hidden service port
- * scanning harder thus instead of returning that the exit policy
- * didn't match, which makes it obvious that the port is closed,
- * return DONE and kill the circuit. That way, a user (malicious or
- * not) needs one circuit per bad port unless it matches the policy of
- * the hidden service. */
- relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_DONE,
- origin_circ->cpath->prev);
- connection_free(TO_CONN(n_stream));
- tor_free(address);
-
- /* Drop the circuit here since it might be someone deliberately
- * scanning the hidden service ports. Note that this mitigates port
- * scanning by adding more work on the attacker side to successfully
- * scan but does not fully solve it. */
- if (r < -1)
- return END_CIRC_AT_ORIGIN;
- else
- return 0;
- }
- assert_circuit_ok(circ);
- log_debug(LD_REND,"Finished assigning addr/port");
- n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */
-
- /* add it into the linked list of p_streams on this circuit */
- n_stream->next_stream = origin_circ->p_streams;
- n_stream->on_circuit = circ;
- origin_circ->p_streams = n_stream;
- assert_circuit_ok(circ);
-
- origin_circ->rend_data->nr_streams++;
-
- connection_exit_connect(n_stream);
-
- /* For path bias: This circuit was used successfully */
- pathbias_mark_use_success(origin_circ);
-
tor_free(address);
- return 0;
+ /* We handle this circuit and stream in this function for all supported
+ * hidden service version. */
+ return handle_hs_exit_conn(circ, n_stream);
}
tor_strlower(address);
n_stream->base_.address = address;
@@ -3146,6 +3400,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.
*
@@ -3160,14 +3432,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);
@@ -3224,11 +3500,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. */
@@ -3324,27 +3598,32 @@ connection_exit_connect_dir(edge_connection_t *exitconn)
* it is a general stream.
*/
int
-connection_edge_is_rendezvous_stream(edge_connection_t *conn)
+connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
{
tor_assert(conn);
- if (conn->rend_data)
+ /* It should not be possible to set both of these structs */
+ tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident));
+
+ if (conn->rend_data || conn->hs_ident) {
return 1;
+ }
return 0;
}
-/** 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.
@@ -3353,7 +3632,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);
@@ -3378,7 +3657,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)
@@ -3387,10 +3667,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_edge.h b/src/or/connection_edge.h
index 5dfc8af901..9987f88b85 100644
--- a/src/or/connection_edge.h
+++ b/src/or/connection_edge.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn);
void connection_ap_about_to_close(entry_connection_t *edge_conn);
void connection_exit_about_to_close(edge_connection_t *edge_conn);
-int connection_ap_handshake_send_begin(entry_connection_t *ap_conn);
+MOCK_DECL(int,
+ connection_ap_handshake_send_begin,(entry_connection_t *ap_conn));
int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn);
entry_connection_t *connection_ap_make_link(connection_t *partner,
@@ -60,7 +61,7 @@ void connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn,
int connection_exit_begin_conn(cell_t *cell, circuit_t *circ);
int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ);
void connection_exit_connect(edge_connection_t *conn);
-int connection_edge_is_rendezvous_stream(edge_connection_t *conn);
+int connection_edge_is_rendezvous_stream(const edge_connection_t *conn);
int connection_ap_can_use_exit(const entry_connection_t *conn,
const node_t *exit);
void connection_ap_expire_beginning(void);
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index f8be763792..7c929e5272 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -1,15 +1,27 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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 "bridges.h"
#include "buffers.h"
/*
* Define this so we get channel internal functions, since we're implementing
@@ -31,19 +43,19 @@
#include "geoip.h"
#include "main.h"
#include "link_handshake.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "reasons.h"
#include "relay.h"
#include "rephist.h"
#include "router.h"
+#include "routerkeys.h"
#include "routerlist.h"
#include "ext_orport.h"
#include "scheduler.h"
-
-#ifdef USE_BUFFEREVENTS
-#include <event2/bufferevent_ssl.h>
-#endif
+#include "torcert.h"
+#include "channelpadding.h"
static int connection_tls_finish_handshake(or_connection_t *conn);
static int connection_or_launch_v3_or_handshake(or_connection_t *conn);
@@ -65,62 +77,25 @@ 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
+static void connection_or_check_canonicity(or_connection_t *conn,
+ int started_here);
/**************************************************************/
-/** Map from identity digest of connected OR or desired OR to a connection_t
- * with that identity digest. If there is more than one such connection_t,
- * they form a linked list, with next_with_same_id as the next pointer. */
-static digestmap_t *orconn_identity_map = NULL;
-
/** Global map between Extended ORPort identifiers and OR
* connections. */
static digestmap_t *orconn_ext_or_id_map = NULL;
-/** If conn is listed in orconn_identity_map, remove it, and clear
- * conn->identity_digest. Otherwise do nothing. */
+/** Clear clear conn->identity_digest and update other data
+ * structures as appropriate.*/
void
-connection_or_remove_from_identity_map(or_connection_t *conn)
+connection_or_clear_identity(or_connection_t *conn)
{
- or_connection_t *tmp;
tor_assert(conn);
- if (!orconn_identity_map)
- return;
- tmp = digestmap_get(orconn_identity_map, conn->identity_digest);
- if (!tmp) {
- if (!tor_digest_is_zero(conn->identity_digest)) {
- log_warn(LD_BUG, "Didn't find connection '%s' on identity map when "
- "trying to remove it.",
- conn->nickname ? conn->nickname : "NULL");
- }
- return;
- }
- if (conn == tmp) {
- if (conn->next_with_same_id)
- digestmap_set(orconn_identity_map, conn->identity_digest,
- conn->next_with_same_id);
- else
- digestmap_remove(orconn_identity_map, conn->identity_digest);
- } else {
- while (tmp->next_with_same_id) {
- if (tmp->next_with_same_id == conn) {
- tmp->next_with_same_id = conn->next_with_same_id;
- break;
- }
- tmp = tmp->next_with_same_id;
- }
- }
memset(conn->identity_digest, 0, DIGEST_LEN);
- conn->next_with_same_id = NULL;
}
-/** Remove all entries from the identity-to-orconn map, and clear
- * all identities in OR conns.*/
+/** Clear all identities in OR conns.*/
void
connection_or_clear_identity_map(void)
{
@@ -128,57 +103,72 @@ connection_or_clear_identity_map(void)
SMARTLIST_FOREACH(conns, connection_t *, conn,
{
if (conn->type == CONN_TYPE_OR) {
- or_connection_t *or_conn = TO_OR_CONN(conn);
- memset(or_conn->identity_digest, 0, DIGEST_LEN);
- or_conn->next_with_same_id = NULL;
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
});
-
- digestmap_free(orconn_identity_map, NULL);
- orconn_identity_map = NULL;
}
/** Change conn->identity_digest to digest, and add conn into
- * orconn_digest_map. */
+ * the appropriate digest maps.
+ *
+ * NOTE that this function only allows two kinds of transitions: from
+ * unset identity to set identity, and from idempotent re-settings
+ * of the same identity. It's not allowed to clear an identity or to
+ * change an identity. Return 0 on success, and -1 if the transition
+ * is not allowed.
+ **/
static void
-connection_or_set_identity_digest(or_connection_t *conn, const char *digest)
+connection_or_set_identity_digest(or_connection_t *conn,
+ const char *rsa_digest,
+ const ed25519_public_key_t *ed_id)
{
- or_connection_t *tmp;
+ channel_t *chan = NULL;
tor_assert(conn);
- tor_assert(digest);
+ tor_assert(rsa_digest);
- if (!orconn_identity_map)
- orconn_identity_map = digestmap_new();
- if (tor_memeq(conn->identity_digest, digest, DIGEST_LEN))
+ if (conn->chan)
+ chan = TLS_CHAN_TO_BASE(conn->chan);
+
+ log_info(LD_HANDSHAKE, "Set identity digest for %p (%s): %s %s.",
+ conn,
+ escaped_safe_str(conn->base_.address),
+ hex_str(rsa_digest, DIGEST_LEN),
+ ed25519_fmt(ed_id));
+ log_info(LD_HANDSHAKE, " (Previously: %s %s)",
+ hex_str(conn->identity_digest, DIGEST_LEN),
+ chan ? ed25519_fmt(&chan->ed25519_identity) : "<null>");
+
+ const int rsa_id_was_set = ! tor_digest_is_zero(conn->identity_digest);
+ const int ed_id_was_set =
+ chan && !ed25519_public_key_is_zero(&chan->ed25519_identity);
+ const int rsa_changed =
+ tor_memneq(conn->identity_digest, rsa_digest, DIGEST_LEN);
+ const int ed_changed = ed_id_was_set &&
+ (!ed_id || !ed25519_pubkey_eq(ed_id, &chan->ed25519_identity));
+
+ tor_assert(!rsa_changed || !rsa_id_was_set);
+ tor_assert(!ed_changed || !ed_id_was_set);
+
+ if (!rsa_changed && !ed_changed)
return;
/* If the identity was set previously, remove the old mapping. */
- if (! tor_digest_is_zero(conn->identity_digest)) {
- connection_or_remove_from_identity_map(conn);
- if (conn->chan)
- channel_clear_identity_digest(TLS_CHAN_TO_BASE(conn->chan));
+ if (rsa_id_was_set) {
+ connection_or_clear_identity(conn);
+ if (chan)
+ channel_clear_identity_digest(chan);
}
- memcpy(conn->identity_digest, digest, DIGEST_LEN);
+ memcpy(conn->identity_digest, rsa_digest, DIGEST_LEN);
- /* If we're setting the ID to zero, don't add a mapping. */
- if (tor_digest_is_zero(digest))
+ /* If we're initializing the IDs to zero, don't add a mapping yet. */
+ if (tor_digest_is_zero(rsa_digest) &&
+ (!ed_id || ed25519_public_key_is_zero(ed_id)))
return;
- tmp = digestmap_set(orconn_identity_map, digest, conn);
- conn->next_with_same_id = tmp;
-
/* Deal with channels */
- if (conn->chan)
- channel_set_identity_digest(TLS_CHAN_TO_BASE(conn->chan), digest);
-
-#if 1
- /* Testing code to check for bugs in representation. */
- for (; tmp; tmp = tmp->next_with_same_id) {
- tor_assert(tor_memeq(tmp->identity_digest, digest, DIGEST_LEN));
- tor_assert(tmp != conn);
- }
-#endif
+ if (chan)
+ channel_set_identity_digest(chan, rsa_digest, ed_id);
}
/** Remove the Extended ORPort identifier of <b>conn</b> from the
@@ -403,8 +393,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);
@@ -430,9 +420,11 @@ cell_pack(packed_cell_t *dst, const cell_t *src, int wide_circ_ids)
set_uint32(dest, htonl(src->circ_id));
dest += 4;
} else {
+ /* Clear the last two bytes of dest, in case we can accidentally
+ * send them to the network somehow. */
+ memset(dest+CELL_MAX_NETWORK_SIZE-2, 0, 2);
set_uint16(dest, htons(src->circ_id));
dest += 2;
- memset(dest+CELL_MAX_NETWORK_SIZE-2, 0, 2); /*make sure it's clear */
}
set_uint8(dest, src->command);
memcpy(dest+1, src->payload, CELL_PAYLOAD_SIZE);
@@ -480,7 +472,7 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out, int wide_circ_ids)
var_cell_t *
var_cell_new(uint16_t payload_len)
{
- size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len;
+ size_t size = offsetof(var_cell_t, payload) + payload_len;
var_cell_t *cell = tor_malloc_zero(size);
cell->payload_len = payload_len;
cell->command = 0;
@@ -499,7 +491,7 @@ var_cell_copy(const var_cell_t *src)
size_t size = 0;
if (src != NULL) {
- size = STRUCT_OFFSET(var_cell_t, payload) + src->payload_len;
+ size = offsetof(var_cell_t, payload) + src->payload_len;
copy = tor_malloc_zero(size);
copy->payload_len = src->payload_len;
copy->command = src->command;
@@ -564,13 +556,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:
@@ -583,7 +568,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) "
@@ -734,8 +719,8 @@ connection_or_about_to_close(or_connection_t *or_conn)
const or_options_t *options = get_options();
connection_or_note_state_when_broken(or_conn);
rep_hist_note_connect_failed(or_conn->identity_digest, now);
- entry_guard_register_connect_status(or_conn->identity_digest,0,
- !options->HTTPSProxy, now);
+ /* Tell the new guard API about the channel failure */
+ entry_guard_chan_failed(TLS_CHAN_TO_BASE(or_conn->chan));
if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) {
int reason = tls_error_to_orconn_end_reason(or_conn->tls_error);
control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED,
@@ -806,27 +791,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;
@@ -837,7 +801,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.
@@ -854,24 +817,6 @@ connection_or_update_token_buckets(smartlist_t *conns,
});
}
-/** How long do we wait before killing non-canonical OR connections with no
- * circuits? In Tor versions up to 0.2.1.25 and 0.2.2.12-alpha, we waited 15
- * minutes before cancelling these connections, which caused fast relays to
- * accrue many many idle connections. Hopefully 3-4.5 minutes is low enough
- * that it kills most idle connections, without being so low that we cause
- * clients to bounce on and off.
- *
- * For canonical connections, the limit is higher, at 15-22.5 minutes.
- *
- * For each OR connection, we randomly add up to 50% extra to its idle_timeout
- * field, to avoid exposing when exactly the last circuit closed. Since we're
- * storing idle_timeout in a uint16_t, don't let these values get higher than
- * 12 hours or so without revising connection_or_set_canonical and/or expanding
- * idle_timeout.
- */
-#define IDLE_OR_CONN_TIMEOUT_NONCANONICAL 180
-#define IDLE_OR_CONN_TIMEOUT_CANONICAL 900
-
/* Mark <b>or_conn</b> as canonical if <b>is_canonical</b> is set, and
* non-canonical otherwise. Adjust idle_timeout accordingly.
*/
@@ -879,9 +824,6 @@ void
connection_or_set_canonical(or_connection_t *or_conn,
int is_canonical)
{
- const unsigned int timeout_base = is_canonical ?
- IDLE_OR_CONN_TIMEOUT_CANONICAL : IDLE_OR_CONN_TIMEOUT_NONCANONICAL;
-
if (bool_eq(is_canonical, or_conn->is_canonical) &&
or_conn->idle_timeout != 0) {
/* Don't recalculate an existing idle_timeout unless the canonical
@@ -890,7 +832,14 @@ connection_or_set_canonical(or_connection_t *or_conn,
}
or_conn->is_canonical = !! is_canonical; /* force to a 1-bit boolean */
- or_conn->idle_timeout = timeout_base + crypto_rand_int(timeout_base / 2);
+ or_conn->idle_timeout = channelpadding_get_channel_idle_timeout(
+ TLS_CHAN_TO_BASE(or_conn->chan), is_canonical);
+
+ log_info(LD_CIRC,
+ "Channel " U64_FORMAT " chose an idle timeout of %d.",
+ or_conn->chan ?
+ U64_PRINTF_ARG(TLS_CHAN_TO_BASE(or_conn->chan)->global_identifier):0,
+ or_conn->idle_timeout);
}
/** If we don't necessarily know the router we're connecting to, but we
@@ -902,15 +851,47 @@ void
connection_or_init_conn_from_address(or_connection_t *conn,
const tor_addr_t *addr, uint16_t port,
const char *id_digest,
+ const ed25519_public_key_t *ed_id,
int started_here)
{
- const node_t *r = node_get_by_id(id_digest);
- connection_or_set_identity_digest(conn, id_digest);
+ log_debug(LD_HANDSHAKE, "init conn from address %s: %s, %s (%d)",
+ fmt_addr(addr),
+ hex_str((const char*)id_digest, DIGEST_LEN),
+ ed25519_fmt(ed_id),
+ started_here);
+
+ connection_or_set_identity_digest(conn, id_digest, ed_id);
connection_or_update_token_buckets_helper(conn, 1, get_options());
conn->base_.port = port;
tor_addr_copy(&conn->base_.addr, addr);
tor_addr_copy(&conn->real_addr, addr);
+
+ connection_or_check_canonicity(conn, started_here);
+}
+
+/** Check whether the identity of <b>conn</b> matches a known node. If it
+ * does, check whether the address of conn matches the expected address, and
+ * update the connection's is_canonical flag, nickname, and address fields as
+ * appropriate. */
+static void
+connection_or_check_canonicity(or_connection_t *conn, int started_here)
+{
+ const char *id_digest = conn->identity_digest;
+ const ed25519_public_key_t *ed_id = NULL;
+ const tor_addr_t *addr = &conn->real_addr;
+ if (conn->chan)
+ ed_id = & TLS_CHAN_TO_BASE(conn->chan)->ed25519_identity;
+
+ const node_t *r = node_get_by_id(id_digest);
+ if (r &&
+ node_supports_ed25519_link_authentication(r) &&
+ ! node_ed25519_id_matches(r, ed_id)) {
+ /* If this node is capable of proving an ed25519 ID,
+ * we can't call this a canonical connection unless both IDs match. */
+ r = NULL;
+ }
+
if (r) {
tor_addr_port_t node_ap;
node_get_pref_orport(r, &node_ap);
@@ -932,10 +913,12 @@ connection_or_init_conn_from_address(or_connection_t *conn,
tor_addr_copy(&conn->base_.addr, &node_ap.addr);
conn->base_.port = node_ap.port;
}
+ tor_free(conn->nickname);
conn->nickname = tor_strdup(node_get_nickname(r));
tor_free(conn->base_.address);
conn->base_.address = tor_addr_to_str_dup(&node_ap.addr);
} else {
+ tor_free(conn->nickname);
conn->nickname = tor_malloc(HEX_DIGEST_LEN+2);
conn->nickname[0] = '$';
base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1,
@@ -981,7 +964,7 @@ connection_or_mark_bad_for_new_circs(or_connection_t *or_conn)
* too old for new circuits? */
#define TIME_BEFORE_OR_CONN_IS_TOO_OLD (60*60*24*7)
-/** Given the head of the linked list for all the or_connections with a given
+/** Given a list of all the or_connections with a given
* identity, set elements of that list as is_bad_for_new_circs as
* appropriate. Helper for connection_or_set_bad_connections().
*
@@ -998,16 +981,19 @@ connection_or_mark_bad_for_new_circs(or_connection_t *or_conn)
* See channel_is_better() in channel.c for our idea of what makes one OR
* connection better than another.
*/
-static void
-connection_or_group_set_badness(or_connection_t *head, int force)
+void
+connection_or_group_set_badness_(smartlist_t *group, int force)
{
- or_connection_t *or_conn = NULL, *best = NULL;
+ /* XXXX this function should be entirely about channels, not OR
+ * XXXX connections. */
+
+ or_connection_t *best = NULL;
int n_old = 0, n_inprogress = 0, n_canonical = 0, n_other = 0;
time_t now = time(NULL);
/* Pass 1: expire everything that's old, and see what the status of
* everything else is. */
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn))
continue;
@@ -1031,11 +1017,11 @@ connection_or_group_set_badness(or_connection_t *head, int force)
} else {
++n_other;
}
- }
+ } SMARTLIST_FOREACH_END(or_conn);
/* Pass 2: We know how about how good the best connection is.
* expire everything that's worse, and find the very best if we can. */
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn))
continue; /* This one doesn't need to be marked bad. */
@@ -1056,13 +1042,11 @@ connection_or_group_set_badness(or_connection_t *head, int force)
}
if (!best ||
- channel_is_better(now,
- TLS_CHAN_TO_BASE(or_conn->chan),
- TLS_CHAN_TO_BASE(best->chan),
- 0)) {
+ channel_is_better(TLS_CHAN_TO_BASE(or_conn->chan),
+ TLS_CHAN_TO_BASE(best->chan))) {
best = or_conn;
}
- }
+ } SMARTLIST_FOREACH_END(or_conn);
if (!best)
return;
@@ -1081,17 +1065,15 @@ connection_or_group_set_badness(or_connection_t *head, int force)
* 0.1.2.x dies out, the first case will go away, and the second one is
* "mostly harmless", so a fix can wait until somebody is bored.
*/
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn) ||
or_conn->base_.state != OR_CONN_STATE_OPEN)
continue;
if (or_conn != best &&
- channel_is_better(now,
- TLS_CHAN_TO_BASE(best->chan),
- TLS_CHAN_TO_BASE(or_conn->chan), 1)) {
- /* This isn't the best conn, _and_ the best conn is better than it,
- even when we're being forgiving. */
+ channel_is_better(TLS_CHAN_TO_BASE(best->chan),
+ TLS_CHAN_TO_BASE(or_conn->chan))) {
+ /* This isn't the best conn, _and_ the best conn is better than it */
if (best->is_canonical) {
log_info(LD_OR,
"Marking OR conn to %s:%d as unsuitable for new circuits: "
@@ -1115,24 +1097,7 @@ connection_or_group_set_badness(or_connection_t *head, int force)
connection_or_mark_bad_for_new_circs(or_conn);
}
}
- }
-}
-
-/** Go through all the OR connections (or if <b>digest</b> is non-NULL, just
- * the OR connections with that digest), and set the is_bad_for_new_circs
- * flag based on the rules in connection_or_group_set_badness() (or just
- * always set it if <b>force</b> is true).
- */
-void
-connection_or_set_bad_connections(const char *digest, int force)
-{
- if (!orconn_identity_map)
- return;
-
- DIGESTMAP_FOREACH(orconn_identity_map, identity, or_connection_t *, conn) {
- if (!digest || tor_memeq(digest, conn->identity_digest, DIGEST_LEN))
- connection_or_group_set_badness(conn, force);
- } DIGESTMAP_FOREACH_END;
+ } SMARTLIST_FOREACH_END(or_conn);
}
/** <b>conn</b> is in the 'connecting' state, and it failed to complete
@@ -1198,7 +1163,9 @@ connection_or_notify_error(or_connection_t *conn,
MOCK_IMPL(or_connection_t *,
connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
- const char *id_digest, channel_tls_t *chan))
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id,
+ channel_tls_t *chan))
{
or_connection_t *conn;
const or_options_t *options = get_options();
@@ -1218,6 +1185,11 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
log_info(LD_PROTOCOL,"Client asked me to connect to myself. Refusing.");
return NULL;
}
+ if (server_mode(options) && router_ed25519_id_is_me(ed_id)) {
+ log_info(LD_PROTOCOL,"Client asked me to connect to myself by Ed25519 "
+ "identity. Refusing.");
+ return NULL;
+ }
conn = or_connection_new(CONN_TYPE_OR, tor_addr_family(&addr));
@@ -1230,7 +1202,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
*/
conn->chan = chan;
chan->conn = conn;
- connection_or_init_conn_from_address(conn, &addr, port, id_digest, 1);
+ connection_or_init_conn_from_address(conn, &addr, port, id_digest, ed_id, 1);
connection_or_change_state(conn, OR_CONN_STATE_CONNECTING);
control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0);
@@ -1394,40 +1366,13 @@ 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;
}
@@ -1516,75 +1461,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.
*/
@@ -1682,23 +1558,38 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn,
crypto_pk_free(identity_rcvd);
- if (started_here)
+ if (started_here) {
+ /* A TLS handshake can't teach us an Ed25519 ID, so we set it to NULL
+ * here. */
+ log_debug(LD_HANDSHAKE, "Calling client_learned_peer_id from "
+ "check_valid_tls_handshake");
return connection_or_client_learned_peer_id(conn,
- (const uint8_t*)digest_rcvd_out);
+ (const uint8_t*)digest_rcvd_out,
+ NULL);
+ }
return 0;
}
/** Called when we (as a connection initiator) have definitively,
* authenticatedly, learned that ID of the Tor instance on the other
- * side of <b>conn</b> is <b>peer_id</b>. For v1 and v2 handshakes,
+ * side of <b>conn</b> is <b>rsa_peer_id</b> and optionally <b>ed_peer_id</b>.
+ * For v1 and v2 handshakes,
* this is right after we get a certificate chain in a TLS handshake
- * or renegotiation. For v3 handshakes, this is right after we get a
+ * or renegotiation. For v3+ handshakes, this is right after we get a
* certificate chain in a CERTS cell.
*
- * If we want any particular ID before, record the one we got.
+ * If we did not know the ID before, record the one we got.
*
- * If we wanted an ID, but we didn't get it, log a warning and return -1.
+ * If we wanted an ID, but we didn't get the one we expected, log a message
+ * and return -1.
+ * On relays:
+ * - log a protocol warning whenever the fingerprints don't match;
+ * On clients:
+ * - if a relay's fingerprint doesn't match, log a warning;
+ * - if we don't have updated relay fingerprints from a recent consensus, and
+ * a fallback directory mirror's hard-coded fingerprint has changed, log an
+ * info explaining that we will try another fallback.
*
* If we're testing reachability, remember what we learned.
*
@@ -1706,13 +1597,31 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn,
*/
int
connection_or_client_learned_peer_id(or_connection_t *conn,
- const uint8_t *peer_id)
+ const uint8_t *rsa_peer_id,
+ const ed25519_public_key_t *ed_peer_id)
{
const or_options_t *options = get_options();
- int severity = server_mode(options) ? LOG_PROTOCOL_WARN : LOG_WARN;
-
- if (tor_digest_is_zero(conn->identity_digest)) {
- connection_or_set_identity_digest(conn, (const char*)peer_id);
+ channel_tls_t *chan_tls = conn->chan;
+ channel_t *chan = channel_tls_to_base(chan_tls);
+ int changed_identity = 0;
+ tor_assert(chan);
+
+ const int expected_rsa_key =
+ ! tor_digest_is_zero(conn->identity_digest);
+ const int expected_ed_key =
+ ! ed25519_public_key_is_zero(&chan->ed25519_identity);
+
+ log_info(LD_HANDSHAKE, "learned peer id for %p (%s): %s, %s",
+ conn,
+ safe_str_client(conn->base_.address),
+ hex_str((const char*)rsa_peer_id, DIGEST_LEN),
+ ed25519_fmt(ed_peer_id));
+
+ if (! expected_rsa_key && ! expected_ed_key) {
+ log_info(LD_HANDSHAKE, "(we had no ID in mind when we made this "
+ "connection.");
+ connection_or_set_identity_digest(conn,
+ (const char*)rsa_peer_id, ed_peer_id);
tor_free(conn->nickname);
conn->nickname = tor_malloc(HEX_DIGEST_LEN+2);
conn->nickname[0] = '$';
@@ -1724,22 +1633,80 @@ connection_or_client_learned_peer_id(or_connection_t *conn,
/* if it's a bridge and we didn't know its identity fingerprint, now
* we do -- remember it for future attempts. */
learned_router_identity(&conn->base_.addr, conn->base_.port,
- (const char*)peer_id);
+ (const char*)rsa_peer_id, ed_peer_id);
+ changed_identity = 1;
}
- if (tor_memneq(peer_id, conn->identity_digest, DIGEST_LEN)) {
+ const int rsa_mismatch = expected_rsa_key &&
+ tor_memneq(rsa_peer_id, conn->identity_digest, DIGEST_LEN);
+ /* It only counts as an ed25519 mismatch if we wanted an ed25519 identity
+ * and didn't get it. It's okay if we get one that we didn't ask for. */
+ const int ed25519_mismatch =
+ expected_ed_key &&
+ (ed_peer_id == NULL ||
+ ! ed25519_pubkey_eq(&chan->ed25519_identity, ed_peer_id));
+
+ if (rsa_mismatch || ed25519_mismatch) {
/* I was aiming for a particular digest. I didn't get it! */
- char seen[HEX_DIGEST_LEN+1];
- char expected[HEX_DIGEST_LEN+1];
- base16_encode(seen, sizeof(seen), (const char*)peer_id, DIGEST_LEN);
- base16_encode(expected, sizeof(expected), conn->identity_digest,
+ char seen_rsa[HEX_DIGEST_LEN+1];
+ char expected_rsa[HEX_DIGEST_LEN+1];
+ char seen_ed[ED25519_BASE64_LEN+1];
+ char expected_ed[ED25519_BASE64_LEN+1];
+ base16_encode(seen_rsa, sizeof(seen_rsa),
+ (const char*)rsa_peer_id, DIGEST_LEN);
+ base16_encode(expected_rsa, sizeof(expected_rsa), conn->identity_digest,
DIGEST_LEN);
+ if (ed_peer_id) {
+ ed25519_public_to_base64(seen_ed, ed_peer_id);
+ } else {
+ strlcpy(seen_ed, "no ed25519 key", sizeof(seen_ed));
+ }
+ if (! ed25519_public_key_is_zero(&chan->ed25519_identity)) {
+ ed25519_public_to_base64(expected_ed, &chan->ed25519_identity);
+ } else {
+ strlcpy(expected_ed, "no ed25519 key", sizeof(expected_ed));
+ }
+ const int using_hardcoded_fingerprints =
+ !networkstatus_get_reasonably_live_consensus(time(NULL),
+ usable_consensus_flavor());
+ const int is_fallback_fingerprint = router_digest_is_fallback_dir(
+ conn->identity_digest);
+ const int is_authority_fingerprint = router_digest_is_trusted_dir(
+ conn->identity_digest);
+ int severity;
+ const char *extra_log = "";
+
+ if (server_mode(options)) {
+ severity = LOG_PROTOCOL_WARN;
+ } else {
+ if (using_hardcoded_fingerprints) {
+ /* We need to do the checks in this order, because the list of
+ * fallbacks includes the list of authorities */
+ if (is_authority_fingerprint) {
+ severity = LOG_WARN;
+ } else if (is_fallback_fingerprint) {
+ /* we expect a small number of fallbacks to change from their
+ * hard-coded fingerprints over the life of a release */
+ severity = LOG_INFO;
+ extra_log = " Tor will try a different fallback.";
+ } else {
+ /* it's a bridge, it's either a misconfiguration, or unexpected */
+ severity = LOG_WARN;
+ }
+ } else {
+ /* a relay has changed its fingerprint from the one in the consensus */
+ severity = LOG_WARN;
+ }
+ }
+
log_fn(severity, LD_HANDSHAKE,
- "Tried connecting to router at %s:%d, but identity key was not "
- "as expected: wanted %s but got %s.",
- conn->base_.address, conn->base_.port, expected, seen);
- entry_guard_register_connect_status(conn->identity_digest, 0, 1,
- time(NULL));
+ "Tried connecting to router at %s:%d, but RSA identity key was not "
+ "as expected: wanted %s + %s but got %s + %s.%s",
+ conn->base_.address, conn->base_.port,
+ expected_rsa, expected_ed, seen_rsa, seen_ed, extra_log);
+
+ /* Tell the new guard API about the channel failure */
+ entry_guard_chan_failed(TLS_CHAN_TO_BASE(conn->chan));
control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED,
END_OR_CONN_REASON_OR_IDENTITY);
if (!authdir_mode_tests_reachability(options))
@@ -1749,9 +1716,24 @@ connection_or_client_learned_peer_id(or_connection_t *conn,
conn);
return -1;
}
+
+ if (!expected_ed_key && ed_peer_id) {
+ log_info(LD_HANDSHAKE, "(we had no Ed25519 ID in mind when we made this "
+ "connection.");
+ connection_or_set_identity_digest(conn,
+ (const char*)rsa_peer_id, ed_peer_id);
+ changed_identity = 1;
+ }
+
+ if (changed_identity) {
+ /* If we learned an identity for this connection, then we might have
+ * just discovered it to be canonical. */
+ connection_or_check_canonicity(conn, conn->handshake_state->started_here);
+ }
+
if (authdir_mode_tests_reachability(options)) {
dirserv_orconn_tls_done(&conn->base_.addr, conn->base_.port,
- (const char*)peer_id);
+ (const char*)rsa_peer_id, ed_peer_id);
}
return 0;
@@ -1807,7 +1789,8 @@ connection_tls_finish_handshake(or_connection_t *conn)
if (tor_tls_used_v1_handshake(conn->tls)) {
conn->link_proto = 1;
connection_or_init_conn_from_address(conn, &conn->base_.addr,
- conn->base_.port, digest_rcvd, 0);
+ conn->base_.port, digest_rcvd,
+ NULL, 0);
tor_tls_block_renegotiation(conn->tls);
rep_hist_note_negotiated_link_proto(1, started_here);
return connection_or_set_state_open(conn);
@@ -1816,7 +1799,8 @@ connection_tls_finish_handshake(or_connection_t *conn)
if (connection_init_or_handshake_state(conn, started_here) < 0)
return -1;
connection_or_init_conn_from_address(conn, &conn->base_.addr,
- conn->base_.port, digest_rcvd, 0);
+ conn->base_.port, digest_rcvd,
+ NULL, 0);
return connection_or_send_versions(conn, 0);
}
}
@@ -1855,6 +1839,11 @@ connection_init_or_handshake_state(or_connection_t *conn, int started_here)
s->started_here = started_here ? 1 : 0;
s->digest_sent_data = 1;
s->digest_received_data = 1;
+ if (! started_here && get_current_link_cert_cert()) {
+ s->own_link_cert = tor_cert_dup(get_current_link_cert_cert());
+ }
+ s->certs = or_handshake_certs_new();
+ s->certs->started_here = s->started_here;
return 0;
}
@@ -1866,8 +1855,8 @@ or_handshake_state_free(or_handshake_state_t *state)
return;
crypto_digest_free(state->digest_sent);
crypto_digest_free(state->digest_received);
- tor_x509_cert_free(state->auth_cert);
- tor_x509_cert_free(state->id_cert);
+ or_handshake_certs_free(state->certs);
+ tor_cert_free(state->own_link_cert);
memwipe(state, 0xBE, sizeof(or_handshake_state_t));
tor_free(state);
}
@@ -1962,11 +1951,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;
}
@@ -1986,12 +1971,23 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn)
cell_pack(&networkcell, cell, conn->wide_circ_ids);
+ rep_hist_padding_count_write(PADDING_TYPE_TOTAL);
+ if (cell->command == CELL_PADDING)
+ rep_hist_padding_count_write(PADDING_TYPE_CELL);
+
connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn));
/* Touch the channel's active timestamp if there is one */
- if (conn->chan)
+ if (conn->chan) {
channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan));
+ if (TLS_CHAN_TO_BASE(conn->chan)->currently_padding) {
+ rep_hist_padding_count_write(PADDING_TYPE_ENABLED_TOTAL);
+ if (cell->command == CELL_PADDING)
+ rep_hist_padding_count_write(PADDING_TYPE_ENABLED_CELL);
+ }
+ }
+
if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3)
or_handshake_state_record_cell(conn, conn->handshake_state, cell, 0);
}
@@ -2026,12 +2022,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.
@@ -2102,7 +2093,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn)
}
/** Array of recognized link protocol versions. */
-static const uint16_t or_protocol_versions[] = { 1, 2, 3, 4 };
+static const uint16_t or_protocol_versions[] = { 1, 2, 3, 4, 5 };
/** Number of versions in <b>or_protocol_versions</b>. */
static const int n_or_protocol_versions =
(int)( sizeof(or_protocol_versions)/sizeof(uint16_t) );
@@ -2223,58 +2214,187 @@ connection_or_send_netinfo,(or_connection_t *conn))
return 0;
}
+/** Helper used to add an encoded certs to a cert cell */
+static void
+add_certs_cell_cert_helper(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const uint8_t *cert_encoded,
+ size_t cert_len)
+{
+ tor_assert(cert_len <= UINT16_MAX);
+ certs_cell_cert_t *ccc = certs_cell_cert_new();
+ ccc->cert_type = cert_type;
+ ccc->cert_len = cert_len;
+ certs_cell_cert_setlen_body(ccc, cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc), cert_encoded, cert_len);
+
+ certs_cell_add_certs(certs_cell, ccc);
+}
+
+/** Add an encoded X509 cert (stored as <b>cert_len</b> bytes at
+ * <b>cert_encoded</b>) to the trunnel certs_cell_t object that we are
+ * building in <b>certs_cell</b>. Set its type field to <b>cert_type</b>.
+ * (If <b>cert</b> is NULL, take no action.) */
+static void
+add_x509_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_x509_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ const uint8_t *cert_encoded = NULL;
+ size_t cert_len;
+ tor_x509_cert_get_der(cert, &cert_encoded, &cert_len);
+
+ add_certs_cell_cert_helper(certs_cell, cert_type, cert_encoded, cert_len);
+}
+
+/** Add an Ed25519 cert from <b>cert</b> to the trunnel certs_cell_t object
+ * that we are building in <b>certs_cell</b>. Set its type field to
+ * <b>cert_type</b>. (If <b>cert</b> is NULL, take no action.) */
+static void
+add_ed25519_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ add_certs_cell_cert_helper(certs_cell, cert_type,
+ cert->encoded, cert->encoded_len);
+}
+
+#ifdef TOR_UNIT_TESTS
+int certs_cell_ed25519_disabled_for_testing = 0;
+#else
+#define certs_cell_ed25519_disabled_for_testing 0
+#endif
+
/** Send a CERTS cell on the connection <b>conn</b>. Return 0 on success, -1
* on failure. */
int
connection_or_send_certs_cell(or_connection_t *conn)
{
- const tor_x509_cert_t *link_cert = NULL, *id_cert = NULL;
- const uint8_t *link_encoded = NULL, *id_encoded = NULL;
- size_t link_len, id_len;
+ const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL;
+ tor_x509_cert_t *own_link_cert = NULL;
var_cell_t *cell;
- size_t cell_len;
- ssize_t pos;
- int server_mode;
+
+ certs_cell_t *certs_cell = NULL;
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;
+
+ /* Get the encoded values of the X509 certificates */
+ 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);
- tor_x509_cert_get_der(id_cert, &id_encoded, &id_len);
- cell_len = 1 /* 1 byte: num certs in cell */ +
- 2 * ( 1 + 2 ) /* For each cert: 1 byte for type, 2 for length */ +
- link_len + id_len;
- cell = var_cell_new(cell_len);
- cell->command = CELL_CERTS;
- cell->payload[0] = 2;
- pos = 1;
+ if (conn_in_server_mode) {
+ own_link_cert = tor_tls_get_own_cert(conn->tls);
+ }
+ tor_assert(id_cert);
- if (server_mode)
- cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */
- else
- cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */
- set_uint16(&cell->payload[pos+1], htons(link_len));
- memcpy(&cell->payload[pos+3], link_encoded, link_len);
- pos += 3 + link_len;
+ certs_cell = certs_cell_new();
+
+ /* Start adding certs. First the link cert or auth1024 cert. */
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(own_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_TLS_LINK, own_link_cert);
+ } else {
+ tor_assert(global_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_AUTH_1024, global_link_cert);
+ }
+
+ /* Next the RSA->RSA ID cert */
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_ID_1024, id_cert);
+
+ /* Next the Ed25519 certs */
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_ID_SIGN,
+ get_master_signing_key_cert());
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(conn->handshake_state->own_link_cert ||
+ certs_cell_ed25519_disabled_for_testing);
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_LINK,
+ conn->handshake_state->own_link_cert);
+ } else {
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_AUTH,
+ get_current_auth_key_cert());
+ }
- cell->payload[pos] = OR_CERT_TYPE_ID_1024; /* ID cert */
- set_uint16(&cell->payload[pos+1], htons(id_len));
- memcpy(&cell->payload[pos+3], id_encoded, id_len);
- pos += 3 + id_len;
+ /* And finally the crosscert. */
+ {
+ const uint8_t *crosscert=NULL;
+ size_t crosscert_len;
+ get_master_rsa_crosscert(&crosscert, &crosscert_len);
+ if (crosscert) {
+ add_certs_cell_cert_helper(certs_cell,
+ CERTTYPE_RSA1024_ID_EDID,
+ crosscert, crosscert_len);
+ }
+ }
- tor_assert(pos == (int)cell_len); /* Otherwise we just smashed the heap */
+ /* We've added all the certs; make the cell. */
+ certs_cell->n_certs = certs_cell_getlen_certs(certs_cell);
+
+ ssize_t alloc_len = certs_cell_encoded_len(certs_cell);
+ tor_assert(alloc_len >= 0 && alloc_len <= UINT16_MAX);
+ cell = var_cell_new(alloc_len);
+ cell->command = CELL_CERTS;
+ ssize_t enc_len = certs_cell_encode(cell->payload, alloc_len, certs_cell);
+ tor_assert(enc_len > 0 && enc_len <= alloc_len);
+ cell->payload_len = enc_len;
connection_or_write_var_cell_to_buf(cell, conn);
var_cell_free(cell);
+ certs_cell_free(certs_cell);
+ tor_x509_cert_free(own_link_cert);
return 0;
}
+/** Return true iff <b>challenge_type</b> is an AUTHCHALLENGE type that
+ * we can send and receive. */
+int
+authchallenge_type_is_supported(uint16_t challenge_type)
+{
+ switch (challenge_type) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ return 1;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ default:
+ return 0;
+ }
+}
+
+/** Return true iff <b>challenge_type_a</b> is one that we would rather
+ * use than <b>challenge_type_b</b>. */
+int
+authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b)
+{
+ /* Any supported type is better than an unsupported one;
+ * all unsupported types are equally bad. */
+ if (!authchallenge_type_is_supported(challenge_type_a))
+ return 0;
+ if (!authchallenge_type_is_supported(challenge_type_b))
+ return 1;
+ /* It happens that types are superior in numerically ascending order.
+ * If that ever changes, this must change too. */
+ return (challenge_type_a > challenge_type_b);
+}
+
/** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0
* on success, -1 on failure. */
int
@@ -2289,17 +2409,26 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
auth_challenge_cell_t *ac = auth_challenge_cell_new();
+ tor_assert(sizeof(ac->challenge) == 32);
crypto_rand((char*)ac->challenge, sizeof(ac->challenge));
auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET);
+ /* Disabled, because everything that supports this method also supports
+ * the much-superior ED25519_SHA256_RFC5705 */
+ /* auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_RFC5705); */
+ auth_challenge_cell_add_methods(ac, AUTHTYPE_ED25519_SHA256_RFC5705);
auth_challenge_cell_set_n_methods(ac,
auth_challenge_cell_getlen_methods(ac));
cell = var_cell_new(auth_challenge_cell_encoded_len(ac));
ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len,
ac);
- if (len != cell->payload_len)
+ if (len != cell->payload_len) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Encoded auth challenge cell length not as expected");
goto done;
+ /* LCOV_EXCL_STOP */
+ }
cell->command = CELL_AUTH_CHALLENGE;
connection_or_write_var_cell_to_buf(cell, conn);
@@ -2313,8 +2442,8 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
}
/** Compute the main body of an AUTHENTICATE cell that a client can use
- * to authenticate itself on a v3 handshake for <b>conn</b>. Write it to the
- * <b>outlen</b>-byte buffer at <b>out</b>.
+ * to authenticate itself on a v3 handshake for <b>conn</b>. Return it
+ * in a var_cell_t.
*
* If <b>server</b> is true, only calculate the first
* V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's
@@ -2330,34 +2459,54 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
*
* Return the length of the cell body on success, and -1 on failure.
*/
-int
+var_cell_t *
connection_or_compute_authenticate_cell_body(or_connection_t *conn,
- uint8_t *out, size_t outlen,
+ const int authtype,
crypto_pk_t *signing_key,
- int server)
+ const ed25519_keypair_t *ed_signing_key,
+ int server)
{
auth1_t *auth = NULL;
auth_ctx_t *ctx = auth_ctx_new();
- int result;
+ var_cell_t *result = NULL;
+ int old_tlssecrets_algorithm = 0;
+ const char *authtype_str = NULL;
- /* assert state is reasonable XXXX */
+ int is_ed = 0;
- ctx->is_ed = 0;
+ /* assert state is reasonable XXXX */
+ switch (authtype) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+ authtype_str = "AUTH0001";
+ old_tlssecrets_algorithm = 1;
+ break;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ authtype_str = "AUTH0002";
+ break;
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ authtype_str = "AUTH0003";
+ is_ed = 1;
+ break;
+ default:
+ tor_assert(0);
+ break;
+ }
auth = auth1_new();
+ ctx->is_ed = is_ed;
/* Type: 8 bytes. */
- memcpy(auth1_getarray_type(auth), "AUTH0001", 8);
+ memcpy(auth1_getarray_type(auth), authtype_str, 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 =
- tor_x509_cert_get_id_digests(conn->handshake_state->id_cert);
+ tor_x509_cert_get_id_digests(conn->handshake_state->certs->id_cert);
tor_assert(my_digests);
tor_assert(their_digests);
my_id = (uint8_t*)my_digests->d[DIGEST_SHA256];
@@ -2373,6 +2522,22 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
memcpy(auth->sid, server_id, 32);
}
+ if (is_ed) {
+ const ed25519_public_key_t *my_ed_id, *their_ed_id;
+ if (!conn->handshake_state->certs->ed_id_sign) {
+ log_warn(LD_OR, "Ed authenticate without Ed ID cert from peer.");
+ goto err;
+ }
+ my_ed_id = get_master_identity_key();
+ their_ed_id = &conn->handshake_state->certs->ed_id_sign->signing_key;
+
+ const uint8_t *cid_ed = (server ? their_ed_id : my_ed_id)->pubkey;
+ const uint8_t *sid_ed = (server ? my_ed_id : their_ed_id)->pubkey;
+
+ memcpy(auth->u1_cid_ed, cid_ed, ED25519_PUBKEY_LEN);
+ memcpy(auth->u1_sid_ed, sid_ed, ED25519_PUBKEY_LEN);
+ }
+
{
crypto_digest_t *server_d, *client_d;
if (server) {
@@ -2392,57 +2557,98 @@ 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.");
+ log_warn(LD_OR, "Unable to find cert when making %s data.",
+ authtype_str);
goto err;
}
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 */
- tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets);
+ if (old_tlssecrets_algorithm) {
+ tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets);
+ } else {
+ char label[128];
+ tor_snprintf(label, sizeof(label),
+ "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str);
+ tor_tls_export_key_material(conn->tls, auth->tlssecrets,
+ auth->cid, sizeof(auth->cid),
+ label);
+ }
/* 8 octets were reserved for the current time, but we're trying to get out
* of the habit of sending time around willynilly. Fortunately, nothing
* checks it. That's followed by 16 bytes of nonce. */
crypto_rand((char*)auth->rand, 24);
+ ssize_t maxlen = auth1_encoded_len(auth, ctx);
+ if (ed_signing_key && is_ed) {
+ maxlen += ED25519_SIG_LEN;
+ } else if (signing_key && !is_ed) {
+ maxlen += crypto_pk_keysize(signing_key);
+ }
+
+ const int AUTH_CELL_HEADER_LEN = 4; /* 2 bytes of type, 2 bytes of length */
+ result = var_cell_new(AUTH_CELL_HEADER_LEN + maxlen);
+ uint8_t *const out = result->payload + AUTH_CELL_HEADER_LEN;
+ const size_t outlen = maxlen;
ssize_t len;
+
+ result->command = CELL_AUTHENTICATE;
+ set_uint16(result->payload, htons(authtype));
+
if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) {
- log_warn(LD_OR, "Unable to encode signed part of AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed part of AUTH1 data.");
goto err;
+ /* LCOV_EXCL_STOP */
}
if (server) {
auth1_t *tmp = NULL;
ssize_t len2 = auth1_parse(&tmp, out, len, ctx);
if (!tmp) {
- log_warn(LD_OR, "Unable to parse signed part of AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to parse signed part of AUTH1 data that "
+ "we just encoded");
goto err;
+ /* LCOV_EXCL_STOP */
}
- result = (int) (tmp->end_of_fixed_part - out);
+ result->payload_len = (tmp->end_of_signed - result->payload);
+
auth1_free(tmp);
if (len2 != len) {
- log_warn(LD_OR, "Mismatched length when re-parsing AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data.");
goto err;
+ /* LCOV_EXCL_STOP */
}
goto done;
}
- if (signing_key) {
+ if (ed_signing_key && is_ed) {
+ ed25519_signature_t sig;
+ if (ed25519_sign(&sig, out, len, ed_signing_key) < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to sign ed25519 authentication data");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ auth1_setlen_sig(auth, ED25519_SIG_LEN);
+ memcpy(auth1_getarray_sig(auth), sig.sig, ED25519_SIG_LEN);
+
+ } else if (signing_key && !is_ed) {
auth1_setlen_sig(auth, crypto_pk_keysize(signing_key));
char d[32];
@@ -2457,18 +2663,24 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
}
auth1_setlen_sig(auth, siglen);
+ }
- len = auth1_encode(out, outlen, auth, ctx);
- if (len < 0) {
- log_warn(LD_OR, "Unable to encode signed AUTH1 data.");
- goto err;
- }
+ len = auth1_encode(out, outlen, auth, ctx);
+ if (len < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed AUTH1 data.");
+ goto err;
+ /* LCOV_EXCL_STOP */
}
- result = (int) len;
+ tor_assert(len + AUTH_CELL_HEADER_LEN <= result->payload_len);
+ result->payload_len = len + AUTH_CELL_HEADER_LEN;
+ set_uint16(result->payload+2, htons(len));
+
goto done;
err:
- result = -1;
+ var_cell_free(result);
+ result = NULL;
done:
auth1_free(auth);
auth_ctx_free(ctx);
@@ -2482,44 +2694,29 @@ connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype))
{
var_cell_t *cell;
crypto_pk_t *pk = tor_tls_get_my_client_auth_key();
- int authlen;
- size_t cell_maxlen;
/* XXXX make sure we're actually supposed to send this! */
if (!pk) {
log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key");
return -1;
}
- if (authtype != AUTHTYPE_RSA_SHA256_TLSSECRET) {
+ if (! authchallenge_type_is_supported(authtype)) {
log_warn(LD_BUG, "Tried to send authenticate cell with unknown "
"authentication type %d", authtype);
return -1;
}
- cell_maxlen = 4 + /* overhead */
- V3_AUTH_BODY_LEN + /* Authentication body */
- crypto_pk_keysize(pk) + /* Max signature length */
- 16 /* add a few extra bytes just in case. */;
-
- cell = var_cell_new(cell_maxlen);
- cell->command = CELL_AUTHENTICATE;
- set_uint16(cell->payload, htons(AUTHTYPE_RSA_SHA256_TLSSECRET));
- /* skip over length ; we don't know that yet. */
-
- authlen = connection_or_compute_authenticate_cell_body(conn,
- cell->payload+4,
- cell_maxlen-4,
- pk,
- 0 /* not server */);
- if (authlen < 0) {
+ cell = connection_or_compute_authenticate_cell_body(conn,
+ authtype,
+ pk,
+ get_current_auth_keypair(),
+ 0 /* not server */);
+ if (! cell) {
+ /* LCOV_EXCL_START */
log_warn(LD_BUG, "Unable to compute authenticate cell!");
- var_cell_free(cell);
return -1;
+ /* LCOV_EXCL_STOP */
}
- tor_assert(authlen + 4 <= cell->payload_len);
- set_uint16(cell->payload+2, htons(authlen));
- cell->payload_len = authlen + 4;
-
connection_or_write_var_cell_to_buf(cell, conn);
var_cell_free(cell);
diff --git a/src/or/connection_or.h b/src/or/connection_or.h
index e2ec47a4f2..fe85a3f5fd 100644
--- a/src/or/connection_or.h
+++ b/src/or/connection_or.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -12,14 +12,13 @@
#ifndef TOR_CONNECTION_OR_H
#define TOR_CONNECTION_OR_H
-void connection_or_remove_from_identity_map(or_connection_t *conn);
+void connection_or_clear_identity(or_connection_t *conn);
void connection_or_clear_identity_map(void);
void clear_broken_connection_map(int disable);
or_connection_t *connection_or_get_for_extend(const char *digest,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out);
-void connection_or_set_bad_connections(const char *digest, int force);
void connection_or_block_renegotiation(or_connection_t *conn);
int connection_or_reached_eof(or_connection_t *conn);
@@ -40,7 +39,9 @@ void connection_or_notify_error(or_connection_t *conn,
MOCK_DECL(or_connection_t *,
connection_or_connect,
(const tor_addr_t *addr, uint16_t port,
- const char *id_digest, channel_tls_t *chan));
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id,
+ channel_tls_t *chan));
void connection_or_close_normally(or_connection_t *orconn, int flush);
MOCK_DECL(void,connection_or_close_for_error,
@@ -59,12 +60,14 @@ int connection_init_or_handshake_state(or_connection_t *conn,
void connection_or_init_conn_from_address(or_connection_t *conn,
const tor_addr_t *addr,
uint16_t port,
- const char *id_digest,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
int started_here);
int connection_or_client_learned_peer_id(or_connection_t *conn,
- const uint8_t *peer_id);
+ const uint8_t *rsa_peer_id,
+ const ed25519_public_key_t *ed_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,
@@ -84,10 +87,14 @@ int connection_or_send_versions(or_connection_t *conn, int v3_plus);
MOCK_DECL(int,connection_or_send_netinfo,(or_connection_t *conn));
int connection_or_send_certs_cell(or_connection_t *conn);
int connection_or_send_auth_challenge_cell(or_connection_t *conn);
-int connection_or_compute_authenticate_cell_body(or_connection_t *conn,
- uint8_t *out, size_t outlen,
- crypto_pk_t *signing_key,
- int server);
+int authchallenge_type_is_supported(uint16_t challenge_type);
+int authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b);
+var_cell_t *connection_or_compute_authenticate_cell_body(or_connection_t *conn,
+ const int authtype,
+ crypto_pk_t *signing_key,
+ const ed25519_keypair_t *ed_signing_key,
+ int server);
MOCK_DECL(int,connection_or_send_authenticate_cell,
(or_connection_t *conn, int type));
@@ -102,6 +109,14 @@ void var_cell_free(var_cell_t *cell);
/* DOCDOC */
#define MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS 4
+#define MIN_LINK_PROTO_FOR_CHANNEL_PADDING 5
+#define MAX_LINK_PROTO MIN_LINK_PROTO_FOR_CHANNEL_PADDING
+
+void connection_or_group_set_badness_(smartlist_t *group, int force);
+
+#ifdef TOR_UNIT_TESTS
+extern int certs_cell_ed25519_disabled_for_testing;
+#endif
#endif
diff --git a/src/or/conscache.c b/src/or/conscache.c
new file mode 100644
index 0000000000..5ffa129bbe
--- /dev/null
+++ b/src/or/conscache.c
@@ -0,0 +1,541 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "or.h"
+
+#include "config.h"
+#include "conscache.h"
+#include "storagedir.h"
+
+#define CCE_MAGIC 0x17162253
+
+/**
+ * A consensus_cache_entry_t is a reference-counted handle to an
+ * item in a consensus_cache_t. It can be mmapped into RAM, or not,
+ * depending whether it's currently in use.
+ */
+struct consensus_cache_entry_t {
+ uint32_t magic; /**< Must be set to CCE_MAGIC */
+ HANDLE_ENTRY(consensus_cache_entry, consensus_cache_entry_t);
+ int32_t refcnt; /**< Reference count. */
+ unsigned can_remove : 1; /**< If true, we want to delete this file. */
+ /** If true, we intend to unmap this file as soon as we're done with it. */
+ unsigned release_aggressively : 1;
+
+ /** Filename for this object within the storage_dir_t */
+ char *fname;
+ /** Labels associated with this object. Immutable once the object
+ * is created. */
+ config_line_t *labels;
+ /** Pointer to the cache that includes this entry (if any). */
+ consensus_cache_t *in_cache;
+
+ /** Since what time has this object been mapped into RAM, but with the cache
+ * being the only having a reference to it? */
+ time_t unused_since;
+ /** mmaped contents of the underlying file. May be NULL */
+ tor_mmap_t *map;
+ /** Length of the body within <b>map</b>. */
+ size_t bodylen;
+ /** Pointer to the body within <b>map</b>. */
+ const uint8_t *body;
+};
+
+/**
+ * A consensus_cache_t holds a directory full of labeled items.
+ */
+struct consensus_cache_t {
+ /** Underling storage_dir_t to handle persistence */
+ storage_dir_t *dir;
+ /** List of all the entries in the directory. */
+ smartlist_t *entries;
+};
+
+static void consensus_cache_clear(consensus_cache_t *cache);
+static void consensus_cache_rescan(consensus_cache_t *);
+static void consensus_cache_entry_map(consensus_cache_t *,
+ consensus_cache_entry_t *);
+static void consensus_cache_entry_unmap(consensus_cache_entry_t *ent);
+
+/**
+ * Helper: Open a consensus cache in subdirectory <b>subdir</b> of the
+ * data directory, to hold up to <b>max_entries</b> of data.
+ */
+consensus_cache_t *
+consensus_cache_open(const char *subdir, int max_entries)
+{
+ consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t));
+ char *directory = get_datadir_fname(subdir);
+ cache->dir = storage_dir_new(directory, max_entries);
+ tor_free(directory);
+ if (!cache->dir) {
+ tor_free(cache);
+ return NULL;
+ }
+
+ consensus_cache_rescan(cache);
+ return cache;
+}
+
+/**
+ * Tell the sandbox (if any) configured by <b>cfg</b> to allow the
+ * operations that <b>cache</b> will need.
+ */
+int
+consensus_cache_register_with_sandbox(consensus_cache_t *cache,
+ struct sandbox_cfg_elem **cfg)
+{
+ return storage_dir_register_with_sandbox(cache->dir, cfg);
+}
+
+/**
+ * Helper: clear all entries from <b>cache</b> (but do not delete
+ * any that aren't marked for removal
+ */
+static void
+consensus_cache_clear(consensus_cache_t *cache)
+{
+ consensus_cache_delete_pending(cache, 0);
+
+ SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) {
+ ent->in_cache = NULL;
+ consensus_cache_entry_decref(ent);
+ } SMARTLIST_FOREACH_END(ent);
+ smartlist_free(cache->entries);
+ cache->entries = NULL;
+}
+
+/**
+ * Drop all storage held by <b>cache</b>.
+ */
+void
+consensus_cache_free(consensus_cache_t *cache)
+{
+ if (! cache)
+ return;
+
+ if (cache->entries) {
+ consensus_cache_clear(cache);
+ }
+ storage_dir_free(cache->dir);
+ tor_free(cache);
+}
+
+/**
+ * Write <b>datalen</b> bytes of data at <b>data</b> into the <b>cache</b>,
+ * labeling that data with <b>labels</b>. On failure, return NULL. On
+ * success, return a newly created consensus_cache_entry_t.
+ *
+ * The returned value will be owned by the cache, and you will have a
+ * reference to it. Call consensus_cache_entry_decref() when you are
+ * done with it.
+ *
+ * The provided <b>labels</b> MUST have distinct keys: if they don't,
+ * this API does not specify which values (if any) for the duplicate keys
+ * will be considered.
+ */
+consensus_cache_entry_t *
+consensus_cache_add(consensus_cache_t *cache,
+ const config_line_t *labels,
+ const uint8_t *data,
+ size_t datalen)
+{
+ char *fname = NULL;
+ int r = storage_dir_save_labeled_to_file(cache->dir,
+ labels, data, datalen, &fname);
+ if (r < 0 || fname == NULL) {
+ return NULL;
+ }
+ consensus_cache_entry_t *ent =
+ tor_malloc_zero(sizeof(consensus_cache_entry_t));
+ ent->magic = CCE_MAGIC;
+ ent->fname = fname;
+ ent->labels = config_lines_dup(labels);
+ ent->in_cache = cache;
+ ent->unused_since = TIME_MAX;
+ smartlist_add(cache->entries, ent);
+ /* Start the reference count at 2: the caller owns one copy, and the
+ * cache owns another.
+ */
+ ent->refcnt = 2;
+
+ return ent;
+}
+
+/**
+ * Given a <b>cache</b>, return some entry for which <b>key</b>=<b>value</b>.
+ * Return NULL if no such entry exists.
+ *
+ * Does not adjust reference counts.
+ */
+consensus_cache_entry_t *
+consensus_cache_find_first(consensus_cache_t *cache,
+ const char *key,
+ const char *value)
+{
+ smartlist_t *tmp = smartlist_new();
+ consensus_cache_find_all(tmp, cache, key, value);
+ consensus_cache_entry_t *ent = NULL;
+ if (smartlist_len(tmp))
+ ent = smartlist_get(tmp, 0);
+ smartlist_free(tmp);
+ return ent;
+}
+
+/**
+ * Given a <b>cache</b>, add every entry to <b>out<b> for which
+ * <b>key</b>=<b>value</b>. If <b>key</b> is NULL, add every entry.
+ *
+ * Do not add any entry that has been marked for removal.
+ *
+ * Does not adjust reference counts.
+ */
+void
+consensus_cache_find_all(smartlist_t *out,
+ consensus_cache_t *cache,
+ const char *key,
+ const char *value)
+{
+ SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) {
+ if (ent->can_remove == 1) {
+ /* We want to delete this; pretend it isn't there. */
+ continue;
+ }
+ if (! key) {
+ smartlist_add(out, ent);
+ continue;
+ }
+ const char *found_val = consensus_cache_entry_get_value(ent, key);
+ if (found_val && !strcmp(value, found_val)) {
+ smartlist_add(out, ent);
+ }
+ } SMARTLIST_FOREACH_END(ent);
+}
+
+/**
+ * Given a list of consensus_cache_entry_t, remove all those entries
+ * that do not have <b>key</b>=<b>value</b> in their labels.
+ *
+ * Does not adjust reference counts.
+ */
+void
+consensus_cache_filter_list(smartlist_t *lst,
+ const char *key,
+ const char *value)
+{
+ if (BUG(lst == NULL))
+ return; // LCOV_EXCL_LINE
+ if (key == NULL)
+ return;
+ SMARTLIST_FOREACH_BEGIN(lst, consensus_cache_entry_t *, ent) {
+ const char *found_val = consensus_cache_entry_get_value(ent, key);
+ if (! found_val || strcmp(value, found_val)) {
+ SMARTLIST_DEL_CURRENT(lst, ent);
+ }
+ } SMARTLIST_FOREACH_END(ent);
+}
+
+/**
+ * If <b>ent</b> has a label with the given <b>key</b>, return its
+ * value. Otherwise return NULL.
+ *
+ * The return value is only guaranteed to be valid for as long as you
+ * hold a reference to <b>ent</b>.
+ */
+const char *
+consensus_cache_entry_get_value(const consensus_cache_entry_t *ent,
+ const char *key)
+{
+ const config_line_t *match = config_line_find(ent->labels, key);
+ if (match)
+ return match->value;
+ else
+ return NULL;
+}
+
+/**
+ * Return a pointer to the labels in <b>ent</b>.
+ *
+ * This pointer is only guaranteed to be valid for as long as you
+ * hold a reference to <b>ent</b>.
+ */
+const config_line_t *
+consensus_cache_entry_get_labels(const consensus_cache_entry_t *ent)
+{
+ return ent->labels;
+}
+
+/**
+ * Increase the reference count of <b>ent</b>.
+ */
+void
+consensus_cache_entry_incref(consensus_cache_entry_t *ent)
+{
+ if (BUG(ent->magic != CCE_MAGIC))
+ return; // LCOV_EXCL_LINE
+ ++ent->refcnt;
+ ent->unused_since = TIME_MAX;
+}
+
+/**
+ * Release a reference held to <b>ent</b>.
+ *
+ * If it was the last reference, ent will be freed. Therefore, you must not
+ * use <b>ent</b> after calling this function.
+ */
+void
+consensus_cache_entry_decref(consensus_cache_entry_t *ent)
+{
+ if (! ent)
+ return;
+ if (BUG(ent->refcnt <= 0))
+ return; // LCOV_EXCL_LINE
+ if (BUG(ent->magic != CCE_MAGIC))
+ return; // LCOV_EXCL_LINE
+
+ --ent->refcnt;
+
+ if (ent->refcnt == 1 && ent->in_cache) {
+ /* Only the cache has a reference: we don't need to keep the file
+ * mapped */
+ if (ent->map) {
+ if (ent->release_aggressively) {
+ consensus_cache_entry_unmap(ent);
+ } else {
+ ent->unused_since = approx_time();
+ }
+ }
+ return;
+ }
+
+ if (ent->refcnt > 0)
+ return;
+
+ /* Refcount is zero; we can free it. */
+ if (ent->map) {
+ consensus_cache_entry_unmap(ent);
+ }
+ tor_free(ent->fname);
+ config_free_lines(ent->labels);
+ consensus_cache_entry_handles_clear(ent);
+ memwipe(ent, 0, sizeof(consensus_cache_entry_t));
+ tor_free(ent);
+}
+
+/**
+ * Mark <b>ent</b> for deletion from the cache. Deletion will not occur
+ * until the cache is the only place that holds a reference to <b>ent</b>.
+ */
+void
+consensus_cache_entry_mark_for_removal(consensus_cache_entry_t *ent)
+{
+ ent->can_remove = 1;
+}
+
+/**
+ * Mark <b>ent</b> as the kind of entry that we don't need to keep mmap'd for
+ * any longer than we're actually using it.
+ */
+void
+consensus_cache_entry_mark_for_aggressive_release(consensus_cache_entry_t *ent)
+{
+ ent->release_aggressively = 1;
+}
+
+/**
+ * Try to read the body of <b>ent</b> into memory if it isn't already
+ * loaded. On success, set *<b>body_out</b> to the body, *<b>sz_out</b>
+ * to its size, and return 0. On failure return -1.
+ *
+ * The resulting body pointer will only be valid for as long as you
+ * hold a reference to <b>ent</b>.
+ */
+int
+consensus_cache_entry_get_body(const consensus_cache_entry_t *ent,
+ const uint8_t **body_out,
+ size_t *sz_out)
+{
+ if (BUG(ent->magic != CCE_MAGIC))
+ return -1; // LCOV_EXCL_LINE
+
+ if (! ent->map) {
+ if (! ent->in_cache)
+ return -1;
+
+ consensus_cache_entry_map((consensus_cache_t *)ent->in_cache,
+ (consensus_cache_entry_t *)ent);
+ if (! ent->map) {
+ return -1;
+ }
+ }
+
+ *body_out = ent->body;
+ *sz_out = ent->bodylen;
+ return 0;
+}
+
+/**
+ * Unmap every mmap'd element of <b>cache</b> that has been unused
+ * since <b>cutoff</b>.
+ */
+void
+consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff)
+{
+ SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) {
+ tor_assert_nonfatal(ent->in_cache == cache);
+ if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) {
+ /* Somebody is using this entry right now */
+ continue;
+ }
+ if (ent->unused_since > cutoff) {
+ /* Has been unused only for a little while */
+ continue;
+ }
+ if (ent->map == NULL) {
+ /* Not actually mapped. */
+ continue;
+ }
+ consensus_cache_entry_unmap(ent);
+ } SMARTLIST_FOREACH_END(ent);
+}
+
+/**
+ * Return the number of currently unused filenames available in this cache.
+ */
+int
+consensus_cache_get_n_filenames_available(consensus_cache_t *cache)
+{
+ tor_assert(cache);
+ int max = storage_dir_get_max_files(cache->dir);
+ int used = smartlist_len(storage_dir_list(cache->dir));
+ tor_assert_nonfatal(max >= used);
+ return max - used;
+}
+
+/**
+ * Delete every element of <b>cache</b> has been marked with
+ * consensus_cache_entry_mark_for_removal. If <b>force</b> is false,
+ * retain those entries which are not in use except by the cache.
+ */
+void
+consensus_cache_delete_pending(consensus_cache_t *cache, int force)
+{
+ SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) {
+ tor_assert_nonfatal(ent->in_cache == cache);
+ if (! force) {
+ if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) {
+ /* Somebody is using this entry right now */
+ continue;
+ }
+ }
+ if (ent->can_remove == 0) {
+ /* Don't want to delete this. */
+ continue;
+ }
+ if (BUG(ent->refcnt <= 0)) {
+ continue; // LCOV_EXCL_LINE
+ }
+
+ SMARTLIST_DEL_CURRENT(cache->entries, ent);
+ ent->in_cache = NULL;
+ char *fname = tor_strdup(ent->fname); /* save a copy */
+ consensus_cache_entry_decref(ent);
+ storage_dir_remove_file(cache->dir, fname);
+ tor_free(fname);
+ } SMARTLIST_FOREACH_END(ent);
+}
+
+/**
+ * Internal helper: rescan <b>cache</b> and rebuild its list of entries.
+ */
+static void
+consensus_cache_rescan(consensus_cache_t *cache)
+{
+ if (cache->entries) {
+ consensus_cache_clear(cache);
+ }
+
+ cache->entries = smartlist_new();
+ const smartlist_t *fnames = storage_dir_list(cache->dir);
+ SMARTLIST_FOREACH_BEGIN(fnames, const char *, fname) {
+ tor_mmap_t *map = NULL;
+ config_line_t *labels = NULL;
+ const uint8_t *body;
+ size_t bodylen;
+ map = storage_dir_map_labeled(cache->dir, fname,
+ &labels, &body, &bodylen);
+ if (! map) {
+ /* Can't load this; continue */
+ log_warn(LD_FS, "Unable to map file %s from consensus cache: %s",
+ escaped(fname), strerror(errno));
+ continue;
+ }
+ consensus_cache_entry_t *ent =
+ tor_malloc_zero(sizeof(consensus_cache_entry_t));
+ ent->magic = CCE_MAGIC;
+ ent->fname = tor_strdup(fname);
+ ent->labels = labels;
+ ent->refcnt = 1;
+ ent->in_cache = cache;
+ ent->unused_since = TIME_MAX;
+ smartlist_add(cache->entries, ent);
+ tor_munmap_file(map); /* don't actually need to keep this around */
+ } SMARTLIST_FOREACH_END(fname);
+}
+
+/**
+ * Make sure that <b>ent</b> is mapped into RAM.
+ */
+static void
+consensus_cache_entry_map(consensus_cache_t *cache,
+ consensus_cache_entry_t *ent)
+{
+ if (ent->map)
+ return;
+
+ ent->map = storage_dir_map_labeled(cache->dir, ent->fname,
+ NULL, &ent->body, &ent->bodylen);
+ ent->unused_since = TIME_MAX;
+}
+
+/**
+ * Unmap <b>ent</b> from RAM.
+ *
+ * Do not call this if something other than the cache is holding a reference
+ * to <b>ent</b>
+ */
+static void
+consensus_cache_entry_unmap(consensus_cache_entry_t *ent)
+{
+ ent->unused_since = TIME_MAX;
+ if (!ent->map)
+ return;
+
+ tor_munmap_file(ent->map);
+ ent->map = NULL;
+ ent->body = NULL;
+ ent->bodylen = 0;
+ ent->unused_since = TIME_MAX;
+}
+
+HANDLE_IMPL(consensus_cache_entry, consensus_cache_entry_t, )
+
+#ifdef TOR_UNIT_TESTS
+/**
+ * Testing only: Return true iff <b>ent</b> is mapped into memory.
+ *
+ * (In normal operation, this information is not exposed.)
+ */
+int
+consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent)
+{
+ if (ent->map) {
+ tor_assert(ent->body);
+ return 1;
+ } else {
+ tor_assert(!ent->body);
+ return 0;
+ }
+}
+#endif
+
diff --git a/src/or/conscache.h b/src/or/conscache.h
new file mode 100644
index 0000000000..aef54201f0
--- /dev/null
+++ b/src/or/conscache.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_CONSCACHE_H
+#define TOR_CONSCACHE_H
+
+#include "handles.h"
+
+typedef struct consensus_cache_entry_t consensus_cache_entry_t;
+typedef struct consensus_cache_t consensus_cache_t;
+
+HANDLE_DECL(consensus_cache_entry, consensus_cache_entry_t, )
+
+consensus_cache_t *consensus_cache_open(const char *subdir, int max_entries);
+void consensus_cache_free(consensus_cache_t *cache);
+struct sandbox_cfg_elem;
+int consensus_cache_register_with_sandbox(consensus_cache_t *cache,
+ struct sandbox_cfg_elem **cfg);
+void consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff);
+void consensus_cache_delete_pending(consensus_cache_t *cache,
+ int force);
+int consensus_cache_get_n_filenames_available(consensus_cache_t *cache);
+consensus_cache_entry_t *consensus_cache_add(consensus_cache_t *cache,
+ const config_line_t *labels,
+ const uint8_t *data,
+ size_t datalen);
+
+consensus_cache_entry_t *consensus_cache_find_first(
+ consensus_cache_t *cache,
+ const char *key,
+ const char *value);
+
+void consensus_cache_find_all(smartlist_t *out,
+ consensus_cache_t *cache,
+ const char *key,
+ const char *value);
+void consensus_cache_filter_list(smartlist_t *lst,
+ const char *key,
+ const char *value);
+
+const char *consensus_cache_entry_get_value(const consensus_cache_entry_t *ent,
+ const char *key);
+const config_line_t *consensus_cache_entry_get_labels(
+ const consensus_cache_entry_t *ent);
+
+void consensus_cache_entry_incref(consensus_cache_entry_t *ent);
+void consensus_cache_entry_decref(consensus_cache_entry_t *ent);
+
+void consensus_cache_entry_mark_for_removal(consensus_cache_entry_t *ent);
+void consensus_cache_entry_mark_for_aggressive_release(
+ consensus_cache_entry_t *ent);
+int consensus_cache_entry_get_body(const consensus_cache_entry_t *ent,
+ const uint8_t **body_out,
+ size_t *sz_out);
+
+#ifdef TOR_UNIT_TESTS
+int consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent);
+#endif
+
+#endif
+
diff --git a/src/or/consdiff.c b/src/or/consdiff.c
new file mode 100644
index 0000000000..1baa11897c
--- /dev/null
+++ b/src/or/consdiff.c
@@ -0,0 +1,1412 @@
+/* Copyright (c) 2014, Daniel Martí
+ * Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file consdiff.c
+ * \brief Consensus diff implementation, including both the generation and the
+ * application of diffs in a minimal ed format.
+ *
+ * The consensus diff application is done in consdiff_apply_diff, which relies
+ * on apply_ed_diff for the main ed diff part and on some digest helper
+ * functions to check the digest hashes found in the consensus diff header.
+ *
+ * The consensus diff generation is more complex. consdiff_gen_diff generates
+ * it, relying on gen_ed_diff to generate the ed diff and some digest helper
+ * functions to generate the digest hashes.
+ *
+ * gen_ed_diff is the tricky bit. In it simplest form, it will take quadratic
+ * time and linear space to generate an ed diff given two smartlists. As shown
+ * in its comment section, calling calc_changes on the entire two consensuses
+ * will calculate what is to be added and what is to be deleted in the diff.
+ * Its comment section briefly explains how it works.
+ *
+ * In our case specific to consensuses, we take advantage of the fact that
+ * consensuses list routers sorted by their identities. We use that
+ * information to avoid running calc_changes on the whole smartlists.
+ * gen_ed_diff will navigate through the two consensuses identity by identity
+ * and will send small couples of slices to calc_changes, keeping the running
+ * time near-linear. This is explained in more detail in the gen_ed_diff
+ * comments.
+ *
+ * The allocation strategy tries to save time and memory by avoiding needless
+ * copies. Instead of actually splitting the inputs into separate strings, we
+ * allocate cdline_t objects, each of which represents a line in the original
+ * object or in the output. We use memarea_t allocators to manage the
+ * temporary memory we use when generating or applying diffs.
+ **/
+
+#define CONSDIFF_PRIVATE
+
+#include "or.h"
+#include "consdiff.h"
+#include "memarea.h"
+#include "routerparse.h"
+
+static const char* ns_diff_version = "network-status-diff-version 1";
+static const char* hash_token = "hash";
+
+static char *consensus_join_lines(const smartlist_t *inp);
+
+/** Return true iff a and b have the same contents. */
+STATIC int
+lines_eq(const cdline_t *a, const cdline_t *b)
+{
+ return a->len == b->len && fast_memeq(a->s, b->s, a->len);
+}
+
+/** Return true iff a has the same contents as the nul-terminated string b. */
+STATIC int
+line_str_eq(const cdline_t *a, const char *b)
+{
+ const size_t len = strlen(b);
+ tor_assert(len <= UINT32_MAX);
+ cdline_t bline = { b, (uint32_t)len };
+ return lines_eq(a, &bline);
+}
+
+/** Return true iff a begins with the same contents as the nul-terminated
+ * string b. */
+static int
+line_starts_with_str(const cdline_t *a, const char *b)
+{
+ const size_t len = strlen(b);
+ tor_assert(len <= UINT32_MAX);
+ return a->len >= len && fast_memeq(a->s, b, len);
+}
+
+/** Return a new cdline_t holding as its contents the nul-terminated
+ * string s. Use the provided memory area for storage. */
+static cdline_t *
+cdline_linecpy(memarea_t *area, const char *s)
+{
+ size_t len = strlen(s);
+ const char *ss = memarea_memdup(area, s, len);
+ cdline_t *line = memarea_alloc(area, sizeof(cdline_t));
+ line->s = ss;
+ line->len = (uint32_t)len;
+ return line;
+}
+
+/** Add a cdline_t to <b>lst</b> holding as its contents the nul-terminated
+ * string s. Use the provided memory area for storage. */
+STATIC void
+smartlist_add_linecpy(smartlist_t *lst, memarea_t *area, const char *s)
+{
+ smartlist_add(lst, cdline_linecpy(area, s));
+}
+
+/** Compute the digest of <b>cons</b>, and store the result in
+ * <b>digest_out</b>. Return 0 on success, -1 on failure. */
+/* This is a separate, mockable function so that we can override it when
+ * fuzzing. */
+MOCK_IMPL(STATIC int,
+consensus_compute_digest,(const char *cons,
+ consensus_digest_t *digest_out))
+{
+ int r = crypto_digest256((char*)digest_out->sha3_256,
+ cons, strlen(cons), DIGEST_SHA3_256);
+ return r;
+}
+
+/** Compute the digest-as-signed of <b>cons</b>, and store the result in
+ * <b>digest_out</b>. Return 0 on success, -1 on failure. */
+/* This is a separate, mockable function so that we can override it when
+ * fuzzing. */
+MOCK_IMPL(STATIC int,
+consensus_compute_digest_as_signed,(const char *cons,
+ consensus_digest_t *digest_out))
+{
+ return router_get_networkstatus_v3_sha3_as_signed(digest_out->sha3_256,
+ cons);
+}
+
+/** Return true iff <b>d1</b> and <b>d2</b> contain the same digest */
+/* This is a separate, mockable function so that we can override it when
+ * fuzzing. */
+MOCK_IMPL(STATIC int,
+consensus_digest_eq,(const uint8_t *d1,
+ const uint8_t *d2))
+{
+ return fast_memeq(d1, d2, DIGEST256_LEN);
+}
+
+/** Create (allocate) a new slice from a smartlist. Assumes that the start
+ * and the end indexes are within the bounds of the initial smartlist. The end
+ * element is not part of the resulting slice. If end is -1, the slice is to
+ * reach the end of the smartlist.
+ */
+STATIC smartlist_slice_t *
+smartlist_slice(const smartlist_t *list, int start, int end)
+{
+ int list_len = smartlist_len(list);
+ tor_assert(start >= 0);
+ tor_assert(start <= list_len);
+ if (end == -1) {
+ end = list_len;
+ }
+ tor_assert(start <= end);
+
+ smartlist_slice_t *slice = tor_malloc(sizeof(smartlist_slice_t));
+ slice->list = list;
+ slice->offset = start;
+ slice->len = end - start;
+ return slice;
+}
+
+/** Helper: Compute the longest common subsequence lengths for the two slices.
+ * Used as part of the diff generation to find the column at which to split
+ * slice2 while still having the optimal solution.
+ * If direction is -1, the navigation is reversed. Otherwise it must be 1.
+ * The length of the resulting integer array is that of the second slice plus
+ * one.
+ */
+STATIC int *
+lcs_lengths(const smartlist_slice_t *slice1, const smartlist_slice_t *slice2,
+ int direction)
+{
+ size_t a_size = sizeof(int) * (slice2->len+1);
+
+ /* Resulting lcs lengths. */
+ int *result = tor_malloc_zero(a_size);
+ /* Copy of the lcs lengths from the last iteration. */
+ int *prev = tor_malloc(a_size);
+
+ tor_assert(direction == 1 || direction == -1);
+
+ int si = slice1->offset;
+ if (direction == -1) {
+ si += (slice1->len-1);
+ }
+
+ for (int i = 0; i < slice1->len; ++i, si+=direction) {
+
+ const cdline_t *line1 = smartlist_get(slice1->list, si);
+ /* Store the last results. */
+ memcpy(prev, result, a_size);
+
+ int sj = slice2->offset;
+ if (direction == -1) {
+ sj += (slice2->len-1);
+ }
+
+ for (int j = 0; j < slice2->len; ++j, sj+=direction) {
+
+ const cdline_t *line2 = smartlist_get(slice2->list, sj);
+ if (lines_eq(line1, line2)) {
+ /* If the lines are equal, the lcs is one line longer. */
+ result[j + 1] = prev[j] + 1;
+ } else {
+ /* If not, see what lcs parent path is longer. */
+ result[j + 1] = MAX(result[j], prev[j + 1]);
+ }
+ }
+ }
+ tor_free(prev);
+ return result;
+}
+
+/** Helper: Trim any number of lines that are equally at the start or the end
+ * of both slices.
+ */
+STATIC void
+trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2)
+{
+ while (slice1->len>0 && slice2->len>0) {
+ const cdline_t *line1 = smartlist_get(slice1->list, slice1->offset);
+ const cdline_t *line2 = smartlist_get(slice2->list, slice2->offset);
+ if (!lines_eq(line1, line2)) {
+ break;
+ }
+ slice1->offset++; slice1->len--;
+ slice2->offset++; slice2->len--;
+ }
+
+ int i1 = (slice1->offset+slice1->len)-1;
+ int i2 = (slice2->offset+slice2->len)-1;
+
+ while (slice1->len>0 && slice2->len>0) {
+ const cdline_t *line1 = smartlist_get(slice1->list, i1);
+ const cdline_t *line2 = smartlist_get(slice2->list, i2);
+ if (!lines_eq(line1, line2)) {
+ break;
+ }
+ i1--;
+ slice1->len--;
+ i2--;
+ slice2->len--;
+ }
+}
+
+/** Like smartlist_string_pos, but uses a cdline_t, and is restricted to the
+ * bounds of the slice.
+ */
+STATIC int
+smartlist_slice_string_pos(const smartlist_slice_t *slice,
+ const cdline_t *string)
+{
+ int end = slice->offset + slice->len;
+ for (int i = slice->offset; i < end; ++i) {
+ const cdline_t *el = smartlist_get(slice->list, i);
+ if (lines_eq(el, string)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/** Helper: Set all the appropriate changed booleans to true. The first slice
+ * must be of length 0 or 1. All the lines of slice1 and slice2 which are not
+ * present in the other slice will be set to changed in their bool array.
+ * The two changed bool arrays are passed in the same order as the slices.
+ */
+STATIC void
+set_changed(bitarray_t *changed1, bitarray_t *changed2,
+ const smartlist_slice_t *slice1, const smartlist_slice_t *slice2)
+{
+ int toskip = -1;
+ tor_assert(slice1->len == 0 || slice1->len == 1);
+
+ if (slice1->len == 1) {
+ const cdline_t *line_common = smartlist_get(slice1->list, slice1->offset);
+ toskip = smartlist_slice_string_pos(slice2, line_common);
+ if (toskip == -1) {
+ bitarray_set(changed1, slice1->offset);
+ }
+ }
+ int end = slice2->offset + slice2->len;
+ for (int i = slice2->offset; i < end; ++i) {
+ if (i != toskip) {
+ bitarray_set(changed2, i);
+ }
+ }
+}
+
+/*
+ * Helper: Given that slice1 has been split by half into top and bot, we want
+ * to fetch the column at which to split slice2 so that we are still on track
+ * to the optimal diff solution, i.e. the shortest one. We use lcs_lengths
+ * since the shortest diff is just another way to say the longest common
+ * subsequence.
+ */
+static int
+optimal_column_to_split(const smartlist_slice_t *top,
+ const smartlist_slice_t *bot,
+ const smartlist_slice_t *slice2)
+{
+ int *lens_top = lcs_lengths(top, slice2, 1);
+ int *lens_bot = lcs_lengths(bot, slice2, -1);
+ int column=0, max_sum=-1;
+
+ for (int i = 0; i < slice2->len+1; ++i) {
+ int sum = lens_top[i] + lens_bot[slice2->len-i];
+ if (sum > max_sum) {
+ column = i;
+ max_sum = sum;
+ }
+ }
+ tor_free(lens_top);
+ tor_free(lens_bot);
+
+ return column;
+}
+
+/**
+ * Helper: Figure out what elements are new or gone on the second smartlist
+ * relative to the first smartlist, and store the booleans in the bitarrays.
+ * True on the first bitarray means the element is gone, true on the second
+ * bitarray means it's new.
+ *
+ * In its base case, either of the smartlists is of length <= 1 and we can
+ * quickly see what elements are new or are gone. In the other case, we will
+ * split one smartlist by half and we'll use optimal_column_to_split to find
+ * the optimal column at which to split the second smartlist so that we are
+ * finding the smallest diff possible.
+ */
+STATIC void
+calc_changes(smartlist_slice_t *slice1,
+ smartlist_slice_t *slice2,
+ bitarray_t *changed1, bitarray_t *changed2)
+{
+ trim_slices(slice1, slice2);
+
+ if (slice1->len <= 1) {
+ set_changed(changed1, changed2, slice1, slice2);
+
+ } else if (slice2->len <= 1) {
+ set_changed(changed2, changed1, slice2, slice1);
+
+ /* Keep on splitting the slices in two. */
+ } else {
+ smartlist_slice_t *top, *bot, *left, *right;
+
+ /* Split the first slice in half. */
+ int mid = slice1->len/2;
+ top = smartlist_slice(slice1->list, slice1->offset, slice1->offset+mid);
+ bot = smartlist_slice(slice1->list, slice1->offset+mid,
+ slice1->offset+slice1->len);
+
+ /* Split the second slice by the optimal column. */
+ int mid2 = optimal_column_to_split(top, bot, slice2);
+ left = smartlist_slice(slice2->list, slice2->offset, slice2->offset+mid2);
+ right = smartlist_slice(slice2->list, slice2->offset+mid2,
+ slice2->offset+slice2->len);
+
+ calc_changes(top, left, changed1, changed2);
+ calc_changes(bot, right, changed1, changed2);
+ tor_free(top);
+ tor_free(bot);
+ tor_free(left);
+ tor_free(right);
+ }
+}
+
+/* This table is from crypto.c. The SP and PAD defines are different. */
+#define NOT_VALID_BASE64 255
+#define X NOT_VALID_BASE64
+#define SP NOT_VALID_BASE64
+#define PAD NOT_VALID_BASE64
+static const uint8_t base64_compare_table[256] = {
+ X, X, X, X, X, X, X, X, X, SP, SP, SP, X, SP, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ SP, X, X, X, X, X, X, X, X, X, X, 62, X, X, X, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, X, X, X, PAD, X, X,
+ X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, X, X, X, X, X,
+ X, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,
+};
+
+/** Helper: Get the identity hash from a router line, assuming that the line
+ * at least appears to be a router line and thus starts with "r ".
+ *
+ * If an identity hash is found, store it (without decoding it) in
+ * <b>hash_out</b>, and return 0. On failure, return -1.
+ */
+STATIC int
+get_id_hash(const cdline_t *line, cdline_t *hash_out)
+{
+ if (line->len < 2)
+ return -1;
+
+ /* Skip the router name. */
+ const char *hash = memchr(line->s + 2, ' ', line->len - 2);
+ if (!hash) {
+ return -1;
+ }
+
+ hash++;
+ const char *hash_end = hash;
+ /* Stop when the first non-base64 character is found. Use unsigned chars to
+ * avoid negative indexes causing crashes.
+ */
+ while (base64_compare_table[*((unsigned char*)hash_end)]
+ != NOT_VALID_BASE64 &&
+ hash_end < line->s + line->len) {
+ hash_end++;
+ }
+
+ /* Empty hash. */
+ if (hash_end == hash) {
+ return -1;
+ }
+
+ hash_out->s = hash;
+ /* Always true because lines are limited to this length */
+ tor_assert(hash_end >= hash);
+ tor_assert((size_t)(hash_end - hash) <= UINT32_MAX);
+ hash_out->len = (uint32_t)(hash_end - hash);
+
+ return 0;
+}
+
+/** Helper: Check that a line is a valid router entry. We must at least be
+ * able to fetch a proper identity hash from it for it to be valid.
+ */
+STATIC int
+is_valid_router_entry(const cdline_t *line)
+{
+ if (line->len < 2 || fast_memneq(line->s, "r ", 2))
+ return 0;
+ cdline_t tmp;
+ return (get_id_hash(line, &tmp) == 0);
+}
+
+/** Helper: Find the next router line starting at the current position.
+ * Assumes that cur is lower than the length of the smartlist, i.e. it is a
+ * line within the bounds of the consensus. The only exception is when we
+ * don't want to skip the first line, in which case cur will be -1.
+ */
+STATIC int
+next_router(const smartlist_t *cons, int cur)
+{
+ int len = smartlist_len(cons);
+ tor_assert(cur >= -1 && cur < len);
+
+ if (++cur >= len) {
+ return len;
+ }
+
+ const cdline_t *line = smartlist_get(cons, cur);
+ while (!is_valid_router_entry(line)) {
+ if (++cur >= len) {
+ return len;
+ }
+ line = smartlist_get(cons, cur);
+ }
+ return cur;
+}
+
+/** Helper: compare two base64-encoded identity hashes, which may be of
+ * different lengths. Comparison ends when the first non-base64 char is found.
+ */
+STATIC int
+base64cmp(const cdline_t *hash1, const cdline_t *hash2)
+{
+ /* NULL is always lower, useful for last_hash which starts at NULL. */
+ if (!hash1->s && !hash2->s) {
+ return 0;
+ }
+ if (!hash1->s) {
+ return -1;
+ }
+ if (!hash2->s) {
+ return 1;
+ }
+
+ /* Don't index with a char; char may be signed. */
+ const unsigned char *a = (unsigned char*)hash1->s;
+ const unsigned char *b = (unsigned char*)hash2->s;
+ const unsigned char *a_end = a + hash1->len;
+ const unsigned char *b_end = b + hash2->len;
+ while (1) {
+ uint8_t av = base64_compare_table[*a];
+ uint8_t bv = base64_compare_table[*b];
+ if (av == NOT_VALID_BASE64) {
+ if (bv == NOT_VALID_BASE64) {
+ /* Both ended with exactly the same characters. */
+ return 0;
+ } else {
+ /* hash2 goes on longer than hash1 and thus hash1 is lower. */
+ return -1;
+ }
+ } else if (bv == NOT_VALID_BASE64) {
+ /* hash1 goes on longer than hash2 and thus hash1 is greater. */
+ return 1;
+ } else if (av < bv) {
+ /* The first difference shows that hash1 is lower. */
+ return -1;
+ } else if (av > bv) {
+ /* The first difference shows that hash1 is greater. */
+ return 1;
+ } else {
+ a++;
+ b++;
+ if (a == a_end) {
+ if (b == b_end) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else if (b == b_end) {
+ return 1;
+ }
+ }
+ }
+}
+
+/** Structure used to remember the previous and current identity hash of
+ * the "r " lines in a consensus, to enforce well-ordering. */
+typedef struct router_id_iterator_t {
+ cdline_t last_hash;
+ cdline_t hash;
+} router_id_iterator_t;
+
+/**
+ * Initializer for a router_id_iterator_t.
+ */
+#define ROUTER_ID_ITERATOR_INIT { { NULL, 0 }, { NULL, 0 } }
+
+/** Given an index *<b>idxp</b> into the consensus at <b>cons</b>, advance
+ * the index to the next router line ("r ...") in the consensus, or to
+ * an index one after the end of the list if there is no such line.
+ *
+ * Use <b>iter</b> to record the hash of the found router line, if any,
+ * and to enforce ordering on the hashes. If the hashes are mis-ordered,
+ * return -1. Else, return 0.
+ **/
+static int
+find_next_router_line(const smartlist_t *cons,
+ const char *consname,
+ int *idxp,
+ router_id_iterator_t *iter)
+{
+ *idxp = next_router(cons, *idxp);
+ if (*idxp < smartlist_len(cons)) {
+ memcpy(&iter->last_hash, &iter->hash, sizeof(cdline_t));
+ if (get_id_hash(smartlist_get(cons, *idxp), &iter->hash) < 0 ||
+ base64cmp(&iter->hash, &iter->last_hash) <= 0) {
+ log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because "
+ "the %s consensus doesn't have its router entries sorted "
+ "properly.", consname);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/** Line-prefix indicating the beginning of the signatures section that we
+ * intend to delete. */
+#define START_OF_SIGNATURES_SECTION "directory-signature "
+
+/** Pre-process a consensus in <b>cons</b> (represented as a list of cdline_t)
+ * to remove the signatures from it. If the footer is removed, return a
+ * cdline_t containing a delete command to delete the footer, allocated in
+ * <b>area</>. If no footer is removed, return NULL.
+ *
+ * We remove the signatures here because they are not themselves signed, and
+ * as such there might be different encodings for them.
+ */
+static cdline_t *
+preprocess_consensus(memarea_t *area,
+ smartlist_t *cons)
+{
+ int idx;
+ int dirsig_idx = -1;
+ for (idx = 0; idx < smartlist_len(cons); ++idx) {
+ cdline_t *line = smartlist_get(cons, idx);
+ if (line_starts_with_str(line, START_OF_SIGNATURES_SECTION)) {
+ dirsig_idx = idx;
+ break;
+ }
+ }
+ if (dirsig_idx >= 0) {
+ char buf[64];
+ while (smartlist_len(cons) > dirsig_idx)
+ smartlist_del(cons, dirsig_idx);
+ tor_snprintf(buf, sizeof(buf), "%d,$d", dirsig_idx+1);
+ return cdline_linecpy(area, buf);
+ } else {
+ return NULL;
+ }
+}
+
+/** Generate an ed diff as a smartlist from two consensuses, also given as
+ * smartlists. Will return NULL if the diff could not be generated, which can
+ * happen if any lines the script had to add matched "." or if the routers
+ * were not properly ordered.
+ *
+ * All cdline_t objects in the resulting object are either references to lines
+ * in one of the inputs, or are newly allocated lines in the provided memarea.
+ *
+ * This implementation is consensus-specific. To generate an ed diff for any
+ * given input in quadratic time, you can replace all the code until the
+ * navigation in reverse order with the following:
+ *
+ * int len1 = smartlist_len(cons1);
+ * int len2 = smartlist_len(cons2);
+ * bitarray_t *changed1 = bitarray_init_zero(len1);
+ * bitarray_t *changed2 = bitarray_init_zero(len2);
+ * cons1_sl = smartlist_slice(cons1, 0, -1);
+ * cons2_sl = smartlist_slice(cons2, 0, -1);
+ * calc_changes(cons1_sl, cons2_sl, changed1, changed2);
+ */
+STATIC smartlist_t *
+gen_ed_diff(const smartlist_t *cons1_orig, const smartlist_t *cons2,
+ memarea_t *area)
+{
+ smartlist_t *cons1 = smartlist_new();
+ smartlist_add_all(cons1, cons1_orig);
+ cdline_t *remove_trailer = preprocess_consensus(area, cons1);
+
+ int len1 = smartlist_len(cons1);
+ int len2 = smartlist_len(cons2);
+ smartlist_t *result = smartlist_new();
+
+ if (remove_trailer) {
+ /* There's a delete-the-trailer line at the end, so add it here. */
+ smartlist_add(result, remove_trailer);
+ }
+
+ /* Initialize the changed bitarrays to zero, so that calc_changes only needs
+ * to set the ones that matter and leave the rest untouched.
+ */
+ bitarray_t *changed1 = bitarray_init_zero(len1);
+ bitarray_t *changed2 = bitarray_init_zero(len2);
+ int i1=-1, i2=-1;
+ int start1=0, start2=0;
+
+ /* To check that hashes are ordered properly */
+ router_id_iterator_t iter1 = ROUTER_ID_ITERATOR_INIT;
+ router_id_iterator_t iter2 = ROUTER_ID_ITERATOR_INIT;
+
+ /* i1 and i2 are initialized at the first line of each consensus. They never
+ * reach past len1 and len2 respectively, since next_router doesn't let that
+ * happen. i1 and i2 are advanced by at least one line at each iteration as
+ * long as they have not yet reached len1 and len2, so the loop is
+ * guaranteed to end, and each pair of (i1,i2) will be inspected at most
+ * once.
+ */
+ while (i1 < len1 || i2 < len2) {
+
+ /* Advance each of the two navigation positions by one router entry if not
+ * yet at the end.
+ */
+ if (i1 < len1) {
+ if (find_next_router_line(cons1, "base", &i1, &iter1) < 0) {
+ goto error_cleanup;
+ }
+ }
+
+ if (i2 < len2) {
+ if (find_next_router_line(cons2, "target", &i2, &iter2) < 0) {
+ goto error_cleanup;
+ }
+ }
+
+ /* If we have reached the end of both consensuses, there is no need to
+ * compare hashes anymore, since this is the last iteration.
+ */
+ if (i1 < len1 || i2 < len2) {
+
+ /* Keep on advancing the lower (by identity hash sorting) position until
+ * we have two matching positions. The only other possible outcome is
+ * that a lower position reaches the end of the consensus before it can
+ * reach a hash that is no longer the lower one. Since there will always
+ * be a lower hash for as long as the loop runs, one of the two indexes
+ * will always be incremented, thus assuring that the loop must end
+ * after a finite number of iterations. If that cannot be because said
+ * consensus has already reached the end, both are extended to their
+ * respecting ends since we are done.
+ */
+ int cmp = base64cmp(&iter1.hash, &iter2.hash);
+ while (cmp != 0) {
+ if (i1 < len1 && cmp < 0) {
+ if (find_next_router_line(cons1, "base", &i1, &iter1) < 0) {
+ goto error_cleanup;
+ }
+ if (i1 == len1) {
+ /* We finished the first consensus, so grab all the remaining
+ * lines of the second consensus and finish up.
+ */
+ i2 = len2;
+ break;
+ }
+ } else if (i2 < len2 && cmp > 0) {
+ if (find_next_router_line(cons2, "target", &i2, &iter2) < 0) {
+ goto error_cleanup;
+ }
+ if (i2 == len2) {
+ /* We finished the second consensus, so grab all the remaining
+ * lines of the first consensus and finish up.
+ */
+ i1 = len1;
+ break;
+ }
+ } else {
+ i1 = len1;
+ i2 = len2;
+ break;
+ }
+ cmp = base64cmp(&iter1.hash, &iter2.hash);
+ }
+ }
+
+ /* Make slices out of these chunks (up to the common router entry) and
+ * calculate the changes for them.
+ * Error if any of the two slices are longer than 10K lines. That should
+ * never happen with any pair of real consensuses. Feeding more than 10K
+ * lines to calc_changes would be very slow anyway.
+ */
+#define MAX_LINE_COUNT (10000)
+ if (i1-start1 > MAX_LINE_COUNT || i2-start2 > MAX_LINE_COUNT) {
+ log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because "
+ "we found too few common router ids.");
+ goto error_cleanup;
+ }
+
+ smartlist_slice_t *cons1_sl = smartlist_slice(cons1, start1, i1);
+ smartlist_slice_t *cons2_sl = smartlist_slice(cons2, start2, i2);
+ calc_changes(cons1_sl, cons2_sl, changed1, changed2);
+ tor_free(cons1_sl);
+ tor_free(cons2_sl);
+ start1 = i1, start2 = i2;
+ }
+
+ /* Navigate the changes in reverse order and generate one ed command for
+ * each chunk of changes.
+ */
+ i1=len1-1, i2=len2-1;
+ char buf[128];
+ while (i1 >= 0 || i2 >= 0) {
+
+ int start1x, start2x, end1, end2, added, deleted;
+
+ /* We are at a point were no changed bools are true, so just keep going. */
+ if (!(i1 >= 0 && bitarray_is_set(changed1, i1)) &&
+ !(i2 >= 0 && bitarray_is_set(changed2, i2))) {
+ if (i1 >= 0) {
+ i1--;
+ }
+ if (i2 >= 0) {
+ i2--;
+ }
+ continue;
+ }
+
+ end1 = i1, end2 = i2;
+
+ /* Grab all contiguous changed lines */
+ while (i1 >= 0 && bitarray_is_set(changed1, i1)) {
+ i1--;
+ }
+ while (i2 >= 0 && bitarray_is_set(changed2, i2)) {
+ i2--;
+ }
+
+ start1x = i1+1, start2x = i2+1;
+ added = end2-i2, deleted = end1-i1;
+
+ if (added == 0) {
+ if (deleted == 1) {
+ tor_snprintf(buf, sizeof(buf), "%id", start1x+1);
+ smartlist_add_linecpy(result, area, buf);
+ } else {
+ tor_snprintf(buf, sizeof(buf), "%i,%id", start1x+1, start1x+deleted);
+ smartlist_add_linecpy(result, area, buf);
+ }
+ } else {
+ int i;
+ if (deleted == 0) {
+ tor_snprintf(buf, sizeof(buf), "%ia", start1x);
+ smartlist_add_linecpy(result, area, buf);
+ } else if (deleted == 1) {
+ tor_snprintf(buf, sizeof(buf), "%ic", start1x+1);
+ smartlist_add_linecpy(result, area, buf);
+ } else {
+ tor_snprintf(buf, sizeof(buf), "%i,%ic", start1x+1, start1x+deleted);
+ smartlist_add_linecpy(result, area, buf);
+ }
+
+ for (i = start2x; i <= end2; ++i) {
+ cdline_t *line = smartlist_get(cons2, i);
+ if (line_str_eq(line, ".")) {
+ log_warn(LD_CONSDIFF, "Cannot generate consensus diff because "
+ "one of the lines to be added is \".\".");
+ goto error_cleanup;
+ }
+ smartlist_add(result, line);
+ }
+ smartlist_add_linecpy(result, area, ".");
+ }
+ }
+
+ smartlist_free(cons1);
+ bitarray_free(changed1);
+ bitarray_free(changed2);
+
+ return result;
+
+ error_cleanup:
+
+ smartlist_free(cons1);
+ bitarray_free(changed1);
+ bitarray_free(changed2);
+
+ smartlist_free(result);
+
+ return NULL;
+}
+
+/* Helper: Read a base-10 number between 0 and INT32_MAX from <b>s</b> and
+ * store it in <b>num_out</b>. Advance <b>s</b> to the characer immediately
+ * after the number. Return 0 on success, -1 on failure. */
+static int
+get_linenum(const char **s, int *num_out)
+{
+ int ok;
+ char *next;
+ if (!TOR_ISDIGIT(**s)) {
+ return -1;
+ }
+ *num_out = (int) tor_parse_long(*s, 10, 0, INT32_MAX, &ok, &next);
+ if (ok && next) {
+ *s = next;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/** Apply the ed diff, starting at <b>diff_starting_line</b>, to the consensus
+ * and return a new consensus, also as a line-based smartlist. Will return
+ * NULL if the ed diff is not properly formatted.
+ *
+ * All cdline_t objects in the resulting object are references to lines
+ * in one of the inputs; nothing is copied.
+ */
+STATIC smartlist_t *
+apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
+ int diff_starting_line)
+{
+ int diff_len = smartlist_len(diff);
+ int j = smartlist_len(cons1);
+ smartlist_t *cons2 = smartlist_new();
+
+ for (int i=diff_starting_line; i<diff_len; ++i) {
+ const cdline_t *diff_cdline = smartlist_get(diff, i);
+ char diff_line[128];
+
+ if (diff_cdline->len > sizeof(diff_line) - 1) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an ed command was far too long");
+ goto error_cleanup;
+ }
+ /* Copy the line to make it nul-terminated. */
+ memcpy(diff_line, diff_cdline->s, diff_cdline->len);
+ diff_line[diff_cdline->len] = 0;
+ const char *ptr = diff_line;
+ int start = 0, end = 0;
+ int had_range = 0;
+ int end_was_eof = 0;
+ if (get_linenum(&ptr, &start) < 0) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an ed command was missing a line number.");
+ goto error_cleanup;
+ }
+ if (*ptr == ',') {
+ /* Two-item range */
+ had_range = 1;
+ ++ptr;
+ if (*ptr == '$') {
+ end_was_eof = 1;
+ end = smartlist_len(cons1);
+ ++ptr;
+ } else if (get_linenum(&ptr, &end) < 0) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an ed command was missing a range end line number.");
+ goto error_cleanup;
+ }
+ /* Incoherent range. */
+ if (end <= start) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an invalid range was found in an ed command.");
+ goto error_cleanup;
+ }
+ } else {
+ /* We'll take <n1> as <n1>,<n1> for simplicity. */
+ end = start;
+ }
+
+ if (end > j) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "its commands are not properly sorted in reverse order.");
+ goto error_cleanup;
+ }
+
+ if (*ptr == '\0') {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "a line with no ed command was found");
+ goto error_cleanup;
+ }
+
+ if (*(ptr+1) != '\0') {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an ed command longer than one char was found.");
+ goto error_cleanup;
+ }
+
+ char action = *ptr;
+
+ switch (action) {
+ case 'a':
+ case 'c':
+ case 'd':
+ break;
+ default:
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "an unrecognised ed command was found.");
+ goto error_cleanup;
+ }
+
+ /** $ is not allowed with non-d actions. */
+ if (end_was_eof && action != 'd') {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "it wanted to use $ with a command other than delete");
+ goto error_cleanup;
+ }
+
+ /* 'a' commands are not allowed to have ranges. */
+ if (had_range && action == 'a') {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "it wanted to add lines after a range.");
+ goto error_cleanup;
+ }
+
+ /* Add unchanged lines. */
+ for (; j && j > end; --j) {
+ cdline_t *cons_line = smartlist_get(cons1, j-1);
+ smartlist_add(cons2, cons_line);
+ }
+
+ /* Ignore removed lines. */
+ if (action == 'c' || action == 'd') {
+ while (--j >= start) {
+ /* Skip line */
+ }
+ }
+
+ /* Add new lines in reverse order, since it will all be reversed at the
+ * end.
+ */
+ if (action == 'a' || action == 'c') {
+ int added_end = i;
+
+ i++; /* Skip the line with the range and command. */
+ while (i < diff_len) {
+ if (line_str_eq(smartlist_get(diff, i), ".")) {
+ break;
+ }
+ if (++i == diff_len) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "it has lines to be inserted that don't end with a \".\".");
+ goto error_cleanup;
+ }
+ }
+
+ int added_i = i-1;
+
+ /* It would make no sense to add zero new lines. */
+ if (added_i == added_end) {
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
+ "it has an ed command that tries to insert zero lines.");
+ goto error_cleanup;
+ }
+
+ while (added_i > added_end) {
+ cdline_t *added_line = smartlist_get(diff, added_i--);
+ smartlist_add(cons2, added_line);
+ }
+ }
+ }
+
+ /* Add remaining unchanged lines. */
+ for (; j > 0; --j) {
+ cdline_t *cons_line = smartlist_get(cons1, j-1);
+ smartlist_add(cons2, cons_line);
+ }
+
+ /* Reverse the whole thing since we did it from the end. */
+ smartlist_reverse(cons2);
+ return cons2;
+
+ error_cleanup:
+
+ smartlist_free(cons2);
+
+ return NULL;
+}
+
+/** Generate a consensus diff as a smartlist from two given consensuses, also
+ * as smartlists. Will return NULL if the consensus diff could not be
+ * generated. Neither of the two consensuses are modified in any way, so it's
+ * up to the caller to free their resources.
+ */
+smartlist_t *
+consdiff_gen_diff(const smartlist_t *cons1,
+ const smartlist_t *cons2,
+ const consensus_digest_t *digests1,
+ const consensus_digest_t *digests2,
+ memarea_t *area)
+{
+ smartlist_t *ed_diff = gen_ed_diff(cons1, cons2, area);
+ /* ed diff could not be generated - reason already logged by gen_ed_diff. */
+ if (!ed_diff) {
+ goto error_cleanup;
+ }
+
+ /* See that the script actually produces what we want. */
+ smartlist_t *ed_cons2 = apply_ed_diff(cons1, ed_diff, 0);
+ if (!ed_cons2) {
+ /* LCOV_EXCL_START -- impossible if diff generation is correct */
+ log_warn(LD_BUG|LD_CONSDIFF, "Refusing to generate consensus diff because "
+ "the generated ed diff could not be tested to successfully generate "
+ "the target consensus.");
+ goto error_cleanup;
+ /* LCOV_EXCL_STOP */
+ }
+
+ int cons2_eq = 1;
+ if (smartlist_len(cons2) == smartlist_len(ed_cons2)) {
+ SMARTLIST_FOREACH_BEGIN(cons2, const cdline_t *, line1) {
+ const cdline_t *line2 = smartlist_get(ed_cons2, line1_sl_idx);
+ if (! lines_eq(line1, line2) ) {
+ cons2_eq = 0;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(line1);
+ } else {
+ cons2_eq = 0;
+ }
+ smartlist_free(ed_cons2);
+ if (!cons2_eq) {
+ /* LCOV_EXCL_START -- impossible if diff generation is correct. */
+ log_warn(LD_BUG|LD_CONSDIFF, "Refusing to generate consensus diff because "
+ "the generated ed diff did not generate the target consensus "
+ "successfully when tested.");
+ goto error_cleanup;
+ /* LCOV_EXCL_STOP */
+ }
+
+ char cons1_hash_hex[HEX_DIGEST256_LEN+1];
+ char cons2_hash_hex[HEX_DIGEST256_LEN+1];
+ base16_encode(cons1_hash_hex, HEX_DIGEST256_LEN+1,
+ (const char*)digests1->sha3_256, DIGEST256_LEN);
+ base16_encode(cons2_hash_hex, HEX_DIGEST256_LEN+1,
+ (const char*)digests2->sha3_256, DIGEST256_LEN);
+
+ /* Create the resulting consensus diff. */
+ char buf[160];
+ smartlist_t *result = smartlist_new();
+ tor_snprintf(buf, sizeof(buf), "%s", ns_diff_version);
+ smartlist_add_linecpy(result, area, buf);
+ tor_snprintf(buf, sizeof(buf), "%s %s %s", hash_token,
+ cons1_hash_hex, cons2_hash_hex);
+ smartlist_add_linecpy(result, area, buf);
+ smartlist_add_all(result, ed_diff);
+ smartlist_free(ed_diff);
+ return result;
+
+ error_cleanup:
+
+ if (ed_diff) {
+ /* LCOV_EXCL_START -- ed_diff is NULL except in unreachable cases above */
+ smartlist_free(ed_diff);
+ /* LCOV_EXCL_STOP */
+ }
+
+ return NULL;
+}
+
+/** Fetch the digest of the base consensus in the consensus diff, encoded in
+ * base16 as found in the diff itself. digest1_out and digest2_out must be of
+ * length DIGEST256_LEN or larger if not NULL.
+ */
+int
+consdiff_get_digests(const smartlist_t *diff,
+ char *digest1_out,
+ char *digest2_out)
+{
+ smartlist_t *hash_words = NULL;
+ const cdline_t *format;
+ char cons1_hash[DIGEST256_LEN], cons2_hash[DIGEST256_LEN];
+ char *cons1_hash_hex, *cons2_hash_hex;
+ if (smartlist_len(diff) < 2) {
+ log_info(LD_CONSDIFF, "The provided consensus diff is too short.");
+ goto error_cleanup;
+ }
+
+ /* Check that it's the format and version we know. */
+ format = smartlist_get(diff, 0);
+ if (!line_str_eq(format, ns_diff_version)) {
+ log_warn(LD_CONSDIFF, "The provided consensus diff format is not known.");
+ goto error_cleanup;
+ }
+
+ /* Grab the base16 digests. */
+ hash_words = smartlist_new();
+ {
+ const cdline_t *line2 = smartlist_get(diff, 1);
+ char *h = tor_memdup_nulterm(line2->s, line2->len);
+ smartlist_split_string(hash_words, h, " ", 0, 0);
+ tor_free(h);
+ }
+
+ /* There have to be three words, the first of which must be hash_token. */
+ if (smartlist_len(hash_words) != 3 ||
+ strcmp(smartlist_get(hash_words, 0), hash_token)) {
+ log_info(LD_CONSDIFF, "The provided consensus diff does not include "
+ "the necessary digests.");
+ goto error_cleanup;
+ }
+
+ /* Expected hashes as found in the consensus diff header. They must be of
+ * length HEX_DIGEST256_LEN, normally 64 hexadecimal characters.
+ * If any of the decodings fail, error to make sure that the hashes are
+ * proper base16-encoded digests.
+ */
+ cons1_hash_hex = smartlist_get(hash_words, 1);
+ cons2_hash_hex = smartlist_get(hash_words, 2);
+ if (strlen(cons1_hash_hex) != HEX_DIGEST256_LEN ||
+ strlen(cons2_hash_hex) != HEX_DIGEST256_LEN) {
+ log_info(LD_CONSDIFF, "The provided consensus diff includes "
+ "base16-encoded digests of incorrect size.");
+ goto error_cleanup;
+ }
+
+ if (base16_decode(cons1_hash, DIGEST256_LEN,
+ cons1_hash_hex, HEX_DIGEST256_LEN) != DIGEST256_LEN ||
+ base16_decode(cons2_hash, DIGEST256_LEN,
+ cons2_hash_hex, HEX_DIGEST256_LEN) != DIGEST256_LEN) {
+ log_info(LD_CONSDIFF, "The provided consensus diff includes "
+ "malformed digests.");
+ goto error_cleanup;
+ }
+
+ if (digest1_out) {
+ memcpy(digest1_out, cons1_hash, DIGEST256_LEN);
+ }
+ if (digest2_out) {
+ memcpy(digest2_out, cons2_hash, DIGEST256_LEN);
+ }
+
+ SMARTLIST_FOREACH(hash_words, char *, cp, tor_free(cp));
+ smartlist_free(hash_words);
+ return 0;
+
+ error_cleanup:
+
+ if (hash_words) {
+ SMARTLIST_FOREACH(hash_words, char *, cp, tor_free(cp));
+ smartlist_free(hash_words);
+ }
+ return 1;
+}
+
+/** Apply the consensus diff to the given consensus and return a new
+ * consensus, also as a line-based smartlist. Will return NULL if the diff
+ * could not be applied. Neither the consensus nor the diff are modified in
+ * any way, so it's up to the caller to free their resources.
+ */
+char *
+consdiff_apply_diff(const smartlist_t *cons1,
+ const smartlist_t *diff,
+ const consensus_digest_t *digests1)
+{
+ smartlist_t *cons2 = NULL;
+ char *cons2_str = NULL;
+ char e_cons1_hash[DIGEST256_LEN];
+ char e_cons2_hash[DIGEST256_LEN];
+
+ if (consdiff_get_digests(diff, e_cons1_hash, e_cons2_hash) != 0) {
+ goto error_cleanup;
+ }
+
+ /* See that the consensus that was given to us matches its hash. */
+ if (!consensus_digest_eq(digests1->sha3_256,
+ (const uint8_t*)e_cons1_hash)) {
+ char hex_digest1[HEX_DIGEST256_LEN+1];
+ char e_hex_digest1[HEX_DIGEST256_LEN+1];
+ log_warn(LD_CONSDIFF, "Refusing to apply consensus diff because "
+ "the base consensus doesn't match the digest as found in "
+ "the consensus diff header.");
+ base16_encode(hex_digest1, HEX_DIGEST256_LEN+1,
+ (const char *)digests1->sha3_256, DIGEST256_LEN);
+ base16_encode(e_hex_digest1, HEX_DIGEST256_LEN+1,
+ e_cons1_hash, DIGEST256_LEN);
+ log_warn(LD_CONSDIFF, "Expected: %s; found: %s",
+ hex_digest1, e_hex_digest1);
+ goto error_cleanup;
+ }
+
+ /* Grab the ed diff and calculate the resulting consensus. */
+ /* Skip the first two lines. */
+ cons2 = apply_ed_diff(cons1, diff, 2);
+
+ /* ed diff could not be applied - reason already logged by apply_ed_diff. */
+ if (!cons2) {
+ goto error_cleanup;
+ }
+
+ cons2_str = consensus_join_lines(cons2);
+
+ consensus_digest_t cons2_digests;
+ if (consensus_compute_digest(cons2_str, &cons2_digests) < 0) {
+ /* LCOV_EXCL_START -- digest can't fail */
+ log_warn(LD_CONSDIFF, "Could not compute digests of the consensus "
+ "resulting from applying a consensus diff.");
+ goto error_cleanup;
+ /* LCOV_EXCL_STOP */
+ }
+
+ /* See that the resulting consensus matches its hash. */
+ if (!consensus_digest_eq(cons2_digests.sha3_256,
+ (const uint8_t*)e_cons2_hash)) {
+ log_warn(LD_CONSDIFF, "Refusing to apply consensus diff because "
+ "the resulting consensus doesn't match the digest as found in "
+ "the consensus diff header.");
+ char hex_digest2[HEX_DIGEST256_LEN+1];
+ char e_hex_digest2[HEX_DIGEST256_LEN+1];
+ base16_encode(hex_digest2, HEX_DIGEST256_LEN+1,
+ (const char *)cons2_digests.sha3_256, DIGEST256_LEN);
+ base16_encode(e_hex_digest2, HEX_DIGEST256_LEN+1,
+ e_cons2_hash, DIGEST256_LEN);
+ log_warn(LD_CONSDIFF, "Expected: %s; found: %s",
+ hex_digest2, e_hex_digest2);
+ goto error_cleanup;
+ }
+
+ goto done;
+
+ error_cleanup:
+ tor_free(cons2_str); /* Sets it to NULL */
+
+ done:
+ if (cons2) {
+ smartlist_free(cons2);
+ }
+
+ return cons2_str;
+}
+
+/** Any consensus line longer than this means that the input is invalid. */
+#define CONSENSUS_LINE_MAX_LEN (1<<20)
+
+/**
+ * Helper: For every NL-terminated line in <b>s</b>, add a cdline referring to
+ * that line (without trailing newline) to <b>out</b>. Return -1 if there are
+ * any non-NL terminated lines; 0 otherwise.
+ *
+ * Unlike tor_split_lines, this function avoids ambiguity on its
+ * handling of a final line that isn't NL-terminated.
+ *
+ * All cdline_t objects are allocated in the provided memarea. Strings
+ * are not copied: if <b>s</b> changes or becomes invalid, then all
+ * generated cdlines will become invalid.
+ */
+STATIC int
+consensus_split_lines(smartlist_t *out, const char *s, memarea_t *area)
+{
+ while (*s) {
+ const char *eol = strchr(s, '\n');
+ if (!eol) {
+ /* File doesn't end with newline. */
+ return -1;
+ }
+ if (eol - s > CONSENSUS_LINE_MAX_LEN) {
+ /* Line is far too long. */
+ return -1;
+ }
+ cdline_t *line = memarea_alloc(area, sizeof(cdline_t));
+ line->s = s;
+ line->len = (uint32_t)(eol - s);
+ smartlist_add(out, line);
+ s = eol+1;
+ }
+ return 0;
+}
+
+/** Given a list of cdline_t, return a newly allocated string containing
+ * all of the lines, terminated with NL, concatenated.
+ *
+ * Unlike smartlist_join_strings(), avoids lossy operations on empty
+ * lists. */
+static char *
+consensus_join_lines(const smartlist_t *inp)
+{
+ size_t n = 0;
+ SMARTLIST_FOREACH(inp, const cdline_t *, cdline, n += cdline->len + 1);
+ n += 1;
+ char *result = tor_malloc(n);
+ char *out = result;
+ SMARTLIST_FOREACH_BEGIN(inp, const cdline_t *, cdline) {
+ memcpy(out, cdline->s, cdline->len);
+ out += cdline->len;
+ *out++ = '\n';
+ } SMARTLIST_FOREACH_END(cdline);
+ *out++ = '\0';
+ tor_assert(out == result+n);
+ return result;
+}
+
+/** Given two consensus documents, try to compute a diff between them. On
+ * success, retun a newly allocated string containing that diff. On failure,
+ * return NULL. */
+char *
+consensus_diff_generate(const char *cons1,
+ const char *cons2)
+{
+ consensus_digest_t d1, d2;
+ smartlist_t *lines1 = NULL, *lines2 = NULL, *result_lines = NULL;
+ int r1, r2;
+ char *result = NULL;
+
+ r1 = consensus_compute_digest_as_signed(cons1, &d1);
+ r2 = consensus_compute_digest(cons2, &d2);
+ if (BUG(r1 < 0 || r2 < 0))
+ return NULL; // LCOV_EXCL_LINE
+
+ memarea_t *area = memarea_new();
+ lines1 = smartlist_new();
+ lines2 = smartlist_new();
+ if (consensus_split_lines(lines1, cons1, area) < 0)
+ goto done;
+ if (consensus_split_lines(lines2, cons2, area) < 0)
+ goto done;
+
+ result_lines = consdiff_gen_diff(lines1, lines2, &d1, &d2, area);
+
+ done:
+ if (result_lines) {
+ result = consensus_join_lines(result_lines);
+ smartlist_free(result_lines);
+ }
+
+ memarea_drop_all(area);
+ smartlist_free(lines1);
+ smartlist_free(lines2);
+
+ return result;
+}
+
+/** Given a consensus document and a diff, try to apply the diff to the
+ * consensus. On success return a newly allocated string containing the new
+ * consensus. On failure, return NULL. */
+char *
+consensus_diff_apply(const char *consensus,
+ const char *diff)
+{
+ consensus_digest_t d1;
+ smartlist_t *lines1 = NULL, *lines2 = NULL;
+ int r1;
+ char *result = NULL;
+ memarea_t *area = memarea_new();
+
+ r1 = consensus_compute_digest_as_signed(consensus, &d1);
+ if (BUG(r1 < 0))
+ return NULL; // LCOV_EXCL_LINE
+
+ lines1 = smartlist_new();
+ lines2 = smartlist_new();
+ if (consensus_split_lines(lines1, consensus, area) < 0)
+ goto done;
+ if (consensus_split_lines(lines2, diff, area) < 0)
+ goto done;
+
+ result = consdiff_apply_diff(lines1, lines2, &d1);
+
+ done:
+ smartlist_free(lines1);
+ smartlist_free(lines2);
+ memarea_drop_all(area);
+
+ return result;
+}
+
+/** Return true iff, based on its header, <b>document</b> is likely
+ * to be a consensus diff. */
+int
+looks_like_a_consensus_diff(const char *document, size_t len)
+{
+ return (len >= strlen(ns_diff_version) &&
+ fast_memeq(document, ns_diff_version, strlen(ns_diff_version)));
+}
+
diff --git a/src/or/consdiff.h b/src/or/consdiff.h
new file mode 100644
index 0000000000..d05df74b75
--- /dev/null
+++ b/src/or/consdiff.h
@@ -0,0 +1,98 @@
+/* Copyright (c) 2014, Daniel Martí
+ * Copyright (c) 2014, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_CONSDIFF_H
+#define TOR_CONSDIFF_H
+
+#include "or.h"
+
+char *consensus_diff_generate(const char *cons1,
+ const char *cons2);
+char *consensus_diff_apply(const char *consensus,
+ const char *diff);
+
+int looks_like_a_consensus_diff(const char *document, size_t len);
+
+#ifdef CONSDIFF_PRIVATE
+struct memarea_t;
+
+/** Line type used for constructing consensus diffs. Each of these lines
+ * refers to a chunk of memory allocated elsewhere, and is not necessarily
+ * NUL-terminated: this helps us avoid copies and save memory. */
+typedef struct cdline_t {
+ const char *s;
+ uint32_t len;
+} cdline_t;
+
+typedef struct consensus_digest_t {
+ uint8_t sha3_256[DIGEST256_LEN];
+} consensus_digest_t;
+
+STATIC smartlist_t *consdiff_gen_diff(const smartlist_t *cons1,
+ const smartlist_t *cons2,
+ const consensus_digest_t *digests1,
+ const consensus_digest_t *digests2,
+ struct memarea_t *area);
+STATIC char *consdiff_apply_diff(const smartlist_t *cons1,
+ const smartlist_t *diff,
+ const consensus_digest_t *digests1);
+STATIC int consdiff_get_digests(const smartlist_t *diff,
+ char *digest1_out,
+ char *digest2_out);
+
+/** Data structure to define a slice of a smarltist. */
+typedef struct smartlist_slice_t {
+ /**
+ * Smartlist that this slice is made from.
+ * References the whole original smartlist that the slice was made out of.
+ * */
+ const smartlist_t *list;
+ /** Starting position of the slice in the smartlist. */
+ int offset;
+ /** Length of the slice, i.e. the number of elements it holds. */
+ int len;
+} smartlist_slice_t;
+STATIC smartlist_t *gen_ed_diff(const smartlist_t *cons1,
+ const smartlist_t *cons2,
+ struct memarea_t *area);
+STATIC smartlist_t *apply_ed_diff(const smartlist_t *cons1,
+ const smartlist_t *diff,
+ int start_line);
+STATIC void calc_changes(smartlist_slice_t *slice1, smartlist_slice_t *slice2,
+ bitarray_t *changed1, bitarray_t *changed2);
+STATIC smartlist_slice_t *smartlist_slice(const smartlist_t *list,
+ int start, int end);
+STATIC int next_router(const smartlist_t *cons, int cur);
+STATIC int *lcs_lengths(const smartlist_slice_t *slice1,
+ const smartlist_slice_t *slice2,
+ int direction);
+STATIC void trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2);
+STATIC int base64cmp(const cdline_t *hash1, const cdline_t *hash2);
+STATIC int get_id_hash(const cdline_t *line, cdline_t *hash_out);
+STATIC int is_valid_router_entry(const cdline_t *line);
+STATIC int smartlist_slice_string_pos(const smartlist_slice_t *slice,
+ const cdline_t *string);
+STATIC void set_changed(bitarray_t *changed1, bitarray_t *changed2,
+ const smartlist_slice_t *slice1,
+ const smartlist_slice_t *slice2);
+STATIC int consensus_split_lines(smartlist_t *out, const char *s,
+ struct memarea_t *area);
+STATIC void smartlist_add_linecpy(smartlist_t *lst, struct memarea_t *area,
+ const char *s);
+STATIC int lines_eq(const cdline_t *a, const cdline_t *b);
+STATIC int line_str_eq(const cdline_t *a, const char *b);
+
+MOCK_DECL(STATIC int,
+ consensus_compute_digest,(const char *cons,
+ consensus_digest_t *digest_out));
+MOCK_DECL(STATIC int,
+ consensus_compute_digest_as_signed,(const char *cons,
+ consensus_digest_t *digest_out));
+MOCK_DECL(STATIC int,
+ consensus_digest_eq,(const uint8_t *d1,
+ const uint8_t *d2));
+#endif
+
+#endif
+
diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c
new file mode 100644
index 0000000000..928fc26f54
--- /dev/null
+++ b/src/or/consdiffmgr.c
@@ -0,0 +1,1878 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file consdiffmsr.c
+ *
+ * \brief consensus diff manager functions
+ *
+ * This module is run by directory authorities and caches in order
+ * to remember a number of past consensus documents, and to generate
+ * and serve the diffs from those documents to the latest consensus.
+ */
+
+#define CONSDIFFMGR_PRIVATE
+
+#include "or.h"
+#include "config.h"
+#include "conscache.h"
+#include "consdiff.h"
+#include "consdiffmgr.h"
+#include "cpuworker.h"
+#include "networkstatus.h"
+#include "routerparse.h"
+#include "workqueue.h"
+
+/**
+ * Labels to apply to items in the conscache object.
+ *
+ * @{
+ */
+/* One of DOCTYPE_CONSENSUS or DOCTYPE_CONSENSUS_DIFF */
+#define LABEL_DOCTYPE "document-type"
+/* The valid-after time for a consensus (or for the target consensus of a
+ * diff), encoded as ISO UTC. */
+#define LABEL_VALID_AFTER "consensus-valid-after"
+/* The fresh-until time for a consensus (or for the target consensus of a
+ * diff), encoded as ISO UTC. */
+#define LABEL_FRESH_UNTIL "consensus-fresh-until"
+/* The valid-until time for a consensus (or for the target consensus of a
+ * diff), encoded as ISO UTC. */
+#define LABEL_VALID_UNTIL "consensus-valid-until"
+/* Comma-separated list of hex-encoded identity digests for the voting
+ * authorities. */
+#define LABEL_SIGNATORIES "consensus-signatories"
+/* A hex encoded SHA3 digest of the object, as compressed (if any) */
+#define LABEL_SHA3_DIGEST "sha3-digest"
+/* A hex encoded SHA3 digest of the object before compression. */
+#define LABEL_SHA3_DIGEST_UNCOMPRESSED "sha3-digest-uncompressed"
+/* A hex encoded SHA3 digest-as-signed of a consensus */
+#define LABEL_SHA3_DIGEST_AS_SIGNED "sha3-digest-as-signed"
+/* The flavor of the consensus or consensuses diff */
+#define LABEL_FLAVOR "consensus-flavor"
+/* Diff only: the SHA3 digest-as-signed of the source consensus. */
+#define LABEL_FROM_SHA3_DIGEST "from-sha3-digest"
+/* Diff only: the SHA3 digest-in-full of the target consensus. */
+#define LABEL_TARGET_SHA3_DIGEST "target-sha3-digest"
+/* Diff only: the valid-after date of the source consensus. */
+#define LABEL_FROM_VALID_AFTER "from-valid-after"
+/* What kind of compression was used? */
+#define LABEL_COMPRESSION_TYPE "compression"
+/** @} */
+
+#define DOCTYPE_CONSENSUS "consensus"
+#define DOCTYPE_CONSENSUS_DIFF "consensus-diff"
+
+/**
+ * Underlying directory that stores consensuses and consensus diffs. Don't
+ * use this directly: use cdm_cache_get() instead.
+ */
+static consensus_cache_t *cons_diff_cache = NULL;
+/**
+ * If true, we have learned at least one new consensus since the
+ * consensus cache was last up-to-date.
+ */
+static int cdm_cache_dirty = 0;
+/**
+ * If true, we have scanned the cache to update our hashtable of diffs.
+ */
+static int cdm_cache_loaded = 0;
+
+/**
+ * Possible status values for cdm_diff_t.cdm_diff_status
+ **/
+typedef enum cdm_diff_status_t {
+ CDM_DIFF_PRESENT=1,
+ CDM_DIFF_IN_PROGRESS=2,
+ CDM_DIFF_ERROR=3,
+} cdm_diff_status_t;
+
+/** Which methods do we use for precompressing diffs? */
+static const compress_method_t compress_diffs_with[] = {
+ NO_METHOD,
+ GZIP_METHOD,
+#ifdef HAVE_LZMA
+ LZMA_METHOD,
+#endif
+#ifdef HAVE_ZSTD
+ ZSTD_METHOD,
+#endif
+};
+
+/** How many different methods will we try to use for diff compression? */
+STATIC unsigned
+n_diff_compression_methods(void)
+{
+ return ARRAY_LENGTH(compress_diffs_with);
+}
+
+/** Which methods do we use for precompressing consensuses? */
+static const compress_method_t compress_consensus_with[] = {
+ ZLIB_METHOD,
+#ifdef HAVE_LZMA
+ LZMA_METHOD,
+#endif
+#ifdef HAVE_ZSTD
+ ZSTD_METHOD,
+#endif
+};
+
+/** How many different methods will we try to use for diff compression? */
+STATIC unsigned
+n_consensus_compression_methods(void)
+{
+ return ARRAY_LENGTH(compress_consensus_with);
+}
+
+/** For which compression method do we retain old consensuses? There's no
+ * need to keep all of them, since we won't be serving them. We'll
+ * go with ZLIB_METHOD because it's pretty fast and everyone has it.
+ */
+#define RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD ZLIB_METHOD
+
+/** Handles pointing to the latest consensus entries as compressed and
+ * stored. */
+static consensus_cache_entry_handle_t *
+ latest_consensus[N_CONSENSUS_FLAVORS]
+ [ARRAY_LENGTH(compress_consensus_with)];
+
+/** Hashtable node used to remember the current status of the diff
+ * from a given sha3 digest to the current consensus. */
+typedef struct cdm_diff_t {
+ HT_ENTRY(cdm_diff_t) node;
+
+ /** Consensus flavor for this diff (part of ht key) */
+ consensus_flavor_t flavor;
+ /** SHA3-256 digest of the consensus that this diff is _from_. (part of the
+ * ht key) */
+ uint8_t from_sha3[DIGEST256_LEN];
+ /** Method by which the diff is compressed. (part of the ht key */
+ compress_method_t compress_method;
+
+ /** One of the CDM_DIFF_* values, depending on whether this diff
+ * is available, in progress, or impossible to compute. */
+ cdm_diff_status_t cdm_diff_status;
+ /** SHA3-256 digest of the consensus that this diff is _to. */
+ uint8_t target_sha3[DIGEST256_LEN];
+
+ /** Handle to the cache entry for this diff, if any. We use a handle here
+ * to avoid thinking too hard about cache entry lifetime issues. */
+ consensus_cache_entry_handle_t *entry;
+} cdm_diff_t;
+
+/** Hashtable mapping flavor and source consensus digest to status. */
+static HT_HEAD(cdm_diff_ht, cdm_diff_t) cdm_diff_ht = HT_INITIALIZER();
+
+/**
+ * Configuration for this module
+ */
+static consdiff_cfg_t consdiff_cfg = {
+ // XXXX I'd like to make this number bigger, but it interferes with the
+ // XXXX seccomp2 syscall filter, which tops out at BPF_MAXINS (4096)
+ // XXXX rules.
+ /* .cache_max_num = */ 128
+};
+
+static int consdiffmgr_ensure_space_for_files(int n);
+static int consensus_queue_compression_work(const char *consensus,
+ const networkstatus_t *as_parsed);
+static int consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from,
+ consensus_cache_entry_t *diff_to);
+static void consdiffmgr_set_cache_flags(void);
+
+/* =====
+ * Hashtable setup
+ * ===== */
+
+/** Helper: hash the key of a cdm_diff_t. */
+static unsigned
+cdm_diff_hash(const cdm_diff_t *diff)
+{
+ uint8_t tmp[DIGEST256_LEN + 2];
+ memcpy(tmp, diff->from_sha3, DIGEST256_LEN);
+ tmp[DIGEST256_LEN] = (uint8_t) diff->flavor;
+ tmp[DIGEST256_LEN+1] = (uint8_t) diff->compress_method;
+ return (unsigned) siphash24g(tmp, sizeof(tmp));
+}
+/** Helper: compare two cdm_diff_t objects for key equality */
+static int
+cdm_diff_eq(const cdm_diff_t *diff1, const cdm_diff_t *diff2)
+{
+ return fast_memeq(diff1->from_sha3, diff2->from_sha3, DIGEST256_LEN) &&
+ diff1->flavor == diff2->flavor &&
+ diff1->compress_method == diff2->compress_method;
+}
+
+HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq)
+HT_GENERATE2(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq,
+ 0.6, tor_reallocarray, tor_free_)
+
+/** Release all storage held in <b>diff</b>. */
+static void
+cdm_diff_free(cdm_diff_t *diff)
+{
+ if (!diff)
+ return;
+ consensus_cache_entry_handle_free(diff->entry);
+ tor_free(diff);
+}
+
+/** Create and return a new cdm_diff_t with the given values. Does not
+ * add it to the hashtable. */
+static cdm_diff_t *
+cdm_diff_new(consensus_flavor_t flav,
+ const uint8_t *from_sha3,
+ const uint8_t *target_sha3,
+ compress_method_t method)
+{
+ cdm_diff_t *ent;
+ ent = tor_malloc_zero(sizeof(cdm_diff_t));
+ ent->flavor = flav;
+ memcpy(ent->from_sha3, from_sha3, DIGEST256_LEN);
+ memcpy(ent->target_sha3, target_sha3, DIGEST256_LEN);
+ ent->compress_method = method;
+ return ent;
+}
+
+/**
+ * Examine the diff hashtable to see whether we know anything about computing
+ * a diff of type <b>flav</b> between consensuses with the two provided
+ * SHA3-256 digests. If a computation is in progress, or if the computation
+ * has already been tried and failed, return 1. Otherwise, note the
+ * computation as "in progress" so that we don't reattempt it later, and
+ * return 0.
+ */
+static int
+cdm_diff_ht_check_and_note_pending(consensus_flavor_t flav,
+ const uint8_t *from_sha3,
+ const uint8_t *target_sha3)
+{
+ struct cdm_diff_t search, *ent;
+ unsigned u;
+ int result = 0;
+ for (u = 0; u < n_diff_compression_methods(); ++u) {
+ compress_method_t method = compress_diffs_with[u];
+ memset(&search, 0, sizeof(cdm_diff_t));
+ search.flavor = flav;
+ search.compress_method = method;
+ memcpy(search.from_sha3, from_sha3, DIGEST256_LEN);
+ ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
+ if (ent) {
+ tor_assert_nonfatal(ent->cdm_diff_status != CDM_DIFF_PRESENT);
+ result = 1;
+ continue;
+ }
+ ent = cdm_diff_new(flav, from_sha3, target_sha3, method);
+ ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS;
+ HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent);
+ }
+ return result;
+}
+
+/**
+ * Update the status of the diff of type <b>flav</b> between consensuses with
+ * the two provided SHA3-256 digests, so that its status becomes
+ * <b>status</b>, and its value becomes the <b>handle</b>. If <b>handle</b>
+ * is NULL, then the old handle (if any) is freed, and replaced with NULL.
+ */
+static void
+cdm_diff_ht_set_status(consensus_flavor_t flav,
+ const uint8_t *from_sha3,
+ const uint8_t *to_sha3,
+ compress_method_t method,
+ int status,
+ consensus_cache_entry_handle_t *handle)
+{
+ struct cdm_diff_t search, *ent;
+ memset(&search, 0, sizeof(cdm_diff_t));
+ search.flavor = flav;
+ search.compress_method = method,
+ memcpy(search.from_sha3, from_sha3, DIGEST256_LEN);
+ ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
+ if (!ent) {
+ ent = cdm_diff_new(flav, from_sha3, to_sha3, method);
+ ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS;
+ HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent);
+ } else if (fast_memneq(ent->target_sha3, to_sha3, DIGEST256_LEN)) {
+ // This can happen under certain really pathological conditions
+ // if we decide we don't care about a diff before it is actually
+ // done computing.
+ return;
+ }
+
+ tor_assert_nonfatal(ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS);
+
+ ent->cdm_diff_status = status;
+ consensus_cache_entry_handle_free(ent->entry);
+ ent->entry = handle;
+}
+
+/**
+ * Helper: Remove from the hash table every present (actually computed) diff
+ * of type <b>flav</b> whose target digest does not match
+ * <b>unless_target_sha3_matches</b>.
+ *
+ * This function is used for the hash table to throw away references to diffs
+ * that do not lead to the most given consensus of a given flavor.
+ */
+static void
+cdm_diff_ht_purge(consensus_flavor_t flav,
+ const uint8_t *unless_target_sha3_matches)
+{
+ cdm_diff_t **diff, **next;
+ for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) {
+ cdm_diff_t *this = *diff;
+
+ if ((*diff)->cdm_diff_status == CDM_DIFF_PRESENT &&
+ flav == (*diff)->flavor) {
+
+ if (BUG((*diff)->entry == NULL) ||
+ consensus_cache_entry_handle_get((*diff)->entry) == NULL) {
+ /* the underlying entry has gone away; drop this. */
+ next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
+ cdm_diff_free(this);
+ continue;
+ }
+
+ if (unless_target_sha3_matches &&
+ fast_memneq(unless_target_sha3_matches, (*diff)->target_sha3,
+ DIGEST256_LEN)) {
+ /* target hash doesn't match; drop this. */
+ next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
+ cdm_diff_free(this);
+ continue;
+ }
+ }
+ next = HT_NEXT(cdm_diff_ht, &cdm_diff_ht, diff);
+ }
+}
+
+/**
+ * Helper: initialize <b>cons_diff_cache</b>.
+ */
+static void
+cdm_cache_init(void)
+{
+ unsigned n_entries = consdiff_cfg.cache_max_num * 2;
+
+ tor_assert(cons_diff_cache == NULL);
+ cons_diff_cache = consensus_cache_open("diff-cache", n_entries);
+ if (cons_diff_cache == NULL) {
+ // LCOV_EXCL_START
+ log_err(LD_FS, "Error: Couldn't open storage for consensus diffs.");
+ tor_assert_unreached();
+ // LCOV_EXCL_STOP
+ } else {
+ consdiffmgr_set_cache_flags();
+ }
+ cdm_cache_dirty = 1;
+ cdm_cache_loaded = 0;
+}
+
+/**
+ * Helper: return the consensus_cache_t * that backs this manager,
+ * initializing it if needed.
+ */
+STATIC consensus_cache_t *
+cdm_cache_get(void)
+{
+ if (PREDICT_UNLIKELY(cons_diff_cache == NULL)) {
+ cdm_cache_init();
+ }
+ return cons_diff_cache;
+}
+
+/**
+ * Helper: given a list of labels, prepend the hex-encoded SHA3 digest
+ * of the <b>bodylen</b>-byte object at <b>body</b> to those labels,
+ * with <b>label</b> as its label.
+ */
+static void
+cdm_labels_prepend_sha3(config_line_t **labels,
+ const char *label,
+ const uint8_t *body,
+ size_t bodylen)
+{
+ uint8_t sha3_digest[DIGEST256_LEN];
+ char hexdigest[HEX_DIGEST256_LEN+1];
+ crypto_digest256((char *)sha3_digest,
+ (const char *)body, bodylen, DIGEST_SHA3_256);
+ base16_encode(hexdigest, sizeof(hexdigest),
+ (const char *)sha3_digest, sizeof(sha3_digest));
+
+ config_line_prepend(labels, label, hexdigest);
+}
+
+/** Helper: if there is a sha3-256 hex-encoded digest in <b>ent</b> with the
+ * given label, set <b>digest_out</b> to that value (decoded), and return 0.
+ *
+ * Return -1 if there is no such label, and -2 if it is badly formatted. */
+STATIC int
+cdm_entry_get_sha3_value(uint8_t *digest_out,
+ consensus_cache_entry_t *ent,
+ const char *label)
+{
+ if (ent == NULL)
+ return -1;
+
+ const char *hex = consensus_cache_entry_get_value(ent, label);
+ if (hex == NULL)
+ return -1;
+
+ int n = base16_decode((char*)digest_out, DIGEST256_LEN, hex, strlen(hex));
+ if (n != DIGEST256_LEN)
+ return -2;
+ else
+ return 0;
+}
+
+/**
+ * Helper: look for a consensus with the given <b>flavor</b> and
+ * <b>valid_after</b> time in the cache. Return that consensus if it's
+ * present, or NULL if it's missing.
+ */
+STATIC consensus_cache_entry_t *
+cdm_cache_lookup_consensus(consensus_flavor_t flavor, time_t valid_after)
+{
+ char formatted_time[ISO_TIME_LEN+1];
+ format_iso_time_nospace(formatted_time, valid_after);
+ const char *flavname = networkstatus_get_flavor_name(flavor);
+
+ /* We'll filter by valid-after time first, since that should
+ * match the fewest documents. */
+ /* We could add an extra hashtable here, but since we only do this scan
+ * when adding a new consensus, it probably doesn't matter much. */
+ smartlist_t *matches = smartlist_new();
+ consensus_cache_find_all(matches, cdm_cache_get(),
+ LABEL_VALID_AFTER, formatted_time);
+ consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname);
+ consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+
+ consensus_cache_entry_t *result = NULL;
+ if (smartlist_len(matches)) {
+ result = smartlist_get(matches, 0);
+ }
+ smartlist_free(matches);
+
+ return result;
+}
+
+/** Return the maximum age (in seconds) of consensuses that we should consider
+ * storing. The available space in the directory may impose additional limits
+ * on how much we store. */
+static int32_t
+get_max_age_to_cache(void)
+{
+ const int32_t DEFAULT_MAX_AGE_TO_CACHE = 8192;
+ const int32_t MIN_MAX_AGE_TO_CACHE = 0;
+ const int32_t MAX_MAX_AGE_TO_CACHE = 8192;
+ const char MAX_AGE_TO_CACHE_NAME[] = "max-consensus-age-to-cache-for-diff";
+
+ const or_options_t *options = get_options();
+
+ if (options->MaxConsensusAgeForDiffs) {
+ const int v = options->MaxConsensusAgeForDiffs;
+ if (v >= MAX_MAX_AGE_TO_CACHE * 3600)
+ return MAX_MAX_AGE_TO_CACHE;
+ else
+ return v;
+ }
+
+ /* The parameter is in hours, so we multiply */
+ return 3600 * networkstatus_get_param(NULL,
+ MAX_AGE_TO_CACHE_NAME,
+ DEFAULT_MAX_AGE_TO_CACHE,
+ MIN_MAX_AGE_TO_CACHE,
+ MAX_MAX_AGE_TO_CACHE);
+}
+
+/**
+ * Given a string containing a networkstatus consensus, and the results of
+ * having parsed that consensus, add that consensus to the cache if it is not
+ * already present and not too old. Create new consensus diffs from or to
+ * that consensus as appropriate.
+ *
+ * Return 0 on success and -1 on failure.
+ */
+int
+consdiffmgr_add_consensus(const char *consensus,
+ const networkstatus_t *as_parsed)
+{
+ if (BUG(consensus == NULL) || BUG(as_parsed == NULL))
+ return -1; // LCOV_EXCL_LINE
+ if (BUG(as_parsed->type != NS_TYPE_CONSENSUS))
+ return -1; // LCOV_EXCL_LINE
+
+ const consensus_flavor_t flavor = as_parsed->flavor;
+ const time_t valid_after = as_parsed->valid_after;
+
+ if (valid_after < approx_time() - get_max_age_to_cache()) {
+ log_info(LD_DIRSERV, "We don't care about this consensus document; it's "
+ "too old.");
+ return -1;
+ }
+
+ /* Do we already have this one? */
+ consensus_cache_entry_t *entry =
+ cdm_cache_lookup_consensus(flavor, valid_after);
+ if (entry) {
+ log_info(LD_DIRSERV, "We already have a copy of that consensus");
+ return -1;
+ }
+
+ /* We don't have it. Add it to the cache. */
+ return consensus_queue_compression_work(consensus, as_parsed);
+}
+
+/**
+ * Helper: used to sort two smartlists of consensus_cache_entry_t by their
+ * LABEL_VALID_AFTER labels.
+ */
+static int
+compare_by_valid_after_(const void **a, const void **b)
+{
+ const consensus_cache_entry_t *e1 = *a;
+ const consensus_cache_entry_t *e2 = *b;
+ /* We're in luck here: sorting UTC iso-encoded values lexically will work
+ * fine (until 9999). */
+ return strcmp_opt(consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER),
+ consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER));
+}
+
+/**
+ * Helper: Sort <b>lst</b> by LABEL_VALID_AFTER and return the most recent
+ * entry.
+ */
+static consensus_cache_entry_t *
+sort_and_find_most_recent(smartlist_t *lst)
+{
+ smartlist_sort(lst, compare_by_valid_after_);
+ if (smartlist_len(lst)) {
+ return smartlist_get(lst, smartlist_len(lst) - 1);
+ } else {
+ return NULL;
+ }
+}
+
+/** Return i such that compress_consensus_with[i] == method. Return
+ * -1 if no such i exists. */
+static int
+consensus_compression_method_pos(compress_method_t method)
+{
+ unsigned i;
+ for (i = 0; i < n_consensus_compression_methods(); ++i) {
+ if (compress_consensus_with[i] == method) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * If we know a consensus with the flavor <b>flavor</b> compressed with
+ * <b>method</b>, set *<b>entry_out</b> to that value. Return values are as
+ * for consdiffmgr_find_diff_from().
+ */
+consdiff_status_t
+consdiffmgr_find_consensus(struct consensus_cache_entry_t **entry_out,
+ consensus_flavor_t flavor,
+ compress_method_t method)
+{
+ tor_assert(entry_out);
+ tor_assert((int)flavor < N_CONSENSUS_FLAVORS);
+
+ int pos = consensus_compression_method_pos(method);
+ if (pos < 0) {
+ // We don't compress consensuses with this method.
+ return CONSDIFF_NOT_FOUND;
+ }
+ consensus_cache_entry_handle_t *handle = latest_consensus[flavor][pos];
+ if (!handle)
+ return CONSDIFF_NOT_FOUND;
+ *entry_out = consensus_cache_entry_handle_get(handle);
+ if (*entry_out)
+ return CONSDIFF_AVAILABLE;
+ else
+ return CONSDIFF_NOT_FOUND;
+}
+
+/**
+ * Look up consensus_cache_entry_t for the consensus of type <b>flavor</b>,
+ * from the source consensus with the specified digest (which must be SHA3).
+ *
+ * If the diff is present, store it into *<b>entry_out</b> and return
+ * CONSDIFF_AVAILABLE. Otherwise return CONSDIFF_NOT_FOUND or
+ * CONSDIFF_IN_PROGRESS.
+ */
+consdiff_status_t
+consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out,
+ consensus_flavor_t flavor,
+ int digest_type,
+ const uint8_t *digest,
+ size_t digestlen,
+ compress_method_t method)
+{
+ if (BUG(digest_type != DIGEST_SHA3_256) ||
+ BUG(digestlen != DIGEST256_LEN)) {
+ return CONSDIFF_NOT_FOUND; // LCOV_EXCL_LINE
+ }
+
+ // Try to look up the entry in the hashtable.
+ cdm_diff_t search, *ent;
+ memset(&search, 0, sizeof(search));
+ search.flavor = flavor;
+ search.compress_method = method;
+ memcpy(search.from_sha3, digest, DIGEST256_LEN);
+ ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
+
+ if (ent == NULL ||
+ ent->cdm_diff_status == CDM_DIFF_ERROR) {
+ return CONSDIFF_NOT_FOUND;
+ } else if (ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS) {
+ return CONSDIFF_IN_PROGRESS;
+ } else if (BUG(ent->cdm_diff_status != CDM_DIFF_PRESENT)) {
+ return CONSDIFF_IN_PROGRESS;
+ }
+
+ if (BUG(ent->entry == NULL)) {
+ return CONSDIFF_NOT_FOUND;
+ }
+ *entry_out = consensus_cache_entry_handle_get(ent->entry);
+ return (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND;
+
+#if 0
+ // XXXX Remove this. I'm keeping it around for now in case we need to
+ // XXXX debug issues in the hashtable.
+ char hex[HEX_DIGEST256_LEN+1];
+ base16_encode(hex, sizeof(hex), (const char *)digest, digestlen);
+ const char *flavname = networkstatus_get_flavor_name(flavor);
+
+ smartlist_t *matches = smartlist_new();
+ consensus_cache_find_all(matches, cdm_cache_get(),
+ LABEL_FROM_SHA3_DIGEST, hex);
+ consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname);
+ consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
+
+ *entry_out = sort_and_find_most_recent(matches);
+ consdiff_status_t result =
+ (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND;
+ smartlist_free(matches);
+
+ return result;
+#endif
+}
+
+/**
+ * Perform periodic cleanup tasks on the consensus diff cache. Return
+ * the number of objects marked for deletion.
+ */
+int
+consdiffmgr_cleanup(void)
+{
+ smartlist_t *objects = smartlist_new();
+ smartlist_t *consensuses = smartlist_new();
+ smartlist_t *diffs = smartlist_new();
+ int n_to_delete = 0;
+
+ log_debug(LD_DIRSERV, "Looking for consdiffmgr entries to remove");
+
+ // 1. Delete any consensus or diff or anything whose valid_after is too old.
+ const time_t valid_after_cutoff = approx_time() - get_max_age_to_cache();
+
+ consensus_cache_find_all(objects, cdm_cache_get(),
+ NULL, NULL);
+ SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
+ const char *lv_valid_after =
+ consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
+ if (! lv_valid_after) {
+ log_debug(LD_DIRSERV, "Ignoring entry because it had no %s label",
+ LABEL_VALID_AFTER);
+ continue;
+ }
+ time_t valid_after = 0;
+ if (parse_iso_time_nospace(lv_valid_after, &valid_after) < 0) {
+ log_debug(LD_DIRSERV, "Ignoring entry because its %s value (%s) was "
+ "unparseable", LABEL_VALID_AFTER, escaped(lv_valid_after));
+ continue;
+ }
+ if (valid_after < valid_after_cutoff) {
+ log_debug(LD_DIRSERV, "Deleting entry because its %s value (%s) was "
+ "too old", LABEL_VALID_AFTER, lv_valid_after);
+ consensus_cache_entry_mark_for_removal(ent);
+ ++n_to_delete;
+ }
+ } SMARTLIST_FOREACH_END(ent);
+
+ // 2. Delete all diffs that lead to a consensus whose valid-after is not the
+ // latest.
+ for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
+ const char *flavname = networkstatus_get_flavor_name(flav);
+ /* Determine the most recent consensus of this flavor */
+ consensus_cache_find_all(consensuses, cdm_cache_get(),
+ LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+ consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname);
+ consensus_cache_entry_t *most_recent =
+ sort_and_find_most_recent(consensuses);
+ if (most_recent == NULL)
+ continue;
+ const char *most_recent_sha3 =
+ consensus_cache_entry_get_value(most_recent,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED);
+ if (BUG(most_recent_sha3 == NULL))
+ continue; // LCOV_EXCL_LINE
+
+ /* consider all such-flavored diffs, and look to see if they match. */
+ consensus_cache_find_all(diffs, cdm_cache_get(),
+ LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
+ consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname);
+ SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
+ const char *this_diff_target_sha3 =
+ consensus_cache_entry_get_value(diff, LABEL_TARGET_SHA3_DIGEST);
+ if (!this_diff_target_sha3)
+ continue;
+ if (strcmp(this_diff_target_sha3, most_recent_sha3)) {
+ consensus_cache_entry_mark_for_removal(diff);
+ ++n_to_delete;
+ }
+ } SMARTLIST_FOREACH_END(diff);
+ smartlist_clear(consensuses);
+ smartlist_clear(diffs);
+ }
+
+ // 3. Delete all consensuses except the most recent that are compressed with
+ // an un-preferred method.
+ for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
+ const char *flavname = networkstatus_get_flavor_name(flav);
+ /* Determine the most recent consensus of this flavor */
+ consensus_cache_find_all(consensuses, cdm_cache_get(),
+ LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+ consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname);
+ consensus_cache_entry_t *most_recent =
+ sort_and_find_most_recent(consensuses);
+ if (most_recent == NULL)
+ continue;
+ const char *most_recent_sha3_uncompressed =
+ consensus_cache_entry_get_value(most_recent,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED);
+ const char *retain_methodname = compression_method_get_name(
+ RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD);
+
+ if (BUG(most_recent_sha3_uncompressed == NULL))
+ continue;
+ SMARTLIST_FOREACH_BEGIN(consensuses, consensus_cache_entry_t *, ent) {
+ const char *lv_sha3_uncompressed =
+ consensus_cache_entry_get_value(ent, LABEL_SHA3_DIGEST_UNCOMPRESSED);
+ if (BUG(! lv_sha3_uncompressed))
+ continue;
+ if (!strcmp(lv_sha3_uncompressed, most_recent_sha3_uncompressed))
+ continue; // This _is_ the most recent.
+ const char *lv_methodname =
+ consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
+ if (! lv_methodname || strcmp(lv_methodname, retain_methodname)) {
+ consensus_cache_entry_mark_for_removal(ent);
+ ++n_to_delete;
+ }
+ } SMARTLIST_FOREACH_END(ent);
+ }
+
+ smartlist_free(objects);
+ smartlist_free(consensuses);
+ smartlist_free(diffs);
+
+ // Actually remove files, if they're not used.
+ consensus_cache_delete_pending(cdm_cache_get(), 0);
+ return n_to_delete;
+}
+
+/**
+ * Initialize the consensus diff manager and its cache, and configure
+ * its parameters based on the latest torrc and networkstatus parameters.
+ */
+void
+consdiffmgr_configure(const consdiff_cfg_t *cfg)
+{
+ if (cfg)
+ memcpy(&consdiff_cfg, cfg, sizeof(consdiff_cfg));
+
+ (void) cdm_cache_get();
+}
+
+/**
+ * Tell the sandbox (if any) configured by <b>cfg</b> to allow the
+ * operations that the consensus diff manager will need.
+ */
+int
+consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg)
+{
+ return consensus_cache_register_with_sandbox(cdm_cache_get(), cfg);
+}
+
+/**
+ * Scan the consensus diff manager's cache for any grossly malformed entries,
+ * and mark them as deletable. Return 0 if no problems were found; 1
+ * if problems were found and fixed.
+ */
+int
+consdiffmgr_validate(void)
+{
+ /* Right now, we only check for entries that have bad sha3 values */
+ int problems = 0;
+
+ smartlist_t *objects = smartlist_new();
+ consensus_cache_find_all(objects, cdm_cache_get(),
+ NULL, NULL);
+ SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, obj) {
+ uint8_t sha3_expected[DIGEST256_LEN];
+ uint8_t sha3_received[DIGEST256_LEN];
+ int r = cdm_entry_get_sha3_value(sha3_expected, obj, LABEL_SHA3_DIGEST);
+ if (r == -1) {
+ /* digest isn't there; that's allowed */
+ continue;
+ } else if (r == -2) {
+ /* digest is malformed; that's not allowed */
+ problems = 1;
+ consensus_cache_entry_mark_for_removal(obj);
+ continue;
+ }
+ const uint8_t *body;
+ size_t bodylen;
+ consensus_cache_entry_incref(obj);
+ r = consensus_cache_entry_get_body(obj, &body, &bodylen);
+ if (r == 0) {
+ crypto_digest256((char *)sha3_received, (const char *)body, bodylen,
+ DIGEST_SHA3_256);
+ }
+ consensus_cache_entry_decref(obj);
+ if (r < 0)
+ continue;
+
+ // Deconfuse coverity about the possibility of sha3_received being
+ // uninitialized
+ tor_assert(r <= 0);
+
+ if (fast_memneq(sha3_received, sha3_expected, DIGEST256_LEN)) {
+ problems = 1;
+ consensus_cache_entry_mark_for_removal(obj);
+ continue;
+ }
+
+ } SMARTLIST_FOREACH_END(obj);
+ smartlist_free(objects);
+ return problems;
+}
+
+/**
+ * Helper: build new diffs of <b>flavor</b> as needed
+ */
+static void
+consdiffmgr_rescan_flavor_(consensus_flavor_t flavor)
+{
+ smartlist_t *matches = NULL;
+ smartlist_t *diffs = NULL;
+ smartlist_t *compute_diffs_from = NULL;
+ strmap_t *have_diff_from = NULL;
+
+ // look for the most recent consensus, and for all previous in-range
+ // consensuses. Do they all have diffs to it?
+ const char *flavname = networkstatus_get_flavor_name(flavor);
+
+ // 1. find the most recent consensus, and the ones that we might want
+ // to diff to it.
+ const char *methodname = compression_method_get_name(
+ RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD);
+
+ matches = smartlist_new();
+ consensus_cache_find_all(matches, cdm_cache_get(),
+ LABEL_FLAVOR, flavname);
+ consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+ consensus_cache_filter_list(matches, LABEL_COMPRESSION_TYPE, methodname);
+ consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches);
+ if (!most_recent) {
+ log_info(LD_DIRSERV, "No 'most recent' %s consensus found; "
+ "not making diffs", flavname);
+ goto done;
+ }
+ tor_assert(smartlist_len(matches));
+ smartlist_del(matches, smartlist_len(matches) - 1);
+
+ const char *most_recent_valid_after =
+ consensus_cache_entry_get_value(most_recent, LABEL_VALID_AFTER);
+ if (BUG(most_recent_valid_after == NULL))
+ goto done; //LCOV_EXCL_LINE
+ uint8_t most_recent_sha3[DIGEST256_LEN];
+ if (BUG(cdm_entry_get_sha3_value(most_recent_sha3, most_recent,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0))
+ goto done; //LCOV_EXCL_LINE
+
+ // 2. Find all the relevant diffs _to_ this consensus. These are ones
+ // that we don't need to compute.
+ diffs = smartlist_new();
+ consensus_cache_find_all(diffs, cdm_cache_get(),
+ LABEL_VALID_AFTER, most_recent_valid_after);
+ consensus_cache_filter_list(diffs, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
+ consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname);
+ have_diff_from = strmap_new();
+ SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
+ const char *va = consensus_cache_entry_get_value(diff,
+ LABEL_FROM_VALID_AFTER);
+ if (BUG(va == NULL))
+ continue; // LCOV_EXCL_LINE
+ strmap_set(have_diff_from, va, diff);
+ } SMARTLIST_FOREACH_END(diff);
+
+ // 3. See which consensuses in 'matches' don't have diffs yet.
+ smartlist_reverse(matches); // from newest to oldest.
+ compute_diffs_from = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) {
+ const char *va = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
+ if (BUG(va == NULL))
+ continue; // LCOV_EXCL_LINE
+ if (strmap_get(have_diff_from, va) != NULL)
+ continue; /* we already have this one. */
+ smartlist_add(compute_diffs_from, ent);
+ /* Since we are not going to serve this as the most recent consensus
+ * any more, we should stop keeping it mmap'd when it's not in use.
+ */
+ consensus_cache_entry_mark_for_aggressive_release(ent);
+ } SMARTLIST_FOREACH_END(ent);
+
+ log_info(LD_DIRSERV,
+ "The most recent %s consensus is valid-after %s. We have diffs to "
+ "this consensus for %d/%d older %s consensuses. Generating diffs "
+ "for the other %d.",
+ flavname,
+ most_recent_valid_after,
+ smartlist_len(matches) - smartlist_len(compute_diffs_from),
+ smartlist_len(matches),
+ flavname,
+ smartlist_len(compute_diffs_from));
+
+ // 4. Update the hashtable; remove entries in this flavor to other
+ // target consensuses.
+ cdm_diff_ht_purge(flavor, most_recent_sha3);
+
+ // 5. Actually launch the requests.
+ SMARTLIST_FOREACH_BEGIN(compute_diffs_from, consensus_cache_entry_t *, c) {
+ if (BUG(c == most_recent))
+ continue; // LCOV_EXCL_LINE
+
+ uint8_t this_sha3[DIGEST256_LEN];
+ if (cdm_entry_get_sha3_value(this_sha3, c,
+ LABEL_SHA3_DIGEST_AS_SIGNED)<0) {
+ // Not actually a bug, since we might be running with a directory
+ // with stale files from before the #22143 fixes.
+ continue;
+ }
+ if (cdm_diff_ht_check_and_note_pending(flavor,
+ this_sha3, most_recent_sha3)) {
+ // This is already pending, or we encountered an error.
+ continue;
+ }
+ consensus_diff_queue_diff_work(c, most_recent);
+ } SMARTLIST_FOREACH_END(c);
+
+ done:
+ smartlist_free(matches);
+ smartlist_free(diffs);
+ smartlist_free(compute_diffs_from);
+ strmap_free(have_diff_from, NULL);
+}
+
+/**
+ * Scan the cache for the latest consensuses and add their handles to
+ * latest_consensus
+ */
+static void
+consdiffmgr_consensus_load(void)
+{
+ smartlist_t *matches = smartlist_new();
+ for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
+ const char *flavname = networkstatus_get_flavor_name(flav);
+ smartlist_clear(matches);
+ consensus_cache_find_all(matches, cdm_cache_get(),
+ LABEL_FLAVOR, flavname);
+ consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+ consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches);
+ if (! most_recent)
+ continue; // no consensuses.
+ const char *most_recent_sha3 =
+ consensus_cache_entry_get_value(most_recent,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED);
+ if (BUG(most_recent_sha3 == NULL))
+ continue; // LCOV_EXCL_LINE
+ consensus_cache_filter_list(matches, LABEL_SHA3_DIGEST_UNCOMPRESSED,
+ most_recent_sha3);
+
+ // Everything that remains matches the most recent consensus of this
+ // flavor.
+ SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) {
+ const char *lv_compression =
+ consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
+ compress_method_t method =
+ compression_method_get_by_name(lv_compression);
+ int pos = consensus_compression_method_pos(method);
+ if (pos < 0)
+ continue;
+ consensus_cache_entry_handle_free(latest_consensus[flav][pos]);
+ latest_consensus[flav][pos] = consensus_cache_entry_handle_new(ent);
+ } SMARTLIST_FOREACH_END(ent);
+ }
+ smartlist_free(matches);
+}
+
+/**
+ * Scan the cache for diffs, and add them to the hashtable.
+ */
+static void
+consdiffmgr_diffs_load(void)
+{
+ smartlist_t *diffs = smartlist_new();
+ consensus_cache_find_all(diffs, cdm_cache_get(),
+ LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
+ SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
+ const char *lv_flavor =
+ consensus_cache_entry_get_value(diff, LABEL_FLAVOR);
+ if (!lv_flavor)
+ continue;
+ int flavor = networkstatus_parse_flavor_name(lv_flavor);
+ if (flavor < 0)
+ continue;
+ const char *lv_compression =
+ consensus_cache_entry_get_value(diff, LABEL_COMPRESSION_TYPE);
+ compress_method_t method = NO_METHOD;
+ if (lv_compression) {
+ method = compression_method_get_by_name(lv_compression);
+ if (method == UNKNOWN_METHOD) {
+ continue;
+ }
+ }
+
+ uint8_t from_sha3[DIGEST256_LEN];
+ uint8_t to_sha3[DIGEST256_LEN];
+ if (cdm_entry_get_sha3_value(from_sha3, diff, LABEL_FROM_SHA3_DIGEST)<0)
+ continue;
+ if (cdm_entry_get_sha3_value(to_sha3, diff, LABEL_TARGET_SHA3_DIGEST)<0)
+ continue;
+
+ cdm_diff_ht_set_status(flavor, from_sha3, to_sha3,
+ method,
+ CDM_DIFF_PRESENT,
+ consensus_cache_entry_handle_new(diff));
+ } SMARTLIST_FOREACH_END(diff);
+ smartlist_free(diffs);
+}
+
+/**
+ * Build new diffs as needed.
+ */
+void
+consdiffmgr_rescan(void)
+{
+ if (cdm_cache_dirty == 0)
+ return;
+
+ // Clean up here to make room for new diffs, and to ensure that older
+ // consensuses do not have any entries.
+ consdiffmgr_cleanup();
+
+ if (cdm_cache_loaded == 0) {
+ consdiffmgr_diffs_load();
+ consdiffmgr_consensus_load();
+ cdm_cache_loaded = 1;
+ }
+
+ for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
+ consdiffmgr_rescan_flavor_((consensus_flavor_t) flav);
+ }
+
+ cdm_cache_dirty = 0;
+}
+
+/**
+ * Helper: compare two files by their from-valid-after and valid-after labels,
+ * trying to sort in ascending order by from-valid-after (when present) and
+ * valid-after (when not). Place everything that has neither label first in
+ * the list.
+ */
+static int
+compare_by_staleness_(const void **a, const void **b)
+{
+ const consensus_cache_entry_t *e1 = *a;
+ const consensus_cache_entry_t *e2 = *b;
+ const char *va1, *fva1, *va2, *fva2;
+ va1 = consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER);
+ va2 = consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER);
+ fva1 = consensus_cache_entry_get_value(e1, LABEL_FROM_VALID_AFTER);
+ fva2 = consensus_cache_entry_get_value(e2, LABEL_FROM_VALID_AFTER);
+
+ if (fva1)
+ va1 = fva1;
+ if (fva2)
+ va2 = fva2;
+
+ /* See note about iso-encoded values in compare_by_valid_after_. Also note
+ * that missing dates will get placed first. */
+ return strcmp_opt(va1, va2);
+}
+
+/** If there are not enough unused filenames to store <b>n</b> files, then
+ * delete old consensuses until there are. (We have to keep track of the
+ * number of filenames because of the way that the seccomp2 cache works.)
+ *
+ * Return 0 on success, -1 on failure.
+ **/
+static int
+consdiffmgr_ensure_space_for_files(int n)
+{
+ consensus_cache_t *cache = cdm_cache_get();
+ if (consensus_cache_get_n_filenames_available(cache) >= n) {
+ // there are already enough unused filenames.
+ return 0;
+ }
+ // Try a cheap deletion of stuff that's waiting to get deleted.
+ consensus_cache_delete_pending(cache, 0);
+ if (consensus_cache_get_n_filenames_available(cache) >= n) {
+ // okay, _that_ made enough filenames available.
+ return 0;
+ }
+ // Let's get more assertive: clean out unused stuff, and force-remove
+ // the files.
+ consdiffmgr_cleanup();
+ consensus_cache_delete_pending(cache, 1);
+ const int n_to_remove = n - consensus_cache_get_n_filenames_available(cache);
+ if (n_to_remove <= 0) {
+ // okay, finally!
+ return 0;
+ }
+
+ // At this point, we're going to have to throw out objects that will be
+ // missed. Too bad!
+ smartlist_t *objects = smartlist_new();
+ consensus_cache_find_all(objects, cache, NULL, NULL);
+ smartlist_sort(objects, compare_by_staleness_);
+ int n_marked = 0;
+ SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
+ consensus_cache_entry_mark_for_removal(ent);
+ if (++n_marked >= n_to_remove)
+ break;
+ } SMARTLIST_FOREACH_END(ent);
+ smartlist_free(objects);
+
+ consensus_cache_delete_pending(cache, 1);
+ if (BUG(n_marked < n_to_remove))
+ return -1;
+ else
+ return 0;
+}
+
+/**
+ * Set consensus cache flags on the objects in this consdiffmgr.
+ */
+static void
+consdiffmgr_set_cache_flags(void)
+{
+ /* Right now, we just mark the consensus objects for aggressive release,
+ * so that they get mmapped for as little time as possible. */
+ smartlist_t *objects = smartlist_new();
+ consensus_cache_find_all(objects, cdm_cache_get(), LABEL_DOCTYPE,
+ DOCTYPE_CONSENSUS);
+ SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
+ consensus_cache_entry_mark_for_aggressive_release(ent);
+ } SMARTLIST_FOREACH_END(ent);
+ smartlist_free(objects);
+}
+
+/**
+ * Called before shutdown: drop all storage held by the consdiffmgr.c module.
+ */
+void
+consdiffmgr_free_all(void)
+{
+ cdm_diff_t **diff, **next;
+ for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) {
+ cdm_diff_t *this = *diff;
+ next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
+ cdm_diff_free(this);
+ }
+ int i;
+ unsigned j;
+ for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
+ for (j = 0; j < n_consensus_compression_methods(); ++j) {
+ consensus_cache_entry_handle_free(latest_consensus[i][j]);
+ }
+ }
+ memset(latest_consensus, 0, sizeof(latest_consensus));
+ consensus_cache_free(cons_diff_cache);
+ cons_diff_cache = NULL;
+}
+
+/* =====
+ Thread workers
+ =====*/
+
+typedef struct compressed_result_t {
+ config_line_t *labels;
+ /**
+ * Output: Body of the diff, as compressed.
+ */
+ uint8_t *body;
+ /**
+ * Output: length of body_out
+ */
+ size_t bodylen;
+} compressed_result_t;
+
+/**
+ * Compress the bytestring <b>input</b> of length <b>len</b> using the
+ * <n>n_methods</b> compression methods listed in the array <b>methods</b>.
+ *
+ * For each successful compression, set the fields in the <b>results_out</b>
+ * array in the position corresponding to the compression method. Use
+ * <b>labels_in</b> as a basis for the labels of the result.
+ *
+ * Return 0 if all compression succeeded; -1 if any failed.
+ */
+static int
+compress_multiple(compressed_result_t *results_out, int n_methods,
+ const compress_method_t *methods,
+ const uint8_t *input, size_t len,
+ const config_line_t *labels_in)
+{
+ int rv = 0;
+ int i;
+ for (i = 0; i < n_methods; ++i) {
+ compress_method_t method = methods[i];
+ const char *methodname = compression_method_get_name(method);
+ char *result;
+ size_t sz;
+ if (0 == tor_compress(&result, &sz, (const char*)input, len, method)) {
+ results_out[i].body = (uint8_t*)result;
+ results_out[i].bodylen = sz;
+ results_out[i].labels = config_lines_dup(labels_in);
+ cdm_labels_prepend_sha3(&results_out[i].labels, LABEL_SHA3_DIGEST,
+ results_out[i].body,
+ results_out[i].bodylen);
+ config_line_prepend(&results_out[i].labels,
+ LABEL_COMPRESSION_TYPE,
+ methodname);
+ } else {
+ rv = -1;
+ }
+ }
+ return rv;
+}
+
+/**
+ * Given an array of <b>n</b> compressed_result_t in <b>results</b>,
+ * as produced by compress_multiple, store them all into the
+ * consdiffmgr, and store handles to them in the <b>handles_out</b>
+ * array.
+ *
+ * Return CDM_DIFF_PRESENT if any was stored, and CDM_DIFF_ERROR if none
+ * was stored.
+ */
+static cdm_diff_status_t
+store_multiple(consensus_cache_entry_handle_t **handles_out,
+ int n,
+ const compress_method_t *methods,
+ const compressed_result_t *results,
+ const char *description)
+{
+ cdm_diff_status_t status = CDM_DIFF_ERROR;
+ consdiffmgr_ensure_space_for_files(n);
+
+ int i;
+ for (i = 0; i < n; ++i) {
+ compress_method_t method = methods[i];
+ uint8_t *body_out = results[i].body;
+ size_t bodylen_out = results[i].bodylen;
+ config_line_t *labels = results[i].labels;
+ const char *methodname = compression_method_get_name(method);
+ if (body_out && bodylen_out && labels) {
+ /* Success! Store the results */
+ log_info(LD_DIRSERV, "Adding %s, compressed with %s",
+ description, methodname);
+
+ consensus_cache_entry_t *ent =
+ consensus_cache_add(cdm_cache_get(),
+ labels,
+ body_out,
+ bodylen_out);
+ if (BUG(ent == NULL))
+ continue;
+
+ status = CDM_DIFF_PRESENT;
+ handles_out[i] = consensus_cache_entry_handle_new(ent);
+ consensus_cache_entry_decref(ent);
+ }
+ }
+ return status;
+}
+
+/**
+ * An object passed to a worker thread that will try to produce a consensus
+ * diff.
+ */
+typedef struct consensus_diff_worker_job_t {
+ /**
+ * Input: The consensus to compute the diff from. Holds a reference to the
+ * cache entry, which must not be released until the job is passed back to
+ * the main thread. The body must be mapped into memory in the main thread.
+ */
+ consensus_cache_entry_t *diff_from;
+ /**
+ * Input: The consensus to compute the diff to. Holds a reference to the
+ * cache entry, which must not be released until the job is passed back to
+ * the main thread. The body must be mapped into memory in the main thread.
+ */
+ consensus_cache_entry_t *diff_to;
+
+ /** Output: labels and bodies */
+ compressed_result_t out[ARRAY_LENGTH(compress_diffs_with)];
+} consensus_diff_worker_job_t;
+
+/** Given a consensus_cache_entry_t, check whether it has a label claiming
+ * that it was compressed. If so, uncompress its contents into <b>out</b> and
+ * set <b>outlen</b> to hold their size. If not, just copy the body into
+ * <b>out</b> and set <b>outlen</b> to its length. Return 0 on success,
+ * -1 on failure.
+ *
+ * In all cases, the output is nul-terminated. */
+STATIC int
+uncompress_or_copy(char **out, size_t *outlen,
+ consensus_cache_entry_t *ent)
+{
+ const uint8_t *body;
+ size_t bodylen;
+
+ if (consensus_cache_entry_get_body(ent, &body, &bodylen) < 0)
+ return -1;
+
+ const char *lv_compression =
+ consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
+ compress_method_t method = NO_METHOD;
+
+ if (lv_compression)
+ method = compression_method_get_by_name(lv_compression);
+
+ return tor_uncompress(out, outlen, (const char *)body, bodylen,
+ method, 1, LOG_WARN);
+}
+
+/**
+ * Worker function. This function runs inside a worker thread and receives
+ * a consensus_diff_worker_job_t as its input.
+ */
+static workqueue_reply_t
+consensus_diff_worker_threadfn(void *state_, void *work_)
+{
+ (void)state_;
+ consensus_diff_worker_job_t *job = work_;
+ const uint8_t *diff_from, *diff_to;
+ size_t len_from, len_to;
+ int r;
+ /* We need to have the body already mapped into RAM here.
+ */
+ r = consensus_cache_entry_get_body(job->diff_from, &diff_from, &len_from);
+ if (BUG(r < 0))
+ return WQ_RPL_REPLY; // LCOV_EXCL_LINE
+ r = consensus_cache_entry_get_body(job->diff_to, &diff_to, &len_to);
+ if (BUG(r < 0))
+ return WQ_RPL_REPLY; // LCOV_EXCL_LINE
+
+ const char *lv_to_valid_after =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_AFTER);
+ const char *lv_to_fresh_until =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_FRESH_UNTIL);
+ const char *lv_to_valid_until =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_UNTIL);
+ const char *lv_to_signatories =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_SIGNATORIES);
+ const char *lv_from_valid_after =
+ consensus_cache_entry_get_value(job->diff_from, LABEL_VALID_AFTER);
+ const char *lv_from_digest =
+ consensus_cache_entry_get_value(job->diff_from,
+ LABEL_SHA3_DIGEST_AS_SIGNED);
+ const char *lv_from_flavor =
+ consensus_cache_entry_get_value(job->diff_from, LABEL_FLAVOR);
+ const char *lv_to_flavor =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR);
+ const char *lv_to_digest =
+ consensus_cache_entry_get_value(job->diff_to,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED);
+
+ if (! lv_from_digest) {
+ /* This isn't a bug right now, since it can happen if you're migrating
+ * from an older version of master to a newer one. The older ones didn't
+ * annotate their stored consensus objects with sha3-digest-as-signed.
+ */
+ return WQ_RPL_REPLY; // LCOV_EXCL_LINE
+ }
+
+ /* All these values are mandatory on the input */
+ if (BUG(!lv_to_valid_after) ||
+ BUG(!lv_from_valid_after) ||
+ BUG(!lv_from_flavor) ||
+ BUG(!lv_to_flavor)) {
+ return WQ_RPL_REPLY; // LCOV_EXCL_LINE
+ }
+ /* The flavors need to match */
+ if (BUG(strcmp(lv_from_flavor, lv_to_flavor))) {
+ return WQ_RPL_REPLY; // LCOV_EXCL_LINE
+ }
+
+ char *consensus_diff;
+ {
+ char *diff_from_nt = NULL, *diff_to_nt = NULL;
+ size_t diff_from_nt_len, diff_to_nt_len;
+
+ if (uncompress_or_copy(&diff_from_nt, &diff_from_nt_len,
+ job->diff_from) < 0) {
+ return WQ_RPL_REPLY;
+ }
+ if (uncompress_or_copy(&diff_to_nt, &diff_to_nt_len,
+ job->diff_to) < 0) {
+ tor_free(diff_from_nt);
+ return WQ_RPL_REPLY;
+ }
+ tor_assert(diff_from_nt);
+ tor_assert(diff_to_nt);
+
+ // XXXX ugh; this is going to calculate the SHA3 of both its
+ // XXXX inputs again, even though we already have that. Maybe it's time
+ // XXXX to change the API here?
+ consensus_diff = consensus_diff_generate(diff_from_nt, diff_to_nt);
+ tor_free(diff_from_nt);
+ tor_free(diff_to_nt);
+ }
+ if (!consensus_diff) {
+ /* Couldn't generate consensus; we'll leave the reply blank. */
+ return WQ_RPL_REPLY;
+ }
+
+ /* Compress the results and send the reply */
+ tor_assert(compress_diffs_with[0] == NO_METHOD);
+ size_t difflen = strlen(consensus_diff);
+ job->out[0].body = (uint8_t *) consensus_diff;
+ job->out[0].bodylen = difflen;
+
+ config_line_t *common_labels = NULL;
+ if (lv_to_valid_until)
+ config_line_prepend(&common_labels, LABEL_VALID_UNTIL, lv_to_valid_until);
+ if (lv_to_fresh_until)
+ config_line_prepend(&common_labels, LABEL_FRESH_UNTIL, lv_to_fresh_until);
+ if (lv_to_signatories)
+ config_line_prepend(&common_labels, LABEL_SIGNATORIES, lv_to_signatories);
+ cdm_labels_prepend_sha3(&common_labels,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED,
+ job->out[0].body,
+ job->out[0].bodylen);
+ config_line_prepend(&common_labels, LABEL_FROM_VALID_AFTER,
+ lv_from_valid_after);
+ config_line_prepend(&common_labels, LABEL_VALID_AFTER,
+ lv_to_valid_after);
+ config_line_prepend(&common_labels, LABEL_FLAVOR, lv_from_flavor);
+ config_line_prepend(&common_labels, LABEL_FROM_SHA3_DIGEST,
+ lv_from_digest);
+ config_line_prepend(&common_labels, LABEL_TARGET_SHA3_DIGEST,
+ lv_to_digest);
+ config_line_prepend(&common_labels, LABEL_DOCTYPE,
+ DOCTYPE_CONSENSUS_DIFF);
+
+ job->out[0].labels = config_lines_dup(common_labels);
+ cdm_labels_prepend_sha3(&job->out[0].labels,
+ LABEL_SHA3_DIGEST,
+ job->out[0].body,
+ job->out[0].bodylen);
+
+ compress_multiple(job->out+1,
+ n_diff_compression_methods()-1,
+ compress_diffs_with+1,
+ (const uint8_t*)consensus_diff, difflen, common_labels);
+
+ config_free_lines(common_labels);
+ return WQ_RPL_REPLY;
+}
+
+/**
+ * Helper: release all storage held in <b>job</b>.
+ */
+static void
+consensus_diff_worker_job_free(consensus_diff_worker_job_t *job)
+{
+ if (!job)
+ return;
+ unsigned u;
+ for (u = 0; u < n_diff_compression_methods(); ++u) {
+ config_free_lines(job->out[u].labels);
+ tor_free(job->out[u].body);
+ }
+ consensus_cache_entry_decref(job->diff_from);
+ consensus_cache_entry_decref(job->diff_to);
+ tor_free(job);
+}
+
+/**
+ * Worker function: This function runs in the main thread, and receives
+ * a consensus_diff_worker_job_t that the worker thread has already
+ * processed.
+ */
+static void
+consensus_diff_worker_replyfn(void *work_)
+{
+ tor_assert(in_main_thread());
+ tor_assert(work_);
+
+ consensus_diff_worker_job_t *job = work_;
+
+ const char *lv_from_digest =
+ consensus_cache_entry_get_value(job->diff_from,
+ LABEL_SHA3_DIGEST_AS_SIGNED);
+ const char *lv_to_digest =
+ consensus_cache_entry_get_value(job->diff_to,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED);
+ const char *lv_flavor =
+ consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR);
+ if (BUG(lv_from_digest == NULL))
+ lv_from_digest = "???"; // LCOV_EXCL_LINE
+ if (BUG(lv_to_digest == NULL))
+ lv_to_digest = "???"; // LCOV_EXCL_LINE
+
+ uint8_t from_sha3[DIGEST256_LEN];
+ uint8_t to_sha3[DIGEST256_LEN];
+ int flav = -1;
+ int cache = 1;
+ if (BUG(cdm_entry_get_sha3_value(from_sha3, job->diff_from,
+ LABEL_SHA3_DIGEST_AS_SIGNED) < 0))
+ cache = 0;
+ if (BUG(cdm_entry_get_sha3_value(to_sha3, job->diff_to,
+ LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0))
+ cache = 0;
+ if (BUG(lv_flavor == NULL)) {
+ cache = 0;
+ } else if ((flav = networkstatus_parse_flavor_name(lv_flavor)) < 0) {
+ cache = 0;
+ }
+
+ consensus_cache_entry_handle_t *handles[ARRAY_LENGTH(compress_diffs_with)];
+ memset(handles, 0, sizeof(handles));
+
+ char description[128];
+ tor_snprintf(description, sizeof(description),
+ "consensus diff from %s to %s",
+ lv_from_digest, lv_to_digest);
+
+ int status = store_multiple(handles,
+ n_diff_compression_methods(),
+ compress_diffs_with,
+ job->out,
+ description);
+
+ if (status != CDM_DIFF_PRESENT) {
+ /* Failure! Nothing to do but complain */
+ log_warn(LD_DIRSERV,
+ "Worker was unable to compute consensus diff "
+ "from %s to %s", lv_from_digest, lv_to_digest);
+ /* Cache this error so we don't try to compute this one again. */
+ status = CDM_DIFF_ERROR;
+ }
+
+ unsigned u;
+ for (u = 0; u < ARRAY_LENGTH(handles); ++u) {
+ compress_method_t method = compress_diffs_with[u];
+ if (cache) {
+ cdm_diff_ht_set_status(flav, from_sha3, to_sha3, method, status,
+ handles[u]);
+ } else {
+ consensus_cache_entry_handle_free(handles[u]);
+ }
+ }
+
+ consensus_diff_worker_job_free(job);
+}
+
+/**
+ * Queue the job of computing the diff from <b>diff_from</b> to <b>diff_to</b>
+ * in a worker thread.
+ */
+static int
+consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from,
+ consensus_cache_entry_t *diff_to)
+{
+ tor_assert(in_main_thread());
+
+ consensus_cache_entry_incref(diff_from);
+ consensus_cache_entry_incref(diff_to);
+
+ consensus_diff_worker_job_t *job = tor_malloc_zero(sizeof(*job));
+ job->diff_from = diff_from;
+ job->diff_to = diff_to;
+
+ /* Make sure body is mapped. */
+ const uint8_t *body;
+ size_t bodylen;
+ int r1 = consensus_cache_entry_get_body(diff_from, &body, &bodylen);
+ int r2 = consensus_cache_entry_get_body(diff_to, &body, &bodylen);
+ if (r1 < 0 || r2 < 0)
+ goto err;
+
+ workqueue_entry_t *work;
+ work = cpuworker_queue_work(WQ_PRI_LOW,
+ consensus_diff_worker_threadfn,
+ consensus_diff_worker_replyfn,
+ job);
+ if (!work)
+ goto err;
+
+ return 0;
+ err:
+ consensus_diff_worker_job_free(job); // includes decrefs.
+ return -1;
+}
+
+/**
+ * Holds requests and replies for consensus_compress_workers.
+ */
+typedef struct consensus_compress_worker_job_t {
+ char *consensus;
+ size_t consensus_len;
+ consensus_flavor_t flavor;
+ config_line_t *labels_in;
+ compressed_result_t out[ARRAY_LENGTH(compress_consensus_with)];
+} consensus_compress_worker_job_t;
+
+/**
+ * Free all resources held in <b>job</b>
+ */
+static void
+consensus_compress_worker_job_free(consensus_compress_worker_job_t *job)
+{
+ if (!job)
+ return;
+ tor_free(job->consensus);
+ config_free_lines(job->labels_in);
+ unsigned u;
+ for (u = 0; u < n_consensus_compression_methods(); ++u) {
+ config_free_lines(job->out[u].labels);
+ tor_free(job->out[u].body);
+ }
+ tor_free(job);
+}
+/**
+ * Worker function. This function runs inside a worker thread and receives
+ * a consensus_compress_worker_job_t as its input.
+ */
+static workqueue_reply_t
+consensus_compress_worker_threadfn(void *state_, void *work_)
+{
+ (void)state_;
+ consensus_compress_worker_job_t *job = work_;
+ consensus_flavor_t flavor = job->flavor;
+ const char *consensus = job->consensus;
+ size_t bodylen = job->consensus_len;
+
+ config_line_t *labels = config_lines_dup(job->labels_in);
+ const char *flavname = networkstatus_get_flavor_name(flavor);
+
+ cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_UNCOMPRESSED,
+ (const uint8_t *)consensus, bodylen);
+ {
+ const char *start, *end;
+ if (router_get_networkstatus_v3_signed_boundaries(consensus,
+ &start, &end) < 0) {
+ start = consensus;
+ end = consensus+bodylen;
+ }
+ cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_AS_SIGNED,
+ (const uint8_t *)start,
+ end - start);
+ }
+ config_line_prepend(&labels, LABEL_FLAVOR, flavname);
+ config_line_prepend(&labels, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
+
+ compress_multiple(job->out,
+ n_consensus_compression_methods(),
+ compress_consensus_with,
+ (const uint8_t*)consensus, bodylen, labels);
+ config_free_lines(labels);
+ return WQ_RPL_REPLY;
+}
+
+/**
+ * Worker function: This function runs in the main thread, and receives
+ * a consensus_diff_compress_job_t that the worker thread has already
+ * processed.
+ */
+static void
+consensus_compress_worker_replyfn(void *work_)
+{
+ consensus_compress_worker_job_t *job = work_;
+
+ consensus_cache_entry_handle_t *handles[
+ ARRAY_LENGTH(compress_consensus_with)];
+ memset(handles, 0, sizeof(handles));
+
+ store_multiple(handles,
+ n_consensus_compression_methods(),
+ compress_consensus_with,
+ job->out,
+ "consensus");
+ cdm_cache_dirty = 1;
+
+ unsigned u;
+ consensus_flavor_t f = job->flavor;
+ tor_assert((int)f < N_CONSENSUS_FLAVORS);
+ for (u = 0; u < ARRAY_LENGTH(handles); ++u) {
+ if (handles[u] == NULL)
+ continue;
+ consensus_cache_entry_handle_free(latest_consensus[f][u]);
+ latest_consensus[f][u] = handles[u];
+ }
+
+ consensus_compress_worker_job_free(job);
+}
+
+/**
+ * If true, we compress in worker threads.
+ */
+static int background_compression = 0;
+
+/**
+ * Queue a job to compress <b>consensus</b> and store its compressed
+ * text in the cache.
+ */
+static int
+consensus_queue_compression_work(const char *consensus,
+ const networkstatus_t *as_parsed)
+{
+ tor_assert(consensus);
+ tor_assert(as_parsed);
+
+ consensus_compress_worker_job_t *job = tor_malloc_zero(sizeof(*job));
+ job->consensus = tor_strdup(consensus);
+ job->consensus_len = strlen(consensus);
+ job->flavor = as_parsed->flavor;
+
+ char va_str[ISO_TIME_LEN+1];
+ char vu_str[ISO_TIME_LEN+1];
+ char fu_str[ISO_TIME_LEN+1];
+ format_iso_time_nospace(va_str, as_parsed->valid_after);
+ format_iso_time_nospace(fu_str, as_parsed->fresh_until);
+ format_iso_time_nospace(vu_str, as_parsed->valid_until);
+ config_line_append(&job->labels_in, LABEL_VALID_AFTER, va_str);
+ config_line_append(&job->labels_in, LABEL_FRESH_UNTIL, fu_str);
+ config_line_append(&job->labels_in, LABEL_VALID_UNTIL, vu_str);
+ if (as_parsed->voters) {
+ smartlist_t *hexvoters = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(as_parsed->voters,
+ networkstatus_voter_info_t *, vi) {
+ if (smartlist_len(vi->sigs) == 0)
+ continue; // didn't sign.
+ char d[HEX_DIGEST_LEN+1];
+ base16_encode(d, sizeof(d), vi->identity_digest, DIGEST_LEN);
+ smartlist_add_strdup(hexvoters, d);
+ } SMARTLIST_FOREACH_END(vi);
+ char *signers = smartlist_join_strings(hexvoters, ",", 0, NULL);
+ config_line_prepend(&job->labels_in, LABEL_SIGNATORIES, signers);
+ tor_free(signers);
+ SMARTLIST_FOREACH(hexvoters, char *, cp, tor_free(cp));
+ smartlist_free(hexvoters);
+ }
+
+ if (background_compression) {
+ workqueue_entry_t *work;
+ work = cpuworker_queue_work(WQ_PRI_LOW,
+ consensus_compress_worker_threadfn,
+ consensus_compress_worker_replyfn,
+ job);
+ if (!work) {
+ consensus_compress_worker_job_free(job);
+ return -1;
+ }
+
+ return 0;
+ } else {
+ consensus_compress_worker_threadfn(NULL, job);
+ consensus_compress_worker_replyfn(job);
+ return 0;
+ }
+}
+
+/**
+ * Tell the consdiffmgr backend to compress consensuses in worker threads.
+ */
+void
+consdiffmgr_enable_background_compression(void)
+{
+ // This isn't the default behavior because it would break unit tests.
+ background_compression = 1;
+}
+
+/** Read the set of voters from the cached object <b>ent</b> into
+ * <b>out</b>, as a list of hex-encoded digests. Return 0 on success,
+ * -1 if no signatories were recorded. */
+int
+consensus_cache_entry_get_voter_id_digests(const consensus_cache_entry_t *ent,
+ smartlist_t *out)
+{
+ tor_assert(ent);
+ tor_assert(out);
+ const char *s;
+ s = consensus_cache_entry_get_value(ent, LABEL_SIGNATORIES);
+ if (s == NULL)
+ return -1;
+ smartlist_split_string(out, s, ",", SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE, 0);
+ return 0;
+}
+
+/** Read the fresh-until time of cached object <b>ent</b> into *<b>out</b>
+ * and return 0, or return -1 if no such time was recorded. */
+int
+consensus_cache_entry_get_fresh_until(const consensus_cache_entry_t *ent,
+ time_t *out)
+{
+ tor_assert(ent);
+ tor_assert(out);
+ const char *s;
+ s = consensus_cache_entry_get_value(ent, LABEL_FRESH_UNTIL);
+ if (s == NULL || parse_iso_time_nospace(s, out) < 0)
+ return -1;
+ else
+ return 0;
+}
+
+/** Read the valid until timestamp from the cached object <b>ent</b> into
+ * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
+int
+consensus_cache_entry_get_valid_until(const consensus_cache_entry_t *ent,
+ time_t *out)
+{
+ tor_assert(ent);
+ tor_assert(out);
+
+ const char *s;
+ s = consensus_cache_entry_get_value(ent, LABEL_VALID_UNTIL);
+ if (s == NULL || parse_iso_time_nospace(s, out) < 0)
+ return -1;
+ else
+ return 0;
+}
+
+/** Read the valid after timestamp from the cached object <b>ent</b> into
+ * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
+int
+consensus_cache_entry_get_valid_after(const consensus_cache_entry_t *ent,
+ time_t *out)
+{
+ tor_assert(ent);
+ tor_assert(out);
+
+ const char *s;
+ s = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
+
+ if (s == NULL || parse_iso_time_nospace(s, out) < 0)
+ return -1;
+ else
+ return 0;
+}
+
diff --git a/src/or/consdiffmgr.h b/src/or/consdiffmgr.h
new file mode 100644
index 0000000000..079f9fe2d2
--- /dev/null
+++ b/src/or/consdiffmgr.h
@@ -0,0 +1,74 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_CONSDIFFMGR_H
+#define TOR_CONSDIFFMGR_H
+
+/**
+ * Possible outcomes from trying to look up a given consensus diff.
+ */
+typedef enum consdiff_status_t {
+ CONSDIFF_AVAILABLE,
+ CONSDIFF_NOT_FOUND,
+ CONSDIFF_IN_PROGRESS,
+} consdiff_status_t;
+
+typedef struct consdiff_cfg_t {
+ int32_t cache_max_num;
+} consdiff_cfg_t;
+
+struct consensus_cache_entry_t; // from conscache.h
+
+int consdiffmgr_add_consensus(const char *consensus,
+ const networkstatus_t *as_parsed);
+
+consdiff_status_t consdiffmgr_find_consensus(
+ struct consensus_cache_entry_t **entry_out,
+ consensus_flavor_t flavor,
+ compress_method_t method);
+
+consdiff_status_t consdiffmgr_find_diff_from(
+ struct consensus_cache_entry_t **entry_out,
+ consensus_flavor_t flavor,
+ int digest_type,
+ const uint8_t *digest,
+ size_t digestlen,
+ compress_method_t method);
+
+int consensus_cache_entry_get_voter_id_digests(
+ const struct consensus_cache_entry_t *ent,
+ smartlist_t *out);
+int consensus_cache_entry_get_fresh_until(
+ const struct consensus_cache_entry_t *ent,
+ time_t *out);
+int consensus_cache_entry_get_valid_until(
+ const struct consensus_cache_entry_t *ent,
+ time_t *out);
+int consensus_cache_entry_get_valid_after(
+ const struct consensus_cache_entry_t *ent,
+ time_t *out);
+
+void consdiffmgr_rescan(void);
+int consdiffmgr_cleanup(void);
+void consdiffmgr_enable_background_compression(void);
+void consdiffmgr_configure(const consdiff_cfg_t *cfg);
+struct sandbox_cfg_elem;
+int consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem **cfg);
+void consdiffmgr_free_all(void);
+int consdiffmgr_validate(void);
+
+#ifdef CONSDIFFMGR_PRIVATE
+STATIC unsigned n_diff_compression_methods(void);
+STATIC unsigned n_consensus_compression_methods(void);
+STATIC consensus_cache_t *cdm_cache_get(void);
+STATIC consensus_cache_entry_t *cdm_cache_lookup_consensus(
+ consensus_flavor_t flavor, time_t valid_after);
+STATIC int cdm_entry_get_sha3_value(uint8_t *digest_out,
+ consensus_cache_entry_t *ent,
+ const char *label);
+STATIC int uncompress_or_copy(char **out, size_t *outlen,
+ consensus_cache_entry_t *ent);
+#endif
+
+#endif
+
diff --git a/src/or/control.c b/src/or/control.c
index e06d7d28a2..724d4b35c0 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -1,17 +1,42 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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
#include "or.h"
#include "addressmap.h"
+#include "bridges.h"
#include "buffers.h"
#include "channel.h"
#include "channeltls.h"
@@ -33,7 +58,9 @@
#include "entrynodes.h"
#include "geoip.h"
#include "hibernate.h"
+#include "hs_common.h"
#include "main.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
@@ -45,17 +72,14 @@
#include "router.h"
#include "routerlist.h"
#include "routerparse.h"
+#include "shared_random.h"
#ifndef _WIN32
#include <pwd.h>
#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 +214,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 +622,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 +657,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;
}
@@ -777,7 +803,7 @@ queued_events_flush_all(int force)
}
/** Libevent callback: Flushes pending events to controllers that are
- * interested in them */
+ * interested in them. */
static void
flush_queued_events_cb(evutil_socket_t fd, short what, void *arg)
{
@@ -873,7 +899,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();
@@ -919,7 +946,7 @@ control_setconf_helper(control_connection_t *conn, uint32_t len, char *body,
++body;
}
- smartlist_add(entries, tor_strdup(""));
+ smartlist_add_strdup(entries, "");
config = smartlist_join_strings(entries, "\n", 0, NULL);
SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp));
smartlist_free(entries);
@@ -933,7 +960,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 +1238,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 +1290,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 +1399,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 +1411,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 +1439,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 +1450,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;
@@ -1434,8 +1463,10 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len,
const char *body)
{
(void) len;
- (void) body;
- if (options_save_current()<0) {
+
+ int force = !strcmpstart(body, "FORCE");
+ const or_options_t *options = get_options();
+ if ((!force && options->IncludeUsed) || options_save_current() < 0) {
connection_write_str_to_buf(
"551 Unable to write configuration to disk.\r\n", conn);
} else {
@@ -1649,6 +1680,8 @@ getinfo_helper_misc(control_connection_t *conn, const char *question,
*answer = tor_strdup(a);
} else if (!strcmp(question, "config-text")) {
*answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
+ } else if (!strcmp(question, "config-can-saveconf")) {
+ *answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1");
} else if (!strcmp(question, "info/names")) {
*answer = list_getinfo_options();
} else if (!strcmp(question, "dormant")) {
@@ -1679,7 +1712,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 +1757,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())) {
@@ -1847,34 +1878,50 @@ getinfo_helper_listeners(control_connection_t *control_conn,
/** Implementation helper for GETINFO: knows the answers for questions about
* directory information. */
-static int
+STATIC int
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) {
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/id/* "
+ "instead of desc/id/*.";
+ return 0;
}
} else if (!strcmpstart(question, "desc/name/")) {
- /* 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) {
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/name/* "
+ "instead of desc/name/*.";
+ return 0;
}
+ } else if (!strcmp(question, "desc/download-enabled")) {
+ int r = we_fetch_router_descriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmp(question, "desc/all-recent")) {
routerlist_t *routerlist = router_get_routerlist();
smartlist_t *sl = smartlist_new();
@@ -1951,7 +1998,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 */
@@ -1960,8 +2007,13 @@ getinfo_helper_dir(control_connection_t *control_conn,
if (md && md->body) {
*answer = tor_strndup(md->body, md->bodylen);
}
+ } else if (!strcmp(question, "md/download-enabled")) {
+ int r = we_fetch_microdescriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmpstart(question, "desc-annotations/id/")) {
- 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) {
@@ -2002,7 +2054,7 @@ getinfo_helper_dir(control_connection_t *control_conn,
} else if (!strcmpstart(question, "dir/status/")) {
*answer = tor_strdup("");
} else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */
- if (directory_caches_dir_info(get_options())) {
+ if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) {
const cached_dir_t *consensus = dirserv_get_consensus("ns");
if (consensus)
*answer = tor_strdup(consensus->dir);
@@ -2018,6 +2070,12 @@ getinfo_helper_dir(control_connection_t *control_conn,
}
}
} else if (!strcmp(question, "network-status")) { /* v1 */
+ static int network_status_warned = 0;
+ if (!network_status_warned) {
+ log_warn(LD_CONTROL, "GETINFO network-status is deprecated; it will "
+ "go away in a future version of Tor.");
+ network_status_warned = 1;
+ }
routerlist_t *routerlist = router_get_routerlist();
if (!routerlist || !routerlist->routers ||
list_server_status_v1(routerlist->routers, answer, 1) < 0) {
@@ -2028,7 +2086,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 +2109,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 *
@@ -2107,7 +2571,7 @@ circuit_describe_status_for_controller(origin_circuit_t *circ)
if (circ->rend_data != NULL) {
smartlist_add_asprintf(descparts, "REND_QUERY=%s",
- circ->rend_data->onion_address);
+ rend_data_get_address(circ->rend_data));
}
{
@@ -2148,6 +2612,7 @@ getinfo_helper_events(control_connection_t *control_conn,
const char *question, char **answer,
const char **errmsg)
{
+ const or_options_t *options = get_options();
(void) control_conn;
if (!strcmp(question, "circuit-status")) {
smartlist_t *status = smartlist_new();
@@ -2161,6 +2626,8 @@ getinfo_helper_events(control_connection_t *control_conn,
if (circ->base_.state == CIRCUIT_STATE_OPEN)
state = "BUILT";
+ else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT)
+ state = "GUARD_WAIT";
else if (circ->cpath)
state = "EXTENDED";
else
@@ -2284,17 +2751,19 @@ getinfo_helper_events(control_connection_t *control_conn,
*answer = tor_strdup(directories_have_accepted_server_descriptor()
? "1" : "0");
} else if (!strcmp(question, "status/reachability-succeeded/or")) {
- *answer = tor_strdup(check_whether_orport_reachable() ? "1" : "0");
+ *answer = tor_strdup(check_whether_orport_reachable(options) ?
+ "1" : "0");
} else if (!strcmp(question, "status/reachability-succeeded/dir")) {
- *answer = tor_strdup(check_whether_dirport_reachable() ? "1" : "0");
+ *answer = tor_strdup(check_whether_dirport_reachable(options) ?
+ "1" : "0");
} else if (!strcmp(question, "status/reachability-succeeded")) {
tor_asprintf(answer, "OR=%d DIR=%d",
- check_whether_orport_reachable() ? 1 : 0,
- check_whether_dirport_reachable() ? 1 : 0);
+ check_whether_orport_reachable(options) ? 1 : 0,
+ check_whether_dirport_reachable(options) ? 1 : 0);
} else if (!strcmp(question, "status/bootstrap-phase")) {
*answer = tor_strdup(last_sent_bootstrap_message);
} else if (!strcmpstart(question, "status/version/")) {
- int is_server = server_mode(get_options());
+ int is_server = server_mode(options);
networkstatus_t *c = networkstatus_get_latest_consensus();
version_status_t status;
const char *recommended;
@@ -2336,7 +2805,7 @@ getinfo_helper_events(control_connection_t *control_conn,
}
*answer = bridge_stats;
} else if (!strcmp(question, "status/fresh-relay-descs")) {
- if (!server_mode(get_options())) {
+ if (!server_mode(options)) {
*errmsg = "Only relays have descriptors";
return -1;
}
@@ -2384,12 +2853,13 @@ getinfo_helper_events(control_connection_t *control_conn,
/** Implementation helper for GETINFO: knows how to enumerate hidden services
* created via the control port. */
-static int
+STATIC int
getinfo_helper_onions(control_connection_t *control_conn,
const char *question, char **answer,
const char **errmsg)
{
smartlist_t *onion_list = NULL;
+ (void) errmsg; /* no errors from this method */
if (control_conn && !strcmp(question, "onions/current")) {
onion_list = control_conn->ephemeral_onion_services;
@@ -2399,13 +2869,13 @@ getinfo_helper_onions(control_connection_t *control_conn,
return 0;
}
if (!onion_list || smartlist_len(onion_list) == 0) {
- if (errmsg) {
- *errmsg = "No onion services of the specified type.";
+ if (answer) {
+ *answer = tor_strdup("");
}
- return -1;
- }
- if (answer) {
+ } else {
+ if (answer) {
*answer = smartlist_join_strings(onion_list, "\r\n", 0, NULL);
+ }
}
return 0;
@@ -2431,12 +2901,33 @@ getinfo_helper_liveness(control_connection_t *control_conn,
return 0;
}
+/** Implementation helper for GETINFO: answers queries about shared random
+ * value. */
+static int
+getinfo_helper_sr(control_connection_t *control_conn,
+ const char *question, char **answer,
+ const char **errmsg)
+{
+ (void) control_conn;
+ (void) errmsg;
+
+ if (!strcmp(question, "sr/current")) {
+ *answer = sr_get_current_for_control();
+ } else if (!strcmp(question, "sr/previous")) {
+ *answer = sr_get_previous_for_control();
+ }
+ /* Else statement here is unrecognized key so do nothing. */
+
+ return 0;
+}
+
/** Callback function for GETINFO: on a given control connection, try to
* answer the question <b>q</b> and store the newly-allocated answer in
* *<b>a</b>. If an internal error occurs, return -1 and optionally set
* *<b>error_out</b> to point to an error message to be delivered to the
* controller. On success, _or if the key is not recognized_, return 0. Do not
- * set <b>a</b> if the key is not recognized.
+ * set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
+ * to improve the error message.
*/
typedef int (*getinfo_helper_t)(control_connection_t *,
const char *q, char **a,
@@ -2464,6 +2955,8 @@ static const getinfo_item_t getinfo_items[] = {
ITEM("config-defaults-file", misc, "Current location of the defaults file."),
ITEM("config-text", misc,
"Return the string that would be written by a saveconf command."),
+ ITEM("config-can-saveconf", misc,
+ "Is it possible to save the configuration to the \"torrc\" file?"),
ITEM("accounting/bytes", accounting,
"Number of bytes read/written so far in the accounting interval."),
ITEM("accounting/bytes-left", accounting,
@@ -2486,6 +2979,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,
@@ -2496,9 +3032,13 @@ static const getinfo_item_t getinfo_items[] = {
PREFIX("desc/name/", dir, "Router descriptors by nickname."),
ITEM("desc/all-recent", dir,
"All non-expired, non-superseded router descriptors."),
+ ITEM("desc/download-enabled", dir,
+ "Do we try to download router descriptors?"),
ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
PREFIX("md/id/", dir, "Microdescriptors by ID"),
PREFIX("md/name/", dir, "Microdescriptors by name"),
+ ITEM("md/download-enabled", dir,
+ "Do we try to download microdescriptors?"),
PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
PREFIX("hs/client/desc/id", dir,
"Hidden Service descriptor in client's cache by onion."),
@@ -2558,7 +3098,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,
@@ -2572,7 +3111,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"),
@@ -2581,6 +3120,8 @@ static const getinfo_item_t getinfo_items[] = {
"Onion services owned by the current control connection."),
ITEM("onions/detached", onions,
"Onion services detached from the control connection."),
+ ITEM("sr/current", sr, "Get current shared random value."),
+ ITEM("sr/previous", sr, "Get previous shared random value."),
{ NULL, NULL, NULL, 0 }
};
@@ -2645,7 +3186,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_t *questions = smartlist_new();
smartlist_t *answers = smartlist_new();
smartlist_t *unrecognized = smartlist_new();
- char *msg = NULL, *ans = NULL;
+ char *ans = NULL;
int i;
(void) len; /* body is NUL-terminated, so it's safe to ignore the length. */
@@ -2660,20 +3201,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
goto done;
}
if (!ans) {
- smartlist_add(unrecognized, (char*)q);
+ if (errmsg) /* use provided error message */
+ smartlist_add_strdup(unrecognized, errmsg);
+ else /* use default error message */
+ smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q);
} else {
- smartlist_add(answers, tor_strdup(q));
+ smartlist_add_strdup(answers, q);
smartlist_add(answers, ans);
}
} SMARTLIST_FOREACH_END(q);
+
if (smartlist_len(unrecognized)) {
+ /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */
for (i=0; i < smartlist_len(unrecognized)-1; ++i)
connection_printf_to_buf(conn,
- "552-Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552-%s\r\n",
+ (char *)smartlist_get(unrecognized, i));
+
connection_printf_to_buf(conn,
- "552 Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552 %s\r\n",
+ (char *)smartlist_get(unrecognized, i));
goto done;
}
@@ -2700,8 +3247,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_free(answers);
SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
smartlist_free(questions);
+ SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
smartlist_free(unrecognized);
- tor_free(msg);
return 0;
}
@@ -2873,7 +3420,8 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
SMARTLIST_FOREACH(nodes, const node_t *, node,
{
extend_info_t *info = extend_info_from_node(node, first_node);
- if (first_node && !info) {
+ if (!info) {
+ tor_assert_nonfatal(first_node);
log_warn(LD_CONTROL,
"controller tried to connect to a node that doesn't have any "
"addresses that are allowed by the firewall configuration; "
@@ -2881,10 +3429,6 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED);
connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn);
goto done;
- } else {
- /* True, since node_has_descriptor(node) == true and we are extending
- * to the node's primary address */
- tor_assert(info);
}
circuit_append_new_exit(circ, info);
extend_info_free(info);
@@ -2900,7 +3444,8 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
goto done;
}
} else {
- if (circ->base_.state == CIRCUIT_STATE_OPEN) {
+ if (circ->base_.state == CIRCUIT_STATE_OPEN ||
+ circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) {
int err_reason = 0;
circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) {
@@ -3042,24 +3587,9 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len,
}
/* Is this a single hop circuit? */
if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) {
- const node_t *node = NULL;
- char *exit_digest = NULL;
- if (circ->build_state &&
- circ->build_state->chosen_exit &&
- !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) {
- exit_digest = circ->build_state->chosen_exit->identity_digest;
- node = node_get_by_id(exit_digest);
- }
- /* Do both the client and relay allow one-hop exit circuits? */
- if (!node ||
- !node_allows_single_hop_exits(node) ||
- !get_options()->AllowSingleHopCircuits) {
- connection_write_str_to_buf(
- "551 Can't attach stream to this one-hop circuit.\r\n", conn);
- return 0;
- }
- tor_assert(exit_digest);
- ap_conn->chosen_exit_name = tor_strdup(hex_str(exit_digest, DIGEST_LEN));
+ connection_write_str_to_buf(
+ "551 Can't attach stream to this one-hop circuit.\r\n", conn);
+ return 0;
}
if (circ && hop>0) {
@@ -3442,7 +3972,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));
@@ -3558,6 +4089,14 @@ handle_control_dropguards(control_connection_t *conn,
smartlist_split_string(args, body, " ",
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ static int have_warned = 0;
+ if (! have_warned) {
+ log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand "
+ "the risks before using it. It may be removed in a future "
+ "version of Tor.");
+ have_warned = 1;
+ }
+
if (smartlist_len(args)) {
connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n");
} else {
@@ -3603,7 +4142,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
* of the id. */
desc_id = digest;
} else {
- connection_printf_to_buf(conn, "513 Unrecognized \"%s\"\r\n",
+ connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n",
arg1);
goto done;
}
@@ -3788,14 +4327,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)) {
@@ -3826,10 +4371,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;
@@ -3848,6 +4400,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",
@@ -3860,6 +4416,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;
@@ -3868,6 +4460,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. */
@@ -3888,35 +4505,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();
@@ -3927,9 +4530,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:
@@ -3941,6 +4561,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");
@@ -3957,6 +4580,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);
@@ -4065,6 +4698,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
@@ -4210,21 +4902,48 @@ 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);
}
+static int
+peek_connection_has_http_command(connection_t *conn)
+{
+ return peek_buf_has_http_command(conn->inbuf);
+}
+
+static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy"
+ "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor's ControlPort is not an HTTP proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor's control port"
+ " as an HTTP proxy.\n"
+ "This is not correct: Tor's default SOCKS proxy port is 9050.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Called when data has arrived on a v1 control connection: Try to fetch
* commands from conn->inbuf, and execute them.
*/
@@ -4264,6 +4983,15 @@ connection_control_process_inbuf(control_connection_t *conn)
return 0;
}
+ /* If the user has the HTTP proxy port and the control port confused. */
+ if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH &&
+ peek_connection_has_http_command(TO_CONN(conn))) {
+ connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn);
+ log_notice(LD_CONTROL, "Received HTTP request on ControlPort");
+ connection_mark_and_flush(TO_CONN(conn));
+ return 0;
+ }
+
again:
while (1) {
size_t last_idx;
@@ -5419,9 +6147,9 @@ control_event_networkstatus_changed_helper(smartlist_t *statuses,
return 0;
strs = smartlist_new();
- smartlist_add(strs, tor_strdup("650+"));
- smartlist_add(strs, tor_strdup(event_string));
- smartlist_add(strs, tor_strdup("\r\n"));
+ smartlist_add_strdup(strs, "650+");
+ smartlist_add_strdup(strs, event_string);
+ smartlist_add_strdup(strs, "\r\n");
SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs,
{
s = networkstatus_getinfo_helper_single(rs);
@@ -5501,14 +6229,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;
@@ -5529,7 +6257,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;
}
@@ -5849,7 +6577,7 @@ monitor_owning_controller_process(const char *process_spec)
msg);
owning_controller_process_spec = NULL;
tor_cleanup();
- exit(0);
+ exit(1);
}
}
@@ -6052,8 +6780,8 @@ control_event_bootstrap(bootstrap_status_t status, int progress)
* is the connection that caused this problem.
*/
MOCK_IMPL(void,
- control_event_bootstrap_problem, (const char *warn, int reason,
- or_connection_t *or_conn))
+control_event_bootstrap_problem, (const char *warn, int reason,
+ or_connection_t *or_conn))
{
int status = bootstrap_percent;
const char *tag = "", *summary = "";
@@ -6230,8 +6958,10 @@ control_event_hs_descriptor_requested(const rend_data_t *rend_query,
send_control_event(EVENT_HS_DESC,
"650 HS_DESC REQUESTED %s %s %s %s\r\n",
- rend_hsaddress_str_or_unknown(rend_query->onion_address),
- rend_auth_type_to_string(rend_query->auth_type),
+ rend_hsaddress_str_or_unknown(
+ rend_data_get_address(rend_query)),
+ rend_auth_type_to_string(
+ TO_REND_DATA_V2(rend_query)->auth_type),
node_describe_longname_by_id(id_digest),
desc_id_base32);
}
@@ -6247,19 +6977,25 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp)
{
int replica;
const char *desc_id = NULL;
+ const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data);
/* Possible if the fetch was done using a descriptor ID. This means that
* the HSFETCH command was used. */
- if (!tor_digest_is_zero(rend_data->desc_id_fetch)) {
- desc_id = rend_data->desc_id_fetch;
+ if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) {
+ desc_id = rend_data_v2->desc_id_fetch;
goto end;
}
+ /* Without a directory fingerprint at this stage, we can't do much. */
+ if (hsdir_fp == NULL) {
+ goto end;
+ }
+
/* OK, we have an onion address so now let's find which descriptor ID
* is the one associated with the HSDir fingerprint. */
for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
replica++) {
- const char *digest = rend_data->descriptor_id[replica];
+ const char *digest = rend_data_get_desc_id(rend_data, replica, NULL);
SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) {
if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) {
@@ -6344,10 +7080,9 @@ control_event_hs_descriptor_receive_end(const char *action,
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
const char *desc_id = NULL;
- if (!action || !id_digest || !rend_data || !onion_address) {
- log_warn(LD_BUG, "Called with action==%p, id_digest==%p, "
- "rend_data==%p, onion_address==%p", action, id_digest,
- rend_data, onion_address);
+ if (!action || !rend_data || !onion_address) {
+ log_warn(LD_BUG, "Called with action==%p, rend_data==%p, "
+ "onion_address==%p", action, rend_data, onion_address);
return;
}
@@ -6368,8 +7103,10 @@ control_event_hs_descriptor_receive_end(const char *action,
"650 HS_DESC %s %s %s %s%s%s\r\n",
action,
rend_hsaddress_str_or_unknown(onion_address),
- rend_auth_type_to_string(rend_data->auth_type),
- node_describe_longname_by_id(id_digest),
+ rend_auth_type_to_string(
+ TO_REND_DATA_V2(rend_data)->auth_type),
+ id_digest ?
+ node_describe_longname_by_id(id_digest) : "UNKNOWN",
desc_id_field ? desc_id_field : "",
reason_field ? reason_field : "");
@@ -6449,28 +7186,30 @@ control_event_hs_descriptor_uploaded(const char *id_digest,
id_digest, NULL);
}
-/** Send HS_DESC event to inform controller that query <b>rend_query</b>
- * failed to retrieve hidden service descriptor identified by
- * <b>id_digest</b>. If <b>reason</b> is not NULL, add it to REASON=
- * field.
+/** Send HS_DESC event to inform controller that query <b>rend_data</b>
+ * failed to retrieve hidden service descriptor from directory identified by
+ * <b>id_digest</b>. If NULL, "UNKNOWN" is used. If <b>reason</b> is not NULL,
+ * add it to REASON= field.
*/
void
control_event_hs_descriptor_failed(const rend_data_t *rend_data,
const char *id_digest,
const char *reason)
{
- if (!rend_data || !id_digest) {
- log_warn(LD_BUG, "Called with rend_data==%p, id_digest==%p",
- rend_data, id_digest);
+ if (!rend_data) {
+ log_warn(LD_BUG, "Called with rend_data==%p", rend_data);
return;
}
control_event_hs_descriptor_receive_end("FAILED",
- rend_data->onion_address,
+ rend_data_get_address(rend_data),
rend_data, id_digest, reason);
}
-/** send HS_DESC_CONTENT event after completion of a successful fetch from
- * hs directory. */
+/** Send HS_DESC_CONTENT event after completion of a successful fetch from hs
+ * directory. If <b>hsdir_id_digest</b> is NULL, it is replaced by "UNKNOWN".
+ * If <b>content</b> is NULL, it is replaced by an empty string. The
+ * <b>onion_address</b> or <b>desc_id</b> set to NULL will no trigger the
+ * control event. */
void
control_event_hs_descriptor_content(const char *onion_address,
const char *desc_id,
@@ -6480,9 +7219,9 @@ control_event_hs_descriptor_content(const char *onion_address,
static const char *event_name = "HS_DESC_CONTENT";
char *esc_content = NULL;
- if (!onion_address || !desc_id || !hsdir_id_digest) {
- log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, "
- "hsdir_id_digest==%p", onion_address, desc_id, hsdir_id_digest);
+ if (!onion_address || !desc_id) {
+ log_warn(LD_BUG, "Called with onion_address==%p, desc_id==%p, ",
+ onion_address, desc_id);
return;
}
@@ -6497,7 +7236,9 @@ control_event_hs_descriptor_content(const char *onion_address,
event_name,
rend_hsaddress_str_or_unknown(onion_address),
desc_id,
- node_describe_longname_by_id(hsdir_id_digest),
+ hsdir_id_digest ?
+ node_describe_longname_by_id(hsdir_id_digest) :
+ "UNKNOWN",
esc_content);
tor_free(esc_content);
}
diff --git a/src/or/control.h b/src/or/control.h
index 008bfb1c3b..41a194bfcb 100644
--- a/src/or/control.h
+++ b/src/or/control.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -259,6 +259,42 @@ 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 int getinfo_helper_onions(
+ control_connection_t *control_conn,
+ const char *question,
+ char **answer,
+ const char **errmsg);
+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);
+STATIC int getinfo_helper_dir(
+ 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..d9371b3446 100644
--- a/src/or/cpuworker.c
+++ b/src/or/cpuworker.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2003-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -8,7 +8,14 @@
* \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 use this infrastructure
+ * <ul><li>for processing onionskins in onion.c
+ * <li>for compressing consensuses in consdiffmgr.c,
+ * <li>and for calculating diffs and compressing them in consdiffmgr.c.
+ * </ul>
**/
#include "or.h"
#include "channel.h"
@@ -23,11 +30,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);
@@ -89,7 +92,14 @@ cpu_init(void)
event_add(reply_event, NULL);
}
if (!threadpool) {
- threadpool = threadpool_new(get_num_cpus(get_options()),
+ /*
+ In our threadpool implementation, half the threads are permissive and
+ half are strict (when it comes to running lower-priority tasks). So we
+ always make sure we have at least two threads, so that there will be at
+ least one thread of each kind.
+ */
+ const int n_threads = get_num_cpus(get_options()) + 1;
+ threadpool = threadpool_new(n_threads,
replyqueue,
worker_state_new,
worker_state_free,
@@ -168,6 +178,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;
}
@@ -371,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_)
if (onionskin_answer(circ,
&rpl.created_cell,
- (const char*)rpl.keys,
+ (const char*)rpl.keys, sizeof(rpl.keys),
rpl.rend_auth_material) < 0) {
log_warn(LD_OR,"onionskin_answer failed. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
@@ -473,11 +484,27 @@ queue_pending_tasks(void)
if (!circ)
return;
- if (assign_onionskin_to_cpuworker(circ, onionskin))
- log_warn(LD_OR,"assign_to_cpuworker failed. Ignoring.");
+ if (assign_onionskin_to_cpuworker(circ, onionskin) < 0)
+ log_info(LD_OR,"assign_to_cpuworker failed. Ignoring.");
}
}
+/** DOCDOC */
+MOCK_IMPL(workqueue_entry_t *,
+cpuworker_queue_work,(workqueue_priority_t priority,
+ workqueue_reply_t (*fn)(void *, void *),
+ void (*reply_fn)(void *),
+ void *arg))
+{
+ tor_assert(threadpool);
+
+ return threadpool_queue_work_priority(threadpool,
+ priority,
+ fn,
+ reply_fn,
+ arg);
+}
+
/** Try to tell a cpuworker to perform the public key operations necessary to
* respond to <b>onionskin</b> for the circuit <b>circ</b>.
*
@@ -530,7 +557,8 @@ assign_onionskin_to_cpuworker(or_circuit_t *circ,
memwipe(&req, 0, sizeof(req));
++total_pending_tasks;
- queue_entry = threadpool_queue_work(threadpool,
+ queue_entry = threadpool_queue_work_priority(threadpool,
+ WQ_PRI_HIGH,
cpuworker_onion_handshake_threadfn,
cpuworker_onion_handshake_replyfn,
job);
diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h
index 62cf0eb164..320de9532f 100644
--- a/src/or/cpuworker.h
+++ b/src/or/cpuworker.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -14,6 +14,14 @@
void cpu_init(void);
void cpuworkers_rotate_keyinfo(void);
+struct workqueue_entry_s;
+enum workqueue_reply_t;
+enum workqueue_priority_t;
+MOCK_DECL(struct workqueue_entry_s *, cpuworker_queue_work, (
+ enum workqueue_priority_t priority,
+ enum workqueue_reply_t (*fn)(void *, void *),
+ void (*reply_fn)(void *),
+ void *arg));
struct create_cell_t;
int assign_onionskin_to_cpuworker(or_circuit_t *circ,
diff --git a/src/or/dircollate.c b/src/or/dircollate.c
index 3f9d78f02d..d34ebe8af5 100644
--- a/src/or/dircollate.c
+++ b/src/or/dircollate.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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. */
@@ -39,16 +53,20 @@ ddmap_entry_free(ddmap_entry_t *e)
static ddmap_entry_t *
ddmap_entry_new(int n_votes)
{
- return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) +
+ return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) +
sizeof(vote_routerstatus_t *) * n_votes);
}
+/** 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/dircollate.h b/src/or/dircollate.h
index 358c730cbb..52214282b9 100644
--- a/src/or/dircollate.h
+++ b/src/or/dircollate.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/directory.c b/src/or/directory.c
index f74e470a50..e079a5941f 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -1,21 +1,30 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
+#define DIRECTORY_PRIVATE
+
#include "or.h"
#include "backtrace.h"
+#include "bridges.h"
#include "buffers.h"
#include "circuitbuild.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
+#include "conscache.h"
+#include "consdiff.h"
+#include "consdiffmgr.h"
#include "control.h"
+#include "compat.h"
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
#include "entrynodes.h"
#include "geoip.h"
+#include "hs_cache.h"
+#include "hs_common.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -30,18 +39,48 @@
#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
+#if !defined(OpenBSD)
#include <malloc.h>
#endif
#endif
/**
* \file directory.c
- * \brief Code to send and fetch directories and router
- * descriptors via HTTP. Directories use dirserv.c to generate the
- * results; clients use routers.c to parse them.
+ * \brief Code to send and fetch information from directory authorities and
+ * caches via HTTP.
+ *
+ * Directory caches and authorities use dirserv.c to generate the results of a
+ * query and stream them to the connection; clients use routerparse.c to parse
+ * them.
+ *
+ * Every directory request has a dir_connection_t on the client side and on
+ * the server side. In most cases, the dir_connection_t object is a linked
+ * connection, tunneled through an edge_connection_t so that it can be a
+ * stream on the Tor network. The only non-tunneled connections are those
+ * that are used to upload material (descriptors and votes) to authorities.
+ * Among tunneled connections, some use one-hop circuits, and others use
+ * multi-hop circuits for anonymity.
+ *
+ * Directory requests are launched by calling
+ * directory_initiate_request(). This
+ * launch the connection, will construct an HTTP request with
+ * directory_send_command(), send the and wait for a response. The client
+ * later handles the response with connection_dir_client_reached_eof(),
+ * which passes the information received to another part of Tor.
+ *
+ * On the server side, requests are read in directory_handle_command(),
+ * which dispatches first on the request type (GET or POST), and then on
+ * the URL requested. GET requests are processed with a table-based
+ * dispatcher in url_table[]. The process of handling larger GET requests
+ * is complicated because we need to avoid allocating a copy of all the
+ * data to be sent to the client in one huge buffer. Instead, we spool the
+ * data into the buffer using logic in connection_dirserv_flushed_some() in
+ * dirserv.c. (TODO: If we extended buf.c to have a zero-copy
+ * reference-based buffer type, we could remove most of that code, at the
+ * cost of a bit more reference counting.)
**/
/* In-points to directory.c:
@@ -61,10 +100,8 @@
* connection_finished_connecting() in connection.c
*/
static void directory_send_command(dir_connection_t *conn,
- int purpose, int direct, const char *resource,
- const char *payload, size_t payload_len,
- time_t if_modified_since);
-static int directory_handle_command(dir_connection_t *conn);
+ int direct,
+ const directory_request_t *request);
static int body_is_plausible(const char *body, size_t body_len, int purpose);
static char *http_get_header(const char *headers, const char *which);
static void http_set_address_origin(const char *headers, connection_t *conn);
@@ -80,21 +117,11 @@ 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(
- const tor_addr_port_t *or_addr_port,
- const tor_addr_port_t *dir_addr_port,
- const char *digest,
- uint8_t dir_purpose,
- uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload,
- size_t payload_len,
- time_t if_modified_since,
- const rend_data_t *rend_query);
+static int client_likes_consensus(const struct consensus_cache_entry_t *ent,
+ const char *want_url);
+
+static void connection_dir_close_consensus_fetches(
+ dir_connection_t *except_this_one, const char *resource);
/********* START VARIABLES **********/
@@ -103,6 +130,7 @@ static void directory_initiate_command_rend(
#define ALLOW_DIRECTORY_TIME_SKEW (30*60)
#define X_ADDRESS_HEADER "X-Your-Address-Is: "
+#define X_OR_DIFF_FROM_CONSENSUS_HEADER "X-Or-Diff-From-Consensus: "
/** HTTP cache control: how long do we tell proxies they can cache each
* kind of document we serve? */
@@ -117,29 +145,57 @@ static void directory_initiate_command_rend(
/********* END VARIABLES ************/
-/** 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
-purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose)
+/** Return false if the directory purpose <b>dir_purpose</b>
+ * does not require an anonymous (three-hop) connection.
+ *
+ * Return true 1) by default, 2) if all directory actions have
+ * specifically been configured to be over an anonymous connection,
+ * or 3) if the router is a bridge */
+int
+purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
+ const char *resource)
{
if (get_options()->AllDirActionsPrivate)
return 1;
- if (router_purpose == ROUTER_PURPOSE_BRIDGE)
+
+ if (router_purpose == ROUTER_PURPOSE_BRIDGE) {
+ if (dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC
+ && resource && !strcmp(resource, "authority.z")) {
+ /* We are asking a bridge for its own descriptor. That doesn't need
+ anonymity. */
+ return 0;
+ }
+ /* Assume all other bridge stuff needs anonymity. */
return 1; /* if no circuits yet, this might break bootstrapping, but it's
* needed to be safe. */
- if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR ||
- dir_purpose == DIR_PURPOSE_UPLOAD_VOTE ||
- dir_purpose == DIR_PURPOSE_UPLOAD_SIGNATURES ||
- dir_purpose == DIR_PURPOSE_FETCH_STATUS_VOTE ||
- dir_purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES ||
- dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS ||
- dir_purpose == DIR_PURPOSE_FETCH_CERTIFICATE ||
- dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC ||
- dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO ||
- dir_purpose == DIR_PURPOSE_FETCH_MICRODESC)
- return 0;
- return 1;
+ }
+
+ switch (dir_purpose)
+ {
+ case DIR_PURPOSE_UPLOAD_DIR:
+ case DIR_PURPOSE_UPLOAD_VOTE:
+ case DIR_PURPOSE_UPLOAD_SIGNATURES:
+ case DIR_PURPOSE_FETCH_STATUS_VOTE:
+ case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES:
+ case DIR_PURPOSE_FETCH_CONSENSUS:
+ case DIR_PURPOSE_FETCH_CERTIFICATE:
+ case DIR_PURPOSE_FETCH_SERVERDESC:
+ case DIR_PURPOSE_FETCH_EXTRAINFO:
+ case DIR_PURPOSE_FETCH_MICRODESC:
+ return 0;
+ case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
+ case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
+ case DIR_PURPOSE_FETCH_RENDDESC_V2:
+ case DIR_PURPOSE_FETCH_HSDESC:
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ return 1;
+ case DIR_PURPOSE_SERVER:
+ default:
+ log_warn(LD_BUG, "Called with dir_purpose=%d, router_purpose=%d",
+ dir_purpose, router_purpose);
+ tor_assert_nonfatal_unreached();
+ return 1; /* Assume it needs anonymity; better safe than sorry. */
+ }
}
/** Return a newly allocated string describing <b>auth</b>. Only describes
@@ -190,6 +246,10 @@ dir_conn_purpose_to_string(int purpose)
return "hidden-service v2 descriptor fetch";
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
return "hidden-service v2 descriptor upload";
+ case DIR_PURPOSE_FETCH_HSDESC:
+ return "hidden-service descriptor fetch";
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ return "hidden-service descriptor upload";
case DIR_PURPOSE_FETCH_MICRODESC:
return "microdescriptor fetch";
}
@@ -344,7 +404,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
log_info(LD_DIR, "Uploading an extrainfo too (length %d)",
(int) extrainfo_len);
}
- if (purpose_needs_anonymity(dir_purpose, router_purpose)) {
+ if (purpose_needs_anonymity(dir_purpose, router_purpose, NULL)) {
indirection = DIRIND_ANONYMOUS;
} else if (!fascist_firewall_allows_dir_server(ds,
FIREWALL_DIR_CONNECTION,
@@ -356,10 +416,14 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
} else {
indirection = DIRIND_DIRECT_CONN;
}
- directory_initiate_command_routerstatus(rs, dir_purpose,
- router_purpose,
- indirection,
- NULL, payload, upload_len, 0);
+
+ directory_request_t *req = directory_request_new(dir_purpose);
+ directory_request_set_routerstatus(req, rs);
+ directory_request_set_router_purpose(req, router_purpose);
+ directory_request_set_indirection(req, indirection);
+ directory_request_set_payload(req, payload, upload_len);
+ directory_initiate_request(req);
+ directory_request_free(req);
} SMARTLIST_FOREACH_END(ds);
if (!found) {
char *s = authdir_type_to_string(type);
@@ -377,10 +441,9 @@ should_use_directory_guards(const or_options_t *options)
/* Public (non-bridge) servers never use directory guards. */
if (public_server_mode(options))
return 0;
- /* If guards are disabled, or directory guards are disabled, we can't
- * use directory guards.
+ /* If guards are disabled, we can't use directory guards.
*/
- if (!options->UseEntryGuards || !options->UseEntryGuardsAsDirGuards)
+ if (!options->UseEntryGuards)
return 0;
/* If we're configured to fetch directory info aggressively or of a
* nonstandard type, don't use directory guards. */
@@ -395,7 +458,8 @@ should_use_directory_guards(const or_options_t *options)
* information of type <b>type</b>, and return its routerstatus. */
static const routerstatus_t *
directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags,
- uint8_t dir_purpose)
+ uint8_t dir_purpose,
+ circuit_guard_state_t **guard_state_out)
{
const routerstatus_t *rs = NULL;
const or_options_t *options = get_options();
@@ -404,7 +468,7 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags,
log_warn(LD_BUG, "Called when we have UseBridges set.");
if (should_use_directory_guards(options)) {
- const node_t *node = choose_random_dirguard(type);
+ const node_t *node = guards_choose_dirguard(guard_state_out);
if (node)
rs = node->rs;
} else {
@@ -420,13 +484,94 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags,
return rs;
}
+/**
+ * Set the extra fields in <b>req</b> that are used when requesting a
+ * consensus of type <b>resource</b>.
+ *
+ * Right now, these fields are if-modified-since and x-or-diff-from-consensus.
+ */
+static void
+dir_consensus_request_set_additional_headers(directory_request_t *req,
+ const char *resource)
+{
+ time_t if_modified_since = 0;
+ uint8_t or_diff_from[DIGEST256_LEN];
+ int or_diff_from_is_set = 0;
+
+ /* DEFAULT_IF_MODIFIED_SINCE_DELAY is 1/20 of the default consensus
+ * period of 1 hour.
+ */
+ const int DEFAULT_IF_MODIFIED_SINCE_DELAY = 180;
+ const int32_t DEFAULT_TRY_DIFF_FOR_CONSENSUS_NEWER = 72;
+ const int32_t MIN_TRY_DIFF_FOR_CONSENSUS_NEWER = 0;
+ const int32_t MAX_TRY_DIFF_FOR_CONSENSUS_NEWER = 8192;
+ const char TRY_DIFF_FOR_CONSENSUS_NEWER_NAME[] =
+ "try-diff-for-consensus-newer-than";
+
+ int flav = FLAV_NS;
+ if (resource)
+ flav = networkstatus_parse_flavor_name(resource);
+
+ int32_t max_age_for_diff = 3600 *
+ networkstatus_get_param(NULL,
+ TRY_DIFF_FOR_CONSENSUS_NEWER_NAME,
+ DEFAULT_TRY_DIFF_FOR_CONSENSUS_NEWER,
+ MIN_TRY_DIFF_FOR_CONSENSUS_NEWER,
+ MAX_TRY_DIFF_FOR_CONSENSUS_NEWER);
+
+ if (flav != -1) {
+ /* IF we have a parsed consensus of this type, we can do an
+ * if-modified-time based on it. */
+ networkstatus_t *v;
+ v = networkstatus_get_latest_consensus_by_flavor(flav);
+ if (v) {
+ /* In networks with particularly short V3AuthVotingIntervals,
+ * ask for the consensus if it's been modified since half the
+ * V3AuthVotingInterval of the most recent consensus. */
+ time_t ims_delay = DEFAULT_IF_MODIFIED_SINCE_DELAY;
+ if (v->fresh_until > v->valid_after
+ && ims_delay > (v->fresh_until - v->valid_after)/2) {
+ ims_delay = (v->fresh_until - v->valid_after)/2;
+ }
+ if_modified_since = v->valid_after + ims_delay;
+ if (v->valid_after >= approx_time() - max_age_for_diff) {
+ memcpy(or_diff_from, v->digest_sha3_as_signed, DIGEST256_LEN);
+ or_diff_from_is_set = 1;
+ }
+ }
+ } else {
+ /* Otherwise it might be a consensus we don't parse, but which we
+ * do cache. Look at the cached copy, perhaps. */
+ cached_dir_t *cd = dirserv_get_consensus(resource);
+ /* We have no method of determining the voting interval from an
+ * unparsed consensus, so we use the default. */
+ if (cd) {
+ if_modified_since = cd->published + DEFAULT_IF_MODIFIED_SINCE_DELAY;
+ if (cd->published >= approx_time() - max_age_for_diff) {
+ memcpy(or_diff_from, cd->digest_sha3_as_signed, DIGEST256_LEN);
+ or_diff_from_is_set = 1;
+ }
+ }
+ }
+
+ if (if_modified_since > 0)
+ directory_request_set_if_modified_since(req, if_modified_since);
+ if (or_diff_from_is_set) {
+ char hex[HEX_DIGEST256_LEN + 1];
+ base16_encode(hex, sizeof(hex),
+ (const char*)or_diff_from, sizeof(or_diff_from));
+ directory_request_add_header(req, X_OR_DIFF_FROM_CONSENSUS_HEADER, hex);
+ }
+}
+
/** Start a connection to a random running directory server, using
* connection purpose <b>dir_purpose</b>, intending to fetch descriptors
* of purpose <b>router_purpose</b>, and requesting <b>resource</b>.
* Use <b>pds_flags</b> as arguments to router_pick_directory_server()
* or router_pick_trusteddirserver().
*/
-MOCK_IMPL(void, directory_get_from_dirserver, (
+MOCK_IMPL(void,
+directory_get_from_dirserver,(
uint8_t dir_purpose,
uint8_t router_purpose,
const char *resource,
@@ -438,52 +583,17 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
int prefer_authority = (directory_fetches_from_authorities(options)
|| want_authority == DL_WANT_AUTHORITY);
int require_authority = 0;
- int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose);
+ int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose,
+ resource);
dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource);
- time_t if_modified_since = 0;
if (type == NO_DIRINFO)
return;
- if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
- int flav = FLAV_NS;
- networkstatus_t *v;
- if (resource)
- flav = networkstatus_parse_flavor_name(resource);
-
- /* DEFAULT_IF_MODIFIED_SINCE_DELAY is 1/20 of the default consensus
- * period of 1 hour.
- */
-#define DEFAULT_IF_MODIFIED_SINCE_DELAY (180)
- if (flav != -1) {
- /* IF we have a parsed consensus of this type, we can do an
- * if-modified-time based on it. */
- v = networkstatus_get_latest_consensus_by_flavor(flav);
- if (v) {
- /* In networks with particularly short V3AuthVotingIntervals,
- * ask for the consensus if it's been modified since half the
- * V3AuthVotingInterval of the most recent consensus. */
- time_t ims_delay = DEFAULT_IF_MODIFIED_SINCE_DELAY;
- if (v->fresh_until > v->valid_after
- && ims_delay > (v->fresh_until - v->valid_after)/2) {
- ims_delay = (v->fresh_until - v->valid_after)/2;
- }
- if_modified_since = v->valid_after + ims_delay;
- }
- } else {
- /* Otherwise it might be a consensus we don't parse, but which we
- * do cache. Look at the cached copy, perhaps. */
- cached_dir_t *cd = dirserv_get_consensus(resource);
- /* We have no method of determining the voting interval from an
- * unparsed consensus, so we use the default. */
- if (cd)
- if_modified_since = cd->published + DEFAULT_IF_MODIFIED_SINCE_DELAY;
- }
- }
-
if (!options->FetchServerDescriptors)
return;
+ circuit_guard_state_t *guard_state = NULL;
if (!get_via_tor) {
if (options->UseBridges && !(type & BRIDGE_DIRINFO)) {
/* We want to ask a running bridge for which we have a descriptor.
@@ -492,27 +602,34 @@ 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);
+ const node_t *node = guards_choose_dirguard(&guard_state);
if (node && node->ri) {
/* every bridge has a routerinfo. */
routerinfo_t *ri = node->ri;
/* clients always make OR connections to bridges */
tor_addr_port_t or_ap;
+ directory_request_t *req = directory_request_new(dir_purpose);
/* 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*/
- ri->cache_info.identity_digest,
- dir_purpose,
- router_purpose,
- DIRIND_ONEHOP,
- resource, NULL, 0, if_modified_since);
- } else
+ directory_request_set_or_addr_port(req, &or_ap);
+ directory_request_set_directory_id_digest(req,
+ ri->cache_info.identity_digest);
+ directory_request_set_router_purpose(req, router_purpose);
+ directory_request_set_resource(req, resource);
+ if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS)
+ dir_consensus_request_set_additional_headers(req, resource);
+ directory_request_set_guard_state(req, guard_state);
+ directory_initiate_request(req);
+ directory_request_free(req);
+ } else {
+ if (guard_state) {
+ entry_guard_cancel(&guard_state);
+ }
log_notice(LD_DIR, "Ignoring directory request, since no bridge "
"nodes are available yet.");
+ }
+
return;
} else {
if (prefer_authority || (type & BRIDGE_DIRINFO)) {
@@ -543,9 +660,9 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
}
}
if (!rs && !(type & BRIDGE_DIRINFO)) {
- /* */
rs = directory_pick_generic_dirserver(type, pds_flags,
- dir_purpose);
+ dir_purpose,
+ &guard_state);
if (!rs)
get_via_tor = 1; /* last resort: try routing it via Tor */
}
@@ -564,17 +681,23 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
if (rs) {
const dir_indirection_t indirection =
get_via_tor ? DIRIND_ANONYMOUS : DIRIND_ONEHOP;
- directory_initiate_command_routerstatus(rs, dir_purpose,
- router_purpose,
- indirection,
- resource, NULL, 0,
- if_modified_since);
+ directory_request_t *req = directory_request_new(dir_purpose);
+ directory_request_set_routerstatus(req, rs);
+ directory_request_set_router_purpose(req, router_purpose);
+ directory_request_set_indirection(req, indirection);
+ directory_request_set_resource(req, resource);
+ if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS)
+ dir_consensus_request_set_additional_headers(req, resource);
+ if (guard_state)
+ directory_request_set_guard_state(req, guard_state);
+ directory_initiate_request(req);
+ directory_request_free(req);
} else {
log_notice(LD_DIR,
"While fetching directory info, "
"no running dirservers known. Will try again later. "
"(purpose %d)", dir_purpose);
- if (!purpose_needs_anonymity(dir_purpose, router_purpose)) {
+ if (!purpose_needs_anonymity(dir_purpose, router_purpose, resource)) {
/* remember we tried them all and failed. */
directory_all_unreachable(time(NULL));
}
@@ -594,15 +717,17 @@ directory_get_from_all_authorities(uint8_t dir_purpose,
SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(),
dir_server_t *, ds) {
- routerstatus_t *rs;
if (router_digest_is_me(ds->digest))
continue;
if (!(ds->type & V3_DIRINFO))
continue;
- rs = &ds->fake_status;
- directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose,
- DIRIND_ONEHOP, resource, NULL,
- 0, 0);
+ const routerstatus_t *rs = &ds->fake_status;
+ directory_request_t *req = directory_request_new(dir_purpose);
+ directory_request_set_routerstatus(req, rs);
+ directory_request_set_router_purpose(req, router_purpose);
+ directory_request_set_resource(req, resource);
+ directory_initiate_request(req);
+ directory_request_free(req);
} SMARTLIST_FOREACH_END(ds);
}
@@ -630,6 +755,7 @@ directory_choose_address_routerstatus(const routerstatus_t *status,
tor_assert(use_or_ap != NULL);
tor_assert(use_dir_ap != NULL);
+ const or_options_t *options = get_options();
int have_or = 0, have_dir = 0;
/* We expect status to have at least one reachable address if we're
@@ -671,10 +797,11 @@ directory_choose_address_routerstatus(const routerstatus_t *status,
}
/* DirPort connections
- * DIRIND_ONEHOP uses ORPort, but may fall back to the DirPort */
+ * DIRIND_ONEHOP uses ORPort, but may fall back to the DirPort on relays */
if (indirection == DIRIND_DIRECT_CONN ||
indirection == DIRIND_ANON_DIRPORT ||
- indirection == DIRIND_ONEHOP) {
+ (indirection == DIRIND_ONEHOP
+ && !directory_must_use_begindir(options))) {
have_dir = fascist_firewall_choose_address_rs(status,
FIREWALL_DIR_CONNECTION, 0,
use_dir_ap);
@@ -700,102 +827,6 @@ directory_choose_address_routerstatus(const routerstatus_t *status,
return 0;
}
-/** Same as directory_initiate_command_routerstatus(), but accepts
- * rendezvous data to fetch a hidden service descriptor. */
-void
-directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
- uint8_t dir_purpose,
- uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload,
- size_t payload_len,
- time_t if_modified_since,
- const rend_data_t *rend_query)
-{
- const or_options_t *options = get_options();
- const node_t *node;
- tor_addr_port_t use_or_ap, use_dir_ap;
- const int anonymized_connection = dirind_is_anon(indirection);
-
- tor_assert(status != NULL);
-
- node = node_get_by_id(status->identity_digest);
-
- if (!node && anonymized_connection) {
- log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we "
- "don't have its router descriptor.",
- routerstatus_describe(status));
- return;
- }
-
- if (options->ExcludeNodes && options->StrictNodes &&
- routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) {
- log_warn(LD_DIR, "Wanted to contact directory mirror %s for %s, but "
- "it's in our ExcludedNodes list and StrictNodes is set. "
- "Skipping. This choice might make your Tor not work.",
- routerstatus_describe(status),
- dir_conn_purpose_to_string(dir_purpose));
- return;
- }
-
- /* At this point, if we are a clients 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
- * possible. (If UseBridges is set, clients always use IPv6, and prefer it
- * by default.)
- *
- * Now choose an address that we can use to connect to the directory server.
- */
- if (directory_choose_address_routerstatus(status, indirection, &use_or_ap,
- &use_dir_ap) < 0) {
- return;
- }
-
- /* We don't retry the alternate OR/Dir address for the same directory if
- * the address we choose fails (#6772).
- * Instead, we'll retry another directory on failure. */
-
- directory_initiate_command_rend(&use_or_ap, &use_dir_ap,
- status->identity_digest,
- dir_purpose, router_purpose,
- indirection, resource,
- payload, payload_len, if_modified_since,
- rend_query);
-}
-
-/** Launch a new connection to the directory server <b>status</b> to
- * upload or download a server or rendezvous
- * descriptor. <b>dir_purpose</b> determines what
- * kind of directory connection we're launching, and must be one of
- * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC_V2}. <b>router_purpose</b>
- * specifies the descriptor purposes we have in mind (currently only
- * used for FETCH_DIR).
- *
- * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
- * of the HTTP post. Otherwise, <b>payload</b> should be NULL.
- *
- * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we
- * want to fetch.
- */
-MOCK_IMPL(void, directory_initiate_command_routerstatus,
- (const routerstatus_t *status,
- uint8_t dir_purpose,
- uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload,
- size_t payload_len,
- time_t if_modified_since))
-{
- directory_initiate_command_routerstatus_rend(status, dir_purpose,
- router_purpose,
- indirection, resource,
- payload, payload_len,
- if_modified_since, NULL);
-}
-
/** Return true iff <b>conn</b> is the client side of a directory connection
* we launched to ourself in order to determine the reachability of our
* dir_port. */
@@ -821,6 +852,11 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn)
static void
connection_dir_request_failed(dir_connection_t *conn)
{
+ if (conn->guard_state) {
+ /* We haven't seen a success on this guard state, so consider it to have
+ * failed. */
+ entry_guard_failed(&conn->guard_state);
+ }
if (directory_conn_is_self_reachability_test(conn)) {
return; /* this was a test fetch. don't retry. */
}
@@ -864,7 +900,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;
@@ -964,117 +1000,431 @@ connection_dir_download_cert_failed(dir_connection_t *conn, int status)
update_certificate_downloads(time(NULL));
}
+/* Should this tor instance only use begindir for all its directory requests?
+ */
+int
+directory_must_use_begindir(const or_options_t *options)
+{
+ /* Clients, onion services, and bridges must use begindir,
+ * relays and authorities do not have to */
+ return !public_server_mode(options);
+}
+
+struct directory_request_t {
+ /**
+ * These fields specify which directory we're contacting. Routerstatus,
+ * if present, overrides the other fields.
+ *
+ * @{ */
+ tor_addr_port_t or_addr_port;
+ tor_addr_port_t dir_addr_port;
+ char digest[DIGEST_LEN];
+
+ const routerstatus_t *routerstatus;
+ /** @} */
+ /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what
+ * kind of operation we'll be doing (upload/download), and of what kind
+ * of document. */
+ uint8_t dir_purpose;
+ /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo
+ * and extrainfo docs. */
+ uint8_t router_purpose;
+ /** Enum: determines whether to anonymize, and whether to use dirport or
+ * orport. */
+ dir_indirection_t indirection;
+ /** Alias to the variable part of the URL for this request */
+ const char *resource;
+ /** Alias to the payload to upload (if any) */
+ const char *payload;
+ /** Number of bytes to upload from payload</b> */
+ size_t payload_len;
+ /** Value to send in an if-modified-since header, or 0 for none. */
+ time_t if_modified_since;
+ /** Hidden-service-specific information v2. */
+ const rend_data_t *rend_query;
+ /** Extra headers to append to the request */
+ config_line_t *additional_headers;
+ /** Hidden-service-specific information for v3+. */
+ const hs_ident_dir_conn_t *hs_ident;
+ /** Used internally to directory.c: gets informed when the attempt to
+ * connect to the directory succeeds or fails, if that attempt bears on the
+ * directory's usability as a directory guard. */
+ circuit_guard_state_t *guard_state;
+};
+
/** Evaluate the situation and decide if we should use an encrypted
* "begindir-style" connection for this directory request.
+ * 0) If there is no DirPort, yes.
* 1) If or_port is 0, or it's a direct conn and or_port is firewalled
* or we're a dir mirror, no.
* 2) If we prefer to avoid begindir conns, and we're not fetching or
* publishing a bridge relay descriptor, no.
* 3) Else yes.
+ * If returning 0, return in *reason why we can't use begindir.
+ * reason must not be NULL.
*/
static int
directory_command_should_use_begindir(const or_options_t *options,
- const tor_addr_t *addr,
- int or_port, uint8_t router_purpose,
- dir_indirection_t indirection)
+ const directory_request_t *req,
+ const char **reason)
{
- (void) router_purpose;
- if (!or_port)
+ const tor_addr_t *or_addr = &req->or_addr_port.addr;
+ //const tor_addr_t *dir_addr = &req->dir_addr_port.addr;
+ const int or_port = req->or_addr_port.port;
+ const int dir_port = req->dir_addr_port.port;
+
+ const dir_indirection_t indirection = req->indirection;
+
+ tor_assert(reason);
+ *reason = NULL;
+
+ /* Reasons why we must use begindir */
+ if (!dir_port) {
+ *reason = "(using begindir - directory with no DirPort)";
+ return 1; /* We don't know a DirPort -- must begindir. */
+ }
+ /* Reasons why we can't possibly use begindir */
+ if (!or_port) {
+ *reason = "directory with unknown ORPort";
return 0; /* We don't know an ORPort -- no chance. */
- if (indirection == DIRIND_DIRECT_CONN || indirection == DIRIND_ANON_DIRPORT)
+ }
+ if (indirection == DIRIND_DIRECT_CONN ||
+ indirection == DIRIND_ANON_DIRPORT) {
+ *reason = "DirPort connection";
return 0;
- if (indirection == DIRIND_ONEHOP)
- if (!fascist_firewall_allows_address_addr(addr, or_port,
- FIREWALL_OR_CONNECTION, 0, 0) ||
- directory_fetches_from_authorities(options))
- return 0; /* We're firewalled or are acting like a relay -- also no. */
+ }
+ if (indirection == DIRIND_ONEHOP) {
+ /* We're firewalled and want a direct OR connection */
+ if (!fascist_firewall_allows_address_addr(or_addr, or_port,
+ FIREWALL_OR_CONNECTION, 0, 0)) {
+ *reason = "ORPort not reachable";
+ return 0;
+ }
+ }
+ /* Reasons why we want to avoid using begindir */
+ if (indirection == DIRIND_ONEHOP) {
+ if (!directory_must_use_begindir(options)) {
+ *reason = "in relay mode";
+ return 0;
+ }
+ }
+ /* DIRIND_ONEHOP on a client, or DIRIND_ANONYMOUS
+ */
+ *reason = "(using begindir)";
return 1;
}
-/** Helper for directory_initiate_command_rend: send the
- * command to a server whose OR address/port is <b>or_addr</b>/<b>or_port</b>,
- * whose directory address/port is <b>dir_addr</b>/<b>dir_port</b>, whose
- * identity key digest is <b>digest</b>, with purposes <b>dir_purpose</b> and
- * <b>router_purpose</b>, making an (in)direct connection as specified in
- * <b>indirection</b>, with command <b>resource</b>, <b>payload</b> of
- * <b>payload_len</b>, and asking for a result only <b>if_modified_since</b>.
+/**
+ * Create and return a new directory_request_t with purpose
+ * <b>dir_purpose</b>.
+ */
+directory_request_t *
+directory_request_new(uint8_t dir_purpose)
+{
+ tor_assert(dir_purpose >= DIR_PURPOSE_MIN_);
+ tor_assert(dir_purpose <= DIR_PURPOSE_MAX_);
+ tor_assert(dir_purpose != DIR_PURPOSE_SERVER);
+ tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2);
+
+ directory_request_t *result = tor_malloc_zero(sizeof(*result));
+ tor_addr_make_null(&result->or_addr_port.addr, AF_INET);
+ result->or_addr_port.port = 0;
+ tor_addr_make_null(&result->dir_addr_port.addr, AF_INET);
+ result->dir_addr_port.port = 0;
+ result->dir_purpose = dir_purpose;
+ result->router_purpose = ROUTER_PURPOSE_GENERAL;
+ result->indirection = DIRIND_ONEHOP;
+ return result;
+}
+/**
+ * Release all resources held by <b>req</b>.
+ */
+void
+directory_request_free(directory_request_t *req)
+{
+ if (req == NULL)
+ return;
+ config_free_lines(req->additional_headers);
+ tor_free(req);
+}
+/**
+ * Set the address and OR port to use for this directory request. If there is
+ * no OR port, we'll have to connect over the dirport. (If there are both,
+ * the indirection setting determins which to use.)
+ */
+void
+directory_request_set_or_addr_port(directory_request_t *req,
+ const tor_addr_port_t *p)
+{
+ memcpy(&req->or_addr_port, p, sizeof(*p));
+}
+/**
+ * Set the address and dirport to use for this directory request. If there
+ * is no dirport, we'll have to connect over the OR port. (If there are both,
+ * the indirection setting determins which to use.)
*/
void
-directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
- const tor_addr_t *dir_addr, uint16_t dir_port,
- const char *digest,
- uint8_t dir_purpose, uint8_t router_purpose,
- dir_indirection_t indirection, const char *resource,
- const char *payload, size_t payload_len,
- time_t if_modified_since)
+directory_request_set_dir_addr_port(directory_request_t *req,
+ const tor_addr_port_t *p)
{
- tor_addr_port_t or_ap, dir_ap;
+ memcpy(&req->dir_addr_port, p, sizeof(*p));
+}
+/**
+ * Set the RSA identity digest of the directory to use for this directory
+ * request.
+ */
+void
+directory_request_set_directory_id_digest(directory_request_t *req,
+ const char *digest)
+{
+ memcpy(req->digest, digest, DIGEST_LEN);
+}
+/**
+ * Set the router purpose associated with uploaded and downloaded router
+ * descriptors and extrainfo documents in this directory request. The purpose
+ * must be one of ROUTER_PURPOSE_GENERAL (the default) or
+ * ROUTER_PURPOSE_BRIDGE.
+ */
+void
+directory_request_set_router_purpose(directory_request_t *req,
+ uint8_t router_purpose)
+{
+ tor_assert(router_purpose == ROUTER_PURPOSE_GENERAL ||
+ router_purpose == ROUTER_PURPOSE_BRIDGE);
+ // assert that it actually makes sense to set this purpose, given
+ // the dir_purpose.
+ req->router_purpose = router_purpose;
+}
+/**
+ * Set the indirection to be used for the directory request. The indirection
+ * parameter configures whether to connect to a DirPort or ORPort, and whether
+ * to anonymize the connection. DIRIND_ONEHOP (use ORPort, don't anonymize)
+ * is the default. See dir_indirection_t for more information.
+ */
+void
+directory_request_set_indirection(directory_request_t *req,
+ dir_indirection_t indirection)
+{
+ req->indirection = indirection;
+}
- /* Use the null tor_addr and 0 port if the address or port isn't valid. */
- if (tor_addr_port_is_valid(or_addr, or_port, 0)) {
- tor_addr_copy(&or_ap.addr, or_addr);
- or_ap.port = or_port;
- } else {
- /* the family doesn't matter here, so make it IPv4 */
- tor_addr_make_null(&or_ap.addr, AF_INET);
- or_ap.port = or_port = 0;
- }
+/**
+ * Set a pointer to the resource to request from a directory. Different
+ * request types use resources to indicate different components of their URL.
+ * Note that only an alias to <b>resource</b> is stored, so the
+ * <b>resource</b> must outlive the request.
+ */
+void
+directory_request_set_resource(directory_request_t *req,
+ const char *resource)
+{
+ req->resource = resource;
+}
+/**
+ * Set a pointer to the payload to include with this directory request, along
+ * with its length. Note that only an alias to <b>payload</b> is stored, so
+ * the <b>payload</b> must outlive the request.
+ */
+void
+directory_request_set_payload(directory_request_t *req,
+ const char *payload,
+ size_t payload_len)
+{
+ tor_assert(DIR_PURPOSE_IS_UPLOAD(req->dir_purpose));
- if (tor_addr_port_is_valid(dir_addr, dir_port, 0)) {
- tor_addr_copy(&dir_ap.addr, dir_addr);
- dir_ap.port = dir_port;
- } else {
- /* the family doesn't matter here, so make it IPv4 */
- tor_addr_make_null(&dir_ap.addr, AF_INET);
- dir_ap.port = dir_port = 0;
+ req->payload = payload;
+ req->payload_len = payload_len;
+}
+/**
+ * Set an if-modified-since date to send along with the request. The
+ * default is 0 (meaning, send no if-modified-since header).
+ */
+void
+directory_request_set_if_modified_since(directory_request_t *req,
+ time_t if_modified_since)
+{
+ req->if_modified_since = if_modified_since;
+}
+
+/** Include a header of name <b>key</b> with content <b>val</b> in the
+ * request. Neither may include newlines or other odd characters. Their
+ * ordering is not currently guaranteed.
+ *
+ * Note that, as elsewhere in this module, header keys include a trailing
+ * colon and space.
+ */
+void
+directory_request_add_header(directory_request_t *req,
+ const char *key,
+ const char *val)
+{
+ config_line_prepend(&req->additional_headers, key, val);
+}
+/**
+ * Set an object containing HS data to be associated with this request. Note
+ * that only an alias to <b>query</b> is stored, so the <b>query</b> object
+ * must outlive the request.
+ */
+void
+directory_request_set_rend_query(directory_request_t *req,
+ const rend_data_t *query)
+{
+ if (query) {
+ tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 ||
+ req->dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2);
}
+ req->rend_query = query;
+}
+/**
+ * Set an object containing HS connection identifier to be associated with
+ * this request. Note that only an alias to <b>ident</b> is stored, so the
+ * <b>ident</b> object must outlive the request.
+ */
+void
+directory_request_upload_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident)
+{
+ if (ident) {
+ tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+ }
+ req->hs_ident = ident;
+}
+/** Set a static circuit_guard_state_t object to affliate with the request in
+ * <b>req</b>. This object will receive notification when the attempt to
+ * connect to the guard either succeeds or fails. */
+void
+directory_request_set_guard_state(directory_request_t *req,
+ circuit_guard_state_t *state)
+{
+ req->guard_state = state;
+}
- directory_initiate_command_rend(&or_ap, &dir_ap,
- digest, dir_purpose,
- router_purpose, indirection,
- resource, payload, payload_len,
- if_modified_since, NULL);
+/**
+ * Internal: Return true if any information for contacting the directory in
+ * <b>req</b> has been set, other than by the routerstatus. */
+static int
+directory_request_dir_contact_info_specified(const directory_request_t *req)
+{
+ /* We only check for ports here, since we don't use an addr unless the port
+ * is set */
+ return (req->or_addr_port.port ||
+ req->dir_addr_port.port ||
+ ! tor_digest_is_zero(req->digest));
}
-/** Return non-zero iff a directory connection with purpose
- * <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.) */
+/**
+ * Set the routerstatus to use for the directory associated with this
+ * request. If this option is set, then no other function to set the
+ * directory's address or identity should be called.
+ */
+void
+directory_request_set_routerstatus(directory_request_t *req,
+ const routerstatus_t *status)
+{
+ req->routerstatus = status;
+}
+/**
+ * Helper: update the addresses, ports, and identities in <b>req</b>
+ * from the routerstatus object in <b>req</b>. Return 0 on success.
+ * On failure, warn and return -1.
+ */
static int
-is_sensitive_dir_purpose(uint8_t dir_purpose)
+directory_request_set_dir_from_routerstatus(directory_request_t *req)
+
{
- return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) ||
- (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) ||
- (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2));
+ const routerstatus_t *status = req->routerstatus;
+ if (BUG(status == NULL))
+ return -1;
+ const or_options_t *options = get_options();
+ const node_t *node;
+ tor_addr_port_t use_or_ap, use_dir_ap;
+ const int anonymized_connection = dirind_is_anon(req->indirection);
+
+ tor_assert(status != NULL);
+
+ 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.",
+ routerstatus_describe(status));
+ return -1;
+ }
+
+ if (options->ExcludeNodes && options->StrictNodes &&
+ routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) {
+ log_warn(LD_DIR, "Wanted to contact directory mirror %s for %s, but "
+ "it's in our ExcludedNodes list and StrictNodes is set. "
+ "Skipping. This choice might make your Tor not work.",
+ routerstatus_describe(status),
+ dir_conn_purpose_to_string(req->dir_purpose));
+ return -1;
+ }
+
+ /* 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
+ * possible. (If UseBridges is set, clients always use IPv6, and prefer it
+ * by default.)
+ *
+ * Now choose an address that we can use to connect to the directory server.
+ */
+ if (directory_choose_address_routerstatus(status,
+ req->indirection, &use_or_ap,
+ &use_dir_ap) < 0) {
+ return -1;
+ }
+
+ directory_request_set_or_addr_port(req, &use_or_ap);
+ directory_request_set_dir_addr_port(req, &use_dir_ap);
+ directory_request_set_directory_id_digest(req, status->identity_digest);
+ return 0;
}
-/** Same as directory_initiate_command(), but accepts rendezvous data to
- * fetch a hidden service descriptor, and takes its address & port arguments
- * as tor_addr_port_t. */
-static void
-directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
- const tor_addr_port_t *dir_addr_port,
- const char *digest,
- uint8_t dir_purpose, uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload, size_t payload_len,
- time_t if_modified_since,
- const rend_data_t *rend_query)
+/**
+ * Launch the provided directory request, configured in <b>request</b>.
+ * After this function is called, you can free <b>request</b>.
+ */
+MOCK_IMPL(void,
+directory_initiate_request,(directory_request_t *request))
{
- tor_assert(or_addr_port);
- tor_assert(dir_addr_port);
+ tor_assert(request);
+ if (request->routerstatus) {
+ tor_assert_nonfatal(
+ ! directory_request_dir_contact_info_specified(request));
+ if (directory_request_set_dir_from_routerstatus(request) < 0) {
+ return;
+ }
+ }
+
+ const tor_addr_port_t *or_addr_port = &request->or_addr_port;
+ const tor_addr_port_t *dir_addr_port = &request->dir_addr_port;
+ const char *digest = request->digest;
+ const uint8_t dir_purpose = request->dir_purpose;
+ const uint8_t router_purpose = request->router_purpose;
+ const dir_indirection_t indirection = request->indirection;
+ const char *resource = request->resource;
+ const rend_data_t *rend_query = request->rend_query;
+ const hs_ident_dir_conn_t *hs_ident = request->hs_ident;
+ circuit_guard_state_t *guard_state = request->guard_state;
+
tor_assert(or_addr_port->port || dir_addr_port->port);
tor_assert(digest);
dir_connection_t *conn;
const or_options_t *options = get_options();
int socket_error = 0;
+ const char *begindir_reason = NULL;
/* Should the connection be to a relay's OR port (and inside that we will
* send our directory request)? */
- const int use_begindir = directory_command_should_use_begindir(options,
- &or_addr_port->addr, or_addr_port->port,
- router_purpose, indirection);
+ const int use_begindir =
+ directory_command_should_use_begindir(options, request, &begindir_reason);
+
/* Will the connection go via a three-hop Tor circuit? Note that this
* is separate from whether it will use_begindir. */
const int anonymized_connection = dirind_is_anon(indirection);
@@ -1093,12 +1443,18 @@ 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 (purpose_needs_anonymity(dir_purpose, router_purpose, resource)) {
+ 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 */
+ if (!use_begindir && directory_must_use_begindir(options)) {
+ log_warn(LD_BUG, "Client could not use begindir connection: %s",
+ begindir_reason ? begindir_reason : "(NULL)");
+ return;
+ }
/* ensure that we don't make direct connections when a SOCKS server is
* configured. */
@@ -1113,9 +1469,9 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
if (!port || tor_addr_is_null(&addr)) {
static int logged_backtrace = 0;
log_warn(LD_DIR,
- "Cannot make an outgoing %sconnection without %sPort.",
+ "Cannot make an outgoing %sconnection without a remote %sPort.",
use_begindir ? "begindir " : "",
- use_begindir ? "an OR" : "a Dir");
+ use_begindir ? "OR" : "Dir");
if (!logged_backtrace) {
log_backtrace(LOG_INFO, LD_BUG, "Address came from");
logged_backtrace = 1;
@@ -1123,12 +1479,6 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
return;
}
- /* ensure we don't make excess connections when we're already downloading
- * a consensus during bootstrap */
- if (connection_dir_avoid_extra_connection_for_purpose(dir_purpose)) {
- return;
- }
-
conn = dir_connection_new(tor_addr_family(&addr));
/* set up conn so it's got all the data we need to remember */
@@ -1148,8 +1498,16 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
conn->dirconn_direct = !anonymized_connection;
/* copy rendezvous data, if any */
- if (rend_query)
+ if (rend_query) {
+ /* We can't have both v2 and v3+ identifier. */
+ tor_assert_nonfatal(!hs_ident);
conn->rend_data = rend_data_dup(rend_query);
+ }
+ if (hs_ident) {
+ /* We can't have both v2 and v3+ identifier. */
+ tor_assert_nonfatal(!rend_query);
+ conn->hs_ident = hs_ident_dir_conn_dup(hs_ident);
+ }
if (!anonymized_connection && !use_begindir) {
/* then we want to connect to dirport directly */
@@ -1159,6 +1517,11 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
port = options->HTTPProxyPort;
}
+ // In this case we should not have picked a directory guard.
+ if (BUG(guard_state)) {
+ entry_guard_cancel(&guard_state);
+ }
+
switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr,
port, &socket_error)) {
case -1:
@@ -1169,15 +1532,8 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING;
/* fall through */
case 0:
- /* Close this connection if there's another consensus connection
- * downloading (during bootstrap), or connecting (after bootstrap). */
- if (connection_dir_close_consensus_conn_if_extra(conn)) {
- return;
- }
/* queue the command on the outbuf */
- directory_send_command(conn, dir_purpose, 1, resource,
- payload, payload_len,
- if_modified_since);
+ directory_send_command(conn, 1, request);
connection_watch_events(TO_CONN(conn), READ_EVENT | WRITE_EVENT);
/* writable indicates finish, readable indicates broken link,
error indicates broken link in windowsland. */
@@ -1200,6 +1556,14 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
else if (anonymized_connection && !use_begindir)
rep_hist_note_used_port(time(NULL), conn->base_.port);
+ // In this case we should not have a directory guard; we'll
+ // get a regular guard later when we build the circuit.
+ if (BUG(anonymized_connection && guard_state)) {
+ entry_guard_cancel(&guard_state);
+ }
+
+ conn->guard_state = guard_state;
+
/* make an AP connection
* populate it and add it at the right state
* hook up both sides
@@ -1221,30 +1585,19 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
connection_mark_for_close(TO_CONN(conn));
return;
}
- /* Close this connection if there's another consensus connection
- * downloading (during bootstrap), or connecting (after bootstrap). */
- if (connection_dir_close_consensus_conn_if_extra(conn)) {
- return;
- }
conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING;
/* queue the command on the outbuf */
- directory_send_command(conn, dir_purpose, 0, resource,
- payload, payload_len,
- if_modified_since);
+ directory_send_command(conn, 0, request);
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));
}
}
/** Return true iff anything we say on <b>conn</b> is being encrypted before
* we send it to the client/server. */
int
-connection_dir_is_encrypted(dir_connection_t *conn)
+connection_dir_is_encrypted(const dir_connection_t *conn)
{
/* Right now it's sufficient to see if conn is or has been linked, since
* the only thing it could be linked to is an edge connection on a
@@ -1272,9 +1625,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.
*/
@@ -1337,15 +1690,23 @@ copy_ipv6_address(char* destination, const char* source, size_t len,
}
}
-/** Queue an appropriate HTTP command on conn-\>outbuf. The other args
- * are as in directory_initiate_command().
+/** Queue an appropriate HTTP command for <b>request</b> on
+ * <b>conn</b>-\>outbuf. If <b>direct</b> is true, we're making a
+ * non-anonymized connection to the dirport.
*/
static void
directory_send_command(dir_connection_t *conn,
- int purpose, int direct, const char *resource,
- const char *payload, size_t payload_len,
- time_t if_modified_since)
+ const int direct,
+ const directory_request_t *req)
{
+ tor_assert(req);
+ const int purpose = req->dir_purpose;
+ const char *resource = req->resource;
+ const char *payload = req->payload;
+ const size_t payload_len = req->payload_len;
+ const time_t if_modified_since = req->if_modified_since;
+ const int anonymized_connection = dirind_is_anon(req->indirection);
+
char proxystring[256];
char hoststring[128];
/* NEEDS to be the same size hoststring.
@@ -1353,7 +1714,10 @@ directory_send_command(dir_connection_t *conn,
char decorated_address[128];
smartlist_t *headers = smartlist_new();
char *url;
+ char *accept_encoding;
+ size_t url_len;
char request[8192];
+ size_t request_len, total_request_len = 0;
const char *httpcommand = NULL;
tor_assert(conn);
@@ -1407,6 +1771,22 @@ directory_send_command(dir_connection_t *conn,
proxystring[0] = 0;
}
+ if (! anonymized_connection) {
+ /* Add Accept-Encoding. */
+ accept_encoding = accept_encoding_header();
+ smartlist_add_asprintf(headers, "Accept-Encoding: %s\r\n",
+ accept_encoding);
+ tor_free(accept_encoding);
+ }
+
+ /* Add additional headers, if any */
+ {
+ config_line_t *h;
+ for (h = req->additional_headers; h; h = h->next) {
+ smartlist_add_asprintf(headers, "%s%s\r\n", h->key, h->value);
+ }
+ }
+
switch (purpose) {
case DIR_PURPOSE_FETCH_CONSENSUS:
/* resource is optional. If present, it's a flavor name */
@@ -1485,6 +1865,12 @@ directory_send_command(dir_connection_t *conn,
httpcommand = "POST";
url = tor_strdup("/tor/rendezvous2/publish");
break;
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ tor_assert(resource);
+ tor_assert(payload);
+ httpcommand = "POST";
+ tor_asprintf(&url, "/tor/hs/%s/publish", resource);
+ break;
default:
tor_assert(0);
return;
@@ -1499,8 +1885,14 @@ directory_send_command(dir_connection_t *conn,
}
tor_snprintf(request, sizeof(request), "%s %s", httpcommand, proxystring);
- connection_write_to_buf(request, strlen(request), TO_CONN(conn));
- connection_write_to_buf(url, strlen(url), TO_CONN(conn));
+
+ request_len = strlen(request);
+ total_request_len += request_len;
+ connection_write_to_buf(request, request_len, TO_CONN(conn));
+
+ url_len = strlen(url);
+ total_request_len += url_len;
+ connection_write_to_buf(url, url_len, TO_CONN(conn));
tor_free(url);
if (!strcmp(httpcommand, "POST") || payload) {
@@ -1515,15 +1907,27 @@ directory_send_command(dir_connection_t *conn,
tor_free(header);
}
- connection_write_to_buf(request, strlen(request), TO_CONN(conn));
+ request_len = strlen(request);
+ total_request_len += request_len;
+ connection_write_to_buf(request, request_len, TO_CONN(conn));
if (payload) {
/* then send the payload afterwards too */
connection_write_to_buf(payload, payload_len, TO_CONN(conn));
+ total_request_len += payload_len;
}
SMARTLIST_FOREACH(headers, char *, h, tor_free(h));
smartlist_free(headers);
+
+ log_debug(LD_DIR,
+ "Sent request to directory server '%s:%d': "
+ "(purpose: %d, request size: " U64_FORMAT ", "
+ "payload size: " U64_FORMAT ")",
+ conn->base_.address, conn->base_.port,
+ conn->base_.purpose,
+ U64_PRINTF_ARG(total_request_len),
+ U64_PRINTF_ARG(payload ? payload_len : 0));
}
/** Parse an HTTP request string <b>headers</b> of the form
@@ -1708,16 +2112,15 @@ parse_http_response(const char *headers, int *code, time_t *date,
if (!strcmpstart(s, "Content-Encoding: ")) {
enc = s+18; break;
});
- if (!enc || !strcmp(enc, "identity")) {
+
+ if (enc == NULL)
*compression = NO_METHOD;
- } else if (!strcmp(enc, "deflate") || !strcmp(enc, "x-deflate")) {
- *compression = ZLIB_METHOD;
- } else if (!strcmp(enc, "gzip") || !strcmp(enc, "x-gzip")) {
- *compression = GZIP_METHOD;
- } else {
- log_info(LD_HTTP, "Unrecognized content encoding: %s. Trying to deal.",
- escaped(enc));
- *compression = UNKNOWN_METHOD;
+ else {
+ *compression = compression_method_get_by_name(enc);
+
+ if (*compression == UNKNOWN_METHOD)
+ log_info(LD_HTTP, "Unrecognized content encoding: %s. Trying to deal.",
+ escaped(enc));
}
}
SMARTLIST_FOREACH(parsed_headers, char *, s, tor_free(s));
@@ -1740,15 +2143,15 @@ body_is_plausible(const char *body, size_t len, int purpose)
if (purpose == DIR_PURPOSE_FETCH_MICRODESC) {
return (!strcmpstart(body,"onion-key"));
}
- if (1) {
- if (!strcmpstart(body,"router") ||
- !strcmpstart(body,"network-status"))
- return 1;
- for (i=0;i<32;++i) {
- if (!TOR_ISPRINT(body[i]) && !TOR_ISSPACE(body[i]))
- return 0;
- }
+
+ if (!strcmpstart(body,"router") ||
+ !strcmpstart(body,"network-status"))
+ return 1;
+ for (i=0;i<32;++i) {
+ if (!TOR_ISPRINT(body[i]) && !TOR_ISSPACE(body[i]))
+ return 0;
}
+
return 1;
}
@@ -1790,6 +2193,158 @@ load_downloaded_routers(const char *body, smartlist_t *which,
return added;
}
+/** A structure to hold arguments passed into each directory response
+ * handler */
+typedef struct response_handler_args_t {
+ int status_code;
+ const char *reason;
+ const char *body;
+ size_t body_len;
+ const char *headers;
+} response_handler_args_t;
+
+static int handle_response_fetch_consensus(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_certificate(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_status_vote(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_detached_signatures(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_desc(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_microdesc(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_upload_dir(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_upload_vote(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_upload_signatures(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_fetch_renddesc_v2(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_upload_renddesc_v2(dir_connection_t *,
+ const response_handler_args_t *);
+static int handle_response_upload_hsdesc(dir_connection_t *,
+ const response_handler_args_t *);
+
+static int
+dir_client_decompress_response_body(char **bodyp, size_t *bodylenp,
+ dir_connection_t *conn,
+ compress_method_t compression,
+ int anonymized_connection)
+{
+ int rv = 0;
+ const char *body = *bodyp;
+ size_t body_len = *bodylenp;
+ 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 plausible = body_is_plausible(body, body_len, conn->base_.purpose);
+
+ if (plausible && compression == NO_METHOD) {
+ return 0;
+ }
+
+ int severity = LOG_DEBUG;
+ char *new_body = NULL;
+ size_t new_len = 0;
+ const char *description1, *description2;
+ int want_to_try_both = 0;
+ int tried_both = 0;
+ compress_method_t guessed = detect_compression_method(body, body_len);
+
+ description1 = compression_method_get_human_name(compression);
+
+ if (BUG(description1 == NULL))
+ description1 = compression_method_get_human_name(UNKNOWN_METHOD);
+
+ if (guessed == UNKNOWN_METHOD && !plausible)
+ description2 = "confusing binary junk";
+ else
+ description2 = compression_method_get_human_name(guessed);
+
+ /* Tell the user if we don't believe what we're told about compression.*/
+ want_to_try_both = (compression == UNKNOWN_METHOD ||
+ guessed != compression);
+ if (want_to_try_both) {
+ severity = LOG_PROTOCOL_WARN;
+ }
+
+ tor_log(severity, LD_HTTP,
+ "HTTP body from server '%s:%d' was labeled as %s, "
+ "%s it seems to be %s.%s",
+ conn->base_.address, conn->base_.port, description1,
+ guessed != compression?"but":"and",
+ description2,
+ (compression>0 && guessed>0 && want_to_try_both)?
+ " Trying both.":"");
+
+ /* Try declared compression first if we can.
+ * tor_compress_supports_method() also returns true for NO_METHOD.
+ * Ensure that the server is not sending us data compressed using a
+ * compression method that is not allowed for anonymous connections. */
+ if (anonymized_connection &&
+ ! allowed_anonymous_connection_compression_method(compression)) {
+ warn_disallowed_anonymous_compression_method(compression);
+ rv = -1;
+ goto done;
+ }
+
+ if (tor_compress_supports_method(compression)) {
+ tor_uncompress(&new_body, &new_len, body, body_len, compression,
+ !allow_partial, LOG_PROTOCOL_WARN);
+ if (new_body) {
+ /* We succeeded with the declared compression method. Great! */
+ rv = 0;
+ goto done;
+ }
+ }
+
+ /* Okay, if that didn't work, and we think that it was compressed
+ * differently, try that. */
+ if (anonymized_connection &&
+ ! allowed_anonymous_connection_compression_method(guessed)) {
+ warn_disallowed_anonymous_compression_method(guessed);
+ rv = -1;
+ goto done;
+ }
+
+ if (tor_compress_supports_method(guessed) &&
+ compression != guessed) {
+ tor_uncompress(&new_body, &new_len, body, body_len, guessed,
+ !allow_partial, LOG_INFO);
+ tried_both = 1;
+ }
+ /* If we're pretty sure that we have a compressed directory, and
+ * we didn't manage to uncompress it, then warn and bail. */
+ if (!plausible && !new_body) {
+ log_fn(LOG_PROTOCOL_WARN, LD_HTTP,
+ "Unable to decompress HTTP body (tried %s%s%s, server '%s:%d').",
+ description1,
+ tried_both?" and ":"",
+ tried_both?description2:"",
+ conn->base_.address, conn->base_.port);
+ rv = -1;
+ goto done;
+ }
+
+ done:
+ if (new_body) {
+ if (rv == 0) {
+ /* success! */
+ tor_free(*bodyp);
+ *bodyp = new_body;
+ *bodylenp = new_len;
+ } else {
+ tor_free(new_body);
+ }
+ }
+
+ return rv;
+}
+
/** We are a client, and we've finished reading the server's
* response. Parse it and act appropriately.
*
@@ -1802,22 +2357,26 @@ load_downloaded_routers(const char *body, smartlist_t *which,
static int
connection_dir_client_reached_eof(dir_connection_t *conn)
{
- char *body;
- char *headers;
+ char *body = NULL;
+ char *headers = NULL;
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;
compress_method_t compression;
- int plausible;
int skewed = 0;
+ int rv;
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;
+ size_t received_bytes;
+ const int anonymized_connection =
+ purpose_needs_anonymity(conn->base_.purpose,
+ conn->router_purpose,
+ conn->requested_resource);
+
+ received_bytes = connection_get_inbuf_len(TO_CONN(conn));
switch (connection_fetch_from_buf_http(TO_CONN(conn),
&headers, MAX_HEADERS_SIZE,
@@ -1834,23 +2393,44 @@ 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) {
log_warn(LD_HTTP,"Unparseable headers (server '%s:%d'). Closing.",
conn->base_.address, conn->base_.port);
- tor_free(body); tor_free(headers);
- return -1;
+
+ rv = -1;
+ goto done;
}
if (!reason) reason = tor_strdup("[no reason given]");
- log_debug(LD_DIR,
+ tor_log(LOG_DEBUG, LD_DIR,
"Received response from directory server '%s:%d': %d %s "
- "(purpose: %d)",
+ "(purpose: %d, response size: " U64_FORMAT
+#ifdef MEASUREMENTS_21206
+ ", data cells received: %d, data cells sent: %d"
+#endif
+ ", compression: %d)",
conn->base_.address, conn->base_.port, status_code,
- escaped(reason),
- conn->base_.purpose);
+ escaped(reason), conn->base_.purpose,
+ U64_PRINTF_ARG(received_bytes),
+#ifdef MEASUREMENTS_21206
+ conn->data_cells_received, conn->data_cells_sent,
+#endif
+ compression);
+
+ if (conn->guard_state) {
+ /* we count the connection as successful once we can read from it. We do
+ * not, however, delay use of the circuit here, since it's just for a
+ * one-hop directory request. */
+ /* XXXXprop271 note that this will not do the right thing for other
+ * waiting circuits that would be triggered by this circuit becoming
+ * complete/usable. But that's ok, I think.
+ */
+ entry_guard_succeeded(&conn->guard_state);
+ circuit_guard_state_free(conn->guard_state);
+ conn->guard_state = NULL;
+ }
/* now check if it's got any hints for us about our IP address. */
if (conn->dirconn_direct) {
@@ -1888,532 +2468,802 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
"'%s:%d'. I'll try again soon.",
status_code, escaped(reason), conn->base_.address,
conn->base_.port);
+ time_t now = approx_time();
if ((rs = router_get_mutable_consensus_status_by_id(id_digest)))
rs->last_dir_503_at = now;
if ((ds = router_get_fallback_dirserver_by_digest(id_digest)))
ds->fake_status.last_dir_503_at = now;
- tor_free(body); tor_free(headers); tor_free(reason);
- return -1;
+ rv = -1;
+ goto done;
}
- plausible = body_is_plausible(body, body_len, conn->base_.purpose);
- if (compression != NO_METHOD || !plausible) {
- char *new_body = NULL;
- size_t new_len = 0;
- compress_method_t guessed = detect_compression_method(body, body_len);
- if (compression == UNKNOWN_METHOD || guessed != compression) {
- /* Tell the user if we don't believe what we're told about compression.*/
- const char *description1, *description2;
- if (compression == ZLIB_METHOD)
- description1 = "as deflated";
- else if (compression == GZIP_METHOD)
- description1 = "as gzipped";
- else if (compression == NO_METHOD)
- description1 = "as uncompressed";
- else
- description1 = "with an unknown Content-Encoding";
- if (guessed == ZLIB_METHOD)
- description2 = "deflated";
- else if (guessed == GZIP_METHOD)
- description2 = "gzipped";
- else if (!plausible)
- description2 = "confusing binary junk";
- else
- description2 = "uncompressed";
+ if (dir_client_decompress_response_body(&body, &body_len,
+ conn, compression, anonymized_connection) < 0) {
+ rv = -1;
+ goto done;
+ }
- log_info(LD_HTTP, "HTTP body from server '%s:%d' was labeled %s, "
- "but it seems to be %s.%s",
- conn->base_.address, conn->base_.port, description1,
- description2,
- (compression>0 && guessed>0)?" Trying both.":"");
- }
- /* Try declared compression first if we can. */
- if (compression == GZIP_METHOD || compression == ZLIB_METHOD)
- tor_gzip_uncompress(&new_body, &new_len, body, body_len, compression,
- !allow_partial, LOG_PROTOCOL_WARN);
- /* Okay, if that didn't work, and we think that it was compressed
- * differently, try that. */
- if (!new_body &&
- (guessed == GZIP_METHOD || guessed == ZLIB_METHOD) &&
- compression != guessed)
- tor_gzip_uncompress(&new_body, &new_len, body, body_len, guessed,
- !allow_partial, LOG_PROTOCOL_WARN);
- /* If we're pretty sure that we have a compressed directory, and
- * we didn't manage to uncompress it, then warn and bail. */
- if (!plausible && !new_body) {
- log_fn(LOG_PROTOCOL_WARN, LD_HTTP,
- "Unable to decompress HTTP body (server '%s:%d').",
- conn->base_.address, conn->base_.port);
- tor_free(body); tor_free(headers); tor_free(reason);
- return -1;
- }
- if (new_body) {
- tor_free(body);
- body = new_body;
- body_len = new_len;
- was_compressed = 1;
- }
+ response_handler_args_t args;
+ memset(&args, 0, sizeof(args));
+ args.status_code = status_code;
+ args.reason = reason;
+ args.body = body;
+ args.body_len = body_len;
+ args.headers = headers;
+
+ switch (conn->base_.purpose) {
+ case DIR_PURPOSE_FETCH_CONSENSUS:
+ rv = handle_response_fetch_consensus(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_CERTIFICATE:
+ rv = handle_response_fetch_certificate(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_STATUS_VOTE:
+ rv = handle_response_fetch_status_vote(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES:
+ rv = handle_response_fetch_detached_signatures(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_SERVERDESC:
+ case DIR_PURPOSE_FETCH_EXTRAINFO:
+ rv = handle_response_fetch_desc(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_MICRODESC:
+ rv = handle_response_fetch_microdesc(conn, &args);
+ break;
+ case DIR_PURPOSE_FETCH_RENDDESC_V2:
+ rv = handle_response_fetch_renddesc_v2(conn, &args);
+ break;
+ case DIR_PURPOSE_UPLOAD_DIR:
+ rv = handle_response_upload_dir(conn, &args);
+ break;
+ case DIR_PURPOSE_UPLOAD_SIGNATURES:
+ rv = handle_response_upload_signatures(conn, &args);
+ break;
+ case DIR_PURPOSE_UPLOAD_VOTE:
+ rv = handle_response_upload_vote(conn, &args);
+ break;
+ case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
+ rv = handle_response_upload_renddesc_v2(conn, &args);
+ break;
+ case DIR_PURPOSE_UPLOAD_HSDESC:
+ rv = handle_response_upload_hsdesc(conn, &args);
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ rv = -1;
+ break;
}
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
- int r;
- const char *flavname = conn->requested_resource;
- if (status_code != 200) {
- int severity = (status_code == 304) ? LOG_INFO : LOG_WARN;
- tor_log(severity, LD_DIR,
- "Received http status code %d (%s) from server "
- "'%s:%d' while fetching consensus directory.",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port);
- tor_free(body); tor_free(headers); tor_free(reason);
- networkstatus_consensus_download_failed(status_code, flavname);
- return -1;
+ done:
+ tor_free(body);
+ tor_free(headers);
+ tor_free(reason);
+ return rv;
+}
+
+/**
+ * Handler function: processes a response to a request for a networkstatus
+ * consensus document by checking the consensus, storing it, and marking
+ * router requests as reachable.
+ **/
+static int
+handle_response_fetch_consensus(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS);
+ const int status_code = args->status_code;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+ const char *reason = args->reason;
+ const time_t now = approx_time();
+
+ const char *consensus;
+ char *new_consensus = NULL;
+ const char *sourcename;
+
+ int r;
+ const char *flavname = conn->requested_resource;
+ if (status_code != 200) {
+ int severity = (status_code == 304) ? LOG_INFO : LOG_WARN;
+ tor_log(severity, LD_DIR,
+ "Received http status code %d (%s) from server "
+ "'%s:%d' while fetching consensus directory.",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ networkstatus_consensus_download_failed(status_code, flavname);
+ return -1;
+ }
+
+ if (looks_like_a_consensus_diff(body, body_len)) {
+ /* First find our previous consensus. Maybe it's in ram, maybe not. */
+ cached_dir_t *cd = dirserv_get_consensus(flavname);
+ const char *consensus_body;
+ char *owned_consensus = NULL;
+ if (cd) {
+ consensus_body = cd->dir;
+ } else {
+ owned_consensus = networkstatus_read_cached_consensus(flavname);
+ consensus_body = owned_consensus;
}
- 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) {
- 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.",
- flavname, conn->base_.address, conn->base_.port);
- tor_free(body); tor_free(headers); tor_free(reason);
+ if (!consensus_body) {
+ log_warn(LD_DIR, "Received a consensus diff, but we can't find "
+ "any %s-flavored consensus in our current cache.",flavname);
networkstatus_consensus_download_failed(0, flavname);
+ // XXXX if this happens too much, see below
return -1;
}
- /* launches router downloads as needed */
- routers_update_all_from_networkstatus(now, 3);
- update_microdescs_from_networkstatus(now);
- update_microdesc_downloads(now);
- directory_info_has_arrived(now, 0, 0);
- log_info(LD_DIR, "Successfully loaded consensus.");
- }
-
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) {
- if (status_code != 200) {
- log_warn(LD_DIR,
- "Received http status code %d (%s) from server "
- "'%s:%d' while fetching \"/tor/keys/%s\".",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port, conn->requested_resource);
- connection_dir_download_cert_failed(conn, status_code);
- tor_free(body); tor_free(headers); tor_free(reason);
+
+ new_consensus = consensus_diff_apply(consensus_body, body);
+ tor_free(owned_consensus);
+ if (new_consensus == NULL) {
+ log_warn(LD_DIR, "Could not apply consensus diff received from server "
+ "'%s:%d'", conn->base_.address, conn->base_.port);
+ // XXXX If this happens too many times, we should maybe not use
+ // XXXX this directory for diffs any more?
+ networkstatus_consensus_download_failed(0, flavname);
return -1;
}
- log_info(LD_DIR,"Received authority certificates (size %d) from server "
+ log_info(LD_DIR, "Applied consensus diff (size %d) from server "
+ "'%s:%d', resulting in a new consensus document (size %d).",
+ (int)body_len, conn->base_.address, conn->base_.port,
+ (int)strlen(new_consensus));
+ consensus = new_consensus;
+ sourcename = "generated based on a diff";
+ } else {
+ log_info(LD_DIR,"Received consensus directory (body size %d) from server "
"'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port);
+ consensus = body;
+ sourcename = "downloaded";
+ }
+
+ if ((r=networkstatus_set_current_consensus(consensus, flavname, 0,
+ conn->identity_digest))<0) {
+ log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR,
+ "Unable to load %s consensus directory %s from "
+ "server '%s:%d'. I'll try again soon.",
+ flavname, sourcename, conn->base_.address, conn->base_.port);
+ networkstatus_consensus_download_failed(0, flavname);
+ tor_free(new_consensus);
+ return -1;
+ }
- /*
- * Tell trusted_dirs_load_certs_from_string() whether it was by fp
- * or fp-sk pair.
- */
- src_code = -1;
- if (!strcmpstart(conn->requested_resource, "fp/")) {
- src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST;
- } else if (!strcmpstart(conn->requested_resource, "fp-sk/")) {
- src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST;
- }
+ /* If we launched other fetches for this consensus, cancel them. */
+ connection_dir_close_consensus_fetches(conn, flavname);
- if (src_code != -1) {
- if (trusted_dirs_load_certs_from_string(body, src_code, 1)<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 */
- connection_dir_download_cert_failed(conn, status_code);
- } else {
- directory_info_has_arrived(now, 0, 0);
- log_info(LD_DIR, "Successfully loaded certificates from fetch.");
- }
- } else {
- log_warn(LD_DIR,
- "Couldn't figure out what to do with fetched certificates for "
- "unknown resource %s",
- conn->requested_resource);
+ /* launches router downloads as needed */
+ routers_update_all_from_networkstatus(now, 3);
+ update_microdescs_from_networkstatus(now);
+ update_microdesc_downloads(now);
+ directory_info_has_arrived(now, 0, 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.");
+
+ tor_free(new_consensus);
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for one or more
+ * authority certificates
+ **/
+static int
+handle_response_fetch_certificate(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ if (status_code != 200) {
+ log_warn(LD_DIR,
+ "Received http status code %d (%s) from server "
+ "'%s:%d' while fetching \"/tor/keys/%s\".",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port, conn->requested_resource);
+ connection_dir_download_cert_failed(conn, status_code);
+ return -1;
+ }
+ log_info(LD_DIR,"Received authority certificates (body size %d) from "
+ "server '%s:%d'",
+ (int)body_len, conn->base_.address, conn->base_.port);
+
+ /*
+ * Tell trusted_dirs_load_certs_from_string() whether it was by fp
+ * or fp-sk pair.
+ */
+ int src_code = -1;
+ if (!strcmpstart(conn->requested_resource, "fp/")) {
+ src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST;
+ } else if (!strcmpstart(conn->requested_resource, "fp-sk/")) {
+ src_code = TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_SK_DIGEST;
+ }
+
+ if (src_code != -1) {
+ 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 */
connection_dir_download_cert_failed(conn, status_code);
+ } else {
+ time_t now = approx_time();
+ directory_info_has_arrived(now, 0, 0);
+ log_info(LD_DIR, "Successfully loaded certificates from fetch.");
}
+ } else {
+ log_warn(LD_DIR,
+ "Couldn't figure out what to do with fetched certificates for "
+ "unknown resource %s",
+ conn->requested_resource);
+ connection_dir_download_cert_failed(conn, status_code);
}
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) {
- const char *msg;
- int st;
- log_info(LD_DIR,"Got votes (size %d) from server %s:%d",
- (int)body_len, conn->base_.address, conn->base_.port);
- if (status_code != 200) {
- log_warn(LD_DIR,
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for an authority's
+ * current networkstatus vote.
+ **/
+static int
+handle_response_fetch_status_vote(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ const char *msg;
+ int st;
+ log_info(LD_DIR,"Got votes (body size %d) from server %s:%d",
+ (int)body_len, conn->base_.address, conn->base_.port);
+ if (status_code != 200) {
+ log_warn(LD_DIR,
"Received http status code %d (%s) from server "
"'%s:%d' while fetching \"/tor/status-vote/next/%s.z\".",
status_code, escaped(reason), conn->base_.address,
conn->base_.port, conn->requested_resource);
- tor_free(body); tor_free(headers); tor_free(reason);
- return -1;
- }
- dirvote_add_vote(body, &msg, &st);
- if (st > 299) {
- log_warn(LD_DIR, "Error adding retrieved vote: %s", msg);
- } else {
- log_info(LD_DIR, "Added vote(s) successfully [msg: %s]", msg);
- }
+ return -1;
}
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) {
- const char *msg = NULL;
- log_info(LD_DIR,"Got detached signatures (size %d) from server %s:%d",
- (int)body_len, conn->base_.address, conn->base_.port);
- if (status_code != 200) {
- log_warn(LD_DIR,
+ dirvote_add_vote(body, &msg, &st);
+ if (st > 299) {
+ log_warn(LD_DIR, "Error adding retrieved vote: %s", msg);
+ } else {
+ log_info(LD_DIR, "Added vote(s) successfully [msg: %s]", msg);
+ }
+
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for the signatures
+ * that an authority knows about on a given consensus.
+ **/
+static int
+handle_response_fetch_detached_signatures(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ const char *msg = NULL;
+ log_info(LD_DIR,"Got detached signatures (body size %d) from server %s:%d",
+ (int)body_len, conn->base_.address, conn->base_.port);
+ if (status_code != 200) {
+ log_warn(LD_DIR,
"Received http status code %d (%s) from server '%s:%d' while fetching "
"\"/tor/status-vote/next/consensus-signatures.z\".",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port);
- tor_free(body); tor_free(headers); tor_free(reason);
- return -1;
- }
- if (dirvote_add_signatures(body, conn->base_.address, &msg)<0) {
- log_warn(LD_DIR, "Problem adding detached signatures from %s:%d: %s",
- conn->base_.address, conn->base_.port, msg?msg:"???");
- }
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ return -1;
+ }
+ if (dirvote_add_signatures(body, conn->base_.address, &msg)<0) {
+ log_warn(LD_DIR, "Problem adding detached signatures from %s:%d: %s",
+ conn->base_.address, conn->base_.port, msg?msg:"???");
}
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC ||
- conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) {
- int was_ei = conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO;
- smartlist_t *which = NULL;
- int n_asked_for = 0;
- int descriptor_digests = conn->requested_resource &&
- !strcmpstart(conn->requested_resource,"d/");
- log_info(LD_DIR,"Received %s (size %d) from server '%s:%d'",
- was_ei ? "extra server info" : "server info",
- (int)body_len, conn->base_.address, conn->base_.port);
- if (conn->requested_resource &&
- (!strcmpstart(conn->requested_resource,"d/") ||
- !strcmpstart(conn->requested_resource,"fp/"))) {
- which = smartlist_new();
- dir_split_resource_into_fingerprints(conn->requested_resource +
- (descriptor_digests ? 2 : 3),
- which, NULL, 0);
- n_asked_for = smartlist_len(which);
- }
- if (status_code != 200) {
- int dir_okay = status_code == 404 ||
- (status_code == 400 && !strcmp(reason, "Servers unavailable."));
- /* 404 means that it didn't have them; no big deal.
- * Older (pre-0.1.1.8) servers said 400 Servers unavailable instead. */
- log_fn(dir_okay ? LOG_INFO : LOG_WARN, LD_DIR,
- "Received http status code %d (%s) from server '%s:%d' "
- "while fetching \"/tor/server/%s\". I'll try again soon.",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port, conn->requested_resource);
- if (!which) {
- connection_dir_download_routerdesc_failed(conn);
- } else {
- dir_routerdesc_download_failed(which, status_code,
- conn->router_purpose,
- was_ei, descriptor_digests);
- SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
- smartlist_free(which);
- }
- tor_free(body); tor_free(headers); tor_free(reason);
- return dir_okay ? 0 : -1;
- }
- /* Learn the routers, assuming we requested by fingerprint or "all"
- * or "authority".
- *
- * We use "authority" to fetch our own descriptor for
- * testing, and to fetch bridge descriptors for bootstrapping. Ignore
- * the output of "authority" requests unless we are using bridges,
- * since otherwise they'll be the response from reachability tests,
- * and we don't really want to add that to our routerlist. */
- if (which || (conn->requested_resource &&
- (!strcmpstart(conn->requested_resource, "all") ||
- (!strcmpstart(conn->requested_resource, "authority") &&
- get_options()->UseBridges)))) {
- /* as we learn from them, we remove them from 'which' */
- if (was_ei) {
- router_load_extrainfo_from_string(body, NULL, SAVED_NOWHERE, which,
- descriptor_digests);
- } else {
- //router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which,
- // descriptor_digests, conn->router_purpose);
- if (load_downloaded_routers(body, which, descriptor_digests,
- conn->router_purpose,
- conn->base_.address))
- directory_info_has_arrived(now, 0, 0);
- }
- }
- if (which) { /* mark remaining ones as failed */
- log_info(LD_DIR, "Received %d/%d %s requested from %s:%d",
- n_asked_for-smartlist_len(which), n_asked_for,
- was_ei ? "extra-info documents" : "router descriptors",
- conn->base_.address, (int)conn->base_.port);
- if (smartlist_len(which)) {
- dir_routerdesc_download_failed(which, status_code,
- conn->router_purpose,
- was_ei, descriptor_digests);
- }
- SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
- smartlist_free(which);
- }
- if (directory_conn_is_self_reachability_test(conn))
- router_dirport_found_reachable();
- }
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) {
- smartlist_t *which = NULL;
- log_info(LD_DIR,"Received answer to microdescriptor request (status %d, "
- "size %d) from server '%s:%d'",
- status_code, (int)body_len, conn->base_.address,
- conn->base_.port);
- tor_assert(conn->requested_resource &&
- !strcmpstart(conn->requested_resource, "d/"));
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for a group of server
+ * descriptors or an extrainfo documents.
+ **/
+static int
+handle_response_fetch_desc(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC ||
+ conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ int was_ei = conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO;
+ smartlist_t *which = NULL;
+ int n_asked_for = 0;
+ int descriptor_digests = conn->requested_resource &&
+ !strcmpstart(conn->requested_resource,"d/");
+ log_info(LD_DIR,"Received %s (body size %d) from server '%s:%d'",
+ was_ei ? "extra server info" : "server info",
+ (int)body_len, conn->base_.address, conn->base_.port);
+ if (conn->requested_resource &&
+ (!strcmpstart(conn->requested_resource,"d/") ||
+ !strcmpstart(conn->requested_resource,"fp/"))) {
which = smartlist_new();
- dir_split_resource_into_fingerprints(conn->requested_resource+2,
- which, NULL,
- DSR_DIGEST256|DSR_BASE64);
- if (status_code != 200) {
- log_info(LD_DIR, "Received status code %d (%s) from server "
- "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again "
- "soon.",
- status_code, escaped(reason), conn->base_.address,
- (int)conn->base_.port, conn->requested_resource);
- dir_microdesc_download_failed(which, status_code);
+ dir_split_resource_into_fingerprints(conn->requested_resource +
+ (descriptor_digests ? 2 : 3),
+ which, NULL, 0);
+ n_asked_for = smartlist_len(which);
+ }
+ if (status_code != 200) {
+ int dir_okay = status_code == 404 ||
+ (status_code == 400 && !strcmp(reason, "Servers unavailable."));
+ /* 404 means that it didn't have them; no big deal.
+ * Older (pre-0.1.1.8) servers said 400 Servers unavailable instead. */
+ log_fn(dir_okay ? LOG_INFO : LOG_WARN, LD_DIR,
+ "Received http status code %d (%s) from server '%s:%d' "
+ "while fetching \"/tor/server/%s\". I'll try again soon.",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port, conn->requested_resource);
+ if (!which) {
+ connection_dir_download_routerdesc_failed(conn);
+ } else {
+ dir_routerdesc_download_failed(which, status_code,
+ conn->router_purpose,
+ was_ei, descriptor_digests);
SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
smartlist_free(which);
- tor_free(body); tor_free(headers); tor_free(reason);
- return 0;
+ }
+ return dir_okay ? 0 : -1;
+ }
+ /* Learn the routers, assuming we requested by fingerprint or "all"
+ * or "authority".
+ *
+ * We use "authority" to fetch our own descriptor for
+ * testing, and to fetch bridge descriptors for bootstrapping. Ignore
+ * the output of "authority" requests unless we are using bridges,
+ * since otherwise they'll be the response from reachability tests,
+ * and we don't really want to add that to our routerlist. */
+ if (which || (conn->requested_resource &&
+ (!strcmpstart(conn->requested_resource, "all") ||
+ (!strcmpstart(conn->requested_resource, "authority") &&
+ get_options()->UseBridges)))) {
+ /* as we learn from them, we remove them from 'which' */
+ if (was_ei) {
+ router_load_extrainfo_from_string(body, NULL, SAVED_NOWHERE, which,
+ descriptor_digests);
} else {
- smartlist_t *mds;
- mds = microdescs_add_to_cache(get_microdesc_cache(),
- body, body+body_len, SAVED_NOWHERE, 0,
- now, which);
- if (smartlist_len(which)) {
- /* Mark remaining ones as failed. */
- dir_microdesc_download_failed(which, status_code);
- }
- if (mds && smartlist_len(mds)) {
- control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS,
- count_loading_descriptors_progress());
- directory_info_has_arrived(now, 0, 1);
+ //router_load_routers_from_string(body, NULL, SAVED_NOWHERE, which,
+ // descriptor_digests, conn->router_purpose);
+ if (load_downloaded_routers(body, which, descriptor_digests,
+ conn->router_purpose,
+ conn->base_.address)) {
+ time_t now = approx_time();
+ directory_info_has_arrived(now, 0, 0);
}
- SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
- smartlist_free(which);
- smartlist_free(mds);
}
}
+ if (which) { /* mark remaining ones as failed */
+ log_info(LD_DIR, "Received %d/%d %s requested from %s:%d",
+ n_asked_for-smartlist_len(which), n_asked_for,
+ was_ei ? "extra-info documents" : "router descriptors",
+ conn->base_.address, (int)conn->base_.port);
+ if (smartlist_len(which)) {
+ dir_routerdesc_download_failed(which, status_code,
+ conn->router_purpose,
+ was_ei, descriptor_digests);
+ }
+ SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
+ smartlist_free(which);
+ }
+ if (directory_conn_is_self_reachability_test(conn))
+ router_dirport_found_reachable();
- if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_DIR) {
- switch (status_code) {
- case 200: {
- dir_server_t *ds =
- router_get_trusteddirserver_by_digest(conn->identity_digest);
- char *rejected_hdr = http_get_header(headers,
- "X-Descriptor-Not-New: ");
- if (rejected_hdr) {
- if (!strcmp(rejected_hdr, "Yes")) {
- log_info(LD_GENERAL,
- "Authority '%s' declined our descriptor (not new)",
- 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
- * 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
- * do. -NM */
- }
- tor_free(rejected_hdr);
- }
- log_info(LD_GENERAL,"eof (status 200) after uploading server "
- "descriptor: finished.");
- control_event_server_status(
- LOG_NOTICE, "ACCEPTED_SERVER_DESCRIPTOR DIRAUTH=%s:%d",
- conn->base_.address, conn->base_.port);
-
- ds->has_accepted_serverdesc = 1;
- if (directories_have_accepted_server_descriptor())
- control_event_server_status(LOG_NOTICE, "GOOD_SERVER_DESCRIPTOR");
- }
- break;
- case 400:
- log_warn(LD_GENERAL,"http status 400 (%s) response from "
- "dirserver '%s:%d'. Please correct.",
- escaped(reason), conn->base_.address, conn->base_.port);
- control_event_server_status(LOG_WARN,
- "BAD_SERVER_DESCRIPTOR DIRAUTH=%s:%d REASON=\"%s\"",
- conn->base_.address, conn->base_.port, escaped(reason));
- break;
- default:
- log_warn(LD_GENERAL,
- "http status %d (%s) reason unexpected while uploading "
- "descriptor to server '%s:%d').",
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for a group of
+ * microdescriptors
+ **/
+static int
+handle_response_fetch_microdesc(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+ smartlist_t *which = NULL;
+ log_info(LD_DIR,"Received answer to microdescriptor request (status %d, "
+ "body size %d) from server '%s:%d'",
+ status_code, (int)body_len, conn->base_.address,
+ conn->base_.port);
+ tor_assert(conn->requested_resource &&
+ !strcmpstart(conn->requested_resource, "d/"));
+ which = smartlist_new();
+ dir_split_resource_into_fingerprints(conn->requested_resource+2,
+ which, NULL,
+ DSR_DIGEST256|DSR_BASE64);
+ if (status_code != 200) {
+ log_info(LD_DIR, "Received status code %d (%s) from server "
+ "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again "
+ "soon.",
status_code, escaped(reason), conn->base_.address,
- conn->base_.port);
- break;
+ (int)conn->base_.port, conn->requested_resource);
+ dir_microdesc_download_failed(which, status_code);
+ SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
+ smartlist_free(which);
+ return 0;
+ } else {
+ smartlist_t *mds;
+ time_t now = approx_time();
+ mds = microdescs_add_to_cache(get_microdesc_cache(),
+ body, body+body_len, SAVED_NOWHERE, 0,
+ now, which);
+ if (smartlist_len(which)) {
+ /* Mark remaining ones as failed. */
+ dir_microdesc_download_failed(which, status_code);
+ }
+ if (mds && smartlist_len(mds)) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS,
+ count_loading_descriptors_progress());
+ directory_info_has_arrived(now, 0, 1);
}
- /* return 0 in all cases, since we don't want to mark any
- * dirservers down just because they don't like us. */
+ SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
+ smartlist_free(which);
+ smartlist_free(mds);
}
- if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_VOTE) {
- switch (status_code) {
- case 200: {
- log_notice(LD_DIR,"Uploaded a vote to dirserver %s:%d",
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a POST request to upload our
+ * router descriptor.
+ **/
+static int
+handle_response_upload_dir(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_DIR);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *headers = args->headers;
+
+ switch (status_code) {
+ case 200: {
+ dir_server_t *ds =
+ router_get_trusteddirserver_by_digest(conn->identity_digest);
+ char *rejected_hdr = http_get_header(headers,
+ "X-Descriptor-Not-New: ");
+ if (rejected_hdr) {
+ if (!strcmp(rejected_hdr, "Yes")) {
+ log_info(LD_GENERAL,
+ "Authority '%s' declined our descriptor (not new)",
+ ds->nickname);
+ /* XXXX use this information; be sure to upload next one
+ * sooner. -NM */
+ /* 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
+ * do. -NM */
+ }
+ tor_free(rejected_hdr);
+ }
+ log_info(LD_GENERAL,"eof (status 200) after uploading server "
+ "descriptor: finished.");
+ control_event_server_status(
+ LOG_NOTICE, "ACCEPTED_SERVER_DESCRIPTOR DIRAUTH=%s:%d",
conn->base_.address, conn->base_.port);
- }
- break;
- case 400:
- log_warn(LD_DIR,"http status 400 (%s) response after uploading "
- "vote to dirserver '%s:%d'. Please correct.",
- escaped(reason), conn->base_.address, conn->base_.port);
- break;
- default:
- log_warn(LD_GENERAL,
- "http status %d (%s) reason unexpected while uploading "
- "vote to server '%s:%d').",
+
+ ds->has_accepted_serverdesc = 1;
+ if (directories_have_accepted_server_descriptor())
+ control_event_server_status(LOG_NOTICE, "GOOD_SERVER_DESCRIPTOR");
+ }
+ break;
+ case 400:
+ log_warn(LD_GENERAL,"http status 400 (%s) response from "
+ "dirserver '%s:%d'. Please correct.",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ control_event_server_status(LOG_WARN,
+ "BAD_SERVER_DESCRIPTOR DIRAUTH=%s:%d REASON=\"%s\"",
+ conn->base_.address, conn->base_.port, escaped(reason));
+ break;
+ default:
+ log_warn(LD_GENERAL,
+ "HTTP status %d (%s) was unexpected while uploading "
+ "descriptor to server '%s:%d'. Possibly the server is "
+ "misconfigured?",
status_code, escaped(reason), conn->base_.address,
conn->base_.port);
- break;
- }
- /* return 0 in all cases, since we don't want to mark any
- * dirservers down just because they don't like us. */
+ break;
}
+ /* return 0 in all cases, since we don't want to mark any
+ * dirservers down just because they don't like us. */
- if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES) {
- switch (status_code) {
- case 200: {
- log_notice(LD_DIR,"Uploaded signature(s) to dirserver %s:%d",
- conn->base_.address, conn->base_.port);
- }
- break;
- case 400:
- log_warn(LD_DIR,"http status 400 (%s) response after uploading "
- "signatures to dirserver '%s:%d'. Please correct.",
- escaped(reason), conn->base_.address, conn->base_.port);
- break;
- default:
- log_warn(LD_GENERAL,
- "http status %d (%s) reason unexpected while uploading "
- "signatures to server '%s:%d').",
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to POST request to upload our
+ * own networkstatus vote.
+ **/
+static int
+handle_response_upload_vote(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_VOTE);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+
+ switch (status_code) {
+ case 200: {
+ log_notice(LD_DIR,"Uploaded a vote to dirserver %s:%d",
+ conn->base_.address, conn->base_.port);
+ }
+ break;
+ case 400:
+ log_warn(LD_DIR,"http status 400 (%s) response after uploading "
+ "vote to dirserver '%s:%d'. Please correct.",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ break;
+ default:
+ log_warn(LD_GENERAL,
+ "HTTP status %d (%s) was unexpected while uploading "
+ "vote to server '%s:%d'.",
status_code, escaped(reason), conn->base_.address,
conn->base_.port);
- break;
- }
- /* return 0 in all cases, since we don't want to mark any
- * dirservers down just because they don't like us. */
- }
-
- if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) {
- #define SEND_HS_DESC_FAILED_EVENT(reason) ( \
- control_event_hs_descriptor_failed(conn->rend_data, \
- conn->identity_digest, \
- reason) )
- #define SEND_HS_DESC_FAILED_CONTENT() ( \
- control_event_hs_descriptor_content(conn->rend_data->onion_address, \
- conn->requested_resource, \
- conn->identity_digest, \
- NULL) )
- tor_assert(conn->rend_data);
- log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
- "(%s))",
- (int)body_len, status_code, escaped(reason));
- switch (status_code) {
- case 200:
- {
- rend_cache_entry_t *entry = NULL;
-
- if (rend_cache_store_v2_desc_as_client(body,
- conn->requested_resource, conn->rend_data, &entry) < 0) {
- log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
- "Retrying at another directory.");
- /* We'll retry when connection_about_to_close_connection()
- * cleans this dir conn up. */
- SEND_HS_DESC_FAILED_EVENT("BAD_DESC");
- SEND_HS_DESC_FAILED_CONTENT();
- } else {
- char service_id[REND_SERVICE_ID_LEN_BASE32 + 1];
- /* Should never be NULL here if we found the descriptor. */
- tor_assert(entry);
- rend_get_service_id(entry->parsed->pk, service_id);
-
- /* success. notify pending connections about this. */
- log_info(LD_REND, "Successfully fetched v2 rendezvous "
- "descriptor.");
- control_event_hs_descriptor_received(service_id,
- conn->rend_data,
- conn->identity_digest);
- control_event_hs_descriptor_content(service_id,
- conn->requested_resource,
- conn->identity_digest,
- body);
- conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2;
- rend_client_desc_trynow(service_id);
- memwipe(service_id, 0, sizeof(service_id));
- }
- break;
- }
- case 404:
- /* Not there. We'll retry when
- * connection_about_to_close_connection() cleans this conn up. */
- log_info(LD_REND,"Fetching v2 rendezvous descriptor failed: "
- "Retrying at another directory.");
- SEND_HS_DESC_FAILED_EVENT("NOT_FOUND");
- SEND_HS_DESC_FAILED_CONTENT();
- break;
- case 400:
- log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: "
- "http status 400 (%s). Dirserver didn't like our "
- "v2 rendezvous query? Retrying at another directory.",
- escaped(reason));
- SEND_HS_DESC_FAILED_EVENT("QUERY_REJECTED");
- SEND_HS_DESC_FAILED_CONTENT();
- break;
- default:
- log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: "
- "http status %d (%s) response unexpected while "
- "fetching v2 hidden service descriptor (server '%s:%d'). "
- "Retrying at another directory.",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port);
- SEND_HS_DESC_FAILED_EVENT("UNEXPECTED");
+ break;
+ }
+ /* return 0 in all cases, since we don't want to mark any
+ * dirservers down just because they don't like us. */
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to POST request to upload our
+ * view of the signatures on the current consensus.
+ **/
+static int
+handle_response_upload_signatures(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+
+ switch (status_code) {
+ case 200: {
+ log_notice(LD_DIR,"Uploaded signature(s) to dirserver %s:%d",
+ conn->base_.address, conn->base_.port);
+ }
+ break;
+ case 400:
+ log_warn(LD_DIR,"http status 400 (%s) response after uploading "
+ "signatures to dirserver '%s:%d'. Please correct.",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ break;
+ default:
+ log_warn(LD_GENERAL,
+ "HTTP status %d (%s) was unexpected while uploading "
+ "signatures to server '%s:%d'.",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ break;
+ }
+ /* return 0 in all cases, since we don't want to mark any
+ * dirservers down just because they don't like us. */
+
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a request for a v2 hidden service
+ * descriptor.
+ **/
+static int
+handle_response_fetch_renddesc_v2(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+ const char *body = args->body;
+ const size_t body_len = args->body_len;
+
+#define SEND_HS_DESC_FAILED_EVENT(reason) \
+ (control_event_hs_descriptor_failed(conn->rend_data, \
+ conn->identity_digest, \
+ reason))
+#define SEND_HS_DESC_FAILED_CONTENT() \
+ (control_event_hs_descriptor_content( \
+ rend_data_get_address(conn->rend_data), \
+ conn->requested_resource, \
+ conn->identity_digest, \
+ NULL))
+
+ tor_assert(conn->rend_data);
+ log_info(LD_REND,"Received rendezvous descriptor (body size %d, status %d "
+ "(%s))",
+ (int)body_len, status_code, escaped(reason));
+ switch (status_code) {
+ case 200:
+ {
+ rend_cache_entry_t *entry = NULL;
+
+ if (rend_cache_store_v2_desc_as_client(body,
+ conn->requested_resource,
+ conn->rend_data, &entry) < 0) {
+ log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
+ "Retrying at another directory.");
+ /* We'll retry when connection_about_to_close_connection()
+ * cleans this dir conn up. */
+ SEND_HS_DESC_FAILED_EVENT("BAD_DESC");
SEND_HS_DESC_FAILED_CONTENT();
- break;
+ } else {
+ char service_id[REND_SERVICE_ID_LEN_BASE32 + 1];
+ /* Should never be NULL here if we found the descriptor. */
+ tor_assert(entry);
+ rend_get_service_id(entry->parsed->pk, service_id);
+
+ /* success. notify pending connections about this. */
+ log_info(LD_REND, "Successfully fetched v2 rendezvous "
+ "descriptor.");
+ control_event_hs_descriptor_received(service_id,
+ conn->rend_data,
+ conn->identity_digest);
+ control_event_hs_descriptor_content(service_id,
+ conn->requested_resource,
+ conn->identity_digest,
+ body);
+ conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2;
+ rend_client_desc_trynow(service_id);
+ memwipe(service_id, 0, sizeof(service_id));
+ }
+ break;
}
+ case 404:
+ /* Not there. We'll retry when
+ * connection_about_to_close_connection() cleans this conn up. */
+ log_info(LD_REND,"Fetching v2 rendezvous descriptor failed: "
+ "Retrying at another directory.");
+ SEND_HS_DESC_FAILED_EVENT("NOT_FOUND");
+ SEND_HS_DESC_FAILED_CONTENT();
+ break;
+ case 400:
+ log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: "
+ "http status 400 (%s). Dirserver didn't like our "
+ "v2 rendezvous query? Retrying at another directory.",
+ escaped(reason));
+ SEND_HS_DESC_FAILED_EVENT("QUERY_REJECTED");
+ SEND_HS_DESC_FAILED_CONTENT();
+ break;
+ default:
+ log_warn(LD_REND, "Fetching v2 rendezvous descriptor failed: "
+ "http status %d (%s) response unexpected while "
+ "fetching v2 hidden service descriptor (server '%s:%d'). "
+ "Retrying at another directory.",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ SEND_HS_DESC_FAILED_EVENT("UNEXPECTED");
+ SEND_HS_DESC_FAILED_CONTENT();
+ break;
}
- if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) {
- #define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) ( \
- control_event_hs_descriptor_upload_failed( \
- conn->identity_digest, \
- conn->rend_data->onion_address, \
- reason) )
- log_info(LD_REND,"Uploaded rendezvous descriptor (status %d "
- "(%s))",
- status_code, escaped(reason));
- /* Without the rend data, we'll have a problem identifying what has been
- * uploaded for which service. */
- tor_assert(conn->rend_data);
- switch (status_code) {
- case 200:
- log_info(LD_REND,
- "Uploading rendezvous descriptor: finished with status "
- "200 (%s)", escaped(reason));
- control_event_hs_descriptor_uploaded(conn->identity_digest,
- conn->rend_data->onion_address);
- rend_service_desc_has_uploaded(conn->rend_data);
- break;
- case 400:
- log_warn(LD_REND,"http status 400 (%s) response from dirserver "
- "'%s:%d'. Malformed rendezvous descriptor?",
- escaped(reason), conn->base_.address, conn->base_.port);
- SEND_HS_DESC_UPLOAD_FAILED_EVENT("UPLOAD_REJECTED");
- break;
- default:
- log_warn(LD_REND,"http status %d (%s) response unexpected (server "
- "'%s:%d').",
- status_code, escaped(reason), conn->base_.address,
- conn->base_.port);
- SEND_HS_DESC_UPLOAD_FAILED_EVENT("UNEXPECTED");
- break;
- }
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a POST request to upload a v2
+ * hidden service descriptor.
+ **/
+static int
+handle_response_upload_renddesc_v2(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2);
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+
+#define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) \
+ (control_event_hs_descriptor_upload_failed( \
+ conn->identity_digest, \
+ rend_data_get_address(conn->rend_data), \
+ reason))
+
+ log_info(LD_REND,"Uploaded rendezvous descriptor (status %d "
+ "(%s))",
+ status_code, escaped(reason));
+ /* Without the rend data, we'll have a problem identifying what has been
+ * uploaded for which service. */
+ tor_assert(conn->rend_data);
+ switch (status_code) {
+ case 200:
+ log_info(LD_REND,
+ "Uploading rendezvous descriptor: finished with status "
+ "200 (%s)", escaped(reason));
+ control_event_hs_descriptor_uploaded(conn->identity_digest,
+ rend_data_get_address(conn->rend_data));
+ rend_service_desc_has_uploaded(conn->rend_data);
+ break;
+ case 400:
+ log_warn(LD_REND,"http status 400 (%s) response from dirserver "
+ "'%s:%d'. Malformed rendezvous descriptor?",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ SEND_HS_DESC_UPLOAD_FAILED_EVENT("UPLOAD_REJECTED");
+ break;
+ default:
+ log_warn(LD_REND,"http status %d (%s) response unexpected (server "
+ "'%s:%d').",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ SEND_HS_DESC_UPLOAD_FAILED_EVENT("UNEXPECTED");
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * Handler function: processes a response to a POST request to upload an
+ * hidden service descriptor.
+ **/
+static int
+handle_response_upload_hsdesc(dir_connection_t *conn,
+ const response_handler_args_t *args)
+{
+ const int status_code = args->status_code;
+ const char *reason = args->reason;
+
+ tor_assert(conn);
+ tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+
+ log_info(LD_REND, "Uploaded hidden service descriptor (status %d "
+ "(%s))",
+ status_code, escaped(reason));
+ /* For this directory response, it MUST have an hidden service identifier on
+ * this connection. */
+ tor_assert(conn->hs_ident);
+ switch (status_code) {
+ case 200:
+ log_info(LD_REND, "Uploading hidden service descriptor: "
+ "finished with status 200 (%s)", escaped(reason));
+ /* XXX: Trigger control event. */
+ break;
+ case 400:
+ log_warn(LD_REND, "Uploading hidden service descriptor: http "
+ "status 400 (%s) response from dirserver "
+ "'%s:%d'. Malformed hidden service descriptor?",
+ escaped(reason), conn->base_.address, conn->base_.port);
+ /* XXX: Trigger control event. */
+ break;
+ default:
+ log_warn(LD_REND, "Uploading hidden service descriptor: http "
+ "status %d (%s) response unexpected (server "
+ "'%s:%d').",
+ status_code, escaped(reason), conn->base_.address,
+ conn->base_.port);
+ /* XXX: Trigger control event. */
+ break;
}
- note_client_request(conn->base_.purpose, was_compressed, orig_len);
- tor_free(body); tor_free(headers); tor_free(reason);
+
return 0;
}
@@ -2506,7 +3356,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn)
* refetching is unnecessary.) */
if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
dir_conn->rend_data &&
- strlen(dir_conn->rend_data->onion_address) == REND_SERVICE_ID_LEN_BASE32)
+ strlen(rend_data_get_address(dir_conn->rend_data)) ==
+ REND_SERVICE_ID_LEN_BASE32)
rend_client_refetch_v2_renddesc(dir_conn->rend_data);
}
@@ -2603,139 +3454,116 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length,
/** As write_http_response_header_impl, but sets encoding and content-typed
* based on whether the response will be <b>compressed</b> or not. */
static void
-write_http_response_header(dir_connection_t *conn, ssize_t length,
- int compressed, long cache_lifetime)
+write_http_response_headers(dir_connection_t *conn, ssize_t length,
+ compress_method_t method,
+ const char *extra_headers, long cache_lifetime)
{
+ const char *methodname = compression_method_get_name(method);
+ const char *doctype;
+ if (method == NO_METHOD)
+ doctype = "text/plain";
+ else
+ doctype = "application/octet-stream";
write_http_response_header_impl(conn, length,
- compressed?"application/octet-stream":"text/plain",
- compressed?"deflate":"identity",
- NULL,
- cache_lifetime);
+ doctype,
+ methodname,
+ extra_headers,
+ 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. */
+/** As write_http_response_headers, but assumes extra_headers is NULL */
static void
-note_client_request(int purpose, int compressed, size_t bytes)
+write_http_response_header(dir_connection_t *conn, ssize_t length,
+ compress_method_t method,
+ long cache_lifetime)
{
- 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);
+ write_http_response_headers(conn, length, method, NULL, cache_lifetime);
}
-/** Helper: initialize the request map to instrument downloads. */
-static void
-ensure_request_map_initialized(void)
+/** Array of compression methods to use (if supported) for serving
+ * precompressed data, ordered from best to worst. */
+static compress_method_t srv_meth_pref_precompressed[] = {
+ LZMA_METHOD,
+ ZSTD_METHOD,
+ ZLIB_METHOD,
+ GZIP_METHOD,
+ NO_METHOD
+};
+
+/** Array of compression methods to use (if supported) for serving
+ * streamed data, ordered from best to worst. */
+static compress_method_t srv_meth_pref_streaming_compression[] = {
+ ZSTD_METHOD,
+ ZLIB_METHOD,
+ GZIP_METHOD,
+ NO_METHOD
+};
+
+/** Array of allowed compression methods to use (if supported) when receiving a
+ * response from a request that was required to be anonymous. */
+static compress_method_t client_meth_allowed_anonymous_compression[] = {
+ ZLIB_METHOD,
+ GZIP_METHOD,
+ NO_METHOD
+};
+
+/** Parse the compression methods listed in an Accept-Encoding header <b>h</b>,
+ * and convert them to a bitfield where compression method x is supported if
+ * and only if 1 &lt;&lt; x is set in the bitfield. */
+STATIC unsigned
+parse_accept_encoding_header(const char *h)
{
- if (!request_map)
- request_map = strmap_new();
+ unsigned result = (1u << NO_METHOD);
+ smartlist_t *methods = smartlist_new();
+ smartlist_split_string(methods, h, ",",
+ SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+
+ SMARTLIST_FOREACH_BEGIN(methods, const char *, m) {
+ compress_method_t method = compression_method_get_by_name(m);
+ if (method != UNKNOWN_METHOD) {
+ tor_assert(((unsigned)method) < 8*sizeof(unsigned));
+ result |= (1u << method);
+ }
+ } SMARTLIST_FOREACH_END(m);
+ SMARTLIST_FOREACH_BEGIN(methods, char *, m) {
+ tor_free(m);
+ } SMARTLIST_FOREACH_END(m);
+ smartlist_free(methods);
+ return result;
}
-/** 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)
+/** Array of compression methods to use (if supported) for requesting
+ * compressed data, ordered from best to worst. */
+static compress_method_t client_meth_pref[] = {
+ LZMA_METHOD,
+ ZSTD_METHOD,
+ ZLIB_METHOD,
+ GZIP_METHOD,
+ NO_METHOD
+};
+
+/** Return a newly allocated string containing a comma separated list of
+ * supported encodings. */
+STATIC char *
+accept_encoding_header(void)
{
- request_t *r;
- ensure_request_map_initialized();
+ smartlist_t *methods = smartlist_new();
+ char *header = NULL;
+ compress_method_t method;
+ unsigned i;
- r = strmap_get(request_map, key);
- if (!r) {
- r = tor_malloc_zero(sizeof(request_t));
- strmap_set(request_map, key, r);
+ for (i = 0; i < ARRAY_LENGTH(client_meth_pref); ++i) {
+ method = client_meth_pref[i];
+ if (tor_compress_supports_method(method))
+ smartlist_add(methods, (char *)compression_method_get_name(method));
}
- 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;
-}
+ header = smartlist_join_strings(methods, ", ", 0, NULL);
+ smartlist_free(methods);
-void
-note_request(const char *key, size_t bytes)
-{
- (void)key;
- (void)bytes;
+ return header;
}
-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
@@ -2751,47 +3579,45 @@ directory_dump_request_log(void)
* consensus, 0 otherwise.
*/
int
-client_likes_consensus(networkstatus_t *v, const char *want_url)
+client_likes_consensus(const struct consensus_cache_entry_t *ent,
+ const char *want_url)
{
- smartlist_t *want_authorities = smartlist_new();
+ smartlist_t *voters = smartlist_new();
int need_at_least;
int have = 0;
+ if (consensus_cache_entry_get_voter_id_digests(ent, voters) != 0) {
+ return 1; // We don't know the voters; assume the client won't mind. */
+ }
+
+ smartlist_t *want_authorities = smartlist_new();
dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0);
need_at_least = smartlist_len(want_authorities)/2+1;
- SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, d) {
- char want_digest[DIGEST_LEN];
- size_t want_len = strlen(d)/2;
- if (want_len > DIGEST_LEN)
- want_len = DIGEST_LEN;
-
- if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2) < 0) {
- log_fn(LOG_PROTOCOL_WARN, LD_DIR,
- "Failed to decode requested authority digest %s.", escaped(d));
- continue;
- };
- SMARTLIST_FOREACH_BEGIN(v->voters, networkstatus_voter_info_t *, vi) {
- if (smartlist_len(vi->sigs) &&
- tor_memeq(vi->identity_digest, want_digest, want_len)) {
+ SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, want_digest) {
+
+ SMARTLIST_FOREACH_BEGIN(voters, const char *, digest) {
+ if (!strcasecmpstart(digest, want_digest)) {
have++;
break;
};
- } SMARTLIST_FOREACH_END(vi);
+ } SMARTLIST_FOREACH_END(digest);
/* early exit, if we already have enough */
if (have >= need_at_least)
break;
- } SMARTLIST_FOREACH_END(d);
+ } SMARTLIST_FOREACH_END(want_digest);
SMARTLIST_FOREACH(want_authorities, char *, d, tor_free(d));
smartlist_free(want_authorities);
+ SMARTLIST_FOREACH(voters, char *, cp, tor_free(cp));
+ smartlist_free(voters);
return (have >= need_at_least);
}
/** Return the compression level we should use for sending a compressed
* response of size <b>n_bytes</b>. */
-STATIC zlib_compression_level_t
+STATIC compression_level_t
choose_compression_level(ssize_t n_bytes)
{
if (! have_been_under_memory_pressure()) {
@@ -2807,21 +3633,87 @@ choose_compression_level(ssize_t n_bytes)
}
}
+/** Information passed to handle a GET request. */
+typedef struct get_handler_args_t {
+ /** Bitmask of compression methods that the client said (or implied) it
+ * supported. */
+ unsigned compression_supported;
+ /** 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_hs_descriptor_v2(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_hs_descriptor_v2 },
+ { "/tor/hs/3/", 1, handle_get_hs_descriptor_v3 },
+ { "/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. */
-STATIC int
-directory_handle_command_get(dir_connection_t *conn, const char *headers,
- const char *req_body, size_t req_body_len)
+ * 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. */
+MOCK_IMPL(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;
+ int zlib_compressed_in_url;
size_t url_len;
+ unsigned compression_methods_supported;
/* We ignore the body of a GET request. */
(void)req_body;
@@ -2852,163 +3744,616 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
url_mem = url;
url_len = strlen(url);
- compressed = url_len > 2 && !strcmp(url+url_len-2, ".z");
- if (compressed) {
+
+ zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z");
+ if (zlib_compressed_in_url) {
url[url_len-2] = '\0';
url_len -= 2;
}
- if (!strcmp(url,"/tor/")) {
- const char *frontpage = get_dirportfrontpage();
+ if ((header = http_get_header(headers, "Accept-Encoding: "))) {
+ compression_methods_supported = parse_accept_encoding_header(header);
+ tor_free(header);
+ } else {
+ compression_methods_supported = (1u << NO_METHOD);
+ }
+ if (zlib_compressed_in_url) {
+ compression_methods_supported |= (1u << ZLIB_METHOD);
+ }
- 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). */
+ /* Remove all methods that we don't both support. */
+ compression_methods_supported &= tor_compress_get_supported_method_bitmask();
- /* [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.compression_supported = compression_methods_supported;
+
+ 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")) {
- /* v3 network status fetch. */
- smartlist_t *dir_fps = smartlist_new();
- const char *request_type = NULL;
- long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
-
- if (1) {
- networkstatus_t *v;
- time_t now = time(NULL);
- const char *want_fps = NULL;
- char *flavor = NULL;
- int flav = FLAV_NS;
- #define CONSENSUS_URL_PREFIX "/tor/status-vote/current/consensus/"
- #define CONSENSUS_FLAVORED_PREFIX "/tor/status-vote/current/consensus-"
- /* figure out the flavor if any, and who we wanted to sign the thing */
- if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
- const char *f, *cp;
- f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
- cp = strchr(f, '/');
- if (cp) {
- want_fps = cp+1;
- flavor = tor_strndup(f, cp-f);
- } else {
- flavor = tor_strdup(f);
- }
- flav = networkstatus_parse_flavor_name(flavor);
- if (flav < 0)
- flav = FLAV_NS;
- } else {
- if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
- want_fps = url+strlen(CONSENSUS_URL_PREFIX);
- }
+ }
- v = networkstatus_get_latest_consensus_by_flavor(flav);
+ /* we didn't recognize the url */
+ write_http_status_line(conn, 404, "Not found");
+ result = 0;
- if (v && want_fps &&
- !client_likes_consensus(v, want_fps)) {
- write_http_status_line(conn, 404, "Consensus not signed by sufficient "
- "number of requested authorities");
- smartlist_free(dir_fps);
- geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS);
- tor_free(flavor);
- goto done;
- }
+ done:
+ tor_free(url_mem);
+ return result;
+}
- {
- char *fp = tor_malloc_zero(DIGEST_LEN);
- if (flavor)
- strlcpy(fp, flavor, DIGEST_LEN);
- 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;
+/** 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;
+}
+
+/** Warn that the cached consensus <b>consensus</b> of type
+ * <b>flavor</b> is too old and will not be served to clients. Rate-limit the
+ * warning to avoid logging an entry on every request.
+ */
+static void
+warn_consensus_is_too_old(const struct consensus_cache_entry_t *consensus,
+ const char *flavor, time_t now)
+{
+#define TOO_OLD_WARNING_INTERVAL (60*60)
+ static ratelim_t warned = RATELIM_INIT(TOO_OLD_WARNING_INTERVAL);
+ char timestamp[ISO_TIME_LEN+1];
+ time_t valid_until;
+ char *dupes;
+
+ if (consensus_cache_entry_get_valid_until(consensus, &valid_until))
+ return;
+
+ if ((dupes = rate_limit_log(&warned, now))) {
+ format_local_iso_time(timestamp, valid_until);
+ log_warn(LD_DIRSERV, "Our %s%sconsensus is too old, so we will not "
+ "serve it to clients. It was valid until %s local time and we "
+ "continued to serve it for up to 24 hours after it expired.%s",
+ flavor ? flavor : "", flavor ? " " : "", timestamp, dupes);
+ tor_free(dupes);
+ }
+}
+
+/**
+ * Parse a single hex-encoded sha3-256 digest from <b>hex</b> into
+ * <b>digest</b>. Return 0 on success. On failure, report that the hash came
+ * from <b>location</b>, report that we are taking <b>action</b> with it, and
+ * return -1.
+ */
+static int
+parse_one_diff_hash(uint8_t *digest, const char *hex, const char *location,
+ const char *action)
+{
+ if (base16_decode((char*)digest, DIGEST256_LEN, hex, strlen(hex)) ==
+ DIGEST256_LEN) {
+ return 0;
+ } else {
+ log_fn(LOG_PROTOCOL_WARN, LD_DIR,
+ "%s contained bogus digest %s; %s.",
+ location, escaped(hex), action);
+ return -1;
+ }
+}
+
+/** If there is an X-Or-Diff-From-Consensus header included in <b>headers</b>,
+ * set <b>digest_out<b> to a new smartlist containing every 256-bit
+ * hex-encoded digest listed in that header and return 0. Otherwise return
+ * -1. */
+static int
+parse_or_diff_from_header(smartlist_t **digests_out, const char *headers)
+{
+ char *hdr = http_get_header(headers, X_OR_DIFF_FROM_CONSENSUS_HEADER);
+ if (hdr == NULL) {
+ return -1;
+ }
+ smartlist_t *hex_digests = smartlist_new();
+ *digests_out = smartlist_new();
+ smartlist_split_string(hex_digests, hdr, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ SMARTLIST_FOREACH_BEGIN(hex_digests, const char *, hex) {
+ uint8_t digest[DIGEST256_LEN];
+ if (!parse_one_diff_hash(digest, hex, "X-Or-Diff-From-Consensus header",
+ "ignoring")) {
+ smartlist_add(*digests_out, tor_memdup(digest, sizeof(digest)));
}
+ } SMARTLIST_FOREACH_END(hex);
+ SMARTLIST_FOREACH(hex_digests, char *, cp, tor_free(cp));
+ smartlist_free(hex_digests);
+ tor_free(hdr);
+ return 0;
+}
- if (!smartlist_len(dir_fps)) { /* we failed to create/cache cp */
- write_http_status_line(conn, 503, "Network status object unavailable");
- smartlist_free(dir_fps);
- geoip_note_ns_response(GEOIP_REJECT_UNAVAILABLE);
- goto done;
+/** Fallback compression method. The fallback compression method is used in
+ * case a client requests a non-compressed document. We only store compressed
+ * documents, so we use this compression method to fetch the document and let
+ * the spooling system do the streaming decompression.
+ */
+#define FALLBACK_COMPRESS_METHOD ZLIB_METHOD
+
+/**
+ * Try to find the best consensus diff possible in order to serve a client
+ * request for a diff from one of the consensuses in <b>digests</b> to the
+ * current consensus of flavor <b>flav</b>. The client supports the
+ * compression methods listed in the <b>compression_methods</b> bitfield:
+ * place the method chosen (if any) into <b>compression_used_out</b>.
+ */
+static struct consensus_cache_entry_t *
+find_best_diff(const smartlist_t *digests, int flav,
+ unsigned compression_methods,
+ compress_method_t *compression_used_out)
+{
+ struct consensus_cache_entry_t *result = NULL;
+
+ SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) {
+ unsigned u;
+ for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) {
+ compress_method_t method = srv_meth_pref_precompressed[u];
+ if (0 == (compression_methods & (1u<<method)))
+ continue; // client doesn't like this one, or we don't have it.
+ if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256,
+ diff_from, DIGEST256_LEN,
+ method) == CONSDIFF_AVAILABLE) {
+ tor_assert_nonfatal(result);
+ *compression_used_out = method;
+ return result;
+ }
+ }
+ } SMARTLIST_FOREACH_END(diff_from);
+
+ SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, diff_from) {
+ if (consdiffmgr_find_diff_from(&result, flav, DIGEST_SHA3_256, diff_from,
+ DIGEST256_LEN, FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) {
+ tor_assert_nonfatal(result);
+ *compression_used_out = FALLBACK_COMPRESS_METHOD;
+ return result;
}
+ } SMARTLIST_FOREACH_END(diff_from);
- if (!dirserv_remove_old_statuses(dir_fps, if_modified_since)) {
- write_http_status_line(conn, 404, "Not found");
- SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp));
- smartlist_free(dir_fps);
- geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
- goto done;
- } else if (!smartlist_len(dir_fps)) {
- write_http_status_line(conn, 304, "Not modified");
- SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp));
- smartlist_free(dir_fps);
- geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED);
- goto done;
+ return NULL;
+}
+
+/** Lookup the cached consensus document by the flavor found in <b>flav</b>.
+ * The prefered set of compression methods should be listed in the
+ * <b>compression_methods</b> bitfield. The compression method chosen (if any)
+ * is stored in <b>compression_used_out</b>. */
+static struct consensus_cache_entry_t *
+find_best_consensus(int flav,
+ unsigned compression_methods,
+ compress_method_t *compression_used_out)
+{
+ struct consensus_cache_entry_t *result = NULL;
+ unsigned u;
+
+ for (u = 0; u < ARRAY_LENGTH(srv_meth_pref_precompressed); ++u) {
+ compress_method_t method = srv_meth_pref_precompressed[u];
+
+ if (0 == (compression_methods & (1u<<method)))
+ continue;
+
+ if (consdiffmgr_find_consensus(&result, flav,
+ method) == CONSDIFF_AVAILABLE) {
+ tor_assert_nonfatal(result);
+ *compression_used_out = method;
+ return result;
}
+ }
- 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 "
- "writing too many bytes lately. Sending 503 Dir busy.");
- write_http_status_line(conn, 503, "Directory busy, try again later");
- SMARTLIST_FOREACH(dir_fps, char *, fp, tor_free(fp));
- smartlist_free(dir_fps);
+ if (consdiffmgr_find_consensus(&result, flav,
+ FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) {
+ tor_assert_nonfatal(result);
+ *compression_used_out = FALLBACK_COMPRESS_METHOD;
+ return result;
+ }
- geoip_note_ns_response(GEOIP_REJECT_BUSY);
- goto done;
+ return NULL;
+}
+
+/** Try to find the best supported compression method possible from a given
+ * <b>compression_methods</b>. Return NO_METHOD if no mutually supported
+ * compression method could be found. */
+static compress_method_t
+find_best_compression_method(unsigned compression_methods, int stream)
+{
+ unsigned u;
+ compress_method_t *methods;
+ size_t length;
+
+ if (stream) {
+ methods = srv_meth_pref_streaming_compression;
+ length = ARRAY_LENGTH(srv_meth_pref_streaming_compression);
+ } else {
+ methods = srv_meth_pref_precompressed;
+ length = ARRAY_LENGTH(srv_meth_pref_precompressed);
+ }
+
+ for (u = 0; u < length; ++u) {
+ compress_method_t method = methods[u];
+ if (compression_methods & (1u<<method))
+ return method;
+ }
+
+ return NO_METHOD;
+}
+
+/** Check if any of the digests in <b>digests</b> matches the latest consensus
+ * flavor (given in <b>flavor</b>) that we have available. */
+static int
+digest_list_contains_best_consensus(consensus_flavor_t flavor,
+ const smartlist_t *digests)
+{
+ const networkstatus_t *ns = NULL;
+
+ if (digests == NULL)
+ return 0;
+
+ ns = networkstatus_get_latest_consensus_by_flavor(flavor);
+
+ if (ns == NULL)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(digests, const uint8_t *, digest) {
+ if (tor_memeq(ns->digest_sha3_as_signed, digest, DIGEST256_LEN))
+ return 1;
+ } SMARTLIST_FOREACH_END(digest);
+
+ return 0;
+}
+
+/** Check if the given compression method is allowed for a connection that is
+ * supposed to be anonymous. Returns 1 if the compression method is allowed,
+ * otherwise 0. */
+STATIC int
+allowed_anonymous_connection_compression_method(compress_method_t method)
+{
+ unsigned u;
+
+ for (u = 0; u < ARRAY_LENGTH(client_meth_allowed_anonymous_compression);
+ ++u) {
+ compress_method_t allowed_method =
+ client_meth_allowed_anonymous_compression[u];
+
+ if (! tor_compress_supports_method(allowed_method))
+ continue;
+
+ if (method == allowed_method)
+ return 1;
+ }
+
+ return 0;
+}
+
+/** Log a warning when a remote server has sent us a document using a
+ * compression method that is not allowed for anonymous directory requests. */
+STATIC void
+warn_disallowed_anonymous_compression_method(compress_method_t method)
+{
+ log_fn(LOG_PROTOCOL_WARN, LD_HTTP,
+ "Received a %s HTTP response, which is not "
+ "allowed for anonymous directory requests.",
+ compression_method_get_human_name(method));
+}
+
+/** Encodes the results of parsing a consensus request to figure out what
+ * consensus, and possibly what diffs, the user asked for. */
+typedef struct {
+ /** name of the flavor to retrieve. */
+ char *flavor;
+ /** flavor to retrive, as enum. */
+ consensus_flavor_t flav;
+ /** plus-separated list of authority fingerprints; see
+ * client_likes_consensus(). Aliases the URL in the request passed to
+ * parse_consensus_request(). */
+ const char *want_fps;
+ /** Optionally, a smartlist of sha3 digests-as-signed of the consensuses
+ * to return a diff from. */
+ smartlist_t *diff_from_digests;
+ /** If true, never send a full consensus. If there is no diff, send
+ * a 404 instead. */
+ int diff_only;
+} parsed_consensus_request_t;
+
+/** Remove all data held in <b>req</b>. Do not free <b>req</b> itself, since
+ * it is stack-allocated. */
+static void
+parsed_consensus_request_clear(parsed_consensus_request_t *req)
+{
+ if (!req)
+ return;
+ tor_free(req->flavor);
+ if (req->diff_from_digests) {
+ SMARTLIST_FOREACH(req->diff_from_digests, uint8_t *, d, tor_free(d));
+ smartlist_free(req->diff_from_digests);
+ }
+ memset(req, 0, sizeof(parsed_consensus_request_t));
+}
+
+/**
+ * Parse the URL and relevant headers of <b>args</b> for a current-consensus
+ * request to learn what flavor of consensus we want, what keys it must be
+ * signed with, and what diffs we would accept (or demand) instead. Return 0
+ * on success and -1 on failure.
+ */
+static int
+parse_consensus_request(parsed_consensus_request_t *out,
+ const get_handler_args_t *args)
+{
+ const char *url = args->url;
+ memset(out, 0, sizeof(parsed_consensus_request_t));
+ out->flav = FLAV_NS;
+
+ const char CONSENSUS_URL_PREFIX[] = "/tor/status-vote/current/consensus/";
+ const char CONSENSUS_FLAVORED_PREFIX[] =
+ "/tor/status-vote/current/consensus-";
+
+ /* figure out the flavor if any, and who we wanted to sign the thing */
+ const char *after_flavor = NULL;
+
+ if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
+ const char *f, *cp;
+ f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
+ cp = strchr(f, '/');
+ if (cp) {
+ after_flavor = cp+1;
+ out->flavor = tor_strndup(f, cp-f);
+ } else {
+ out->flavor = tor_strdup(f);
+ }
+ int flav = networkstatus_parse_flavor_name(out->flavor);
+ if (flav < 0)
+ flav = FLAV_NS;
+ out->flav = flav;
+ } else {
+ if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
+ after_flavor = url+strlen(CONSENSUS_URL_PREFIX);
+ }
+
+ /* see whether we've been asked explicitly for a diff from an older
+ * consensus. (The user might also have said that a diff would be okay,
+ * via X-Or-Diff-From-Consensus */
+ const char DIFF_COMPONENT[] = "diff/";
+ char *diff_hash_in_url = NULL;
+ if (after_flavor && !strcmpstart(after_flavor, DIFF_COMPONENT)) {
+ after_flavor += strlen(DIFF_COMPONENT);
+ const char *cp = strchr(after_flavor, '/');
+ if (cp) {
+ diff_hash_in_url = tor_strndup(after_flavor, cp-after_flavor);
+ out->want_fps = cp+1;
+ } else {
+ diff_hash_in_url = tor_strdup(after_flavor);
+ out->want_fps = NULL;
}
+ } else {
+ out->want_fps = after_flavor;
+ }
+
+ if (diff_hash_in_url) {
+ uint8_t diff_from[DIGEST256_LEN];
+ out->diff_from_digests = smartlist_new();
+ out->diff_only = 1;
+ int ok = !parse_one_diff_hash(diff_from, diff_hash_in_url, "URL",
+ "rejecting");
+ tor_free(diff_hash_in_url);
+ if (ok) {
+ smartlist_add(out->diff_from_digests,
+ tor_memdup(diff_from, DIGEST256_LEN));
+ } else {
+ return -1;
+ }
+ } else {
+ parse_or_diff_from_header(&out->diff_from_digests, args->headers);
+ }
- if (1) {
- tor_addr_t addr;
- if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
- geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS,
- &addr, NULL,
- time(NULL));
- geoip_note_ns_response(GEOIP_SUCCESS);
- /* Note that a request for a network status has started, so that we
- * can measure the download time later on. */
- if (conn->dirreq_id)
- geoip_start_dirreq(conn->dirreq_id, dlen, DIRREQ_TUNNELED);
- else
- geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen,
- DIRREQ_DIRECT);
- }
+ 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 compress_method_t compress_method =
+ find_best_compression_method(args->compression_supported, 0);
+ const time_t if_modified_since = args->if_modified_since;
+ int clear_spool = 0;
+
+ /* v3 network status fetch. */
+ long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
+
+ time_t now = time(NULL);
+ parsed_consensus_request_t req;
+
+ if (parse_consensus_request(&req, args) < 0) {
+ write_http_status_line(conn, 404, "Couldn't parse request");
+ goto done;
+ }
+
+ if (digest_list_contains_best_consensus(req.flav,
+ req.diff_from_digests)) {
+ write_http_status_line(conn, 304, "Not modified");
+ geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED);
+ goto done;
+ }
+
+ struct consensus_cache_entry_t *cached_consensus = NULL;
+
+ compress_method_t compression_used = NO_METHOD;
+ if (req.diff_from_digests) {
+ cached_consensus = find_best_diff(req.diff_from_digests, req.flav,
+ args->compression_supported,
+ &compression_used);
+ }
+
+ if (req.diff_only && !cached_consensus) {
+ write_http_status_line(conn, 404, "No such diff available");
+ // XXXX warn_consensus_is_too_old(v, req.flavor, now);
+ geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
+ goto done;
+ }
+
+ if (! cached_consensus) {
+ cached_consensus = find_best_consensus(req.flav,
+ args->compression_supported,
+ &compression_used);
+ }
+
+ time_t fresh_until, valid_until;
+ int have_fresh_until = 0, have_valid_until = 0;
+ if (cached_consensus) {
+ have_fresh_until =
+ !consensus_cache_entry_get_fresh_until(cached_consensus, &fresh_until);
+ have_valid_until =
+ !consensus_cache_entry_get_valid_until(cached_consensus, &valid_until);
+ }
+
+ if (cached_consensus && have_valid_until &&
+ !networkstatus_valid_until_is_reasonably_live(valid_until, now)) {
+ write_http_status_line(conn, 404, "Consensus is too old");
+ warn_consensus_is_too_old(cached_consensus, req.flavor, now);
+ geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
+ goto done;
+ }
+
+ if (cached_consensus && req.want_fps &&
+ !client_likes_consensus(cached_consensus, req.want_fps)) {
+ write_http_status_line(conn, 404, "Consensus not signed by sufficient "
+ "number of requested authorities");
+ geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS);
+ goto done;
+ }
+
+ conn->spool = smartlist_new();
+ clear_spool = 1;
+ {
+ spooled_resource_t *spooled;
+ if (cached_consensus) {
+ spooled = spooled_resource_new_from_cache_entry(cached_consensus);
+ smartlist_add(conn->spool, spooled);
}
+ }
+
+ lifetime = (have_fresh_until && fresh_until > now) ? fresh_until - now : 0;
+
+ size_t size_guess = 0;
+ int n_expired = 0;
+ dirserv_spool_remove_missing_and_guess_size(conn, if_modified_since,
+ compress_method != NO_METHOD,
+ &size_guess,
+ &n_expired);
- // 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;
- if (! compressed)
- conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION);
-
- /* Prime the connection with some data. */
- conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS;
- connection_dirserv_flushed_some(conn);
+ if (!smartlist_len(conn->spool) && !n_expired) {
+ write_http_status_line(conn, 404, "Not found");
+ geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
goto done;
+ } else if (!smartlist_len(conn->spool)) {
+ write_http_status_line(conn, 304, "Not modified");
+ geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED);
+ goto done;
+ }
+
+ if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) {
+ log_debug(LD_DIRSERV,
+ "Client asked for network status lists, but we've been "
+ "writing too many bytes lately. Sending 503 Dir busy.");
+ write_http_status_line(conn, 503, "Directory busy, try again later");
+ geoip_note_ns_response(GEOIP_REJECT_BUSY);
+ goto done;
+ }
+
+ tor_addr_t addr;
+ if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
+ geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS,
+ &addr, NULL,
+ time(NULL));
+ geoip_note_ns_response(GEOIP_SUCCESS);
+ /* Note that a request for a network status has started, so that we
+ * can measure the download time later on. */
+ if (conn->dirreq_id)
+ geoip_start_dirreq(conn->dirreq_id, size_guess, DIRREQ_TUNNELED);
+ else
+ geoip_start_dirreq(TO_CONN(conn)->global_identifier, size_guess,
+ DIRREQ_DIRECT);
+ }
+
+ /* Use this header to tell caches that the response depends on the
+ * X-Or-Diff-From-Consensus header (or lack thereof). */
+ const char vary_header[] = "Vary: X-Or-Diff-From-Consensus\r\n";
+
+ clear_spool = 0;
+
+ // The compress_method might have been NO_METHOD, but we store the data
+ // compressed. Decompress them using `compression_used`. See fallback code in
+ // find_best_consensus() and find_best_diff().
+ write_http_response_headers(conn, -1,
+ compress_method == NO_METHOD ?
+ NO_METHOD : compression_used,
+ vary_header,
+ smartlist_len(conn->spool) == 1 ? lifetime : 0);
+
+ if (compress_method == NO_METHOD && smartlist_len(conn->spool))
+ conn->compress_state = tor_compress_new(0, compression_used,
+ HIGH_COMPRESSION);
+
+ /* Prime the connection with some data. */
+ const int initial_flush_result = connection_dirserv_flushed_some(conn);
+ tor_assert_nonfatal(initial_flush_result == 0);
+ goto done;
+
+ done:
+ parsed_consensus_request_clear(&req);
+ if (clear_spool) {
+ dir_conn_clear_spool(conn);
}
+ return 0;
+}
- 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. */
+/** 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;
+ {
int current;
ssize_t body_len = 0;
ssize_t estimated_len = 0;
+ /* This smartlist holds strings that we can compress on the fly. */
smartlist_t *items = smartlist_new();
+ /* This smartlist holds cached_dir_t objects that have a precompressed
+ * deflated version. */
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, '/');
@@ -3057,12 +4402,33 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
write_http_status_line(conn, 404, "Not found");
goto vote_done;
}
+
+ /* We're sending items from at most one kind of source */
+ tor_assert_nonfatal(smartlist_len(items) == 0 ||
+ smartlist_len(dir_items) == 0);
+
+ int streaming;
+ unsigned mask;
+ if (smartlist_len(items)) {
+ /* We're taking strings and compressing them on the fly. */
+ streaming = 1;
+ mask = ~0u;
+ } else {
+ /* We're taking cached_dir_t objects. We only have them uncompressed
+ * or deflated. */
+ streaming = 0;
+ mask = (1u<<NO_METHOD) | (1u<<ZLIB_METHOD);
+ }
+ const compress_method_t compress_method = find_best_compression_method(
+ args->compression_supported&mask, streaming);
+
SMARTLIST_FOREACH(dir_items, cached_dir_t *, d,
- body_len += compressed ? d->dir_z_len : d->dir_len);
+ body_len += compress_method != NO_METHOD ?
+ d->dir_compressed_len : d->dir_len);
estimated_len += body_len;
SMARTLIST_FOREACH(items, const char *, item, {
size_t ln = strlen(item);
- if (compressed) {
+ if (compress_method != NO_METHOD) {
estimated_len += ln/2;
} else {
body_len += ln; estimated_len += ln;
@@ -3073,24 +4439,27 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
write_http_status_line(conn, 503, "Directory busy, try again later");
goto vote_done;
}
- write_http_response_header(conn, body_len ? body_len : -1, compressed,
+ write_http_response_header(conn, body_len ? body_len : -1,
+ compress_method,
lifetime);
if (smartlist_len(items)) {
- if (compressed) {
- conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD,
- choose_compression_level(estimated_len));
+ if (compress_method != NO_METHOD) {
+ conn->compress_state = tor_compress_new(1, compress_method,
+ choose_compression_level(estimated_len));
SMARTLIST_FOREACH(items, const char *, c,
- connection_write_to_buf_zlib(c, strlen(c), conn, 0));
- connection_write_to_buf_zlib("", 0, conn, 1);
+ connection_write_to_buf_compress(c, strlen(c), conn, 0));
+ connection_write_to_buf_compress("", 0, conn, 1);
} else {
SMARTLIST_FOREACH(items, const char *, c,
connection_write_to_buf(c, strlen(c), TO_CONN(conn)));
}
} else {
SMARTLIST_FOREACH(dir_items, cached_dir_t *, d,
- connection_write_to_buf(compressed ? d->dir_z : d->dir,
- compressed ? d->dir_z_len : d->dir_len,
+ connection_write_to_buf(compress_method != NO_METHOD ?
+ d->dir_compressed : d->dir,
+ compress_method != NO_METHOD ?
+ d->dir_compressed_len : d->dir_len,
TO_CONN(conn)));
}
vote_done:
@@ -3098,114 +4467,168 @@ 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/")) {
- smartlist_t *fps = smartlist_new();
+/** 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 compress_method_t compress_method =
+ find_best_compression_method(args->compression_supported, 1);
+ int clear_spool = 1;
+ {
+ conn->spool = smartlist_new();
- dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"),
- fps, NULL,
+ dir_split_resource_into_spoolable(url+strlen("/tor/micro/d/"),
+ DIR_SPOOL_MICRODESC,
+ conn->spool, NULL,
DSR_DIGEST256|DSR_BASE64|DSR_SORT_UNIQ);
- if (!dirserv_have_any_microdesc(fps)) {
+ size_t size_guess = 0;
+ dirserv_spool_remove_missing_and_guess_size(conn, 0,
+ compress_method != NO_METHOD,
+ &size_guess, NULL);
+ if (smartlist_len(conn->spool) == 0) {
write_http_status_line(conn, 404, "Not found");
- SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp));
- smartlist_free(fps);
goto done;
}
- dlen = dirserv_estimate_microdesc_size(fps, compressed);
- if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
+ if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) {
log_info(LD_DIRSERV,
"Client asked for server descriptors, but we've been "
"writing too many bytes lately. Sending 503 Dir busy.");
write_http_status_line(conn, 503, "Directory busy, try again later");
- SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp));
- smartlist_free(fps);
goto done;
}
- write_http_response_header(conn, -1, compressed, MICRODESC_CACHE_LIFETIME);
- conn->dir_spool_src = DIR_SPOOL_MICRODESC;
- conn->fingerprint_stack = fps;
+ clear_spool = 0;
+ write_http_response_header(conn, -1,
+ compress_method,
+ MICRODESC_CACHE_LIFETIME);
- if (compressed)
- conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD,
- choose_compression_level(dlen));
+ if (compress_method != NO_METHOD)
+ conn->compress_state = tor_compress_new(1, compress_method,
+ choose_compression_level(size_guess));
- connection_dirserv_flushed_some(conn);
+ const int initial_flush_result = connection_dirserv_flushed_some(conn);
+ tor_assert_nonfatal(initial_flush_result == 0);
goto done;
}
+ done:
+ if (clear_spool) {
+ dir_conn_clear_spool(conn);
+ }
+ 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 compress_method_t compress_method =
+ find_best_compression_method(args->compression_supported, 1);
+ const or_options_t *options = get_options();
+ int clear_spool = 1;
if (!strcmpstart(url,"/tor/server/") ||
(!options->BridgeAuthoritativeDir &&
!options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
int res;
- const char *msg;
- const char *request_type = NULL;
+ const char *msg = NULL;
int cache_lifetime = 0;
int is_extra = !strcmpstart(url,"/tor/extra/");
url += is_extra ? strlen("/tor/extra/") : strlen("/tor/server/");
- conn->fingerprint_stack = smartlist_new();
- res = dirserv_get_routerdesc_fingerprints(conn->fingerprint_stack, url,
- &msg,
- !connection_dir_is_encrypted(conn),
- 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 =
+ dir_spool_source_t source;
+ time_t publish_cutoff = 0;
+ if (!strcmpstart(url, "d/")) {
+ source =
is_extra ? DIR_SPOOL_EXTRA_BY_DIGEST : DIR_SPOOL_SERVER_BY_DIGEST;
- else
- conn->dir_spool_src =
+ } else {
+ source =
is_extra ? DIR_SPOOL_EXTRA_BY_FP : DIR_SPOOL_SERVER_BY_FP;
+ /* We only want to apply a publish cutoff when we're requesting
+ * resources by fingerprint. */
+ publish_cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH;
+ }
- if (!dirserv_have_any_serverdesc(conn->fingerprint_stack,
- conn->dir_spool_src)) {
- res = -1;
- msg = "Not found";
+ conn->spool = smartlist_new();
+ res = dirserv_get_routerdesc_spool(conn->spool, url,
+ source,
+ connection_dir_is_encrypted(conn),
+ &msg);
+
+ if (!strcmpstart(url, "all")) {
+ cache_lifetime = FULL_DIR_CACHE_LIFETIME;
+ } else if (smartlist_len(conn->spool) == 1) {
+ cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME;
}
- if (res < 0)
+ size_t size_guess = 0;
+ int n_expired = 0;
+ dirserv_spool_remove_missing_and_guess_size(conn, publish_cutoff,
+ compress_method != NO_METHOD,
+ &size_guess, &n_expired);
+
+ /* If we are the bridge authority and the descriptor is a bridge
+ * descriptor, remember that we served this descriptor for desc stats. */
+ /* XXXX it's a bit of a kludge to have this here. */
+ if (get_options()->BridgeAuthoritativeDir &&
+ source == DIR_SPOOL_SERVER_BY_FP) {
+ SMARTLIST_FOREACH_BEGIN(conn->spool, spooled_resource_t *, spooled) {
+ const routerinfo_t *router =
+ router_get_by_id_digest((const char *)spooled->digest);
+ /* router can be NULL here when the bridge auth is asked for its own
+ * descriptor. */
+ if (router && router->purpose == ROUTER_PURPOSE_BRIDGE)
+ rep_hist_note_desc_served(router->cache_info.identity_digest);
+ } SMARTLIST_FOREACH_END(spooled);
+ }
+
+ if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) {
+ if (msg == NULL)
+ msg = "Not found";
write_http_status_line(conn, 404, msg);
- else {
- dlen = dirserv_estimate_data_size(conn->fingerprint_stack,
- 1, compressed);
- if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
+ } else {
+ if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) {
log_info(LD_DIRSERV,
"Client asked for server descriptors, but we've been "
"writing too many bytes lately. Sending 503 Dir busy.");
write_http_status_line(conn, 503, "Directory busy, try again later");
- conn->dir_spool_src = DIR_SPOOL_NONE;
+ dir_conn_clear_spool(conn);
goto done;
}
- write_http_response_header(conn, -1, compressed, cache_lifetime);
- if (compressed)
- conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD,
- choose_compression_level(dlen));
+ write_http_response_header(conn, -1, compress_method, cache_lifetime);
+ if (compress_method != NO_METHOD)
+ conn->compress_state = tor_compress_new(1, compress_method,
+ choose_compression_level(size_guess));
+ clear_spool = 0;
/* Prime the connection with some data. */
- connection_dirserv_flushed_some(conn);
+ int initial_flush_result = connection_dirserv_flushed_some(conn);
+ tor_assert_nonfatal(initial_flush_result == 0);
}
goto done;
}
+ done:
+ if (clear_spool)
+ dir_conn_clear_spool(conn);
+ 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 compress_method_t compress_method =
+ find_best_compression_method(args->compression_supported, 1);
+ 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")) {
@@ -3266,20 +4689,26 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
SMARTLIST_FOREACH(certs, authority_cert_t *, c,
len += c->cache_info.signed_descriptor_len);
- if (global_write_bucket_low(TO_CONN(conn), compressed?len/2:len, 2)) {
+ if (global_write_bucket_low(TO_CONN(conn),
+ compress_method != NO_METHOD ? len/2 : len,
+ 2)) {
write_http_status_line(conn, 503, "Directory busy, try again later");
goto keys_done;
}
- write_http_response_header(conn, compressed?-1:len, compressed, 60*60);
- if (compressed) {
- conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD,
- choose_compression_level(len));
+ write_http_response_header(conn,
+ compress_method != NO_METHOD ? -1 : len,
+ compress_method,
+ 60*60);
+ if (compress_method != NO_METHOD) {
+ conn->compress_state = tor_compress_new(1, compress_method,
+ choose_compression_level(len));
SMARTLIST_FOREACH(certs, authority_cert_t *, c,
- connection_write_to_buf_zlib(c->cache_info.signed_descriptor_body,
- c->cache_info.signed_descriptor_len,
- conn, 0));
- connection_write_to_buf_zlib("", 0, conn, 1);
+ connection_write_to_buf_compress(
+ c->cache_info.signed_descriptor_body,
+ c->cache_info.signed_descriptor_len,
+ conn, 0));
+ connection_write_to_buf_compress("", 0, conn, 1);
} else {
SMARTLIST_FOREACH(certs, authority_cert_t *, c,
connection_write_to_buf(c->cache_info.signed_descriptor_body,
@@ -3290,9 +4719,18 @@ 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_hs_descriptor_v2(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/");
@@ -3301,7 +4739,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
safe_str(escaped(query)));
switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) {
case 1: /* valid */
- write_http_response_header(conn, strlen(descp), 0, 0);
+ write_http_response_header(conn, strlen(descp), NO_METHOD, 0);
connection_write_to_buf(descp, strlen(descp), TO_CONN(conn));
break;
case 0: /* well-formed but not present */
@@ -3315,16 +4753,67 @@ 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/hs/3/<z>. Only for version 3.
+ */
+STATIC int
+handle_get_hs_descriptor_v3(dir_connection_t *conn,
+ const get_handler_args_t *args)
+{
+ int retval;
+ const char *desc_str = NULL;
+ const char *pubkey_str = NULL;
+ const char *url = args->url;
+
+ /* Reject unencrypted dir connections */
+ if (!connection_dir_is_encrypted(conn)) {
+ write_http_status_line(conn, 404, "Not found");
+ goto done;
}
+ /* After the path prefix follows the base64 encoded blinded pubkey which we
+ * use to get the descriptor from the cache. Skip the prefix and get the
+ * pubkey. */
+ tor_assert(!strcmpstart(url, "/tor/hs/3/"));
+ pubkey_str = url + strlen("/tor/hs/3/");
+ retval = hs_cache_lookup_as_dir(HS_VERSION_THREE,
+ pubkey_str, &desc_str);
+ if (retval <= 0 || desc_str == NULL) {
+ write_http_status_line(conn, 404, "Not found");
+ goto done;
+ }
+
+ /* Found requested descriptor! Pass it to this nice client. */
+ write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0);
+ connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn));
+
+ 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);
@@ -3340,76 +4829,112 @@ 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);
- write_http_response_header(conn, dlen, 0, 0);
+ size_t dlen = strlen(status);
+ write_http_response_header(conn, dlen, NO_METHOD, 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);
+ write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME);
connection_write_to_buf(robots, len, TO_CONN(conn));
- goto done;
}
+ return 0;
+}
-#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;
+/* Given the <b>url</b> from a POST request, try to extract the version number
+ * using the provided <b>prefix</b>. The version should be after the prefix and
+ * ending with the seperator "/". For instance:
+ * /tor/hs/3/publish
+ *
+ * On success, <b>end_pos</b> points to the position right after the version
+ * was found. On error, it is set to NULL.
+ *
+ * Return version on success else negative value. */
+STATIC int
+parse_hs_version_from_post(const char *url, const char *prefix,
+ const char **end_pos)
+{
+ int ok;
+ unsigned long version;
+ const char *start;
+ char *end = NULL;
+
+ tor_assert(url);
+ tor_assert(prefix);
+ tor_assert(end_pos);
+
+ /* Check if the prefix does start the url. */
+ if (strcmpstart(url, prefix)) {
+ goto err;
+ }
+ /* Move pointer to the end of the prefix string. */
+ start = url + strlen(prefix);
+ /* Try this to be the HS version and if we are still at the separator, next
+ * will be move to the right value. */
+ version = tor_parse_long(start, 10, 0, INT_MAX, &ok, &end);
+ if (!ok) {
+ goto err;
+ }
+
+ *end_pos = end;
+ return (int) version;
+ err:
+ *end_pos = NULL;
+ return -1;
+}
+
+/* Handle the POST request for a hidden service descripror. The request is in
+ * <b>url</b>, the body of the request is in <b>body</b>. Return 200 on success
+ * else return 400 indicating a bad request. */
+STATIC int
+handle_post_hs_descriptor(const char *url, const char *body)
+{
+ int version;
+ const char *end_pos;
+
+ tor_assert(url);
+ tor_assert(body);
+
+ version = parse_hs_version_from_post(url, "/tor/hs/", &end_pos);
+ if (version < 0) {
+ goto err;
}
-#endif
- /* we didn't recognize the url */
- write_http_status_line(conn, 404, "Not found");
+ /* We have a valid version number, now make sure it's a publish request. Use
+ * the end position just after the version and check for the command. */
+ if (strcmpstart(end_pos, "/publish")) {
+ goto err;
+ }
- done:
- tor_free(url_mem);
- return 0;
+ switch (version) {
+ case HS_VERSION_THREE:
+ if (hs_cache_store_as_dir(body) < 0) {
+ goto err;
+ }
+ log_info(LD_REND, "Publish request for HS descriptor handled "
+ "successfully.");
+ break;
+ default:
+ /* Unsupported version, return a bad request. */
+ goto err;
+ }
+
+ return 200;
+ err:
+ /* Bad request. */
+ return 400;
}
/** Helper function: called when a dirserver gets a complete HTTP POST
@@ -3417,9 +4942,9 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers,
* service descriptor. On finding one, process it and write a
* response into conn-\>outbuf. If the request is unrecognized, send a
* 400. Always return 0. */
-static int
-directory_handle_command_post(dir_connection_t *conn, const char *headers,
- const char *body, size_t body_len)
+MOCK_IMPL(STATIC int,
+directory_handle_command_post,(dir_connection_t *conn, const char *headers,
+ const char *body, size_t body_len))
{
char *url = NULL;
const or_options_t *options = get_options();
@@ -3445,7 +4970,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
if (connection_dir_is_encrypted(conn) &&
!strcmpstart(url,"/tor/rendezvous2/publish")) {
if (rend_cache_store_v2_desc_as_dir(body) < 0) {
- log_warn(LD_REND, "Rejected v2 rend descriptor (length %d) from %s.",
+ log_warn(LD_REND, "Rejected v2 rend descriptor (body size %d) from %s.",
(int)body_len, conn->base_.address);
write_http_status_line(conn, 400,
"Invalid v2 service descriptor rejected");
@@ -3456,6 +4981,21 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
goto done;
}
+ /* Handle HS descriptor publish request. */
+ /* XXX: This should be disabled with a consensus param until we want to
+ * the prop224 be deployed and thus use. */
+ if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) {
+ const char *msg = "HS descriptor stored successfully.";
+
+ /* We most probably have a publish request for an HS descriptor. */
+ int code = handle_post_hs_descriptor(url, body);
+ if (code != 200) {
+ msg = "Invalid HS descriptor. Rejected.";
+ }
+ write_http_status_line(conn, code, msg);
+ goto done;
+ }
+
if (!authdir_mode(options)) {
/* we just provide cached directories; we don't want to
* receive anything. */
@@ -3464,7 +5004,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
goto done;
}
- if (authdir_mode_handles_descs(options, -1) &&
+ if (authdir_mode(options) &&
!strcmp(url,"/tor/")) { /* server descriptor post */
const char *msg = "[None]";
uint8_t purpose = authdir_mode_bridge(options) ?
@@ -3473,14 +5013,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
conn->base_.address, &msg);
tor_assert(msg);
- if (r == ROUTER_ADDED_NOTIFY_GENERATOR) {
- /* Accepted with a message. */
- log_info(LD_DIRSERV,
- "Problematic router descriptor or extra-info from %s "
- "(\"%s\").",
- conn->base_.address, msg);
- write_http_status_line(conn, 400, msg);
- } else if (r == ROUTER_ADDED_SUCCESSFULLY) {
+ if (r == ROUTER_ADDED_SUCCESSFULLY) {
write_http_status_line(conn, 200, msg);
} else if (WRA_WAS_OUTDATED(r)) {
write_http_response_header_impl(conn, -1, NULL, NULL,
@@ -3536,7 +5069,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
* from the inbuf, try to process it; otherwise, leave it on the
* buffer. Return a 0 on success, or -1 on error.
*/
-static int
+STATIC int
directory_handle_command(dir_connection_t *conn)
{
char *headers=NULL, *body=NULL;
@@ -3607,17 +5140,9 @@ connection_dir_finished_flushing(dir_connection_t *conn)
conn->base_.state = DIR_CONN_STATE_CLIENT_READING;
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
+ if (conn->spool) {
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));
@@ -3632,226 +5157,37 @@ connection_dir_finished_flushing(dir_connection_t *conn)
return 0;
}
-/* A helper function for connection_dir_close_consensus_conn_if_extra()
- * and connection_dir_close_extra_consensus_conns() that returns 0 if
- * we can't have, or don't want to close, excess consensus connections. */
-STATIC int
-connection_dir_would_close_consensus_conn_helper(void)
-{
- const or_options_t *options = get_options();
-
- /* we're only interested in closing excess connections if we could
- * have created any in the first place */
- if (!networkstatus_consensus_can_use_multiple_directories(options)) {
- return 0;
- }
-
- /* We want to close excess connections downloading a consensus.
- * If there aren't any excess, we don't have anything to close. */
- if (!networkstatus_consensus_has_excess_connections()) {
- return 0;
- }
-
- /* If we have excess connections, but none of them are downloading a
- * consensus, and we are still bootstrapping (that is, we have no usable
- * consensus), we don't want to close any until one starts downloading. */
- if (!networkstatus_consensus_is_downloading_usable_flavor()
- && networkstatus_consensus_is_bootstrapping(time(NULL))) {
- return 0;
- }
-
- /* If we have just stopped bootstrapping (that is, just parsed a consensus),
- * we might still have some excess connections hanging around. So we still
- * have to check if we want to close any, even if we've stopped
- * bootstrapping. */
- return 1;
-}
-
-/* Check if we would close excess consensus connections. If we would, any
- * new consensus connection would become excess immediately, so return 1.
- * Otherwise, return 0. */
-int
-connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose)
-{
- const or_options_t *options = get_options();
-
- /* We're not interested in connections that aren't fetching a consensus. */
- if (purpose != DIR_PURPOSE_FETCH_CONSENSUS) {
- return 0;
- }
-
- /* we're only interested in avoiding excess connections if we could
- * have created any in the first place */
- if (!networkstatus_consensus_can_use_multiple_directories(options)) {
- return 0;
- }
-
- /* If there are connections downloading a consensus, and we are still
- * bootstrapping (that is, we have no usable consensus), we can be sure that
- * any further connections would be excess. */
- if (networkstatus_consensus_is_downloading_usable_flavor()
- && networkstatus_consensus_is_bootstrapping(time(NULL))) {
- return 1;
- }
-
- return 0;
-}
-
-/* Check if we have more than one consensus download connection attempt, and
- * close conn:
- * - if we don't have a consensus, and we're downloading a consensus, and conn
- * is not downloading a consensus yet;
- * - if we do have a consensus, and there's more than one consensus connection.
+/* We just got a new consensus! If there are other in-progress requests
+ * for this consensus flavor (for example because we launched several in
+ * parallel), cancel them.
*
- * Post-bootstrap consensus connection attempts are initiated one at a time.
- * So this function won't close any consensus connection attempts that
- * are initiated after bootstrap.
- */
-int
-connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn)
-{
- tor_assert(conn);
- tor_assert(conn->base_.type == CONN_TYPE_DIR);
-
- /* We're not interested in connections that aren't fetching a consensus. */
- if (conn->base_.purpose != DIR_PURPOSE_FETCH_CONSENSUS) {
- return 0;
- }
-
- /* The connection has already been closed */
- if (conn->base_.marked_for_close) {
- return 0;
- }
-
- /* Only close this connection if there's another consensus connection
- * downloading (during bootstrap), or connecting (after bootstrap).
- * Post-bootstrap consensus connection attempts won't be closed, because
- * they only occur one at a time. */
- if (!connection_dir_would_close_consensus_conn_helper()) {
- return 0;
- }
-
- const int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping(
- time(NULL));
-
- /* We don't want to check other connections to see if they are downloading,
- * as this is prone to race-conditions. So leave it for
- * connection_dir_close_extra_consensus_conns(() to clean up.
- *
- * But if conn has just started connecting, or we have a consensus already,
- * we can be sure it's not needed any more. */
- if (!we_are_bootstrapping
- || conn->base_.state == DIR_CONN_STATE_CONNECTING) {
- connection_close_immediate(&conn->base_);
- connection_mark_for_close(&conn->base_);
- return -1;
- }
-
- return 0;
-}
-
-/* Clean up excess consensus download connection attempts.
- * During bootstrap, or when the bootstrap consensus has just been downloaded,
- * if we have more than one active consensus connection:
- * - if we don't have a consensus, and we're downloading a consensus, keep an
- * earlier connection, or a connection to a fallback directory, and close
- * all other connections;
- * - if we have just downloaded the bootstrap consensus, and have other
- * consensus connections left over, close all of them.
+ * We do this check here (not just in
+ * connection_ap_handshake_attach_circuit()) to handle the edge case where
+ * a consensus fetch begins and ends before some other one tries to attach to
+ * a circuit, in which case the other one won't know that we're all happy now.
*
- * Post-bootstrap consensus connection attempts are initiated one at a time.
- * So this function won't close any consensus connection attempts that
- * are initiated after bootstrap.
+ * Don't mark the conn that just gave us the consensus -- otherwise we
+ * would end up double-marking it when it cleans itself up.
*/
-void
-connection_dir_close_extra_consensus_conns(void)
+static void
+connection_dir_close_consensus_fetches(dir_connection_t *except_this_one,
+ const char *resource)
{
- /* Only cleanup connections if there is more than one consensus connection,
- * and at least one of those connections is already downloading
- * (during bootstrap), or connecting (just after the bootstrap consensus is
- * downloaded).
- * Post-bootstrap consensus connection attempts won't be cleaned up, because
- * they only occur one at a time. */
- if (!connection_dir_would_close_consensus_conn_helper()) {
- return;
- }
-
- int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping(
- time(NULL));
-
- const char *usable_resource = networkstatus_get_flavor_name(
- usable_consensus_flavor());
- smartlist_t *consens_usable_conns =
- connection_dir_list_by_purpose_and_resource(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource);
-
- /* If we want to keep a connection that's downloading, find a connection to
- * keep, favouring:
- * - connections opened earlier (they are likely to have progressed further)
- * - connections to fallbacks (to reduce the load on authorities) */
- dir_connection_t *kept_download_conn = NULL;
- int kept_is_authority = 0;
- if (we_are_bootstrapping) {
- SMARTLIST_FOREACH_BEGIN(consens_usable_conns,
- dir_connection_t *, d) {
- tor_assert(d);
- int d_is_authority = router_digest_is_trusted_dir(d->identity_digest);
- /* keep the first connection that is past the connecting state, but
- * prefer fallbacks. */
- if (d->base_.state != DIR_CONN_STATE_CONNECTING) {
- if (!kept_download_conn || (kept_is_authority && !d_is_authority)) {
- kept_download_conn = d;
- kept_is_authority = d_is_authority;
- /* we've found the earliest fallback, and want to keep it regardless
- * of any other connections */
- if (!kept_is_authority)
- break;
- }
- }
- } SMARTLIST_FOREACH_END(d);
- }
-
- SMARTLIST_FOREACH_BEGIN(consens_usable_conns,
- dir_connection_t *, d) {
- tor_assert(d);
- /* don't close this connection if it's the one we want to keep */
- if (kept_download_conn && d == kept_download_conn)
+ smartlist_t *conns_to_close =
+ connection_dir_list_by_purpose_and_resource(DIR_PURPOSE_FETCH_CONSENSUS,
+ resource);
+ SMARTLIST_FOREACH_BEGIN(conns_to_close, dir_connection_t *, d) {
+ if (d == except_this_one)
continue;
- /* mark all other connections for close */
- if (!d->base_.marked_for_close) {
- connection_close_immediate(&d->base_);
- connection_mark_for_close(&d->base_);
- }
+ log_info(LD_DIR, "Closing consensus fetch (to %s) since one "
+ "has just arrived.", TO_CONN(d)->address);
+ connection_mark_for_close(TO_CONN(d));
} SMARTLIST_FOREACH_END(d);
-
- smartlist_free(consens_usable_conns);
- consens_usable_conns = NULL;
-
- /* make sure we've closed all excess connections */
- const int final_connecting_conn_count =
- connection_dir_count_by_purpose_resource_and_state(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource,
- DIR_CONN_STATE_CONNECTING);
- if (final_connecting_conn_count > 0) {
- log_warn(LD_BUG, "Expected 0 consensus connections connecting after "
- "cleanup, got %d.", final_connecting_conn_count);
- }
- const int expected_final_conn_count = (we_are_bootstrapping ? 1 : 0);
- const int final_conn_count =
- connection_dir_count_by_purpose_and_resource(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource);
- if (final_conn_count > expected_final_conn_count) {
- log_warn(LD_BUG, "Expected %d consensus connections after cleanup, got "
- "%d.", expected_final_conn_count, final_connecting_conn_count);
- }
+ smartlist_free(conns_to_close);
}
/** Connected handler for directory connections: begin sending data to the
- * server, and return 0, or, if the connection is an excess bootstrap
- * connection, close all excess bootstrap connections.
+ * server, and return 0.
* Only used when connections don't immediately connect. */
int
connection_dir_finished_connecting(dir_connection_t *conn)
@@ -3863,12 +5199,6 @@ connection_dir_finished_connecting(dir_connection_t *conn)
log_debug(LD_HTTP,"Dir connection to router %s:%u established.",
conn->base_.address,conn->base_.port);
- /* Close this connection if there's another consensus connection
- * downloading (during bootstrap), or connecting (after bootstrap). */
- if (connection_dir_close_consensus_conn_if_extra(conn)) {
- return -1;
- }
-
/* start flushing conn */
conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING;
return 0;
@@ -3880,7 +5210,7 @@ connection_dir_finished_connecting(dir_connection_t *conn)
* Helper function for download_status_increment_failure(),
* download_status_reset(), and download_status_increment_attempt(). */
STATIC const smartlist_t *
-find_dl_schedule(download_status_t *dls, const or_options_t *options)
+find_dl_schedule(const download_status_t *dls, const or_options_t *options)
{
const int dir_server = dir_server_mode(options);
const int multi_d = networkstatus_consensus_can_use_multiple_directories(
@@ -3930,17 +5260,103 @@ 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)));
+ /* Increment on failure schedules always use exponential backoff, but they
+ * have a smaller limit when they're deterministic */
+ if (dls->backoff == DL_SCHED_DETERMINISTIC)
+ *max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1))));
+ else
+ *max = INT_MAX;
+}
+
+/** Advance one delay step. The algorithm is to use the previous delay to
+ * compute an increment, we construct a value uniformly at random between
+ * delay+1 and (delay*(DIR_DEFAULT_RANDOM_MULTIPLIER+1))+1 (or
+ * DIR_TEST_NET_RANDOM_MULTIPLIER in test networks).
+ * 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 = DIR_DEFAULT_RANDOM_MULTIPLIER;
+ if (get_options()->TestingTorNetwork) {
+ /* Decrease the multiplier in testing networks. This reduces the variance,
+ * so that bootstrap is more reliable. */
+ multiplier = DIR_TEST_NET_RANDOM_MULTIPLIER;
+ }
+
+ 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
@@ -3948,20 +5364,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_BUG_ONCE(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;
@@ -4013,13 +5459,21 @@ 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;
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
+ /* count the failure */
+ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) {
+ ++dls->n_download_failures;
}
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
@@ -4034,19 +5488,23 @@ 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",
"concurrently", dls->n_download_failures,
- increment, dls->next_attempt_at, now);
+ increment,
+ download_status_get_next_attempt_at(dls),
+ now);
if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) {
/* stop this schedule retrying on failure, it will launch concurrent
* connections instead */
return TIME_MAX;
} else {
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
}
}
@@ -4063,12 +5521,19 @@ 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);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
/* this schedule should retry on failure, and not launch any concurrent
attempts */
- 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;
}
@@ -4077,13 +5542,25 @@ 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,
- delay, dls->next_attempt_at, now);
+ delay, download_status_get_next_attempt_at(dls),
+ now);
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
+}
+
+static time_t
+download_status_get_initial_delay_from_now(const download_status_t *dls)
+{
+ const smartlist_t *schedule = find_dl_schedule(dls, get_options());
+ /* We use constant initial delays, even in exponential backoff
+ * schedules. */
+ return time(NULL) + *(int *)smartlist_get(schedule, 0);
}
/** Reset <b>dls</b> so that it will be considered downloadable
@@ -4104,11 +5581,11 @@ download_status_reset(download_status_t *dls)
|| dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD)
return; /* Don't reset this. */
- const smartlist_t *schedule = find_dl_schedule(dls, get_options());
-
dls->n_download_failures = 0;
dls->n_download_attempts = 0;
- dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0);
+ dls->next_attempt_at = download_status_get_initial_delay_from_now(dls);
+ dls->last_backoff_position = 0;
+ dls->last_delay_used = 0;
/* Don't reset dls->want_authority or dls->increment_on */
}
@@ -4133,6 +5610,12 @@ download_status_get_n_attempts(const download_status_t *dls)
time_t
download_status_get_next_attempt_at(const download_status_t *dls)
{
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ /* so give the answer we would have given if it had been */
+ return download_status_get_initial_delay_from_now(dls);
+ }
+
return dls->next_attempt_at;
}
@@ -4158,7 +5641,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;
}
@@ -4255,9 +5738,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)));
@@ -4335,8 +5819,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;
@@ -4367,3 +5852,34 @@ dir_split_resource_into_fingerprints(const char *resource,
return 0;
}
+/** As dir_split_resource_into_fingerprints, but instead fills
+ * <b>spool_out</b> with a list of spoolable_resource_t for the resource
+ * identified through <b>source</b>. */
+int
+dir_split_resource_into_spoolable(const char *resource,
+ dir_spool_source_t source,
+ smartlist_t *spool_out,
+ int *compressed_out,
+ int flags)
+{
+ smartlist_t *fingerprints = smartlist_new();
+
+ tor_assert(flags & (DSR_HEX|DSR_BASE64));
+ const size_t digest_len =
+ (flags & DSR_DIGEST256) ? DIGEST256_LEN : DIGEST_LEN;
+
+ int r = dir_split_resource_into_fingerprints(resource, fingerprints,
+ compressed_out, flags);
+ /* This is not a very efficient implementation XXXX */
+ SMARTLIST_FOREACH_BEGIN(fingerprints, uint8_t *, digest) {
+ spooled_resource_t *spooled =
+ spooled_resource_new(source, digest, digest_len);
+ if (spooled)
+ smartlist_add(spool_out, spooled);
+ tor_free(digest);
+ } SMARTLIST_FOREACH_END(digest);
+
+ smartlist_free(fingerprints);
+ return r;
+}
+
diff --git a/src/or/directory.h b/src/or/directory.h
index 03c04c10c9..d3f8a45a82 100644
--- a/src/or/directory.h
+++ b/src/or/directory.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -12,6 +12,8 @@
#ifndef TOR_DIRECTORY_H
#define TOR_DIRECTORY_H
+#include "hs_ident.h"
+
int directories_have_accepted_server_descriptor(void);
void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
dirinfo_type_t type, const char *payload,
@@ -28,8 +30,8 @@ void directory_get_from_all_authorities(uint8_t dir_purpose,
/** Enumeration of ways to connect to a directory server */
typedef enum {
- /** Default: connect over a one-hop Tor circuit but fall back to direct
- * connection */
+ /** Default: connect over a one-hop Tor circuit. Relays fall back to direct
+ * DirPort connections, clients, onion services, and bridges do not */
DIRIND_ONEHOP=0,
/** Connect over a multi-hop anonymizing Tor circuit */
DIRIND_ANONYMOUS=1,
@@ -39,46 +41,57 @@ typedef enum {
DIRIND_ANON_DIRPORT,
} dir_indirection_t;
-MOCK_DECL(void, directory_initiate_command_routerstatus,
- (const routerstatus_t *status,
- uint8_t dir_purpose,
- uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload,
- size_t payload_len,
- time_t if_modified_since));
-
-void directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
- uint8_t dir_purpose,
- uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload,
- size_t payload_len,
- time_t if_modified_since,
- const rend_data_t *rend_query);
+int directory_must_use_begindir(const or_options_t *options);
+
+/**
+ * A directory_request_t describes the information about a directory request
+ * at the client side. It describes what we're going to ask for, which
+ * directory we're going to ask for it, how we're going to contact that
+ * directory, and (in some cases) what to do with it when we're done.
+ */
+typedef struct directory_request_t directory_request_t;
+directory_request_t *directory_request_new(uint8_t dir_purpose);
+void directory_request_free(directory_request_t *req);
+void directory_request_set_or_addr_port(directory_request_t *req,
+ const tor_addr_port_t *p);
+void directory_request_set_dir_addr_port(directory_request_t *req,
+ const tor_addr_port_t *p);
+void directory_request_set_directory_id_digest(directory_request_t *req,
+ const char *digest);
+void directory_request_set_guard_state(directory_request_t *req,
+ struct circuit_guard_state_t *state);
+void directory_request_set_router_purpose(directory_request_t *req,
+ uint8_t router_purpose);
+void directory_request_set_indirection(directory_request_t *req,
+ dir_indirection_t indirection);
+void directory_request_set_resource(directory_request_t *req,
+ const char *resource);
+void directory_request_set_payload(directory_request_t *req,
+ const char *payload,
+ size_t payload_len);
+void directory_request_set_if_modified_since(directory_request_t *req,
+ time_t if_modified_since);
+void directory_request_set_rend_query(directory_request_t *req,
+ const rend_data_t *query);
+void directory_request_upload_set_hs_ident(directory_request_t *req,
+ const hs_ident_dir_conn_t *ident);
+
+void directory_request_set_routerstatus(directory_request_t *req,
+ const routerstatus_t *rs);
+void directory_request_add_header(directory_request_t *req,
+ const char *key,
+ const char *val);
+MOCK_DECL(void, directory_initiate_request, (directory_request_t *request));
int parse_http_response(const char *headers, int *code, time_t *date,
compress_method_t *compression, char **response);
-int connection_dir_is_encrypted(dir_connection_t *conn);
+int connection_dir_is_encrypted(const dir_connection_t *conn);
int connection_dir_reached_eof(dir_connection_t *conn);
int connection_dir_process_inbuf(dir_connection_t *conn);
int connection_dir_finished_flushing(dir_connection_t *conn);
int connection_dir_finished_connecting(dir_connection_t *conn);
void connection_dir_about_to_close(dir_connection_t *dir_conn);
-void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
- const tor_addr_t *dir_addr, uint16_t dir_port,
- const char *digest,
- uint8_t dir_purpose, uint8_t router_purpose,
- dir_indirection_t indirection,
- const char *resource,
- const char *payload, size_t payload_len,
- time_t if_modified_since);
-int connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose);
-int connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn);
-void connection_dir_close_extra_consensus_conns(void);
#define DSR_HEX (1<<0)
#define DSR_BASE64 (1<<1)
@@ -87,7 +100,12 @@ void connection_dir_close_extra_consensus_conns(void);
int dir_split_resource_into_fingerprints(const char *resource,
smartlist_t *fp_out, int *compressed_out,
int flags);
-
+enum dir_spool_source_t;
+int dir_split_resource_into_spoolable(const char *resource,
+ enum dir_spool_source_t source,
+ smartlist_t *spool_out,
+ int *compressed_out,
+ int flags);
int dir_split_resource_into_fingerprint_pairs(const char *res,
smartlist_t *pairs_out);
char *directory_dump_request_log(void);
@@ -109,15 +127,28 @@ time_t download_status_increment_attempt(download_status_t *dls,
void download_status_reset(download_status_t *dls);
static int download_status_is_ready(download_status_t *dls, time_t now,
int max_failures);
+time_t download_status_get_next_attempt_at(const download_status_t *dls);
+
/** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is
* ready to get its download reattempted. */
static inline int
download_status_is_ready(download_status_t *dls, time_t now,
int max_failures)
{
- 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);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
+ if (dls->backoff == DL_SCHED_DETERMINISTIC) {
+ /* Deterministic schedules can hit an endpoint; exponential backoff
+ * schedules just wait longer and longer. */
+ int under_failure_limit = (dls->n_download_failures <= max_failures
+ && dls->n_download_attempts <= max_failures);
+ if (!under_failure_limit)
+ return 0;
+ }
+ return download_status_get_next_attempt_at(dls) <= now;
}
static void download_status_mark_impossible(download_status_t *dl);
@@ -131,31 +162,68 @@ download_status_mark_impossible(download_status_t *dl)
int download_status_get_n_failures(const download_status_t *dls);
int download_status_get_n_attempts(const download_status_t *dls);
-time_t download_status_get_next_attempt_at(const download_status_t *dls);
+
+int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
+ const char *resource);
+
+#ifdef DIRECTORY_PRIVATE
+
+struct get_handler_args_t;
+STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
+ const struct get_handler_args_t *args);
+STATIC int directory_handle_command(dir_connection_t *conn);
+STATIC char *accept_encoding_header(void);
+STATIC int allowed_anonymous_connection_compression_method(compress_method_t);
+STATIC void warn_disallowed_anonymous_compression_method(compress_method_t);
+
+#endif
#ifdef TOR_UNIT_TESTS
-/* Used only by directory.c and test_dir.c */
+/* Used only by 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,
- const char *headers,
- const char *req_body,
- size_t req_body_len);
-STATIC int connection_dir_would_close_consensus_conn_helper(void);
+MOCK_DECL(STATIC int, directory_handle_command_get,(dir_connection_t *conn,
+ const char *headers,
+ const char *req_body,
+ size_t req_body_len));
+MOCK_DECL(STATIC int, directory_handle_command_post,(dir_connection_t *conn,
+ const char *headers,
+ const char *body,
+ size_t 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 int handle_post_hs_descriptor(const char *url, const char *body);
+
STATIC char* authdir_type_to_string(dirinfo_type_t auth);
STATIC const char * dir_conn_purpose_to_string(int purpose);
STATIC int should_use_directory_guards(const or_options_t *options);
-STATIC zlib_compression_level_t choose_compression_level(ssize_t n_bytes);
-STATIC const smartlist_t *find_dl_schedule(download_status_t *dls,
+STATIC compression_level_t choose_compression_level(ssize_t n_bytes);
+STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls,
const or_options_t *options);
+STATIC void find_dl_min_and_max_delay(download_status_t *dls,
+ const or_options_t *options,
+ int *min, int *max);
+STATIC int next_random_exponential_delay(int delay, int max_delay);
+
+STATIC int parse_hs_version_from_post(const char *url, const char *prefix,
+ const char **end_pos);
+
+STATIC unsigned parse_accept_encoding_header(const char *h);
+#endif
+
+#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE)
+/* Used only by directory.c and test_dir.c */
+
+/* no more than quadruple the previous delay (multiplier + 1) */
+#define DIR_DEFAULT_RANDOM_MULTIPLIER (3)
+/* no more than triple the previous delay */
+#define DIR_TEST_NET_RANDOM_MULTIPLIER (2)
+
#endif
#endif
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index f012b7bf64..e5654e3b90 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define DIRSERV_PRIVATE
@@ -13,16 +13,20 @@
#include "command.h"
#include "connection.h"
#include "connection_or.h"
+#include "conscache.h"
+#include "consdiffmgr.h"
#include "control.h"
#include "directory.h"
#include "dirserv.h"
#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 +38,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 +66,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
@@ -65,14 +83,23 @@ dirserv_get_status_impl(const char *fp, const char *nickname,
int severity);
static void clear_cached_dir(cached_dir_t *d);
static const signed_descriptor_t *get_signed_descriptor_by_fp(
- const char *fp,
- int extrainfo,
- time_t publish_cutoff);
+ const uint8_t *fp,
+ int extrainfo);
static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei,
const char **msg);
static uint32_t dirserv_get_bandwidth_for_router_kb(const routerinfo_t *ri);
static uint32_t dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri);
+static int spooled_resource_lookup_body(const spooled_resource_t *spooled,
+ int conn_is_encrypted,
+ const uint8_t **body_out,
+ size_t *size_out,
+ time_t *published_out);
+static cached_dir_t *spooled_resource_lookup_cached_dir(
+ const spooled_resource_t *spooled,
+ time_t *published_out);
+static cached_dir_t *lookup_cached_dir_by_fp(const uint8_t *fp);
+
/************** Fingerprint handling code ************/
/* 1 Historically used to indicate Named */
@@ -125,7 +152,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 +230,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,11 +285,32 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg,
return FP_REJECT;
}
- if (router->signing_key_cert) {
+ /* Check for the more usual versions to reject a router first. */
+ const uint32_t r = dirserv_get_status_impl(d, router->nickname,
+ router->addr, router->or_port,
+ router->platform, msg, severity);
+ if (r)
+ return r;
+
+ /* 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 ==
keypin_check((const uint8_t*)router->cache_info.identity_digest,
- router->signing_key_cert->signing_key.pubkey)) {
+ router->cache_info.signing_key_cert->signing_key.pubkey)) {
log_fn(severity, LD_DIR,
"Descriptor from router %s has an Ed25519 key, "
"but the <rsa,ed25519> keys don't match what they were before.",
@@ -293,9 +342,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg,
}
}
- return dirserv_get_status_impl(d, router->nickname,
- router->addr, router->or_port,
- router->platform, msg, severity);
+ return 0;
}
/** Return true if there is no point in downloading the router described by
@@ -334,6 +381,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 +399,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 +417,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 +435,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)) {
@@ -520,6 +588,8 @@ dirserv_add_multiple_descriptors(const char *desc, uint8_t purpose,
!general ? router_purpose_to_string(purpose) : "",
!general ? "\n" : "")<0) {
*msg = "Couldn't format annotations";
+ /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is
+ * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */
return -1;
}
@@ -574,7 +644,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 +676,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,17 +697,29 @@ 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 */
int keypin_status;
- if (ri->signing_key_cert) {
+ if (ri->cache_info.signing_key_cert) {
+ ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
+ /* First let's validate this pubkey before pinning it */
+ if (ed25519_validate_pubkey(pkey) < 0) {
+ log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
+ router_describe(ri), source);
+ control_event_or_authdir_new_descriptor("REJECTED",
+ ri->cache_info.signed_descriptor_body,
+ desclen, *msg);
+ routerinfo_free(ri);
+ return ROUTER_AUTHDIR_REJECTS;
+ }
+
+ /* Now pin it! */
keypin_status = keypin_check_and_add(
(const uint8_t*)ri->cache_info.identity_digest,
- ri->signing_key_cert->signing_key.pubkey,
- ! key_pinning);
+ pkey->pubkey, ! key_pinning);
} else {
keypin_status = keypin_check_lone_rsa(
(const uint8_t*)ri->cache_info.identity_digest);
@@ -647,7 +733,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,22 +772,39 @@ 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>. */
static was_router_added_t
dirserv_add_extrainfo(extrainfo_t *ei, const char **msg)
{
- const routerinfo_t *ri;
+ routerinfo_t *ri;
int r;
+ was_router_added_t rv;
tor_assert(msg);
*msg = NULL;
- ri = router_get_by_id_digest(ei->cache_info.identity_digest);
+ /* Needs to be mutable so routerinfo_incompatible_with_extrainfo
+ * can mess with some of the flags in ri->cache_info. */
+ 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
@@ -712,16 +816,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, ei, NULL, msg))) {
- extrainfo_free(ei);
- return r < 0 ? ROUTER_IS_ALREADY_KNOWN : ROUTER_BAD_EI;
+ if ((r = routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
+ &ri->cache_info, msg))) {
+ 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
@@ -770,6 +892,9 @@ directory_remove_invalid(void)
* Allocate and return a description of the status of the server <b>desc</b>,
* for use in a v1-style router-status line. The server is listed
* as running iff <b>is_live</b> is true.
+ *
+ * This is deprecated: it's only used for controllers that want outputs in
+ * the old format.
*/
static char *
list_single_server_status(const routerinfo_t *desc, int is_live)
@@ -820,7 +945,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.
*/
@@ -882,6 +1007,9 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now)
* *<b>router_status_out</b>. Return 0 on success, -1 on failure.
*
* If for_controller is true, include the routers with very old descriptors.
+ *
+ * This is deprecated: it's only used for controllers that want outputs in
+ * the old format.
*/
int
list_server_status_v1(smartlist_t *routers, char **router_status_out,
@@ -914,7 +1042,7 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out,
if (!node->is_running)
*cp++ = '!';
router_get_verbose_nickname(cp, ri);
- smartlist_add(rs_entries, tor_strdup(name_buf));
+ smartlist_add_strdup(rs_entries, name_buf);
} else if (ri->cache_info.published_on >= cutoff) {
smartlist_add(rs_entries, list_single_server_status(ri,
node->is_running));
@@ -982,94 +1110,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
@@ -1087,7 +1127,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);
@@ -1122,8 +1163,10 @@ directory_fetches_dir_info_later(const or_options_t *options)
return options->UseBridges != 0;
}
-/** Return true iff we want to fetch and keep certificates for authorities
+/** Return true iff we want to serve certificates for authorities
* that we don't acknowledge as authorities ourself.
+ * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch
+ * and keep these certificates.
*/
int
directory_caches_unknown_auth_certs(const or_options_t *options)
@@ -1131,8 +1174,14 @@ directory_caches_unknown_auth_certs(const or_options_t *options)
return dir_server_mode(options) || options->BridgeRelay;
}
-/** Return 1 if we want to keep descriptors, networkstatuses, etc around
- * and we're willing to serve them to others. Else return 0.
+/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc
+ * Else return 0.
+ * Check options->DirPort_set and directory_permits_begindir_requests()
+ * to see if we are willing to serve these directory documents to others via
+ * the DirPort and begindir-over-ORPort, respectively.
+ *
+ * To check if we should fetch documents, use we_want_to_fetch_flavor and
+ * we_want_to_fetch_unknown_auth_certs instead of this function.
*/
int
directory_caches_dir_info(const or_options_t *options)
@@ -1147,7 +1196,7 @@ directory_caches_dir_info(const or_options_t *options)
should_refuse_unknown_exits(options);
}
-/** Return 1 if we want to allow remote people to ask us directory
+/** Return 1 if we want to allow remote clients to ask us directory
* requests via the "begin_dir" interface, which doesn't require
* having any separate port open. */
int
@@ -1196,8 +1245,8 @@ new_cached_dir(char *s, time_t published)
d->dir = s;
d->dir_len = strlen(s);
d->published = published;
- if (tor_gzip_compress(&(d->dir_z), &(d->dir_z_len), d->dir, d->dir_len,
- ZLIB_METHOD)) {
+ if (tor_compress(&(d->dir_compressed), &(d->dir_compressed_len),
+ d->dir, d->dir_len, ZLIB_METHOD)) {
log_warn(LD_BUG, "Error compressing directory");
}
return d;
@@ -1208,7 +1257,7 @@ static void
clear_cached_dir(cached_dir_t *d)
{
tor_free(d->dir);
- tor_free(d->dir_z);
+ tor_free(d->dir_compressed);
memset(d, 0, sizeof(cached_dir_t));
}
@@ -1231,6 +1280,7 @@ void
dirserv_set_cached_consensus_networkstatus(const char *networkstatus,
const char *flavor_name,
const common_digests_t *digests,
+ const uint8_t *sha3_as_signed,
time_t published)
{
cached_dir_t *new_networkstatus;
@@ -1240,6 +1290,8 @@ dirserv_set_cached_consensus_networkstatus(const char *networkstatus,
new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published);
memcpy(&new_networkstatus->digests, digests, sizeof(common_digests_t));
+ memcpy(&new_networkstatus->digest_sha3_as_signed, sha3_as_signed,
+ DIGEST256_LEN);
old_networkstatus = strmap_set(cached_consensuses, flavor_name,
new_networkstatus);
if (old_networkstatus)
@@ -1323,7 +1375,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,
@@ -1424,13 +1476,13 @@ router_counts_toward_thresholds(const node_t *node, time_t now,
*
* Also, set the is_exit flag of each router appropriately. */
static void
-dirserv_compute_performance_thresholds(const smartlist_t *routers,
- digestmap_t *omit_as_sybil)
+dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil)
{
int n_active, n_active_nonexit, n_familiar;
uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb;
long *tks;
double *mtbfs, *wfus;
+ smartlist_t *nodelist;
time_t now = time(NULL);
const or_options_t *options = get_options();
@@ -1448,27 +1500,28 @@ dirserv_compute_performance_thresholds(const smartlist_t *routers,
guard_tk = 0;
guard_wfu = 0;
+ nodelist_assert_ok();
+ nodelist = nodelist_get_list();
+
/* Initialize arrays that will hold values for each router. We'll
* sort them and use that to compute thresholds. */
n_active = n_active_nonexit = 0;
/* Uptime for every active router. */
- uptimes = tor_calloc(smartlist_len(routers), sizeof(uint32_t));
+ uptimes = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
/* Bandwidth for every active router. */
- bandwidths_kb = tor_calloc(smartlist_len(routers), sizeof(uint32_t));
+ bandwidths_kb = tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
/* Bandwidth for every active non-exit router. */
bandwidths_excluding_exits_kb =
- tor_calloc(smartlist_len(routers), sizeof(uint32_t));
+ tor_calloc(smartlist_len(nodelist), sizeof(uint32_t));
/* Weighted mean time between failure for each active router. */
- mtbfs = tor_calloc(smartlist_len(routers), sizeof(double));
+ mtbfs = tor_calloc(smartlist_len(nodelist), sizeof(double));
/* Time-known for each active router. */
- tks = tor_calloc(smartlist_len(routers), sizeof(long));
+ tks = tor_calloc(smartlist_len(nodelist), sizeof(long));
/* Weighted fractional uptime for each active router. */
- wfus = tor_calloc(smartlist_len(routers), sizeof(double));
-
- nodelist_assert_ok();
+ wfus = tor_calloc(smartlist_len(nodelist), sizeof(double));
/* Now, fill in the arrays. */
- SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) {
+ SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
if (options->BridgeAuthoritativeDir &&
node->ri &&
node->ri->purpose != ROUTER_PURPOSE_BRIDGE)
@@ -1544,7 +1597,7 @@ dirserv_compute_performance_thresholds(const smartlist_t *routers,
* fill wfus with the wfu of every such "familiar" router. */
n_familiar = 0;
- SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) {
+ SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) {
if (router_counts_toward_thresholds(node, now,
omit_as_sybil, require_mbw)) {
routerinfo_t *ri = node->ri;
@@ -1598,11 +1651,10 @@ dirserv_compute_performance_thresholds(const smartlist_t *routers,
* networkstatus_getinfo_by_purpose().
*/
void
-dirserv_compute_bridge_flag_thresholds(const smartlist_t *routers)
+dirserv_compute_bridge_flag_thresholds(void)
{
-
digestmap_t *omit_as_sybil = digestmap_new();
- dirserv_compute_performance_thresholds(routers, omit_as_sybil);
+ dirserv_compute_performance_thresholds(omit_as_sybil);
digestmap_free(omit_as_sybil, NULL);
}
@@ -1865,6 +1917,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)
{
@@ -1928,6 +1981,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);
@@ -1995,7 +2051,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
vrs->status.guardfraction_percentage);
}
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
if (desc) {
summary = policy_summarize(desc->exit_policy, AF_INET);
@@ -2005,7 +2061,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
if (format == NS_V3_VOTE && vrs) {
if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) {
- smartlist_add(chunks, tor_strdup("id ed25519 none\n"));
+ smartlist_add_strdup(chunks, "id ed25519 none\n");
} else {
char ed_b64[BASE64_DIGEST256_LEN+1];
digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id);
@@ -2097,12 +2153,8 @@ get_possible_sybil_list(const smartlist_t *routers)
int addr_count;
/* Allow at most this number of Tor servers on a single IP address, ... */
int max_with_same_addr = options->AuthDirMaxServersPerAddr;
- /* ... unless it's a directory authority, in which case allow more. */
- int max_with_same_addr_on_authority = options->AuthDirMaxServersPerAuthAddr;
if (max_with_same_addr <= 0)
max_with_same_addr = INT_MAX;
- if (max_with_same_addr_on_authority <= 0)
- max_with_same_addr_on_authority = INT_MAX;
smartlist_add_all(routers_by_ip, routers);
smartlist_sort(routers_by_ip, compare_routerinfo_by_ip_and_bw_);
@@ -2115,9 +2167,7 @@ get_possible_sybil_list(const smartlist_t *routers)
last_addr = ri->addr;
addr_count = 1;
} else if (++addr_count > max_with_same_addr) {
- if (!router_addr_is_trusted_dir(ri->addr) ||
- addr_count > max_with_same_addr_on_authority)
- digestmap_set(omit_as_sybil, ri->cache_info.identity_digest, ri);
+ digestmap_set(omit_as_sybil, ri->cache_info.identity_digest, ri);
}
} SMARTLIST_FOREACH_END(ri);
@@ -2136,9 +2186,9 @@ routers_make_ed_keys_unique(smartlist_t *routers)
SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
ri->omit_from_vote = 0;
- if (ri->signing_key_cert == NULL)
+ if (ri->cache_info.signing_key_cert == NULL)
continue; /* No ed key */
- const uint8_t *pk = ri->signing_key_cert->signing_key.pubkey;
+ const uint8_t *pk = ri->cache_info.signing_key_cert->signing_key.pubkey;
if ((ri2 = digest256map_get(by_ed_key, pk))) {
/* Duplicate; must omit one. Set the omit_from_vote flag in whichever
* one has the earlier published_on. */
@@ -2146,8 +2196,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 {
@@ -2277,15 +2327,19 @@ 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.
+ * too narrow an IP-space. Clear out its flags since we don't want it be used
+ * because of its Sybil-like appearance.
+ *
+ * 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. */
@@ -2359,7 +2413,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;
}
@@ -2663,7 +2718,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);
@@ -2873,7 +2929,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
* this must come before dirserv_compute_performance_thresholds() */
dirserv_count_measured_bws(routers);
- dirserv_compute_performance_thresholds(routers, omit_as_sybil);
+ dirserv_compute_performance_thresholds(omit_as_sybil);
routerstatuses = smartlist_new();
microdescriptors = smartlist_new();
@@ -2891,8 +2947,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
set_routerstatus_from_routerinfo(rs, node, ri, now,
listbadexits);
- if (ri->signing_key_cert) {
- memcpy(vrs->ed25519_id, ri->signing_key_cert->signing_key.pubkey,
+ if (ri->cache_info.signing_key_cert) {
+ memcpy(vrs->ed25519_id,
+ ri->cache_info.signing_key_cert->signing_key.pubkey,
ED25519_PUBKEY_LEN);
}
@@ -2903,6 +2960,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);
@@ -2975,12 +3038,37 @@ 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;
for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) {
if (validate_recommended_package_line(cl->value))
- smartlist_add(v3_out->package_lines, tor_strdup(cl->value));
+ smartlist_add_strdup(v3_out->package_lines, cl->value);
}
}
@@ -2989,9 +3077,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
"Authority Exit Fast Guard Stable V2Dir Valid HSDir",
0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (vote_on_reachability)
- smartlist_add(v3_out->known_flags, tor_strdup("Running"));
+ smartlist_add_strdup(v3_out->known_flags, "Running");
if (listbadexits)
- smartlist_add(v3_out->known_flags, tor_strdup("BadExit"));
+ smartlist_add_strdup(v3_out->known_flags, "BadExit");
smartlist_sort_strings(v3_out->known_flags);
if (options->ConsensusParams) {
@@ -3036,58 +3124,61 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
* requests, adds identity digests.
*/
int
-dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key,
- const char **msg, int for_unencrypted_conn,
- int is_extrainfo)
+dirserv_get_routerdesc_spool(smartlist_t *spool_out,
+ const char *key,
+ dir_spool_source_t source,
+ int conn_is_encrypted,
+ const char **msg_out)
{
- int by_id = 1;
- *msg = NULL;
+ *msg_out = NULL;
if (!strcmp(key, "all")) {
- routerlist_t *rl = router_get_routerlist();
- SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r,
- smartlist_add(fps_out,
- tor_memdup(r->cache_info.identity_digest, DIGEST_LEN)));
- /* Treat "all" requests as if they were unencrypted */
- for_unencrypted_conn = 1;
+ const routerlist_t *rl = router_get_routerlist();
+ SMARTLIST_FOREACH_BEGIN(rl->routers, const routerinfo_t *, r) {
+ spooled_resource_t *spooled;
+ spooled = spooled_resource_new(source,
+ (const uint8_t *)r->cache_info.identity_digest,
+ DIGEST_LEN);
+ /* Treat "all" requests as if they were unencrypted */
+ conn_is_encrypted = 0;
+ smartlist_add(spool_out, spooled);
+ } SMARTLIST_FOREACH_END(r);
} else if (!strcmp(key, "authority")) {
const routerinfo_t *ri = router_get_my_routerinfo();
if (ri)
- smartlist_add(fps_out,
- tor_memdup(ri->cache_info.identity_digest, DIGEST_LEN));
+ smartlist_add(spool_out,
+ spooled_resource_new(source,
+ (const uint8_t *)ri->cache_info.identity_digest,
+ DIGEST_LEN));
} else if (!strcmpstart(key, "d/")) {
- by_id = 0;
key += strlen("d/");
- dir_split_resource_into_fingerprints(key, fps_out, NULL,
- DSR_HEX|DSR_SORT_UNIQ);
+ dir_split_resource_into_spoolable(key, source, spool_out, NULL,
+ DSR_HEX|DSR_SORT_UNIQ);
} else if (!strcmpstart(key, "fp/")) {
key += strlen("fp/");
- dir_split_resource_into_fingerprints(key, fps_out, NULL,
- DSR_HEX|DSR_SORT_UNIQ);
+ dir_split_resource_into_spoolable(key, source, spool_out, NULL,
+ DSR_HEX|DSR_SORT_UNIQ);
} else {
- *msg = "Key not recognized";
+ *msg_out = "Not found";
return -1;
}
- if (for_unencrypted_conn) {
+ if (! conn_is_encrypted) {
/* Remove anything that insists it not be sent unencrypted. */
- SMARTLIST_FOREACH_BEGIN(fps_out, char *, cp) {
- const signed_descriptor_t *sd;
- if (by_id)
- sd = get_signed_descriptor_by_fp(cp,is_extrainfo,0);
- else if (is_extrainfo)
- sd = extrainfo_get_by_descriptor_digest(cp);
- else
- sd = router_get_by_descriptor_digest(cp);
- if (sd && !sd->send_unencrypted) {
- tor_free(cp);
- SMARTLIST_DEL_CURRENT(fps_out, cp);
- }
- } SMARTLIST_FOREACH_END(cp);
+ SMARTLIST_FOREACH_BEGIN(spool_out, spooled_resource_t *, spooled) {
+ const uint8_t *body = NULL;
+ size_t bodylen = 0;
+ int r = spooled_resource_lookup_body(spooled, conn_is_encrypted,
+ &body, &bodylen, NULL);
+ if (r < 0 || body == NULL || bodylen == 0) {
+ SMARTLIST_DEL_CURRENT(spool_out, spooled);
+ spooled_resource_free(spooled);
+ }
+ } SMARTLIST_FOREACH_END(spooled);
}
- if (!smartlist_len(fps_out)) {
- *msg = "Servers unavailable";
+ if (!smartlist_len(spool_out)) {
+ *msg_out = "Servers unavailable";
return -1;
}
return 0;
@@ -3183,7 +3274,8 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
void
dirserv_orconn_tls_done(const tor_addr_t *addr,
uint16_t or_port,
- const char *digest_rcvd)
+ const char *digest_rcvd,
+ const ed25519_public_key_t *ed_id_rcvd)
{
node_t *node = NULL;
tor_addr_port_t orport;
@@ -3195,8 +3287,26 @@ dirserv_orconn_tls_done(const tor_addr_t *addr,
node = node_get_mutable_by_id(digest_rcvd);
if (node == NULL || node->ri == NULL)
return;
+
ri = node->ri;
+ if (get_options()->AuthDirTestEd25519LinkKeys &&
+ node_supports_ed25519_link_authentication(node) &&
+ ri->cache_info.signing_key_cert) {
+ /* We allow the node to have an ed25519 key if we haven't been told one in
+ * the routerinfo, but if we *HAVE* been told one in the routerinfo, it
+ * needs to match. */
+ const ed25519_public_key_t *expected_id =
+ &ri->cache_info.signing_key_cert->signing_key;
+ tor_assert(!ed25519_public_key_is_zero(expected_id));
+ if (! ed_id_rcvd || ! ed25519_pubkey_eq(ed_id_rcvd, expected_id)) {
+ log_info(LD_DIRSERV, "Router at %s:%d with RSA ID %s "
+ "did not present expected Ed25519 ID.",
+ fmt_addr(addr), or_port, hex_str(digest_rcvd, DIGEST_LEN));
+ return; /* Don't mark it as reachable. */
+ }
+ }
+
tor_addr_copy(&orport.addr, addr);
orport.port = or_port;
if (router_has_orport(ri, &orport)) {
@@ -3204,7 +3314,7 @@ dirserv_orconn_tls_done(const tor_addr_t *addr,
if (!authdir_mode_bridge(get_options()) ||
ri->purpose == ROUTER_PURPOSE_BRIDGE) {
char addrstr[TOR_ADDR_BUF_LEN];
- /* This is a bridge or we're not a bridge authorititative --
+ /* This is a bridge or we're not a bridge authority --
mark it as reachable. */
log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.",
router_describe(ri),
@@ -3252,21 +3362,31 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri,
void
dirserv_single_reachability_test(time_t now, routerinfo_t *router)
{
+ const or_options_t *options = get_options();
channel_t *chan = NULL;
- node_t *node = NULL;
+ const node_t *node = NULL;
tor_addr_t router_addr;
+ const ed25519_public_key_t *ed_id_key;
(void) now;
tor_assert(router);
- node = node_get_mutable_by_id(router->cache_info.identity_digest);
+ node = node_get_by_id(router->cache_info.identity_digest);
tor_assert(node);
+ if (options->AuthDirTestEd25519LinkKeys &&
+ node_supports_ed25519_link_authentication(node)) {
+ ed_id_key = &router->cache_info.signing_key_cert->signing_key;
+ } else {
+ ed_id_key = NULL;
+ }
+
/* IPv4. */
log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
router->nickname, fmt_addr32(router->addr), router->or_port);
tor_addr_from_ipv4h(&router_addr, router->addr);
chan = channel_tls_connect(&router_addr, router->or_port,
- router->cache_info.identity_digest);
+ router->cache_info.identity_digest,
+ ed_id_key);
if (chan) command_setup_channel(chan);
/* Possible IPv6. */
@@ -3278,7 +3398,8 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router)
tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1),
router->ipv6_orport);
chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport,
- router->cache_info.identity_digest);
+ router->cache_info.identity_digest,
+ ed_id_key);
if (chan) command_setup_channel(chan);
}
}
@@ -3321,410 +3442,502 @@ dirserv_test_reachability(time_t now)
ctr = (ctr + 1) % REACHABILITY_MODULO_PER_TEST; /* increment ctr */
}
-/** Given a fingerprint <b>fp</b> which is either set if we're looking for a
- * v2 status, or zeroes if we're looking for a v3 status, or a NUL-padded
- * flavor name if we want a flavored v3 status, return a pointer to the
- * appropriate cached dir object, or NULL if there isn't one available. */
-static cached_dir_t *
-lookup_cached_dir_by_fp(const char *fp)
+/* ==========
+ * Spooling code.
+ * ========== */
+
+spooled_resource_t *
+spooled_resource_new(dir_spool_source_t source,
+ const uint8_t *digest, size_t digestlen)
{
- cached_dir_t *d = NULL;
- if (tor_digest_is_zero(fp) && cached_consensuses) {
- 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 */;
+ spooled_resource_t *spooled = tor_malloc_zero(sizeof(spooled_resource_t));
+ spooled->spool_source = source;
+ switch (source) {
+ case DIR_SPOOL_NETWORKSTATUS:
+ spooled->spool_eagerly = 0;
+ break;
+ case DIR_SPOOL_SERVER_BY_DIGEST:
+ case DIR_SPOOL_SERVER_BY_FP:
+ case DIR_SPOOL_EXTRA_BY_DIGEST:
+ case DIR_SPOOL_EXTRA_BY_FP:
+ case DIR_SPOOL_MICRODESC:
+ default:
+ spooled->spool_eagerly = 1;
+ break;
+ case DIR_SPOOL_CONSENSUS_CACHE_ENTRY:
+ tor_assert_unreached();
+ break;
}
- return d;
+ tor_assert(digestlen <= sizeof(spooled->digest));
+ if (digest)
+ memcpy(spooled->digest, digest, digestlen);
+ return spooled;
}
-/** Remove from <b>fps</b> every networkstatus key where both
- * a) we have a networkstatus document and
- * b) it is not newer than <b>cutoff</b>.
+/**
+ * Create a new spooled_resource_t to spool the contents of <b>entry</b> to
+ * the user. Return the spooled object on success, or NULL on failure (which
+ * is probably caused by a failure to map the body of the item from disk).
*
- * Return 1 if any items were present at all; else return 0.
+ * Adds a reference to entry's reference counter.
*/
-int
-dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff)
-{
- int found_any = 0;
- SMARTLIST_FOREACH_BEGIN(fps, char *, digest) {
- cached_dir_t *d = lookup_cached_dir_by_fp(digest);
- if (!d)
- continue;
- found_any = 1;
- if (d->published <= cutoff) {
- tor_free(digest);
- SMARTLIST_DEL_CURRENT(fps, digest);
- }
- } SMARTLIST_FOREACH_END(digest);
-
- return found_any;
+spooled_resource_t *
+spooled_resource_new_from_cache_entry(consensus_cache_entry_t *entry)
+{
+ spooled_resource_t *spooled = tor_malloc_zero(sizeof(spooled_resource_t));
+ spooled->spool_source = DIR_SPOOL_CONSENSUS_CACHE_ENTRY;
+ spooled->spool_eagerly = 0;
+ consensus_cache_entry_incref(entry);
+ spooled->consensus_cache_entry = entry;
+
+ int r = consensus_cache_entry_get_body(entry,
+ &spooled->cce_body,
+ &spooled->cce_len);
+ if (r == 0) {
+ return spooled;
+ } else {
+ spooled_resource_free(spooled);
+ return NULL;
+ }
}
-/** Return the cache-info for identity fingerprint <b>fp</b>, or
- * its extra-info document if <b>extrainfo</b> is true. Return
- * NULL if not found or if the descriptor is older than
- * <b>publish_cutoff</b>. */
-static const signed_descriptor_t *
-get_signed_descriptor_by_fp(const char *fp, int extrainfo,
- time_t publish_cutoff)
+/** Release all storage held by <b>spooled</b>. */
+void
+spooled_resource_free(spooled_resource_t *spooled)
{
- if (router_digest_is_me(fp)) {
- if (extrainfo)
- return &(router_get_my_extrainfo()->cache_info);
- else
- return &(router_get_my_routerinfo()->cache_info);
- } else {
- const routerinfo_t *ri = router_get_by_id_digest(fp);
- if (ri &&
- ri->cache_info.published_on > publish_cutoff) {
- if (extrainfo)
- return extrainfo_get_by_descriptor_digest(
- ri->cache_info.extra_info_digest);
- else
- return &ri->cache_info;
- }
+ if (spooled == NULL)
+ return;
+
+ if (spooled->cached_dir_ref) {
+ cached_dir_decref(spooled->cached_dir_ref);
}
- return NULL;
-}
-/** Return true iff we have any of the documents (extrainfo or routerdesc)
- * specified by the fingerprints in <b>fps</b> and <b>spool_src</b>. Used to
- * decide whether to send a 404. */
-int
-dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src)
-{
- time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH;
- SMARTLIST_FOREACH_BEGIN(fps, const char *, fp) {
- switch (spool_src)
- {
- case DIR_SPOOL_EXTRA_BY_DIGEST:
- if (extrainfo_get_by_descriptor_digest(fp)) return 1;
- break;
- case DIR_SPOOL_SERVER_BY_DIGEST:
- if (router_get_by_descriptor_digest(fp)) return 1;
- break;
- case DIR_SPOOL_EXTRA_BY_FP:
- case DIR_SPOOL_SERVER_BY_FP:
- if (get_signed_descriptor_by_fp(fp,
- spool_src == DIR_SPOOL_EXTRA_BY_FP, publish_cutoff))
- return 1;
- break;
- }
- } SMARTLIST_FOREACH_END(fp);
- return 0;
+ if (spooled->consensus_cache_entry) {
+ consensus_cache_entry_decref(spooled->consensus_cache_entry);
+ }
+
+ tor_free(spooled);
}
-/** Return true iff any of the 256-bit elements in <b>fps</b> is the digest of
- * a microdescriptor we have. */
-int
-dirserv_have_any_microdesc(const smartlist_t *fps)
+/** When spooling data from a cached_dir_t object, we always add
+ * at least this much. */
+#define DIRSERV_CACHED_DIR_CHUNK_SIZE 8192
+
+/** Return an compression ratio for compressing objects from <b>source</b>.
+ */
+static double
+estimate_compression_ratio(dir_spool_source_t source)
{
- microdesc_cache_t *cache = get_microdesc_cache();
- SMARTLIST_FOREACH(fps, const char *, fp,
- if (microdesc_cache_lookup_by_digest256(cache, fp))
- return 1);
- return 0;
+ /* We should put in better estimates here, depending on the number of
+ objects and their type */
+ (void) source;
+ return 0.5;
}
-/** Return an approximate estimate of the number of bytes that will
- * be needed to transmit the server descriptors (if is_serverdescs --
- * they can be either d/ or fp/ queries) or networkstatus objects (if
- * !is_serverdescs) listed in <b>fps</b>. If <b>compressed</b> is set,
- * we guess how large the data will be after compression.
+/** Return an estimated number of bytes needed for transmitting the
+ * resource in <b>spooled</b> on <b>conn</b>
*
- * The return value is an estimate; it might be larger or smaller.
- **/
-size_t
-dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs,
- int compressed)
-{
- size_t result;
- tor_assert(fps);
- if (is_serverdescs) {
- int n = smartlist_len(fps);
- const routerinfo_t *me = router_get_my_routerinfo();
- result = (me?me->cache_info.signed_descriptor_len:2048) * n;
- if (compressed)
- result /= 2; /* observed compressibility is between 35 and 55%. */
+ * As a convenient side-effect, set *<b>published_out</b> to the resource's
+ * publication time.
+ */
+static size_t
+spooled_resource_estimate_size(const spooled_resource_t *spooled,
+ dir_connection_t *conn,
+ int compressed,
+ time_t *published_out)
+{
+ if (spooled->spool_eagerly) {
+ const uint8_t *body = NULL;
+ size_t bodylen = 0;
+ int r = spooled_resource_lookup_body(spooled,
+ connection_dir_is_encrypted(conn),
+ &body, &bodylen,
+ published_out);
+ if (r == -1 || body == NULL || bodylen == 0)
+ return 0;
+ if (compressed) {
+ double ratio = estimate_compression_ratio(spooled->spool_source);
+ bodylen = (size_t)(bodylen * ratio);
+ }
+ return bodylen;
} else {
- result = 0;
- SMARTLIST_FOREACH(fps, const char *, digest, {
- cached_dir_t *dir = lookup_cached_dir_by_fp(digest);
- if (dir)
- result += compressed ? dir->dir_z_len : dir->dir_len;
- });
+ cached_dir_t *cached;
+ if (spooled->consensus_cache_entry) {
+ if (published_out) {
+ consensus_cache_entry_get_valid_after(
+ spooled->consensus_cache_entry, published_out);
+ }
+
+ return spooled->cce_len;
+ }
+ if (spooled->cached_dir_ref) {
+ cached = spooled->cached_dir_ref;
+ } else {
+ cached = spooled_resource_lookup_cached_dir(spooled,
+ published_out);
+ }
+ if (cached == NULL) {
+ return 0;
+ }
+ size_t result = compressed ? cached->dir_compressed_len : cached->dir_len;
+ return result;
}
- return result;
}
-/** Given a list of microdescriptor hashes, guess how many bytes will be
- * needed to transmit them, and return the guess. */
-size_t
-dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed)
-{
- size_t result = smartlist_len(fps) * microdesc_average_size(NULL);
- if (compressed)
- result /= 2;
- return result;
-}
+/** Return code for spooled_resource_flush_some */
+typedef enum {
+ SRFS_ERR = -1,
+ SRFS_MORE = 0,
+ SRFS_DONE
+} spooled_resource_flush_status_t;
-/** When we're spooling data onto our outbuf, add more whenever we dip
- * below this threshold. */
-#define DIRSERV_BUFFER_MIN 16384
+/** Flush some or all of the bytes from <b>spooled</b> onto <b>conn</b>.
+ * Return SRFS_ERR on error, SRFS_MORE if there are more bytes to flush from
+ * this spooled resource, or SRFS_DONE if we are done flushing this spooled
+ * resource.
+ */
+static spooled_resource_flush_status_t
+spooled_resource_flush_some(spooled_resource_t *spooled,
+ dir_connection_t *conn)
+{
+ if (spooled->spool_eagerly) {
+ /* Spool_eagerly resources are sent all-at-once. */
+ const uint8_t *body = NULL;
+ size_t bodylen = 0;
+ int r = spooled_resource_lookup_body(spooled,
+ connection_dir_is_encrypted(conn),
+ &body, &bodylen, NULL);
+ if (r == -1 || body == NULL || bodylen == 0) {
+ /* Absent objects count as "done". */
+ return SRFS_DONE;
+ }
+ if (conn->compress_state) {
+ connection_write_to_buf_compress((const char*)body, bodylen, conn, 0);
+ } else {
+ connection_write_to_buf((const char*)body, bodylen, TO_CONN(conn));
+ }
+ return SRFS_DONE;
+ } else {
+ cached_dir_t *cached = spooled->cached_dir_ref;
+ consensus_cache_entry_t *cce = spooled->consensus_cache_entry;
+ if (cached == NULL && cce == NULL) {
+ /* The cached_dir_t hasn't been materialized yet. So let's look it up. */
+ cached = spooled->cached_dir_ref =
+ spooled_resource_lookup_cached_dir(spooled, NULL);
+ if (!cached) {
+ /* Absent objects count as done. */
+ return SRFS_DONE;
+ }
+ ++cached->refcnt;
+ tor_assert_nonfatal(spooled->cached_dir_offset == 0);
+ }
-/** Spooling helper: called when we have no more data to spool to <b>conn</b>.
- * Flushes any remaining data to be (un)compressed, and changes the spool
- * source to NONE. Returns 0 on success, negative on failure. */
-static int
-connection_dirserv_finish_spooling(dir_connection_t *conn)
-{
- if (conn->zlib_state) {
- connection_write_to_buf_zlib("", 0, conn, 1);
- tor_zlib_free(conn->zlib_state);
- conn->zlib_state = NULL;
+ if (BUG(!cached && !cce))
+ return SRFS_DONE;
+
+ int64_t total_len;
+ const char *ptr;
+ if (cached) {
+ total_len = cached->dir_compressed_len;
+ ptr = cached->dir_compressed;
+ } else {
+ total_len = spooled->cce_len;
+ ptr = (const char *)spooled->cce_body;
+ }
+ /* How many bytes left to flush? */
+ int64_t remaining;
+ remaining = total_len - spooled->cached_dir_offset;
+ if (BUG(remaining < 0))
+ return SRFS_ERR;
+ ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining);
+ if (conn->compress_state) {
+ connection_write_to_buf_compress(
+ ptr + spooled->cached_dir_offset,
+ bytes, conn, 0);
+ } else {
+ connection_write_to_buf(ptr + spooled->cached_dir_offset,
+ bytes, TO_CONN(conn));
+ }
+ spooled->cached_dir_offset += bytes;
+ if (spooled->cached_dir_offset >= (off_t)total_len) {
+ return SRFS_DONE;
+ } else {
+ return SRFS_MORE;
+ }
}
- conn->dir_spool_src = DIR_SPOOL_NONE;
- return 0;
}
-/** Spooling helper: called when we're sending a bunch of server descriptors,
- * and the outbuf has become too empty. Pulls some entries from
- * fingerprint_stack, and writes the corresponding servers onto outbuf. If we
- * run out of entries, flushes the zlib state and sets the spool source to
- * NONE. Returns 0 on success, negative on failure.
+/** Helper: find the cached_dir_t for a spooled_resource_t, for
+ * sending it to <b>conn</b>. Set *<b>published_out</b>, if provided,
+ * to the published time of the cached_dir_t.
+ *
+ * DOES NOT increase the reference count on the result. Callers must do that
+ * themselves if they mean to hang on to it.
*/
+static cached_dir_t *
+spooled_resource_lookup_cached_dir(const spooled_resource_t *spooled,
+ time_t *published_out)
+{
+ tor_assert(spooled->spool_eagerly == 0);
+ cached_dir_t *d = lookup_cached_dir_by_fp(spooled->digest);
+ if (d != NULL) {
+ if (published_out)
+ *published_out = d->published;
+ }
+ return d;
+}
+
+/** Helper: Look up the body for an eagerly-served spooled_resource. If
+ * <b>conn_is_encrypted</b> is false, don't look up any resource that
+ * shouldn't be sent over an unencrypted connection. On success, set
+ * <b>body_out</b>, <b>size_out</b>, and <b>published_out</b> to refer
+ * to the resource's body, size, and publication date, and return 0.
+ * On failure return -1. */
static int
-connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn)
+spooled_resource_lookup_body(const spooled_resource_t *spooled,
+ int conn_is_encrypted,
+ const uint8_t **body_out,
+ size_t *size_out,
+ time_t *published_out)
{
- int by_fp = (conn->dir_spool_src == DIR_SPOOL_SERVER_BY_FP ||
- conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP);
- int extra = (conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP ||
- conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_DIGEST);
- time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH;
+ tor_assert(spooled->spool_eagerly == 1);
- const or_options_t *options = get_options();
+ const signed_descriptor_t *sd = NULL;
- while (smartlist_len(conn->fingerprint_stack) &&
- connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) {
- const char *body;
- char *fp = smartlist_pop_last(conn->fingerprint_stack);
- const signed_descriptor_t *sd = NULL;
- if (by_fp) {
- sd = get_signed_descriptor_by_fp(fp, extra, publish_cutoff);
- } else {
- sd = extra ? extrainfo_get_by_descriptor_digest(fp)
- : router_get_by_descriptor_digest(fp);
+ switch (spooled->spool_source) {
+ case DIR_SPOOL_EXTRA_BY_FP: {
+ sd = get_signed_descriptor_by_fp(spooled->digest, 1);
+ break;
}
- tor_free(fp);
- if (!sd)
- continue;
- if (!connection_dir_is_encrypted(conn) && !sd->send_unencrypted) {
- /* we did this check once before (so we could have an accurate size
- * estimate and maybe send a 404 if somebody asked for only bridges on a
- * connection), but we need to do it again in case a previously
- * unknown bridge descriptor has shown up between then and now. */
- continue;
+ case DIR_SPOOL_SERVER_BY_FP: {
+ sd = get_signed_descriptor_by_fp(spooled->digest, 0);
+ break;
}
-
- /** If we are the bridge authority and the descriptor is a bridge
- * descriptor, remember that we served this descriptor for desc stats. */
- if (options->BridgeAuthoritativeDir && by_fp) {
- const routerinfo_t *router =
- router_get_by_id_digest(sd->identity_digest);
- /* router can be NULL here when the bridge auth is asked for its own
- * descriptor. */
- if (router && router->purpose == ROUTER_PURPOSE_BRIDGE)
- rep_hist_note_desc_served(sd->identity_digest);
- }
- body = signed_descriptor_get_body(sd);
- if (conn->zlib_state) {
- int last = ! smartlist_len(conn->fingerprint_stack);
- connection_write_to_buf_zlib(body, sd->signed_descriptor_len, conn,
- last);
- if (last) {
- tor_zlib_free(conn->zlib_state);
- conn->zlib_state = NULL;
+ case DIR_SPOOL_SERVER_BY_DIGEST: {
+ sd = router_get_by_descriptor_digest((const char *)spooled->digest);
+ break;
+ }
+ case DIR_SPOOL_EXTRA_BY_DIGEST: {
+ sd = extrainfo_get_by_descriptor_digest((const char *)spooled->digest);
+ break;
+ }
+ case DIR_SPOOL_MICRODESC: {
+ microdesc_t *md = microdesc_cache_lookup_by_digest256(
+ get_microdesc_cache(),
+ (const char *)spooled->digest);
+ if (! md || ! md->body) {
+ return -1;
}
- } else {
- connection_write_to_buf(body,
- sd->signed_descriptor_len,
- TO_CONN(conn));
+ *body_out = (const uint8_t *)md->body;
+ *size_out = md->bodylen;
+ if (published_out)
+ *published_out = TIME_MAX;
+ return 0;
}
+ case DIR_SPOOL_NETWORKSTATUS:
+ case DIR_SPOOL_CONSENSUS_CACHE_ENTRY:
+ default:
+ /* LCOV_EXCL_START */
+ tor_assert_nonfatal_unreached();
+ return -1;
+ /* LCOV_EXCL_STOP */
}
- if (!smartlist_len(conn->fingerprint_stack)) {
- /* We just wrote the last one; finish up. */
- if (conn->zlib_state) {
- connection_write_to_buf_zlib("", 0, conn, 1);
- tor_zlib_free(conn->zlib_state);
- conn->zlib_state = NULL;
- }
- conn->dir_spool_src = DIR_SPOOL_NONE;
- smartlist_free(conn->fingerprint_stack);
- conn->fingerprint_stack = NULL;
+ /* If we get here, then we tried to set "sd" to a signed_descriptor_t. */
+
+ if (sd == NULL) {
+ return -1;
}
+ if (sd->send_unencrypted == 0 && ! conn_is_encrypted) {
+ /* we did this check once before (so we could have an accurate size
+ * estimate and maybe send a 404 if somebody asked for only bridges on
+ * a connection), but we need to do it again in case a previously
+ * unknown bridge descriptor has shown up between then and now. */
+ return -1;
+ }
+ *body_out = (const uint8_t *) signed_descriptor_get_body(sd);
+ *size_out = sd->signed_descriptor_len;
+ if (published_out)
+ *published_out = sd->published_on;
return 0;
}
-/** Spooling helper: called when we're sending a bunch of microdescriptors,
- * and the outbuf has become too empty. Pulls some entries from
- * fingerprint_stack, and writes the corresponding microdescs onto outbuf. If
- * we run out of entries, flushes the zlib state and sets the spool source to
- * NONE. Returns 0 on success, negative on failure.
- */
-static int
-connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn)
-{
- microdesc_cache_t *cache = get_microdesc_cache();
- while (smartlist_len(conn->fingerprint_stack) &&
- connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) {
- char *fp256 = smartlist_pop_last(conn->fingerprint_stack);
- microdesc_t *md = microdesc_cache_lookup_by_digest256(cache, fp256);
- tor_free(fp256);
- if (!md || !md->body)
- continue;
- if (conn->zlib_state) {
- int last = !smartlist_len(conn->fingerprint_stack);
- connection_write_to_buf_zlib(md->body, md->bodylen, conn, last);
- if (last) {
- tor_zlib_free(conn->zlib_state);
- conn->zlib_state = NULL;
- }
- } else {
- connection_write_to_buf(md->body, md->bodylen, TO_CONN(conn));
- }
- }
- if (!smartlist_len(conn->fingerprint_stack)) {
- if (conn->zlib_state) {
- connection_write_to_buf_zlib("", 0, conn, 1);
- tor_zlib_free(conn->zlib_state);
- conn->zlib_state = NULL;
- }
- conn->dir_spool_src = DIR_SPOOL_NONE;
- smartlist_free(conn->fingerprint_stack);
- conn->fingerprint_stack = NULL;
+/** Given a fingerprint <b>fp</b> which is either set if we're looking for a
+ * v2 status, or zeroes if we're looking for a v3 status, or a NUL-padded
+ * flavor name if we want a flavored v3 status, return a pointer to the
+ * appropriate cached dir object, or NULL if there isn't one available. */
+static cached_dir_t *
+lookup_cached_dir_by_fp(const uint8_t *fp)
+{
+ cached_dir_t *d = NULL;
+ if (tor_digest_is_zero((const char *)fp) && cached_consensuses) {
+ d = strmap_get(cached_consensuses, "ns");
+ } else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses) {
+ /* this here interface is a nasty hack: we're shoving a flavor into
+ * a digest field. */
+ d = strmap_get(cached_consensuses, (const char *)fp);
}
- return 0;
+ return d;
}
-/** Spooling helper: Called when we're sending a directory or networkstatus,
- * and the outbuf has become too empty. Pulls some bytes from
- * <b>conn</b>-\>cached_dir-\>dir_z, uncompresses them if appropriate, and
- * puts them on the outbuf. If we run out of entries, flushes the zlib state
- * and sets the spool source to NONE. Returns 0 on success, negative on
- * failure. */
-static int
-connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn)
-{
- ssize_t bytes;
- int64_t remaining;
-
- bytes = DIRSERV_BUFFER_MIN - connection_get_outbuf_len(TO_CONN(conn));
- tor_assert(bytes > 0);
- tor_assert(conn->cached_dir);
- if (bytes < 8192)
- bytes = 8192;
- remaining = conn->cached_dir->dir_z_len - conn->cached_dir_offset;
- if (bytes > remaining)
- bytes = (ssize_t) remaining;
-
- if (conn->zlib_state) {
- connection_write_to_buf_zlib(
- conn->cached_dir->dir_z + conn->cached_dir_offset,
- bytes, conn, bytes == remaining);
- } else {
- connection_write_to_buf(conn->cached_dir->dir_z + conn->cached_dir_offset,
- bytes, TO_CONN(conn));
+/** Try to guess the number of bytes that will be needed to send the
+ * spooled objects for <b>conn</b>'s outgoing spool. In the process,
+ * remove every element of the spool that refers to an absent object, or
+ * which was published earlier than <b>cutoff</b>. Set *<b>size_out</b>
+ * to the number of bytes, and *<b>n_expired_out</b> to the number of
+ * objects removed for being too old. */
+void
+dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn,
+ time_t cutoff,
+ int compression,
+ size_t *size_out,
+ int *n_expired_out)
+{
+ if (BUG(!conn))
+ return;
+
+ smartlist_t *spool = conn->spool;
+ if (!spool) {
+ if (size_out)
+ *size_out = 0;
+ if (n_expired_out)
+ *n_expired_out = 0;
+ return;
}
- conn->cached_dir_offset += bytes;
- if (conn->cached_dir_offset == (int)conn->cached_dir->dir_z_len) {
- /* We just wrote the last one; finish up. */
- connection_dirserv_finish_spooling(conn);
- cached_dir_decref(conn->cached_dir);
- conn->cached_dir = NULL;
+ int n_expired = 0;
+ uint64_t total = 0;
+ SMARTLIST_FOREACH_BEGIN(spool, spooled_resource_t *, spooled) {
+ time_t published = TIME_MAX;
+ size_t sz = spooled_resource_estimate_size(spooled, conn,
+ compression, &published);
+ if (published < cutoff) {
+ ++n_expired;
+ SMARTLIST_DEL_CURRENT(spool, spooled);
+ spooled_resource_free(spooled);
+ } else if (sz == 0) {
+ SMARTLIST_DEL_CURRENT(spool, spooled);
+ spooled_resource_free(spooled);
+ } else {
+ total += sz;
+ }
+ } SMARTLIST_FOREACH_END(spooled);
+
+ if (size_out) {
+ *size_out = (total > SIZE_MAX) ? SIZE_MAX : (size_t)total;
}
- return 0;
+ if (n_expired_out)
+ *n_expired_out = n_expired;
}
-/** Spooling helper: Called when we're spooling networkstatus objects on
- * <b>conn</b>, and the outbuf has become too empty. If the current
- * networkstatus object (in <b>conn</b>-\>cached_dir) has more data, pull data
- * from there. Otherwise, pop the next fingerprint from fingerprint_stack,
- * and start spooling the next networkstatus. (A digest of all 0 bytes is
- * treated as a request for the current consensus.) If we run out of entries,
- * flushes the zlib state and sets the spool source to NONE. Returns 0 on
- * success, negative on failure. */
+/** Helper: used to sort a connection's spool. */
static int
-connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn)
-{
-
- while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) {
- if (conn->cached_dir) {
- int uncompressing = (conn->zlib_state != NULL);
- int r = connection_dirserv_add_dir_bytes_to_outbuf(conn);
- if (conn->dir_spool_src == DIR_SPOOL_NONE) {
- /* add_dir_bytes thinks we're done with the cached_dir. But we
- * may have more cached_dirs! */
- conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS;
- /* This bit is tricky. If we were uncompressing the last
- * networkstatus, we may need to make a new zlib object to
- * uncompress the next one. */
- if (uncompressing && ! conn->zlib_state &&
- conn->fingerprint_stack &&
- smartlist_len(conn->fingerprint_stack)) {
- conn->zlib_state = tor_zlib_new(0, ZLIB_METHOD, HIGH_COMPRESSION);
- }
- }
- if (r) return r;
- } else if (conn->fingerprint_stack &&
- smartlist_len(conn->fingerprint_stack)) {
- /* Add another networkstatus; start serving it. */
- char *fp = smartlist_pop_last(conn->fingerprint_stack);
- cached_dir_t *d = lookup_cached_dir_by_fp(fp);
- tor_free(fp);
- if (d) {
- ++d->refcnt;
- conn->cached_dir = d;
- conn->cached_dir_offset = 0;
- }
- } else {
- connection_dirserv_finish_spooling(conn);
- smartlist_free(conn->fingerprint_stack);
- conn->fingerprint_stack = NULL;
- return 0;
+dirserv_spool_sort_comparison_(const void **a_, const void **b_)
+{
+ const spooled_resource_t *a = *a_;
+ const spooled_resource_t *b = *b_;
+ return fast_memcmp(a->digest, b->digest, sizeof(a->digest));
+}
+
+/** Sort all the entries in <b>conn</b> by digest. */
+void
+dirserv_spool_sort(dir_connection_t *conn)
+{
+ if (conn->spool == NULL)
+ return;
+ smartlist_sort(conn->spool, dirserv_spool_sort_comparison_);
+}
+
+/** Return the cache-info for identity fingerprint <b>fp</b>, or
+ * its extra-info document if <b>extrainfo</b> is true. Return
+ * NULL if not found or if the descriptor is older than
+ * <b>publish_cutoff</b>. */
+static const signed_descriptor_t *
+get_signed_descriptor_by_fp(const uint8_t *fp, int extrainfo)
+{
+ if (router_digest_is_me((const char *)fp)) {
+ if (extrainfo)
+ return &(router_get_my_extrainfo()->cache_info);
+ else
+ return &(router_get_my_routerinfo()->cache_info);
+ } else {
+ const routerinfo_t *ri = router_get_by_id_digest((const char *)fp);
+ if (ri) {
+ if (extrainfo)
+ return extrainfo_get_by_descriptor_digest(
+ ri->cache_info.extra_info_digest);
+ else
+ return &ri->cache_info;
}
}
- return 0;
+ return NULL;
}
-/** Called whenever we have flushed some directory data in state
- * SERVER_WRITING. */
+/** When we're spooling data onto our outbuf, add more whenever we dip
+ * below this threshold. */
+#define DIRSERV_BUFFER_MIN 16384
+
+/**
+ * Called whenever we have flushed some directory data in state
+ * SERVER_WRITING, or whenever we want to fill the buffer with initial
+ * directory data (so that subsequent writes will occur, and trigger this
+ * function again.)
+ *
+ * Return 0 on success, and -1 on failure.
+ */
int
connection_dirserv_flushed_some(dir_connection_t *conn)
{
tor_assert(conn->base_.state == DIR_CONN_STATE_SERVER_WRITING);
-
- if (connection_get_outbuf_len(TO_CONN(conn)) >= DIRSERV_BUFFER_MIN)
+ if (conn->spool == NULL)
return 0;
- switch (conn->dir_spool_src) {
- case DIR_SPOOL_EXTRA_BY_DIGEST:
- case DIR_SPOOL_EXTRA_BY_FP:
- case DIR_SPOOL_SERVER_BY_DIGEST:
- case DIR_SPOOL_SERVER_BY_FP:
- return connection_dirserv_add_servers_to_outbuf(conn);
- case DIR_SPOOL_MICRODESC:
- return connection_dirserv_add_microdescs_to_outbuf(conn);
- case DIR_SPOOL_CACHED_DIR:
- return connection_dirserv_add_dir_bytes_to_outbuf(conn);
- case DIR_SPOOL_NETWORKSTATUS:
- return connection_dirserv_add_networkstatus_bytes_to_outbuf(conn);
- case DIR_SPOOL_NONE:
- default:
+ while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN &&
+ smartlist_len(conn->spool)) {
+ spooled_resource_t *spooled =
+ smartlist_get(conn->spool, smartlist_len(conn->spool)-1);
+ spooled_resource_flush_status_t status;
+ status = spooled_resource_flush_some(spooled, conn);
+ if (status == SRFS_ERR) {
+ return -1;
+ } else if (status == SRFS_MORE) {
return 0;
+ }
+ tor_assert(status == SRFS_DONE);
+
+ /* If we're here, we're done flushing this resource. */
+ tor_assert(smartlist_pop_last(conn->spool) == spooled);
+ spooled_resource_free(spooled);
}
+
+ if (smartlist_len(conn->spool) > 0) {
+ /* We're still spooling something. */
+ return 0;
+ }
+
+ /* If we get here, we're done. */
+ smartlist_free(conn->spool);
+ conn->spool = NULL;
+ if (conn->compress_state) {
+ /* Flush the compression state: there could be more bytes pending in there,
+ * and we don't want to omit bytes. */
+ connection_write_to_buf_compress("", 0, conn, 1);
+ tor_compress_free(conn->compress_state);
+ conn->compress_state = NULL;
+ }
+ return 0;
+}
+
+/** Remove every element from <b>conn</b>'s outgoing spool, and delete
+ * the spool. */
+void
+dir_conn_clear_spool(dir_connection_t *conn)
+{
+ if (!conn || ! conn->spool)
+ return;
+ SMARTLIST_FOREACH(conn->spool, spooled_resource_t *, s,
+ spooled_resource_free(s));
+ smartlist_free(conn->spool);
+ conn->spool = NULL;
}
/** Return true iff <b>line</b> is a valid RecommendedPackages line.
diff --git a/src/or/dirserv.h b/src/or/dirserv.h
index 3e735db071..480174d5bb 100644
--- a/src/or/dirserv.h
+++ b/src/or/dirserv.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -32,6 +32,61 @@
/** Maximum allowable length of a version line in a networkstatus. */
#define MAX_V_LINE_LEN 128
+/** Ways to convert a spoolable_resource_t to a bunch of bytes. */
+typedef enum dir_spool_source_t {
+ DIR_SPOOL_SERVER_BY_DIGEST=1, DIR_SPOOL_SERVER_BY_FP,
+ DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP,
+ DIR_SPOOL_MICRODESC,
+ DIR_SPOOL_NETWORKSTATUS,
+ DIR_SPOOL_CONSENSUS_CACHE_ENTRY,
+} dir_spool_source_t;
+#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t)
+
+/** Object to remember the identity of an object that we are spooling,
+ * or about to spool, in response to a directory request.
+ *
+ * (Why do we spool? Because some directory responses are very large,
+ * and we don't want to just shove the complete answer into the output
+ * buffer: that would take a ridiculous amount of RAM.)
+ *
+ * If the spooled resource is relatively small (like microdescriptors,
+ * descriptors, etc), we look them up by ID as needed, and add the whole
+ * thing onto the output buffer at once. If the spooled reseource is
+ * big (like networkstatus documents), we reference-count it, and add it
+ * a few K at a time.
+ */
+typedef struct spooled_resource_t {
+ /**
+ * If true, we add the entire object to the outbuf. If false,
+ * we spool the object a few K at a time.
+ */
+ unsigned spool_eagerly : 1;
+ /**
+ * Tells us what kind of object to get, and how to look it up.
+ */
+ dir_spool_source_bitfield_t spool_source : 7;
+ /**
+ * Tells us the specific object to spool.
+ */
+ uint8_t digest[DIGEST256_LEN];
+ /**
+ * A large object that we're spooling. Holds a reference count. Only
+ * used when spool_eagerly is false.
+ */
+ struct cached_dir_t *cached_dir_ref;
+ /**
+ * A different kind of large object that we might be spooling. Also
+ * reference-counted. Also only used when spool_eagerly is false.
+ */
+ struct consensus_cache_entry_t *consensus_cache_entry;
+ const uint8_t *cce_body;
+ size_t cce_len;
+ /**
+ * The current offset into cached_dir or cce_body. Only used when
+ * spool_eagerly is false */
+ off_t cached_dir_offset;
+} spooled_resource_t;
+
int connection_dirserv_flushed_some(dir_connection_t *conn);
int dirserv_add_own_fingerprint(crypto_pk_t *pk);
@@ -47,10 +102,8 @@ 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(const smartlist_t *routers);
+void dirserv_compute_bridge_flag_thresholds(void);
int directory_fetches_from_authorities(const or_options_t *options);
int directory_fetches_dir_info_early(const or_options_t *options);
@@ -65,17 +118,19 @@ cached_dir_t *dirserv_get_consensus(const char *flavor_name);
void dirserv_set_cached_consensus_networkstatus(const char *consensus,
const char *flavor_name,
const common_digests_t *digests,
+ const uint8_t *sha3_as_signed,
time_t published);
void dirserv_clear_old_networkstatuses(time_t cutoff);
-int dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key,
- const char **msg,
- int for_unencrypted_conn,
- int is_extrainfo);
+int dirserv_get_routerdesc_spool(smartlist_t *spools_out, const char *key,
+ dir_spool_source_t source,
+ int conn_is_encrytped,
+ const char **msg_out);
int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
const char **msg);
void dirserv_orconn_tls_done(const tor_addr_t *addr,
uint16_t or_port,
- const char *digest_rcvd);
+ const char *digest_rcvd,
+ const ed25519_public_key_t *ed_id_rcvd);
int dirserv_should_launch_reachability_test(const routerinfo_t *ri,
const routerinfo_t *ri_old);
void dirserv_single_reachability_test(time_t now, routerinfo_t *router);
@@ -90,15 +145,10 @@ void dirserv_set_node_flags_from_authoritative_status(node_t *node,
uint32_t authstatus);
int dirserv_would_reject_router(const routerstatus_t *rs);
-int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff);
-int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src);
-int dirserv_have_any_microdesc(const smartlist_t *fps);
-size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs,
- int compressed);
-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);
@@ -140,5 +190,19 @@ int dirserv_read_measured_bandwidths(const char *from_file,
int dirserv_read_guardfraction_file(const char *fname,
smartlist_t *vote_routerstatuses);
+spooled_resource_t *spooled_resource_new(dir_spool_source_t source,
+ const uint8_t *digest,
+ size_t digestlen);
+spooled_resource_t *spooled_resource_new_from_cache_entry(
+ struct consensus_cache_entry_t *entry);
+void spooled_resource_free(spooled_resource_t *spooled);
+void dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn,
+ time_t cutoff,
+ int compression,
+ size_t *size_out,
+ int *n_expired_out);
+void dirserv_spool_sort(dir_connection_t *conn);
+void dir_conn_clear_spool(dir_connection_t *conn);
+
#endif
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index 9854af7d7f..c65945fea7 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define DIRVOTE_PRIVATE
@@ -13,16 +13,52 @@
#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
* \brief Functions to compute directory consensus, and schedule voting.
+ *
+ * This module is the center of the consensus-voting based directory
+ * authority system. With this system, a set of authorities first
+ * publish vote based on their opinions of the network, and then compute
+ * a consensus from those votes. Each authority signs the consensus,
+ * and clients trust the consensus if enough known authorities have
+ * signed it.
+ *
+ * The code in this module is only invoked on directory authorities. It's
+ * responsible for:
+ *
+ * <ul>
+ * <li>Generating this authority's vote networkstatus, based on the
+ * authority's view of the network as represented in dirserv.c
+ * <li>Formatting the vote networkstatus objects.
+ * <li>Generating the microdescriptors that correspond to our own
+ * vote.
+ * <li>Sending votes to all the other authorities.
+ * <li>Trying to fetch missing votes from other authorities.
+ * <li>Computing the consensus from a set of votes, as well as
+ * a "detached signature" object for other authorities to fetch.
+ * <li>Collecting other authorities' signatures on the same consensus,
+ * until there are enough.
+ * <li>Publishing the consensus to the reset of the directory system.
+ * <li>Scheduling all of the above operations.
+ * </ul>
+ *
+ * The main entry points are in dirvote_act(), which handles scheduled
+ * actions; and dirvote_add_vote() and dirvote_add_signatures(), which
+ * handle uploaded and downloaded votes and signatures.
+ *
+ * (See dir-spec.txt from torspec.git for a complete specification of
+ * the directory protocol and voting algorithms.)
**/
/** A consensus that we have built and are appending signatures to. Once it's
@@ -59,6 +95,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 +155,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 +174,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 +194,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 +231,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,16 +277,17 @@ 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);
for (h = vrs->microdesc; h; h = h->next) {
- smartlist_add(chunks, tor_strdup(h->microdesc_hash_line));
+ smartlist_add_strdup(chunks, h->microdesc_hash_line);
}
} SMARTLIST_FOREACH_END(vrs);
- smartlist_add(chunks, tor_strdup("directory-footer\n"));
+ smartlist_add_strdup(chunks, "directory-footer\n");
/* The digest includes everything up through the space after
* directory-signature. (Yuck.) */
@@ -215,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
signing_key_fingerprint);
}
- note_crypto_pk_op(SIGN_DIR);
{
char *sig = router_get_dirobj_signature(digest, DIGEST_LEN,
private_signing_key);
@@ -247,6 +337,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 +453,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 +712,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 {
+ tor_assert_nonfatal(n_found == 0);
+ 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 +761,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 +782,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 +830,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) \
@@ -794,7 +926,7 @@ networkstatus_check_weights(int64_t Wgg, int64_t Wgd, int64_t Wmg,
*
* It returns true if weights could be computed, false otherwise.
*/
-static int
+int
networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
int64_t M, int64_t E, int64_t D,
int64_t T, int64_t weight_scale)
@@ -815,7 +947,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
@@ -876,7 +1008,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
Wgd = weight_scale;
}
} else { // Subcase b: R+D >= S
- casename = "Case 2b1 (Wgg=1, Wmd=Wgd)";
+ casename = "Case 2b1 (Wgg=weight_scale, Wmd=Wgd)";
Wee = (weight_scale*(E - G + M))/E;
Wed = (weight_scale*(D - 2*E + 4*G - 2*M))/(3*D);
Wme = (weight_scale*(G-M))/E;
@@ -889,7 +1021,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
weight_scale, G, M, E, D, T, 10, 1);
if (berr) {
- casename = "Case 2b2 (Wgg=1, Wee=1)";
+ casename = "Case 2b2 (Wgg=weight_scale, Wee=weight_scale)";
Wgg = weight_scale;
Wee = weight_scale;
Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
@@ -958,7 +1090,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
} else { // Subcase b: S+D >= T/3
// D != 0 because S+D >= T/3
if (G < E) {
- casename = "Case 3bg (G scarce, Wgg=1, Wmd == Wed)";
+ casename = "Case 3bg (G scarce, Wgg=weight_scale, Wmd == Wed)";
Wgg = weight_scale;
Wgd = (weight_scale*(D - 2*G + E + M))/(3*D);
Wmg = 0;
@@ -970,7 +1102,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
Wed, weight_scale, G, M, E, D, T, 10, 1);
} else { // G >= E
- casename = "Case 3be (E scarce, Wee=1, Wmd == Wgd)";
+ casename = "Case 3be (E scarce, Wee=weight_scale, Wmd == Wgd)";
Wee = weight_scale;
Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
Wme = 0;
@@ -1004,7 +1136,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
tor_assert(0 < weight_scale && weight_scale <= INT32_MAX);
/*
- * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine
+ * Provide Wgm=Wgg, Wmm=weight_scale, Wem=Wee, Weg=Wed. May later determine
* that middle nodes need different bandwidth weights for dirport traffic,
* or that weird exit policies need special weight, or that bridges
* need special weight.
@@ -1114,6 +1246,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
@@ -1121,7 +1319,17 @@ update_total_bandwidth_weights(const routerstatus_t *rs,
* value in a newly allocated string.
*
* Note: this function DOES NOT check whether the votes are from
- * recognized authorities. (dirvote_add_vote does that.) */
+ * recognized authorities. (dirvote_add_vote does that.)
+ *
+ * <strong>WATCH OUT</strong>: You need to think before you change the
+ * behavior of this function, or of the functions it calls! If some
+ * authorities compute the consensus with a different algorithm than
+ * others, they will not reach the same result, and they will not all
+ * sign the same thing! If you really need to change the algorithm
+ * here, you should allocate a new "consensus_method" for the new
+ * behavior, and make the new behavior conditional on a new-enough
+ * consensus_method.
+ **/
char *
networkstatus_compute_consensus(smartlist_t *votes,
int total_authorities,
@@ -1140,13 +1348,15 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_t *flags;
const char *flavor_name;
uint32_t max_unmeasured_bw_kb = DEFAULT_MAX_UNMEASURED_BW_KB;
- int64_t G=0, M=0, E=0, D=0, T=0; /* For bandwidth weights */
+ int64_t G, M, E, D, T; /* For bandwidth weights */
const routerstatus_format_type_t rs_format =
flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC;
char *params = NULL;
char *packages = NULL;
int added_weights = 0;
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);
@@ -1170,6 +1380,16 @@ networkstatus_compute_consensus(smartlist_t *votes,
consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD;
}
+ if (consensus_method >= MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE) {
+ /* It's smarter to initialize these weights to 1, so that later on,
+ * we can't accidentally divide by zero. */
+ G = M = E = D = 1;
+ T = 4;
+ } else {
+ /* ...but originally, they were set to zero. */
+ G = M = E = D = T = 0;
+ }
+
/* Compute medians of time-related things, and figure out how many
* routers we might need to talk about. */
{
@@ -1209,7 +1429,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(sv); /* elements get freed later. */
}
SMARTLIST_FOREACH(v->known_flags, const char *, cp,
- smartlist_add(flags, tor_strdup(cp)));
+ smartlist_add_strdup(flags, cp));
} SMARTLIST_FOREACH_END(v);
valid_after = median_time(va_times, n_votes);
fresh_until = median_time(fu_times, n_votes);
@@ -1242,7 +1462,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(combined_client_versions);
if (consensus_method >= MIN_METHOD_FOR_ED25519_ID_VOTING)
- smartlist_add(flags, tor_strdup("NoEdConsensus"));
+ smartlist_add_strdup(flags, "NoEdConsensus");
smartlist_sort_strings(flags);
smartlist_uniq_strings(flags);
@@ -1291,12 +1511,40 @@ networkstatus_compute_consensus(smartlist_t *votes,
tor_free(flaglist);
}
- params = dirvote_compute_params(votes, consensus_method,
- total_authorities);
- if (params) {
- smartlist_add(chunks, tor_strdup("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_strdup(chunks, "params ");
smartlist_add(chunks, params);
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\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. */
@@ -1350,7 +1598,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 +1622,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 +1629,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 +1657,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 +1772,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 +1786,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 +1806,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 +1933,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 +1944,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 +1958,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 +2099,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);
}
@@ -1844,10 +2115,14 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_join_strings(chosen_flags, " ", 0, NULL));
/* Now the version line. */
if (chosen_version) {
- smartlist_add(chunks, tor_strdup("\nv "));
- smartlist_add(chunks, tor_strdup(chosen_version));
+ smartlist_add_strdup(chunks, "\nv ");
+ smartlist_add_strdup(chunks, chosen_version);
+ }
+ smartlist_add_strdup(chunks, "\n");
+ if (chosen_protocol_list &&
+ consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) {
+ smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list);
}
- smartlist_add(chunks, tor_strdup("\n"));
/* Now the weight line. */
if (rs_out.has_bandwidth) {
char *guardfraction_str = NULL;
@@ -1875,7 +2150,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 +2163,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);
@@ -1896,7 +2171,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
}
/* Mark the directory footer region */
- smartlist_add(chunks, tor_strdup("directory-footer\n"));
+ smartlist_add_strdup(chunks, "directory-footer\n");
{
int64_t weight_scale = BW_WEIGHT_SCALE;
@@ -1905,7 +2180,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;
@@ -1947,7 +2222,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
const char *algname = crypto_digest_algorithm_get_name(digest_alg);
char *signature;
- smartlist_add(chunks, tor_strdup("directory-signature "));
+ smartlist_add_strdup(chunks, "directory-signature ");
/* Compute the hash of the chunks. */
crypto_digest_smartlist(digest, digest_len, chunks, "", digest_alg);
@@ -1974,7 +2249,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_add(chunks, signature);
if (legacy_id_key_digest && legacy_signing_key) {
- smartlist_add(chunks, tor_strdup("directory-signature "));
+ smartlist_add_strdup(chunks, "directory-signature ");
base16_encode(fingerprint, sizeof(fingerprint),
legacy_id_key_digest, DIGEST_LEN);
crypto_pk_get_fingerprint(legacy_signing_key,
@@ -2025,6 +2300,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 +2445,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";
}
}
@@ -2285,7 +2562,7 @@ networkstatus_format_signatures(networkstatus_t *consensus,
base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len,
BASE64_ENCODE_MULTILINE);
strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
- smartlist_add(elements, tor_strdup(buf));
+ smartlist_add_strdup(elements, buf);
} SMARTLIST_FOREACH_END(sig);
} SMARTLIST_FOREACH_END(v);
@@ -2363,15 +2640,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 +2787,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 +2856,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 +2864,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 +2937,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 +3219,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 +3279,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 +3327,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 +3360,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 +3372,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 +3409,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;
@@ -3347,8 +3672,8 @@ dirvote_add_signatures(const char *detached_signatures_body,
"Queuing it for the next consensus.", source);
if (!pending_consensus_signature_list)
pending_consensus_signature_list = smartlist_new();
- smartlist_add(pending_consensus_signature_list,
- tor_strdup(detached_signatures_body));
+ smartlist_add_strdup(pending_consensus_signature_list,
+ detached_signatures_body);
*msg = "Signature queued";
return 0;
}
@@ -3373,7 +3698,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 +3840,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);
@@ -3528,10 +3853,11 @@ dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method)
char idbuf[ED25519_BASE64_LEN+1];
const char *keytype;
if (consensus_method >= MIN_METHOD_FOR_ED25519_ID_IN_MD &&
- ri->signing_key_cert &&
- ri->signing_key_cert->signing_key_included) {
+ ri->cache_info.signing_key_cert &&
+ ri->cache_info.signing_key_cert->signing_key_included) {
keytype = "ed25519";
- ed25519_public_to_base64(idbuf, &ri->signing_key_cert->signing_key);
+ ed25519_public_to_base64(idbuf,
+ &ri->cache_info.signing_key_cert->signing_key);
} else {
keytype = "rsa1024";
digest_to_base64(idbuf, ri->cache_info.identity_digest);
diff --git a/src/or/dirvote.h b/src/or/dirvote.h
index 0b1d284060..e342dc78ea 100644
--- a/src/or/dirvote.h
+++ b/src/or/dirvote.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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 26
/** Lowest consensus method where microdesc consensuses omit any entry
* with no microdesc. */
@@ -90,10 +90,31 @@
* 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
+
+/** Lowest consensus method where authorities initialize bandwidth weights to 1
+ * instead of 0. See #14881 */
+#define MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE 26
+
/** 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 +142,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,12 +228,20 @@ 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);
+STATIC int
+networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
+ int64_t M, int64_t E, int64_t D,
+ int64_t T, int64_t weight_scale);
#endif
#endif
diff --git a/src/or/dns.c b/src/or/dns.c
index c7adfbc971..2d642773f3 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2003-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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? */
@@ -177,8 +160,9 @@ evdns_log_cb(int warn, const char *msg)
}
if (!strcmpstart(msg, "Nameserver ") && (cp=strstr(msg, " has failed: "))) {
char *ns = tor_strndup(msg+11, cp-(msg+11));
- const char *err = strchr(cp, ':')+2;
- tor_assert(err);
+ const char *colon = strchr(cp, ':');
+ tor_assert(colon);
+ const char *err = colon+2;
/* Don't warn about a single failed nameserver; we'll warn with 'all
* nameservers have failed' if we're completely out of nameservers;
* otherwise, the situation is tolerable. */
@@ -198,6 +182,18 @@ evdns_log_cb(int warn, const char *msg)
} else if (!strcmp(msg, "All nameservers have failed")) {
control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN");
all_down = 1;
+ } else if (!strcmpstart(msg, "Address mismatch on received DNS")) {
+ static ratelim_t mismatch_limit = RATELIM_INIT(3600);
+ const char *src = strstr(msg, " Apparent source");
+ if (!src || get_options()->SafeLogging) {
+ src = "";
+ }
+ log_fn_ratelim(&mismatch_limit, severity, LD_EXIT,
+ "eventdns: Received a DNS packet from "
+ "an IP address to which we did not send a request. This "
+ "could be a DNS spoofing attempt, or some kind of "
+ "misconfiguration.%s", src);
+ return;
}
tor_log(severity, LD_EXIT, "eventdns: %s", msg);
}
@@ -260,29 +256,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 +339,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 +354,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;
}
}
@@ -391,7 +378,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires)
resolve->expire = expires;
smartlist_pqueue_add(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx),
+ offsetof(cached_resolve_t, minheap_idx),
resolve);
}
@@ -438,7 +425,7 @@ purge_expired_resolves(time_t now)
break;
smartlist_pqueue_pop(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
if (resolve->state == CACHE_STATE_PENDING) {
log_debug(LD_EXIT,
@@ -548,6 +535,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 +834,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 +1184,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 +1322,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 +1372,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,7 +1436,7 @@ 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 (evdns_base_count_nameservers(the_evdns_base) == 1) {
SET("max-timeouts:", "16");
@@ -1773,7 +1755,7 @@ wildcard_increment_answer(const char *id)
"invalid addresses. Apparently they are hijacking DNS failures. "
"I'll try to correct for this by treating future occurrences of "
"\"%s\" as 'not found'.", id, *ip, id);
- smartlist_add(dns_wildcard_list, tor_strdup(id));
+ smartlist_add_strdup(dns_wildcard_list, id);
}
if (!dns_wildcard_notice_given)
control_event_server_status(LOG_NOTICE, "DNS_HIJACKED");
@@ -1797,7 +1779,7 @@ add_wildcarded_test_address(const char *address)
n_test_addrs = get_options()->ServerDNSTestAddresses ?
smartlist_len(get_options()->ServerDNSTestAddresses) : 0;
- smartlist_add(dns_wildcarded_test_address_list, tor_strdup(address));
+ smartlist_add_strdup(dns_wildcarded_test_address_list, address);
n = smartlist_len(dns_wildcarded_test_address_list);
if (n > n_test_addrs/2) {
tor_log(dns_wildcarded_test_address_notice_given ? LOG_INFO : LOG_NOTICE,
@@ -1958,7 +1940,7 @@ dns_launch_wildcard_checks(void)
launch_wildcard_check(8, 16, ipv6, ".com");
launch_wildcard_check(8, 16, ipv6, ".org");
launch_wildcard_check(8, 16, ipv6, ".net");
- }
+ }
}
}
@@ -2101,7 +2083,7 @@ assert_cache_ok_(void)
smartlist_pqueue_assert_ok(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
{
@@ -2117,8 +2099,8 @@ assert_cache_ok_(void)
#endif
-cached_resolve_t
-*dns_get_cache_entry(cached_resolve_t *query)
+cached_resolve_t *
+dns_get_cache_entry(cached_resolve_t *query)
{
return HT_FIND(cache_map, &cache_root, query);
}
diff --git a/src/or/dns.h b/src/or/dns.h
index b14f7dd29c..a81cbd20da 100644
--- a/src/or/dns.h
+++ b/src/or/dns.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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..dc00e9f7b9 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-2017, 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 edca50f6f9..54a22a5150 100644
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@ -1,12 +1,24 @@
-/* Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file dnsserv.c
- * \brief Implements client-side DNS proxy server code. Note:
- * this is the DNS Server code, not the Server DNS code. Confused? This code
- * runs on client-side, and acts as a DNS server. The code in dns.c, on the
- * other hand, runs on Tor servers, and acts as a DNS client.
+ * \brief Implements client-side DNS proxy server code.
+ *
+ * When a user enables the DNSPort configuration option to have their local
+ * Tor client handle DNS requests, this module handles it. It functions as a
+ * "DNS Server" on the client side, which client applications use.
+ *
+ * Inbound DNS requests are represented as entry_connection_t here (since
+ * that's how Tor represents client-side streams), which are kept associated
+ * with an evdns_server_request structure as exposed by Libevent's
+ * evdns code.
+ *
+ * Upon receiving a DNS request, libevent calls our evdns_server_callback()
+ * function here, which causes this module to create an entry_connection_t
+ * request as appropriate. Later, when that request is answered,
+ * connection_edge.c calls dnsserv_resolved() so we can finish up and tell the
+ * DNS client.
**/
#include "or.h"
@@ -17,14 +29,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>.
@@ -140,6 +148,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;
@@ -274,7 +284,7 @@ dnsserv_reject_request(entry_connection_t *conn)
}
/** Look up the original name that corresponds to 'addr' in req. We use this
- * to preserve case in order to facilitate people using 0x20-hacks to avoid
+ * to preserve case in order to facilitate clients using 0x20-hacks to avoid
* DNS poisoning. */
static const char *
evdns_get_orig_address(const struct evdns_server_request *req,
@@ -292,6 +302,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/dnsserv.h b/src/or/dnsserv.h
index ad0e248c83..6c0643b8dc 100644
--- a/src/or/dnsserv.h
+++ b/src/or/dnsserv.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c
index 310a948b35..739ec82484 100644
--- a/src/or/entrynodes.c
+++ b/src/or/entrynodes.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -10,18 +10,118 @@
*
* Entry nodes can be guards (for general use) or bridges (for censorship
* circumvention).
+ *
+ * In general, we use entry guards to prevent traffic-sampling attacks:
+ * if we chose every circuit independently, an adversary controlling
+ * some fraction of paths on the network would observe a sample of every
+ * user's traffic. Using guards gives users a chance of not being
+ * profiled.
+ *
+ * The current entry guard selection code is designed to try to avoid
+ * _ever_ trying every guard on the network, to try to stick to guards
+ * that we've used before, to handle hostile/broken networks, and
+ * to behave sanely when the network goes up and down.
+ *
+ * Our algorithm works as follows: First, we maintain a SAMPLE of guards
+ * we've seen in the networkstatus consensus. We maintain this sample
+ * over time, and store it persistently; it is chosen without reference
+ * to our configuration or firewall rules. Guards remain in the sample
+ * as they enter and leave the consensus. We expand this sample as
+ * needed, up to a maximum size.
+ *
+ * As a subset of the sample, we maintain a FILTERED SET of the guards
+ * that we would be willing to use if we could connect to them. The
+ * filter removes all the guards that we're excluding because they're
+ * bridges (or not bridges), because we have restrictive firewall rules,
+ * because of ExcludeNodes, because we of path bias restrictions,
+ * because they're absent from the network at present, and so on.
+ *
+ * As a subset of the filtered set, we keep a REACHABLE FILTERED SET
+ * (also called a "usable filtered set") of those guards that we call
+ * "reachable" or "maybe reachable". A guard is reachable if we've
+ * connected to it more recently than we've failed. A guard is "maybe
+ * reachable" if we have never tried to connect to it, or if we
+ * failed to connect to it so long ago that we no longer think our
+ * failure means it's down.
+ *
+ * As a persistent ordered list whose elements are taken from the
+ * sampled set, we track a CONFIRMED GUARDS LIST. A guard becomes
+ * confirmed when we successfully build a circuit through it, and decide
+ * to use that circuit. We order the guards on this list by the order
+ * in which they became confirmed.
+ *
+ * And as a final group, we have an ordered list of PRIMARY GUARDS,
+ * whose elements are taken from the filtered set. We prefer
+ * confirmed guards to non-confirmed guards for this list, and place
+ * other restrictions on it. The primary guards are the ones that we
+ * connect to "when nothing is wrong" -- circuits through them can be used
+ * immediately.
+ *
+ * To build circuits, we take a primary guard if possible -- or a
+ * reachable filtered confirmed guard if no primary guard is possible --
+ * or a random reachable filtered guard otherwise. If the guard is
+ * primary, we can use the circuit immediately on success. Otherwise,
+ * the guard is now "pending" -- we won't use its circuit unless all
+ * of the circuits we're trying to build through better guards have
+ * definitely failed.
+ *
+ * While we're building circuits, we track a little "guard state" for
+ * each circuit. We use this to keep track of whether the circuit is
+ * one that we can use as soon as it's done, or whether it's one that
+ * we should keep around to see if we can do better. In the latter case,
+ * a periodic call to entry_guards_upgrade_waiting_circuits() will
+ * eventually upgrade it.
**/
+/* DOCDOC -- expand this.
+ *
+ * Information invariants:
+ *
+ * [x] whenever a guard becomes unreachable, clear its usable_filtered flag.
+ *
+ * [x] Whenever a guard becomes reachable or maybe-reachable, if its filtered
+ * flag is set, set its usable_filtered flag.
+ *
+ * [x] Whenever we get a new consensus, call update_from_consensus(). (LATER.)
+ *
+ * [x] Whenever the configuration changes in a relevant way, update the
+ * filtered/usable flags. (LATER.)
+ *
+ * [x] Whenever we add a guard to the sample, make sure its filtered/usable
+ * flags are set as possible.
+ *
+ * [x] Whenever we remove a guard from the sample, remove it from the primary
+ * and confirmed lists.
+ *
+ * [x] When we make a guard confirmed, update the primary list.
+ *
+ * [x] When we make a guard filtered or unfiltered, update the primary list.
+ *
+ * [x] When we are about to pick a guard, make sure that the primary list is
+ * full.
+ *
+ * [x] Before calling sample_reachable_filtered_entry_guards(), make sure
+ * that the filtered, primary, and confirmed flags are up-to-date.
+ *
+ * [x] Call entry_guard_consider_retry every time we are about to check
+ * is_usable_filtered or is_reachable, and every time we set
+ * is_filtered to 1.
+ *
+ * [x] Call entry_guards_changed_for_guard_selection() whenever we update
+ * a persistent field.
+ */
#define ENTRYNODES_PRIVATE
#include "or.h"
+#include "channel.h"
+#include "bridges.h"
#include "circpathbias.h"
#include "circuitbuild.h"
+#include "circuitlist.h"
#include "circuitstats.h"
#include "config.h"
#include "confparse.h"
#include "connection.h"
-#include "connection_or.h"
#include "control.h"
#include "directory.h"
#include "entrynodes.h"
@@ -37,2460 +137,3370 @@
#include "transports.h"
#include "statefile.h"
-/** Information about a configured bridge. Currently this just matches the
- * ones in the torrc file, but one day we may be able to learn about new
- * bridges on our own, and remember them in the state file. */
-typedef struct {
- /** Address of the bridge. */
- tor_addr_t addr;
- /** TLS port for the bridge. */
- uint16_t port;
- /** Boolean: We are re-parsing our bridge list, and we are going to remove
- * this one if we don't find it in the list of configured bridges. */
- unsigned marked_for_removal : 1;
- /** Expected identity digest, or all zero bytes if we don't know what the
- * digest should be. */
- char identity[DIGEST_LEN];
-
- /** Name of pluggable transport protocol taken from its config line. */
- char *transport_name;
-
- /** When should we next try to fetch a descriptor for this bridge? */
- download_status_t fetch_status;
-
- /** A smartlist of k=v values to be passed to the SOCKS proxy, if
- transports are used for this bridge. */
- smartlist_t *socks_args;
-} bridge_info_t;
-
-/** A list of our chosen entry guards. */
-static smartlist_t *entry_guards = NULL;
-/** A value of 1 means that the entry_guards list has changed
+/** A list of existing guard selection contexts. */
+static smartlist_t *guard_contexts = NULL;
+/** The currently enabled guard selection context. */
+static guard_selection_t *curr_guard_context = NULL;
+
+/** A value of 1 means that at least one context has changed,
* and those changes need to be flushed to disk. */
static int entry_guards_dirty = 0;
-static void bridge_free(bridge_info_t *bridge);
-static const node_t *choose_random_entry_impl(cpath_build_state_t *state,
- int for_directory,
- dirinfo_type_t dirtype,
- int *n_options_out);
-static int num_bridges_usable(void);
+static void entry_guard_set_filtered_flags(const or_options_t *options,
+ guard_selection_t *gs,
+ entry_guard_t *guard);
+static void pathbias_check_use_success_count(entry_guard_t *guard);
+static void pathbias_check_close_success_count(entry_guard_t *guard);
+static int node_is_possible_guard(const node_t *node);
+static int node_passes_guard_filter(const or_options_t *options,
+ const node_t *node);
+static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs,
+ const uint8_t *rsa_id_digest,
+ const char *nickname,
+ const tor_addr_port_t *bridge_addrport);
+static entry_guard_t *get_sampled_guard_by_bridge_addr(guard_selection_t *gs,
+ const tor_addr_port_t *addrport);
+static int entry_guard_obeys_restriction(const entry_guard_t *guard,
+ const entry_guard_restriction_t *rst);
+
+/** Return 0 if we should apply guardfraction information found in the
+ * consensus. A specific consensus can be specified with the
+ * <b>ns</b> argument, if NULL the most recent one will be picked.*/
+int
+should_apply_guardfraction(const networkstatus_t *ns)
+{
+ /* We need to check the corresponding torrc option and the consensus
+ * parameter if we need to. */
+ const or_options_t *options = get_options();
+
+ /* If UseGuardFraction is 'auto' then check the same-named consensus
+ * parameter. If the consensus parameter is not present, default to
+ * "off". */
+ if (options->UseGuardFraction == -1) {
+ return networkstatus_get_param(ns, "UseGuardFraction",
+ 0, /* default to "off" */
+ 0, 1);
+ }
+
+ return options->UseGuardFraction;
+}
-/** Return the list of entry guards, creating it if necessary. */
-const smartlist_t *
-get_entry_guards(void)
+/** Return true iff we know a descriptor for <b>guard</b> */
+static int
+guard_has_descriptor(const entry_guard_t *guard)
{
- if (! entry_guards)
- entry_guards = smartlist_new();
- return entry_guards;
+ const node_t *node = node_get_by_id(guard->identity);
+ if (!node)
+ return 0;
+ return node_has_descriptor(node);
}
-/** Check whether the entry guard <b>e</b> is usable, given the directory
- * authorities' opinion about the router (stored in <b>ri</b>) and the user's
- * configuration (in <b>options</b>). Set <b>e</b>->bad_since
- * accordingly. Return true iff the entry guard's status changes.
- *
- * If it's not usable, set *<b>reason</b> to a static string explaining why.
+/**
+ * Try to determine the correct type for a selection named "name",
+ * if <b>type</b> is GS_TYPE_INFER.
*/
-static int
-entry_guard_set_status(entry_guard_t *e, const node_t *node,
- time_t now, const or_options_t *options,
- const char **reason)
+STATIC guard_selection_type_t
+guard_selection_infer_type(guard_selection_type_t type,
+ const char *name)
+{
+ if (type == GS_TYPE_INFER) {
+ if (!strcmp(name, "bridges"))
+ type = GS_TYPE_BRIDGE;
+ else if (!strcmp(name, "restricted"))
+ type = GS_TYPE_RESTRICTED;
+ else
+ type = GS_TYPE_NORMAL;
+ }
+ return type;
+}
+
+/**
+ * Allocate and return a new guard_selection_t, with the name <b>name</b>.
+ */
+STATIC guard_selection_t *
+guard_selection_new(const char *name,
+ guard_selection_type_t type)
{
- char buf[HEX_DIGEST_LEN+1];
- int changed = 0;
+ guard_selection_t *gs;
- *reason = NULL;
+ type = guard_selection_infer_type(type, name);
- /* Do we want to mark this guard as bad? */
- if (!node)
- *reason = "unlisted";
- else if (!node->is_running)
- *reason = "down";
- else if (options->UseBridges && (!node->ri ||
- node->ri->purpose != ROUTER_PURPOSE_BRIDGE))
- *reason = "not a bridge";
- else if (options->UseBridges && !node_is_a_configured_bridge(node))
- *reason = "not a configured bridge";
- else if (!options->UseBridges && !node->is_possible_guard &&
- !routerset_contains_node(options->EntryNodes,node))
- *reason = "not recommended as a guard";
- else if (routerset_contains_node(options->ExcludeNodes, node))
- *reason = "excluded";
- /* We only care about OR connection connectivity for entry guards. */
- else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0))
- *reason = "unreachable by config";
- else if (e->path_bias_disabled)
- *reason = "path-biased";
-
- if (*reason && ! e->bad_since) {
- /* Router is newly bad. */
- base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard %s (%s) is %s: marking as unusable.",
- e->nickname, buf, *reason);
-
- e->bad_since = now;
- control_event_guard(e->nickname, e->identity, "BAD");
- changed = 1;
- } else if (!*reason && e->bad_since) {
- /* There's nothing wrong with the router any more. */
- base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard %s (%s) is no longer unusable: "
- "marking as ok.", e->nickname, buf);
-
- e->bad_since = 0;
- control_event_guard(e->nickname, e->identity, "GOOD");
- changed = 1;
+ gs = tor_malloc_zero(sizeof(*gs));
+ gs->name = tor_strdup(name);
+ gs->type = type;
+ gs->sampled_entry_guards = smartlist_new();
+ gs->confirmed_entry_guards = smartlist_new();
+ gs->primary_entry_guards = smartlist_new();
+
+ return gs;
+}
+
+/**
+ * Return the guard selection called <b>name</b>. If there is none, and
+ * <b>create_if_absent</b> is true, then create and return it. If there
+ * is none, and <b>create_if_absent</b> is false, then return NULL.
+ */
+STATIC guard_selection_t *
+get_guard_selection_by_name(const char *name,
+ guard_selection_type_t type,
+ int create_if_absent)
+{
+ if (!guard_contexts) {
+ guard_contexts = smartlist_new();
}
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ if (!strcmp(gs->name, name))
+ return gs;
+ } SMARTLIST_FOREACH_END(gs);
- if (node) {
- int is_dir = node_is_dir(node);
- if (options->UseBridges && node_is_a_configured_bridge(node))
- is_dir = 1;
- if (e->is_dir_cache != is_dir) {
- e->is_dir_cache = is_dir;
- changed = 1;
- }
+ if (! create_if_absent)
+ return NULL;
+
+ log_debug(LD_GUARD, "Creating a guard selection called %s", name);
+ guard_selection_t *new_selection = guard_selection_new(name, type);
+ smartlist_add(guard_contexts, new_selection);
+
+ return new_selection;
+}
+
+/**
+ * Allocate the first guard context that we're planning to use,
+ * and make it the current context.
+ */
+static void
+create_initial_guard_context(void)
+{
+ tor_assert(! curr_guard_context);
+ if (!guard_contexts) {
+ guard_contexts = smartlist_new();
+ }
+ guard_selection_type_t type = GS_TYPE_INFER;
+ const char *name = choose_guard_selection(
+ get_options(),
+ networkstatus_get_live_consensus(approx_time()),
+ NULL,
+ &type);
+ tor_assert(name); // "name" can only be NULL if we had an old name.
+ tor_assert(type != GS_TYPE_INFER);
+ log_notice(LD_GUARD, "Starting with guard context \"%s\"", name);
+ curr_guard_context = get_guard_selection_by_name(name, type, 1);
+}
+
+/** Get current default guard_selection_t, creating it if necessary */
+guard_selection_t *
+get_guard_selection_info(void)
+{
+ if (!curr_guard_context) {
+ create_initial_guard_context();
}
- return changed;
+ return curr_guard_context;
}
-/** Return true iff enough time has passed since we last tried to connect
- * to the unreachable guard <b>e</b> that we're willing to try again. */
-STATIC int
-entry_is_time_to_retry(const entry_guard_t *e, time_t now)
+/** Return a statically allocated human-readable description of <b>guard</b>
+ */
+const char *
+entry_guard_describe(const entry_guard_t *guard)
{
- struct guard_retry_period_s {
- time_t period_duration;
- time_t interval_during_period;
- };
+ static char buf[256];
+ tor_snprintf(buf, sizeof(buf),
+ "%s ($%s)",
+ strlen(guard->nickname) ? guard->nickname : "[bridge]",
+ hex_str(guard->identity, DIGEST_LEN));
+ return buf;
+}
- struct guard_retry_period_s periods[] = {
- { 6*60*60, 60*60 }, /* For first 6 hrs., retry hourly; */
- { 3*24*60*60, 4*60*60 }, /* Then retry every 4 hrs. until the
- 3-day mark; */
- { 7*24*60*60, 18*60*60 }, /* After 3 days, retry every 18 hours until
- 1 week mark. */
- { TIME_MAX, 36*60*60 } /* After 1 week, retry every 36 hours. */
- };
+/** Return <b>guard</b>'s 20-byte RSA identity digest */
+const char *
+entry_guard_get_rsa_id_digest(const entry_guard_t *guard)
+{
+ return guard->identity;
+}
- time_t ith_deadline_for_retry;
- time_t unreachable_for;
- unsigned i;
+/** Return the pathbias state associated with <b>guard</b>. */
+guard_pathbias_t *
+entry_guard_get_pathbias_state(entry_guard_t *guard)
+{
+ return &guard->pb;
+}
- if (e->last_attempted < e->unreachable_since)
- return 1;
+HANDLE_IMPL(entry_guard, entry_guard_t, ATTR_UNUSED STATIC)
- unreachable_for = now - e->unreachable_since;
+/** Return an interval betweeen 'now' and 'max_backdate' seconds in the past,
+ * chosen uniformly at random. We use this before recording persistent
+ * dates, so that we aren't leaking exactly when we recorded it.
+ */
+MOCK_IMPL(STATIC time_t,
+randomize_time,(time_t now, time_t max_backdate))
+{
+ tor_assert(max_backdate > 0);
- for (i = 0; i < ARRAY_LENGTH(periods); i++) {
- if (unreachable_for <= periods[i].period_duration) {
- ith_deadline_for_retry = e->last_attempted +
- periods[i].interval_during_period;
+ time_t earliest = now - max_backdate;
+ time_t latest = now;
+ if (earliest <= 0)
+ earliest = 1;
+ if (latest <= earliest)
+ latest = earliest + 1;
- return (now > ith_deadline_for_retry);
- }
- }
- return 0;
+ return crypto_rand_time_range(earliest, latest);
}
-/** Return the node corresponding to <b>e</b>, if <b>e</b> is
- * working well enough that we are willing to use it as an entry
- * right now. (Else return NULL.) In particular, it must be
- * - Listed as either up or never yet contacted;
- * - Present in the routerlist;
- * - Listed as 'stable' or 'fast' by the current dirserver consensus,
- * if demanded by <b>need_uptime</b> or <b>need_capacity</b>
- * (unless it's a configured EntryNode);
- * - Allowed by our current ReachableORAddresses config option; and
- * - Currently thought to be reachable by us (unless <b>assume_reachable</b>
- * is true).
- *
- * If the answer is no, set *<b>msg</b> to an explanation of why.
+/**
+ * @name parameters for networkstatus algorithm
*
- * If need_descriptor is true, only return the node if we currently have
- * a descriptor (routerinfo or microdesc) for it.
+ * These parameters are taken from the consensus; some are overrideable in
+ * the torrc.
*/
-STATIC const node_t *
-entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags,
- const char **msg)
+/**@{*/
+/**
+ * We never let our sampled guard set grow larger than this fraction
+ * of the guards on the network.
+ */
+STATIC double
+get_max_sample_threshold(void)
{
- const node_t *node;
- const or_options_t *options = get_options();
- int need_uptime = (flags & ENTRY_NEED_UPTIME) != 0;
- int need_capacity = (flags & ENTRY_NEED_CAPACITY) != 0;
- const int assume_reachable = (flags & ENTRY_ASSUME_REACHABLE) != 0;
- const int need_descriptor = (flags & ENTRY_NEED_DESCRIPTOR) != 0;
-
- tor_assert(msg);
+ int32_t pct =
+ networkstatus_get_param(NULL, "guard-max-sample-threshold-percent",
+ DFLT_MAX_SAMPLE_THRESHOLD_PERCENT,
+ 1, 100);
+ return pct / 100.0;
+}
+/**
+ * We never let our sampled guard set grow larger than this number.
+ */
+STATIC int
+get_max_sample_size_absolute(void)
+{
+ return (int) networkstatus_get_param(NULL, "guard-max-sample-size",
+ DFLT_MAX_SAMPLE_SIZE,
+ 1, INT32_MAX);
+}
+/**
+ * We always try to make our sample contain at least this many guards.
+ */
+STATIC int
+get_min_filtered_sample_size(void)
+{
+ return networkstatus_get_param(NULL, "guard-min-filtered-sample-size",
+ DFLT_MIN_FILTERED_SAMPLE_SIZE,
+ 1, INT32_MAX);
+}
+/**
+ * If a guard is unlisted for this many days in a row, we remove it.
+ */
+STATIC int
+get_remove_unlisted_guards_after_days(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-remove-unlisted-guards-after-days",
+ DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS,
+ 1, 365*10);
+}
+/**
+ * We remove unconfirmed guards from the sample after this many days,
+ * regardless of whether they are listed or unlisted.
+ */
+STATIC int
+get_guard_lifetime(void)
+{
+ if (get_options()->GuardLifetime >= 86400)
+ return get_options()->GuardLifetime;
+ int32_t days;
+ days = networkstatus_get_param(NULL,
+ "guard-lifetime-days",
+ DFLT_GUARD_LIFETIME_DAYS, 1, 365*10);
+ return days * 86400;
+}
+/**
+ * We remove confirmed guards from the sample if they were sampled
+ * GUARD_LIFETIME_DAYS ago and confirmed this many days ago.
+ */
+STATIC int
+get_guard_confirmed_min_lifetime(void)
+{
+ if (get_options()->GuardLifetime >= 86400)
+ return get_options()->GuardLifetime;
+ int32_t days;
+ days = networkstatus_get_param(NULL, "guard-confirmed-min-lifetime-days",
+ DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS,
+ 1, 365*10);
+ return days * 86400;
+}
+/**
+ * How many guards do we try to keep on our primary guard list?
+ */
+STATIC int
+get_n_primary_guards(void)
+{
+ const int n = get_options()->NumEntryGuards;
+ const int n_dir = get_options()->NumDirectoryGuards;
+ if (n > 5) {
+ return MAX(n_dir, n + n / 2);
+ } else if (n >= 1) {
+ return MAX(n_dir, n * 2);
+ }
- if (e->path_bias_disabled) {
- *msg = "path-biased";
- return NULL;
+ return networkstatus_get_param(NULL,
+ "guard-n-primary-guards",
+ DFLT_N_PRIMARY_GUARDS, 1, INT32_MAX);
+}
+/**
+ * Return the number of the live primary guards we should look at when
+ * making a circuit.
+ */
+STATIC int
+get_n_primary_guards_to_use(guard_usage_t usage)
+{
+ int configured;
+ const char *param_name;
+ int param_default;
+ if (usage == GUARD_USAGE_DIRGUARD) {
+ configured = get_options()->NumDirectoryGuards;
+ param_name = "guard-n-primary-dir-guards-to-use";
+ param_default = DFLT_N_PRIMARY_DIR_GUARDS_TO_USE;
+ } else {
+ configured = get_options()->NumEntryGuards;
+ param_name = "guard-n-primary-guards-to-use";
+ param_default = DFLT_N_PRIMARY_GUARDS_TO_USE;
}
- if (e->bad_since) {
- *msg = "bad";
- return NULL;
+ if (configured >= 1) {
+ return configured;
}
- /* no good if it's unreachable, unless assume_unreachable or can_retry. */
- if (!assume_reachable && !e->can_retry &&
- e->unreachable_since && !entry_is_time_to_retry(e, time(NULL))) {
- *msg = "unreachable";
- return NULL;
+ return networkstatus_get_param(NULL,
+ param_name, param_default, 1, INT32_MAX);
+}
+/**
+ * If we haven't successfully built or used a circuit in this long, then
+ * consider that the internet is probably down.
+ */
+STATIC int
+get_internet_likely_down_interval(void)
+{
+ return networkstatus_get_param(NULL, "guard-internet-likely-down-interval",
+ DFLT_INTERNET_LIKELY_DOWN_INTERVAL,
+ 1, INT32_MAX);
+}
+/**
+ * If we're trying to connect to a nonprimary guard for at least this
+ * many seconds, and we haven't gotten the connection to work, we will treat
+ * lower-priority guards as usable.
+ */
+STATIC int
+get_nonprimary_guard_connect_timeout(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-nonprimary-guard-connect-timeout",
+ DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT,
+ 1, INT32_MAX);
+}
+/**
+ * If a circuit has been sitting around in 'waiting for better guard' state
+ * for at least this long, we'll expire it.
+ */
+STATIC int
+get_nonprimary_guard_idle_timeout(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-nonprimary-guard-idle-timeout",
+ DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT,
+ 1, INT32_MAX);
+}
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in a restricted setting.
+ */
+STATIC double
+get_meaningful_restriction_threshold(void)
+{
+ int32_t pct = networkstatus_get_param(NULL,
+ "guard-meaningful-restriction-percent",
+ DFLT_MEANINGFUL_RESTRICTION_PERCENT,
+ 1, INT32_MAX);
+ return pct / 100.0;
+}
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in an extremely restricted setting, and should warn.
+ */
+STATIC double
+get_extreme_restriction_threshold(void)
+{
+ int32_t pct = networkstatus_get_param(NULL,
+ "guard-extreme-restriction-percent",
+ DFLT_EXTREME_RESTRICTION_PERCENT,
+ 1, INT32_MAX);
+ return pct / 100.0;
+}
+
+/* Mark <b>guard</b> as maybe reachable again. */
+static void
+mark_guard_maybe_reachable(entry_guard_t *guard)
+{
+ if (guard->is_reachable != GUARD_REACHABLE_NO) {
+ return;
}
- node = node_get_by_id(e->identity);
- if (!node) {
- *msg = "no node info";
- return NULL;
+
+ /* Note that we do not clear failing_since: this guard is now only
+ * _maybe-reachable_. */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+}
+
+/**
+ * Called when the network comes up after having seemed to be down for
+ * a while: Mark the primary guards as maybe-reachable so that we'll
+ * try them again.
+ */
+STATIC void
+mark_primary_guards_maybe_reachable(guard_selection_t *gs)
+{
+ tor_assert(gs);
+
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ mark_guard_maybe_reachable(guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
+
+/* Called when we exhaust all guards in our sampled set: Marks all guards as
+ maybe-reachable so that we 'll try them again. */
+static void
+mark_all_guards_maybe_reachable(guard_selection_t *gs)
+{
+ tor_assert(gs);
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ mark_guard_maybe_reachable(guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
+
+/**@}*/
+
+/**
+ * Given our options and our list of nodes, return the name of the
+ * guard selection that we should use. Return NULL for "use the
+ * same selection you were using before.
+ */
+STATIC const char *
+choose_guard_selection(const or_options_t *options,
+ const networkstatus_t *live_ns,
+ const guard_selection_t *old_selection,
+ guard_selection_type_t *type_out)
+{
+ tor_assert(options);
+ tor_assert(type_out);
+
+ if (options->UseBridges) {
+ *type_out = GS_TYPE_BRIDGE;
+ return "bridges";
}
- if (need_descriptor && !node_has_descriptor(node)) {
- *msg = "no descriptor";
- return NULL;
+
+ if (! live_ns) {
+ /* without a networkstatus, we can't tell any more than that. */
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
}
- if (get_options()->UseBridges) {
- if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) {
- *msg = "not a bridge";
- return NULL;
- }
- if (!node_is_a_configured_bridge(node)) {
- *msg = "not a configured bridge";
- return NULL;
- }
- } else { /* !get_options()->UseBridges */
- if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) {
- *msg = "not general-purpose";
- return NULL;
+
+ const smartlist_t *nodes = nodelist_get_list();
+ int n_guards = 0, n_passing_filter = 0;
+ SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) {
+ if (node_is_possible_guard(node)) {
+ ++n_guards;
+ if (node_passes_guard_filter(options, node)) {
+ ++n_passing_filter;
+ }
}
+ } SMARTLIST_FOREACH_END(node);
+
+ /* We use separate 'high' and 'low' thresholds here to prevent flapping
+ * back and forth */
+ const int meaningful_threshold_high =
+ (int)(n_guards * get_meaningful_restriction_threshold() * 1.05);
+ const int meaningful_threshold_mid =
+ (int)(n_guards * get_meaningful_restriction_threshold());
+ const int meaningful_threshold_low =
+ (int)(n_guards * get_meaningful_restriction_threshold() * .95);
+ const int extreme_threshold =
+ (int)(n_guards * get_extreme_restriction_threshold());
+
+ /*
+ If we have no previous selection, then we're "restricted" iff we are
+ below the meaningful restriction threshold. That's easy enough.
+
+ But if we _do_ have a previous selection, we make it a little
+ "sticky": we only move from "restricted" to "default" when we find
+ that we're above the threshold plus 5%, and we only move from
+ "default" to "restricted" when we're below the threshold minus 5%.
+ That should prevent us from flapping back and forth if we happen to
+ be hovering very close to the default.
+
+ The extreme threshold is for warning only.
+ */
+
+ static int have_warned_extreme_threshold = 0;
+ if (n_guards &&
+ n_passing_filter < extreme_threshold &&
+ ! have_warned_extreme_threshold) {
+ have_warned_extreme_threshold = 1;
+ const double exclude_frac =
+ (n_guards - n_passing_filter) / (double)n_guards;
+ log_warn(LD_GUARD, "Your configuration excludes %d%% of all possible "
+ "guards. That's likely to make you stand out from the "
+ "rest of the world.", (int)(exclude_frac * 100));
}
- if (routerset_contains_node(options->EntryNodes, node)) {
- /* they asked for it, they get it */
- need_uptime = need_capacity = 0;
- }
- if (node_is_unreliable(node, need_uptime, need_capacity, 0)) {
- *msg = "not fast/stable";
- return NULL;
+
+ /* Easy case: no previous selection. Just check if we are in restricted or
+ normal guard selection. */
+ if (old_selection == NULL) {
+ if (n_passing_filter >= meaningful_threshold_mid) {
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
+ } else {
+ *type_out = GS_TYPE_RESTRICTED;
+ return "restricted";
+ }
}
- if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) {
- *msg = "unreachable by config";
- return NULL;
+
+ /* Trickier case: we do have a previous guard selection context. */
+ tor_assert(old_selection);
+
+ /* Use high and low thresholds to decide guard selection, and if we fall in
+ the middle then keep the current guard selection context. */
+ if (n_passing_filter >= meaningful_threshold_high) {
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
+ } else if (n_passing_filter < meaningful_threshold_low) {
+ *type_out = GS_TYPE_RESTRICTED;
+ return "restricted";
+ } else {
+ /* we are in the middle: maintain previous guard selection */
+ *type_out = old_selection->type;
+ return old_selection->name;
}
- return node;
}
-/** Return the number of entry guards that we think are usable. */
+/**
+ * Check whether we should switch from our current guard selection to a
+ * different one. If so, switch and return 1. Return 0 otherwise.
+ *
+ * On a 1 return, the caller should mark all currently live circuits unusable
+ * for new streams, by calling circuit_mark_all_unused_circs() and
+ * circuit_mark_all_dirty_circs_as_unusable().
+ */
int
-num_live_entry_guards(int for_directory)
+update_guard_selection_choice(const or_options_t *options)
{
- int n = 0;
- const char *msg;
- /* Set the entry node attributes we are interested in. */
- entry_is_live_flags_t entry_flags = ENTRY_NEED_CAPACITY;
- if (!for_directory) {
- entry_flags |= ENTRY_NEED_DESCRIPTOR;
+ if (!curr_guard_context) {
+ create_initial_guard_context();
+ return 1;
}
- if (! entry_guards)
- return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- if (for_directory && !entry->is_dir_cache)
- continue;
- if (entry_is_live(entry, entry_flags, &msg))
- ++n;
- } SMARTLIST_FOREACH_END(entry);
- return n;
+ guard_selection_type_t type = GS_TYPE_INFER;
+ const char *new_name = choose_guard_selection(
+ options,
+ networkstatus_get_live_consensus(approx_time()),
+ curr_guard_context,
+ &type);
+ tor_assert(new_name);
+ tor_assert(type != GS_TYPE_INFER);
+
+ const char *cur_name = curr_guard_context->name;
+ if (! strcmp(cur_name, new_name)) {
+ log_debug(LD_GUARD,
+ "Staying with guard context \"%s\" (no change)", new_name);
+ return 0; // No change
+ }
+
+ log_notice(LD_GUARD, "Switching to guard context \"%s\" (was using \"%s\")",
+ new_name, cur_name);
+ guard_selection_t *new_guard_context;
+ new_guard_context = get_guard_selection_by_name(new_name, type, 1);
+ tor_assert(new_guard_context);
+ tor_assert(new_guard_context != curr_guard_context);
+ curr_guard_context = new_guard_context;
+
+ return 1;
}
-/** If <b>digest</b> matches the identity of any node in the
- * entry_guards list, return that node. Else return NULL. */
-entry_guard_t *
-entry_guard_get_by_id_digest(const char *digest)
+/**
+ * Return true iff <b>node</b> has all the flags needed for us to consider it
+ * a possible guard when sampling guards.
+ */
+static int
+node_is_possible_guard(const node_t *node)
{
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
- if (tor_memeq(digest, entry->identity, DIGEST_LEN))
- return entry;
- );
+ /* The "GUARDS" set is all nodes in the nodelist for which this predicate
+ * holds. */
+
+ tor_assert(node);
+ return (node->is_possible_guard &&
+ node->is_stable &&
+ node->is_fast &&
+ node->is_valid &&
+ node_is_dir(node));
+}
+
+/**
+ * Return the sampled guard with the RSA identity digest <b>rsa_id</b>, or
+ * NULL if we don't have one. */
+STATIC entry_guard_t *
+get_sampled_guard_with_id(guard_selection_t *gs,
+ const uint8_t *rsa_id)
+{
+ tor_assert(gs);
+ tor_assert(rsa_id);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (tor_memeq(guard->identity, rsa_id, DIGEST_LEN))
+ return guard;
+ } SMARTLIST_FOREACH_END(guard);
return NULL;
}
-/** Dump a description of our list of entry guards to the log at level
- * <b>severity</b>. */
-static void
-log_entry_guards(int severity)
-{
- smartlist_t *elements = smartlist_new();
- char *s;
-
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e)
- {
- const char *msg = NULL;
- if (entry_is_live(e, ENTRY_NEED_CAPACITY, &msg))
- smartlist_add_asprintf(elements, "%s [%s] (up %s)",
- e->nickname,
- hex_str(e->identity, DIGEST_LEN),
- e->made_contact ? "made-contact" : "never-contacted");
- else
- smartlist_add_asprintf(elements, "%s [%s] (%s, %s)",
- e->nickname,
- hex_str(e->identity, DIGEST_LEN),
- msg,
- e->made_contact ? "made-contact" : "never-contacted");
- }
- SMARTLIST_FOREACH_END(e);
+/** If <b>gs</b> contains a sampled entry guard matching <b>bridge</b>,
+ * return that guard. Otherwise return NULL. */
+static entry_guard_t *
+get_sampled_guard_for_bridge(guard_selection_t *gs,
+ const bridge_info_t *bridge)
+{
+ const uint8_t *id = bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
+ entry_guard_t *guard;
+ if (BUG(!addrport))
+ return NULL; // LCOV_EXCL_LINE
+ guard = get_sampled_guard_by_bridge_addr(gs, addrport);
+ if (! guard || (id && tor_memneq(id, guard->identity, DIGEST_LEN)))
+ return NULL;
+ else
+ return guard;
+}
- s = smartlist_join_strings(elements, ",", 0, NULL);
- SMARTLIST_FOREACH(elements, char*, cp, tor_free(cp));
- smartlist_free(elements);
- log_fn(severity,LD_CIRC,"%s",s);
- tor_free(s);
+/** If we know a bridge_info_t matching <b>guard</b>, return that
+ * bridge. Otherwise return NULL. */
+static bridge_info_t *
+get_bridge_info_for_guard(const entry_guard_t *guard)
+{
+ const uint8_t *identity = NULL;
+ if (! tor_digest_is_zero(guard->identity)) {
+ identity = (const uint8_t *)guard->identity;
+ }
+ if (BUG(guard->bridge_addr == NULL))
+ return NULL;
+
+ return get_configured_bridge_by_exact_addr_port_digest(
+ &guard->bridge_addr->addr,
+ guard->bridge_addr->port,
+ (const char*)identity);
}
-/** Called when one or more guards that we would previously have used for some
- * purpose are no longer in use because a higher-priority guard has become
- * usable again. */
-static void
-control_event_guard_deferred(void)
-{
- /* XXXX We don't actually have a good way to figure out _how many_ entries
- * are live for some purpose. We need an entry_is_even_slightly_live()
- * function for this to work right. NumEntryGuards isn't reliable: if we
- * need guards with weird properties, we can have more than that number
- * live.
- **/
-#if 0
- int n = 0;
- const char *msg;
- const or_options_t *options = get_options();
- if (!entry_guards)
- return;
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
- {
- if (entry_is_live(entry, 0, 1, 0, &msg)) {
- if (n++ == options->NumEntryGuards) {
- control_event_guard(entry->nickname, entry->identity, "DEFERRED");
- return;
- }
- }
- });
-#endif
+/**
+ * Return true iff we have a sampled guard with the RSA identity digest
+ * <b>rsa_id</b>. */
+static inline int
+have_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id)
+{
+ return get_sampled_guard_with_id(gs, rsa_id) != NULL;
+}
+
+/**
+ * Allocate a new entry_guard_t object for <b>node</b>, add it to the
+ * sampled entry guards in <b>gs</b>, and return it. <b>node</b> must
+ * not currently be a sampled guard in <b>gs</b>.
+ */
+STATIC entry_guard_t *
+entry_guard_add_to_sample(guard_selection_t *gs,
+ const node_t *node)
+{
+ log_info(LD_GUARD, "Adding %s to the entry guard sample set.",
+ node_describe(node));
+
+ /* make sure that the guard is not already sampled. */
+ if (BUG(have_sampled_guard_with_id(gs, (const uint8_t*)node->identity)))
+ return NULL; // LCOV_EXCL_LINE
+
+ return entry_guard_add_to_sample_impl(gs,
+ (const uint8_t*)node->identity,
+ node_get_nickname(node),
+ NULL);
}
-/** Largest amount that we'll backdate chosen_on_date */
-#define CHOSEN_ON_DATE_SLOP (30*86400)
+/**
+ * Backend: adds a new sampled guard to <b>gs</b>, with given identity,
+ * nickname, and ORPort. rsa_id_digest and bridge_addrport are optional, but
+ * we need one of them. nickname is optional. The caller is responsible for
+ * maintaining the size limit of the SAMPLED_GUARDS set.
+ */
+static entry_guard_t *
+entry_guard_add_to_sample_impl(guard_selection_t *gs,
+ const uint8_t *rsa_id_digest,
+ const char *nickname,
+ const tor_addr_port_t *bridge_addrport)
+{
+ const int GUARD_LIFETIME = get_guard_lifetime();
+ tor_assert(gs);
+
+ // XXXX #20827 take ed25519 identity here too.
+
+ /* Make sure we can actually identify the guard. */
+ if (BUG(!rsa_id_digest && !bridge_addrport))
+ return NULL; // LCOV_EXCL_LINE
+
+ entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t));
+
+ /* persistent fields */
+ guard->is_persistent = (rsa_id_digest != NULL);
+ guard->selection_name = tor_strdup(gs->name);
+ if (rsa_id_digest)
+ memcpy(guard->identity, rsa_id_digest, DIGEST_LEN);
+ if (nickname)
+ strlcpy(guard->nickname, nickname, sizeof(guard->nickname));
+ guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10);
+ tor_free(guard->sampled_by_version);
+ guard->sampled_by_version = tor_strdup(VERSION);
+ guard->currently_listed = 1;
+ guard->confirmed_idx = -1;
+
+ /* non-persistent fields */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (bridge_addrport)
+ guard->bridge_addr = tor_memdup(bridge_addrport, sizeof(*bridge_addrport));
+
+ smartlist_add(gs->sampled_entry_guards, guard);
+ guard->in_selection = gs;
+ entry_guard_set_filtered_flags(get_options(), gs, guard);
+ entry_guards_changed_for_guard_selection(gs);
+ return guard;
+}
-/** Add a new (preferably stable and fast) router to our
- * entry_guards list. Return a pointer to the router if we succeed,
- * or NULL if we can't find any more suitable entries.
- *
- * If <b>chosen</b> is defined, use that one, and if it's not
- * already in our entry_guards list, put it at the *beginning*.
- * Else, put the one we pick at the end of the list. */
-STATIC const node_t *
-add_an_entry_guard(const node_t *chosen, int reset_status, int prepend,
- int for_discovery, int for_directory)
+/**
+ * Add an entry guard to the "bridges" guard selection sample, with
+ * information taken from <b>bridge</b>. Return that entry guard.
+ */
+static entry_guard_t *
+entry_guard_add_bridge_to_sample(guard_selection_t *gs,
+ const bridge_info_t *bridge)
{
- const node_t *node;
- entry_guard_t *entry;
-
- if (chosen) {
- node = chosen;
- entry = entry_guard_get_by_id_digest(node->identity);
- if (entry) {
- if (reset_status) {
- entry->bad_since = 0;
- entry->can_retry = 1;
- }
- entry->is_dir_cache = node_is_dir(node);
- if (get_options()->UseBridges && node_is_a_configured_bridge(node))
- entry->is_dir_cache = 1;
+ const uint8_t *id_digest = bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
- return NULL;
- }
- } else if (!for_directory) {
- node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL);
- if (!node)
- return NULL;
- } else {
- const routerstatus_t *rs;
- rs = router_pick_directory_server(MICRODESC_DIRINFO|V3_DIRINFO,
- PDS_FOR_GUARD);
- if (!rs)
- return NULL;
- node = node_get_by_id(rs->identity_digest);
- if (!node)
- return NULL;
- }
- if (node->using_as_guard)
+ tor_assert(addrport);
+
+ /* make sure that the guard is not already sampled. */
+ if (BUG(get_sampled_guard_for_bridge(gs, bridge)))
+ return NULL; // LCOV_EXCL_LINE
+
+ return entry_guard_add_to_sample_impl(gs, id_digest, NULL, addrport);
+}
+
+/**
+ * Return the entry_guard_t in <b>gs</b> whose address is <b>addrport</b>,
+ * or NULL if none exists.
+*/
+static entry_guard_t *
+get_sampled_guard_by_bridge_addr(guard_selection_t *gs,
+ const tor_addr_port_t *addrport)
+{
+ if (! gs)
return NULL;
- if (entry_guard_get_by_id_digest(node->identity) != NULL) {
- log_info(LD_CIRC, "I was about to add a duplicate entry guard.");
- /* This can happen if we choose a guard, then the node goes away, then
- * comes back. */
- ((node_t*) node)->using_as_guard = 1;
+ if (BUG(!addrport))
return NULL;
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) {
+ if (g->bridge_addr && tor_addr_port_eq(addrport, g->bridge_addr))
+ return g;
+ } SMARTLIST_FOREACH_END(g);
+ return NULL;
+}
+
+/** Update the guard subsystem's knowledge of the identity of the bridge
+ * at <b>addrport</b>. Idempotent.
+ */
+void
+entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport,
+ const uint8_t *rsa_id_digest)
+{
+ guard_selection_t *gs = get_guard_selection_by_name("bridges",
+ GS_TYPE_BRIDGE,
+ 0);
+ if (!gs)
+ return;
+
+ entry_guard_t *g = get_sampled_guard_by_bridge_addr(gs, addrport);
+ if (!g)
+ return;
+
+ int make_persistent = 0;
+
+ if (tor_digest_is_zero(g->identity)) {
+ memcpy(g->identity, rsa_id_digest, DIGEST_LEN);
+ make_persistent = 1;
+ } else if (tor_memeq(g->identity, rsa_id_digest, DIGEST_LEN)) {
+ /* Nothing to see here; we learned something we already knew. */
+ if (BUG(! g->is_persistent))
+ make_persistent = 1;
+ } else {
+ char old_id[HEX_DIGEST_LEN+1];
+ base16_encode(old_id, sizeof(old_id), g->identity, sizeof(g->identity));
+ log_warn(LD_BUG, "We 'learned' an identity %s for a bridge at %s:%d, but "
+ "we already knew a different one (%s). Ignoring the new info as "
+ "possibly bogus.",
+ hex_str((const char *)rsa_id_digest, DIGEST_LEN),
+ fmt_and_decorate_addr(&addrport->addr), addrport->port,
+ old_id);
+ return; // redundant, but let's be clear: we're not making this persistent.
}
- entry = tor_malloc_zero(sizeof(entry_guard_t));
- log_info(LD_CIRC, "Chose %s as new entry guard.",
- node_describe(node));
- strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname));
- memcpy(entry->identity, node->identity, DIGEST_LEN);
- entry->is_dir_cache = node_is_dir(node);
- if (get_options()->UseBridges && node_is_a_configured_bridge(node))
- entry->is_dir_cache = 1;
-
- /* Choose expiry time smudged over the past month. The goal here
- * is to a) spread out when Tor clients rotate their guards, so they
- * don't all select them on the same day, and b) avoid leaving a
- * precise timestamp in the state file about when we first picked
- * this guard. For details, see the Jan 2010 or-dev thread. */
- time_t now = time(NULL);
- entry->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now);
- entry->chosen_by_version = tor_strdup(VERSION);
-
- /* Are we picking this guard because all of our current guards are
- * down so we need another one (for_discovery is 1), or because we
- * decided we need more variety in our guard list (for_discovery is 0)?
- *
- * Currently we hack this behavior into place by setting "made_contact"
- * for guards of the latter variety, so we'll be willing to use any of
- * them right off the bat.
- */
- if (!for_discovery)
- entry->made_contact = 1;
- ((node_t*)node)->using_as_guard = 1;
- if (prepend)
- smartlist_insert(entry_guards, 0, entry);
- else
- smartlist_add(entry_guards, entry);
- control_event_guard(entry->nickname, entry->identity, "NEW");
- control_event_guard_deferred();
- log_entry_guards(LOG_INFO);
- return node;
+ if (make_persistent) {
+ g->is_persistent = 1;
+ entry_guards_changed_for_guard_selection(gs);
+ }
}
-/** Choose how many entry guards or directory guards we'll use. If
- * <b>for_directory</b> is true, we return how many directory guards to
- * use; else we return how many entry guards to use. */
+/**
+ * Return the number of sampled guards in <b>gs</b> that are "filtered"
+ * (that is, we're willing to connect to them) and that are "usable"
+ * (that is, either "reachable" or "maybe reachable").
+ *
+ * If a restriction is provided in <b>rst</b>, do not count any guards that
+ * violate it.
+ */
STATIC int
-decide_num_guards(const or_options_t *options, int for_directory)
+num_reachable_filtered_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst)
{
- if (for_directory) {
- int answer;
- if (options->NumDirectoryGuards != 0)
- return options->NumDirectoryGuards;
- answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10);
- if (answer) /* non-zero means use the consensus value */
- return answer;
- }
-
- if (options->NumEntryGuards)
- return options->NumEntryGuards;
+ int n_reachable_filtered_guards = 0;
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ if (guard->is_usable_filtered_guard)
+ ++n_reachable_filtered_guards;
+ } SMARTLIST_FOREACH_END(guard);
+ return n_reachable_filtered_guards;
+}
- /* Use the value from the consensus, or 3 if no guidance. */
- return networkstatus_get_param(NULL, "NumEntryGuards", 3, 1, 10);
+/** Return the actual maximum size for the sample in <b>gs</b>,
+ * given that we know about <b>n_guards</b> total. */
+static int
+get_max_sample_size(guard_selection_t *gs,
+ int n_guards)
+{
+ const int using_bridges = (gs->type == GS_TYPE_BRIDGE);
+ const int min_sample = get_min_filtered_sample_size();
+
+ /* If we are in bridge mode, expand our sample set as needed without worrying
+ * about max size. We should respect the user's wishes to use many bridges if
+ * that's what they have specified in their configuration file. */
+ if (using_bridges)
+ return INT_MAX;
+
+ const int max_sample_by_pct = (int)(n_guards * get_max_sample_threshold());
+ const int max_sample_absolute = get_max_sample_size_absolute();
+ const int max_sample = MIN(max_sample_by_pct, max_sample_absolute);
+ if (max_sample < min_sample)
+ return min_sample;
+ else
+ return max_sample;
}
-/** If the use of entry guards is configured, choose more entry guards
- * until we have enough in the list. */
-static void
-pick_entry_guards(const or_options_t *options, int for_directory)
-{
- int changed = 0;
- const int num_needed = decide_num_guards(options, for_directory);
+/**
+ * Return a smartlist of the all the guards that are not currently
+ * members of the sample (GUARDS - SAMPLED_GUARDS). The elements of
+ * this list are node_t pointers in the non-bridge case, and
+ * bridge_info_t pointers in the bridge case. Set *<b>n_guards_out/b>
+ * to the number of guards that we found in GUARDS, including those
+ * that were already sampled.
+ */
+static smartlist_t *
+get_eligible_guards(const or_options_t *options,
+ guard_selection_t *gs,
+ int *n_guards_out)
+{
+ /* Construct eligible_guards as GUARDS - SAMPLED_GUARDS */
+ smartlist_t *eligible_guards = smartlist_new();
+ int n_guards = 0; // total size of "GUARDS"
+
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const smartlist_t *bridges = bridge_list_get();
+ SMARTLIST_FOREACH_BEGIN(bridges, bridge_info_t *, bridge) {
+ ++n_guards;
+ if (NULL != get_sampled_guard_for_bridge(gs, bridge)) {
+ continue;
+ }
+ smartlist_add(eligible_guards, bridge);
+ } SMARTLIST_FOREACH_END(bridge);
+ } else {
+ const smartlist_t *nodes = nodelist_get_list();
+ const int n_sampled = smartlist_len(gs->sampled_entry_guards);
+
+ /* Build a bloom filter of our current guards: let's keep this O(N). */
+ digestset_t *sampled_guard_ids = digestset_new(n_sampled);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, const entry_guard_t *,
+ guard) {
+ digestset_add(sampled_guard_ids, guard->identity);
+ } SMARTLIST_FOREACH_END(guard);
+
+ SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) {
+ if (! node_is_possible_guard(node))
+ continue;
+ if (gs->type == GS_TYPE_RESTRICTED) {
+ /* In restricted mode, we apply the filter BEFORE sampling, so
+ * that we are sampling from the nodes that we might actually
+ * select. If we sampled first, we might wind up with a sample
+ * that didn't include any EntryNodes at all. */
+ if (! node_passes_guard_filter(options, node))
+ continue;
+ }
+ ++n_guards;
+ if (digestset_contains(sampled_guard_ids, node->identity))
+ continue;
+ smartlist_add(eligible_guards, (node_t*)node);
+ } SMARTLIST_FOREACH_END(node);
- tor_assert(entry_guards);
+ /* Now we can free that bloom filter. */
+ digestset_free(sampled_guard_ids);
+ }
- while (num_live_entry_guards(for_directory) < num_needed) {
- if (!add_an_entry_guard(NULL, 0, 0, 0, for_directory))
- break;
- changed = 1;
+ *n_guards_out = n_guards;
+ return eligible_guards;
+}
+
+/** Helper: given a smartlist of either bridge_info_t (if gs->type is
+ * GS_TYPE_BRIDGE) or node_t (otherwise), pick one that can be a guard,
+ * add it as a guard, remove it from the list, and return a new
+ * entry_guard_t. Return NULL on failure. */
+static entry_guard_t *
+select_and_add_guard_item_for_sample(guard_selection_t *gs,
+ smartlist_t *eligible_guards)
+{
+ entry_guard_t *added_guard;
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const bridge_info_t *bridge = smartlist_choose(eligible_guards);
+ if (BUG(!bridge))
+ return NULL; // LCOV_EXCL_LINE
+ smartlist_remove(eligible_guards, bridge);
+ added_guard = entry_guard_add_bridge_to_sample(gs, bridge);
+ } else {
+ const node_t *node =
+ node_sl_choose_by_bandwidth(eligible_guards, WEIGHT_FOR_GUARD);
+ if (BUG(!node))
+ return NULL; // LCOV_EXCL_LINE
+ smartlist_remove(eligible_guards, node);
+ added_guard = entry_guard_add_to_sample(gs, node);
}
- if (changed)
- entry_guards_changed();
-}
-/** How long (in seconds) do we allow an entry guard to be nonfunctional,
- * unlisted, excluded, or otherwise nonusable before we give up on it? */
-#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60)
+ return added_guard;
+}
-/** Release all storage held by <b>e</b>. */
-static void
-entry_guard_free(entry_guard_t *e)
+/** Return true iff we need a consensus to maintain our */
+static int
+live_consensus_is_missing(const guard_selection_t *gs)
{
- if (!e)
- return;
- tor_free(e->chosen_by_version);
- tor_free(e);
+ tor_assert(gs);
+ if (gs->type == GS_TYPE_BRIDGE) {
+ /* We don't update bridges from the consensus; they aren't there. */
+ return 0;
+ }
+ return networkstatus_get_live_consensus(approx_time()) == NULL;
}
/**
- * Return the minimum lifetime of working entry guard, in seconds,
- * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP,
- * so that we can do the chosen_on_date randomization while achieving the
- * desired minimum lifetime.)
+ * Add new guards to the sampled guards in <b>gs</b> until there are
+ * enough usable filtered guards, but never grow the sample beyond its
+ * maximum size. Return the last guard added, or NULL if none were
+ * added.
*/
-static int32_t
-guards_get_lifetime(void)
+STATIC entry_guard_t *
+entry_guards_expand_sample(guard_selection_t *gs)
{
+ tor_assert(gs);
const or_options_t *options = get_options();
-#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */
-#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */
-#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */
- if (options->GuardLifetime >= 1) {
- return CLAMP(MIN_GUARD_LIFETIME,
- options->GuardLifetime,
- MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP;
+ if (live_consensus_is_missing(gs)) {
+ log_info(LD_GUARD, "Not expanding the sample guard set; we have "
+ "no live consensus.");
+ return NULL;
}
- return networkstatus_get_param(NULL, "GuardLifetime",
- DFLT_GUARD_LIFETIME,
- MIN_GUARD_LIFETIME,
- MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP;
-}
-
-/** Remove any entry guard which was selected by an unknown version of Tor,
- * or which was selected by a version of Tor that's known to select
- * entry guards badly, or which was selected more 2 months ago. */
-/* XXXX The "obsolete guards" and "chosen long ago guards" things should
- * probably be different functions. */
-static int
-remove_obsolete_entry_guards(time_t now)
-{
- int changed = 0, i;
- int32_t guard_lifetime = guards_get_lifetime();
-
- for (i = 0; i < smartlist_len(entry_guards); ++i) {
- entry_guard_t *entry = smartlist_get(entry_guards, i);
- const char *ver = entry->chosen_by_version;
- const char *msg = NULL;
- tor_version_t v;
- int version_is_bad = 0, date_is_bad = 0;
- if (!ver) {
- msg = "does not say what version of Tor it was selected by";
- version_is_bad = 1;
- } else if (tor_version_parse(ver, &v)) {
- msg = "does not seem to be from any recognized version of Tor";
- version_is_bad = 1;
- }
- if (!version_is_bad && entry->chosen_on_date + guard_lifetime < now) {
- /* It's been too long since the date listed in our state file. */
- msg = "was selected several months ago";
- date_is_bad = 1;
+ int n_sampled = smartlist_len(gs->sampled_entry_guards);
+ entry_guard_t *added_guard = NULL;
+ int n_usable_filtered_guards = num_reachable_filtered_guards(gs, NULL);
+ int n_guards = 0;
+ smartlist_t *eligible_guards = get_eligible_guards(options, gs, &n_guards);
+
+ const int max_sample = get_max_sample_size(gs, n_guards);
+ const int min_filtered_sample = get_min_filtered_sample_size();
+
+ log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards "
+ "in the sample, and %d eligible guards to extend it with.",
+ n_sampled, smartlist_len(eligible_guards));
+
+ while (n_usable_filtered_guards < min_filtered_sample) {
+ /* Has our sample grown too large to expand? */
+ if (n_sampled >= max_sample) {
+ log_info(LD_GUARD, "Not expanding the guard sample any further; "
+ "just hit the maximum sample threshold of %d",
+ max_sample);
+ goto done;
}
- if (version_is_bad || date_is_bad) { /* we need to drop it */
- char dbuf[HEX_DIGEST_LEN+1];
- tor_assert(msg);
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC,
- "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.",
- entry->nickname, dbuf, msg, ver?escaped(ver):"none");
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, i--);
- log_entry_guards(LOG_INFO);
- changed = 1;
+ /* Did we run out of guards? */
+ if (smartlist_len(eligible_guards) == 0) {
+ /* LCOV_EXCL_START
+ As long as MAX_SAMPLE_THRESHOLD makes can't be adjusted to
+ allow all guards to be sampled, this can't be reached.
+ */
+ log_info(LD_GUARD, "Not expanding the guard sample any further; "
+ "just ran out of eligible guards");
+ goto done;
+ /* LCOV_EXCL_STOP */
}
+
+ /* Otherwise we can add at least one new guard. */
+ added_guard = select_and_add_guard_item_for_sample(gs, eligible_guards);
+ if (!added_guard)
+ goto done; // LCOV_EXCL_LINE -- only fails on BUG.
+
+ ++n_sampled;
+
+ if (added_guard->is_usable_filtered_guard)
+ ++n_usable_filtered_guards;
}
- return changed ? 1 : 0;
+ done:
+ smartlist_free(eligible_guards);
+ return added_guard;
}
-/** Remove all entry guards that have been down or unlisted for so
- * long that we don't think they'll come up again. Return 1 if we
- * removed any, or 0 if we did nothing. */
-static int
-remove_dead_entry_guards(time_t now)
-{
- char dbuf[HEX_DIGEST_LEN+1];
- char tbuf[ISO_TIME_LEN+1];
- int i;
- int changed = 0;
-
- for (i = 0; i < smartlist_len(entry_guards); ) {
- entry_guard_t *entry = smartlist_get(entry_guards, i);
- if (entry->bad_since &&
- ! entry->path_bias_disabled &&
- entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) {
-
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- format_local_iso_time(tbuf, entry->bad_since);
- log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted "
- "since %s local time; removing.",
- entry->nickname, dbuf, tbuf);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, i);
- log_entry_guards(LOG_INFO);
- changed = 1;
- } else
- ++i;
- }
- return changed ? 1 : 0;
-}
-
-/** Remove all currently listed entry guards. So new ones will be chosen. */
-void
-remove_all_entry_guards(void)
+/**
+ * Helper: <b>guard</b> has just been removed from the sampled guards:
+ * also remove it from primary and confirmed. */
+static void
+remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs,
+ entry_guard_t *guard)
{
- char dbuf[HEX_DIGEST_LEN+1];
+ if (guard->is_primary) {
+ guard->is_primary = 0;
+ smartlist_remove_keeporder(gs->primary_entry_guards, guard);
+ } else {
+ if (BUG(smartlist_contains(gs->primary_entry_guards, guard))) {
+ smartlist_remove_keeporder(gs->primary_entry_guards, guard);
+ }
+ }
- while (smartlist_len(entry_guards)) {
- entry_guard_t *entry = smartlist_get(entry_guards, 0);
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.",
- entry->nickname, dbuf);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del(entry_guards, 0);
+ if (guard->confirmed_idx >= 0) {
+ smartlist_remove_keeporder(gs->confirmed_entry_guards, guard);
+ guard->confirmed_idx = -1;
+ guard->confirmed_on_date = 0;
+ } else {
+ if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) {
+ // LCOV_EXCL_START
+ smartlist_remove_keeporder(gs->confirmed_entry_guards, guard);
+ // LCOV_EXCL_STOP
+ }
}
- log_entry_guards(LOG_INFO);
- entry_guards_changed();
}
-/** A new directory or router-status has arrived; update the down/listed
- * status of the entry guards.
- *
- * An entry is 'down' if the directory lists it as nonrunning.
- * An entry is 'unlisted' if the directory doesn't include it.
- *
- * Don't call this on startup; only on a fresh download. Otherwise we'll
- * think that things are unlisted.
- */
-void
-entry_guards_compute_status(const or_options_t *options, time_t now)
+/** Return true iff <b>guard</b> is currently "listed" -- that is, it
+ * appears in the consensus, or as a configured bridge (as
+ * appropriate) */
+MOCK_IMPL(STATIC int,
+entry_guard_is_listed,(guard_selection_t *gs, const entry_guard_t *guard))
{
- int changed = 0;
- digestmap_t *reasons;
+ if (gs->type == GS_TYPE_BRIDGE) {
+ return NULL != get_bridge_info_for_guard(guard);
+ } else {
+ const node_t *node = node_get_by_id(guard->identity);
+
+ return node && node_is_possible_guard(node);
+ }
+}
- if (! entry_guards)
+/**
+ * Update the status of all sampled guards based on the arrival of a
+ * new consensus networkstatus document. This will include marking
+ * some guards as listed or unlisted, and removing expired guards. */
+STATIC void
+sampled_guards_update_from_consensus(guard_selection_t *gs)
+{
+ tor_assert(gs);
+ const int REMOVE_UNLISTED_GUARDS_AFTER =
+ (get_remove_unlisted_guards_after_days() * 86400);
+ const int unlisted_since_slop = REMOVE_UNLISTED_GUARDS_AFTER / 5;
+
+ // It's important to use only a live consensus here; we don't want to
+ // make changes based on anything expired or old.
+ if (live_consensus_is_missing(gs)) {
+ log_info(LD_GUARD, "Not updating the sample guard set; we have "
+ "no live consensus.");
return;
+ }
+ log_info(LD_GUARD, "Updating sampled guard status based on received "
+ "consensus.");
+
+ int n_changes = 0;
+
+ /* First: Update listed/unlisted. */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ /* XXXX #20827 check ed ID too */
+ const int is_listed = entry_guard_is_listed(gs, guard);
+
+ if (is_listed && ! guard->currently_listed) {
+ ++n_changes;
+ guard->currently_listed = 1;
+ guard->unlisted_since_date = 0;
+ log_info(LD_GUARD, "Sampled guard %s is now listed again.",
+ entry_guard_describe(guard));
+ } else if (!is_listed && guard->currently_listed) {
+ ++n_changes;
+ guard->currently_listed = 0;
+ guard->unlisted_since_date = randomize_time(approx_time(),
+ unlisted_since_slop);
+ log_info(LD_GUARD, "Sampled guard %s is now unlisted.",
+ entry_guard_describe(guard));
+ } else if (is_listed && guard->currently_listed) {
+ log_debug(LD_GUARD, "Sampled guard %s is still listed.",
+ entry_guard_describe(guard));
+ } else {
+ tor_assert(! is_listed && ! guard->currently_listed);
+ log_debug(LD_GUARD, "Sampled guard %s is still unlisted.",
+ entry_guard_describe(guard));
+ }
- if (options->EntryNodes) /* reshuffle the entry guard list if needed */
- entry_nodes_should_be_added();
-
- reasons = digestmap_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry)
- {
- const node_t *r = node_get_by_id(entry->identity);
- const char *reason = NULL;
- if (entry_guard_set_status(entry, r, now, options, &reason))
- changed = 1;
-
- if (entry->bad_since)
- tor_assert(reason);
- if (reason)
- digestmap_set(reasons, entry->identity, (char*)reason);
+ /* Clean up unlisted_since_date, just in case. */
+ if (guard->currently_listed && guard->unlisted_since_date) {
+ ++n_changes;
+ guard->unlisted_since_date = 0;
+ log_warn(LD_BUG, "Sampled guard %s was listed, but with "
+ "unlisted_since_date set. Fixing.",
+ entry_guard_describe(guard));
+ } else if (!guard->currently_listed && ! guard->unlisted_since_date) {
+ ++n_changes;
+ guard->unlisted_since_date = randomize_time(approx_time(),
+ unlisted_since_slop);
+ log_warn(LD_BUG, "Sampled guard %s was unlisted, but with "
+ "unlisted_since_date unset. Fixing.",
+ entry_guard_describe(guard));
}
- SMARTLIST_FOREACH_END(entry);
-
- if (remove_dead_entry_guards(now))
- changed = 1;
- if (remove_obsolete_entry_guards(now))
- changed = 1;
-
- if (changed) {
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- const char *reason = digestmap_get(reasons, entry->identity);
- const char *live_msg = "";
- const node_t *r = entry_is_live(entry, ENTRY_NEED_CAPACITY, &live_msg);
- log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.",
- entry->nickname,
- hex_str(entry->identity, DIGEST_LEN),
- entry->unreachable_since ? "unreachable" : "reachable",
- entry->bad_since ? "unusable" : "usable",
- reason ? ", ": "",
- reason ? reason : "",
- r ? "live" : "not live / ",
- r ? "" : live_msg);
- } SMARTLIST_FOREACH_END(entry);
- log_info(LD_CIRC, " (%d/%d entry guards are usable/new)",
- num_live_entry_guards(0), smartlist_len(entry_guards));
- log_entry_guards(LOG_INFO);
- entry_guards_changed();
+ } SMARTLIST_FOREACH_END(guard);
+
+ const time_t remove_if_unlisted_since =
+ approx_time() - REMOVE_UNLISTED_GUARDS_AFTER;
+ const time_t maybe_remove_if_sampled_before =
+ approx_time() - get_guard_lifetime();
+ const time_t remove_if_confirmed_before =
+ approx_time() - get_guard_confirmed_min_lifetime();
+
+ /* Then: remove the ones that have been junk for too long */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ int rmv = 0;
+
+ if (guard->currently_listed == 0 &&
+ guard->unlisted_since_date < remove_if_unlisted_since) {
+ /*
+ "We have a live consensus, and {IS_LISTED} is false, and
+ {FIRST_UNLISTED_AT} is over {REMOVE_UNLISTED_GUARDS_AFTER}
+ days in the past."
+ */
+ log_info(LD_GUARD, "Removing sampled guard %s: it has been unlisted "
+ "for over %d days", entry_guard_describe(guard),
+ get_remove_unlisted_guards_after_days());
+ rmv = 1;
+ } else if (guard->sampled_on_date < maybe_remove_if_sampled_before) {
+ /* We have a live consensus, and {ADDED_ON_DATE} is over
+ {GUARD_LIFETIME} ago, *and* {CONFIRMED_ON_DATE} is either
+ "never", or over {GUARD_CONFIRMED_MIN_LIFETIME} ago.
+ */
+ if (guard->confirmed_on_date == 0) {
+ rmv = 1;
+ log_info(LD_GUARD, "Removing sampled guard %s: it was sampled "
+ "over %d days ago, but never confirmed.",
+ entry_guard_describe(guard),
+ get_guard_lifetime() / 86400);
+ } else if (guard->confirmed_on_date < remove_if_confirmed_before) {
+ rmv = 1;
+ log_info(LD_GUARD, "Removing sampled guard %s: it was sampled "
+ "over %d days ago, and confirmed over %d days ago.",
+ entry_guard_describe(guard),
+ get_guard_lifetime() / 86400,
+ get_guard_confirmed_min_lifetime() / 86400);
+ }
+ }
+
+ if (rmv) {
+ ++n_changes;
+ SMARTLIST_DEL_CURRENT(gs->sampled_entry_guards, guard);
+ remove_guard_from_confirmed_and_primary_lists(gs, guard);
+ entry_guard_free(guard);
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ if (n_changes) {
+ gs->primary_guards_up_to_date = 0;
+ entry_guards_update_filtered_sets(gs);
+ /* We don't need to rebuild the confirmed list right here -- we may have
+ * removed confirmed guards above, but we can't have added any new
+ * confirmed guards.
+ */
+ entry_guards_changed_for_guard_selection(gs);
}
+}
+
+/**
+ * Return true iff <b>node</b> is a Tor relay that we are configured to
+ * be able to connect to. */
+static int
+node_passes_guard_filter(const or_options_t *options,
+ const node_t *node)
+{
+ /* NOTE: Make sure that this function stays in sync with
+ * options_transition_affects_entry_guards */
+ if (routerset_contains_node(options->ExcludeNodes, node))
+ return 0;
+
+ if (options->EntryNodes &&
+ !routerset_contains_node(options->EntryNodes, node))
+ return 0;
+
+ if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0))
+ return 0;
+
+ if (node_is_a_configured_bridge(node))
+ return 0;
- digestmap_free(reasons, NULL);
+ return 1;
}
-/** Called when a connection to an OR with the identity digest <b>digest</b>
- * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0).
- * If the OR is an entry, change that entry's up/down status.
- * Return 0 normally, or -1 if we want to tear down the new connection.
- *
- * If <b>mark_relay_status</b>, also call router_set_status() on this
- * relay.
- *
- * XXX024 change succeeded and mark_relay_status into 'int flags'.
- */
-int
-entry_guard_register_connect_status(const char *digest, int succeeded,
- int mark_relay_status, time_t now)
+/** Helper: Return true iff <b>bridge</b> passes our configuration
+ * filter-- if it is a relay that we are configured to be able to
+ * connect to. */
+static int
+bridge_passes_guard_filter(const or_options_t *options,
+ const bridge_info_t *bridge)
{
- int changed = 0;
- int refuse_conn = 0;
- int first_contact = 0;
- entry_guard_t *entry = NULL;
- int idx = -1;
- char buf[HEX_DIGEST_LEN+1];
+ tor_assert(bridge);
+ if (!bridge)
+ return 0;
- if (! entry_guards)
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge))
return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- tor_assert(e);
- if (tor_memeq(e->identity, digest, DIGEST_LEN)) {
- entry = e;
- idx = e_sl_idx;
- break;
- }
- } SMARTLIST_FOREACH_END(e);
+ /* Ignore entrynodes */
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
- if (!entry)
+ if (!fascist_firewall_allows_address_addr(&addrport->addr,
+ addrport->port,
+ FIREWALL_OR_CONNECTION,
+ 0, 0))
return 0;
- base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN);
-
- if (succeeded) {
- if (entry->unreachable_since) {
- log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again. Good.",
- entry->nickname, buf);
- entry->can_retry = 0;
- entry->unreachable_since = 0;
- entry->last_attempted = now;
- control_event_guard(entry->nickname, entry->identity, "UP");
- changed = 1;
- }
- if (!entry->made_contact) {
- entry->made_contact = 1;
- first_contact = changed = 1;
- }
- } else { /* ! succeeded */
- if (!entry->made_contact) {
- /* We've never connected to this one. */
- log_info(LD_CIRC,
- "Connection to never-contacted entry guard '%s' (%s) failed. "
- "Removing from the list. %d/%d entry guards usable/new.",
- entry->nickname, buf,
- num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, idx);
- log_entry_guards(LOG_INFO);
- changed = 1;
- } else if (!entry->unreachable_since) {
- log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). "
- "Marking as unreachable.", entry->nickname, buf);
- entry->unreachable_since = entry->last_attempted = now;
- control_event_guard(entry->nickname, entry->identity, "DOWN");
- changed = 1;
- entry->can_retry = 0; /* We gave it an early chance; no good. */
- } else {
- char tbuf[ISO_TIME_LEN+1];
- format_iso_time(tbuf, entry->unreachable_since);
- log_debug(LD_CIRC, "Failed to connect to unreachable entry guard "
- "'%s' (%s). It has been unreachable since %s.",
- entry->nickname, buf, tbuf);
- entry->last_attempted = now;
- entry->can_retry = 0; /* We gave it an early chance; no good. */
- }
- }
+ return 1;
+}
- /* if the caller asked us to, also update the is_running flags for this
- * relay */
- if (mark_relay_status)
- router_set_status(digest, succeeded);
-
- if (first_contact) {
- /* We've just added a new long-term entry guard. Perhaps the network just
- * came back? We should give our earlier entries another try too,
- * and close this connection so we don't use it before we've given
- * the others a shot. */
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- if (e == entry)
- break;
- if (e->made_contact) {
- const char *msg;
- const node_t *r = entry_is_live(e,
- ENTRY_NEED_CAPACITY | ENTRY_ASSUME_REACHABLE,
- &msg);
- if (r && e->unreachable_since) {
- refuse_conn = 1;
- e->can_retry = 1;
- }
- }
- } SMARTLIST_FOREACH_END(e);
- if (refuse_conn) {
- log_info(LD_CIRC,
- "Connected to new entry guard '%s' (%s). Marking earlier "
- "entry guards up. %d/%d entry guards usable/new.",
- entry->nickname, buf,
- num_live_entry_guards(0), smartlist_len(entry_guards));
- log_entry_guards(LOG_INFO);
- changed = 1;
+/**
+ * Return true iff <b>guard</b> is a Tor relay that we are configured to
+ * be able to connect to, and we haven't disabled it for omission from
+ * the consensus or path bias issues. */
+static int
+entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs,
+ entry_guard_t *guard)
+{
+ if (guard->currently_listed == 0)
+ return 0;
+ if (guard->pb.path_bias_disabled)
+ return 0;
+
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const bridge_info_t *bridge = get_bridge_info_for_guard(guard);
+ if (bridge == NULL)
+ return 0;
+ return bridge_passes_guard_filter(options, bridge);
+ } else {
+ const node_t *node = node_get_by_id(guard->identity);
+ if (node == NULL) {
+ // This can happen when currently_listed is true, and we're not updating
+ // it because we don't have a live consensus.
+ return 0;
}
- }
- if (changed)
- entry_guards_changed();
- return refuse_conn ? -1 : 0;
+ return node_passes_guard_filter(options, node);
+ }
}
-/** When we try to choose an entry guard, should we parse and add
- * config's EntryNodes first? */
-static int should_add_entry_nodes = 0;
-
-/** Called when the value of EntryNodes changes in our configuration. */
-void
-entry_nodes_should_be_added(void)
+/** Return true iff <b>guard</b> is in the same family as <b>node</b>.
+ */
+static int
+guard_in_node_family(const entry_guard_t *guard, const node_t *node)
{
- log_info(LD_CIRC, "EntryNodes config option set. Putting configured "
- "relays at the front of the entry guard list.");
- should_add_entry_nodes = 1;
+ const node_t *guard_node = node_get_by_id(guard->identity);
+ if (guard_node) {
+ return nodes_in_same_family(guard_node, node);
+ } else {
+ /* If we don't have a node_t for the guard node, we might have
+ * a bridge_info_t for it. So let's check to see whether the bridge
+ * address matches has any family issues.
+ *
+ * (Strictly speaking, I believe this check is unnecessary, since we only
+ * use it to avoid the exit's family when building circuits, and we don't
+ * build multihop circuits until we have a routerinfo_t for the
+ * bridge... at which point, we'll also have a node_t for the
+ * bridge. Nonetheless, it seems wise to include it, in case our
+ * assumptions change down the road. -nickm.)
+ */
+ if (get_options()->EnforceDistinctSubnets && guard->bridge_addr) {
+ tor_addr_t node_addr;
+ node_get_addr(node, &node_addr);
+ if (addrs_in_same_network_family(&node_addr,
+ &guard->bridge_addr->addr)) {
+ return 1;
+ }
+ }
+ return 0;
+ }
}
-/** Update the using_as_guard fields of all the nodes. We do this after we
- * remove entry guards from the list: This is the only function that clears
- * the using_as_guard field. */
-static void
-update_node_guard_status(void)
+/**
+ * Return true iff <b>guard</b> obeys the restrictions defined in <b>rst</b>.
+ * (If <b>rst</b> is NULL, there are no restrictions.)
+ */
+static int
+entry_guard_obeys_restriction(const entry_guard_t *guard,
+ const entry_guard_restriction_t *rst)
{
- smartlist_t *nodes = nodelist_get_list();
- SMARTLIST_FOREACH(nodes, node_t *, node, node->using_as_guard = 0);
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- node_t *node = node_get_mutable_by_id(entry->identity);
- if (node)
- node->using_as_guard = 1;
- } SMARTLIST_FOREACH_END(entry);
+ tor_assert(guard);
+ if (! rst)
+ return 1; // No restriction? No problem.
+
+ // Only one kind of restriction exists right now: excluding an exit
+ // ID and all of its family.
+ const node_t *node = node_get_by_id((const char*)rst->exclude_id);
+ if (node && guard_in_node_family(guard, node))
+ return 0;
+
+ return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN);
}
-/** Adjust the entry guards list so that it only contains entries from
- * EntryNodes, adding new entries from EntryNodes to the list as needed. */
-STATIC void
-entry_guards_set_from_config(const or_options_t *options)
+/**
+ * Update the <b>is_filtered_guard</b> and <b>is_usable_filtered_guard</b>
+ * flags on <b>guard</b>. */
+void
+entry_guard_set_filtered_flags(const or_options_t *options,
+ guard_selection_t *gs,
+ entry_guard_t *guard)
{
- smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps;
- smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list;
- const int numentryguards = decide_num_guards(options, 0);
- tor_assert(entry_guards);
+ unsigned was_filtered = guard->is_filtered_guard;
+ guard->is_filtered_guard = 0;
+ guard->is_usable_filtered_guard = 0;
- should_add_entry_nodes = 0;
+ if (entry_guard_passes_filter(options, gs, guard)) {
+ guard->is_filtered_guard = 1;
- if (!options->EntryNodes) {
- /* It's possible that a controller set EntryNodes, thus making
- * should_add_entry_nodes set, then cleared it again, all before the
- * call to choose_random_entry() that triggered us. If so, just return.
- */
- return;
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ guard->is_usable_filtered_guard = 1;
+
+ entry_guard_consider_retry(guard);
}
+ log_debug(LD_GUARD, "Updated sampled guard %s: filtered=%d; "
+ "reachable_filtered=%d.", entry_guard_describe(guard),
+ guard->is_filtered_guard, guard->is_usable_filtered_guard);
- {
- char *string = routerset_to_string(options->EntryNodes);
- log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string);
- tor_free(string);
+ if (!bool_eq(was_filtered, guard->is_filtered_guard)) {
+ /* This guard might now be primary or nonprimary. */
+ gs->primary_guards_up_to_date = 0;
}
+}
- entry_nodes = smartlist_new();
- worse_entry_nodes = smartlist_new();
- entry_fps = smartlist_new();
- old_entry_guards_on_list = smartlist_new();
- old_entry_guards_not_on_list = smartlist_new();
+/**
+ * Update the <b>is_filtered_guard</b> and <b>is_usable_filtered_guard</b>
+ * flag on every guard in <b>gs</b>. */
+STATIC void
+entry_guards_update_filtered_sets(guard_selection_t *gs)
+{
+ const or_options_t *options = get_options();
- /* Split entry guards into those on the list and those not. */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_set_filtered_flags(options, gs, guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
- routerset_get_all_nodes(entry_nodes, options->EntryNodes,
- options->ExcludeNodes, 0);
- SMARTLIST_FOREACH(entry_nodes, const node_t *,node,
- smartlist_add(entry_fps, (void*)node->identity));
+/**
+ * Return a random guard from the reachable filtered sample guards
+ * in <b>gs</b>, subject to the exclusion rules listed in <b>flags</b>.
+ * Return NULL if no such guard can be found.
+ *
+ * Make sure that the sample is big enough, and that all the filter flags
+ * are set correctly, before calling this function.
+ *
+ * If a restriction is provided in <b>rst</b>, do not return any guards that
+ * violate it.
+ **/
+STATIC entry_guard_t *
+sample_reachable_filtered_entry_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst,
+ unsigned flags)
+{
+ tor_assert(gs);
+ entry_guard_t *result = NULL;
+ const unsigned exclude_confirmed = flags & SAMPLE_EXCLUDE_CONFIRMED;
+ const unsigned exclude_primary = flags & SAMPLE_EXCLUDE_PRIMARY;
+ const unsigned exclude_pending = flags & SAMPLE_EXCLUDE_PENDING;
+ const unsigned no_update_primary = flags & SAMPLE_NO_UPDATE_PRIMARY;
+ const unsigned need_descriptor = flags & SAMPLE_EXCLUDE_NO_DESCRIPTOR;
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ const int n_reachable_filtered = num_reachable_filtered_guards(gs, rst);
+
+ log_info(LD_GUARD, "Trying to sample a reachable guard: We know of %d "
+ "in the USABLE_FILTERED set.", n_reachable_filtered);
+
+ const int min_filtered_sample = get_min_filtered_sample_size();
+ if (n_reachable_filtered < min_filtered_sample) {
+ log_info(LD_GUARD, " (That isn't enough. Trying to expand the sample.)");
+ entry_guards_expand_sample(gs);
+ }
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, {
- if (smartlist_contains_digest(entry_fps, e->identity))
- smartlist_add(old_entry_guards_on_list, e);
- else
- smartlist_add(old_entry_guards_not_on_list, e);
- });
+ if (exclude_primary && !gs->primary_guards_up_to_date && !no_update_primary)
+ entry_guards_update_primary(gs);
- /* Remove all currently configured guard nodes, excluded nodes, unreachable
- * nodes, or non-Guard nodes from entry_nodes. */
- SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) {
- if (entry_guard_get_by_id_digest(node->identity)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ /* Build the set of reachable filtered guards. */
+ smartlist_t *reachable_filtered_sample = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);// redundant, but cheap.
+ if (! entry_guard_obeys_restriction(guard, rst))
continue;
- } else if (routerset_contains_node(options->ExcludeNodes, node)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ if (! guard->is_usable_filtered_guard)
continue;
- } else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION,
- 0)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ if (exclude_confirmed && guard->confirmed_idx >= 0)
continue;
- } else if (! node->is_possible_guard) {
- smartlist_add(worse_entry_nodes, (node_t*)node);
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
- }
- } SMARTLIST_FOREACH_END(node);
+ if (exclude_primary && guard->is_primary)
+ continue;
+ if (exclude_pending && guard->is_pending)
+ continue;
+ if (need_descriptor && !guard_has_descriptor(guard))
+ continue;
+ smartlist_add(reachable_filtered_sample, guard);
+ } SMARTLIST_FOREACH_END(guard);
- /* Now build the new entry_guards list. */
- smartlist_clear(entry_guards);
- /* First, the previously configured guards that are in EntryNodes. */
- smartlist_add_all(entry_guards, old_entry_guards_on_list);
- /* Next, scramble the rest of EntryNodes, putting the guards first. */
- smartlist_shuffle(entry_nodes);
- smartlist_shuffle(worse_entry_nodes);
- smartlist_add_all(entry_nodes, worse_entry_nodes);
-
- /* Next, the rest of EntryNodes */
- SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) {
- add_an_entry_guard(node, 0, 0, 1, 0);
- if (smartlist_len(entry_guards) > numentryguards * 10)
- break;
- } SMARTLIST_FOREACH_END(node);
- log_notice(LD_GENERAL, "%d entries in guards", smartlist_len(entry_guards));
- /* Finally, free the remaining previously configured guards that are not in
- * EntryNodes. */
- SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e,
- entry_guard_free(e));
+ log_info(LD_GUARD, " (After filters [%x], we have %d guards to consider.)",
+ flags, smartlist_len(reachable_filtered_sample));
- update_node_guard_status();
+ if (smartlist_len(reachable_filtered_sample)) {
+ result = smartlist_choose(reachable_filtered_sample);
+ log_info(LD_GUARD, " (Selected %s.)",
+ result ? entry_guard_describe(result) : "<null>");
+ }
+ smartlist_free(reachable_filtered_sample);
- smartlist_free(entry_nodes);
- smartlist_free(worse_entry_nodes);
- smartlist_free(entry_fps);
- smartlist_free(old_entry_guards_on_list);
- smartlist_free(old_entry_guards_not_on_list);
- entry_guards_changed();
+ return result;
}
-/** Return 0 if we're fine adding arbitrary routers out of the
- * directory to our entry guard list, or return 1 if we have a
- * list already and we must stick to it.
+/**
+ * Helper: compare two entry_guard_t by their confirmed_idx values.
+ * Used to sort the confirmed list.
*/
-int
-entry_list_is_constrained(const or_options_t *options)
+static int
+compare_guards_by_confirmed_idx(const void **a_, const void **b_)
{
- if (options->EntryNodes)
- return 1;
- if (options->UseBridges)
+ const entry_guard_t *a = *a_, *b = *b_;
+ if (a->confirmed_idx < b->confirmed_idx)
+ return -1;
+ else if (a->confirmed_idx > b->confirmed_idx)
return 1;
- return 0;
+ else
+ return 0;
}
-/** Pick a live (up and listed) entry guard from entry_guards. If
- * <b>state</b> is non-NULL, this is for a specific circuit --
- * make sure not to pick this circuit's exit or any node in the
- * exit's family. If <b>state</b> is NULL, we're looking for a random
- * guard (likely a bridge). If <b>dirinfo</b> is not NO_DIRINFO (zero),
- * then only select from nodes that know how to answer directory questions
- * of that type. */
-const node_t *
-choose_random_entry(cpath_build_state_t *state)
+/**
+ * Find the confirmed guards from among the sampled guards in <b>gs</b>,
+ * and put them in confirmed_entry_guards in the correct
+ * order. Recalculate their indices.
+ */
+STATIC void
+entry_guards_update_confirmed(guard_selection_t *gs)
{
- return choose_random_entry_impl(state, 0, NO_DIRINFO, NULL);
+ smartlist_clear(gs->confirmed_entry_guards);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->confirmed_idx >= 0)
+ smartlist_add(gs->confirmed_entry_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ smartlist_sort(gs->confirmed_entry_guards, compare_guards_by_confirmed_idx);
+
+ int any_changed = 0;
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (guard->confirmed_idx != guard_sl_idx) {
+ any_changed = 1;
+ guard->confirmed_idx = guard_sl_idx;
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ gs->next_confirmed_idx = smartlist_len(gs->confirmed_entry_guards);
+
+ if (any_changed) {
+ entry_guards_changed_for_guard_selection(gs);
+ }
}
-/** Pick a live (up and listed) directory guard from entry_guards for
- * downloading information of type <b>type</b>. */
-const node_t *
-choose_random_dirguard(dirinfo_type_t type)
+/**
+ * Mark <b>guard</b> as a confirmed guard -- that is, one that we have
+ * connected to, and intend to use again.
+ */
+STATIC void
+make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard)
{
- return choose_random_entry_impl(NULL, 1, type, NULL);
+ if (BUG(guard->confirmed_on_date && guard->confirmed_idx >= 0))
+ return; // LCOV_EXCL_LINE
+
+ if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard)))
+ return; // LCOV_EXCL_LINE
+
+ const int GUARD_LIFETIME = get_guard_lifetime();
+ guard->confirmed_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10);
+
+ log_info(LD_GUARD, "Marking %s as a confirmed guard (index %d)",
+ entry_guard_describe(guard),
+ gs->next_confirmed_idx);
+
+ guard->confirmed_idx = gs->next_confirmed_idx++;
+ smartlist_add(gs->confirmed_entry_guards, guard);
+
+ // This confirmed guard might kick something else out of the primary
+ // guards.
+ gs->primary_guards_up_to_date = 0;
+
+ entry_guards_changed_for_guard_selection(gs);
}
-/** Filter <b>all_entry_guards</b> for usable entry guards and put them
- * in <b>live_entry_guards</b>. We filter based on whether the node is
- * currently alive, and on whether it satisfies the restrictions
- * imposed by the other arguments of this function.
- *
- * We don't place more guards than NumEntryGuards in <b>live_entry_guards</b>.
- *
- * If <b>chosen_exit</b> is set, it contains the exit node of this
- * circuit. Make sure to not use it or its family as an entry guard.
- *
- * If <b>need_uptime</b> is set, we are looking for a stable entry guard.
- * if <b>need_capacity</b> is set, we are looking for a fast entry guard.
- *
- * The rest of the arguments are the same as in choose_random_entry_impl().
- *
- * Return 1 if we should choose a guard right away. Return 0 if we
- * should try to add more nodes to our list before deciding on a
- * guard.
+/**
+ * Recalculate the list of primary guards (the ones we'd prefer to use) from
+ * the filtered sample and the confirmed list.
*/
-STATIC int
-populate_live_entry_guards(smartlist_t *live_entry_guards,
- const smartlist_t *all_entry_guards,
- const node_t *chosen_exit,
- dirinfo_type_t dirinfo_type,
- int for_directory,
- int need_uptime, int need_capacity)
+STATIC void
+entry_guards_update_primary(guard_selection_t *gs)
{
- const or_options_t *options = get_options();
- const node_t *node = NULL;
- const int num_needed = decide_num_guards(options, for_directory);
- smartlist_t *exit_family = smartlist_new();
- int retval = 0;
- entry_is_live_flags_t entry_flags = 0;
+ tor_assert(gs);
- (void) dirinfo_type;
+ // prevent recursion. Recursion is potentially very bad here.
+ static int running = 0;
+ tor_assert(!running);
+ running = 1;
- { /* Set the flags we want our entry node to have */
- if (need_uptime) {
- entry_flags |= ENTRY_NEED_UPTIME;
- }
- if (need_capacity) {
- entry_flags |= ENTRY_NEED_CAPACITY;
- }
- if (!for_directory) {
- entry_flags |= ENTRY_NEED_DESCRIPTOR;
- }
- }
+ const int N_PRIMARY_GUARDS = get_n_primary_guards();
- tor_assert(all_entry_guards);
+ smartlist_t *new_primary_guards = smartlist_new();
+ smartlist_t *old_primary_guards = smartlist_new();
+ smartlist_add_all(old_primary_guards, gs->primary_entry_guards);
- if (chosen_exit) {
- nodelist_add_node_and_family(exit_family, chosen_exit);
+ /* Set this flag now, to prevent the calls below from recursing. */
+ gs->primary_guards_up_to_date = 1;
+
+ /* First, can we fill it up with confirmed guards? */
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS)
+ break;
+ if (! guard->is_filtered_guard)
+ continue;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Can we keep any older primary guards? First remove all the ones
+ * that we already kept. */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ if (smartlist_contains(new_primary_guards, guard)) {
+ SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard);
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Now add any that are still good. */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS)
+ break;
+ if (! guard->is_filtered_guard)
+ continue;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
+ SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Mark the remaining previous primary guards as non-primary */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ guard->is_primary = 0;
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Finally, fill out the list with sampled guards. */
+ while (smartlist_len(new_primary_guards) < N_PRIMARY_GUARDS) {
+ entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, NULL,
+ SAMPLE_EXCLUDE_CONFIRMED|
+ SAMPLE_EXCLUDE_PRIMARY|
+ SAMPLE_NO_UPDATE_PRIMARY);
+ if (!guard)
+ break;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
}
- SMARTLIST_FOREACH_BEGIN(all_entry_guards, const entry_guard_t *, entry) {
- const char *msg;
- node = entry_is_live(entry, entry_flags, &msg);
- if (!node)
- continue; /* down, no point */
- if (for_directory) {
- if (!entry->is_dir_cache)
- continue; /* We need a directory and didn't get one. */
- }
- if (node == chosen_exit)
- continue; /* don't pick the same node for entry and exit */
- if (smartlist_contains(exit_family, node))
- continue; /* avoid relays that are family members of our exit */
- smartlist_add(live_entry_guards, (void*)node);
- if (!entry->made_contact) {
- /* Always start with the first not-yet-contacted entry
- * guard. Otherwise we might add several new ones, pick
- * the second new one, and now we've expanded our entry
- * guard list without needing to. */
- retval = 1;
- goto done;
- }
- if (smartlist_len(live_entry_guards) >= num_needed) {
- retval = 1;
- goto done; /* We picked enough entry guards. Done! */
+#if 1
+ /* Debugging. */
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, guard, {
+ tor_assert_nonfatal(
+ bool_eq(guard->is_primary,
+ smartlist_contains(new_primary_guards, guard)));
+ });
+#endif
+
+ int any_change = 0;
+ if (smartlist_len(gs->primary_entry_guards) !=
+ smartlist_len(new_primary_guards)) {
+ any_change = 1;
+ } else {
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, g) {
+ if (g != smartlist_get(new_primary_guards, g_sl_idx)) {
+ any_change = 1;
}
- } SMARTLIST_FOREACH_END(entry);
+ } SMARTLIST_FOREACH_END(g);
+ }
- done:
- smartlist_free(exit_family);
+ if (any_change) {
+ log_info(LD_GUARD, "Primary entry guards have changed. "
+ "New primary guard list is: ");
+ int n = smartlist_len(new_primary_guards);
+ SMARTLIST_FOREACH_BEGIN(new_primary_guards, entry_guard_t *, g) {
+ log_info(LD_GUARD, " %d/%d: %s%s%s",
+ g_sl_idx+1, n, entry_guard_describe(g),
+ g->confirmed_idx >= 0 ? " (confirmed)" : "",
+ g->is_filtered_guard ? "" : " (excluded by filter)");
+ } SMARTLIST_FOREACH_END(g);
+ }
- return retval;
+ smartlist_free(old_primary_guards);
+ smartlist_free(gs->primary_entry_guards);
+ gs->primary_entry_guards = new_primary_guards;
+ gs->primary_guards_up_to_date = 1;
+ running = 0;
}
-/** Pick a node to be used as the entry guard of a circuit.
- *
- * If <b>state</b> is set, it contains the information we know about
- * the upcoming circuit.
- *
- * If <b>for_directory</b> is set, we are looking for a directory guard.
- *
- * <b>dirinfo_type</b> contains the kind of directory information we
- * are looking for in our node, or NO_DIRINFO (zero) if we are not
- * looking for any particular directory information (when set to
- * NO_DIRINFO, the <b>dirinfo_type</b> filter is ignored).
- *
- * If <b>n_options_out</b> is set, we set it to the number of
- * candidate guard nodes we had before picking a specific guard node.
- *
- * On success, return the node that should be used as the entry guard
- * of the circuit. Return NULL if no such node could be found.
- *
- * Helper for choose_random{entry,dirguard}.
-*/
-static const node_t *
-choose_random_entry_impl(cpath_build_state_t *state, int for_directory,
- dirinfo_type_t dirinfo_type, int *n_options_out)
+/**
+ * Return the number of seconds after the last attempt at which we should
+ * retry a guard that has been failing since <b>failing_since</b>.
+ */
+static int
+get_retry_schedule(time_t failing_since, time_t now,
+ int is_primary)
{
- const or_options_t *options = get_options();
- smartlist_t *live_entry_guards = smartlist_new();
- const node_t *chosen_exit =
- state?build_state_get_exit_node(state) : NULL;
- const node_t *node = NULL;
- int need_uptime = state ? state->need_uptime : 0;
- int need_capacity = state ? state->need_capacity : 0;
- int preferred_min = 0;
- const int num_needed = decide_num_guards(options, for_directory);
- int retval = 0;
-
- if (n_options_out)
- *n_options_out = 0;
-
- if (!entry_guards)
- entry_guards = smartlist_new();
-
- if (should_add_entry_nodes)
- entry_guards_set_from_config(options);
-
- if (!entry_list_is_constrained(options) &&
- smartlist_len(entry_guards) < num_needed)
- pick_entry_guards(options, for_directory);
-
- retry:
- smartlist_clear(live_entry_guards);
-
- /* Populate the list of live entry guards so that we pick one of
- them. */
- retval = populate_live_entry_guards(live_entry_guards,
- entry_guards,
- chosen_exit,
- dirinfo_type,
- for_directory,
- need_uptime, need_capacity);
-
- if (retval == 1) { /* We should choose a guard right now. */
- goto choose_and_finish;
- }
-
- if (entry_list_is_constrained(options)) {
- /* If we prefer the entry nodes we've got, and we have at least
- * one choice, that's great. Use it. */
- preferred_min = 1;
- } else {
- /* Try to have at least 2 choices available. This way we don't
- * get stuck with a single live-but-crummy entry and just keep
- * using it.
- * (We might get 2 live-but-crummy entry guards, but so be it.) */
- preferred_min = 2;
- }
-
- if (smartlist_len(live_entry_guards) < preferred_min) {
- if (!entry_list_is_constrained(options)) {
- /* still no? try adding a new entry then */
- /* XXX if guard doesn't imply fast and stable, then we need
- * to tell add_an_entry_guard below what we want, or it might
- * be a long time til we get it. -RD */
- node = add_an_entry_guard(NULL, 0, 0, 1, for_directory);
- if (node) {
- entry_guards_changed();
- /* XXX we start over here in case the new node we added shares
- * a family with our exit node. There's a chance that we'll just
- * load up on entry guards here, if the network we're using is
- * one big family. Perhaps we should teach add_an_entry_guard()
- * to understand nodes-to-avoid-if-possible? -RD */
- goto retry;
- }
- }
- if (!node && need_uptime) {
- need_uptime = 0; /* try without that requirement */
- goto retry;
- }
- if (!node && need_capacity) {
- /* still no? last attempt, try without requiring capacity */
- need_capacity = 0;
- goto retry;
- }
+ const unsigned SIX_HOURS = 6 * 3600;
+ const unsigned FOUR_DAYS = 4 * 86400;
+ const unsigned SEVEN_DAYS = 7 * 86400;
- /* live_entry_guards may be empty below. Oh well, we tried. */
+ time_t tdiff;
+ if (now > failing_since) {
+ tdiff = now - failing_since;
+ } else {
+ tdiff = 0;
}
- choose_and_finish:
- if (entry_list_is_constrained(options)) {
- /* We need to weight by bandwidth, because our bridges or entryguards
- * were not already selected proportional to their bandwidth. */
- node = node_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD);
- } else {
- /* We choose uniformly at random here, because choose_good_entry_server()
- * already weights its choices by bandwidth, so we don't want to
- * *double*-weight our guard selection. */
- node = smartlist_choose(live_entry_guards);
+ const struct {
+ time_t maximum; int primary_delay; int nonprimary_delay;
+ } delays[] = {
+ { SIX_HOURS, 10*60, 1*60*60 },
+ { FOUR_DAYS, 90*60, 4*60*60 },
+ { SEVEN_DAYS, 4*60*60, 18*60*60 },
+ { TIME_MAX, 9*60*60, 36*60*60 }
+ };
+
+ unsigned i;
+ for (i = 0; i < ARRAY_LENGTH(delays); ++i) {
+ if (tdiff <= delays[i].maximum) {
+ return is_primary ? delays[i].primary_delay : delays[i].nonprimary_delay;
+ }
}
- if (n_options_out)
- *n_options_out = smartlist_len(live_entry_guards);
- smartlist_free(live_entry_guards);
- return node;
+ /* LCOV_EXCL_START -- can't reach, since delays ends with TIME_MAX. */
+ tor_assert_nonfatal_unreached();
+ return 36*60*60;
+ /* LCOV_EXCL_STOP */
}
-/** Parse <b>state</b> and learn about the entry guards it describes.
- * If <b>set</b> is true, and there are no errors, replace the global
- * entry_list with what we find.
- * On success, return 0. On failure, alloc into *<b>msg</b> a string
- * describing the error, and return -1.
+/**
+ * If <b>guard</b> is unreachable, consider whether enough time has passed
+ * to consider it maybe-reachable again.
*/
-int
-entry_guards_parse_state(or_state_t *state, int set, char **msg)
+STATIC void
+entry_guard_consider_retry(entry_guard_t *guard)
{
- entry_guard_t *node = NULL;
- smartlist_t *new_entry_guards = smartlist_new();
- config_line_t *line;
- time_t now = time(NULL);
- const char *state_version = state->TorVersion;
- digestmap_t *added_by = digestmap_new();
-
- *msg = NULL;
- for (line = state->EntryGuards; line; line = line->next) {
- if (!strcasecmp(line->key, "EntryGuard")) {
- smartlist_t *args = smartlist_new();
- node = tor_malloc_zero(sizeof(entry_guard_t));
- /* all entry guards on disk have been contacted */
- node->made_contact = 1;
- smartlist_add(new_entry_guards, node);
- smartlist_split_string(args, line->value, " ",
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
- if (smartlist_len(args)<2) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Too few arguments to EntryGuard");
- } else if (!is_legal_nickname(smartlist_get(args,0))) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad nickname for EntryGuard");
- } 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) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad hex digest for EntryGuard");
- }
- }
- if (smartlist_len(args) >= 3) {
- const char *is_cache = smartlist_get(args, 2);
- if (!strcasecmp(is_cache, "DirCache")) {
- node->is_dir_cache = 1;
- } else if (!strcasecmp(is_cache, "NoDirCache")) {
- node->is_dir_cache = 0;
- } else {
- log_warn(LD_CONFIG, "Bogus third argument to EntryGuard line: %s",
- escaped(is_cache));
- }
- }
- SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
- smartlist_free(args);
- if (*msg)
- break;
- } else if (!strcasecmp(line->key, "EntryGuardDownSince") ||
- !strcasecmp(line->key, "EntryGuardUnlistedSince")) {
- time_t when;
- time_t last_try = 0;
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardDownSince/UnlistedSince without EntryGuard");
- break;
- }
- if (parse_iso_time_(line->value, &when, 0)<0) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad time in EntryGuardDownSince/UnlistedSince");
- break;
- }
- if (when > now) {
- /* It's a bad idea to believe info in the future: you can wind
- * up with timeouts that aren't allowed to happen for years. */
- continue;
- }
- if (strlen(line->value) >= ISO_TIME_LEN+ISO_TIME_LEN+1) {
- /* ignore failure */
- (void) parse_iso_time(line->value+ISO_TIME_LEN+1, &last_try);
- }
- if (!strcasecmp(line->key, "EntryGuardDownSince")) {
- node->unreachable_since = when;
- node->last_attempted = last_try;
- } else {
- node->bad_since = when;
- }
- } else if (!strcasecmp(line->key, "EntryGuardAddedBy")) {
- char d[DIGEST_LEN];
- /* format is digest version date */
- if (strlen(line->value) < HEX_DIGEST_LEN+1+1+1+ISO_TIME_LEN) {
- 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] != ' ') {
- log_warn(LD_BUG, "EntryGuardAddedBy line %s does not begin with "
- "hex digest", escaped(line->value));
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ return; /* No retry needed. */
+
+ const time_t now = approx_time();
+ const int delay =
+ get_retry_schedule(guard->failing_since, now, guard->is_primary);
+ const time_t last_attempt = guard->last_tried_to_connect;
+
+ if (BUG(last_attempt == 0) ||
+ now >= last_attempt + delay) {
+ /* We should mark this retriable. */
+ char tbuf[ISO_TIME_LEN+1];
+ format_local_iso_time(tbuf, last_attempt);
+ log_info(LD_GUARD, "Marked %s%sguard %s for possible retry, since we "
+ "haven't tried to use it since %s.",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard),
+ tbuf);
+
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+ }
+}
+
+/** Tell the entry guards subsystem that we have confirmed that as of
+ * just now, we're on the internet. */
+void
+entry_guards_note_internet_connectivity(guard_selection_t *gs)
+{
+ gs->last_time_on_internet = approx_time();
+}
+
+/**
+ * Get a guard for use with a circuit. Prefer to pick a running primary
+ * guard; then a non-pending running filtered confirmed guard; then a
+ * non-pending runnable filtered guard. Update the
+ * <b>last_tried_to_connect</b> time and the <b>is_pending</b> fields of the
+ * guard as appropriate. Set <b>state_out</b> to the new guard-state
+ * of the circuit.
+ */
+STATIC entry_guard_t *
+select_entry_guard_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ const entry_guard_restriction_t *rst,
+ unsigned *state_out)
+{
+ const int need_descriptor = (usage == GUARD_USAGE_TRAFFIC);
+ tor_assert(gs);
+ tor_assert(state_out);
+
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ int num_entry_guards = get_n_primary_guards_to_use(usage);
+ smartlist_t *usable_primary_guards = smartlist_new();
+
+ /* "If any entry in PRIMARY_GUARDS has {is_reachable} status of
+ <maybe> or <yes>, return the first such guard." */
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ if (guard->is_reachable != GUARD_REACHABLE_NO) {
+ if (need_descriptor && !guard_has_descriptor(guard)) {
continue;
}
- digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1));
- } else if (!strcasecmp(line->key, "EntryGuardPathUseBias")) {
- const or_options_t *options = get_options();
- double use_cnt, success_cnt;
-
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardPathUseBias without EntryGuard");
+ *state_out = GUARD_CIRC_STATE_USABLE_ON_COMPLETION;
+ guard->last_tried_to_connect = approx_time();
+ smartlist_add(usable_primary_guards, guard);
+ if (smartlist_len(usable_primary_guards) >= num_entry_guards)
break;
- }
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ if (smartlist_len(usable_primary_guards)) {
+ entry_guard_t *guard = smartlist_choose(usable_primary_guards);
+ smartlist_free(usable_primary_guards);
+ log_info(LD_GUARD, "Selected primary guard %s for circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+ smartlist_free(usable_primary_guards);
+
+ /* "Otherwise, if the ordered intersection of {CONFIRMED_GUARDS}
+ and {USABLE_FILTERED_GUARDS} is nonempty, return the first
+ entry in that intersection that has {is_pending} set to
+ false." */
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_primary)
+ continue; /* we already considered this one. */
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ entry_guard_consider_retry(guard);
+ if (guard->is_usable_filtered_guard && ! guard->is_pending) {
+ if (need_descriptor && !guard_has_descriptor(guard))
+ continue; /* not a bug */
+ guard->is_pending = 1;
+ guard->last_tried_to_connect = approx_time();
+ *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD;
+ log_info(LD_GUARD, "No primary guards available. Selected confirmed "
+ "guard %s for circuit. Will try other guards before using "
+ "this circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+ } SMARTLIST_FOREACH_END(guard);
- if (tor_sscanf(line->value, "%lf %lf",
- &use_cnt, &success_cnt) != 2) {
- log_info(LD_GENERAL, "Malformed path use bias line for node %s",
- node->nickname);
- continue;
- }
+ /* "Otherwise, if there is no such entry, select a member at
+ random from {USABLE_FILTERED_GUARDS}." */
+ {
+ entry_guard_t *guard;
+ unsigned flags = 0;
+ if (need_descriptor)
+ flags |= SAMPLE_EXCLUDE_NO_DESCRIPTOR;
+ guard = sample_reachable_filtered_entry_guards(gs,
+ rst,
+ SAMPLE_EXCLUDE_CONFIRMED |
+ SAMPLE_EXCLUDE_PRIMARY |
+ SAMPLE_EXCLUDE_PENDING |
+ flags);
+ if (guard == NULL) {
+ log_info(LD_GUARD, "Absolutely no sampled guards were available. "
+ "Marking all guards for retry and starting from top again.");
+ mark_all_guards_maybe_reachable(gs);
+ return NULL;
+ }
+ guard->is_pending = 1;
+ guard->last_tried_to_connect = approx_time();
+ *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD;
+ log_info(LD_GUARD, "No primary or confirmed guards available. Selected "
+ "random guard %s for circuit. Will try other guards before "
+ "using this circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+}
- if (use_cnt < success_cnt) {
- int severity = LOG_INFO;
- /* If this state file was written by a Tor that would have
- * already fixed it, then the overcounting bug is still there.. */
- if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) {
- severity = LOG_NOTICE;
- }
- log_fn(severity, LD_BUG,
- "State file contains unexpectedly high usage success "
- "counts %lf/%lf for Guard %s ($%s)",
- success_cnt, use_cnt,
- node->nickname, hex_str(node->identity, DIGEST_LEN));
- success_cnt = use_cnt;
- }
+/**
+ * Note that we failed to connect to or build circuits through <b>guard</b>.
+ * Use with a guard returned by select_entry_guard_for_circuit().
+ */
+STATIC void
+entry_guards_note_guard_failure(guard_selection_t *gs,
+ entry_guard_t *guard)
+{
+ tor_assert(gs);
- node->use_attempts = use_cnt;
- node->use_successes = success_cnt;
-
- log_info(LD_GENERAL, "Read %f/%f path use bias for node %s",
- node->use_successes, node->use_attempts, node->nickname);
-
- /* Note: We rely on the < comparison here to allow us to set a 0
- * rate and disable the feature entirely. If refactoring, don't
- * change to <= */
- if (pathbias_get_use_success_count(node)/node->use_attempts
- < pathbias_get_extreme_use_rate(options) &&
- pathbias_get_dropguards(options)) {
- node->path_bias_disabled = 1;
- log_info(LD_GENERAL,
- "Path use bias is too high (%f/%f); disabling node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- }
- } else if (!strcasecmp(line->key, "EntryGuardPathBias")) {
- const or_options_t *options = get_options();
- double hop_cnt, success_cnt, timeouts, collapsed, successful_closed,
- unusable;
-
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardPathBias without EntryGuard");
- break;
- }
+ guard->is_reachable = GUARD_REACHABLE_NO;
+ guard->is_usable_filtered_guard = 0;
- /* First try 3 params, then 2. */
- /* In the long run: circuit_success ~= successful_circuit_close +
- * collapsed_circuits +
- * unusable_circuits */
- if (tor_sscanf(line->value, "%lf %lf %lf %lf %lf %lf",
- &hop_cnt, &success_cnt, &successful_closed,
- &collapsed, &unusable, &timeouts) != 6) {
- int old_success, old_hops;
- if (tor_sscanf(line->value, "%u %u", &old_success, &old_hops) != 2) {
- continue;
- }
- log_info(LD_GENERAL, "Reading old-style EntryGuardPathBias %s",
- escaped(line->value));
-
- success_cnt = old_success;
- successful_closed = old_success;
- hop_cnt = old_hops;
- timeouts = 0;
- collapsed = 0;
- unusable = 0;
- }
+ guard->is_pending = 0;
+ if (guard->failing_since == 0)
+ guard->failing_since = approx_time();
- if (hop_cnt < success_cnt) {
- int severity = LOG_INFO;
- /* If this state file was written by a Tor that would have
- * already fixed it, then the overcounting bug is still there.. */
- if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) {
- severity = LOG_NOTICE;
- }
- log_fn(severity, LD_BUG,
- "State file contains unexpectedly high success counts "
- "%lf/%lf for Guard %s ($%s)",
- success_cnt, hop_cnt,
- node->nickname, hex_str(node->identity, DIGEST_LEN));
- success_cnt = hop_cnt;
- }
+ log_info(LD_GUARD, "Recorded failure for %s%sguard %s",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard));
+}
- node->circ_attempts = hop_cnt;
- node->circ_successes = success_cnt;
-
- node->successful_circuits_closed = successful_closed;
- node->timeouts = timeouts;
- node->collapsed_circuits = collapsed;
- node->unusable_circuits = unusable;
-
- log_info(LD_GENERAL, "Read %f/%f path bias for node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- /* Note: We rely on the < comparison here to allow us to set a 0
- * rate and disable the feature entirely. If refactoring, don't
- * change to <= */
- if (pathbias_get_close_success_count(node)/node->circ_attempts
- < pathbias_get_extreme_rate(options) &&
- pathbias_get_dropguards(options)) {
- node->path_bias_disabled = 1;
- log_info(LD_GENERAL,
- "Path bias is too high (%f/%f); disabling node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- }
+/**
+ * Note that we successfully connected to, and built a circuit through
+ * <b>guard</b>. Given the old guard-state of the circuit in <b>old_state</b>,
+ * return the new guard-state of the circuit.
+ *
+ * Be aware: the circuit is only usable when its guard-state becomes
+ * GUARD_CIRC_STATE_COMPLETE.
+ **/
+STATIC unsigned
+entry_guards_note_guard_success(guard_selection_t *gs,
+ entry_guard_t *guard,
+ unsigned old_state)
+{
+ tor_assert(gs);
+
+ /* Save this, since we're about to overwrite it. */
+ const time_t last_time_on_internet = gs->last_time_on_internet;
+ gs->last_time_on_internet = approx_time();
+
+ guard->is_reachable = GUARD_REACHABLE_YES;
+ guard->failing_since = 0;
+ guard->is_pending = 0;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+
+ if (guard->confirmed_idx < 0) {
+ make_guard_confirmed(gs, guard);
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+ }
- } else {
- log_warn(LD_BUG, "Unexpected key %s", line->key);
- }
+ unsigned new_state;
+ switch (old_state) {
+ case GUARD_CIRC_STATE_COMPLETE:
+ case GUARD_CIRC_STATE_USABLE_ON_COMPLETION:
+ new_state = GUARD_CIRC_STATE_COMPLETE;
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ /* Fall through. */
+ case GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD:
+ if (guard->is_primary) {
+ /* XXXX #20832 -- I don't actually like this logic. It seems to make
+ * us a little more susceptible to evil-ISP attacks. The mitigations
+ * I'm thinking of, however, aren't local to this point, so I'll leave
+ * it alone. */
+ /* This guard may have become primary by virtue of being confirmed.
+ * If so, the circuit for it is now complete.
+ */
+ new_state = GUARD_CIRC_STATE_COMPLETE;
+ } else {
+ new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD;
+ }
+ break;
}
- SMARTLIST_FOREACH_BEGIN(new_entry_guards, entry_guard_t *, e) {
- char *sp;
- char *val = digestmap_get(added_by, e->identity);
- if (val && (sp = strchr(val, ' '))) {
- time_t when;
- *sp++ = '\0';
- if (parse_iso_time(sp, &when)<0) {
- log_warn(LD_BUG, "Can't read time %s in EntryGuardAddedBy", sp);
- } else {
- e->chosen_by_version = tor_strdup(val);
- e->chosen_on_date = when;
- }
- } 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);
- }
- }
- if (e->path_bias_disabled && !e->bad_since)
- e->bad_since = time(NULL);
+ if (! guard->is_primary) {
+ if (last_time_on_internet + get_internet_likely_down_interval()
+ < approx_time()) {
+ mark_primary_guards_maybe_reachable(gs);
}
- SMARTLIST_FOREACH_END(e);
+ }
- if (*msg || !set) {
- SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(new_entry_guards);
- } else { /* !err && set */
- if (entry_guards) {
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(entry_guards);
- }
- entry_guards = new_entry_guards;
- entry_guards_dirty = 0;
- /* XXX024 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;
+ log_info(LD_GUARD, "Recorded success for %s%sguard %s",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard));
- update_node_guard_status();
- }
- digestmap_free(added_by, tor_free_);
- return *msg ? -1 : 0;
+ return new_state;
}
-/** How long will we let a change in our guard nodes stay un-saved
- * when we are trying to avoid disk writes? */
-#define SLOW_GUARD_STATE_FLUSH_TIME 600
-/** How long will we let a change in our guard nodes stay un-saved
- * when we are not trying to avoid disk writes? */
-#define FAST_GUARD_STATE_FLUSH_TIME 30
-
-/** Our list of entry guards has changed, or some element of one
- * of our entry guards has changed. Write the changes to disk within
- * the next few minutes.
+/**
+ * Helper: Return true iff <b>a</b> has higher priority than <b>b</b>.
*/
-void
-entry_guards_changed(void)
+STATIC int
+entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b)
{
- time_t when;
- entry_guards_dirty = 1;
+ tor_assert(a && b);
+ if (a == b)
+ return 0;
- if (get_options()->AvoidDiskWrites)
- when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME;
- else
- when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME;
+ /* Confirmed is always better than unconfirmed; lower index better
+ than higher */
+ if (a->confirmed_idx < 0) {
+ if (b->confirmed_idx >= 0)
+ return 0;
+ } else {
+ if (b->confirmed_idx < 0)
+ return 1;
- /* or_state_save() will call entry_guards_update_state(). */
- or_state_mark_dirty(get_or_state(), when);
+ /* Lower confirmed_idx is better than higher. */
+ return (a->confirmed_idx < b->confirmed_idx);
+ }
+
+ /* If we reach this point, both are unconfirmed. If one is pending, it
+ * has higher priority. */
+ if (a->is_pending) {
+ if (! b->is_pending)
+ return 1;
+
+ /* Both are pending: earlier last_tried_connect wins. */
+ return a->last_tried_to_connect < b->last_tried_to_connect;
+ } else {
+ if (b->is_pending)
+ return 0;
+
+ /* Neither is pending: priorities are equal. */
+ return 0;
+ }
}
-/** If the entry guard info has not changed, do nothing and return.
- * Otherwise, free the EntryGuards piece of <b>state</b> and create
- * a new one out of the global entry_guards list, and then mark
- * <b>state</b> dirty so it will get saved to disk.
+/** Release all storage held in <b>restriction</b> */
+static void
+entry_guard_restriction_free(entry_guard_restriction_t *rst)
+{
+ tor_free(rst);
+}
+
+/**
+ * Release all storage held in <b>state</b>.
*/
void
-entry_guards_update_state(or_state_t *state)
+circuit_guard_state_free(circuit_guard_state_t *state)
{
- config_line_t **next, *line;
- if (! entry_guards_dirty)
+ if (!state)
return;
+ entry_guard_restriction_free(state->restrictions);
+ entry_guard_handle_free(state->guard);
+ tor_free(state);
+}
- config_free_lines(state->EntryGuards);
- next = &state->EntryGuards;
- *next = NULL;
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- char dbuf[HEX_DIGEST_LEN+1];
- if (!e->made_contact)
- continue; /* don't write this one to disk */
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuard");
- base16_encode(dbuf, sizeof(dbuf), e->identity, DIGEST_LEN);
- tor_asprintf(&line->value, "%s %s %sDirCache", e->nickname, dbuf,
- e->is_dir_cache ? "" : "No");
- next = &(line->next);
- if (e->unreachable_since) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardDownSince");
- line->value = tor_malloc(ISO_TIME_LEN+1+ISO_TIME_LEN+1);
- format_iso_time(line->value, e->unreachable_since);
- if (e->last_attempted) {
- line->value[ISO_TIME_LEN] = ' ';
- format_iso_time(line->value+ISO_TIME_LEN+1, e->last_attempted);
- }
- next = &(line->next);
- }
- if (e->bad_since) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardUnlistedSince");
- line->value = tor_malloc(ISO_TIME_LEN+1);
- format_iso_time(line->value, e->bad_since);
- next = &(line->next);
- }
- if (e->chosen_on_date && e->chosen_by_version &&
- !strchr(e->chosen_by_version, ' ')) {
- char d[HEX_DIGEST_LEN+1];
- char t[ISO_TIME_LEN+1];
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardAddedBy");
- base16_encode(d, sizeof(d), e->identity, DIGEST_LEN);
- format_iso_time(t, e->chosen_on_date);
- tor_asprintf(&line->value, "%s %s %s",
- d, e->chosen_by_version, t);
- next = &(line->next);
- }
- if (e->circ_attempts > 0) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardPathBias");
- /* In the long run: circuit_success ~= successful_circuit_close +
- * collapsed_circuits +
- * unusable_circuits */
- tor_asprintf(&line->value, "%f %f %f %f %f %f",
- e->circ_attempts, e->circ_successes,
- pathbias_get_close_success_count(e),
- e->collapsed_circuits,
- e->unusable_circuits, e->timeouts);
- next = &(line->next);
- }
- if (e->use_attempts > 0) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardPathUseBias");
-
- tor_asprintf(&line->value, "%f %f",
- e->use_attempts,
- pathbias_get_use_success_count(e));
- next = &(line->next);
- }
+/** Allocate and return a new circuit_guard_state_t to track the result
+ * of using <b>guard</b> for a given operation. */
+static circuit_guard_state_t *
+circuit_guard_state_new(entry_guard_t *guard, unsigned state,
+ entry_guard_restriction_t *rst)
+{
+ circuit_guard_state_t *result;
- } SMARTLIST_FOREACH_END(e);
- if (!get_options()->AvoidDiskWrites)
- or_state_mark_dirty(get_or_state(), 0);
- entry_guards_dirty = 0;
+ result = tor_malloc_zero(sizeof(circuit_guard_state_t));
+ result->guard = entry_guard_handle_new(guard);
+ result->state = state;
+ result->state_set_at = approx_time();
+ result->restrictions = rst;
+
+ return result;
}
-/** If <b>question</b> is the string "entry-guards", then dump
- * to *<b>answer</b> a newly allocated string describing all of
- * the nodes in the global entry_guards list. See control-spec.txt
- * for details.
- * For backward compatibility, we also handle the string "helper-nodes".
- * */
+/**
+ * Pick a suitable entry guard for a circuit in, and place that guard
+ * in *<b>chosen_node_out</b>. Set *<b>guard_state_out</b> to an opaque
+ * state object that will record whether the circuit is ready to be used
+ * or not. Return 0 on success; on failure, return -1.
+ *
+ * If a restriction is provided in <b>rst</b>, do not return any guards that
+ * violate it, and remember that restriction in <b>guard_state_out</b> for
+ * later use. (Takes ownership of the <b>rst</b> object.)
+ */
int
-getinfo_helper_entry_guards(control_connection_t *conn,
- const char *question, char **answer,
- const char **errmsg)
-{
- (void) conn;
- (void) errmsg;
+entry_guard_pick_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ entry_guard_restriction_t *rst,
+ const node_t **chosen_node_out,
+ circuit_guard_state_t **guard_state_out)
+{
+ tor_assert(gs);
+ tor_assert(chosen_node_out);
+ tor_assert(guard_state_out);
+ *chosen_node_out = NULL;
+ *guard_state_out = NULL;
+
+ unsigned state = 0;
+ entry_guard_t *guard =
+ select_entry_guard_for_circuit(gs, usage, rst, &state);
+ if (! guard)
+ goto fail;
+ if (BUG(state == 0))
+ goto fail;
+ const node_t *node = node_get_by_id(guard->identity);
+ // XXXX #20827 check Ed ID.
+ if (! node)
+ goto fail;
+ if (BUG(usage != GUARD_USAGE_DIRGUARD && !node_has_descriptor(node)))
+ goto fail;
+
+ *chosen_node_out = node;
+ *guard_state_out = circuit_guard_state_new(guard, state, rst);
- if (!strcmp(question,"entry-guards") ||
- !strcmp(question,"helper-nodes")) {
- smartlist_t *sl = smartlist_new();
- char tbuf[ISO_TIME_LEN+1];
- char nbuf[MAX_VERBOSE_NICKNAME_LEN+1];
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- const char *status = NULL;
- time_t when = 0;
- const node_t *node;
-
- if (!e->made_contact) {
- status = "never-connected";
- } else if (e->bad_since) {
- when = e->bad_since;
- status = "unusable";
- } else if (e->unreachable_since) {
- when = e->unreachable_since;
- status = "down";
- } else {
- status = "up";
- }
-
- node = node_get_by_id(e->identity);
- if (node) {
- node_get_verbose_nickname(node, nbuf);
- } else {
- nbuf[0] = '$';
- base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
- /* e->nickname field is not very reliable if we don't know about
- * this router any longer; don't include it. */
- }
-
- if (when) {
- format_iso_time(tbuf, when);
- smartlist_add_asprintf(sl, "%s %s %s\n", nbuf, status, tbuf);
- } else {
- smartlist_add_asprintf(sl, "%s %s\n", nbuf, status);
- }
- } SMARTLIST_FOREACH_END(e);
- *answer = smartlist_join_strings(sl, "", 0, NULL);
- SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
- smartlist_free(sl);
- }
return 0;
+ fail:
+ entry_guard_restriction_free(rst);
+ return -1;
}
-/** Return 0 if we should apply guardfraction information found in the
- * consensus. A specific consensus can be specified with the
- * <b>ns</b> argument, if NULL the most recent one will be picked.*/
-int
-should_apply_guardfraction(const networkstatus_t *ns)
+/**
+ * Called by the circuit building module when a circuit has succeeded: informs
+ * the guards code that the guard in *<b>guard_state_p</b> is working, and
+ * advances the state of the guard module. On a GUARD_USABLE_NEVER return
+ * value, the circuit is broken and should not be used. On a GUARD_USABLE_NOW
+ * return value, the circuit is ready to use. On a GUARD_MAYBE_USABLE_LATER
+ * return value, the circuit should not be used until we find out whether
+ * preferred guards will work for us.
+ */
+guard_usable_t
+entry_guard_succeeded(circuit_guard_state_t **guard_state_p)
{
- /* We need to check the corresponding torrc option and the consensus
- * parameter if we need to. */
- const or_options_t *options = get_options();
+ if (BUG(*guard_state_p == NULL))
+ return GUARD_USABLE_NEVER;
- /* If UseGuardFraction is 'auto' then check the same-named consensus
- * parameter. If the consensus parameter is not present, default to
- * "off". */
- if (options->UseGuardFraction == -1) {
- return networkstatus_get_param(ns, "UseGuardFraction",
- 0, /* default to "off" */
- 0, 1);
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard || BUG(guard->in_selection == NULL))
+ return GUARD_USABLE_NEVER;
+
+ unsigned newstate =
+ entry_guards_note_guard_success(guard->in_selection, guard,
+ (*guard_state_p)->state);
+
+ (*guard_state_p)->state = newstate;
+ (*guard_state_p)->state_set_at = approx_time();
+
+ if (newstate == GUARD_CIRC_STATE_COMPLETE) {
+ return GUARD_USABLE_NOW;
+ } else {
+ return GUARD_MAYBE_USABLE_LATER;
}
+}
- return options->UseGuardFraction;
+/** Cancel the selection of *<b>guard_state_p</b> without declaring
+ * success or failure. It is safe to call this function if success or
+ * failure _has_ already been declared. */
+void
+entry_guard_cancel(circuit_guard_state_t **guard_state_p)
+{
+ if (BUG(*guard_state_p == NULL))
+ return;
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard)
+ return;
+
+ /* XXXX prop271 -- last_tried_to_connect_at will be erroneous here, but this
+ * function will only get called in "bug" cases anyway. */
+ guard->is_pending = 0;
+ circuit_guard_state_free(*guard_state_p);
+ *guard_state_p = NULL;
}
-/* Given the original bandwidth of a guard and its guardfraction,
- * calculate how much bandwidth the guard should have as a guard and
- * as a non-guard.
- *
- * Quoting from proposal236:
- *
- * Let Wpf denote the weight from the 'bandwidth-weights' line a
- * client would apply to N for position p if it had the guard
- * flag, Wpn the weight if it did not have the guard flag, and B the
- * measured bandwidth of N in the consensus. Then instead of choosing
- * N for position p proportionally to Wpf*B or Wpn*B, clients should
- * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
- *
- * This function fills the <b>guardfraction_bw</b> structure. It sets
- * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B.
+/**
+ * Called by the circuit building module when a circuit has succeeded:
+ * informs the guards code that the guard in *<b>guard_state_p</b> is
+ * not working, and advances the state of the guard module.
*/
void
-guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
- int orig_bandwidth,
- uint32_t guardfraction_percentage)
+entry_guard_failed(circuit_guard_state_t **guard_state_p)
{
- double guardfraction_fraction;
-
- /* Turn the percentage into a fraction. */
- tor_assert(guardfraction_percentage <= 100);
- guardfraction_fraction = guardfraction_percentage / 100.0;
+ if (BUG(*guard_state_p == NULL))
+ return;
- long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth);
- tor_assert(guard_bw <= INT_MAX);
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard || BUG(guard->in_selection == NULL))
+ return;
- guardfraction_bw->guard_bw = (int) guard_bw;
+ entry_guards_note_guard_failure(guard->in_selection, guard);
- guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw;
+ (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD;
+ (*guard_state_p)->state_set_at = approx_time();
}
-/** A list of configured bridges. Whenever we actually get a descriptor
- * for one, we add it as an entry guard. Note that the order of bridges
- * in this list does not necessarily correspond to the order of bridges
- * in the torrc. */
-static smartlist_t *bridge_list = NULL;
-
-/** Mark every entry of the bridge list to be removed on our next call to
- * sweep_bridge_list unless it has first been un-marked. */
+/**
+ * Run the entry_guard_failed() function on every circuit that is
+ * pending on <b>chan</b>.
+ */
void
-mark_bridge_list(void)
+entry_guard_chan_failed(channel_t *chan)
{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b,
- b->marked_for_removal = 1);
-}
+ if (!chan)
+ return;
-/** Remove every entry of the bridge list that was marked with
- * mark_bridge_list if it has not subsequently been un-marked. */
-void
-sweep_bridge_list(void)
-{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
- if (b->marked_for_removal) {
- SMARTLIST_DEL_CURRENT(bridge_list, b);
- bridge_free(b);
+ smartlist_t *pending = smartlist_new();
+ circuit_get_all_pending_on_channel(pending, chan);
+ SMARTLIST_FOREACH_BEGIN(pending, circuit_t *, circ) {
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ continue;
+
+ origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ if (origin_circ->guard_state) {
+ /* We might have no guard state if we didn't use a guard on this
+ * circuit (eg it's for a fallback directory). */
+ entry_guard_failed(&origin_circ->guard_state);
}
- } SMARTLIST_FOREACH_END(b);
+ } SMARTLIST_FOREACH_END(circ);
+ smartlist_free(pending);
}
-/** Initialize the bridge list to empty, creating it if needed. */
-static void
-clear_bridge_list(void)
+/**
+ * Return true iff every primary guard in <b>gs</b> is believed to
+ * be unreachable.
+ */
+STATIC int
+entry_guards_all_primary_guards_are_down(guard_selection_t *gs)
+{
+ tor_assert(gs);
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ return 0;
+ } SMARTLIST_FOREACH_END(guard);
+ return 1;
+}
+
+/** Wrapper for entry_guard_has_higher_priority that compares the
+ * guard-priorities of a pair of circuits. Return 1 if <b>a</b> has higher
+ * priority than <b>b</b>.
+ *
+ * If a restriction is provided in <b>rst</b>, then do not consider
+ * <b>a</b> to have higher priority if it violates the restriction.
+ */
+static int
+circ_state_has_higher_priority(origin_circuit_t *a,
+ const entry_guard_restriction_t *rst,
+ origin_circuit_t *b)
{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b));
- smartlist_clear(bridge_list);
-}
+ circuit_guard_state_t *state_a = origin_circuit_get_guard_state(a);
+ circuit_guard_state_t *state_b = origin_circuit_get_guard_state(b);
-/** Free the bridge <b>bridge</b>. */
-static void
-bridge_free(bridge_info_t *bridge)
-{
- if (!bridge)
- return;
+ tor_assert(state_a);
+ tor_assert(state_b);
- tor_free(bridge->transport_name);
- if (bridge->socks_args) {
- SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s));
- smartlist_free(bridge->socks_args);
- }
+ entry_guard_t *guard_a = entry_guard_handle_get(state_a->guard);
+ entry_guard_t *guard_b = entry_guard_handle_get(state_b->guard);
- tor_free(bridge);
+ if (! guard_a) {
+ /* Unknown guard -- never higher priority. */
+ return 0;
+ } else if (! guard_b) {
+ /* Known guard -- higher priority than any unknown guard. */
+ return 1;
+ } else if (! entry_guard_obeys_restriction(guard_a, rst)) {
+ /* Restriction violated; guard_a cannot have higher priority. */
+ return 0;
+ } else {
+ /* Both known -- compare.*/
+ return entry_guard_has_higher_priority(guard_a, guard_b);
+ }
}
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches any of the
- * tor_addr_port_t's in <b>orports</b>, return that bridge. Else return
- * NULL. */
-static bridge_info_t *
-get_configured_bridge_by_orports_digest(const char *digest,
- const smartlist_t *orports)
-{
- if (!bridge_list)
- return NULL;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if (tor_digest_is_zero(bridge->identity)) {
- SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap)
- {
- if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 &&
- bridge->port == ap->port)
- return bridge;
- }
- SMARTLIST_FOREACH_END(ap);
+/**
+ * Look at all of the origin_circuit_t * objects in <b>all_circuits_in</b>,
+ * and see if any of them that were previously not ready to use for
+ * guard-related reasons are now ready to use. Place those circuits
+ * in <b>newly_complete_out</b>, and mark them COMPLETE.
+ *
+ * Return 1 if we upgraded any circuits, and 0 otherwise.
+ */
+int
+entry_guards_upgrade_waiting_circuits(guard_selection_t *gs,
+ const smartlist_t *all_circuits_in,
+ smartlist_t *newly_complete_out)
+{
+ tor_assert(gs);
+ tor_assert(all_circuits_in);
+ tor_assert(newly_complete_out);
+
+ if (! entry_guards_all_primary_guards_are_down(gs)) {
+ /* We only upgrade a waiting circuit if the primary guards are all
+ * down. */
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, "
+ "but not all primary guards were definitely down.");
+ return 0;
+ }
+
+ int n_waiting = 0;
+ int n_complete = 0;
+ int n_complete_blocking = 0;
+ origin_circuit_t *best_waiting_circuit = NULL;
+ smartlist_t *all_circuits = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(all_circuits_in, origin_circuit_t *, circ) {
+ // We filter out circuits that aren't ours, or which we can't
+ // reason about.
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (state == NULL)
+ continue;
+ entry_guard_t *guard = entry_guard_handle_get(state->guard);
+ if (!guard || guard->in_selection != gs)
+ continue;
+
+ smartlist_add(all_circuits, circ);
+ } SMARTLIST_FOREACH_END(circ);
+
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if BUG((state == NULL))
+ continue;
+
+ if (state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) {
+ ++n_waiting;
+ if (! best_waiting_circuit ||
+ circ_state_has_higher_priority(circ, NULL, best_waiting_circuit)) {
+ best_waiting_circuit = circ;
}
- if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
}
- SMARTLIST_FOREACH_END(bridge);
- return NULL;
-}
+ } SMARTLIST_FOREACH_END(circ);
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
- * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for
- * address/port matches only. */
-static bridge_info_t *
-get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr,
- uint16_t port,
- const char *digest)
-{
- if (!bridge_list)
- return NULL;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if ((tor_digest_is_zero(bridge->identity) || digest == NULL) &&
- !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
- bridge->port == port)
- return bridge;
- if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
+ if (! best_waiting_circuit) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, "
+ "but didn't find any.");
+ goto no_change;
+ }
+
+ /* We'll need to keep track of what restrictions were used when picking this
+ * circuit, so that we don't allow any circuit without those restrictions to
+ * block it. */
+ const entry_guard_restriction_t *rst_on_best_waiting =
+ origin_circuit_get_guard_state(best_waiting_circuit)->restrictions;
+
+ /* First look at the complete circuits: Do any block this circuit? */
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ /* "C2 "blocks" C1 if:
+ * C2 obeys all the restrictions that C1 had to obey, AND
+ * C2 has higher priority than C1, AND
+ * Either C2 is <complete>, or C2 is <waiting_for_better_guard>,
+ or C2 has been <usable_if_no_better_guard> for no more than
+ {NONPRIMARY_GUARD_CONNECT_TIMEOUT} seconds."
+ */
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if BUG((state == NULL))
+ continue;
+ if (state->state != GUARD_CIRC_STATE_COMPLETE)
+ continue;
+ ++n_complete;
+ if (circ_state_has_higher_priority(circ, rst_on_best_waiting,
+ best_waiting_circuit))
+ ++n_complete_blocking;
+ } SMARTLIST_FOREACH_END(circ);
+
+ if (n_complete_blocking) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d complete and %d guard-stalled. At least one complete "
+ "circuit had higher priority, so not upgrading.",
+ n_complete, n_waiting);
+ goto no_change;
+ }
+
+ /* " * If any circuit C1 is <waiting_for_better_guard>, AND:
+ * All primary guards have reachable status of <no>.
+ * There is no circuit C2 that "blocks" C1.
+ Then, upgrade C1 to <complete>.""
+ */
+ int n_blockers_found = 0;
+ const time_t state_set_at_cutoff =
+ approx_time() - get_nonprimary_guard_connect_timeout();
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (BUG(state == NULL))
+ continue;
+ if (state->state != GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD)
+ continue;
+ if (state->state_set_at <= state_set_at_cutoff)
+ continue;
+ if (circ_state_has_higher_priority(circ, rst_on_best_waiting,
+ best_waiting_circuit))
+ ++n_blockers_found;
+ } SMARTLIST_FOREACH_END(circ);
+
+ if (n_blockers_found) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d guard-stalled, but %d pending circuit(s) had higher "
+ "guard priority, so not upgrading.",
+ n_waiting, n_blockers_found);
+ goto no_change;
+ }
+
+ /* Okay. We have a best waiting circuit, and we aren't waiting for
+ anything better. Add all circuits with that priority to the
+ list, and call them COMPLETE. */
+ int n_succeeded = 0;
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (BUG(state == NULL))
+ continue;
+ if (circ != best_waiting_circuit && rst_on_best_waiting) {
+ /* Can't upgrade other circ with same priority as best; might
+ be blocked. */
+ continue;
}
- SMARTLIST_FOREACH_END(bridge);
- return NULL;
-}
+ if (state->state != GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD)
+ continue;
+ if (circ_state_has_higher_priority(best_waiting_circuit, NULL, circ))
+ continue;
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
- * return 1. Else return 0. If <b>digest</b> is NULL, check for
- * address/port matches only. */
-int
-addr_is_a_configured_bridge(const tor_addr_t *addr,
- uint16_t port,
- const char *digest)
-{
- tor_assert(addr);
- return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0;
-}
+ state->state = GUARD_CIRC_STATE_COMPLETE;
+ state->state_set_at = approx_time();
+ smartlist_add(newly_complete_out, circ);
+ ++n_succeeded;
+ } SMARTLIST_FOREACH_END(circ);
-/** If we have a bridge configured whose digest matches
- * <b>ei->identity_digest</b>, or a bridge with no known digest whose address
- * matches <b>ei->addr</b>:<b>ei->port</b>, return 1. Else return 0.
- * If <b>ei->onion_key</b> is NULL, check for address/port matches only. */
-int
-extend_info_is_a_configured_bridge(const extend_info_t *ei)
-{
- const char *digest = ei->onion_key ? ei->identity_digest : NULL;
- return addr_is_a_configured_bridge(&ei->addr, ei->port, digest);
-}
+ log_info(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d guard-stalled, %d complete. %d of the guard-stalled "
+ "circuit(s) had high enough priority to upgrade.",
+ n_waiting, n_complete, n_succeeded);
-/** Wrapper around get_configured_bridge_by_addr_port_digest() to look
- * it up via router descriptor <b>ri</b>. */
-static bridge_info_t *
-get_configured_bridge_by_routerinfo(const routerinfo_t *ri)
-{
- bridge_info_t *bi = NULL;
- smartlist_t *orports = router_get_all_orports(ri);
- bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest,
- orports);
- SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
- smartlist_free(orports);
- return bi;
+ tor_assert_nonfatal(n_succeeded >= 1);
+ smartlist_free(all_circuits);
+ return 1;
+
+ no_change:
+ smartlist_free(all_circuits);
+ return 0;
}
-/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */
+/**
+ * Return true iff the circuit whose state is <b>guard_state</b> should
+ * expire.
+ */
int
-routerinfo_is_a_configured_bridge(const routerinfo_t *ri)
+entry_guard_state_should_expire(circuit_guard_state_t *guard_state)
{
- return get_configured_bridge_by_routerinfo(ri) ? 1 : 0;
+ if (guard_state == NULL)
+ return 0;
+ const time_t expire_if_waiting_since =
+ approx_time() - get_nonprimary_guard_idle_timeout();
+ return (guard_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD
+ && guard_state->state_set_at < expire_if_waiting_since);
}
-/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */
+/**
+ * Update all derived pieces of the guard selection state in <b>gs</b>.
+ * Return true iff we should stop using all previously generated circuits.
+ */
int
-node_is_a_configured_bridge(const node_t *node)
+entry_guards_update_all(guard_selection_t *gs)
{
- int retval = 0;
- smartlist_t *orports = node_get_all_orports(node);
- retval = get_configured_bridge_by_orports_digest(node->identity,
- orports) != NULL;
- SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
- smartlist_free(orports);
- return retval;
+ sampled_guards_update_from_consensus(gs);
+ entry_guards_update_filtered_sets(gs);
+ entry_guards_update_confirmed(gs);
+ entry_guards_update_primary(gs);
+ return 0;
}
-/** We made a connection to a router at <b>addr</b>:<b>port</b>
- * without knowing its digest. Its digest turned out to be <b>digest</b>.
- * If it was a bridge, and we still don't know its digest, record it.
+/**
+ * Return a newly allocated string for encoding the persistent parts of
+ * <b>guard</b> to the state file.
*/
-void
-learned_router_identity(const tor_addr_t *addr, uint16_t port,
- const char *digest)
-{
- bridge_info_t *bridge =
- get_configured_bridge_by_addr_port_digest(addr, port, digest);
- if (bridge && tor_digest_is_zero(bridge->identity)) {
- char *transport_info = NULL;
- const char *transport_name =
- find_transport_name_by_bridge_addrport(addr, port);
- if (transport_name)
- tor_asprintf(&transport_info, " (with transport '%s')", transport_name);
+STATIC char *
+entry_guard_encode_for_state(entry_guard_t *guard)
+{
+ /*
+ * The meta-format we use is K=V K=V K=V... where K can be any
+ * characters excepts space and =, and V can be any characters except
+ * space. The order of entries is not allowed to matter.
+ * Unrecognized K=V entries are persisted; recognized but erroneous
+ * entries are corrected.
+ */
- memcpy(bridge->identity, digest, DIGEST_LEN);
- log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.",
- hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port),
- transport_info ? transport_info : "");
- tor_free(transport_info);
+ smartlist_t *result = smartlist_new();
+ char tbuf[ISO_TIME_LEN+1];
+
+ tor_assert(guard);
+
+ smartlist_add_asprintf(result, "in=%s", guard->selection_name);
+ smartlist_add_asprintf(result, "rsa_id=%s",
+ hex_str(guard->identity, DIGEST_LEN));
+ if (guard->bridge_addr) {
+ smartlist_add_asprintf(result, "bridge_addr=%s:%d",
+ fmt_and_decorate_addr(&guard->bridge_addr->addr),
+ guard->bridge_addr->port);
+ }
+ if (strlen(guard->nickname) && is_legal_nickname(guard->nickname)) {
+ smartlist_add_asprintf(result, "nickname=%s", guard->nickname);
}
-}
-/** Return true if <b>bridge</b> has the same identity digest as
- * <b>digest</b>. If <b>digest</b> is NULL, it matches
- * bridges with unspecified identity digests. */
-static int
-bridge_has_digest(const bridge_info_t *bridge, const char *digest)
-{
- if (digest)
- return tor_memeq(digest, bridge->identity, DIGEST_LEN);
- else
- return tor_digest_is_zero(bridge->identity);
+ format_iso_time_nospace(tbuf, guard->sampled_on_date);
+ smartlist_add_asprintf(result, "sampled_on=%s", tbuf);
+
+ if (guard->sampled_by_version) {
+ smartlist_add_asprintf(result, "sampled_by=%s",
+ guard->sampled_by_version);
+ }
+
+ if (guard->unlisted_since_date > 0) {
+ format_iso_time_nospace(tbuf, guard->unlisted_since_date);
+ smartlist_add_asprintf(result, "unlisted_since=%s", tbuf);
+ }
+
+ smartlist_add_asprintf(result, "listed=%d",
+ (int)guard->currently_listed);
+
+ if (guard->confirmed_idx >= 0) {
+ format_iso_time_nospace(tbuf, guard->confirmed_on_date);
+ smartlist_add_asprintf(result, "confirmed_on=%s", tbuf);
+
+ smartlist_add_asprintf(result, "confirmed_idx=%d", guard->confirmed_idx);
+ }
+
+ const double EPSILON = 1.0e-6;
+
+ /* Make a copy of the pathbias object, since we will want to update
+ some of them */
+ guard_pathbias_t *pb = tor_memdup(&guard->pb, sizeof(*pb));
+ pb->use_successes = pathbias_get_use_success_count(guard);
+ pb->successful_circuits_closed = pathbias_get_close_success_count(guard);
+
+ #define PB_FIELD(field) do { \
+ if (pb->field >= EPSILON) { \
+ smartlist_add_asprintf(result, "pb_" #field "=%f", pb->field); \
+ } \
+ } while (0)
+ PB_FIELD(use_attempts);
+ PB_FIELD(use_successes);
+ PB_FIELD(circ_attempts);
+ PB_FIELD(circ_successes);
+ PB_FIELD(successful_circuits_closed);
+ PB_FIELD(collapsed_circuits);
+ PB_FIELD(unusable_circuits);
+ PB_FIELD(timeouts);
+ tor_free(pb);
+#undef PB_FIELD
+
+ if (guard->extra_state_fields)
+ smartlist_add_strdup(result, guard->extra_state_fields);
+
+ char *joined = smartlist_join_strings(result, " ", 0, NULL);
+ SMARTLIST_FOREACH(result, char *, cp, tor_free(cp));
+ smartlist_free(result);
+
+ return joined;
}
-/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional
- * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously
- * existing bridge with the same address and port, and warn the user as
- * appropriate.
+/**
+ * Given a string generated by entry_guard_encode_for_state(), parse it
+ * (if possible) and return an entry_guard_t object for it. Return NULL
+ * on complete failure.
*/
-static void
-bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port,
- const char *digest, const char *transport_name)
-{
- /* Iterate the already-registered bridge list:
+STATIC entry_guard_t *
+entry_guard_parse_from_state(const char *s)
+{
+ /* Unrecognized entries get put in here. */
+ smartlist_t *extra = smartlist_new();
+
+ /* These fields get parsed from the string. */
+ char *in = NULL;
+ char *rsa_id = NULL;
+ char *nickname = NULL;
+ char *sampled_on = NULL;
+ char *sampled_by = NULL;
+ char *unlisted_since = NULL;
+ char *listed = NULL;
+ char *confirmed_on = NULL;
+ char *confirmed_idx = NULL;
+ char *bridge_addr = NULL;
+
+ // pathbias
+ char *pb_use_attempts = NULL;
+ char *pb_use_successes = NULL;
+ char *pb_circ_attempts = NULL;
+ char *pb_circ_successes = NULL;
+ char *pb_successful_circuits_closed = NULL;
+ char *pb_collapsed_circuits = NULL;
+ char *pb_unusable_circuits = NULL;
+ char *pb_timeouts = NULL;
+
+ /* Split up the entries. Put the ones we know about in strings and the
+ * rest in "extra". */
+ {
+ smartlist_t *entries = smartlist_new();
+
+ strmap_t *vals = strmap_new(); // Maps keyword to location
+#define FIELD(f) \
+ strmap_set(vals, #f, &f);
+ FIELD(in);
+ FIELD(rsa_id);
+ FIELD(nickname);
+ FIELD(sampled_on);
+ FIELD(sampled_by);
+ FIELD(unlisted_since);
+ FIELD(listed);
+ FIELD(confirmed_on);
+ FIELD(confirmed_idx);
+ FIELD(bridge_addr);
+ FIELD(pb_use_attempts);
+ FIELD(pb_use_successes);
+ FIELD(pb_circ_attempts);
+ FIELD(pb_circ_successes);
+ FIELD(pb_successful_circuits_closed);
+ FIELD(pb_collapsed_circuits);
+ FIELD(pb_unusable_circuits);
+ FIELD(pb_timeouts);
+#undef FIELD
+
+ smartlist_split_string(entries, s, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+
+ SMARTLIST_FOREACH_BEGIN(entries, char *, entry) {
+ const char *eq = strchr(entry, '=');
+ if (!eq) {
+ smartlist_add(extra, entry);
+ continue;
+ }
+ char *key = tor_strndup(entry, eq-entry);
+ char **target = strmap_get(vals, key);
+ if (target == NULL || *target != NULL) {
+ /* unrecognized or already set */
+ smartlist_add(extra, entry);
+ tor_free(key);
+ continue;
+ }
- If you find a bridge with the same adress and port, mark it for
- removal. It doesn't make sense to have two active bridges with
- the same IP:PORT. If the bridge in question has a different
- digest or transport than <b>digest</b>/<b>transport_name</b>,
- it's probably a misconfiguration and we should warn the user.
- */
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
- if (bridge->marked_for_removal)
- continue;
+ *target = tor_strdup(eq+1);
+ tor_free(key);
+ tor_free(entry);
+ } SMARTLIST_FOREACH_END(entry);
- if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) {
-
- bridge->marked_for_removal = 1;
-
- if (!bridge_has_digest(bridge, digest) ||
- strcmp_opt(bridge->transport_name, transport_name)) {
- /* warn the user */
- char *bridge_description_new, *bridge_description_old;
- tor_asprintf(&bridge_description_new, "%s:%s:%s",
- fmt_addrport(addr, port),
- digest ? hex_str(digest, DIGEST_LEN) : "",
- transport_name ? transport_name : "");
- tor_asprintf(&bridge_description_old, "%s:%s:%s",
- fmt_addrport(&bridge->addr, bridge->port),
- tor_digest_is_zero(bridge->identity) ?
- "" : hex_str(bridge->identity,DIGEST_LEN),
- bridge->transport_name ? bridge->transport_name : "");
-
- log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict"
- " with the already registered bridge '%s'. We will discard"
- " the old bridge and keep '%s'. If this is not what you"
- " wanted, please change your configuration file accordingly.",
- bridge_description_new, bridge_description_old,
- bridge_description_new);
-
- tor_free(bridge_description_new);
- tor_free(bridge_description_old);
- }
- }
- } SMARTLIST_FOREACH_END(bridge);
-}
+ smartlist_free(entries);
+ strmap_free(vals, NULL);
+ }
-/** Return True if we have a bridge that uses a transport with name
- * <b>transport_name</b>. */
-MOCK_IMPL(int,
-transport_is_needed, (const char *transport_name))
-{
- if (!bridge_list)
- return 0;
+ entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t));
+ guard->is_persistent = 1;
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (bridge->transport_name &&
- !strcmp(bridge->transport_name, transport_name))
- return 1;
- } SMARTLIST_FOREACH_END(bridge);
+ if (in == NULL) {
+ log_warn(LD_CIRC, "Guard missing 'in' field");
+ goto err;
+ }
- return 0;
-}
+ guard->selection_name = in;
+ in = NULL;
-/** Register the bridge information in <b>bridge_line</b> to the
- * bridge subsystem. Steals reference of <b>bridge_line</b>. */
-void
-bridge_add_from_config(bridge_line_t *bridge_line)
-{
- bridge_info_t *b;
+ if (rsa_id == NULL) {
+ log_warn(LD_CIRC, "Guard missing RSA ID field");
+ goto err;
+ }
- { /* Log the bridge we are about to register: */
- log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)",
- fmt_addrport(&bridge_line->addr, bridge_line->port),
- bridge_line->transport_name ?
- bridge_line->transport_name : "no transport",
- tor_digest_is_zero(bridge_line->digest) ?
- "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN));
+ /* Process the identity and nickname. */
+ if (base16_decode(guard->identity, sizeof(guard->identity),
+ rsa_id, strlen(rsa_id)) != DIGEST_LEN) {
+ log_warn(LD_CIRC, "Unable to decode guard identity %s", escaped(rsa_id));
+ goto err;
+ }
- if (bridge_line->socks_args) { /* print socks arguments */
- int i = 0;
+ if (nickname) {
+ strlcpy(guard->nickname, nickname, sizeof(guard->nickname));
+ } else {
+ guard->nickname[0]='$';
+ base16_encode(guard->nickname+1, sizeof(guard->nickname)-1,
+ guard->identity, DIGEST_LEN);
+ }
- tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+ if (bridge_addr) {
+ tor_addr_port_t res;
+ memset(&res, 0, sizeof(res));
+ int r = tor_addr_port_parse(LOG_WARN, bridge_addr,
+ &res.addr, &res.port, -1);
+ if (r == 0)
+ guard->bridge_addr = tor_memdup(&res, sizeof(res));
+ /* On error, we already warned. */
+ }
- log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:",
- smartlist_len(bridge_line->socks_args));
- SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg,
- log_debug(LD_CONFIG, "%d: %s", ++i, arg));
+ /* Process the various time fields. */
+
+#define HANDLE_TIME(field) do { \
+ if (field) { \
+ int r = parse_iso_time_nospace(field, &field ## _time); \
+ if (r < 0) { \
+ log_warn(LD_CIRC, "Unable to parse %s %s from guard", \
+ #field, escaped(field)); \
+ field##_time = -1; \
+ } \
+ } \
+ } while (0)
+
+ time_t sampled_on_time = 0;
+ time_t unlisted_since_time = 0;
+ time_t confirmed_on_time = 0;
+
+ HANDLE_TIME(sampled_on);
+ HANDLE_TIME(unlisted_since);
+ HANDLE_TIME(confirmed_on);
+
+ if (sampled_on_time <= 0)
+ sampled_on_time = approx_time();
+ if (unlisted_since_time < 0)
+ unlisted_since_time = 0;
+ if (confirmed_on_time < 0)
+ confirmed_on_time = 0;
+
+ #undef HANDLE_TIME
+
+ guard->sampled_on_date = sampled_on_time;
+ guard->unlisted_since_date = unlisted_since_time;
+ guard->confirmed_on_date = confirmed_on_time;
+
+ /* Take sampled_by_version verbatim. */
+ guard->sampled_by_version = sampled_by;
+ sampled_by = NULL; /* prevent free */
+
+ /* Listed is a boolean */
+ if (listed && strcmp(listed, "0"))
+ guard->currently_listed = 1;
+
+ /* The index is a nonnegative integer. */
+ guard->confirmed_idx = -1;
+ if (confirmed_idx) {
+ int ok=1;
+ long idx = tor_parse_long(confirmed_idx, 10, 0, INT_MAX, &ok, NULL);
+ if (! ok) {
+ log_warn(LD_GUARD, "Guard has invalid confirmed_idx %s",
+ escaped(confirmed_idx));
+ } else {
+ guard->confirmed_idx = (int)idx;
}
}
- bridge_resolve_conflicts(&bridge_line->addr,
- bridge_line->port,
- bridge_line->digest,
- bridge_line->transport_name);
+ /* Anything we didn't recognize gets crammed together */
+ if (smartlist_len(extra) > 0) {
+ guard->extra_state_fields = smartlist_join_strings(extra, " ", 0, NULL);
+ }
- b = tor_malloc_zero(sizeof(bridge_info_t));
- tor_addr_copy(&b->addr, &bridge_line->addr);
- b->port = bridge_line->port;
- memcpy(b->identity, bridge_line->digest, DIGEST_LEN);
- if (bridge_line->transport_name)
- b->transport_name = bridge_line->transport_name;
- b->fetch_status.schedule = DL_SCHED_BRIDGE;
- b->socks_args = bridge_line->socks_args;
- if (!bridge_list)
- bridge_list = smartlist_new();
+ /* initialize non-persistent fields */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+
+#define PB_FIELD(field) \
+ do { \
+ if (pb_ ## field) { \
+ int ok = 1; \
+ double r = tor_parse_double(pb_ ## field, 0.0, 1e9, &ok, NULL); \
+ if (! ok) { \
+ log_warn(LD_CIRC, "Guard has invalid pb_%s %s", \
+ #field, pb_ ## field); \
+ } else { \
+ guard->pb.field = r; \
+ } \
+ } \
+ } while (0)
+ PB_FIELD(use_attempts);
+ PB_FIELD(use_successes);
+ PB_FIELD(circ_attempts);
+ PB_FIELD(circ_successes);
+ PB_FIELD(successful_circuits_closed);
+ PB_FIELD(collapsed_circuits);
+ PB_FIELD(unusable_circuits);
+ PB_FIELD(timeouts);
+#undef PB_FIELD
+
+ pathbias_check_use_success_count(guard);
+ pathbias_check_close_success_count(guard);
+
+ /* We update everything on this guard later, after we've parsed
+ * everything. */
+
+ goto done;
+
+ err:
+ // only consider it an error if the guard state was totally unparseable.
+ entry_guard_free(guard);
+ guard = NULL;
- tor_free(bridge_line); /* Deallocate bridge_line now. */
+ done:
+ tor_free(in);
+ tor_free(rsa_id);
+ tor_free(nickname);
+ tor_free(sampled_on);
+ tor_free(sampled_by);
+ tor_free(unlisted_since);
+ tor_free(listed);
+ tor_free(confirmed_on);
+ tor_free(confirmed_idx);
+ tor_free(bridge_addr);
+ tor_free(pb_use_attempts);
+ tor_free(pb_use_successes);
+ tor_free(pb_circ_attempts);
+ tor_free(pb_circ_successes);
+ tor_free(pb_successful_circuits_closed);
+ tor_free(pb_collapsed_circuits);
+ tor_free(pb_unusable_circuits);
+ tor_free(pb_timeouts);
+
+ SMARTLIST_FOREACH(extra, char *, cp, tor_free(cp));
+ smartlist_free(extra);
+
+ return guard;
+}
- smartlist_add(bridge_list, b);
+/**
+ * Replace the Guards entries in <b>state</b> with a list of all our sampled
+ * guards.
+ */
+static void
+entry_guards_update_guards_in_state(or_state_t *state)
+{
+ if (!guard_contexts)
+ return;
+ config_line_t *lines = NULL;
+ config_line_t **nextline = &lines;
+
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_persistent == 0)
+ continue;
+ *nextline = tor_malloc_zero(sizeof(config_line_t));
+ (*nextline)->key = tor_strdup("Guard");
+ (*nextline)->value = entry_guard_encode_for_state(guard);
+ nextline = &(*nextline)->next;
+ } SMARTLIST_FOREACH_END(guard);
+ } SMARTLIST_FOREACH_END(gs);
+
+ config_free_lines(state->Guard);
+ state->Guard = lines;
}
-/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */
+/**
+ * Replace our sampled guards from the Guards entries in <b>state</b>. Return 0
+ * on success, -1 on failure. (If <b>set</b> is true, replace nothing -- only
+ * check whether replacing would work.)
+ */
static int
-routerset_contains_bridge(const routerset_t *routerset,
- const bridge_info_t *bridge)
+entry_guards_load_guards_from_state(or_state_t *state, int set)
+{
+ const config_line_t *line = state->Guard;
+ int n_errors = 0;
+
+ if (!guard_contexts)
+ guard_contexts = smartlist_new();
+
+ /* Wipe all our existing guard info. (we shouldn't have any, but
+ * let's be safe.) */
+ if (set) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ guard_selection_free(gs);
+ if (curr_guard_context == gs)
+ curr_guard_context = NULL;
+ SMARTLIST_DEL_CURRENT(guard_contexts, gs);
+ } SMARTLIST_FOREACH_END(gs);
+ }
+
+ for ( ; line != NULL; line = line->next) {
+ entry_guard_t *guard = entry_guard_parse_from_state(line->value);
+ if (guard == NULL) {
+ ++n_errors;
+ continue;
+ }
+ tor_assert(guard->selection_name);
+ if (!strcmp(guard->selection_name, "legacy")) {
+ ++n_errors;
+ entry_guard_free(guard);
+ continue;
+ }
+
+ if (set) {
+ guard_selection_t *gs;
+ gs = get_guard_selection_by_name(guard->selection_name,
+ GS_TYPE_INFER, 1);
+ tor_assert(gs);
+ smartlist_add(gs->sampled_entry_guards, guard);
+ guard->in_selection = gs;
+ } else {
+ entry_guard_free(guard);
+ }
+ }
+
+ if (set) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ entry_guards_update_all(gs);
+ } SMARTLIST_FOREACH_END(gs);
+ }
+ return n_errors ? -1 : 0;
+}
+
+/** If <b>digest</b> matches the identity of any node in the
+ * entry_guards list for the provided guard selection state,
+ return that node. Else return NULL. */
+entry_guard_t *
+entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs,
+ const char *digest)
{
- int result;
- extend_info_t *extinfo;
- tor_assert(bridge);
- if (!routerset)
- return 0;
+ return get_sampled_guard_with_id(gs, (const uint8_t*)digest);
+}
- extinfo = extend_info_new(
- NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port);
- result = routerset_contains_extendinfo(routerset, extinfo);
- extend_info_free(extinfo);
- return result;
+/** Return the node_t associated with a single entry_guard_t. May
+ * return NULL if the guard is not currently in the consensus. */
+const node_t *
+entry_guard_find_node(const entry_guard_t *guard)
+{
+ tor_assert(guard);
+ return node_get_by_id(guard->identity);
}
-/** If <b>digest</b> is one of our known bridges, return it. */
-static bridge_info_t *
-find_bridge_by_digest(const char *digest)
+/** If <b>digest</b> matches the identity of any node in the
+ * entry_guards list for the default guard selection state,
+ return that node. Else return NULL. */
+entry_guard_t *
+entry_guard_get_by_id_digest(const char *digest)
{
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge,
- {
- if (tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
- });
- return NULL;
+ return entry_guard_get_by_id_digest_for_guard_selection(
+ get_guard_selection_info(), digest);
}
-/** Given the <b>addr</b> and <b>port</b> of a bridge, if that bridge
- * supports a pluggable transport, return its name. Otherwise, return
- * NULL. */
-const char *
-find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+/** We are about to connect to bridge with identity <b>digest</b> to fetch its
+ * descriptor. Create a new guard state for this connection and return it. */
+circuit_guard_state_t *
+get_guard_state_for_bridge_desc_fetch(const char *digest)
{
- if (!bridge_list)
+ circuit_guard_state_t *guard_state = NULL;
+ entry_guard_t *guard = NULL;
+
+ guard = entry_guard_get_by_id_digest_for_guard_selection(
+ get_guard_selection_info(), digest);
+ if (!guard) {
return NULL;
+ }
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (tor_addr_eq(&bridge->addr, addr) &&
- (bridge->port == port))
- return bridge->transport_name;
- } SMARTLIST_FOREACH_END(bridge);
+ /* Update the guard last_tried_to_connect time since it's checked by the
+ * guard susbsystem. */
+ guard->last_tried_to_connect = approx_time();
- return NULL;
+ /* Create the guard state */
+ guard_state = circuit_guard_state_new(guard,
+ GUARD_CIRC_STATE_USABLE_ON_COMPLETION,
+ NULL);
+
+ return guard_state;
}
-/** If <b>addr</b> and <b>port</b> match the address and port of a
- * bridge of ours that uses pluggable transports, place its transport
- * in <b>transport</b>.
- *
- * Return 0 on success (found a transport, or found a bridge with no
- * transport, or found no bridge); return -1 if we should be using a
- * transport, but the transport could not be found.
+/** Release all storage held by <b>e</b>. */
+STATIC void
+entry_guard_free(entry_guard_t *e)
+{
+ if (!e)
+ return;
+ entry_guard_handles_clear(e);
+ tor_free(e->sampled_by_version);
+ tor_free(e->extra_state_fields);
+ tor_free(e->selection_name);
+ tor_free(e->bridge_addr);
+ tor_free(e);
+}
+
+/** Return 0 if we're fine adding arbitrary routers out of the
+ * directory to our entry guard list, or return 1 if we have a
+ * list already and we must stick to it.
*/
int
-get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
- const transport_t **transport)
+entry_list_is_constrained(const or_options_t *options)
{
- *transport = NULL;
- if (!bridge_list)
- return 0;
-
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (tor_addr_eq(&bridge->addr, addr) &&
- (bridge->port == port)) { /* bridge matched */
- if (bridge->transport_name) { /* it also uses pluggable transports */
- *transport = transport_get_by_name(bridge->transport_name);
- if (*transport == NULL) { /* it uses pluggable transports, but
- the transport could not be found! */
- return -1;
- }
- return 0;
- } else { /* bridge matched, but it doesn't use transports. */
- break;
- }
- }
- } SMARTLIST_FOREACH_END(bridge);
-
- *transport = NULL;
+ // XXXX #21425 look at the current selection.
+ if (options->EntryNodes)
+ return 1;
+ if (options->UseBridges)
+ return 1;
return 0;
}
-/** Return a smartlist containing all the SOCKS arguments that we
- * should pass to the SOCKS proxy. */
-const smartlist_t *
-get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+/** Return the number of bridges that have descriptors that are marked with
+ * purpose 'bridge' and are running.
+ */
+int
+num_bridges_usable(void)
{
- bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr,
- port,
- NULL);
- return bridge ? bridge->socks_args : NULL;
+ int n_options = 0;
+
+ tor_assert(get_options()->UseBridges);
+ guard_selection_t *gs = get_guard_selection_info();
+ tor_assert(gs->type == GS_TYPE_BRIDGE);
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_reachable == GUARD_REACHABLE_NO)
+ continue;
+ if (tor_digest_is_zero(guard->identity))
+ continue;
+ const node_t *node = node_get_by_id(guard->identity);
+ if (node && node->ri)
+ ++n_options;
+ } SMARTLIST_FOREACH_END(guard);
+
+ return n_options;
}
-/** We need to ask <b>bridge</b> for its server descriptor. */
+/** Check the pathbias use success count of <b>node</b> and disable it if it
+ * goes over our thresholds. */
static void
-launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
+pathbias_check_use_success_count(entry_guard_t *node)
{
const or_options_t *options = get_options();
-
- if (connection_get_by_type_addr_port_purpose(
- CONN_TYPE_DIR, &bridge->addr, bridge->port,
- DIR_PURPOSE_FETCH_SERVERDESC))
- return; /* it's already on the way */
-
- if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
- download_status_mark_impossible(&bridge->fetch_status);
- log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
- safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
- return;
+ const double EPSILON = 1.0e-9;
+
+ /* Note: We rely on the < comparison here to allow us to set a 0
+ * rate and disable the feature entirely. If refactoring, don't
+ * change to <= */
+ if (node->pb.use_attempts > EPSILON &&
+ pathbias_get_use_success_count(node)/node->pb.use_attempts
+ < pathbias_get_extreme_use_rate(options) &&
+ pathbias_get_dropguards(options)) {
+ node->pb.path_bias_disabled = 1;
+ log_info(LD_GENERAL,
+ "Path use bias is too high (%f/%f); disabling node %s",
+ node->pb.circ_successes, node->pb.circ_attempts,
+ node->nickname);
}
+}
- /* Until we get a descriptor for the bridge, we only know one address for
- * it. */
- if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
- FIREWALL_OR_CONNECTION, 0, 0)) {
- log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a "
- "bridge, but that bridge is not reachable through our "
- "firewall.");
- return;
+/** Check the pathbias close count of <b>node</b> and disable it if it goes
+ * over our thresholds. */
+static void
+pathbias_check_close_success_count(entry_guard_t *node)
+{
+ const or_options_t *options = get_options();
+ const double EPSILON = 1.0e-9;
+
+ /* Note: We rely on the < comparison here to allow us to set a 0
+ * rate and disable the feature entirely. If refactoring, don't
+ * change to <= */
+ if (node->pb.circ_attempts > EPSILON &&
+ pathbias_get_close_success_count(node)/node->pb.circ_attempts
+ < pathbias_get_extreme_rate(options) &&
+ pathbias_get_dropguards(options)) {
+ node->pb.path_bias_disabled = 1;
+ log_info(LD_GENERAL,
+ "Path bias is too high (%f/%f); disabling node %s",
+ node->pb.circ_successes, node->pb.circ_attempts,
+ node->nickname);
}
-
- directory_initiate_command(&bridge->addr, bridge->port,
- NULL, 0, /*no dirport*/
- bridge->identity,
- DIR_PURPOSE_FETCH_SERVERDESC,
- ROUTER_PURPOSE_BRIDGE,
- DIRIND_ONEHOP, "authority.z", NULL, 0, 0);
}
-/** Fetching the bridge descriptor from the bridge authority returned a
- * "not found". Fall back to trying a direct fetch. */
-void
-retry_bridge_descriptor_fetch_directly(const char *digest)
+/** Parse <b>state</b> and learn about the entry guards it describes.
+ * If <b>set</b> is true, and there are no errors, replace the guard
+ * list in the default guard selection context with what we find.
+ * On success, return 0. On failure, alloc into *<b>msg</b> a string
+ * describing the error, and return -1.
+ */
+int
+entry_guards_parse_state(or_state_t *state, int set, char **msg)
{
- bridge_info_t *bridge = find_bridge_by_digest(digest);
- if (!bridge)
- return; /* not found? oh well. */
+ entry_guards_dirty = 0;
+ int r1 = entry_guards_load_guards_from_state(state, set);
+ entry_guards_dirty = 0;
- launch_direct_bridge_descriptor_fetch(bridge);
+ if (r1 < 0) {
+ if (msg && *msg == NULL) {
+ *msg = tor_strdup("parsing error");
+ }
+ return -1;
+ }
+ return 0;
}
-/** For each bridge in our list for which we don't currently have a
- * descriptor, fetch a new copy of its descriptor -- either directly
- * from the bridge or via a bridge authority. */
+/** How long will we let a change in our guard nodes stay un-saved
+ * when we are trying to avoid disk writes? */
+#define SLOW_GUARD_STATE_FLUSH_TIME 600
+/** How long will we let a change in our guard nodes stay un-saved
+ * when we are not trying to avoid disk writes? */
+#define FAST_GUARD_STATE_FLUSH_TIME 30
+
+/** Our list of entry guards has changed for a particular guard selection
+ * context, or some element of one of our entry guards has changed for one.
+ * Write the changes to disk within the next few minutes.
+ */
void
-fetch_bridge_descriptors(const or_options_t *options, time_t now)
+entry_guards_changed_for_guard_selection(guard_selection_t *gs)
{
- int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO);
- int ask_bridge_directly;
- int can_use_bridge_authority;
+ time_t when;
- if (!bridge_list)
- return;
+ tor_assert(gs != NULL);
- /* If we still have unconfigured managed proxies, don't go and
- connect to a bridge. */
- if (pt_proxies_configuration_pending())
- return;
+ entry_guards_dirty = 1;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if (!download_status_is_ready(&bridge->fetch_status, now,
- IMPOSSIBLE_TO_DOWNLOAD))
- continue; /* don't bother, no need to retry yet */
- if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
- download_status_mark_impossible(&bridge->fetch_status);
- log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
- safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
- continue;
- }
+ if (get_options()->AvoidDiskWrites)
+ when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME;
+ else
+ when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME;
- /* schedule another fetch as if this one will fail, in case it does */
- download_status_failed(&bridge->fetch_status, 0);
-
- can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) &&
- num_bridge_auths;
- ask_bridge_directly = !can_use_bridge_authority ||
- !options->UpdateBridgesFromAuthority;
- log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)",
- ask_bridge_directly, tor_digest_is_zero(bridge->identity),
- !options->UpdateBridgesFromAuthority, !num_bridge_auths);
-
- if (ask_bridge_directly &&
- !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
- FIREWALL_OR_CONNECTION, 0,
- 0)) {
- log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
- "firewall policy. %s.",
- fmt_addrport(&bridge->addr, bridge->port),
- can_use_bridge_authority ?
- "Asking bridge authority instead" : "Skipping");
- if (can_use_bridge_authority)
- ask_bridge_directly = 0;
- else
- continue;
- }
+ /* or_state_save() will call entry_guards_update_state() and
+ entry_guards_update_guards_in_state()
+ */
+ or_state_mark_dirty(get_or_state(), when);
+}
- if (ask_bridge_directly) {
- /* we need to ask the bridge itself for its descriptor. */
- launch_direct_bridge_descriptor_fetch(bridge);
- } else {
- /* We have a digest and we want to ask an authority. We could
- * combine all the requests into one, but that may give more
- * hints to the bridge authority than we want to give. */
- char resource[10 + HEX_DIGEST_LEN];
- memcpy(resource, "fp/", 3);
- base16_encode(resource+3, HEX_DIGEST_LEN+1,
- bridge->identity, DIGEST_LEN);
- memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3);
- log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.",
- resource);
- directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC,
- ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY);
- }
- }
- SMARTLIST_FOREACH_END(bridge);
+/** Our list of entry guards has changed for the default guard selection
+ * context, or some element of one of our entry guards has changed. Write
+ * the changes to disk within the next few minutes.
+ */
+void
+entry_guards_changed(void)
+{
+ entry_guards_changed_for_guard_selection(get_guard_selection_info());
}
-/** If our <b>bridge</b> is configured to be a different address than
- * the bridge gives in <b>node</b>, rewrite the routerinfo
- * we received to use the address we meant to use. Now we handle
- * multihomed bridges better.
+/** If the entry guard info has not changed, do nothing and return.
+ * Otherwise, free the EntryGuards piece of <b>state</b> and create
+ * a new one out of the global entry_guards list, and then mark
+ * <b>state</b> dirty so it will get saved to disk.
*/
-static void
-rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
+void
+entry_guards_update_state(or_state_t *state)
{
- /* XXXX move this function. */
- /* XXXX overridden addresses should really live in the node_t, so that the
- * routerinfo_t and the microdesc_t can be immutable. But we can only
- * do that safely if we know that no function that connects to an OR
- * does so through an address from any source other than node_get_addr().
- */
- tor_addr_t addr;
- const or_options_t *options = get_options();
+ entry_guards_dirty = 0;
- if (node->ri) {
- routerinfo_t *ri = node->ri;
- tor_addr_from_ipv4h(&addr, ri->addr);
+ // Handles all guard info.
+ entry_guards_update_guards_in_state(state);
- if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
- bridge->port == ri->or_port) ||
- (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) &&
- bridge->port == ri->ipv6_orport)) {
- /* they match, so no need to do anything */
- } else {
- if (tor_addr_family(&bridge->addr) == AF_INET) {
- ri->addr = tor_addr_to_ipv4h(&bridge->addr);
- ri->or_port = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerinfo for '%s' to match configured "
- "address %s:%d.",
- ri->nickname, fmt_addr32(ri->addr), ri->or_port);
- } else if (tor_addr_family(&bridge->addr) == AF_INET6) {
- tor_addr_copy(&ri->ipv6_addr, &bridge->addr);
- ri->ipv6_orport = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerinfo for '%s' to match configured "
- "address %s.",
- ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport));
- } else {
- log_err(LD_BUG, "Address family not supported: %d.",
- tor_addr_family(&bridge->addr));
- return;
- }
- }
+ entry_guards_dirty = 0;
- if (options->ClientPreferIPv6ORPort == -1) {
- /* Mark which address to use based on which bridge_t we got. */
- node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 &&
- !tor_addr_is_null(&node->ri->ipv6_addr));
- } else {
- /* Mark which address to use based on user preference */
- node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) &&
- !tor_addr_is_null(&node->ri->ipv6_addr));
- }
+ if (!get_options()->AvoidDiskWrites)
+ or_state_mark_dirty(get_or_state(), 0);
+ entry_guards_dirty = 0;
+}
- /* XXXipv6 we lack support for falling back to another address for
- the same relay, warn the user */
- if (!tor_addr_is_null(&ri->ipv6_addr)) {
- tor_addr_port_t ap;
- node_get_pref_orport(node, &ap);
- log_notice(LD_CONFIG,
- "Bridge '%s' has both an IPv4 and an IPv6 address. "
- "Will prefer using its %s address (%s) based on %s.",
- ri->nickname,
- node->ipv6_preferred ? "IPv6" : "IPv4",
- fmt_addrport(&ap.addr, ap.port),
- options->ClientPreferIPv6ORPort == -1 ?
- "the configured Bridge address" :
- "ClientPreferIPv6ORPort");
- }
+/**
+ * Format a single entry guard in the format expected by the controller.
+ * Return a newly allocated string.
+ */
+STATIC char *
+getinfo_helper_format_single_entry_guard(const entry_guard_t *e)
+{
+ const char *status = NULL;
+ time_t when = 0;
+ const node_t *node;
+ char tbuf[ISO_TIME_LEN+1];
+ char nbuf[MAX_VERBOSE_NICKNAME_LEN+1];
+
+ /* This is going to be a bit tricky, since the status
+ * codes weren't really intended for prop271 guards.
+ *
+ * XXXX use a more appropriate format for exporting this information
+ */
+ if (e->confirmed_idx < 0) {
+ status = "never-connected";
+ } else if (! e->currently_listed) {
+ when = e->unlisted_since_date;
+ status = "unusable";
+ } else if (! e->is_filtered_guard) {
+ status = "unusable";
+ } else if (e->is_reachable == GUARD_REACHABLE_NO) {
+ when = e->failing_since;
+ status = "down";
+ } else {
+ status = "up";
}
- if (node->rs) {
- routerstatus_t *rs = node->rs;
- tor_addr_from_ipv4h(&addr, rs->addr);
- if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
- bridge->port == rs->or_port) {
- /* they match, so no need to do anything */
- } else {
- rs->addr = tor_addr_to_ipv4h(&bridge->addr);
- rs->or_port = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerstatus for '%s' to match "
- "configured address %s.",
- rs->nickname, fmt_addrport(&bridge->addr, rs->or_port));
- }
+ node = entry_guard_find_node(e);
+ if (node) {
+ node_get_verbose_nickname(node, nbuf);
+ } else {
+ nbuf[0] = '$';
+ base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
+ /* e->nickname field is not very reliable if we don't know about
+ * this router any longer; don't include it. */
}
-}
-/** We just learned a descriptor for a bridge. See if that
- * digest is in our entry guard list, and add it if not. */
-void
-learned_bridge_descriptor(routerinfo_t *ri, int from_cache)
-{
- tor_assert(ri);
- tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE);
- if (get_options()->UseBridges) {
- int first = num_bridges_usable() <= 1;
- bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri);
- time_t now = time(NULL);
- router_set_status(ri->cache_info.identity_digest, 1);
-
- if (bridge) { /* if we actually want to use this one */
- node_t *node;
- /* it's here; schedule its re-fetch for a long time from now. */
- if (!from_cache)
- download_status_reset(&bridge->fetch_status);
-
- node = node_get_mutable_by_id(ri->cache_info.identity_digest);
- tor_assert(node);
- rewrite_node_address_for_bridge(bridge, node);
- if (tor_digest_is_zero(bridge->identity)) {
- memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN);
- log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d",
- hex_str(bridge->identity, DIGEST_LEN),
- fmt_and_decorate_addr(&bridge->addr),
- (int) bridge->port);
- }
- add_an_entry_guard(node, 1, 1, 0, 0);
-
- log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname,
- from_cache ? "cached" : "fresh", router_describe(ri));
- /* set entry->made_contact so if it goes down we don't drop it from
- * our entry node list */
- entry_guard_register_connect_status(ri->cache_info.identity_digest,
- 1, 0, now);
- if (first) {
- routerlist_retry_directory_downloads(now);
- }
- }
+ char *result = NULL;
+ if (when) {
+ format_iso_time(tbuf, when);
+ tor_asprintf(&result, "%s %s %s\n", nbuf, status, tbuf);
+ } else {
+ tor_asprintf(&result, "%s %s\n", nbuf, status);
}
+ return result;
}
-/** Return the number of bridges that have descriptors that
- * are marked with purpose 'bridge' and are running.
+/** If <b>question</b> is the string "entry-guards", then dump
+ * to *<b>answer</b> a newly allocated string describing all of
+ * the nodes in the global entry_guards list. See control-spec.txt
+ * for details.
+ * For backward compatibility, we also handle the string "helper-nodes".
*
- * We use this function to decide if we're ready to start building
- * circuits through our bridges, or if we need to wait until the
- * directory "server/authority" requests finish. */
+ * XXX this should be totally redesigned after prop 271 too, and that's
+ * going to take some control spec work.
+ * */
int
-any_bridge_descriptors_known(void)
+getinfo_helper_entry_guards(control_connection_t *conn,
+ const char *question, char **answer,
+ const char **errmsg)
{
- tor_assert(get_options()->UseBridges);
- return choose_random_entry(NULL) != NULL;
+ guard_selection_t *gs = get_guard_selection_info();
+
+ tor_assert(gs != NULL);
+
+ (void) conn;
+ (void) errmsg;
+
+ if (!strcmp(question,"entry-guards") ||
+ !strcmp(question,"helper-nodes")) {
+ const smartlist_t *guards;
+ guards = gs->sampled_entry_guards;
+
+ smartlist_t *sl = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(guards, const entry_guard_t *, e) {
+ char *cp = getinfo_helper_format_single_entry_guard(e);
+ smartlist_add(sl, cp);
+ } SMARTLIST_FOREACH_END(e);
+ *answer = smartlist_join_strings(sl, "", 0, NULL);
+ SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+ smartlist_free(sl);
+ }
+ return 0;
}
-/** Return the number of bridges that have descriptors that are marked with
- * purpose 'bridge' and are running.
+/* Given the original bandwidth of a guard and its guardfraction,
+ * calculate how much bandwidth the guard should have as a guard and
+ * as a non-guard.
+ *
+ * Quoting from proposal236:
+ *
+ * Let Wpf denote the weight from the 'bandwidth-weights' line a
+ * client would apply to N for position p if it had the guard
+ * flag, Wpn the weight if it did not have the guard flag, and B the
+ * measured bandwidth of N in the consensus. Then instead of choosing
+ * N for position p proportionally to Wpf*B or Wpn*B, clients should
+ * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
+ *
+ * This function fills the <b>guardfraction_bw</b> structure. It sets
+ * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B.
*/
-static int
-num_bridges_usable(void)
+void
+guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
+ int orig_bandwidth,
+ uint32_t guardfraction_percentage)
{
- int n_options = 0;
- tor_assert(get_options()->UseBridges);
- (void) choose_random_entry_impl(NULL, 0, 0, &n_options);
- return n_options;
+ double guardfraction_fraction;
+
+ /* Turn the percentage into a fraction. */
+ tor_assert(guardfraction_percentage <= 100);
+ guardfraction_fraction = guardfraction_percentage / 100.0;
+
+ long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth);
+ tor_assert(guard_bw <= INT_MAX);
+
+ guardfraction_bw->guard_bw = (int) guard_bw;
+
+ guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw;
}
-/** 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
- * up; else just observe and report. */
-static int
-entries_retry_helper(const or_options_t *options, int act)
+/** Helper: Update the status of all entry guards, in whatever algorithm
+ * is used. Return true if we should stop using all previously generated
+ * circuits, by calling circuit_mark_all_unused_circs() and
+ * circuit_mark_all_dirty_circs_as_unusable().
+ */
+int
+guards_update_all(void)
{
- const node_t *node;
- int any_known = 0;
- int any_running = 0;
- int need_bridges = options->UseBridges != 0;
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- node = node_get_by_id(e->identity);
- if (node && node_has_descriptor(node) &&
- node_is_bridge(node) == need_bridges &&
- (!need_bridges || (!e->bad_since &&
- node_is_a_configured_bridge(node)))) {
- any_known = 1;
- if (node->is_running)
- any_running = 1; /* some entry is both known and running */
- else if (act) {
- /* Mark all current connections to this OR as unhealthy, since
- * otherwise there could be one that started 30 seconds
- * ago, and in 30 seconds it will time out, causing us to mark
- * the node down and undermine the retry attempt. We mark even
- * the established conns, since if the network just came back
- * we'll want to attach circuits to fresh conns. */
- connection_or_set_bad_connections(node->identity, 1);
-
- /* mark this entry node for retry */
- router_set_status(node->identity, 1);
- e->can_retry = 1;
- e->bad_since = 0;
- }
- }
- } SMARTLIST_FOREACH_END(e);
- log_debug(LD_DIR, "%d: any_known %d, any_running %d",
- act, any_known, any_running);
- return any_known && !any_running;
+ int mark_circuits = 0;
+ if (update_guard_selection_choice(get_options()))
+ mark_circuits = 1;
+
+ tor_assert(curr_guard_context);
+
+ if (entry_guards_update_all(curr_guard_context))
+ mark_circuits = 1;
+
+ return mark_circuits;
}
-/** Do we know any descriptors for our bridges / entrynodes, and are
- * all the ones we have descriptors for down? */
-int
-entries_known_but_down(const or_options_t *options)
+/** Helper: pick a guard for a circuit, with whatever algorithm is
+ used. */
+const node_t *
+guards_choose_guard(cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out)
+{
+ const node_t *r = NULL;
+ const uint8_t *exit_id = NULL;
+ entry_guard_restriction_t *rst = NULL;
+ if (state && (exit_id = build_state_get_exit_rsa_id(state))) {
+ /* We're building to a targeted exit node, so that node can't be
+ * chosen as our guard for this circuit. Remember that fact in a
+ * restriction. */
+ rst = tor_malloc_zero(sizeof(entry_guard_restriction_t));
+ memcpy(rst->exclude_id, exit_id, DIGEST_LEN);
+ }
+ if (entry_guard_pick_for_circuit(get_guard_selection_info(),
+ GUARD_USAGE_TRAFFIC,
+ rst,
+ &r,
+ guard_state_out) < 0) {
+ tor_assert(r == NULL);
+ }
+ return r;
+}
+
+/** Remove all currently listed entry guards for a given guard selection
+ * context. This frees and replaces <b>gs</b>, so don't use <b>gs</b>
+ * after calling this function. */
+void
+remove_all_entry_guards_for_guard_selection(guard_selection_t *gs)
{
- tor_assert(entry_list_is_constrained(options));
- return entries_retry_helper(options, 0);
+ // This function shouldn't exist. XXXX
+ tor_assert(gs != NULL);
+ char *old_name = tor_strdup(gs->name);
+ guard_selection_type_t old_type = gs->type;
+
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, entry, {
+ control_event_guard(entry->nickname, entry->identity, "DROPPED");
+ });
+
+ if (gs == curr_guard_context) {
+ curr_guard_context = NULL;
+ }
+
+ smartlist_remove(guard_contexts, gs);
+ guard_selection_free(gs);
+
+ gs = get_guard_selection_by_name(old_name, old_type, 1);
+ entry_guards_changed_for_guard_selection(gs);
+ tor_free(old_name);
}
-/** Mark all down known bridges / entrynodes up. */
+/** Remove all currently listed entry guards, so new ones will be chosen.
+ *
+ * XXXX This function shouldn't exist -- it's meant to support the DROPGUARDS
+ * command, which is deprecated.
+ */
void
-entries_retry_all(const or_options_t *options)
+remove_all_entry_guards(void)
{
- tor_assert(entry_list_is_constrained(options));
- entries_retry_helper(options, 1);
+ remove_all_entry_guards_for_guard_selection(get_guard_selection_info());
}
-/** Return true if at least one of our bridges runs a Tor version that can
- * provide microdescriptors to us. If not, we'll fall back to asking for
- * full descriptors. */
+/** Helper: pick a directory guard, with whatever algorithm is used. */
+const node_t *
+guards_choose_dirguard(circuit_guard_state_t **guard_state_out)
+{
+ const node_t *r = NULL;
+ if (entry_guard_pick_for_circuit(get_guard_selection_info(),
+ GUARD_USAGE_DIRGUARD,
+ NULL,
+ &r,
+ guard_state_out) < 0) {
+ tor_assert(r == NULL);
+ }
+ return r;
+}
+
+/**
+ * If we're running with a constrained guard set, then maybe mark our guards
+ * usable. Return 1 if we do; 0 if we don't.
+ */
int
-any_bridge_supports_microdescriptors(void)
+guards_retry_optimistic(const or_options_t *options)
{
- const node_t *node;
- if (!get_options()->UseBridges || !entry_guards)
+ if (! entry_list_is_constrained(options))
return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- node = node_get_by_id(e->identity);
- if (node && node->is_running &&
- node_is_bridge(node) && node_is_a_configured_bridge(node)) {
- /* This is one of our current bridges, and we know enough about
- * it to know that it will be able to answer our questions. */
- return 1;
- }
- } SMARTLIST_FOREACH_END(e);
- return 0;
+
+ mark_primary_guards_maybe_reachable(get_guard_selection_info());
+
+ return 1;
+}
+
+/**
+ * Return true iff we know enough directory information to construct
+ * circuits through all of the primary guards we'd currently use.
+ */
+int
+guard_selection_have_enough_dir_info_to_build_circuits(guard_selection_t *gs)
+{
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ int n_missing_descriptors = 0;
+ int n_considered = 0;
+ int num_primary_to_check;
+
+ /* We want to check for the descriptor of at least the first two primary
+ * guards in our list, since these are the guards that we typically use for
+ * circuits. */
+ num_primary_to_check = get_n_primary_guards_to_use(GUARD_USAGE_TRAFFIC);
+ num_primary_to_check++;
+
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (guard->is_reachable == GUARD_REACHABLE_NO)
+ continue;
+ n_considered++;
+ if (!guard_has_descriptor(guard))
+ n_missing_descriptors++;
+ if (n_considered >= num_primary_to_check)
+ break;
+ } SMARTLIST_FOREACH_END(guard);
+
+ return n_missing_descriptors == 0;
+}
+
+/** As guard_selection_have_enough_dir_info_to_build_circuits, but uses
+ * the default guard selection. */
+int
+entry_guards_have_enough_dir_info_to_build_circuits(void)
+{
+ return guard_selection_have_enough_dir_info_to_build_circuits(
+ get_guard_selection_info());
+}
+
+/** Free one guard selection context */
+STATIC void
+guard_selection_free(guard_selection_t *gs)
+{
+ if (!gs) return;
+
+ tor_free(gs->name);
+
+ if (gs->sampled_entry_guards) {
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, e,
+ entry_guard_free(e));
+ smartlist_free(gs->sampled_entry_guards);
+ gs->sampled_entry_guards = NULL;
+ }
+
+ smartlist_free(gs->confirmed_entry_guards);
+ smartlist_free(gs->primary_entry_guards);
+
+ tor_free(gs);
}
/** Release all storage held by the list of entry guards and related
@@ -2498,15 +3508,16 @@ any_bridge_supports_microdescriptors(void)
void
entry_guards_free_all(void)
{
- if (entry_guards) {
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(entry_guards);
- entry_guards = NULL;
+ /* Null out the default */
+ curr_guard_context = NULL;
+ /* Free all the guard contexts */
+ if (guard_contexts != NULL) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ guard_selection_free(gs);
+ } SMARTLIST_FOREACH_END(gs);
+ smartlist_free(guard_contexts);
+ guard_contexts = NULL;
}
- clear_bridge_list();
- smartlist_free(bridge_list);
- bridge_list = NULL;
circuit_build_times_free_timeouts(get_circuit_build_times_mutable());
}
diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h
index 247c80940e..735c7738ba 100644
--- a/src/or/entrynodes.h
+++ b/src/or/entrynodes.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -12,25 +12,27 @@
#ifndef TOR_ENTRYNODES_H
#define TOR_ENTRYNODES_H
-#if 1
-/* XXXX NM I would prefer that all of this stuff be private to
- * entrynodes.c. */
+#include "handles.h"
-/** An entry_guard_t represents our information about a chosen long-term
- * first hop, known as a "helper" node in the literature. We can't just
- * use a node_t, since we want to remember these even when we
- * don't have any directory info. */
-typedef struct entry_guard_t {
- char nickname[MAX_NICKNAME_LEN+1];
- char identity[DIGEST_LEN];
- time_t chosen_on_date; /**< Approximately when was this guard added?
- * "0" if we don't know. */
- char *chosen_by_version; /**< What tor version added this guard? NULL
- * if we don't know. */
- unsigned int made_contact : 1; /**< 0 if we have never connected to this
- * router, 1 if we have. */
- unsigned int can_retry : 1; /**< Should we retry connecting to this entry,
- * in spite of having it marked as unreachable?*/
+/* Forward declare for guard_selection_t; entrynodes.c has the real struct */
+typedef struct guard_selection_s guard_selection_t;
+
+/* Forward declare for entry_guard_t; the real declaration is private. */
+typedef struct entry_guard_t entry_guard_t;
+
+/* Forward declaration for circuit_guard_state_t; the real declaration is
+ private. */
+typedef struct circuit_guard_state_t circuit_guard_state_t;
+
+/* Forward declaration for entry_guard_restriction_t; the real declaration is
+ private. */
+typedef struct entry_guard_restriction_t entry_guard_restriction_t;
+
+/* Information about a guard's pathbias status.
+ * These fields are used in circpathbias.c to try to detect entry
+ * nodes that are failing circuits at a suspicious frequency.
+ */
+typedef struct guard_pathbias_t {
unsigned int path_bias_noticed : 1; /**< Did we alert the user about path
* bias for this node already? */
unsigned int path_bias_warned : 1; /**< Did we alert the user about path bias
@@ -43,15 +45,6 @@ typedef struct entry_guard_t {
* use bias for this node already? */
unsigned int path_bias_use_extreme : 1; /**< Did we alert the user about path
* use bias for this node already? */
- unsigned int is_dir_cache : 1; /**< Is this node a directory cache? */
- time_t bad_since; /**< 0 if this guard is currently usable, or the time at
- * which it was observed to become (according to the
- * directory or the user configuration) unusable. */
- time_t unreachable_since; /**< 0 if we can connect to this guard, or the
- * time at which we first noticed we couldn't
- * connect to it. */
- time_t last_attempted; /**< 0 if we can connect to this guard, or the time
- * at which we last failed to connect to it. */
double circ_attempts; /**< Number of circuits this guard has "attempted" */
double circ_successes; /**< Number of successfully built circuits using
@@ -68,98 +61,523 @@ typedef struct entry_guard_t {
double use_attempts; /**< Number of circuits we tried to use with streams */
double use_successes; /**< Number of successfully used circuits using
* this guard as first hop. */
-} entry_guard_t;
+} guard_pathbias_t;
+
+#if defined(ENTRYNODES_PRIVATE)
+/**
+ * @name values for entry_guard_t.is_reachable.
+ *
+ * See entry_guard_t.is_reachable for more information.
+ */
+/**@{*/
+#define GUARD_REACHABLE_NO 0
+#define GUARD_REACHABLE_YES 1
+#define GUARD_REACHABLE_MAYBE 2
+/**@}*/
+
+/** An entry_guard_t represents our information about a chosen long-term
+ * first hop, known as a "helper" node in the literature. We can't just
+ * use a node_t, since we want to remember these even when we
+ * don't have any directory info. */
+struct entry_guard_t {
+ HANDLE_ENTRY(entry_guard, entry_guard_t);
+
+ char nickname[MAX_HEX_NICKNAME_LEN+1];
+ char identity[DIGEST_LEN];
+ ed25519_public_key_t ed_id;
+
+ /**
+ * @name new guard selection algorithm fields.
+ *
+ * Only the new (prop271) algorithm uses these. For a more full
+ * description of the algorithm, see the module documentation for
+ * entrynodes.c
+ */
+ /**@{*/
+
+ /* == Persistent fields, present for all sampled guards. */
+ /** When was this guard added to the sample? */
+ time_t sampled_on_date;
+ /** Since what date has this guard been "unlisted"? A guard counts as
+ * unlisted if we have a live consensus that does not include it, or
+ * if we have a live consensus that does not include it as a usable
+ * guard. This field is zero when the guard is listed. */
+ time_t unlisted_since_date; // can be zero
+ /** What version of Tor added this guard to the sample? */
+ char *sampled_by_version;
+ /** Is this guard listed right now? If this is set, then
+ * unlisted_since_date should be set too. */
+ unsigned currently_listed : 1;
+ /* == Persistent fields, for confirmed guards only */
+ /** When was this guard confirmed? (That is, when did we first use it
+ * successfully and decide to keep it?) This field is zero if this is not a
+ * confirmed guard. */
+ time_t confirmed_on_date; /* 0 if not confirmed */
+ /**
+ * In what order was this guard confirmed? Guards with lower indices
+ * appear earlier on the confirmed list. If the confirmed list is compacted,
+ * this field corresponds to the index of this guard on the confirmed list.
+ *
+ * This field is set to -1 if this guard is not confirmed.
+ */
+ int confirmed_idx; /* -1 if not confirmed; otherwise the order that this
+ * item should occur in the CONFIRMED_GUARDS ordered
+ * list */
+
+ /**
+ * Which selection does this guard belong to?
+ */
+ char *selection_name;
+
+ /** Bridges only: address of the bridge. */
+ tor_addr_port_t *bridge_addr;
+
+ /* ==== Non-persistent fields. */
+ /* == These are used by sampled guards */
+ /** When did we last decide to try using this guard for a circuit? 0 for
+ * "not since we started up." */
+ time_t last_tried_to_connect;
+ /** How reachable do we consider this guard to be? One of
+ * GUARD_REACHABLE_NO, GUARD_REACHABLE_YES, or GUARD_REACHABLE_MAYBE. */
+ unsigned is_reachable : 2;
+ /** Boolean: true iff this guard is pending. A pending guard is one
+ * that we have an in-progress circuit through, and which we do not plan
+ * to try again until it either succeeds or fails. Primary guards can
+ * never be pending. */
+ unsigned is_pending : 1;
+ /** If true, don't write this guard to disk. (Used for bridges with unknown
+ * identities) */
+ unsigned is_persistent : 1;
+ /** When did we get the earliest connection failure for this guard?
+ * We clear this field on a successful connect. We do _not_ clear it
+ * when we mark the guard as "MAYBE" reachable.
+ */
+ time_t failing_since;
+
+ /* == Set inclusion flags. */
+ /** If true, this guard is in the filtered set. The filtered set includes
+ * all sampled guards that our configuration allows us to use. */
+ unsigned is_filtered_guard : 1;
+ /** If true, this guard is in the usable filtered set. The usable filtered
+ * set includes all filtered guards that are not believed to be
+ * unreachable. (That is, those for which is_reachable is not
+ * GUARD_REACHABLE_NO) */
+ unsigned is_usable_filtered_guard : 1;
+ unsigned is_primary:1;
+
+ /** This string holds any fields that we are maintaining because
+ * we saw them in the state, even if we don't understand them. */
+ char *extra_state_fields;
+
+ /** Backpointer to the guard selection that this guard belongs to.
+ * The entry_guard_t must never outlive its guard_selection. */
+ guard_selection_t *in_selection;
+ /**@}*/
+
+ /** Path bias information for this guard. */
+ guard_pathbias_t pb;
+};
+
+/**
+ * Possible rules for a guard selection to follow
+ */
+typedef enum guard_selection_type_t {
+ /** Infer the type of this selection from its name. */
+ GS_TYPE_INFER=0,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * complete list of guards in the consensus. */
+ GS_TYPE_NORMAL=1,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * configured bridges, and allowing it to grow as large as all the configured
+ * bridges */
+ GS_TYPE_BRIDGE,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * set of filtered nodes. */
+ GS_TYPE_RESTRICTED,
+} guard_selection_type_t;
+
+/**
+ * All of the the context for guard selection on a particular client.
+ *
+ * We maintain multiple guard selection contexts for a client, depending
+ * aspects on its current configuration -- whether an extremely
+ * restrictive EntryNodes is used, whether UseBridges is enabled, and so
+ * on.)
+ *
+ * See the module documentation for entrynodes.c for more information
+ * about guard selection algorithms.
+ */
+struct guard_selection_s {
+ /**
+ * The name for this guard-selection object. (Must not contain spaces).
+ */
+ char *name;
+
+ /**
+ * What rules does this guard-selection object follow?
+ */
+ guard_selection_type_t type;
+
+ /**
+ * A value of 1 means that primary_entry_guards is up-to-date; 0
+ * means we need to recalculate it before using primary_entry_guards
+ * or the is_primary flag on any guard.
+ */
+ int primary_guards_up_to_date;
+
+ /**
+ * A list of the sampled entry guards, as entry_guard_t structures.
+ * Not in any particular order. When we 'sample' a guard, we are
+ * noting it as a possible guard to pick in the future. The use of
+ * sampling here prevents us from being forced by an attacker to try
+ * every guard on the network. This list is persistent.
+ */
+ smartlist_t *sampled_entry_guards;
+
+ /**
+ * Ordered list (from highest to lowest priority) of guards that we
+ * have successfully contacted and decided to use. Every member of
+ * this list is a member of sampled_entry_guards. Every member should
+ * have confirmed_on_date set, and have confirmed_idx greater than
+ * any earlier member of the list.
+ *
+ * This list is persistent. It is a subset of the elements in
+ * sampled_entry_guards, and its pointers point to elements of
+ * sampled_entry_guards.
+ */
+ smartlist_t *confirmed_entry_guards;
+
+ /**
+ * Ordered list (from highest to lowest priority) of guards that we
+ * are willing to use the most happily. These guards may or may not
+ * yet be confirmed yet. If we can use one of these guards, we are
+ * probably not on a network that is trying to restrict our guard
+ * choices.
+ *
+ * This list is a subset of the elements in
+ * sampled_entry_guards, and its pointers point to elements of
+ * sampled_entry_guards.
+ */
+ smartlist_t *primary_entry_guards;
+
+ /** When did we last successfully build a circuit or use a circuit? */
+ time_t last_time_on_internet;
+
+ /** What confirmed_idx value should the next-added member of
+ * confirmed_entry_guards receive? */
+ int next_confirmed_idx;
+
+};
+
+struct entry_guard_handle_t;
+
+/**
+ * A restriction to remember which entry guards are off-limits for a given
+ * circuit.
+ *
+ * Right now, we only use restrictions to block a single guard and its family
+ * from being selected; this mechanism is designed to be more extensible in
+ * the future, however.
+ *
+ * Note: This mechanism is NOT for recording which guards are never to be
+ * used: only which guards cannot be used on <em>one particular circuit</em>.
+ */
+struct entry_guard_restriction_t {
+ /**
+ * The guard's RSA identity digest must not equal this; and it must not
+ * be in the same family as any node with this digest.
+ */
+ uint8_t exclude_id[DIGEST_LEN];
+};
+
+/**
+ * Per-circuit state to track whether we'll be able to use the circuit.
+ */
+struct circuit_guard_state_t {
+ /** Handle to the entry guard object for this circuit. */
+ struct entry_guard_handle_t *guard;
+ /** The time at which <b>state</b> last changed. */
+ time_t state_set_at;
+ /** One of GUARD_CIRC_STATE_* */
+ uint8_t state;
+
+ /**
+ * A set of restrictions that were placed on this guard when we selected it
+ * for this particular circuit. We need to remember the restrictions here,
+ * since any guard that breaks these restrictions will not block this
+ * circuit from becoming COMPLETE.
+ */
+ entry_guard_restriction_t *restrictions;
+};
+#endif
+
+/* Common entry points for old and new guard code */
+int guards_update_all(void);
+const node_t *guards_choose_guard(cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out);
+const node_t *guards_choose_dirguard(circuit_guard_state_t **guard_state_out);
+
+#if 1
+/* XXXX NM I would prefer that all of this stuff be private to
+ * entrynodes.c. */
+entry_guard_t *entry_guard_get_by_id_digest_for_guard_selection(
+ guard_selection_t *gs, const char *digest);
entry_guard_t *entry_guard_get_by_id_digest(const char *digest);
+
+circuit_guard_state_t *
+get_guard_state_for_bridge_desc_fetch(const char *digest);
+
+void entry_guards_changed_for_guard_selection(guard_selection_t *gs);
void entry_guards_changed(void);
-const smartlist_t *get_entry_guards(void);
+guard_selection_t * get_guard_selection_info(void);
+int num_live_entry_guards_for_guard_selection(
+ guard_selection_t *gs,
+ int for_directory);
int num_live_entry_guards(int for_directory);
-
#endif
-#ifdef ENTRYNODES_PRIVATE
-STATIC const node_t *add_an_entry_guard(const node_t *chosen,
- int reset_status, int prepend,
- int for_discovery, int for_directory);
-
-STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards,
- const smartlist_t *all_entry_guards,
- const node_t *chosen_exit,
- dirinfo_type_t dirinfo_type,
- int for_directory,
- int need_uptime, int need_capacity);
-STATIC int decide_num_guards(const or_options_t *options, int for_directory);
-
-STATIC void entry_guards_set_from_config(const or_options_t *options);
-
-/** Flags to be passed to entry_is_live() to indicate what kind of
- * entry nodes we are looking for. */
+const node_t *entry_guard_find_node(const entry_guard_t *guard);
+const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard);
+const char *entry_guard_describe(const entry_guard_t *guard);
+guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard);
+
+/** Enum to specify how we're going to use a given guard, when we're picking
+ * one for immediate use. */
+typedef enum {
+ GUARD_USAGE_TRAFFIC = 0,
+ GUARD_USAGE_DIRGUARD = 1
+} guard_usage_t;
+
+void circuit_guard_state_free(circuit_guard_state_t *state);
+int entry_guard_pick_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ entry_guard_restriction_t *rst,
+ const node_t **chosen_node_out,
+ circuit_guard_state_t **guard_state_out);
+
+/* We just connected to an entry guard. What should we do with the circuit? */
typedef enum {
- ENTRY_NEED_UPTIME = 1<<0,
- ENTRY_NEED_CAPACITY = 1<<1,
- ENTRY_ASSUME_REACHABLE = 1<<2,
- ENTRY_NEED_DESCRIPTOR = 1<<3,
-} entry_is_live_flags_t;
+ GUARD_USABLE_NEVER = -1, /* Never use the circuit */
+ GUARD_MAYBE_USABLE_LATER = 0, /* Keep it. We might use it in the future */
+ GUARD_USABLE_NOW = 1, /* Use it right now */
+} guard_usable_t;
+
+guard_usable_t entry_guard_succeeded(circuit_guard_state_t **guard_state_p);
+void entry_guard_failed(circuit_guard_state_t **guard_state_p);
+void entry_guard_cancel(circuit_guard_state_t **guard_state_p);
+void entry_guard_chan_failed(channel_t *chan);
+int entry_guards_update_all(guard_selection_t *gs);
+int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs,
+ const smartlist_t *all_circuits,
+ smartlist_t *newly_complete_out);
+int entry_guard_state_should_expire(circuit_guard_state_t *guard_state);
+void entry_guards_note_internet_connectivity(guard_selection_t *gs);
+
+int update_guard_selection_choice(const or_options_t *options);
+
+/* Used by bridges.c only. */
+int num_bridges_usable(void);
+
+#ifdef ENTRYNODES_PRIVATE
+/**
+ * @name Default values for the parameters for the new (prop271) entry guard
+ * algorithm.
+ */
+/**@{*/
+/**
+ * We never let our sampled guard set grow larger than this percentage
+ * of the guards on the network.
+ */
+#define DFLT_MAX_SAMPLE_THRESHOLD_PERCENT 20
+/**
+ * We never let our sampled guard set grow larger than this number of
+ * guards.
+ */
+#define DFLT_MAX_SAMPLE_SIZE 60
+/**
+ * We always try to make our sample contain at least this many guards.
+ */
+#define DFLT_MIN_FILTERED_SAMPLE_SIZE 20
+/**
+ * If a guard is unlisted for this many days in a row, we remove it.
+ */
+#define DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20
+/**
+ * We remove unconfirmed guards from the sample after this many days,
+ * regardless of whether they are listed or unlisted.
+ */
+#define DFLT_GUARD_LIFETIME_DAYS 120
+/**
+ * We remove confirmed guards from the sample if they were sampled
+ * GUARD_LIFETIME_DAYS ago and confirmed this many days ago.
+ */
+#define DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60
+/**
+ * How many guards do we try to keep on our primary guard list?
+ */
+#define DFLT_N_PRIMARY_GUARDS 3
+/**
+ * Of the live guards on the primary guard list, how many do we consider when
+ * choosing a guard to use?
+ */
+#define DFLT_N_PRIMARY_GUARDS_TO_USE 1
+/**
+ * As DFLT_N_PRIMARY_GUARDS, but for choosing which directory guard to use.
+ */
+#define DFLT_N_PRIMARY_DIR_GUARDS_TO_USE 3
+/**
+ * If we haven't successfully built or used a circuit in this long, then
+ * consider that the internet is probably down.
+ */
+#define DFLT_INTERNET_LIKELY_DOWN_INTERVAL (10*60)
+/**
+ * If we're trying to connect to a nonprimary guard for at least this
+ * many seconds, and we haven't gotten the connection to work, we will treat
+ * lower-priority guards as usable.
+ */
+#define DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT 15
+/**
+ * If a circuit has been sitting around in 'waiting for better guard' state
+ * for at least this long, we'll expire it.
+ */
+#define DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60)
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in a restricted setting.
+ */
+#define DFLT_MEANINGFUL_RESTRICTION_PERCENT 20
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in an extremely restricted setting, and should warn.
+ */
+#define DFLT_EXTREME_RESTRICTION_PERCENT 1
+/**@}*/
+
+STATIC double get_max_sample_threshold(void);
+STATIC int get_max_sample_size_absolute(void);
+STATIC int get_min_filtered_sample_size(void);
+STATIC int get_remove_unlisted_guards_after_days(void);
+STATIC int get_guard_lifetime(void);
+STATIC int get_guard_confirmed_min_lifetime(void);
+STATIC int get_n_primary_guards(void);
+STATIC int get_n_primary_guards_to_use(guard_usage_t usage);
+STATIC int get_internet_likely_down_interval(void);
+STATIC int get_nonprimary_guard_connect_timeout(void);
+STATIC int get_nonprimary_guard_idle_timeout(void);
+STATIC double get_meaningful_restriction_threshold(void);
+STATIC double get_extreme_restriction_threshold(void);
-STATIC const node_t *entry_is_live(const entry_guard_t *e,
- entry_is_live_flags_t flags,
- const char **msg);
+HANDLE_DECL(entry_guard, entry_guard_t, STATIC)
+STATIC guard_selection_type_t guard_selection_infer_type(
+ guard_selection_type_t type_in,
+ const char *name);
+STATIC guard_selection_t *guard_selection_new(const char *name,
+ guard_selection_type_t type);
+STATIC guard_selection_t *get_guard_selection_by_name(
+ const char *name, guard_selection_type_t type, int create_if_absent);
+STATIC void guard_selection_free(guard_selection_t *gs);
+MOCK_DECL(STATIC int, entry_guard_is_listed,
+ (guard_selection_t *gs, const entry_guard_t *guard));
+STATIC const char *choose_guard_selection(const or_options_t *options,
+ const networkstatus_t *ns,
+ const guard_selection_t *old_selection,
+ guard_selection_type_t *type_out);
+STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs,
+ const uint8_t *rsa_id);
-STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now);
+MOCK_DECL(STATIC time_t, randomize_time, (time_t now, time_t max_backdate));
+STATIC entry_guard_t *entry_guard_add_to_sample(guard_selection_t *gs,
+ const node_t *node);
+STATIC entry_guard_t *entry_guards_expand_sample(guard_selection_t *gs);
+STATIC char *entry_guard_encode_for_state(entry_guard_t *guard);
+STATIC entry_guard_t *entry_guard_parse_from_state(const char *s);
+STATIC void entry_guard_free(entry_guard_t *e);
+STATIC void entry_guards_update_filtered_sets(guard_selection_t *gs);
+STATIC int entry_guards_all_primary_guards_are_down(guard_selection_t *gs);
+/**
+ * @name Flags for sample_reachable_filtered_entry_guards()
+ */
+/**@{*/
+#define SAMPLE_EXCLUDE_CONFIRMED (1u<<0)
+#define SAMPLE_EXCLUDE_PRIMARY (1u<<1)
+#define SAMPLE_EXCLUDE_PENDING (1u<<2)
+#define SAMPLE_NO_UPDATE_PRIMARY (1u<<3)
+#define SAMPLE_EXCLUDE_NO_DESCRIPTOR (1u<<4)
+/**@}*/
+STATIC entry_guard_t *sample_reachable_filtered_entry_guards(
+ guard_selection_t *gs,
+ const entry_guard_restriction_t *rst,
+ unsigned flags);
+STATIC void entry_guard_consider_retry(entry_guard_t *guard);
+STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard);
+STATIC void entry_guards_update_confirmed(guard_selection_t *gs);
+STATIC void entry_guards_update_primary(guard_selection_t *gs);
+STATIC int num_reachable_filtered_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst);
+STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs);
+/**
+ * @name Possible guard-states for a circuit.
+ */
+/**@{*/
+/** State for a circuit that can (so far as the guard subsystem is
+ * concerned) be used for actual traffic as soon as it is successfully
+ * opened. */
+#define GUARD_CIRC_STATE_USABLE_ON_COMPLETION 1
+/** State for an non-open circuit that we shouldn't use for actual
+ * traffic, when it completes, unless other circuits to preferable
+ * guards fail. */
+#define GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD 2
+/** State for an open circuit that we shouldn't use for actual traffic
+ * unless other circuits to preferable guards fail. */
+#define GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD 3
+/** State for a circuit that can (so far as the guard subsystem is
+ * concerned) be used for actual traffic. */
+#define GUARD_CIRC_STATE_COMPLETE 4
+/** State for a circuit that is unusable, and will not become usable. */
+#define GUARD_CIRC_STATE_DEAD 5
+/**@}*/
+STATIC void entry_guards_note_guard_failure(guard_selection_t *gs,
+ entry_guard_t *guard);
+STATIC entry_guard_t *select_entry_guard_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ const entry_guard_restriction_t *rst,
+ unsigned *state_out);
+STATIC void mark_primary_guards_maybe_reachable(guard_selection_t *gs);
+STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs,
+ entry_guard_t *guard,
+ unsigned old_state);
+STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b);
+STATIC char *getinfo_helper_format_single_entry_guard(const entry_guard_t *e);
#endif
+void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs);
void remove_all_entry_guards(void);
-void entry_guards_compute_status(const or_options_t *options, time_t now);
-int entry_guard_register_connect_status(const char *digest, int succeeded,
- int mark_relay_status, time_t now);
-void entry_nodes_should_be_added(void);
+struct bridge_info_t;
+void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport,
+ const uint8_t *rsa_id_digest);
+
int entry_list_is_constrained(const or_options_t *options);
-const node_t *choose_random_entry(cpath_build_state_t *state);
-const node_t *choose_random_dirguard(dirinfo_type_t t);
+int guards_retry_optimistic(const or_options_t *options);
+int entry_guards_parse_state_for_guard_selection(
+ guard_selection_t *gs, or_state_t *state, int set, char **msg);
int entry_guards_parse_state(or_state_t *state, int set, char **msg);
void entry_guards_update_state(or_state_t *state);
int getinfo_helper_entry_guards(control_connection_t *conn,
const char *question, char **answer,
const char **errmsg);
-void mark_bridge_list(void);
-void sweep_bridge_list(void);
-
-int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port,
- const char *digest);
-int extend_info_is_a_configured_bridge(const extend_info_t *ei);
-int routerinfo_is_a_configured_bridge(const routerinfo_t *ri);
-int node_is_a_configured_bridge(const node_t *node);
-void learned_router_identity(const tor_addr_t *addr, uint16_t port,
- const char *digest);
-struct bridge_line_t;
-void bridge_add_from_config(struct bridge_line_t *bridge_line);
-void retry_bridge_descriptor_fetch_directly(const char *digest);
-void fetch_bridge_descriptors(const or_options_t *options, time_t now);
-void learned_bridge_descriptor(routerinfo_t *ri, int from_cache);
-int any_bridge_descriptors_known(void);
int entries_known_but_down(const or_options_t *options);
void entries_retry_all(const or_options_t *options);
-int any_bridge_supports_microdescriptors(void);
-const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr,
- uint16_t port);
-
-int any_bridges_dont_support_microdescriptors(void);
+int guard_selection_have_enough_dir_info_to_build_circuits(
+ guard_selection_t *gs);
+int entry_guards_have_enough_dir_info_to_build_circuits(void);
void entry_guards_free_all(void);
-const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr,
- uint16_t port);
-struct transport_t;
-int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
- const struct transport_t **transport);
-
-MOCK_DECL(int, transport_is_needed, (const char *transport_name));
-int validate_pluggable_transports_config(void);
-
double pathbias_get_close_success_count(entry_guard_t *guard);
double pathbias_get_use_success_count(entry_guard_t *guard);
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 8ba3c6afa3..01dc06ce13 100644
--- a/src/or/ext_orport.c
+++ b/src/or/ext_orport.c
@@ -1,10 +1,20 @@
-/* Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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"
@@ -21,7 +31,7 @@
ext_or_cmd_t *
ext_or_cmd_new(uint16_t len)
{
- size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len;
+ size_t size = offsetof(ext_or_cmd_t, body) + len;
ext_or_cmd_t *cmd = tor_malloc(size);
cmd->len = len;
return cmd;
@@ -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
diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h
index 33d954e8d0..b2cd05db8f 100644
--- a/src/or/ext_orport.h
+++ b/src/or/ext_orport.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef EXT_ORPORT_H
diff --git a/src/or/fallback_dirs.inc b/src/or/fallback_dirs.inc
index 8e82a3fb2f..cc37e5f9af 100644
--- a/src/or/fallback_dirs.inc
+++ b/src/or/fallback_dirs.inc
@@ -1,65 +1,98 @@
-/* Whitelist & blacklist excluded 1273 of 1553 candidates. */
+/* Whitelist & blacklist excluded 1326 of 1513 candidates. */
+/* To comment-out entries in this file, use C comments, and add * to the start of each line. (stem finds fallback entries using " at the start of a line.) */
/* Checked IPv4 DirPorts served a consensus within 15.0s. */
/*
-Final Count: 100 (Eligible 280, Target 356 (1781 * 0.20), Max 100)
-Excluded: 180 (Same Operator 102, Failed/Skipped Download 40, Excess 38)
-Bandwidth Range: 6.0 - 67.2 MB/s
+Final Count: 151 (Eligible 187, Target 392 (1963 * 0.20), Max 200)
+Excluded: 36 (Same Operator 27, Failed/Skipped Download 9, Excess 0)
+Bandwidth Range: 1.3 - 40.0 MByte/s
*/
/*
-Onionoo Source: details Date: 2016-04-18 01:00:00 Version: 3.1
-URL: https:onionoo.torproject.orgdetails?fields=fingerprint%2Cnickname%2Ccontact%2Clast_changed_address_or_port%2Cconsensus_weight%2Cadvertised_bandwidth%2Cor_addresses%2Cdir_address%2Crecommended_version%2Cflags%2Ceffective_family&flag=V2Dir&type=relay&last_seen_days=-7&first_seen_days=7-
+Onionoo Source: details Date: 2017-05-16 07:00:00 Version: 4.0
+URL: https:onionoo.torproject.orgdetails?fields=fingerprint%2Cnickname%2Ccontact%2Clast_changed_address_or_port%2Cconsensus_weight%2Cadvertised_bandwidth%2Cor_addresses%2Cdir_address%2Crecommended_version%2Cflags%2Ceffective_family%2Cplatform&flag=V2Dir&type=relay&last_seen_days=-0&first_seen_days=30-
*/
/*
-Onionoo Source: uptime Date: 2016-04-18 01:00:00 Version: 3.1
-URL: https:onionoo.torproject.orguptime?first_seen_days=7-&flag=V2Dir&type=relay&last_seen_days=-7
+Onionoo Source: uptime Date: 2017-05-16 07:00:00 Version: 4.0
+URL: https:onionoo.torproject.orguptime?first_seen_days=30-&flag=V2Dir&type=relay&last_seen_days=-0
*/
+"176.10.104.240:80 orport=443 id=0111BA9B604669E636FFD5B503F382A4B7AD6E80"
+" weight=10",
"193.171.202.146:9030 orport=9001 id=01A9258A46E97FF8B2CAC7910577862C14F2C524"
" weight=10",
+"185.100.85.61:80 orport=443 id=025B66CEBC070FCB0519D206CF0CF4965C20C96E"
+" weight=10",
+"185.97.32.18:9030 orport=9001 id=04250C3835019B26AA6764E85D836088BE441088"
+" weight=10",
"5.9.110.236:9030 orport=9001 id=0756B7CD4DFC8182BE23143FAC0642F515182CEB"
" ipv6=[2a01:4f8:162:51e2::2]:9001"
" weight=10",
-"37.187.1.149:9030 orport=9001 id=08DC0F3C6E3D9C527C1FC8745D35DD1B0DE1875D"
-" ipv6=[2001:41d0:a:195::1]:9001"
+"109.163.234.8:80 orport=443 id=0818DAE0E2DDF795AEDEAC60B15E71901084F281"
" weight=10",
-"5.39.92.199:80 orport=443 id=0BEA4A88D069753218EAAAD6D22EA87B9A1319D6"
+"163.172.149.155:80 orport=443 id=0B85617241252517E8ECF2CFC7F4C1A32DCD153F"
" weight=10",
-"5.196.88.122:9030 orport=9001 id=0C2C599AFCB26F5CFC2C7592435924C1D63D9484"
+"5.39.92.199:80 orport=443 id=0BEA4A88D069753218EAAAD6D22EA87B9A1319D6"
+" ipv6=[2001:41d0:8:b1c7::1]:443"
" weight=10",
"178.62.197.82:80 orport=443 id=0D3EBA17E1C78F1E9900BABDB23861D46FCAF163"
" weight=10",
+"185.100.86.100:80 orport=443 id=0E8C0C8315B66DB5F703804B3889A1DD66C67CE0"
+" weight=10",
+"95.85.8.226:80 orport=443 id=1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423"
+" weight=10",
+"193.11.114.43:9030 orport=9001 id=12AD30E5D25AA67F519780E2111E611A455FDC89"
+" ipv6=[2001:6b0:30:1000::99]:9050"
+" weight=10",
+"37.157.195.87:8030 orport=443 id=12FD624EE73CEF37137C90D38B2406A66F68FAA2"
+" weight=10",
+"178.16.208.59:80 orport=443 id=136F9299A5009A4E0E96494E723BDB556FB0A26B"
+" ipv6=[2a00:1c20:4089:1234:bff6:e1bb:1ce3:8dc6]:443"
+" weight=10",
"144.76.14.145:110 orport=143 id=14419131033443AE6E21DA82B0D307F7CAE42BDB"
" ipv6=[2a01:4f8:190:9490::dead]:443"
" weight=10",
-"178.32.216.146:9030 orport=9001 id=17898F9A2EBC7D69DAF87C00A1BD2FABF3C9E1D2"
+"178.62.60.37:80 orport=443 id=175921396C7C426309AB03775A9930B6F611F794"
+" weight=10",
+"204.11.50.131:9030 orport=9001 id=185F2A57B0C4620582602761097D17DB81654F70"
+" weight=10",
+"5.9.158.75:80 orport=443 id=1AF72E8906E6C49481A791A6F8F84F8DFEBBB2BA"
+" ipv6=[2a01:4f8:190:514a::2]:443"
" weight=10",
"46.101.151.222:80 orport=443 id=1DBAED235E3957DE1ABD25B4206BE71406FB61F8"
" weight=10",
"91.219.237.229:80 orport=443 id=1ECD73B936CB6E6B3CD647CC204F108D9DF2C9F7"
" weight=10",
"212.47.229.2:9030 orport=9001 id=20462CBA5DA4C2D963567D17D0B7249718114A68"
+" ipv6=[2001:bc8:4400:2100::f03]:9001"
" weight=10",
-"185.61.138.18:8080 orport=4443 id=2541759BEC04D37811C2209A88E863320271EC9C"
+"144.76.163.93:9030 orport=9001 id=22F08CF09764C4E8982640D77F71ED72FF26A9AC"
" weight=10",
-"51.254.215.121:80 orport=443 id=262B66AD25C79588AD1FC8ED0E966395B47E5C1D"
+"163.172.176.167:80 orport=443 id=230A8B2A8BA861210D9B4BA97745AEC217A94207"
" weight=10",
-"194.150.168.79:11112 orport=11111 id=29F1020B94BE25E6BE1AD13E93CE19D2131B487C"
+"37.200.98.5:80 orport=443 id=231C2B9C8C31C295C472D031E06964834B745996"
+" ipv6=[2a00:1158:3::11a]:993"
+" weight=10",
+"212.47.240.10:82 orport=443 id=2A4C448784F5A83AFE6C78DA357D5E31F7989DEB"
" weight=10",
"144.76.26.175:9012 orport=9011 id=2BA2C8E96B2590E1072AECE2BDB5C48921BF8510"
" weight=10",
-"62.210.124.124:9130 orport=9101 id=2EBD117806EE43C3CC885A8F1E4DC60F207E7D3E"
-" ipv6=[2001:bc8:3f23:100::1]:9101"
+"97.74.237.196:9030 orport=9001 id=2F0F32AB1E5B943CA7D062C03F18960C86E70D94"
+" weight=10",
+"107.170.101.39:9030 orport=443 id=30973217E70AF00EBE51797FF6D9AA720A902EAA"
" weight=10",
-"213.61.66.118:9031 orport=9001 id=30648BC64CEDB3020F4A405E4AB2A6347FB8FA22"
+"64.113.32.29:9030 orport=9001 id=30C19B81981F450C402306E2E7CFB6C3F79CB6B2"
" weight=10",
"212.83.154.33:8080 orport=8443 id=322C6E3A973BC10FC36DE3037AD27BC89F14723B"
" weight=10",
"109.105.109.162:52860 orport=60784 id=32EE911D968BE3E016ECA572BB1ED0A9EE43FC2F"
" ipv6=[2001:948:7:2::163]:5001"
" weight=10",
-"146.0.32.144:9030 orport=9001 id=35E8B344F661F4F2E68B17648F35798B44672D7E"
+"163.172.13.165:9030 orport=9001 id=33DA0CAB7C27812EFF2E22C9705630A54D101FEB"
+" ipv6=[2001:bc8:38cb:201::8]:9001"
" weight=10",
"217.79.190.25:9030 orport=9090 id=361D33C96D0F161275EE67E2C91EE10B276E778B"
" weight=10",
+"37.187.22.87:9030 orport=9001 id=36B9E7AC1E36B62A9D6F330ABEB6012BA7F0D400"
+" ipv6=[2001:41d0:a:1657::1]:9001"
+" weight=10",
"62.210.92.11:9130 orport=9101 id=387B065A38E4DAA16D9D41C2964ECBC4B31D30FF"
" ipv6=[2001:bc8:338c::1]:9101"
" weight=10",
@@ -67,176 +100,271 @@ URL: https:onionoo.torproject.orguptime?first_seen_days=7-&flag=V2Dir&type=relay
" weight=10",
"164.132.77.175:9030 orport=9001 id=3B33F6FCA645AD4E91428A3AF7DC736AD9FB727B"
" weight=10",
+"212.47.230.49:9030 orport=9001 id=3D6D0771E54056AEFC28BB1DE816951F11826E97"
+" weight=10",
"176.10.107.180:9030 orport=9001 id=3D7E274A87D9A89AF064C13D1EE4CA1F184F2600"
" weight=10",
+"217.79.179.177:9030 orport=9001 id=3E53D3979DB07EFD736661C934A1DED14127B684"
+" ipv6=[2001:4ba0:fff9:131:6c4f::90d3]:9001"
+" weight=10",
+"178.62.86.96:9030 orport=9001 id=439D0447772CB107B886F7782DBC201FA26B92D1"
+" ipv6=[2a03:b0c0:1:d0::3cf:7001]:9050"
+" weight=10",
+"163.172.157.213:8080 orport=443 id=4623A9EC53BFD83155929E56D6F7B55B5E718C24"
+" weight=10",
+"31.31.78.49:80 orport=443 id=46791D156C9B6C255C2665D4D8393EC7DBAA7798"
+" weight=10",
+"69.162.139.9:9030 orport=9001 id=4791FC0692EAB60DF2BCCAFF940B95B74E7654F6"
+" ipv6=[2607:f128:40:1212::45a2:8b09]:9001"
+" weight=10",
+"51.254.246.203:9030 orport=9001 id=47B596B81C9E6277B98623A84B7629798A16E8D5"
+" weight=10",
"37.187.102.186:9030 orport=9001 id=489D94333DF66D57FFE34D9D59CC2D97E2CB0053"
" ipv6=[2001:41d0:a:26ba::1]:9001"
" weight=10",
"188.165.194.195:9030 orport=9001 id=49E7AD01BB96F6FE3AB8C3B15BD2470B150354DF"
" weight=10",
-"108.53.208.157:80 orport=443 id=4F0DB7E687FC7C0AE55C8F243DA8B0EB27FBF1F2"
+"62.102.148.67:80 orport=443 id=4A0C3E177AF684581EF780981AEAF51A98A6B5CF"
" weight=10",
-"212.51.134.123:9030 orport=9001 id=50586E25BE067FD1F739998550EDDCB1A14CA5B2"
-" ipv6=[2a02:168:6e00:0:3a60:77ff:fe9c:8bd1]:9001"
+"51.254.101.242:9002 orport=9001 id=4CC9CC9195EC38645B699A33307058624F660CCF"
" weight=10",
-"5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33"
+"81.7.16.182:80 orport=443 id=51E1CF613FD6F9F11FE24743C91D6F9981807D82"
+" ipv6=[2a02:180:1:1::517:10b6]:993"
" weight=10",
"94.23.204.175:9030 orport=9001 id=5665A3904C89E22E971305EE8C1997BCA4123C69"
" weight=10",
-"109.163.234.9:80 orport=443 id=5714542DCBEE1DD9864824723638FD44B2122CEA"
+"95.130.12.119:80 orport=443 id=587E0A9552E4274B251F29B5B2673D38442EE4BF"
" weight=10",
"185.21.100.50:9030 orport=9001 id=58ED9C9C35E433EE58764D62892B4FFD518A3CD0"
" ipv6=[2a00:1158:2:cd00:0:74:6f:72]:443"
" weight=10",
"78.142.142.246:80 orport=443 id=5A5E03355C1908EBF424CAF1F3ED70782C0D2F74"
" weight=10",
-"185.100.85.138:80 orport=46356 id=5C4DF16A0029CC4F67D3E127356E68F219269859"
+"120.29.217.46:80 orport=443 id=5E853C94AB1F655E9C908924370A0A6707508C62"
" weight=10",
-"178.16.208.62:80 orport=443 id=5CF8AFA5E4B0BB88942A44A3F3AAE08C3BDFD60B"
-" ipv6=[2a00:1c20:4089:1234:a6a4:2926:d0af:dfee]:443"
+"109.163.234.5:80 orport=443 id=5EB8D862E70981B8690DEDEF546789E26AB2BD24"
" weight=10",
"95.128.43.164:80 orport=443 id=616081EC829593AF4232550DE6FFAA1D75B37A90"
" ipv6=[2a02:ec0:209:10::4]:443"
" weight=10",
-"89.187.142.208:80 orport=443 id=64186650FFE4469EBBE52B644AE543864D32F43C"
+"163.172.139.104:8080 orport=443 id=68F175CCABE727AA2D2309BCD8789499CEE36ED7"
+" weight=10",
+"85.214.62.48:80 orport=443 id=6A7551EEE18F78A9813096E82BF84F740D32B911"
" weight=10",
-"144.76.73.140:9030 orport=9001 id=6A640018EABF3DA9BAD9321AA37C2C87BBE1F907"
+"80.127.137.19:80 orport=443 id=6EF897645B79B6CB35E853B32506375014DE3621"
+" ipv6=[2001:981:47c1:1::6]:443"
" weight=10",
-"94.126.23.174:9030 orport=9001 id=6FC6F08270D565BE89B7C819DD8E2D487397C073"
+"95.183.48.12:80 orport=443 id=7187CED1A3871F837D0E60AC98F374AC541CB0DA"
" weight=10",
-"176.31.191.26:9030 orport=9001 id=7350AB9ED7568F22745198359373C04AC783C37C"
+"85.214.151.72:9030 orport=9001 id=722D365140C8C52DBB3C9FF6986E3CEFFE2BA812"
" weight=10",
-"46.101.237.246:9030 orport=9001 id=75F1992FD3F403E9C082A5815EB5D12934CDF46C"
-" ipv6=[2a03:b0c0:3:d0::208:5001]:9050"
+"85.235.250.88:80 orport=443 id=72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE"
" weight=10",
-"185.11.180.67:80 orport=9001 id=794D8EA8343A4E820320265D05D4FA83AB6D1778"
+"176.31.191.26:80 orport=443 id=7350AB9ED7568F22745198359373C04AC783C37C"
" weight=10",
-"62.210.129.246:80 orport=443 id=79E169B25E4C7CE99584F6ED06F379478F23E2B8"
+"134.119.36.135:80 orport=443 id=763C9556602BD6207771A7A3D958091D44C43228"
+" ipv6=[2a00:1158:3::2a8]:993"
+" weight=10",
+"188.166.133.133:9030 orport=9001 id=774555642FDC1E1D4FDF2E0C31B7CA9501C5C9C7"
+" ipv6=[2a03:b0c0:2:d0::5:f001]:9001"
+" weight=10",
+"81.30.158.213:9030 orport=9001 id=789EA6C9AE9ADDD8760903171CFA9AC5741B0C70"
+" ipv6=[2001:4ba0:cafe:e84::1]:9001"
+" weight=10",
+"171.25.193.131:80 orport=443 id=79861CF8522FC637EF046F7688F5289E49D94576"
" weight=10",
"82.223.21.74:9030 orport=9001 id=7A32C9519D80CA458FC8B034A28F5F6815649A98"
" ipv6=[2001:470:53e0::cafe]:9050"
" weight=10",
+"51.254.136.195:80 orport=443 id=7BB70F8585DFC27E75D692970C0EEB0F22983A63"
+" weight=10",
+"193.11.114.45:9031 orport=9002 id=80AAF8D5956A43C197104CEF2550CD42D165C6FB"
+" weight=10",
"192.160.102.164:80 orport=9001 id=823AA81E277F366505545522CEDC2F529CE4DC3F"
+" ipv6=[2605:e200:d00c:c01d::1111]:9002"
" weight=10",
"192.87.28.82:9030 orport=9001 id=844AE9CAD04325E955E2BE1521563B79FE7094B7"
" weight=10",
-"84.219.173.60:9030 orport=443 id=855BC2DABE24C861CD887DB9B2E950424B49FC34"
+"188.166.23.127:80 orport=443 id=8672E8A01B4D3FA4C0BBE21C740D4506302EA487"
+" ipv6=[2a03:b0c0:2:d0::27b:7001]:9050"
" weight=10",
-"163.172.138.22:80 orport=443 id=8664DC892540F3C789DB37008236C096C871734D"
+"93.180.156.84:9030 orport=9001 id=8844D87E9B038BE3270938F05AF797E1D3C74C0F"
" weight=10",
-"185.96.88.29:80 orport=443 id=86C281AD135058238D7A337D546C902BE8505DDE"
+"212.47.241.21:80 orport=443 id=892F941915F6A0C6E0958E52E0A9685C190CF45C"
" weight=10",
-"93.180.156.84:9030 orport=9001 id=8844D87E9B038BE3270938F05AF797E1D3C74C0F"
+"163.172.194.53:9030 orport=9001 id=8C00FA7369A7A308F6A137600F0FA07990D9D451"
+" ipv6=[2001:bc8:225f:142:6c69:7461:7669:73]:9001"
" weight=10",
-"178.217.184.32:9030 orport=443 id=8B7F47AE1A5D954A3E58ACDE0865D09DBA5B738D"
+"178.254.44.135:9030 orport=9001 id=8FA37B93397015B2BC5A525C908485260BE9F422"
" weight=10",
"151.80.42.103:9030 orport=9001 id=9007C1D8E4F03D506A4A011B907A9E8D04E3C605"
" ipv6=[2001:41d0:e:f67::114]:9001"
" weight=10",
-"5.79.68.161:81 orport=443 id=9030DCF419F6E2FBF84F63CBACBA0097B06F557E"
-" ipv6=[2001:1af8:4700:a012:1::1]:443"
+"173.255.245.116:9030 orport=9001 id=91E4015E1F82DAF0121D62267E54A1F661AB6DC7"
" weight=10",
"51.255.41.65:9030 orport=9001 id=9231DF741915AA1630031A93026D88726877E93A"
" weight=10",
+"178.16.208.57:80 orport=443 id=92CFD9565B24646CAC2D172D3DB503D69E777B8A"
+" ipv6=[2a00:1c20:4089:1234:7825:2c5d:1ecd:c66f]:443"
+" weight=10",
"91.219.237.244:80 orport=443 id=92ECC9E0E2AF81BB954719B189AC362E254AD4A5"
" weight=10",
-"46.101.102.71:80 orport=443 id=9504CB22EEB25D344DE63CB7A6F2C46F895C3686"
-" ipv6=[2a03:b0c0:3:d0::2ed:7001]:9050"
+"204.8.156.142:80 orport=443 id=94C4B7B8C50C86A92B6A20107539EE2678CF9A28"
" weight=10",
-"85.214.206.219:9030 orport=9001 id=98F8D5F359949E41DE8DF3DBB1975A86E96A84A0"
+"163.172.223.200:80 orport=443 id=998BF3ED7F70E33D1C307247B9626D9E7573C438"
" weight=10",
"81.7.10.93:31336 orport=31337 id=99E246DB480B313A3012BC3363093CC26CD209C7"
" weight=10",
+"91.229.20.27:9030 orport=9001 id=9A0D54D3A6D2E0767596BF1515E6162A75B3293F"
+" weight=10",
+"66.111.2.20:9030 orport=9001 id=9A68B85A02318F4E7E87F2828039FBD5D75B0142"
+" weight=10",
+"185.100.86.128:9030 orport=9001 id=9B31F1F1C1554F9FFB3455911F82E818EF7C7883"
+" weight=10",
+"5.9.151.241:9030 orport=4223 id=9BF04559224F0F1C3C953D641F1744AF0192543A"
+" ipv6=[2a01:4f8:190:34f0::2]:4223"
+" weight=10",
+"86.105.212.130:9030 orport=443 id=9C900A7F6F5DD034CFFD192DAEC9CCAA813DB022"
+" weight=10",
+"178.254.20.134:80 orport=443 id=9F5068310818ED7C70B0BC4087AB55CB12CB4377"
+" weight=10",
"46.28.110.244:80 orport=443 id=9F7D6E6420183C2B76D3CE99624EBC98A21A967E"
" weight=10",
-"46.165.230.5:80 orport=443 id=A0F06C2FADF88D3A39AA3072B406F09D7095AC9E"
+"91.121.84.137:4952 orport=4052 id=9FBEB75E8BC142565F12CBBE078D63310236A334"
+" ipv6=[2001:41d0:1:8989::1]:4052"
+" weight=10",
+"178.62.22.36:80 orport=443 id=A0766C0D3A667A3232C7D569DE94A28F9922FCB1"
+" ipv6=[2a03:b0c0:1:d0::174:1]:9050"
" weight=10",
"171.25.193.77:80 orport=443 id=A10C4F666D27364036B562823E5830BC448E046A"
" ipv6=[2001:67c:289c:3::77]:443"
" weight=10",
-"176.9.5.116:9030 orport=9001 id=A1EB8D8F1EE28DB98BBB1EAA3B4BEDD303BAB911"
+"171.25.193.78:80 orport=443 id=A478E421F83194C114F41E94F95999672AED51FE"
+" ipv6=[2001:67c:289c:3::78]:443"
+" weight=10",
+"163.172.149.122:80 orport=443 id=A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2"
" weight=10",
"192.34.63.137:9030 orport=443 id=ABCB4965F1FEE193602B50A365425105C889D3F8"
" weight=10",
-"195.154.164.243:80 orport=443 id=AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C"
-" ipv6=[2001:bc8:399f:f000::1]:993"
+"109.163.234.9:80 orport=443 id=ABF7FBF389C9A747938B639B20E80620B460B2A9"
" weight=10",
"86.59.119.88:80 orport=443 id=ACD889D86E02EDDAB1AFD81F598C0936238DC6D0"
" weight=10",
+"185.129.62.62:9030 orport=9001 id=ACDD9E85A05B127BA010466C13C8C47212E8A38F"
+" ipv6=[2a06:d380:0:3700::62]:9001"
+" weight=10",
"163.172.131.88:80 orport=443 id=AD253B49E303C6AB1E048B014392AC569E8A7DAE"
" ipv6=[2001:bc8:4400:2100::2:1009]:443"
" weight=10",
-"178.254.44.135:80 orport=443 id=AE6A8C18E7499B586CD36246AC4BCAFFBBF93AB2"
+"31.185.104.20:80 orport=443 id=ADB2C26629643DBB9F8FE0096E7D16F9414B4F8D"
" weight=10",
"37.187.7.74:80 orport=443 id=AEA43CB1E47BE5F8051711B2BF01683DB1568E05"
" ipv6=[2001:41d0:a:74a::1]:443"
" weight=10",
+"46.28.205.170:80 orport=443 id=AF322D83A4D2048B22F7F1AF5F38AFF4D09D0B76"
+" weight=10",
+"5.9.147.226:9030 orport=9001 id=B0553175AADB0501E5A61FC61CEA3970BE130FF2"
+" weight=10",
"212.129.62.232:80 orport=443 id=B143D439B72D239A419F8DCE07B8A8EB1B486FA7"
" weight=10",
-"185.66.250.141:9030 orport=9001 id=B1726B94885CE3AC3910CA8B60622B97B98E2529"
+"198.199.64.217:80 orport=443 id=B1D81825CFD7209BD1B4520B040EF5653C204A23"
+" ipv6=[2604:a880:400:d0::1a9:b001]:9050"
+" weight=10",
+"136.243.214.137:80 orport=443 id=B291D30517D23299AD7CEE3E60DFE60D0E3A4664"
+" weight=10",
+"178.16.208.60:80 orport=443 id=B44FBE5366AD98B46D829754FA4AC599BAE41A6A"
+" ipv6=[2a00:1c20:4089:1234:67bc:79f3:61c0:6e49]:443"
+" weight=10",
+"93.115.97.242:9030 orport=9001 id=B5212DB685A2A0FCFBAE425738E478D12361710D"
+" weight=10",
+"81.2.209.10:443 orport=80 id=B6904ADD4C0D10CDA7179E051962350A69A63243"
+" ipv6=[2001:15e8:201:1::d10a]:80"
" weight=10",
"193.11.114.46:9032 orport=9003 id=B83DC1558F0D34353BB992EF93AFEAFDB226A73E"
" weight=10",
-"178.62.36.64:9030 orport=9001 id=B87C84E38DAECFFFFDE98E5AEE5786AFDC748F2C"
+"85.248.227.164:444 orport=9002 id=B84F248233FEA90CAD439F292556A3139F6E1B82"
+" ipv6=[2a00:1298:8011:212::164]:9004"
" weight=10",
-"197.231.221.211:9030 orport=9001 id=BC630CBBB518BE7E9F4E09712AB0269E9DC7D626"
+"89.163.247.43:9030 orport=9001 id=BC7ACFAC04854C77167C7D66B7E471314ED8C410"
+" ipv6=[2001:4ba0:fff7:25::5]:9001"
" weight=10",
"198.96.155.3:8080 orport=5001 id=BCEDF6C193AA687AE471B8A22EBF6BC57C2D285E"
" weight=10",
-"148.251.190.229:9030 orport=9010 id=BF0FB582E37F738CD33C3651125F2772705BB8E8"
-" ipv6=[2a01:4f8:211:c68::2]:9010"
+"128.199.55.207:9030 orport=9001 id=BCEF908195805E03E92CCFE669C48738E556B9C5"
+" ipv6=[2a03:b0c0:2:d0::158:3001]:9001"
" weight=10",
-"188.138.112.60:1433 orport=1521 id=C414F28FD2BEC1553024299B31D4E726BEB8E788"
+"185.35.202.221:9030 orport=9001 id=C13B91384CDD52A871E3ECECE4EF74A7AC7DCB08"
+" ipv6=[2a02:ed06::221]:9001"
" weight=10",
-"195.154.79.128:80 orport=443 id=C697612CA5AED06B8D829FCC6065B9287212CB2F"
+"213.239.217.18:1338 orport=1337 id=C37BC191AC389179674578C3E6944E925FE186C2"
+" ipv6=[2a01:4f8:a0:746a:101:1:1:1]:1337"
" weight=10",
-"37.59.46.159:9030 orport=9001 id=CBD0D1BD110EC52963082D839AC6A89D0AE243E7"
+"188.138.112.60:1433 orport=1521 id=C414F28FD2BEC1553024299B31D4E726BEB8E788"
" weight=10",
-"91.121.54.8:9030 orport=9001 id=CBEE0F3303C8C50462A12107CA2AE061831931BC"
+"85.248.227.163:443 orport=9001 id=C793AB88565DDD3C9E4C6F15CCB9D8C7EF964CE9"
+" ipv6=[2a00:1298:8011:212::163]:9003"
" weight=10",
"178.62.199.226:80 orport=443 id=CBEFF7BA4A4062045133C053F2D70524D8BBE5BE"
" ipv6=[2a03:b0c0:2:d0::b7:5001]:443"
" weight=10",
-"81.7.17.171:80 orport=443 id=CFECDDCA990E3EF7B7EC958B22441386B6B8D820"
-" ipv6=[2a02:180:1:1::517:11ab]:443"
-" weight=10",
"134.119.3.164:9030 orport=9001 id=D1B8AAA98C65F3DF7D8BB3AF881CAEB84A33D8EE"
" weight=10",
-"185.13.38.75:9030 orport=9001 id=D2A1703758A0FBBA026988B92C2F88BAB59F9361"
+"31.171.155.108:9030 orport=9001 id=D3E5EDDBE5159388704D6785BE51930AAFACEC6F"
" weight=10",
"37.187.115.157:9030 orport=9001 id=D5039E1EBFD96D9A3F9846BF99EC9F75EDDE902A"
" weight=10",
+"166.82.21.200:9030 orport=9029 id=D5C33F3E203728EDF8361EA868B2939CCC43FAFB"
+" weight=10",
"185.14.185.240:9030 orport=443 id=D62FB817B0288085FAC38A6DC8B36DCD85B70260"
" weight=10",
-"37.221.162.226:9030 orport=9001 id=D64366987CB39F61AD21DBCF8142FA0577B92811"
+"46.101.169.151:9030 orport=9001 id=D760C5B436E42F93D77EF2D969157EEA14F9B39C"
+" ipv6=[2a03:b0c0:3:d0::74f:a001]:9001"
+" weight=10",
+"46.4.111.124:9030 orport=9001 id=D9065F9E57899B3D272AA212317AF61A9B14D204"
" weight=10",
"193.35.52.53:9030 orport=9001 id=DAA39FC00B196B353C2A271459C305C429AF09E4"
" weight=10",
+"178.33.183.251:80 orport=443 id=DD823AFB415380A802DCAEB9461AE637604107FB"
+" ipv6=[2001:41d0:2:a683::251]:443"
+" weight=10",
"178.62.173.203:9030 orport=9001 id=DD85503F2D1F52EF9EAD621E942298F46CD2FC10"
" ipv6=[2a03:b0c0:0:1010::a4:b001]:9001"
" weight=10",
"5.34.183.205:80 orport=443 id=DDD7871C1B7FA32CB55061E08869A236E61BDDF8"
" weight=10",
-"195.191.233.221:80 orport=443 id=DE134FC8E5CC4EC8A5DE66934E70AC9D70267197"
+"78.24.75.53:9030 orport=9001 id=DEB73705B2929AE9BE87091607388939332EF123"
+" weight=10",
+"92.222.38.67:80 orport=443 id=DED6892FF89DBD737BA689698A171B2392EB3E82"
+" weight=10",
+"166.70.207.2:9030 orport=9001 id=E3DB2E354B883B59E8DC56B3E7A353DDFD457812"
" weight=10",
"46.252.26.2:45212 orport=49991 id=E589316576A399C511A9781A73DA4545640B479D"
" weight=10",
-"176.31.180.157:143 orport=22 id=E781F4EC69671B3F1864AE2753E0890351506329"
-" ipv6=[2001:41d0:8:eb9d::1]:22"
+"167.114.35.28:9030 orport=9001 id=E65D300F11E1DB12C534B0146BDAB6972F1A8A48"
" weight=10",
"131.188.40.188:443 orport=80 id=EBE718E1A49EE229071702964F8DB1F318075FF8"
" weight=10",
-"91.219.236.222:80 orport=443 id=EC413181CEB1C8EDC17608BBB177CD5FD8535E99"
+"192.87.28.28:9030 orport=9001 id=ED2338CAC2711B3E331392E1ED2831219B794024"
+" weight=10",
+"192.99.212.139:80 orport=443 id=F10BDE279AE71515DDCCCC61DC19AC8765F8A3CC"
+" weight=10",
+"212.238.208.48:9030 orport=9001 id=F406219CDD339026D160E53FCA0EF6857C70F109"
+" ipv6=[2001:984:a8fb:1:ba27:ebff:feac:c109]:9001"
" weight=10",
-"94.242.246.23:443 orport=9001 id=F65E0196C94DFFF48AFBF2F5F9E3E19AAE583FD0"
-" ipv6=[2a01:608:ffff:ff07::1:23]:9003"
+"46.28.207.141:80 orport=443 id=F69BED36177ED727706512BA6A97755025EEA0FB"
" weight=10",
-"46.101.143.173:80 orport=443 id=F960DF50F0FD4075AC9B505C1D4FFC8384C490FB"
+"78.47.18.110:443 orport=80 id=F8D27B163B9247B232A2EEE68DD8B698695C28DE"
" weight=10",
-"195.154.8.111:80 orport=443 id=FCB6695F8F2DC240E974510A4B3A0F2B12AB5B64"
+"178.254.13.126:80 orport=443 id=F9246DEF2B653807236DA134F2AEAB103D58ABFE"
+" weight=10",
+"185.96.180.29:80 orport=443 id=F93D8F37E35C390BCAD9F9069E13085B745EC216"
+" weight=10",
+"86.59.119.83:80 orport=443 id=FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B"
" weight=10",
"192.187.124.98:9030 orport=9001 id=FD1871854BFC06D7B02F10742073069F0528B5CC"
" weight=10",
+"149.56.45.200:9030 orport=9001 id=FE296180018833AF03A8EACD5894A614623D3F76"
+" weight=10",
"193.11.164.243:9030 orport=9001 id=FFA72BD683BC2FCF988356E6BEC1E490F313FB07"
" ipv6=[2001:6b0:7:125::243]:9001"
" weight=10",
diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c
index 53b311e580..f730106d06 100644
--- a/src/or/fp_pair.c
+++ b/src/or/fp_pair.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Tor Project, Inc. */
+/* Copyright (c) 2013-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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/fp_pair.h b/src/or/fp_pair.h
index b1466581d2..4cea3eda6d 100644
--- a/src/or/fp_pair.h
+++ b/src/or/fp_pair.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Tor Project, Inc. */
+/* Copyright (c) 2013-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/geoip.c b/src/or/geoip.c
index b563db0418..65d00b8659 100644
--- a/src/or/geoip.c
+++ b/src/or/geoip.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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);
}
@@ -144,6 +162,7 @@ geoip_parse_entry(const char *line, sa_family_t family)
if (*line == '#')
return 0;
+ char buf[512];
if (family == AF_INET) {
unsigned int low, high;
if (tor_sscanf(line,"%u,%u,%2s", &low, &high, c) == 3 ||
@@ -154,7 +173,6 @@ geoip_parse_entry(const char *line, sa_family_t family)
goto fail;
country = c;
} else { /* AF_INET6 */
- char buf[512];
char *low_str, *high_str;
struct in6_addr low, high;
char *strtok_state;
@@ -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;
@@ -863,7 +880,7 @@ geoip_get_transport_history(void)
/* If it's the first time we see this transport, note it. */
if (val == 1)
- smartlist_add(transports_used, tor_strdup(transport_name));
+ smartlist_add_strdup(transports_used, transport_name);
log_debug(LD_GENERAL, "Client from '%s' with transport '%s'. "
"I've now seen %d clients.",
@@ -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/geoip.h b/src/or/geoip.h
index 070296dd07..55ca8ca28c 100644
--- a/src/or/geoip.h
+++ b/src/or/geoip.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index 3666abbcf4..f54109fc90 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -1,5 +1,5 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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").
**/
/*
@@ -34,8 +40,6 @@ hibernating, phase 2:
#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;
@@ -51,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
@@ -66,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? */
@@ -321,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. */
@@ -414,8 +424,8 @@ configure_accounting(time_t now)
if (-0.50 <= delta && delta <= 0.50) {
/* The start of the period is now a little later or earlier than we
* remembered. That's fine; we might lose some bytes we could otherwise
- * have written, but better to err on the side of obeying people's
- * accounting settings. */
+ * have written, but better to err on the side of obeying accounting
+ * settings. */
log_info(LD_ACCT, "Accounting interval moved by %.02f%%; "
"that's fine.", delta*100);
interval_end_time = start_of_accounting_period_after(now);
@@ -694,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));
@@ -886,7 +896,7 @@ hibernate_go_dormant(time_t now)
log_notice(LD_ACCT,"Going dormant. Blowing away remaining connections.");
/* Close all OR/AP/exit conns. Leave dir conns because we still want
- * to be able to upload server descriptors so people know we're still
+ * to be able to upload server descriptors so clients know we're still
* running, and download directories so we can detect if we're obsolete.
* Leave control conns because we still want to be controllable.
*/
diff --git a/src/or/hibernate.h b/src/or/hibernate.h
index fa9da6de39..8bdb65a927 100644
--- a/src/or/hibernate.h
+++ b/src/or/hibernate.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c
new file mode 100644
index 0000000000..30215d8681
--- /dev/null
+++ b/src/or/hs_cache.c
@@ -0,0 +1,400 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.c
+ * \brief Handle hidden service descriptor caches.
+ **/
+
+/* For unit tests.*/
+#define HS_CACHE_PRIVATE
+
+#include "hs_cache.h"
+
+#include "or.h"
+#include "config.h"
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "networkstatus.h"
+#include "rendcache.h"
+
+/* Directory descriptor cache. Map indexed by blinded key. */
+static digest256map_t *hs_cache_v3_dir;
+
+/* Remove a given descriptor from our cache. */
+static void
+remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_remove(hs_cache_v3_dir, desc->key);
+}
+
+/* Store a given descriptor in our cache. */
+static void
+store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_set(hs_cache_v3_dir, desc->key, desc);
+}
+
+/* Query our cache and return the entry or NULL if not found. */
+static hs_cache_dir_descriptor_t *
+lookup_v3_desc_as_dir(const uint8_t *key)
+{
+ tor_assert(key);
+ return digest256map_get(hs_cache_v3_dir, key);
+}
+
+/* Free a directory descriptor object. */
+static void
+cache_dir_desc_free(hs_cache_dir_descriptor_t *desc)
+{
+ if (desc == NULL) {
+ return;
+ }
+ hs_desc_plaintext_data_free(desc->plaintext_data);
+ tor_free(desc->encoded_desc);
+ tor_free(desc);
+}
+
+/* Helper function: Use by the free all function using the digest256map
+ * interface to cache entries. */
+static void
+cache_dir_desc_free_(void *ptr)
+{
+ hs_cache_dir_descriptor_t *desc = ptr;
+ cache_dir_desc_free(desc);
+}
+
+/* Create a new directory cache descriptor object from a encoded descriptor.
+ * On success, return the heap-allocated cache object, otherwise return NULL if
+ * we can't decode the descriptor. */
+static hs_cache_dir_descriptor_t *
+cache_dir_desc_new(const char *desc)
+{
+ hs_cache_dir_descriptor_t *dir_desc;
+
+ tor_assert(desc);
+
+ dir_desc = tor_malloc_zero(sizeof(hs_cache_dir_descriptor_t));
+ dir_desc->plaintext_data =
+ tor_malloc_zero(sizeof(hs_desc_plaintext_data_t));
+ dir_desc->encoded_desc = tor_strdup(desc);
+
+ if (hs_desc_decode_plaintext(desc, dir_desc->plaintext_data) < 0) {
+ log_debug(LD_DIR, "Unable to decode descriptor. Rejecting.");
+ goto err;
+ }
+
+ /* The blinded pubkey is the indexed key. */
+ dir_desc->key = dir_desc->plaintext_data->blinded_pubkey.pubkey;
+ dir_desc->created_ts = time(NULL);
+ return dir_desc;
+
+ err:
+ cache_dir_desc_free(dir_desc);
+ return NULL;
+}
+
+/* Return the size of a cache entry in bytes. */
+static size_t
+cache_get_entry_size(const hs_cache_dir_descriptor_t *entry)
+{
+ return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
+ + strlen(entry->encoded_desc));
+}
+
+/* Try to store a valid version 3 descriptor in the directory cache. Return 0
+ * on success else a negative value is returned indicating that we have a
+ * newer version in our cache. On error, caller is responsible to free the
+ * given descriptor desc. */
+static int
+cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
+{
+ hs_cache_dir_descriptor_t *cache_entry;
+
+ tor_assert(desc);
+
+ /* Verify if we have an entry in the cache for that key and if yes, check
+ * if we should replace it? */
+ cache_entry = lookup_v3_desc_as_dir(desc->key);
+ if (cache_entry != NULL) {
+ /* Only replace descriptor if revision-counter is greater than the one
+ * in our cache */
+ if (cache_entry->plaintext_data->revision_counter >=
+ desc->plaintext_data->revision_counter) {
+ log_info(LD_REND, "Descriptor revision counter in our cache is "
+ "greater or equal than the one we received (%d/%d). "
+ "Rejecting!",
+ (int)cache_entry->plaintext_data->revision_counter,
+ (int)desc->plaintext_data->revision_counter);
+ goto err;
+ }
+ /* We now know that the descriptor we just received is a new one so
+ * remove the entry we currently have from our cache so we can then
+ * store the new one. */
+ remove_v3_desc_as_dir(cache_entry);
+ rend_cache_decrement_allocation(cache_get_entry_size(cache_entry));
+ cache_dir_desc_free(cache_entry);
+ }
+ /* Store the descriptor we just got. We are sure here that either we
+ * don't have the entry or we have a newer descriptor and the old one
+ * has been removed from the cache. */
+ store_v3_desc_as_dir(desc);
+
+ /* Update our total cache size with this entry for the OOM. This uses the
+ * old HS protocol cache subsystem for which we are tied with. */
+ rend_cache_increment_allocation(cache_get_entry_size(desc));
+
+ /* XXX: Update HS statistics. We should have specific stats for v3. */
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Using the query which is the base64 encoded blinded key of a version 3
+ * descriptor, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being the
+ * encoded descriptor. If not found, 0 is returned and desc_out is untouched.
+ * On error, a negative value is returned and desc_out is untouched. */
+static int
+cache_lookup_v3_as_dir(const char *query, const char **desc_out)
+{
+ int found = 0;
+ ed25519_public_key_t blinded_key;
+ const hs_cache_dir_descriptor_t *entry;
+
+ tor_assert(query);
+
+ /* Decode blinded key using the given query value. */
+ if (ed25519_public_from_base64(&blinded_key, query) < 0) {
+ log_info(LD_REND, "Unable to decode the v3 HSDir query %s.",
+ safe_str_client(query));
+ goto err;
+ }
+
+ entry = lookup_v3_desc_as_dir(blinded_key.pubkey);
+ if (entry != NULL) {
+ found = 1;
+ if (desc_out) {
+ *desc_out = entry->encoded_desc;
+ }
+ }
+
+ return found;
+
+ err:
+ return -1;
+}
+
+/* Clean the v3 cache by removing any entry that has expired using the
+ * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning
+ * process will use the lifetime found in the plaintext data section. Return
+ * the number of bytes cleaned. */
+STATIC size_t
+cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
+{
+ size_t bytes_removed = 0;
+
+ /* Code flow error if this ever happens. */
+ tor_assert(global_cutoff >= 0);
+
+ if (!hs_cache_v3_dir) { /* No cache to clean. Just return. */
+ return 0;
+ }
+
+ DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_dir, key,
+ hs_cache_dir_descriptor_t *, entry) {
+ size_t entry_size;
+ time_t cutoff = global_cutoff;
+ if (!cutoff) {
+ /* Cutoff is the lifetime of the entry found in the descriptor. */
+ cutoff = now - entry->plaintext_data->lifetime_sec;
+ }
+
+ /* If the entry has been created _after_ the cutoff, not expired so
+ * continue to the next entry in our v3 cache. */
+ if (entry->created_ts > cutoff) {
+ continue;
+ }
+ /* Here, our entry has expired, remove and free. */
+ MAP_DEL_CURRENT(key);
+ entry_size = cache_get_entry_size(entry);
+ bytes_removed += entry_size;
+ /* Entry is not in the cache anymore, destroy it. */
+ cache_dir_desc_free(entry);
+ /* Update our cache entry allocation size for the OOM. */
+ rend_cache_decrement_allocation(entry_size);
+ /* Logging. */
+ {
+ char key_b64[BASE64_DIGEST256_LEN + 1];
+ base64_encode(key_b64, sizeof(key_b64), (const char *) key,
+ DIGEST256_LEN, 0);
+ log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache",
+ safe_str_client(key_b64));
+ }
+ } DIGEST256MAP_FOREACH_END;
+
+ return bytes_removed;
+}
+
+/* Given an encoded descriptor, store it in the directory cache depending on
+ * which version it is. Return a negative value on error. On success, 0 is
+ * returned. */
+int
+hs_cache_store_as_dir(const char *desc)
+{
+ hs_cache_dir_descriptor_t *dir_desc = NULL;
+
+ tor_assert(desc);
+
+ /* Create a new cache object. This can fail if the descriptor plaintext data
+ * is unparseable which in this case a log message will be triggered. */
+ dir_desc = cache_dir_desc_new(desc);
+ if (dir_desc == NULL) {
+ goto err;
+ }
+
+ /* Call the right function against the descriptor version. At this point,
+ * we are sure that the descriptor's version is supported else the
+ * decoding would have failed. */
+ switch (dir_desc->plaintext_data->version) {
+ case HS_VERSION_THREE:
+ default:
+ if (cache_store_v3_as_dir(dir_desc) < 0) {
+ goto err;
+ }
+ break;
+ }
+ return 0;
+
+ err:
+ cache_dir_desc_free(dir_desc);
+ return -1;
+}
+
+/* Using the query, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being
+ * the encoded descriptor. If not found, 0 is returned and desc_out is
+ * untouched. On error, a negative value is returned and desc_out is
+ * untouched. */
+int
+hs_cache_lookup_as_dir(uint32_t version, const char *query,
+ const char **desc_out)
+{
+ int found;
+
+ tor_assert(query);
+ /* This should never be called with an unsupported version. */
+ tor_assert(hs_desc_is_supported_version(version));
+
+ switch (version) {
+ case HS_VERSION_THREE:
+ default:
+ found = cache_lookup_v3_as_dir(query, desc_out);
+ break;
+ }
+
+ return found;
+}
+
+/* Clean all directory caches using the current time now. */
+void
+hs_cache_clean_as_dir(time_t now)
+{
+ time_t cutoff;
+
+ /* Start with v2 cache cleaning. */
+ cutoff = now - rend_cache_max_entry_lifetime();
+ rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
+ * to compute the cutoff by itself using the lifetime value. */
+ cache_clean_v3_as_dir(now, 0);
+}
+
+/* Do a round of OOM cleanup on all directory caches. Return the amount of
+ * removed bytes. It is possible that the returned value is lower than
+ * min_remove_bytes if the caches get emptied out so the caller should be
+ * aware of this. */
+size_t
+hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
+{
+ time_t k;
+ size_t bytes_removed = 0;
+
+ /* Our OOM handler called with 0 bytes to remove is a code flow error. */
+ tor_assert(min_remove_bytes != 0);
+
+ /* The algorithm is as follow. K is the oldest expected descriptor age.
+ *
+ * 1) Deallocate all entries from v2 cache that are older than K hours.
+ * 1.1) If the amount of remove bytes has been reached, stop.
+ * 2) Deallocate all entries from v3 cache that are older than K hours
+ * 2.1) If the amount of remove bytes has been reached, stop.
+ * 3) Set K = K - RendPostPeriod and repeat process until K is < 0.
+ *
+ * This ends up being O(Kn).
+ */
+
+ /* Set K to the oldest expected age in seconds which is the maximum
+ * lifetime of a cache entry. We'll use the v2 lifetime because it's much
+ * bigger than the v3 thus leading to cleaning older descriptors. */
+ k = rend_cache_max_entry_lifetime();
+
+ do {
+ time_t cutoff;
+
+ /* If K becomes negative, it means we've empty the caches so stop and
+ * return what we were able to cleanup. */
+ if (k < 0) {
+ break;
+ }
+ /* Compute a cutoff value with K and the current time. */
+ cutoff = now - k;
+
+ /* Start by cleaning the v2 cache with that cutoff. */
+ bytes_removed += rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ if (bytes_removed < min_remove_bytes) {
+ /* We haven't remove enough bytes so clean v3 cache. */
+ bytes_removed += cache_clean_v3_as_dir(now, cutoff);
+ /* Decrement K by a post period to shorten the cutoff. */
+ k -= get_options()->RendPostPeriod;
+ }
+ } while (bytes_removed < min_remove_bytes);
+
+ return bytes_removed;
+}
+
+/**
+ * Return the maximum size of an HS descriptor we are willing to accept as an
+ * HSDir.
+ */
+unsigned int
+hs_cache_get_max_descriptor_size(void)
+{
+ return (unsigned) networkstatus_get_param(NULL,
+ "HSV3MaxDescriptorSize",
+ HS_DESC_MAX_LEN, 1, INT32_MAX);
+}
+
+/* Initialize the hidden service cache subsystem. */
+void
+hs_cache_init(void)
+{
+ /* Calling this twice is very wrong code flow. */
+ tor_assert(!hs_cache_v3_dir);
+ hs_cache_v3_dir = digest256map_new();
+}
+
+/* Cleanup the hidden service cache subsystem. */
+void
+hs_cache_free_all(void)
+{
+ digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_);
+ hs_cache_v3_dir = NULL;
+}
+
diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h
new file mode 100644
index 0000000000..ed00424234
--- /dev/null
+++ b/src/or/hs_cache.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.h
+ * \brief Header file for hs_cache.c
+ **/
+
+#ifndef TOR_HS_CACHE_H
+#define TOR_HS_CACHE_H
+
+#include <stdint.h>
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "torcert.h"
+
+/* Descriptor representation on the directory side which is a subset of
+ * information that the HSDir can decode and serve it. */
+typedef struct hs_cache_dir_descriptor_t {
+ /* This object is indexed using the blinded pubkey located in the plaintext
+ * data which is populated only once the descriptor has been successfully
+ * decoded and validated. This simply points to that pubkey. */
+ const uint8_t *key;
+
+ /* When does this entry has been created. Used to expire entries. */
+ time_t created_ts;
+
+ /* Descriptor plaintext information. Obviously, we can't decrypt the
+ * encrypted part of the descriptor. */
+ hs_desc_plaintext_data_t *plaintext_data;
+
+ /* Encoded descriptor which is basically in text form. It's a NUL terminated
+ * string thus safe to strlen(). */
+ char *encoded_desc;
+} hs_cache_dir_descriptor_t;
+
+/* Public API */
+
+void hs_cache_init(void);
+void hs_cache_free_all(void);
+void hs_cache_clean_as_dir(time_t now);
+size_t hs_cache_handle_oom(time_t now, size_t min_remove_bytes);
+
+unsigned int hs_cache_get_max_descriptor_size(void);
+
+/* Store and Lookup function. They are version agnostic that is depending on
+ * the requested version of the descriptor, it will be re-routed to the
+ * right function. */
+int hs_cache_store_as_dir(const char *desc);
+int hs_cache_lookup_as_dir(uint32_t version, const char *query,
+ const char **desc_out);
+
+#ifdef HS_CACHE_PRIVATE
+
+STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
+
+#endif /* HS_CACHE_PRIVATE */
+
+#endif /* TOR_HS_CACHE_H */
+
diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c
new file mode 100644
index 0000000000..a0e9074601
--- /dev/null
+++ b/src/or/hs_cell.c
@@ -0,0 +1,584 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.c
+ * \brief Hidden service API for cell creation and handling.
+ **/
+
+#include "or.h"
+#include "config.h"
+#include "rendservice.h"
+#include "replaycache.h"
+
+#include "hs_cell.h"
+#include "hs_ntor.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+#include "hs/cell_rendezvous.h"
+
+/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is
+ * the cell content up to the ENCRYPTED section of length encoded_cell_len.
+ * The encrypted param is the start of the ENCRYPTED section of length
+ * encrypted_len. The mac_key is the key needed for the computation of the MAC
+ * derived from the ntor handshake of length mac_key_len.
+ *
+ * The length mac_out_len must be at least DIGEST256_LEN. */
+static void
+compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ const uint8_t *mac_key, size_t mac_key_len,
+ uint8_t *mac_out, size_t mac_out_len)
+{
+ size_t offset = 0;
+ size_t mac_msg_len;
+ uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(encoded_cell);
+ tor_assert(encrypted);
+ tor_assert(mac_key);
+ tor_assert(mac_out);
+ tor_assert(mac_out_len >= DIGEST256_LEN);
+
+ /* Compute the size of the message which is basically the entire cell until
+ * the MAC field of course. */
+ mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN);
+ tor_assert(mac_msg_len <= sizeof(mac_msg));
+
+ /* First, put the encoded cell in the msg. */
+ memcpy(mac_msg, encoded_cell, encoded_cell_len);
+ offset += encoded_cell_len;
+ /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which
+ * is junk at this point). */
+ memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN));
+ offset += (encrypted_len - DIGEST256_LEN);
+ tor_assert(offset == mac_msg_len);
+
+ crypto_mac_sha3_256(mac_out, mac_out_len,
+ mac_key, mac_key_len,
+ mac_msg, mac_msg_len);
+ memwipe(mac_msg, 0, sizeof(mac_msg));
+}
+
+/* From a set of keys, subcredential and the ENCRYPTED section of an
+ * INTRODUCE2 cell, return a newly allocated intro cell keys structure.
+ * Finally, the client public key is copied in client_pk. On error, return
+ * NULL. */
+static hs_ntor_intro_cell_keys_t *
+get_introduce2_key_material(const ed25519_public_key_t *auth_key,
+ const curve25519_keypair_t *enc_key,
+ const uint8_t *subcredential,
+ const uint8_t *encrypted_section,
+ curve25519_public_key_t *client_pk)
+{
+ hs_ntor_intro_cell_keys_t *keys;
+
+ tor_assert(auth_key);
+ tor_assert(enc_key);
+ tor_assert(subcredential);
+ tor_assert(encrypted_section);
+ tor_assert(client_pk);
+
+ keys = tor_malloc_zero(sizeof(*keys));
+
+ /* First bytes of the ENCRYPTED section are the client public key. */
+ memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);
+
+ if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk,
+ subcredential, keys) < 0) {
+ /* Don't rely on the caller to wipe this on error. */
+ memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
+ tor_free(keys);
+ keys = NULL;
+ }
+ return keys;
+}
+
+/* Using the given encryption key, decrypt the encrypted_section of length
+ * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated
+ * buffer containing the decrypted data. On decryption failure, NULL is
+ * returned. */
+static uint8_t *
+decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section,
+ size_t encrypted_section_len)
+{
+ uint8_t *decrypted = NULL;
+ crypto_cipher_t *cipher = NULL;
+
+ tor_assert(enc_key);
+ tor_assert(encrypted_section);
+
+ /* Decrypt ENCRYPTED section. */
+ cipher = crypto_cipher_new_with_bits((char *) enc_key,
+ CURVE25519_PUBKEY_LEN * 8);
+ tor_assert(cipher);
+
+ /* This is symmetric encryption so can't be bigger than the encrypted
+ * section length. */
+ decrypted = tor_malloc_zero(encrypted_section_len);
+ if (crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted_section,
+ encrypted_section_len) < 0) {
+ tor_free(decrypted);
+ decrypted = NULL;
+ goto done;
+ }
+
+ done:
+ crypto_cipher_free(cipher);
+ return decrypted;
+}
+
+/* Given a pointer to the decrypted data of the ENCRYPTED section of an
+ * INTRODUCE2 cell of length decrypted_len, parse and validate the cell
+ * content. Return a newly allocated cell structure or NULL on error. The
+ * circuit and service object are only used for logging purposes. */
+static trn_cell_introduce_encrypted_t *
+parse_introduce2_encrypted(const uint8_t *decrypted_data,
+ size_t decrypted_len, const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+
+ tor_assert(decrypted_data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data,
+ decrypted_len) < 0) {
+ log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of "
+ "the INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) !=
+ HS_CELL_ONION_KEY_TYPE_NTOR) {
+ log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but "
+ "expected %u on circuit %u for service %s",
+ trn_cell_introduce_encrypted_get_onion_key_type(enc_cell),
+ HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) !=
+ CURVE25519_PUBKEY_LEN) {
+ log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but "
+ "expected %d on circuit %u for service %s",
+ (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* XXX: Validate NSPEC field as well. */
+
+ return enc_cell;
+ err:
+ trn_cell_introduce_encrypted_free(enc_cell);
+ return NULL;
+}
+
+/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA
+ * encryption key. The encoded cell is put in cell_out that MUST at least be
+ * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on
+ * success else a negative value and cell_out is untouched. */
+static ssize_t
+build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+
+ tor_assert(circ_nonce);
+ tor_assert(enc_key);
+ tor_assert(cell_out);
+
+ memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE);
+
+ cell_len = rend_service_encode_establish_intro_cell((char*)cell_out,
+ RELAY_PAYLOAD_SIZE,
+ enc_key, circ_nonce);
+ return cell_len;
+}
+
+/* Parse an INTRODUCE2 cell from payload of size payload_len for the given
+ * service and circuit which are used only for logging purposes. The resulting
+ * parsed cell is put in cell_ptr_out.
+ *
+ * This function only parses prop224 INTRODUCE2 cells even when the intro point
+ * is a legacy intro point. That's because intro points don't actually care
+ * about the contents of the introduce cell. Legacy INTRODUCE cells are only
+ * used by the legacy system now.
+ *
+ * Return 0 on success else a negative value and cell_ptr_out is untouched. */
+static int
+parse_introduce2_cell(const hs_service_t *service,
+ const origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len,
+ trn_cell_introduce1_t **cell_ptr_out)
+{
+ trn_cell_introduce1_t *cell = NULL;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(cell_ptr_out);
+
+ /* Parse the cell so we can start cell validation. */
+ if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Success. */
+ *cell_ptr_out = cell;
+ return 0;
+ err:
+ return -1;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point
+ * object. The encoded cell is put in cell_out that MUST at least be of the
+ * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else
+ * a negative value and cell_out is untouched. This function also supports
+ * legacy cell creation. */
+ssize_t
+hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len = -1;
+ uint16_t sig_len = ED25519_SIG_LEN;
+ trn_cell_extension_t *ext;
+ trn_cell_establish_intro_t *cell = NULL;
+
+ tor_assert(circ_nonce);
+ tor_assert(ip);
+
+ /* Quickly handle the legacy IP. */
+ if (ip->base.is_only_legacy) {
+ tor_assert(ip->legacy_key);
+ cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key,
+ cell_out);
+ tor_assert(cell_len <= RELAY_PAYLOAD_SIZE);
+ /* Success or not we are done here. */
+ goto done;
+ }
+
+ /* Set extension data. None used here. */
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ cell = trn_cell_establish_intro_new();
+ trn_cell_establish_intro_set_extensions(cell, ext);
+ /* Set signature size. Array is then allocated in the cell. We need to do
+ * this early so we can use trunnel API to get the signature length. */
+ trn_cell_establish_intro_set_sig_len(cell, sig_len);
+ trn_cell_establish_intro_setlen_sig(cell, sig_len);
+
+ /* Set AUTH_KEY_TYPE: 2 means ed25519 */
+ trn_cell_establish_intro_set_auth_key_type(cell,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519);
+
+ /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of
+ * AUTH_KEY to match */
+ {
+ uint16_t auth_key_len = ED25519_PUBKEY_LEN;
+ trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
+ trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
+ /* We do this call _after_ setting the length because it's reallocated at
+ * that point only. */
+ uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
+ memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len);
+ }
+
+ /* Calculate HANDSHAKE_AUTH field (MAC). */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_mac_offset =
+ sig_len + sizeof(cell->sig_len) +
+ trn_cell_establish_intro_getlen_handshake_mac(cell);
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the MAC using the raw bytes. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+ /* Sanity check. */
+ tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset);
+
+ /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ (uint8_t *) circ_nonce, DIGEST_LEN,
+ tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset);
+ handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell);
+ memcpy(handshake_ptr, mac, sizeof(mac));
+
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Calculate the cell signature SIG. */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len));
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr;
+ ed25519_signature_t sig;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the signature from the raw bytes of the cell. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+
+ if (ed25519_sign_prefixed(&sig, tmp_cell_enc,
+ tmp_cell_enc_len - tmp_cell_sig_offset,
+ ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) {
+ log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell.");
+ goto done;
+ }
+ /* Copy the signature into the cell. */
+ sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
+ memcpy(sig_ptr, sig.sig, sig_len);
+
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Encode the cell. Can't be bigger than a standard cell. */
+ cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE,
+ cell);
+
+ done:
+ trn_cell_establish_intro_free(cell);
+ return cell_len;
+}
+
+/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we
+ * are successful at parsing it, return the length of the parsed cell else a
+ * negative value on error. */
+ssize_t
+hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
+{
+ ssize_t ret;
+ trn_cell_intro_established_t *cell = NULL;
+
+ tor_assert(payload);
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. */
+ ret = trn_cell_intro_established_parse(&cell, payload, payload_len);
+ if (ret >= 0) {
+ /* On success, we do not keep the cell, we just notify the caller that it
+ * was successfully parsed. */
+ trn_cell_intro_established_free(cell);
+ }
+ return ret;
+}
+
+/* Parsse the INTRODUCE2 cell using data which contains everything we need to
+ * do so and contains the destination buffers of information we extract and
+ * compute from the cell. Return 0 on success else a negative value. The
+ * service and circ are only used for logging purposes. */
+ssize_t
+hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ int ret = -1;
+ time_t elapsed;
+ uint8_t *decrypted = NULL;
+ size_t encrypted_section_len;
+ const uint8_t *encrypted_section;
+ trn_cell_introduce1_t *cell = NULL;
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+ hs_ntor_intro_cell_keys_t *intro_keys = NULL;
+
+ tor_assert(data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ /* Parse the cell into a decoded data structure pointed by cell_ptr. */
+ if (parse_introduce2_cell(service, circ, data->payload, data->payload_len,
+ &cell) < 0) {
+ goto done;
+ }
+
+ log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u "
+ "for service %s. Decoding encrypted section...",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+
+ encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell);
+ encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell);
+
+ /* Encrypted section must at least contain the CLIENT_PK and MAC which is
+ * defined in section 3.3.2 of the specification. */
+ if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length "
+ "for service %s. Dropping cell.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Check our replay cache for this introduction point. */
+ if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
+ encrypted_section_len, &elapsed)) {
+ log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the"
+ "same ENCRYPTED section was seen %ld seconds ago. "
+ "Dropping cell.", elapsed);
+ goto done;
+ }
+
+ /* Build the key material out of the key material found in the cell. */
+ intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
+ data->subcredential,
+ encrypted_section,
+ &data->client_pk);
+ if (intro_keys == NULL) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
+ "compute key material on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Validate MAC from the cell and our computed key material. The MAC field
+ * in the cell is at the end of the encrypted section. */
+ {
+ uint8_t mac[DIGEST256_LEN];
+ /* The MAC field is at the very end of the ENCRYPTED section. */
+ size_t mac_offset = encrypted_section_len - sizeof(mac);
+ /* Compute the MAC. Use the entire encoded payload with a length up to the
+ * ENCRYPTED section. */
+ compute_introduce_mac(data->payload,
+ data->payload_len - encrypted_section_len,
+ encrypted_section, encrypted_section_len,
+ intro_keys->mac_key, sizeof(intro_keys->mac_key),
+ mac, sizeof(mac));
+ if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) {
+ log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on "
+ "circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ }
+
+ {
+ /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
+ const uint8_t *encrypted_data =
+ encrypted_section + sizeof(data->client_pk);
+ /* It's symmetric encryption so it's correct to use the ENCRYPTED length
+ * for decryption. Computes the length of ENCRYPTED_DATA meaning removing
+ * the CLIENT_PK and MAC length. */
+ size_t encrypted_data_len =
+ encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN);
+
+ /* This decrypts the ENCRYPTED_DATA section of the cell. */
+ decrypted = decrypt_introduce2(intro_keys->enc_key,
+ encrypted_data, encrypted_data_len);
+ if (decrypted == NULL) {
+ log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an "
+ "INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Parse this blob into an encrypted cell structure so we can then extract
+ * the data we need out of it. */
+ enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len,
+ circ, service);
+ memwipe(decrypted, 0, encrypted_data_len);
+ if (enc_cell == NULL) {
+ goto done;
+ }
+ }
+
+ /* XXX: Implement client authorization checks. */
+
+ /* Extract onion key and rendezvous cookie from the cell used for the
+ * rendezvous point circuit e2e encryption. */
+ memcpy(data->onion_pk.public_key,
+ trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN);
+ memcpy(data->rendezvous_cookie,
+ trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell),
+ sizeof(data->rendezvous_cookie));
+
+ /* Extract rendezvous link specifiers. */
+ for (size_t idx = 0;
+ idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) {
+ link_specifier_t *lspec =
+ trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx);
+ smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec));
+ }
+
+ /* Success. */
+ ret = 0;
+ log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit.");
+
+ done:
+ if (intro_keys) {
+ memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ tor_free(intro_keys);
+ }
+ tor_free(decrypted);
+ trn_cell_introduce_encrypted_free(enc_cell);
+ trn_cell_introduce1_free(cell);
+ return ret;
+}
+
+/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake
+ * info. The encoded cell is put in cell_out and the length of the data is
+ * returned. This can't fail. */
+ssize_t
+hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+ trn_cell_rendezvous1_t *cell;
+
+ tor_assert(rendezvous_cookie);
+ tor_assert(rendezvous_handshake_info);
+ tor_assert(cell_out);
+
+ cell = trn_cell_rendezvous1_new();
+ /* Set the RENDEZVOUS_COOKIE. */
+ memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell),
+ rendezvous_cookie, rendezvous_cookie_len);
+ /* Set the HANDSHAKE_INFO. */
+ trn_cell_rendezvous1_setlen_handshake_info(cell,
+ rendezvous_handshake_info_len);
+ memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell),
+ rendezvous_handshake_info, rendezvous_handshake_info_len);
+ /* Encoding. */
+ cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+ tor_assert(cell_len > 0);
+
+ trn_cell_rendezvous1_free(cell);
+ return cell_len;
+}
+
diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h
new file mode 100644
index 0000000000..f32f7a4216
--- /dev/null
+++ b/src/or/hs_cell.h
@@ -0,0 +1,75 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.h
+ * \brief Header file containing cell data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CELL_H
+#define TOR_HS_CELL_H
+
+#include "or.h"
+#include "hs_service.h"
+
+/* Onion key type found in the INTRODUCE1 cell. */
+typedef enum {
+ HS_CELL_ONION_KEY_TYPE_NTOR = 1,
+} hs_cell_onion_key_type_t;
+
+/* This data structure contains data that we need to parse an INTRODUCE2 cell
+ * which is used by the INTRODUCE2 cell parsing function. On a successful
+ * parsing, the onion_pk and rendezvous_cookie will be populated with the
+ * computed key material from the cell data. This structure is only used during
+ * INTRO2 parsing and discarded after that. */
+typedef struct hs_cell_introduce2_data_t {
+ /*** Immutable Section: Set on structure init. ***/
+
+ /* Introduction point authentication public key. Pointer owned by the
+ introduction point object through which we received the INTRO2 cell. */
+ const ed25519_public_key_t *auth_pk;
+ /* Introduction point encryption keypair for the ntor handshake. Pointer
+ owned by the introduction point object through which we received the
+ INTRO2 cell*/
+ const curve25519_keypair_t *enc_kp;
+ /* Subcredentials of the service. Pointer owned by the descriptor that owns
+ the introduction point through which we received the INTRO2 cell. */
+ const uint8_t *subcredential;
+ /* Payload of the received encoded cell. */
+ const uint8_t *payload;
+ /* Size of the payload of the received encoded cell. */
+ size_t payload_len;
+
+ /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
+
+ /* Onion public key computed using the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t onion_pk;
+ /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
+ uint8_t rendezvous_cookie[REND_COOKIE_LEN];
+ /* Client public key from the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t client_pk;
+ /* Link specifiers of the rendezvous point. Contains link_specifier_t. */
+ smartlist_t *link_specifiers;
+ /* Replay cache of the introduction point. */
+ replaycache_t *replay_cache;
+} hs_cell_introduce2_data_t;
+
+/* Build cell API. */
+ssize_t hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out);
+
+/* Parse cell API. */
+ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
+ size_t payload_len);
+ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service);
+
+#endif /* TOR_HS_CELL_H */
+
diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c
new file mode 100644
index 0000000000..d0265dc548
--- /dev/null
+++ b/src/or/hs_circuit.c
@@ -0,0 +1,1056 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.c
+ **/
+
+#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "policies.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "rephist.h"
+#include "router.h"
+
+#include "hs_cell.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "hs_ntor.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* A circuit is about to become an e2e rendezvous circuit. Check
+ * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
+ * circuit purpose is properly set, otherwise return false. */
+static int
+circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
+ int is_service_side)
+{
+ if (is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_warn(LD_BUG,
+ "HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ if (!is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_BUG,
+ "Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
+ * circuit. Initialize the crypt path crypto using the output material from the
+ * ntor key exchange at <b>ntor_key_seed</b>.
+ *
+ * If <b>is_service_side</b> is set, we are the hidden service and the final
+ * hop of the rendezvous circuit is the client on the other side. */
+static crypt_path_t *
+create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
+ crypt_path_t *cpath = NULL;
+
+ /* Do the key expansion */
+ if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
+ keys, sizeof(keys)) < 0) {
+ goto err;
+ }
+
+ /* Setup the cpath */
+ cpath = tor_malloc_zero(sizeof(crypt_path_t));
+ cpath->magic = CRYPT_PATH_MAGIC;
+
+ if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
+ is_service_side, 1) < 0) {
+ tor_free(cpath);
+ goto err;
+ }
+
+ err:
+ memwipe(keys, 0, sizeof(keys));
+ return cpath;
+}
+
+/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
+ * service on the other side of the rendezvous circuit <b>circ</b>. Initialize
+ * the crypt path crypto using the body of the RENDEZVOUS1 cell at
+ * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes).
+ */
+static crypt_path_t *
+create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
+{
+ crypt_path_t *hop = NULL;
+ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+
+ /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
+ * handshake...*/
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->pending_final_cpath);
+ hop = circ->build_state->pending_final_cpath;
+
+ tor_assert(hop->rend_dh_handshake_state);
+ if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
+ (char*)rend_cell_body, DH_KEY_LEN,
+ keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
+ log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ goto err;
+ }
+ /* ... and set up cpath. */
+ if (circuit_init_cpath_crypto(hop,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 0, 0) < 0)
+ goto err;
+
+ /* Check whether the digest is right... */
+ if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) {
+ log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
+ goto err;
+ }
+
+ /* clean up the crypto stuff we just made */
+ crypto_dh_free(hop->rend_dh_handshake_state);
+ hop->rend_dh_handshake_state = NULL;
+
+ goto done;
+
+ err:
+ hop = NULL;
+
+ done:
+ memwipe(keys, 0, sizeof(keys));
+ return hop;
+}
+
+/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
+ * <b>circ</b> ready for use to transfer HS relay cells. */
+static void
+finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
+ int is_service_side)
+{
+ tor_assert(circ);
+ tor_assert(hop);
+
+ /* Notify the circuit state machine that we are splicing this circuit */
+ int new_circ_purpose = is_service_side ?
+ CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
+ circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
+
+ /* All is well. Extend the circuit. */
+ hop->state = CPATH_STATE_OPEN;
+ /* Set the windows to default. */
+ hop->package_window = circuit_initial_package_window();
+ hop->deliver_window = CIRCWINDOW_START;
+
+ /* Now that this circuit has finished connecting to its destination,
+ * make sure circuit_get_open_circ_or_launch is willing to return it
+ * so we can actually use it. */
+ circ->hs_circ_has_timed_out = 0;
+
+ /* Append the hop to the cpath of this circuit */
+ onion_append_to_cpath(&circ->cpath, hop);
+
+ /* In legacy code, 'pending_final_cpath' points to the final hop we just
+ * appended to the cpath. We set the original pointer to NULL so that we
+ * don't double free it. */
+ if (circ->build_state) {
+ circ->build_state->pending_final_cpath = NULL;
+ }
+
+ /* Finally, mark circuit as ready to be used for client streams */
+ if (!is_service_side) {
+ circuit_try_attaching_streams(circ);
+ }
+}
+
+/* For a given circuit and a service introduction point object, register the
+ * intro circuit to the circuitmap. This supports legacy intro point. */
+static void
+register_intro_circ(const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ)
+{
+ tor_assert(ip);
+ tor_assert(circ);
+
+ if (ip->base.is_only_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+ return;
+ }
+ hs_circuitmap_register_intro_circ_v2_service_side(circ, digest);
+ } else {
+ hs_circuitmap_register_intro_circ_v3_service_side(circ,
+ &ip->auth_key_kp.pubkey);
+ }
+}
+
+/* Return the number of opened introduction circuit for the given circuit that
+ * is matching its identity key. */
+static unsigned int
+count_opened_desc_intro_point_circuits(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ const circuit_t *circ;
+ const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc == NULL) {
+ continue;
+ }
+ circ = TO_CIRCUIT(ocirc);
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO);
+ /* Having a circuit not for the requested service is really bad. */
+ tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk,
+ &ocirc->hs_ident->identity_pk));
+ /* Only count opened circuit and skip circuit that will be closed. */
+ if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) {
+ count++;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ return count;
+}
+
+/* From a given service, rendezvous cookie and handshake info, create a
+ * rendezvous point circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_rp_circuit_identifier(const hs_service_t *service,
+ const uint8_t *rendezvous_cookie,
+ const curve25519_public_key_t *server_pk,
+ const hs_ntor_rend_cell_keys_t *keys)
+{
+ hs_ident_circuit_t *ident;
+ uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ tor_assert(service);
+ tor_assert(rendezvous_cookie);
+ tor_assert(server_pk);
+ tor_assert(keys);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_RENDEZVOUS);
+ /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */
+ memcpy(ident->rendezvous_cookie, rendezvous_cookie,
+ sizeof(ident->rendezvous_cookie));
+ /* Build the HANDSHAKE_INFO which looks like this:
+ * SERVER_PK [32 bytes]
+ * AUTH_INPUT_MAC [32 bytes]
+ */
+ memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN);
+ memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac,
+ DIGEST256_LEN);
+ tor_assert(sizeof(ident->rendezvous_handshake_info) ==
+ sizeof(handshake_info));
+ memcpy(ident->rendezvous_handshake_info, handshake_info,
+ sizeof(ident->rendezvous_handshake_info));
+ /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */
+ tor_assert(sizeof(ident->rendezvous_ntor_key_seed) ==
+ sizeof(keys->ntor_key_seed));
+ memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed,
+ sizeof(ident->rendezvous_ntor_key_seed));
+ return ident;
+}
+
+/* From a given service and service intro point, create an introduction point
+ * circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_intro_circuit_identifier(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_ident_circuit_t *ident;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_INTRO);
+ ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
+
+ return ident;
+}
+
+/* For a given introduction point and an introduction circuit, send the
+ * ESTABLISH_INTRO cell. The service object is used for logging. This can fail
+ * and if so, the circuit is closed and the intro point object is flagged
+ * that the circuit is not established anymore which is important for the
+ * retry mechanism. */
+static void
+send_establish_intro(const hs_service_t *service,
+ hs_service_intro_point_t *ip, origin_circuit_t *circ)
+{
+ ssize_t cell_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+
+ /* Encode establish intro cell. */
+ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce,
+ ip, payload);
+ if (cell_len < 0) {
+ log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s "
+ "on circuit %u. Closing circuit.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+
+ /* Send the cell on the circuit. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_ESTABLISH_INTRO,
+ (char *) payload, cell_len,
+ circ->cpath->prev) < 0) {
+ log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s "
+ "on circuit %u.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* On error, the circuit has been closed. */
+ goto done;
+ }
+
+ /* Record the attempt to use this circuit. */
+ pathbias_count_use_attempt(circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* From a list of link specifier, an onion key and if we are requesting a
+ * direct connection (ex: single onion service), return a newly allocated
+ * extend_info_t object. This function checks the firewall policies and if we
+ * are allowed to extend to the chosen address.
+ *
+ * if either IPv4 or legacy ID is missing, error.
+ * if not direct_conn, IPv4 is prefered.
+ * if direct_conn, IPv6 is prefered if we have one available.
+ * if firewall does not allow the chosen address, error.
+ *
+ * Return NULL if we can't fulfill the conditions. */
+static extend_info_t *
+get_rp_extend_info(const smartlist_t *link_specifiers,
+ const curve25519_public_key_t *onion_key, int direct_conn)
+{
+ int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+ char legacy_id[DIGEST_LEN] = {0};
+ uint16_t port_v4 = 0, port_v6 = 0, port = 0;
+ tor_addr_t addr_v4, addr_v6, *addr = NULL;
+ ed25519_public_key_t ed25519_pk;
+ extend_info_t *info = NULL;
+
+ tor_assert(link_specifiers);
+ tor_assert(onion_key);
+
+ SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) {
+ switch (link_specifier_get_ls_type(ls)) {
+ case LS_IPV4:
+ /* Skip if we already seen a v4. */
+ if (have_v4) continue;
+ tor_addr_from_ipv4h(&addr_v4,
+ link_specifier_get_un_ipv4_addr(ls));
+ port_v4 = link_specifier_get_un_ipv4_port(ls);
+ have_v4 = 1;
+ break;
+ case LS_IPV6:
+ /* Skip if we already seen a v6. */
+ if (have_v6) continue;
+ tor_addr_from_ipv6_bytes(&addr_v6,
+ (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
+ port_v6 = link_specifier_get_un_ipv6_port(ls);
+ have_v6 = 1;
+ break;
+ case LS_LEGACY_ID:
+ /* Make sure we do have enough bytes for the legacy ID. */
+ if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
+ break;
+ }
+ memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
+ sizeof(legacy_id));
+ have_legacy_id = 1;
+ break;
+ case LS_ED25519_ID:
+ memcpy(ed25519_pk.pubkey,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ ED25519_PUBKEY_LEN);
+ have_ed25519_id = 1;
+ break;
+ default:
+ /* Ignore unknown. */
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* IPv4, legacy ID are mandatory for rend points.
+ * ed25519 keys and ipv6 are optional for rend points */
+ if (!have_v4 || !have_legacy_id) {
+ goto done;
+ }
+ /* By default, we pick IPv4 but this might change to v6 if certain
+ * conditions are met. */
+ addr = &addr_v4; port = port_v4;
+
+ /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
+ * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
+ * address available so go to validation. */
+ if (!direct_conn) {
+ goto validate;
+ }
+
+ /* From this point on, we have a request for a direct connection to the
+ * rendezvous point so make sure we can actually connect through our
+ * firewall. We'll prefer IPv6. */
+
+ /* IPv6 test. */
+ if (have_v6 &&
+ fascist_firewall_allows_address_addr(&addr_v6, port_v6,
+ FIREWALL_OR_CONNECTION, 1, 1)) {
+ /* Direct connection and we can reach it in IPv6 so go for it. */
+ addr = &addr_v6; port = port_v6;
+ goto validate;
+ }
+ /* IPv4 test and we are sure we have a v4 because of the check above. */
+ if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
+ FIREWALL_OR_CONNECTION, 0, 0)) {
+ /* Direct connection and we can reach it in IPv4 so go for it. */
+ addr = &addr_v4; port = port_v4;
+ goto validate;
+ }
+
+ validate:
+ /* We'll validate now that the address we've picked isn't a private one. If
+ * it is, are we allowing to extend to private address? */
+ if (!extend_info_addr_is_allowed(addr)) {
+ log_warn(LD_REND, "Rendezvous point address is private and it is not "
+ "allowed to extend to it: %s:%u",
+ fmt_addr(&addr_v4), port_v4);
+ goto done;
+ }
+
+ /* We do have everything for which we think we can connect successfully. */
+ info = extend_info_new(NULL, legacy_id,
+ have_ed25519_id ? &ed25519_pk : NULL,
+ NULL, onion_key,
+ addr, port);
+ done:
+ return info;
+}
+
+/* For a given service, the ntor onion key and a rendezvous cookie, launch a
+ * circuit to the rendezvous point specified by the link specifiers. On
+ * success, a circuit identifier is attached to the circuit with the needed
+ * data. This function will try to open a circuit for a maximum value of
+ * MAX_REND_FAILURES then it will give up. */
+static void
+launch_rendezvous_point_circuit(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ const hs_cell_introduce2_data_t *data)
+{
+ int circ_needs_uptime;
+ time_t now = time(NULL);
+ extend_info_t *info = NULL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(data);
+
+ circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports);
+
+ /* Get the extend info data structure for the chosen rendezvous point
+ * specified by the given link specifiers. */
+ info = get_rp_extend_info(data->link_specifiers, &data->onion_pk,
+ service->config.is_single_onion);
+ if (info == NULL) {
+ /* We are done here, we can't extend to the rendezvous point. */
+ goto end;
+ }
+
+ for (int i = 0; i < MAX_REND_FAILURES; i++) {
+ int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
+ if (circ_needs_uptime) {
+ circ_flags |= CIRCLAUNCH_NEED_UPTIME;
+ }
+ /* Firewall and policies are checked when getting the extend info. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info,
+ circ_flags);
+ if (circ != NULL) {
+ /* Stop retrying, we have a circuit! */
+ break;
+ }
+ }
+ if (circ == NULL) {
+ log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s "
+ "for service %s",
+ safe_str_client(extend_info_describe(info)),
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
+ "for service %s",
+ safe_str_client(extend_info_describe(info)),
+ safe_str_client(hex_str((const char *) data->rendezvous_cookie,
+ REND_COOKIE_LEN)),
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state);
+ /* Rendezvous circuit have a specific timeout for the time spent on trying
+ * to connect to the rendezvous point. */
+ circ->build_state->expiry_time = now + MAX_REND_TIMEOUT;
+
+ /* Create circuit identifier and key material. */
+ {
+ hs_ntor_rend_cell_keys_t keys;
+ curve25519_keypair_t ephemeral_kp;
+ /* No need for extra strong, this is only for this circuit life time. This
+ * key will be used for the RENDEZVOUS1 cell that will be sent on the
+ * circuit once opened. */
+ curve25519_keypair_generate(&ephemeral_kp, 0);
+ if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey,
+ &ip->enc_key_kp,
+ &ephemeral_kp, &data->client_pk,
+ &keys) < 0) {
+ /* This should not really happened but just in case, don't make tor
+ * freak out, close the circuit and move on. */
+ log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ goto end;
+ }
+ circ->hs_ident = create_rp_circuit_identifier(service,
+ data->rendezvous_cookie,
+ &ephemeral_kp.pubkey, &keys);
+ memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp));
+ memwipe(&keys, 0, sizeof(keys));
+ tor_assert(circ->hs_ident);
+ }
+
+ end:
+ extend_info_free(info);
+}
+
+/* Return true iff the given service rendezvous circuit circ is allowed for a
+ * relaunch to the rendezvous point. */
+static int
+can_relaunch_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* XXX: Retrying under certain condition. This is related to #22455. */
+
+ /* Avoid to relaunch twice a circuit to the same rendezvous point at the
+ * same time. */
+ if (circ->hs_service_side_rend_circ_has_been_relaunched) {
+ log_info(LD_REND, "Rendezvous circuit to %s has already been retried. "
+ "Skipping retry.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)));
+ goto disallow;
+ }
+
+ /* A failure count that has reached maximum allowed or circuit that expired,
+ * we skip relaunching. */
+ if (circ->build_state->failure_count > MAX_REND_FAILURES ||
+ circ->build_state->expiry_time <= time(NULL)) {
+ log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has "
+ "failed with %d attempts and expiry time %ld. "
+ "Giving up building.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)),
+ circ->build_state->failure_count,
+ circ->build_state->expiry_time);
+ goto disallow;
+ }
+
+ /* Allowed to relaunch. */
+ return 1;
+ disallow:
+ return 0;
+}
+
+/* Retry the rendezvous point of circ by launching a new circuit to it. */
+static void
+retry_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ int flags = 0;
+ origin_circuit_t *new_circ;
+ cpath_build_state_t *bstate;
+
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Ease our life. */
+ bstate = circ->build_state;
+
+ log_info(LD_REND, "Retrying rendezvous point circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+
+ /* Get the current build state flags for the next circuit. */
+ flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0;
+ flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0;
+ flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0;
+
+ /* We do NOT add the onehop tunnel flag even though it might be a single
+ * onion service. The reason is that if we failed once to connect to the RP
+ * with a direct connection, we consider that chances are that we will fail
+ * again so try a 3-hop circuit and hope for the best. Because the service
+ * has no anonymity (single onion), this change of behavior won't affect
+ * security directly. */
+
+ new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
+ bstate->chosen_exit, flags);
+ if (new_circ == NULL) {
+ log_warn(LD_REND, "Failed to launch rendezvous circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+ goto done;
+ }
+
+ /* Transfer build state information to the new circuit state in part to
+ * catch any other failures. */
+ new_circ->build_state->failure_count = bstate->failure_count++;
+ new_circ->build_state->expiry_time = bstate->expiry_time;
+ new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident);
+
+ done:
+ return;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Return an introduction point circuit matching the given intro point object.
+ * NULL is returned is no such circuit can be found. */
+origin_circuit_t *
+hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip)
+{
+ origin_circuit_t *circ = NULL;
+
+ tor_assert(ip);
+
+ if (ip->base.is_only_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+ goto end;
+ }
+ circ = hs_circuitmap_get_intro_circ_v2_service_side(digest);
+ } else {
+ circ = hs_circuitmap_get_intro_circ_v3_service_side(
+ &ip->auth_key_kp.pubkey);
+ }
+ end:
+ return circ;
+}
+
+/* Called when we fail building a rendezvous circuit at some point other than
+ * the last hop: launches a new circuit to the same rendezvous point. This
+ * supports legacy service.
+ *
+ * We currently relaunch connections to rendezvous points if:
+ * - A rendezvous circuit timed out before connecting to RP.
+ * - The redenzvous circuit failed to connect to the RP.
+ *
+ * We avoid relaunching a connection to this rendezvous point if:
+ * - We have already tried MAX_REND_FAILURES times to connect to this RP.
+ * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT
+ * seconds
+ * - We've already retried this specific rendezvous circuit.
+ */
+void
+hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Check if we are allowed to relaunch to the rendezvous point of circ. */
+ if (!can_relaunch_service_rendezvous_point(circ)) {
+ goto done;
+ }
+
+ /* Flag the circuit that we are relaunching so to avoid to relaunch twice a
+ * circuit to the same rendezvous point at the same time. */
+ circ->hs_service_side_rend_circ_has_been_relaunched = 1;
+
+ /* Legacy service don't have an hidden service ident. */
+ if (circ->hs_ident) {
+ retry_service_rendezvous_point(circ);
+ } else {
+ rend_service_relaunch_rendezvous(circ);
+ }
+
+ done:
+ return;
+}
+
+/* For a given service and a service intro point, launch a circuit to the
+ * extend info ei. If the service is a single onion, a one-hop circuit will be
+ * requested. Return 0 if the circuit was successfully launched and tagged
+ * with the correct identifier. On error, a negative value is returned. */
+int
+hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei)
+{
+ /* Standard flags for introduction circuit. */
+ int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(ei);
+
+ /* Update circuit flags in case of a single onion service that requires a
+ * direct connection. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ log_info(LD_REND, "Launching a circuit to intro point %s for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+
+ /* Note down the launch for the retry period. Even if the circuit fails to
+ * be launched, we still want to respect the retry period to avoid stress on
+ * the circuit subsystem. */
+ service->state.num_intro_circ_launched++;
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+ ei, circ_flags);
+ if (circ == NULL) {
+ goto end;
+ }
+
+ /* Setup the circuit identifier and attach it to it. */
+ circ->hs_ident = create_intro_circuit_identifier(service, ip);
+ tor_assert(circ->hs_ident);
+ /* Register circuit in the global circuitmap. */
+ register_intro_circ(ip, circ);
+
+ /* Success. */
+ ret = 0;
+ end:
+ return ret;
+}
+
+/* Called when a service introduction point circuit is done building. Given
+ * the service and intro point object, this function will send the
+ * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the
+ * circuit has been repurposed to General because we already have too many
+ * opened. */
+int
+hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ)
+{
+ int ret = 0;
+ unsigned int num_intro_circ, num_needed_circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(desc);
+ tor_assert(circ);
+
+ /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already
+ * established introduction circuits */
+ num_intro_circ = count_opened_desc_intro_point_circuits(service, desc);
+ num_needed_circ = service->config.num_intro_points;
+ if (num_intro_circ > num_needed_circ) {
+ /* There are too many opened valid intro circuit for what the service
+ * needs so repurpose this one. */
+
+ /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just
+ * closes the circuit. I have NO idea why it does that so it hasn't been
+ * added here. I can only assume in case our ExcludeNodes list changes but
+ * in that case, all circuit are flagged unusable (config.c). --dgoulet */
+
+ log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we "
+ "have enough for service %s. Repurposing "
+ "it to general and leaving internal.",
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state->is_internal);
+ /* Remove it from the circuitmap. */
+ hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
+ /* Cleaning up the hidden service identifier and repurpose. */
+ hs_ident_circuit_free(circ->hs_ident);
+ circ->hs_ident = NULL;
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL);
+ /* Inform that this circuit just opened for this new purpose. */
+ circuit_has_opened(circ);
+ /* This return value indicate to the caller that the IP object should be
+ * removed from the service because it's corresponding circuit has just
+ * been repurposed. */
+ ret = 1;
+ goto done;
+ }
+
+ log_info(LD_REND, "Introduction circuit %u established for service %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call
+ * makes sure the circuit gets closed. */
+ send_establish_intro(service, ip, circ);
+
+ done:
+ return ret;
+}
+
+/* Called when a service rendezvous point circuit is done building. Given the
+ * service and the circuit, this function will send a RENDEZVOUS1 cell on the
+ * circuit using the information in the circuit identifier. If the cell can't
+ * be sent, the circuit is closed. */
+void
+hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ)
+{
+ size_t payload_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(circ->hs_ident);
+
+ /* Some useful logging. */
+ log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN),
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* This can't fail. */
+ payload_len = hs_cell_build_rendezvous1(
+ circ->hs_ident->rendezvous_cookie,
+ sizeof(circ->hs_ident->rendezvous_cookie),
+ circ->hs_ident->rendezvous_handshake_info,
+ sizeof(circ->hs_ident->rendezvous_handshake_info),
+ payload);
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_RENDEZVOUS1,
+ (const char *) payload, payload_len,
+ circ->cpath->prev) < 0) {
+ /* On error, circuit is closed. */
+ log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Setup end-to-end rendezvous circuit between the client and us. */
+ if (hs_circuit_setup_e2e_rend_circ(circ,
+ circ->hs_ident->rendezvous_ntor_key_seed,
+ sizeof(circ->hs_ident->rendezvous_ntor_key_seed),
+ 1) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
+ goto done;
+ }
+
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle
+ * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the
+ * given introduction circuit circ. The service is only used for logging
+ * purposes. Return 0 on success else a negative value. */
+int
+hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ goto done;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. For a legacy node, it's an empty payload so as long as we
+ * have the cell, we are good. */
+ if (!ip->base.is_only_legacy &&
+ hs_cell_parse_intro_established(payload, payload_len) < 0) {
+ log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on "
+ "circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Switch the purpose to a fully working intro point. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO);
+ /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the
+ * circuit so update our pathbias subsystem. */
+ pathbias_mark_use_success(circ);
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* We just received an INTRODUCE2 cell on the established introduction circuit
+ * circ. Handle the INTRODUCE2 payload of size payload_len for the given
+ * circuit and service. This cell is associated with the intro point object ip
+ * and the subcredential. Return 0 on success else a negative value. */
+int
+hs_circ_handle_introduce2(const hs_service_t *service,
+ const origin_circuit_t *circ,
+ hs_service_intro_point_t *ip,
+ const uint8_t *subcredential,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ time_t elapsed;
+ hs_cell_introduce2_data_t data;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(ip);
+ tor_assert(subcredential);
+ tor_assert(payload);
+
+ /* Populate the data structure with everything we need for the cell to be
+ * parsed, decrypted and key material computed correctly. */
+ data.auth_pk = &ip->auth_key_kp.pubkey;
+ data.enc_kp = &ip->enc_key_kp;
+ data.subcredential = subcredential;
+ data.payload = payload;
+ data.payload_len = payload_len;
+ data.link_specifiers = smartlist_new();
+ data.replay_cache = ip->replay_cache;
+
+ if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
+ goto done;
+ }
+
+ /* Check whether we've seen this REND_COOKIE before to detect repeats. */
+ if (replaycache_add_test_and_elapsed(
+ service->state.replay_cache_rend_cookie,
+ data.rendezvous_cookie, sizeof(data.rendezvous_cookie),
+ &elapsed)) {
+ /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE
+ * as its previous one if its intro circ times out while in state
+ * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first
+ * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2
+ * cell), we are already trying to connect to that rend point (and may
+ * have already succeeded); drop this cell. */
+ log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE "
+ "field %ld seconds ago. Dropping cell.", elapsed);
+ goto done;
+ }
+
+ /* At this point, we just confirmed that the full INTRODUCE2 cell is valid
+ * so increment our counter that we've seen one on this intro point. */
+ ip->introduce2_count++;
+
+ /* Launch rendezvous circuit with the onion key and rend cookie. */
+ launch_rendezvous_point_circuit(service, ip, &data);
+ /* Success. */
+ ret = 0;
+
+ done:
+ SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec,
+ link_specifier_free(lspec));
+ smartlist_free(data.link_specifiers);
+ memwipe(&data, 0, sizeof(data));
+ return ret;
+}
+
+/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
+ * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
+ * serve as a rendezvous end-to-end circuit between the client and the
+ * service. If <b>is_service_side</b> is set, then we are the hidden service
+ * and the other side is the client.
+ *
+ * Return 0 if the operation went well; in case of error return -1. */
+int
+hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
+ is_service_side))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
+ is_service_side);
+ if (!hop) {
+ log_warn(LD_REND, "Couldn't get v3 %s cpath!",
+ is_service_side ? "service-side" : "client-side");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, is_service_side);
+
+ return 0;
+}
+
+/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
+ * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
+ * extend the crypt path of <b>circ</b> so that the hidden service is on the
+ * other side. */
+int
+hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body)
+{
+
+ if (BUG(!circuit_purpose_is_correct_for_rend(
+ TO_CIRCUIT(circ)->purpose, 0))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
+ if (!hop) {
+ log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, 0);
+
+ return 0;
+}
+
diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h
new file mode 100644
index 0000000000..9e359394e8
--- /dev/null
+++ b/src/or/hs_circuit.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.h
+ * \brief Header file containing circuit data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CIRCUIT_H
+#define TOR_HS_CIRCUIT_H
+
+#include "or.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_service.h"
+
+/* Circuit API. */
+int hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ);
+void hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ);
+int hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei);
+int hs_circ_launch_rendezvous_point(const hs_service_t *service,
+ const curve25519_public_key_t *onion_key,
+ const uint8_t *rendezvous_cookie);
+void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ);
+
+origin_circuit_t *hs_circ_service_get_intro_circ(
+ const hs_service_intro_point_t *ip);
+
+/* Cell API. */
+int hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_circ_handle_introduce2(const hs_service_t *service,
+ const origin_circuit_t *circ,
+ hs_service_intro_point_t *ip,
+ const uint8_t *subcredential,
+ const uint8_t *payload, size_t payload_len);
+
+/* e2e circuit API. */
+
+int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ int is_service_side);
+int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body);
+
+#endif /* TOR_HS_CIRCUIT_H */
+
diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c
new file mode 100644
index 0000000000..ea66fb5194
--- /dev/null
+++ b/src/or/hs_circuitmap.c
@@ -0,0 +1,490 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.c
+ *
+ * \brief Hidden service circuitmap: A hash table that maps binary tokens to
+ * introduction and rendezvous circuits; it's used both by relays acting as
+ * intro points and rendezvous points, and also by hidden services themselves.
+ **/
+
+#define HS_CIRCUITMAP_PRIVATE
+
+#include "or.h"
+#include "config.h"
+#include "circuitlist.h"
+#include "hs_circuitmap.h"
+
+/************************** HS circuitmap code *******************************/
+
+/* This is the hidden service circuitmap. It's a hash table that maps
+ introduction and rendezvous tokens to specific circuits such that given a
+ token it's easy to find the corresponding circuit. */
+static struct hs_circuitmap_ht *the_hs_circuitmap = NULL;
+
+/* This is a helper function used by the hash table code (HT_). It returns 1 if
+ * two circuits have the same HS token. */
+static int
+hs_circuits_have_same_token(const circuit_t *first_circuit,
+ const circuit_t *second_circuit)
+{
+ const hs_token_t *first_token;
+ const hs_token_t *second_token;
+
+ tor_assert(first_circuit);
+ tor_assert(second_circuit);
+
+ first_token = first_circuit->hs_token;
+ second_token = second_circuit->hs_token;
+
+ /* Both circs must have a token */
+ if (BUG(!first_token) || BUG(!second_token)) {
+ return 0;
+ }
+
+ if (first_token->type != second_token->type) {
+ return 0;
+ }
+
+ if (first_token->token_len != second_token->token_len)
+ return 0;
+
+ return tor_memeq(first_token->token,
+ second_token->token,
+ first_token->token_len);
+}
+
+/* This is a helper function for the hash table code (HT_). It hashes a circuit
+ * HS token into an unsigned int for use as a key by the hash table routines.*/
+static inline unsigned int
+hs_circuit_hash_token(const circuit_t *circuit)
+{
+ tor_assert(circuit->hs_token);
+
+ return (unsigned) siphash24g(circuit->hs_token->token,
+ circuit->hs_token->token_len);
+}
+
+/* Register the circuitmap hash table */
+HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct
+ circuit_t, // The name of the element struct,
+ hs_circuitmap_node, // The name of HT_ENTRY member
+ hs_circuit_hash_token, hs_circuits_have_same_token)
+
+HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node,
+ hs_circuit_hash_token, hs_circuits_have_same_token,
+ 0.6, tor_reallocarray, tor_free_)
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global HS circuitmap. Used by unittests. */
+hs_circuitmap_ht *
+get_hs_circuitmap(void)
+{
+ return the_hs_circuitmap;
+}
+
+#endif
+
+/****************** HS circuitmap utility functions **************************/
+
+/** Return a new HS token of type <b>type</b> containing <b>token</b>. */
+static hs_token_t *
+hs_token_new(hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ tor_assert(token);
+
+ hs_token_t *hs_token = tor_malloc_zero(sizeof(hs_token_t));
+ hs_token->type = type;
+ hs_token->token_len = token_len;
+ hs_token->token = tor_memdup(token, token_len);
+
+ return hs_token;
+}
+
+/** Free memory allocated by this <b>hs_token</b>. */
+static void
+hs_token_free(hs_token_t *hs_token)
+{
+ if (!hs_token) {
+ return;
+ }
+
+ tor_free(hs_token->token);
+ tor_free(hs_token);
+}
+
+/** Return the circuit from the circuitmap with token <b>search_token</b>. */
+static circuit_t *
+get_circuit_with_token(hs_token_t *search_token)
+{
+ tor_assert(the_hs_circuitmap);
+
+ /* We use a dummy circuit object for the hash table search routine. */
+ circuit_t search_circ;
+ search_circ.hs_token = search_token;
+ return HT_FIND(hs_circuitmap_ht, the_hs_circuitmap, &search_circ);
+}
+
+/* Helper function that registers <b>circ</b> with <b>token</b> on the HS
+ circuitmap. This function steals reference of <b>token</b>. */
+static void
+hs_circuitmap_register_impl(circuit_t *circ, hs_token_t *token)
+{
+ tor_assert(circ);
+ tor_assert(token);
+ tor_assert(the_hs_circuitmap);
+
+ /* If this circuit already has a token, clear it. */
+ if (circ->hs_token) {
+ hs_circuitmap_remove_circuit(circ);
+ }
+
+ /* Kill old circuits with the same token. We want new intro/rend circuits to
+ take precedence over old ones, so that HSes and clients and reestablish
+ killed circuits without changing the HS token. */
+ {
+ circuit_t *found_circ;
+ found_circ = get_circuit_with_token(token);
+ if (found_circ) {
+ hs_circuitmap_remove_circuit(found_circ);
+ if (!found_circ->marked_for_close) {
+ circuit_mark_for_close(found_circ, END_CIRC_REASON_FINISHED);
+ }
+ }
+ }
+
+ /* Register circuit and token to circuitmap. */
+ circ->hs_token = token;
+ HT_INSERT(hs_circuitmap_ht, the_hs_circuitmap, circ);
+}
+
+/** Helper function: Register <b>circ</b> of <b>type</b> on the HS
+ * circuitmap. Use the HS <b>token</b> as the key to the hash table. If
+ * <b>token</b> is not set, clear the circuit of any HS tokens. */
+static void
+hs_circuitmap_register_circuit(circuit_t *circ,
+ hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ hs_token_t *hs_token = NULL;
+
+ /* Create a new token and register it to the circuitmap */
+ tor_assert(token);
+ hs_token = hs_token_new(type, token_len, token);
+ tor_assert(hs_token);
+ hs_circuitmap_register_impl(circ, hs_token);
+}
+
+/* Helper function for hs_circuitmap_get_origin_circuit() and
+ * hs_circuitmap_get_or_circuit(). Because only circuit_t are indexed in the
+ * circuitmap, this function returns object type so the specialized functions
+ * using this helper can upcast it to the right type.
+ *
+ * Return NULL if not such circuit is found. */
+static circuit_t *
+hs_circuitmap_get_circuit_impl(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *found_circ = NULL;
+
+ tor_assert(the_hs_circuitmap);
+
+ /* Check the circuitmap if we have a circuit with this token */
+ {
+ hs_token_t *search_hs_token = hs_token_new(type, token_len, token);
+ tor_assert(search_hs_token);
+ found_circ = get_circuit_with_token(search_hs_token);
+ hs_token_free(search_hs_token);
+ }
+
+ /* Check that the circuit is useful to us */
+ if (!found_circ ||
+ found_circ->purpose != wanted_circ_purpose ||
+ found_circ->marked_for_close) {
+ return NULL;
+ }
+
+ return found_circ;
+}
+
+/* Helper function: Query circuitmap for origin circuit with <b>token</b> of
+ * size <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose
+ * equal to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked
+ * for close. Return NULL if no such circuit is found. */
+static origin_circuit_t *
+hs_circuitmap_get_origin_circuit(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *circ;
+ tor_assert(token);
+ tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose));
+
+ circ = hs_circuitmap_get_circuit_impl(type, token_len, token,
+ wanted_circ_purpose);
+ if (!circ) {
+ return NULL;
+ }
+
+ tor_assert(CIRCUIT_IS_ORIGIN(circ));
+ return TO_ORIGIN_CIRCUIT(circ);
+}
+
+/* Helper function: Query circuitmap for OR circuit with <b>token</b> of size
+ * <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose equal
+ * to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked for
+ * close. Return NULL if no such circuit is found. */
+static or_circuit_t *
+hs_circuitmap_get_or_circuit(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *circ;
+ tor_assert(token);
+ tor_assert(!CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose));
+
+ circ = hs_circuitmap_get_circuit_impl(type, token_len, token,
+ wanted_circ_purpose);
+ if (!circ) {
+ return NULL;
+ }
+
+ tor_assert(CIRCUIT_IS_ORCIRC(circ));
+ return TO_OR_CIRCUIT(circ);
+}
+
+/************** Public circuitmap API ****************************************/
+
+/**** Public relay-side getters: */
+
+/* Public function: Return a v3 introduction circuit to this relay with
+ * <b>auth_key</b>. Return NULL if no such circuit is found in the
+ * circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v3_relay_side(
+ const ed25519_public_key_t *auth_key)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V3_RELAY_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return v2 introduction circuit to this relay with
+ * <b>digest</b>. Return NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return rendezvous circuit to this relay with rendezvous
+ * <b>cookie</b>. Return NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_REND_RELAY_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_REND_POINT_WAITING);
+}
+
+/** Public relay-side setters: */
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_rend_circ_relay_side(or_circuit_t *circ,
+ const uint8_t *cookie)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_REND_RELAY_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+/* Public function: Register v2 intro circuit with key <b>digest</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v2_relay_side(or_circuit_t *circ,
+ const uint8_t *digest)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ REND_TOKEN_LEN, digest);
+}
+
+/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v3_relay_side(or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V3_RELAY_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey);
+}
+
+/**** Public servide-side getters: */
+
+/* Public function: Return v3 introduction circuit with <b>auth_key</b>
+ * originating from this hidden service. Return NULL if no such circuit is
+ * found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_intro_circ_v3_service_side(const
+ ed25519_public_key_t *auth_key)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Check first for established intro circuits */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_S_INTRO);
+ if (circ) {
+ return circ;
+ }
+
+ /* ...if nothing found, check for pending intro circs */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ return circ;
+}
+
+/* Public function: Return v2 introduction circuit originating from this hidden
+ * service with <b>digest</b>. Return NULL if no such circuit is found in the
+ * circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Check first for established intro circuits */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_S_INTRO);
+ if (circ) {
+ return circ;
+ }
+
+ /* ...if nothing found, check for pending intro circs */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ return circ;
+}
+
+/* Public function: Return rendezvous circuit originating from this hidden
+ * service with rendezvous <b>cookie</b>. Return NULL if no such circuit is
+ * found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Try to check if we have a connecting circuit. */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_S_CONNECT_REND);
+ if (circ) {
+ return circ;
+ }
+
+ /* Then try for connected circuit. */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_S_REND_JOINED);
+ return circ;
+}
+
+/**** Public servide-side setters: */
+
+/* Public function: Register v2 intro circuit with key <b>digest</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v2_service_side(origin_circuit_t *circ,
+ const uint8_t *digest)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest);
+}
+
+/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v3_service_side(origin_circuit_t *circ,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey);
+}
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ,
+ const uint8_t *cookie)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+
+/**** Misc public functions: */
+
+/** Public function: Remove this circuit from the HS circuitmap. Clear its HS
+ * token, and remove it from the hashtable. */
+void
+hs_circuitmap_remove_circuit(circuit_t *circ)
+{
+ tor_assert(the_hs_circuitmap);
+
+ if (!circ || !circ->hs_token) {
+ return;
+ }
+
+ /* Remove circ from circuitmap */
+ circuit_t *tmp;
+ tmp = HT_REMOVE(hs_circuitmap_ht, the_hs_circuitmap, circ);
+ /* ... and ensure the removal was successful. */
+ if (tmp) {
+ tor_assert(tmp == circ);
+ } else {
+ log_warn(LD_BUG, "Could not find circuit (%u) in circuitmap.",
+ circ->n_circ_id);
+ }
+
+ /* Clear token from circ */
+ hs_token_free(circ->hs_token);
+ circ->hs_token = NULL;
+}
+
+/* Public function: Initialize the global HS circuitmap. */
+void
+hs_circuitmap_init(void)
+{
+ tor_assert(!the_hs_circuitmap);
+
+ the_hs_circuitmap = tor_malloc_zero(sizeof(struct hs_circuitmap_ht));
+ HT_INIT(hs_circuitmap_ht, the_hs_circuitmap);
+}
+
+/* Public function: Free all memory allocated by the global HS circuitmap. */
+void
+hs_circuitmap_free_all(void)
+{
+ if (the_hs_circuitmap) {
+ HT_CLEAR(hs_circuitmap_ht, the_hs_circuitmap);
+ tor_free(the_hs_circuitmap);
+ }
+}
+
diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h
new file mode 100644
index 0000000000..33d5b64117
--- /dev/null
+++ b/src/or/hs_circuitmap.h
@@ -0,0 +1,103 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.h
+ * \brief Header file for hs_circuitmap.c.
+ **/
+
+#ifndef TOR_HS_CIRCUITMAP_H
+#define TOR_HS_CIRCUITMAP_H
+
+typedef HT_HEAD(hs_circuitmap_ht, circuit_t) hs_circuitmap_ht;
+
+typedef struct hs_token_s hs_token_t;
+struct or_circuit_t;
+struct origin_circuit_t;
+
+/** Public HS circuitmap API: */
+
+/** Public relay-side API: */
+
+struct or_circuit_t *
+hs_circuitmap_get_intro_circ_v3_relay_side(const
+ ed25519_public_key_t *auth_key);
+struct or_circuit_t *
+hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest);
+struct or_circuit_t *
+hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie);
+
+void hs_circuitmap_register_rend_circ_relay_side(struct or_circuit_t *circ,
+ const uint8_t *cookie);
+void hs_circuitmap_register_intro_circ_v2_relay_side(struct or_circuit_t *circ,
+ const uint8_t *digest);
+void hs_circuitmap_register_intro_circ_v3_relay_side(struct or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key);
+
+/** Public service-side API: */
+
+struct origin_circuit_t *
+hs_circuitmap_get_intro_circ_v3_service_side(const
+ ed25519_public_key_t *auth_key);
+struct origin_circuit_t *
+hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest);
+struct origin_circuit_t *
+hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie);
+
+void hs_circuitmap_register_intro_circ_v2_service_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *digest);
+void hs_circuitmap_register_intro_circ_v3_service_side(
+ struct origin_circuit_t *circ,
+ const ed25519_public_key_t *auth_key);
+void hs_circuitmap_register_rend_circ_service_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *cookie);
+
+void hs_circuitmap_remove_circuit(struct circuit_t *circ);
+
+void hs_circuitmap_init(void);
+void hs_circuitmap_free_all(void);
+
+#ifdef HS_CIRCUITMAP_PRIVATE
+
+/** Represents the type of HS token. */
+typedef enum {
+ /** A rendezvous cookie on a relay (128bit)*/
+ HS_TOKEN_REND_RELAY_SIDE,
+ /** A v2 introduction point pubkey on a relay (160bit) */
+ HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ /** A v3 introduction point pubkey on a relay (256bit) */
+ HS_TOKEN_INTRO_V3_RELAY_SIDE,
+
+ /** A rendezvous cookie on a hidden service (128bit)*/
+ HS_TOKEN_REND_SERVICE_SIDE,
+ /** A v2 introduction point pubkey on a hidden service (160bit) */
+ HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ /** A v3 introduction point pubkey on a hidden service (256bit) */
+ HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+} hs_token_type_t;
+
+/** Represents a token used in the HS protocol. Each such token maps to a
+ * specific introduction or rendezvous circuit. */
+struct hs_token_s {
+ /* Type of HS token. */
+ hs_token_type_t type;
+
+ /* The size of the token (depends on the type). */
+ size_t token_len;
+
+ /* The token itself. Memory allocated at runtime. */
+ uint8_t *token;
+};
+
+#endif /* HS_CIRCUITMAP_PRIVATE */
+
+#ifdef TOR_UNIT_TESTS
+
+hs_circuitmap_ht *get_hs_circuitmap(void);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* TOR_HS_CIRCUITMAP_H */
+
diff --git a/src/or/hs_client.c b/src/or/hs_client.c
new file mode 100644
index 0000000000..051490aaaf
--- /dev/null
+++ b/src/or/hs_client.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service client functionality
+ **/
+
+#include "or.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "connection_edge.h"
+#include "rendclient.h"
+
+#include "hs_client.h"
+
+/** A prop224 v3 HS circuit successfully connected to the hidden
+ * service. Update the stream state at <b>hs_conn_ident</b> appropriately. */
+static void
+hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+{
+ (void) hs_conn_ident;
+
+ /* TODO: When implementing client side */
+ return;
+}
+
+/** A circuit just finished connecting to a hidden service that the stream
+ * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
+void
+hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
+{
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+
+ if (BUG(conn->rend_data && conn->hs_ident)) {
+ log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
+ "Prioritizing hs_ident");
+ }
+
+ if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
+ hs_client_attempt_succeeded(conn->hs_ident);
+ return;
+ } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
+ rend_client_note_connection_attempt_ended(conn->rend_data);
+ return;
+ }
+}
+
diff --git a/src/or/hs_client.h b/src/or/hs_client.h
new file mode 100644
index 0000000000..4f28937b03
--- /dev/null
+++ b/src/or/hs_client.h
@@ -0,0 +1,16 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.h
+ * \brief Header file containing client data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CLIENT_H
+#define TOR_HS_CLIENT_H
+
+void hs_client_note_connection_attempt_succeeded(
+ const edge_connection_t *conn);
+
+#endif /* TOR_HS_CLIENT_H */
+
diff --git a/src/or/hs_common.c b/src/or/hs_common.c
new file mode 100644
index 0000000000..6c860b0cf0
--- /dev/null
+++ b/src/or/hs_common.c
@@ -0,0 +1,1369 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.c
+ * \brief Contains code shared between different HS protocol version as well
+ * as useful data structures and accessors used by other subsystems.
+ * The rendcommon.c should only contains code relating to the v2
+ * protocol.
+ **/
+
+#define HS_COMMON_PRIVATE
+
+#include "or.h"
+
+#include "config.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+#include "hs_cache.h"
+#include "hs_common.h"
+#include "hs_service.h"
+#include "rendcommon.h"
+#include "rendservice.h"
+#include "router.h"
+#include "shared_random.h"
+#include "shared_random_state.h"
+
+/* Ed25519 Basepoint value. Taken from section 5 of
+ * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
+static const char *str_ed25519_basepoint =
+ "(15112221349535400772501151409588531511"
+ "454012693041857206046113283949847762202, "
+ "463168356949264781694283940034751631413"
+ "07993866256225615783033603165251855960)";
+
+#ifdef HAVE_SYS_UN_H
+
+/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
+ * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
+ * else return -ENOSYS if AF_UNIX is not supported (see function in the
+ * #else statement below). */
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ tor_assert(ports);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ smartlist_add(ports, p);
+ return 0;
+}
+
+/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
+ * on success else return -ENOSYS if AF_UNIX is not supported (see function
+ * in the #else statement below). */
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ tor_assert(conn);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ conn->base_.socket_family = AF_UNIX;
+ tor_addr_make_unspec(&conn->base_.addr);
+ conn->base_.port = 1;
+ conn->base_.address = tor_strdup(p->unix_addr);
+ return 0;
+}
+
+#else /* defined(HAVE_SYS_UN_H) */
+
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ (void) conn;
+ (void) p;
+ return -ENOSYS;
+}
+
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ (void) ports;
+ (void) p;
+ return -ENOSYS;
+}
+
+#endif /* HAVE_SYS_UN_H */
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * current hsdir_index. */
+static int
+compare_digest_to_current_hsdir_index(const void *_key, const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN);
+}
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * next hsdir_index. */
+static int
+compare_digest_to_next_hsdir_index(const void *_key, const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects current hsdir_index. */
+static int
+compare_node_current_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index->current,
+ node2->hsdir_index->current,
+ DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects next hsdir_index. */
+static int
+compare_node_next_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index->next,
+ node2->hsdir_index->next,
+ DIGEST256_LEN);
+}
+
+/* Allocate and return a string containing the path to filename in directory.
+ * This function will never return NULL. The caller must free this path. */
+char *
+hs_path_from_filename(const char *directory, const char *filename)
+{
+ char *file_path = NULL;
+
+ tor_assert(directory);
+ tor_assert(filename);
+
+ tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename);
+ return file_path;
+}
+
+/* Make sure that the directory for <b>service</b> is private, using the config
+ * <b>username</b>.
+ * 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. */
+int
+hs_check_service_private_dir(const char *username, const char *path,
+ unsigned int dir_group_readable,
+ unsigned int create)
+{
+ cpd_check_t check_opts = CPD_NONE;
+
+ tor_assert(path);
+
+ if (create) {
+ check_opts |= CPD_CREATE;
+ } else {
+ check_opts |= CPD_CHECK_MODE_ONLY;
+ check_opts |= CPD_CHECK;
+ }
+ if (dir_group_readable) {
+ check_opts |= CPD_GROUP_READ;
+ }
+ /* Check/create directory */
+ if (check_private_dir(path, check_opts, username) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/** Get the default HS time period length in minutes from the consensus. */
+STATIC uint64_t
+get_time_period_length(void)
+{
+ /* If we are on a test network, make the time period smaller than normal so
+ that we actually see it rotate. Specifically, make it the same length as
+ an SRV protocol run. */
+ if (get_options()->TestingTorNetwork) {
+ unsigned run_duration = sr_state_get_protocol_run_duration();
+ /* An SRV run should take more than a minute (it's 24 rounds) */
+ tor_assert_nonfatal(run_duration > 60);
+ /* Turn it from seconds to minutes before returning: */
+ return sr_state_get_protocol_run_duration() / 60;
+ }
+
+ int32_t time_period_length = networkstatus_get_param(NULL, "hsdir-interval",
+ HS_TIME_PERIOD_LENGTH_DEFAULT,
+ HS_TIME_PERIOD_LENGTH_MIN,
+ HS_TIME_PERIOD_LENGTH_MAX);
+ /* Make sure it's a positive value. */
+ tor_assert(time_period_length >= 0);
+ /* uint64_t will always be able to contain a int32_t */
+ return (uint64_t) time_period_length;
+}
+
+/** Get the HS time period number at time <b>now</b> */
+uint64_t
+hs_get_time_period_num(time_t now)
+{
+ uint64_t time_period_num;
+
+ /* Start by calculating minutes since the epoch */
+ uint64_t time_period_length = get_time_period_length();
+ uint64_t minutes_since_epoch = now / 60;
+
+ /* Apply the rotation offset as specified by prop224 (section
+ * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV
+ * publication */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ time_period_rotation_offset /= 60; /* go from seconds to minutes */
+ tor_assert(minutes_since_epoch > time_period_rotation_offset);
+ minutes_since_epoch -= time_period_rotation_offset;
+
+ /* Calculate the time period */
+ time_period_num = minutes_since_epoch / time_period_length;
+ return time_period_num;
+}
+
+/** Get the number of the _upcoming_ HS time period, given that the current
+ * time is <b>now</b>. */
+uint64_t
+hs_get_next_time_period_num(time_t now)
+{
+ return hs_get_time_period_num(now) + 1;
+}
+
+/* Return the start time of the upcoming time period based on <b>now</b>. */
+time_t
+hs_get_start_time_of_next_time_period(time_t now)
+{
+ uint64_t time_period_length = get_time_period_length();
+
+ /* Get start time of next time period */
+ uint64_t next_time_period_num = hs_get_next_time_period_num(now);
+ uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length;
+
+ /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset);
+}
+
+/* Create a new rend_data_t for a specific given <b>version</b>.
+ * Return a pointer to the newly allocated data structure. */
+static rend_data_t *
+rend_data_alloc(uint32_t version)
+{
+ rend_data_t *rend_data = NULL;
+
+ switch (version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2 = tor_malloc_zero(sizeof(*v2));
+ v2->base_.version = HS_VERSION_TWO;
+ v2->base_.hsdirs_fp = smartlist_new();
+ rend_data = &v2->base_;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return rend_data;
+}
+
+/** Free all storage associated with <b>data</b> */
+void
+rend_data_free(rend_data_t *data)
+{
+ if (!data) {
+ return;
+ }
+ /* By using our allocation function, this should always be set. */
+ tor_assert(data->hsdirs_fp);
+ /* Cleanup the HSDir identity digest. */
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d));
+ smartlist_free(data->hsdirs_fp);
+ /* Depending on the version, cleanup. */
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(data);
+ tor_free(v2_data);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Allocate and return a deep copy of <b>data</b>. */
+rend_data_t *
+rend_data_dup(const rend_data_t *data)
+{
+ rend_data_t *data_dup = NULL;
+ smartlist_t *hsdirs_fp = smartlist_new();
+
+ tor_assert(data);
+ tor_assert(data->hsdirs_fp);
+
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp,
+ smartlist_add(hsdirs_fp, tor_memdup(fp, DIGEST_LEN)));
+
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = tor_memdup(TO_REND_DATA_V2(data),
+ sizeof(*v2_data));
+ data_dup = &v2_data->base_;
+ data_dup->hsdirs_fp = hsdirs_fp;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return data_dup;
+}
+
+/* Compute the descriptor ID for each HS descriptor replica and save them. A
+ * valid onion address must be present in the <b>rend_data</b>.
+ *
+ * Return 0 on success else -1. */
+static int
+compute_desc_id(rend_data_t *rend_data)
+{
+ int ret = 0;
+ unsigned replica;
+ time_t now = time(NULL);
+
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ /* Compute descriptor ID for each replicas. */
+ for (replica = 0; replica < ARRAY_LENGTH(v2_data->descriptor_id);
+ replica++) {
+ ret = rend_compute_v2_desc_id(v2_data->descriptor_id[replica],
+ v2_data->onion_address,
+ v2_data->descriptor_cookie,
+ now, replica);
+ if (ret < 0) {
+ goto end;
+ }
+ }
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ end:
+ return ret;
+}
+
+/* Allocate and initialize a rend_data_t object for a service using the
+ * provided arguments. All arguments are optional (can be NULL), except from
+ * <b>onion_address</b> which MUST be set. The <b>pk_digest</b> is the hash of
+ * the service private key. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation this service is configured with.
+ *
+ * Return a valid rend_data_t pointer. This only returns a version 2 object of
+ * rend_data_t. */
+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)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL);
+
+ if (pk_digest) {
+ memcpy(v2->rend_pk_digest, pk_digest, sizeof(v2->rend_pk_digest));
+ }
+ if (cookie) {
+ memcpy(rend_data->rend_cookie, cookie, sizeof(rend_data->rend_cookie));
+ }
+
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ v2->auth_type = auth_type;
+
+ return rend_data;
+}
+
+/* Allocate and initialize a rend_data_t object for a client request using the
+ * given arguments. Either an onion address or a descriptor ID is needed. Both
+ * can be given but in this case only the onion address will be used to make
+ * the descriptor fetch. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation the service is configured with.
+ *
+ * Return a valid rend_data_t pointer or NULL on error meaning the
+ * descriptor IDs couldn't be computed from the given data. */
+rend_data_t *
+rend_data_client_create(const char *onion_address, const char *desc_id,
+ const char *cookie, rend_auth_type_t auth_type)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL || desc_id != NULL);
+
+ if (cookie) {
+ memcpy(v2->descriptor_cookie, cookie, sizeof(v2->descriptor_cookie));
+ }
+ if (desc_id) {
+ memcpy(v2->desc_id_fetch, desc_id, sizeof(v2->desc_id_fetch));
+ }
+ if (onion_address) {
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ if (compute_desc_id(rend_data) < 0) {
+ goto error;
+ }
+ }
+
+ v2->auth_type = auth_type;
+
+ return rend_data;
+
+ error:
+ rend_data_free(rend_data);
+ return NULL;
+}
+
+/* Return the onion address from the rend data. Depending on the version,
+ * the size of the address can vary but it's always NUL terminated. */
+const char *
+rend_data_get_address(const rend_data_t *rend_data)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ return TO_REND_DATA_V2(rend_data)->onion_address;
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the descriptor ID for a specific replica number from the rend
+ * data. The returned data is a binary digest and depending on the version its
+ * size can vary. The size of the descriptor ID is put in <b>len_out</b> if
+ * non NULL. */
+const char *
+rend_data_get_desc_id(const rend_data_t *rend_data, uint8_t replica,
+ size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ tor_assert(replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS);
+ if (len_out) {
+ *len_out = DIGEST_LEN;
+ }
+ return TO_REND_DATA_V2(rend_data)->descriptor_id[replica];
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the public key digest using the given <b>rend_data</b>. The size of
+ * the digest is put in <b>len_out</b> (if set) which can differ depending on
+ * the version. */
+const uint8_t *
+rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ const rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ if (len_out) {
+ *len_out = sizeof(v2_data->rend_pk_digest);
+ }
+ return (const uint8_t *) v2_data->rend_pk_digest;
+ }
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Using the given time period number, compute the disaster shared random
+ * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */
+static void
+compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(srv_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+
+ /* Start setting up payload:
+ * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */
+ crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX,
+ HS_SRV_DISASTER_PREFIX_LEN);
+
+ /* Setup INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/** Due to the high cost of computing the disaster SRV and that potentially we
+ * would have to do it thousands of times in a row, we always cache the
+ * computer disaster SRV (and its corresponding time period num) in case we
+ * want to reuse it soon after. We need to cache two SRVs, one for each active
+ * time period (in case of overlap mode).
+ */
+static uint8_t cached_disaster_srv[2][DIGEST256_LEN];
+static uint64_t cached_time_period_nums[2] = {0};
+
+/** Compute the disaster SRV value for this <b>time_period_num</b> and put it
+ * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches
+ * to see if we have already computed it. */
+STATIC void
+get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ if (time_period_num == cached_time_period_nums[0]) {
+ memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN);
+ return;
+ } else if (time_period_num == cached_time_period_nums[1]) {
+ memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN);
+ return;
+ } else {
+ int replace_idx;
+ // Replace the lower period number.
+ if (cached_time_period_nums[0] <= cached_time_period_nums[1]) {
+ replace_idx = 0;
+ } else {
+ replace_idx = 1;
+ }
+ cached_time_period_nums[replace_idx] = time_period_num;
+ compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]);
+ memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN);
+ return;
+ }
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/** Get the first cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_first_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[0];
+}
+
+/** Get the second cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_second_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[1];
+}
+
+#endif
+
+/* When creating a blinded key, we need a parameter which construction is as
+ * follow: H(pubkey | [secret] | ed25519-basepoint | nonce).
+ *
+ * The nonce has a pre-defined format which uses the time period number
+ * period_num and the start of the period in second start_time_period.
+ *
+ * The secret of size secret_len is optional meaning that it can be NULL and
+ * thus will be ignored for the param construction.
+ *
+ * The result is put in param_out. */
+static void
+build_blinded_key_param(const ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t period_num, uint64_t period_length,
+ uint8_t *param_out)
+{
+ size_t offset = 0;
+ const char blind_str[] = "Derive temporary signing key";
+ uint8_t nonce[HS_KEYBLIND_NONCE_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(pubkey);
+ tor_assert(param_out);
+
+ /* Create the nonce N. The construction is as follow:
+ * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */
+ memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN);
+ offset += HS_KEYBLIND_NONCE_PREFIX_LEN;
+ set_uint64(nonce + offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(nonce + offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == HS_KEYBLIND_NONCE_LEN);
+
+ /* Generate the parameter h and the construction is as follow:
+ * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str));
+ crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN);
+ /* Optional secret. */
+ if (secret) {
+ crypto_digest_add_bytes(digest, (char *) secret, secret_len);
+ }
+ crypto_digest_add_bytes(digest, str_ed25519_basepoint,
+ strlen(str_ed25519_basepoint));
+ crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce));
+
+ /* Extract digest and put it in the param. */
+ crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ memwipe(nonce, 0, sizeof(nonce));
+}
+
+/* Using an ed25519 public key and version to build the checksum of an
+ * address. Put in checksum_out. Format is:
+ * SHA3-256(".onion checksum" || PUBKEY || VERSION)
+ *
+ * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */
+static void
+build_hs_checksum(const ed25519_public_key_t *key, uint8_t version,
+ uint8_t *checksum_out)
+{
+ size_t offset = 0;
+ char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN];
+
+ /* Build checksum data. */
+ memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX,
+ HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN);
+ offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN;
+ memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ set_uint8(data + offset, version);
+ offset += sizeof(version);
+ tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN);
+
+ /* Hash the data payload to create the checksum. */
+ crypto_digest256((char *) checksum_out, data, sizeof(data),
+ DIGEST_SHA3_256);
+}
+
+/* Using an ed25519 public key, checksum and version to build the binary
+ * representation of a service address. Put in addr_out. Format is:
+ * addr_out = PUBKEY || CHECKSUM || VERSION
+ *
+ * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */
+static void
+build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum,
+ uint8_t version, char *addr_out)
+{
+ size_t offset = 0;
+
+ tor_assert(key);
+ tor_assert(checksum);
+
+ memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ set_uint8(addr_out + offset, version);
+ offset += sizeof(uint8_t);
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Helper for hs_parse_address(): Using a binary representation of a service
+ * address, parse its content into the key_out, checksum_out and version_out.
+ * Any out variable can be NULL in case the caller would want only one field.
+ * checksum_out MUST at least be 2 bytes long. address must be at least
+ * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */
+static void
+hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ size_t offset = 0;
+
+ tor_assert(address);
+
+ if (key_out) {
+ /* First is the key. */
+ memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN);
+ }
+ offset += ED25519_PUBKEY_LEN;
+ if (checksum_out) {
+ /* Followed by a 2 bytes checksum. */
+ memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ }
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ if (version_out) {
+ /* Finally, version value is 1 byte. */
+ *version_out = get_uint8(address + offset);
+ }
+ offset += sizeof(uint8_t);
+ /* Extra safety. */
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Using the given identity public key and a blinded public key, compute the
+ * subcredential and put it in subcred_out (must be of size DIGEST256_LEN).
+ * This can't fail. */
+void
+hs_get_subcredential(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ uint8_t *subcred_out)
+{
+ uint8_t credential[DIGEST256_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(blinded_pk);
+ tor_assert(subcred_out);
+
+ /* First, build the credential. Construction is as follow:
+ * credential = H("credential" | public-identity-key) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX,
+ HS_CREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ /* Now, compute the subcredential. Construction is as follow:
+ * subcredential = H("subcredential" | credential | blinded-public-key). */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX,
+ HS_SUBCREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) credential,
+ sizeof(credential));
+ crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ memwipe(credential, 0, sizeof(credential));
+}
+
+/* From the given list of hidden service ports, find the ones that much the
+ * given edge connection conn, pick one at random and use it to set the
+ * connection address. Return 0 on success or -1 if none. */
+int
+hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
+{
+ rend_service_port_config_t *chosen_port;
+ unsigned int warn_once = 0;
+ smartlist_t *matching_ports;
+
+ tor_assert(ports);
+ tor_assert(conn);
+
+ matching_ports = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (TO_CONN(conn)->port != p->virtual_port) {
+ continue;
+ }
+ if (!(p->is_unix_addr)) {
+ smartlist_add(matching_ports, p);
+ } else {
+ if (add_unix_port(matching_ports, p)) {
+ if (!warn_once) {
+ /* Unix port not supported so warn only once. */
+ log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d "
+ "which is unsupported on this platform. "
+ "Ignoring it.",
+ TO_CONN(conn)->port);
+ }
+ warn_once++;
+ }
+ }
+ } SMARTLIST_FOREACH_END(p);
+
+ chosen_port = smartlist_choose(matching_ports);
+ smartlist_free(matching_ports);
+ if (chosen_port) {
+ if (!(chosen_port->is_unix_addr)) {
+ /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
+ tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr);
+ TO_CONN(conn)->port = chosen_port->real_port;
+ } else {
+ if (set_unix_port(conn, chosen_port)) {
+ /* Simply impossible to end up here else we were able to add a Unix
+ * port without AF_UNIX support... ? */
+ tor_assert(0);
+ }
+ }
+ }
+ return (chosen_port) ? 0 : -1;
+}
+
+/* Using a base32 representation of a service address, parse its content into
+ * the key_out, checksum_out and version_out. Any out variable can be NULL in
+ * case the caller would want only one field. checksum_out MUST at least be 2
+ * bytes long.
+ *
+ * Return 0 if parsing went well; return -1 in case of error. */
+int
+hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ char decoded[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(address);
+
+ /* Obvious length check. */
+ if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
+ log_warn(LD_REND, "Service address %s has an invalid length. "
+ "Expected %lu but got %lu.",
+ escaped_safe_str(address),
+ (unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
+ (unsigned long) strlen(address));
+ goto invalid;
+ }
+
+ /* Decode address so we can extract needed fields. */
+ if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) {
+ log_warn(LD_REND, "Service address %s can't be decoded.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Parse the decoded address into the fields we need. */
+ hs_parse_address_impl(decoded, key_out, checksum_out, version_out);
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Validate a given onion address. The length, the base32 decoding and
+ * checksum are validated. Return 1 if valid else 0. */
+int
+hs_address_is_valid(const char *address)
+{
+ uint8_t version;
+ uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED];
+ uint8_t target_checksum[DIGEST256_LEN];
+ ed25519_public_key_t key;
+
+ /* Parse the decoded address into the fields we need. */
+ if (hs_parse_address(address, &key, checksum, &version) < 0) {
+ goto invalid;
+ }
+
+ /* Get the checksum it's suppose to be and compare it with what we have
+ * encoded in the address. */
+ build_hs_checksum(&key, version, target_checksum);
+ if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) {
+ log_warn(LD_REND, "Service address %s invalid checksum.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Valid address. */
+ return 1;
+ invalid:
+ return 0;
+}
+
+/* Build a service address using an ed25519 public key and a given version.
+ * The returned address is base32 encoded and put in addr_out. The caller MUST
+ * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long.
+ *
+ * Format is as follow:
+ * base32(PUBKEY || CHECKSUM || VERSION)
+ * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION)
+ * */
+void
+hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out)
+{
+ uint8_t checksum[DIGEST256_LEN];
+ char address[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(key);
+ tor_assert(addr_out);
+
+ /* Get the checksum of the address. */
+ build_hs_checksum(key, version, checksum);
+ /* Get the binary address representation. */
+ build_hs_address(key, checksum, version, address);
+
+ /* Encode the address. addr_out will be NUL terminated after this. */
+ base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address,
+ sizeof(address));
+ /* Validate what we just built. */
+ tor_assert(hs_address_is_valid(addr_out));
+}
+
+/* Return a newly allocated copy of lspec. */
+link_specifier_t *
+hs_link_specifier_dup(const link_specifier_t *lspec)
+{
+ link_specifier_t *dup = link_specifier_new();
+ memcpy(dup, lspec, sizeof(*dup));
+ /* The unrecognized field is a dynamic array so make sure to copy its
+ * content and not the pointer. */
+ link_specifier_setlen_un_unrecognized(
+ dup, link_specifier_getlen_un_unrecognized(lspec));
+ if (link_specifier_getlen_un_unrecognized(dup)) {
+ memcpy(link_specifier_getarray_un_unrecognized(dup),
+ link_specifier_getconstarray_un_unrecognized(lspec),
+ link_specifier_getlen_un_unrecognized(dup));
+ }
+ return dup;
+}
+
+/* From a given ed25519 public key pk and an optional secret, compute a
+ * blinded public key and put it in blinded_pk_out. This is only useful to
+ * the client side because the client only has access to the identity public
+ * key of the service. */
+void
+hs_build_blinded_pubkey(const ed25519_public_key_t *pk,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_public_key_t *blinded_pk_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(pk);
+ tor_assert(blinded_pk_out);
+ tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN));
+
+ build_blinded_key_param(pk, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_public_blind(blinded_pk_out, pk, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* From a given ed25519 keypair kp and an optional secret, compute a blinded
+ * keypair for the current time period and put it in blinded_kp_out. This is
+ * only useful by the service side because the client doesn't have access to
+ * the identity secret key. */
+void
+hs_build_blinded_keypair(const ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_keypair_t *blinded_kp_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(kp);
+ tor_assert(blinded_kp_out);
+ /* Extra safety. A zeroed key is bad. */
+ tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN));
+ tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN));
+
+ build_blinded_key_param(&kp->pubkey, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_keypair_blind(blinded_kp_out, kp, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* Return true if overlap mode is active given the date in consensus. If
+ * consensus is NULL, then we use the latest live consensus we can find. */
+MOCK_IMPL(int,
+hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now))
+{
+ time_t valid_after;
+ time_t srv_start_time, tp_start_time;
+
+ if (!consensus) {
+ consensus = networkstatus_get_live_consensus(now);
+ if (!consensus) {
+ return 0;
+ }
+ }
+
+ /* We consider to be in overlap mode when we are in the period of time
+ * between a fresh SRV and the beginning of the new time period (in the
+ * normal network this is between 00:00 (inclusive) and 12:00 UTC
+ * (exclusive)) */
+ valid_after = consensus->valid_after;
+ srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after);
+ tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time);
+
+ if (valid_after >= srv_start_time && valid_after < tp_start_time) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Return 1 if any virtual port in ports needs a circuit with good uptime.
+ * Else return 0. */
+int
+hs_service_requires_uptime_circ(const smartlist_t *ports)
+{
+ tor_assert(ports);
+
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
+ p->virtual_port)) {
+ return 1;
+ }
+ } SMARTLIST_FOREACH_END(p);
+ return 0;
+}
+
+/* Build hs_index which is used to find the responsible hsdirs. This index
+ * value is used to select the responsible HSDir where their hsdir_index is
+ * closest to this value.
+ * SHA3-256("store-at-idx" | blinded_public_key |
+ * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(blinded_pk);
+ tor_assert(hs_index_out);
+
+ /* Build hs_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+
+ /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t period_length = get_time_period_length();
+ char buf[sizeof(uint64_t)*3];
+ size_t offset = 0;
+ set_uint64(buf, tor_htonll(replica));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(buf));
+
+ crypto_digest_add_bytes(digest, buf, sizeof(buf));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Build hsdir_index which is used to find the responsible hsdirs. This is the
+ * index value that is compare to the hs_index when selecting an HSDir.
+ * SHA3-256("node-idx" | node_identity |
+ * shared_random_value | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
+ const uint8_t *srv_value, uint64_t period_num,
+ uint8_t *hsdir_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(srv_value);
+ tor_assert(hsdir_index_out);
+
+ /* Build hsdir_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN);
+
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Return a newly allocated buffer containing the current shared random value
+ * or if not present, a disaster value is computed using the given time period
+ * number. If a consensus is provided in <b>ns</b>, use it to get the SRV
+ * value. This function can't fail. */
+uint8_t *
+hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *current_srv = sr_get_current(ns);
+
+ if (current_srv) {
+ memcpy(sr_value, current_srv->value, sizeof(current_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return a newly allocated buffer containing the previous shared random
+ * value or if not present, a disaster value is computed using the given time
+ * period number. This function can't fail. */
+uint8_t *
+hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *previous_srv = sr_get_previous(ns);
+
+ if (previous_srv) {
+ memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return the number of replicas defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_n_replicas(void)
+{
+ /* The [1,16] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_n_replicas",
+ HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16);
+}
+
+/* Return the spread fetch value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_fetch(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_fetch",
+ HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128);
+}
+
+/* Return the spread store value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_store(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_store",
+ HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128);
+}
+
+/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
+ * Return 0 if everything is as expected, else return -1. */
+static int
+node_has_hsdir_index(const node_t *node)
+{
+ tor_assert(node_supports_v3_hsdir(node));
+
+ /* A node can't have an HSDir index without a descriptor since we need desc
+ * to get its ed25519 key */
+ if (!node_has_descriptor(node)) {
+ return 0;
+ }
+
+ /* At this point, since the node has a desc, this node must also have an
+ * hsdir index. If not, something went wrong, so BUG out. */
+ if (BUG(node->hsdir_index == NULL) ||
+ BUG(tor_mem_is_zero((const char*)node->hsdir_index->current,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* For a given blinded key and time period number, get the responsible HSDir
+ * and put their routerstatus_t object in the responsible_dirs list. If
+ * is_next_period is true, the next hsdir_index of the node_t is used. If
+ * is_client is true, the spread fetch consensus parameter is used else the
+ * spread store is used which is only for upload. This function can't fail but
+ * it is possible that the responsible_dirs list contains fewer nodes than
+ * expected.
+ *
+ * This function goes over the latest consensus routerstatus list and sorts it
+ * by their node_t hsdir_index then does a binary search to find the closest
+ * node. All of this makes it a bit CPU intensive so use it wisely. */
+void
+hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num, int is_next_period,
+ int is_client, smartlist_t *responsible_dirs)
+{
+ smartlist_t *sorted_nodes;
+ /* The compare function used for the smartlist bsearch. We have two
+ * different depending on is_next_period. */
+ int (*cmp_fct)(const void *, const void **);
+
+ tor_assert(blinded_pk);
+ tor_assert(responsible_dirs);
+
+ sorted_nodes = smartlist_new();
+
+ /* Add every node_t that support HSDir v3 for which we do have a valid
+ * hsdir_index already computed for them for this consensus. */
+ {
+ networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (!c || smartlist_len(c->routerstatus_list) == 0) {
+ log_warn(LD_REND, "No valid consensus so we can't get the responsible "
+ "hidden service directories.");
+ goto done;
+ }
+ SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) {
+ /* Even though this node_t object won't be modified and should be const,
+ * we can't add const object in a smartlist_t. */
+ node_t *n = node_get_mutable_by_id(rs->identity_digest);
+ tor_assert(n);
+ if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
+ if (!node_has_hsdir_index(n)) {
+ log_info(LD_GENERAL, "Node %s was found without hsdir index.",
+ node_describe(n));
+ continue;
+ }
+ smartlist_add(sorted_nodes, n);
+ }
+ } SMARTLIST_FOREACH_END(rs);
+ }
+ if (smartlist_len(sorted_nodes) == 0) {
+ log_warn(LD_REND, "No nodes found to be HSDir or supporting v3.");
+ goto done;
+ }
+
+ /* First thing we have to do is sort all node_t by hsdir_index. The
+ * is_next_period tells us if we want the current or the next one. Set the
+ * bsearch compare function also while we are at it. */
+ if (is_next_period) {
+ smartlist_sort(sorted_nodes, compare_node_next_hsdir_index);
+ cmp_fct = compare_digest_to_next_hsdir_index;
+ } else {
+ smartlist_sort(sorted_nodes, compare_node_current_hsdir_index);
+ cmp_fct = compare_digest_to_current_hsdir_index;
+ }
+
+ /* For all replicas, we'll select a set of HSDirs using the consensus
+ * parameters and the sorted list. The replica starting at value 1 is
+ * defined by the specification. */
+ for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) {
+ int idx, start, found, n_added = 0;
+ uint8_t hs_index[DIGEST256_LEN] = {0};
+ /* Number of node to add to the responsible dirs list depends on if we are
+ * trying to fetch or store. A client always fetches. */
+ int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() :
+ hs_get_hsdir_spread_store();
+
+ /* Get the index that we should use to select the node. */
+ hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index);
+ /* The compare function pointer has been set correctly earlier. */
+ start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct,
+ &found);
+ /* Getting the length of the list if no member is greater than the key we
+ * are looking for so start at the first element. */
+ if (idx == smartlist_len(sorted_nodes)) {
+ start = idx = 0;
+ }
+ while (n_added < n_to_add) {
+ const node_t *node = smartlist_get(sorted_nodes, idx);
+ /* If the node has already been selected which is possible between
+ * replicas, the specification says to skip over. */
+ if (!smartlist_contains(responsible_dirs, node->rs)) {
+ smartlist_add(responsible_dirs, node->rs);
+ ++n_added;
+ }
+ if (++idx == smartlist_len(sorted_nodes)) {
+ /* Wrap if we've reached the end of the list. */
+ idx = 0;
+ }
+ if (idx == start) {
+ /* We've gone over the whole list, stop and avoid infinite loop. */
+ break;
+ }
+ }
+ }
+
+ done:
+ smartlist_free(sorted_nodes);
+}
+
+/* Initialize the entire HS subsytem. This is called in tor_init() before any
+ * torrc options are loaded. Only for >= v3. */
+void
+hs_init(void)
+{
+ hs_circuitmap_init();
+ hs_service_init();
+ hs_cache_init();
+}
+
+/* Release and cleanup all memory of the HS subsystem (all version). This is
+ * called by tor_free_all(). */
+void
+hs_free_all(void)
+{
+ hs_circuitmap_free_all();
+ hs_service_free_all();
+ hs_cache_free_all();
+}
+
+/* For the given origin circuit circ, decrement the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_dec_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams--;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams--;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
+
+/* For the given origin circuit circ, increment the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_inc_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams++;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams++;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
+
diff --git a/src/or/hs_common.h b/src/or/hs_common.h
new file mode 100644
index 0000000000..fd2a1f4e32
--- /dev/null
+++ b/src/or/hs_common.h
@@ -0,0 +1,244 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.h
+ * \brief Header file containing common data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_COMMON_H
+#define TOR_HS_COMMON_H
+
+#include "or.h"
+
+/* Trunnel */
+#include "ed25519_cert.h"
+
+/* Protocol version 2. Use this instead of hardcoding "2" in the code base,
+ * this adds a clearer semantic to the value when used. */
+#define HS_VERSION_TWO 2
+/* Version 3 of the protocol (prop224). */
+#define HS_VERSION_THREE 3
+/* Earliest and latest version we support. */
+#define HS_VERSION_MIN HS_VERSION_TWO
+#define HS_VERSION_MAX HS_VERSION_THREE
+
+/** Try to maintain this many intro points per service by default. */
+#define NUM_INTRO_POINTS_DEFAULT 3
+/** Maximum number of intro points per generic and version 2 service. */
+#define NUM_INTRO_POINTS_MAX 10
+/** Number of extra intro points we launch if our set of intro nodes is empty.
+ * See proposal 155, section 4. */
+#define NUM_INTRO_POINTS_EXTRA 2
+
+/** If we can't build our intro circuits, don't retry for this long. */
+#define INTRO_CIRC_RETRY_PERIOD (60*5)
+/** Don't try to build more than this many circuits before giving up for a
+ * while.*/
+#define MAX_INTRO_CIRCS_PER_PERIOD 10
+/** How many times will a hidden service operator attempt to connect to a
+ * requested rendezvous point before giving up? */
+#define MAX_REND_FAILURES 1
+/** How many seconds should we spend trying to connect to a requested
+ * rendezvous point before giving up? */
+#define MAX_REND_TIMEOUT 30
+
+/* String prefix for the signature of ESTABLISH_INTRO */
+#define ESTABLISH_INTRO_SIG_PREFIX "Tor establish-intro cell v1"
+
+/* The default HS time period length */
+#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
+
+/* Prefix of the onion address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
+/* Length of the checksum prefix minus the NUL terminated byte. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \
+ (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1)
+/* Length of the resulting checksum of the address. The construction of this
+ * checksum looks like:
+ * CHECKSUM = ".onion checksum" || PUBKEY || VERSION
+ * where VERSION is 1 byte. This is pre-hashing. */
+#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \
+ (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t))
+/* The amount of bytes we use from the address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2
+/* Length of the binary encoded service address which is of course before the
+ * base32 encoding. Construction is:
+ * PUBKEY || CHECKSUM || VERSION
+ * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */
+#define HS_SERVICE_ADDR_LEN \
+ (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t))
+/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the
+ * length ends up to 56 bytes (not counting the terminated NUL byte.) */
+#define HS_SERVICE_ADDR_LEN_BASE32 \
+ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5))
+
+/* The default HS time period length */
+#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
+/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
+
+/* Keyblinding parameter construction is as follow:
+ * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */
+#define HS_KEYBLIND_NONCE_PREFIX "key-blind"
+#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1)
+#define HS_KEYBLIND_NONCE_LEN \
+ (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t))
+
+/* Credential and subcredential prefix value. */
+#define HS_CREDENTIAL_PREFIX "credential"
+#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1)
+#define HS_SUBCREDENTIAL_PREFIX "subcredential"
+#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1)
+
+/* Node hidden service stored at index prefix value. */
+#define HS_INDEX_PREFIX "store-at-idx"
+#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1)
+
+/* Node hidden service directory index prefix value. */
+#define HSDIR_INDEX_PREFIX "node-idx"
+#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1)
+
+/* Prefix of the shared random value disaster mode. */
+#define HS_SRV_DISASTER_PREFIX "shared-random-disaster"
+#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1)
+
+/* Default value of number of hsdir replicas (hsdir_n_replicas). */
+#define HS_DEFAULT_HSDIR_N_REPLICAS 2
+/* Default value of hsdir spread store (hsdir_spread_store). */
+#define HS_DEFAULT_HSDIR_SPREAD_STORE 3
+/* Default value of hsdir spread fetch (hsdir_spread_fetch). */
+#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3
+
+/* Type of authentication key used by an introduction point. */
+typedef enum {
+ HS_AUTH_KEY_TYPE_LEGACY = 1,
+ HS_AUTH_KEY_TYPE_ED25519 = 2,
+} hs_auth_key_type_t;
+
+/* Represents the mapping from a virtual port of a rendezvous service to a
+ * real port on some IP. */
+typedef struct rend_service_port_config_t {
+ /* The incoming HS virtual port we're mapping */
+ uint16_t virtual_port;
+ /* Is this an AF_UNIX port? */
+ unsigned int is_unix_addr:1;
+ /* The outgoing TCP port to use, if !is_unix_addr */
+ uint16_t real_port;
+ /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
+ tor_addr_t real_addr;
+ /* The socket path to connect to, if is_unix_addr */
+ char unix_addr[FLEXIBLE_ARRAY_MEMBER];
+} rend_service_port_config_t;
+
+/* Hidden service directory index used in a node_t which is set once we set
+ * the consensus. */
+typedef struct hsdir_index_t {
+ /* The hsdir index for the current time period. */
+ uint8_t current[DIGEST256_LEN];
+ /* The hsdir index for the next time period. */
+ uint8_t next[DIGEST256_LEN];
+} hsdir_index_t;
+
+void hs_init(void);
+void hs_free_all(void);
+
+int hs_check_service_private_dir(const char *username, const char *path,
+ unsigned int dir_group_readable,
+ unsigned int create);
+char *hs_path_from_filename(const char *directory, const char *filename);
+void hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out);
+int hs_address_is_valid(const char *address);
+int hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out);
+
+void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_public_key_t *pubkey_out);
+void hs_build_blinded_keypair(const ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_keypair_t *kp_out);
+int hs_service_requires_uptime_circ(const smartlist_t *ports);
+
+void rend_data_free(rend_data_t *data);
+rend_data_t *rend_data_dup(const rend_data_t *data);
+rend_data_t *rend_data_client_create(const char *onion_address,
+ const char *desc_id,
+ const char *cookie,
+ rend_auth_type_t auth_type);
+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);
+const char *rend_data_get_address(const rend_data_t *rend_data);
+const char *rend_data_get_desc_id(const rend_data_t *rend_data,
+ uint8_t replica, size_t *len_out);
+const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
+ size_t *len_out);
+
+void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ uint8_t *subcred_out);
+
+uint64_t hs_get_time_period_num(time_t now);
+uint64_t hs_get_next_time_period_num(time_t now);
+time_t hs_get_start_time_of_next_time_period(time_t now);
+
+link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec);
+
+MOCK_DECL(int, hs_overlap_mode_is_active,
+ (const networkstatus_t *consensus, time_t now));
+
+uint8_t *hs_get_current_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+uint8_t *hs_get_previous_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+
+void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
+ const uint8_t *srv, uint64_t period_num,
+ uint8_t *hsdir_index_out);
+void hs_build_hs_index(uint64_t replica,
+ const ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out);
+
+int32_t hs_get_hsdir_n_replicas(void);
+int32_t hs_get_hsdir_spread_fetch(void);
+int32_t hs_get_hsdir_spread_store(void);
+
+void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num, int is_next_period,
+ int is_client, smartlist_t *responsible_dirs);
+
+int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
+
+void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
+void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
+
+#ifdef HS_COMMON_PRIVATE
+
+STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
+
+#ifdef TOR_UNIT_TESTS
+
+STATIC uint64_t get_time_period_length(void);
+
+STATIC uint8_t *get_first_cached_disaster_srv(void);
+STATIC uint8_t *get_second_cached_disaster_srv(void);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* HS_COMMON_PRIVATE */
+
+#endif /* TOR_HS_COMMON_H */
+
diff --git a/src/or/hs_config.c b/src/or/hs_config.c
new file mode 100644
index 0000000000..5f9282ea79
--- /dev/null
+++ b/src/or/hs_config.c
@@ -0,0 +1,582 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.c
+ * \brief Implement hidden service configuration subsystem.
+ *
+ * \details
+ *
+ * This file has basically one main entry point: hs_config_service_all(). It
+ * takes the torrc options and configure hidden service from it. In validate
+ * mode, nothing is added to the global service list or keys are not generated
+ * nor loaded.
+ *
+ * A service is configured in two steps. It is first created using the tor
+ * options and then put in a staging list. It will stay there until
+ * hs_service_load_all_keys() is called. That function is responsible to
+ * load/generate the keys for the service in the staging list and if
+ * successful, transfert the service to the main global service list where
+ * at that point it is ready to be used.
+ *
+ * Configuration functions are per-version and there is a main generic one for
+ * every option that is common to all version (config_generic_service).
+ **/
+
+#define HS_CONFIG_PRIVATE
+
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_service.h"
+#include "rendservice.h"
+
+/* Using the given list of services, stage them into our global state. Every
+ * service version are handled. This function can remove entries in the given
+ * service_list.
+ *
+ * Staging a service means that we take all services in service_list and we
+ * put them in the staging list (global) which acts as a temporary list that
+ * is used by the service loading key process. In other words, staging a
+ * service puts it in a list to be considered when loading the keys and then
+ * moved to the main global list. */
+static void
+stage_services(smartlist_t *service_list)
+{
+ tor_assert(service_list);
+
+ /* This is v2 specific. Trigger service pruning which will make sure the
+ * just configured services end up in the main global list. It should only
+ * be done in non validation mode because v2 subsystem handles service
+ * object differently. */
+ rend_service_prune_list();
+
+ /* Cleanup v2 service from the list, we don't need those object anymore
+ * because we validated them all against the others and we want to stage
+ * only >= v3 service. And remember, v2 has a different object type which is
+ * shadow copied from an hs_service_t type. */
+ SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) {
+ if (s->config.version == HS_VERSION_TWO) {
+ SMARTLIST_DEL_CURRENT(service_list, s);
+ hs_service_free(s);
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ /* This is >= v3 specific. Using the newly configured service list, stage
+ * them into our global state. Every object ownership is lost after. */
+ hs_service_stage_services(service_list);
+}
+
+/* Validate the given service against all service in the given list. If the
+ * service is ephemeral, this function ignores it. Services with the same
+ * directory path aren't allowed and will return an error. If a duplicate is
+ * found, 1 is returned else 0 if none found. */
+static int
+service_is_duplicate_in_list(const smartlist_t *service_list,
+ const hs_service_t *service)
+{
+ int ret = 0;
+
+ tor_assert(service_list);
+ tor_assert(service);
+
+ /* Ephemeral service don't have a directory configured so no need to check
+ * for a service in the list having the same path. */
+ if (service->config.is_ephemeral) {
+ goto end;
+ }
+
+ /* XXX: Validate if we have any service that has the given service dir path.
+ * This has two problems:
+ *
+ * a) It's O(n^2), but the same comment from the bottom of
+ * rend_config_services() should apply.
+ *
+ * b) We only compare directory paths as strings, so we can't
+ * detect two distinct paths that specify the same directory
+ * (which can arise from symlinks, case-insensitivity, bind
+ * mounts, etc.).
+ *
+ * It also can't detect that two separate Tor instances are trying
+ * to use the same HiddenServiceDir; for that, we would need a
+ * lock file. But this is enough to detect a simple mistake that
+ * at least one person has actually made. */
+ SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) {
+ if (!strcmp(s->config.directory_path, service->config.directory_path)) {
+ log_warn(LD_REND, "Another hidden service is already configured "
+ "for directory %s",
+ escaped(service->config.directory_path));
+ ret = 1;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ end:
+ return ret;
+}
+
+/* Helper function: Given an configuration option name, its value, a minimum
+ * min and a maxium max, parse the value as a uint64_t. On success, ok is set
+ * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be
+ * ignored. This function logs both on error and success. */
+static uint64_t
+helper_parse_uint64(const char *opt, const char *value, uint64_t min,
+ uint64_t max, int *ok)
+{
+ uint64_t ret = 0;
+
+ tor_assert(opt);
+ tor_assert(value);
+ tor_assert(ok);
+
+ *ok = 0;
+ ret = tor_parse_uint64(value, 10, min, max, ok, NULL);
+ if (!*ok) {
+ log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64
+ ", not %s.",
+ opt, min, max, value);
+ goto err;
+ }
+ log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret);
+ err:
+ return ret;
+}
+
+/* Return true iff the given options starting at line_ for a hidden service
+ * contains at least one invalid option. Each hidden service option don't
+ * apply to all versions so this function can find out. The line_ MUST start
+ * right after the HiddenServiceDir line of this service.
+ *
+ * This is mainly for usability so we can inform the user of any invalid
+ * option for the hidden service version instead of silently ignoring. */
+static int
+config_has_invalid_options(const config_line_t *line_,
+ const hs_service_t *service)
+{
+ int ret = 0;
+ const char **optlist;
+ const config_line_t *line;
+
+ tor_assert(service);
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+
+ /* List of options that a v3 service doesn't support thus must exclude from
+ * its configuration. */
+ const char *opts_exclude_v3[] = {
+ "HiddenServiceAuthorizeClient",
+ NULL /* End marker. */
+ };
+
+ /* Defining the size explicitly allows us to take advantage of the compiler
+ * which warns us if we ever bump the max version but forget to grow this
+ * array. The plus one is because we have a version 0 :). */
+ struct {
+ const char **list;
+ } exclude_lists[HS_VERSION_MAX + 1] = {
+ { NULL }, /* v0. */
+ { NULL }, /* v1. */
+ { NULL }, /* v2 */
+ { opts_exclude_v3 }, /* v3. */
+ };
+
+ optlist = exclude_lists[service->config.version].list;
+ if (optlist == NULL) {
+ /* No exclude options to look at for this version. */
+ goto end;
+ }
+ for (int i = 0; optlist[i]; i++) {
+ const char *opt = optlist[i];
+ for (line = line_; line; line = line->next) {
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ goto end;
+ }
+ if (!strcasecmp(line->key, opt)) {
+ log_warn(LD_CONFIG, "Hidden service option %s is incompatible with "
+ "version %" PRIu32 " of service in %s",
+ opt, service->config.version,
+ service->config.directory_path);
+ ret = 1;
+ /* Continue the loop so we can find all possible options. */
+ continue;
+ }
+ }
+ }
+ end:
+ return ret;
+}
+
+/* Validate service configuration. This is used when loading the configuration
+ * and once we've setup a service object, it's config object is passed to this
+ * function for further validation. This does not validate service key
+ * material. Return 0 if valid else -1 if invalid. */
+static int
+config_validate_service(const hs_service_config_t *config)
+{
+ tor_assert(config);
+
+ /* Amount of ports validation. */
+ if (!config->ports || smartlist_len(config->ports) == 0) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
+ escaped(config->directory_path));
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Configuration funcion for a version 3 service. The line_ must be pointing
+ * to the directive directly after a HiddenServiceDir. That way, when hitting
+ * the next HiddenServiceDir line or reaching the end of the list of lines, we
+ * know that we have to stop looking for more options. The given service
+ * object must be already allocated and passed through
+ * config_generic_service() prior to calling this function.
+ *
+ * Return 0 on success else a negative value. */
+static int
+config_service_v3(const config_line_t *line_,
+ hs_service_config_t *config)
+{
+ int have_num_ip = 0;
+ const char *dup_opt_seen = NULL;
+ const config_line_t *line;
+
+ tor_assert(config);
+
+ for (line = line_; line; line = line->next) {
+ int ok = 0;
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ break;
+ }
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ config->num_intro_points =
+ (unsigned int) helper_parse_uint64(line->key, line->value,
+ NUM_INTRO_POINTS_DEFAULT,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ &ok);
+ if (!ok || have_num_ip) {
+ if (have_num_ip)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_num_ip = 1;
+ continue;
+ }
+ }
+
+ /* We do not load the key material for the service at this stage. This is
+ * done later once tor can confirm that it is in a running state. */
+
+ /* We are about to return a fully configured service so do one last pass of
+ * validation at it. */
+ if (config_validate_service(config) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given options in line_ and options. This is
+ * called for any service regardless of its version which means that all
+ * directives in this function are generic to any service version. This
+ * function will also check the validity of the service directory path.
+ *
+ * The line_ must be pointing to the directive directly after a
+ * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or
+ * reaching the end of the list of lines, we know that we have to stop looking
+ * for more options.
+ *
+ * Return 0 on success else -1. */
+static int
+config_generic_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_t *service)
+{
+ int dir_seen = 0;
+ const config_line_t *line;
+ hs_service_config_t *config;
+ /* If this is set, we've seen a duplicate of this option. Keep the string
+ * so we can log the directive. */
+ const char *dup_opt_seen = NULL;
+ /* These variables will tell us if we ever have duplicate. */
+ int have_version = 0, have_allow_unknown_ports = 0;
+ int have_dir_group_read = 0, have_max_streams = 0;
+ int have_max_streams_close = 0;
+
+ tor_assert(line_);
+ tor_assert(options);
+ tor_assert(service);
+
+ /* Makes thing easier. */
+ config = &service->config;
+
+ /* The first line starts with HiddenServiceDir so we consider what's next is
+ * the configuration of the service. */
+ for (line = line_; line ; line = line->next) {
+ int ok = 0;
+
+ /* This indicate that we have a new service to configure. */
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* This function only configures one service at a time so if we've
+ * already seen one, stop right now. */
+ if (dir_seen) {
+ break;
+ }
+ /* Ok, we've seen one and we are about to configure it. */
+ dir_seen = 1;
+ config->directory_path = tor_strdup(line->value);
+ log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...",
+ escaped(config->directory_path));
+ continue;
+ }
+ if (BUG(!dir_seen)) {
+ goto err;
+ }
+ /* Version of the service. */
+ if (!strcasecmp(line->key, "HiddenServiceVersion")) {
+ service->config.version =
+ (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN,
+ HS_VERSION_MAX, &ok);
+ if (!ok || have_version) {
+ if (have_version)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_version = 1;
+ continue;
+ }
+ /* Virtual port. */
+ if (!strcasecmp(line->key, "HiddenServicePort")) {
+ char *err_msg = NULL;
+ /* XXX: Can we rename this? */
+ rend_service_port_config_t *portcfg =
+ rend_service_parse_port_config(line->value, " ", &err_msg);
+ if (!portcfg) {
+ if (err_msg) {
+ log_warn(LD_CONFIG, "%s", err_msg);
+ }
+ tor_free(err_msg);
+ goto err;
+ }
+ tor_assert(!err_msg);
+ smartlist_add(config->ports, portcfg);
+ log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
+ line->value, escaped(config->directory_path));
+ continue;
+ }
+ /* Do we allow unknown ports. */
+ if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
+ config->allow_unknown_ports =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_allow_unknown_ports) {
+ if (have_allow_unknown_ports)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_allow_unknown_ports = 1;
+ continue;
+ }
+ /* Directory group readable. */
+ if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) {
+ config->dir_group_readable =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_dir_group_read) {
+ if (have_dir_group_read)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_dir_group_read = 1;
+ continue;
+ }
+ /* Maximum streams per circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
+ config->max_streams_per_rdv_circuit =
+ helper_parse_uint64(line->key, line->value, 0,
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok);
+ if (!ok || have_max_streams) {
+ if (have_max_streams)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams = 1;
+ continue;
+ }
+ /* Maximum amount of streams before we close the circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
+ config->max_streams_close_circuit =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_max_streams_close) {
+ if (have_max_streams_close)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams_close = 1;
+ continue;
+ }
+ }
+
+ /* Check if we are configured in non anonymous mode and single hop mode
+ * meaning every service become single onion. */
+ if (rend_service_allow_non_anonymous_connection(options) &&
+ rend_service_non_anonymous_mode_enabled(options)) {
+ config->is_single_onion = 1;
+ }
+
+ /* Success */
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given line and options. This function will
+ * call the corresponding configuration function for a specific service
+ * version and validate the service against the other ones. On success, add
+ * the service to the given list and return 0. On error, nothing is added to
+ * the list and a negative value is returned. */
+static int
+config_service(const config_line_t *line, const or_options_t *options,
+ smartlist_t *service_list)
+{
+ int ret;
+ hs_service_t *service = NULL;
+
+ tor_assert(line);
+ tor_assert(options);
+ tor_assert(service_list);
+
+ /* We have a new hidden service. */
+ service = hs_service_new(options);
+ /* We'll configure that service as a generic one and then pass it to a
+ * specific function according to the configured version number. */
+ if (config_generic_service(line, options, service) < 0) {
+ goto err;
+ }
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+ /* Before we configure the service on a per-version basis, we'll make
+ * sure that this set of options for a service are valid that is for
+ * instance an option only for v2 is not used for v3. */
+ if (config_has_invalid_options(line->next, service)) {
+ goto err;
+ }
+ /* Check permission on service directory that was just parsed. And this must
+ * be done regardless of the service version. Do not ask for the directory
+ * to be created, this is done when the keys are loaded because we could be
+ * in validation mode right now. */
+ if (hs_check_service_private_dir(options->User,
+ service->config.directory_path,
+ service->config.dir_group_readable,
+ 0) < 0) {
+ goto err;
+ }
+ /* Different functions are in charge of specific options for a version. We
+ * start just after the service directory line so once we hit another
+ * directory line, the function knows that it has to stop parsing. */
+ switch (service->config.version) {
+ case HS_VERSION_TWO:
+ ret = rend_config_service(line->next, options, &service->config);
+ break;
+ case HS_VERSION_THREE:
+ ret = config_service_v3(line->next, &service->config);
+ break;
+ default:
+ /* We do validate before if we support the parsed version. */
+ tor_assert_nonfatal_unreached();
+ goto err;
+ }
+ if (ret < 0) {
+ goto err;
+ }
+ /* We'll check if this service can be kept depending on the others
+ * configured previously. */
+ if (service_is_duplicate_in_list(service_list, service)) {
+ goto err;
+ }
+ /* Passes, add it to the given list. */
+ smartlist_add(service_list, service);
+ return 0;
+
+ err:
+ hs_service_free(service);
+ return -1;
+}
+
+/* From a set of <b>options</b>, setup every hidden service found. Return 0 on
+ * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and
+ * return as normal, but don't actually change the configured services. */
+int
+hs_config_service_all(const or_options_t *options, int validate_only)
+{
+ int dir_option_seen = 0, ret = -1;
+ const config_line_t *line;
+ smartlist_t *new_service_list = NULL;
+
+ tor_assert(options);
+
+ /* Newly configured service are put in that list which is then used for
+ * validation and staging for >= v3. */
+ new_service_list = smartlist_new();
+
+ for (line = options->RendConfigLines; line; line = line->next) {
+ /* Ignore all directives that aren't the start of a service. */
+ if (strcasecmp(line->key, "HiddenServiceDir")) {
+ if (!dir_option_seen) {
+ log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
+ line->key);
+ goto err;
+ }
+ continue;
+ }
+ /* Flag that we've seen a directory directive and we'll use it to make
+ * sure that the torrc options ordering is actually valid. */
+ dir_option_seen = 1;
+
+ /* Try to configure this service now. On success, it will be added to the
+ * list and validated against the service in that same list. */
+ if (config_service(line, options, new_service_list) < 0) {
+ goto err;
+ }
+ }
+
+ /* In non validation mode, we'll stage those services we just successfully
+ * configured. Service ownership is transfered from the list to the global
+ * state. If any service is invalid, it will be removed from the list and
+ * freed. All versions are handled in that function. */
+ if (!validate_only) {
+ stage_services(new_service_list);
+ } else {
+ /* We've just validated that we were able to build a clean working list of
+ * services. We don't need those objects anymore. */
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s,
+ hs_service_free(s));
+ /* For the v2 subsystem, the configuration function adds the service
+ * object to the staging list and it is transferred in the main list
+ * through the prunning process. In validation mode, we thus have to purge
+ * the staging list so it's not kept in memory as valid service. */
+ rend_service_free_staging_list();
+ }
+
+ /* Success. Note that the service list has no ownership of its content. */
+ ret = 0;
+ goto end;
+
+ err:
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s));
+
+ end:
+ smartlist_free(new_service_list);
+ /* Tor main should call the free all function on error. */
+ return ret;
+}
+
diff --git a/src/or/hs_config.h b/src/or/hs_config.h
new file mode 100644
index 0000000000..2f8cbdc130
--- /dev/null
+++ b/src/or/hs_config.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.h
+ * \brief Header file containing configuration ABI/API for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CONFIG_H
+#define TOR_HS_CONFIG_H
+
+#include "or.h"
+
+/* Max value for HiddenServiceMaxStreams */
+#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535
+/* Maximum number of intro points per version 3 services. */
+#define HS_CONFIG_V3_MAX_INTRO_POINTS 20
+
+/* API */
+
+int hs_config_service_all(const or_options_t *options, int validate_only);
+
+#endif /* TOR_HS_CONFIG_H */
+
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
new file mode 100644
index 0000000000..9a1e377155
--- /dev/null
+++ b/src/or/hs_descriptor.c
@@ -0,0 +1,2546 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.c
+ * \brief Handle hidden service descriptor encoding/decoding.
+ *
+ * \details
+ * Here is a graphical depiction of an HS descriptor and its layers:
+ *
+ * +------------------------------------------------------+
+ * |DESCRIPTOR HEADER: |
+ * | hs-descriptor 3 |
+ * | descriptor-lifetime 180 |
+ * | ... |
+ * | superencrypted |
+ * |+---------------------------------------------------+ |
+ * ||SUPERENCRYPTED LAYER (aka OUTER ENCRYPTED LAYER): | |
+ * || desc-auth-type x25519 | |
+ * || desc-auth-ephemeral-key | |
+ * || auth-client | |
+ * || auth-client | |
+ * || ... | |
+ * || encrypted | |
+ * ||+-------------------------------------------------+| |
+ * |||ENCRYPTED LAYER (aka INNER ENCRYPTED LAYER): || |
+ * ||| create2-formats || |
+ * ||| intro-auth-required || |
+ * ||| introduction-point || |
+ * ||| introduction-point || |
+ * ||| ... || |
+ * ||+-------------------------------------------------+| |
+ * |+---------------------------------------------------+ |
+ * +------------------------------------------------------+
+ *
+ * The DESCRIPTOR HEADER section is completely unencrypted and contains generic
+ * descriptor metadata.
+ *
+ * The SUPERENCRYPTED LAYER section is the first layer of encryption, and it's
+ * encrypted using the blinded public key of the hidden service to protect
+ * against entities who don't know its onion address. The clients of the hidden
+ * service know its onion address and blinded public key, whereas third-parties
+ * (like HSDirs) don't know it (except if it's a public hidden service).
+ *
+ * The ENCRYPTED LAYER section is the second layer of encryption, and it's
+ * encrypted using the client authorization key material (if those exist). When
+ * client authorization is enabled, this second layer of encryption protects
+ * the descriptor content from unauthorized entities. If client authorization
+ * is disabled, this second layer of encryption does not provide any extra
+ * security but is still present. The plaintext of this layer contains all the
+ * information required to connect to the hidden service like its list of
+ * introduction points.
+ **/
+
+/* For unit tests.*/
+#define HS_DESCRIPTOR_PRIVATE
+
+#include "hs_descriptor.h"
+
+#include "or.h"
+#include "circuitbuild.h"
+#include "ed25519_cert.h" /* Trunnel interface. */
+#include "parsecommon.h"
+#include "rendcache.h"
+#include "hs_cache.h"
+#include "hs_config.h"
+#include "torcert.h" /* tor_cert_encode_ed22519() */
+
+/* Constant string value used for the descriptor format. */
+#define str_hs_desc "hs-descriptor"
+#define str_desc_cert "descriptor-signing-key-cert"
+#define str_rev_counter "revision-counter"
+#define str_superencrypted "superencrypted"
+#define str_encrypted "encrypted"
+#define str_signature "signature"
+#define str_lifetime "descriptor-lifetime"
+/* Constant string value for the encrypted part of the descriptor. */
+#define str_create2_formats "create2-formats"
+#define str_intro_auth_required "intro-auth-required"
+#define str_single_onion "single-onion-service"
+#define str_intro_point "introduction-point"
+#define str_ip_onion_key "onion-key"
+#define str_ip_auth_key "auth-key"
+#define str_ip_enc_key "enc-key"
+#define str_ip_enc_key_cert "enc-key-cert"
+#define str_ip_legacy_key "legacy-key"
+#define str_ip_legacy_key_cert "legacy-key-cert"
+#define str_intro_point_start "\n" str_intro_point " "
+/* Constant string value for the construction to encrypt the encrypted data
+ * section. */
+#define str_enc_const_superencryption "hsdir-superencrypted-data"
+#define str_enc_const_encryption "hsdir-encrypted-data"
+/* Prefix required to compute/verify HS desc signatures */
+#define str_desc_sig_prefix "Tor onion service descriptor sig v3"
+#define str_desc_auth_type "desc-auth-type"
+#define str_desc_auth_key "desc-auth-ephemeral-key"
+#define str_desc_auth_client "auth-client"
+#define str_encrypted "encrypted"
+
+/* Authentication supported types. */
+static const struct {
+ hs_desc_auth_type_t type;
+ const char *identifier;
+} intro_auth_types[] = {
+ { HS_DESC_AUTH_ED25519, "ed25519" },
+ /* Indicate end of array. */
+ { 0, NULL }
+};
+
+/* Descriptor ruleset. */
+static token_rule_t hs_desc_v3_token_table[] = {
+ T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ),
+ T1(str_lifetime, R3_DESC_LIFETIME, EQ(1), NO_OBJ),
+ T1(str_desc_cert, R3_DESC_SIGNING_CERT, NO_ARGS, NEED_OBJ),
+ T1(str_rev_counter, R3_REVISION_COUNTER, EQ(1), NO_OBJ),
+ T1(str_superencrypted, R3_SUPERENCRYPTED, NO_ARGS, NEED_OBJ),
+ T1_END(str_signature, R3_SIGNATURE, EQ(1), NO_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the superencrypted section. */
+static token_rule_t hs_desc_superencrypted_v3_token_table[] = {
+ T1_START(str_desc_auth_type, R3_DESC_AUTH_TYPE, GE(1), NO_OBJ),
+ T1(str_desc_auth_key, R3_DESC_AUTH_KEY, GE(1), NO_OBJ),
+ T1N(str_desc_auth_client, R3_DESC_AUTH_CLIENT, GE(3), NO_OBJ),
+ T1(str_encrypted, R3_ENCRYPTED, NO_ARGS, NEED_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the encrypted section. */
+static token_rule_t hs_desc_encrypted_v3_token_table[] = {
+ T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ),
+ T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, ARGS, NO_OBJ),
+ T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the introduction points section. */
+static token_rule_t hs_desc_intro_point_v3_token_table[] = {
+ T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ),
+ T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK),
+ T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ),
+ T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK),
+ T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK),
+ T01(str_ip_legacy_key, R3_INTRO_LEGACY_KEY, ARGS, NEED_KEY_1024),
+ T01(str_ip_legacy_key_cert, R3_INTRO_LEGACY_KEY_CERT, ARGS, OBJ_OK),
+ END_OF_TABLE
+};
+
+/* Free the content of the plaintext section of a descriptor. */
+static void
+desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->superencrypted_blob) {
+ tor_free(desc->superencrypted_blob);
+ }
+ tor_cert_free(desc->signing_key_cert);
+
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Free the content of the encrypted section of a descriptor. */
+static void
+desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->intro_auth_types) {
+ SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a));
+ smartlist_free(desc->intro_auth_types);
+ }
+ if (desc->intro_points) {
+ SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
+ hs_desc_intro_point_free(ip));
+ smartlist_free(desc->intro_points);
+ }
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out.
+ * We use SHA3-256 for the MAC computation.
+ * This function can't fail. */
+static void
+build_mac(const uint8_t *mac_key, size_t mac_key_len,
+ const uint8_t *salt, size_t salt_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ uint8_t *mac_out, size_t mac_len)
+{
+ crypto_digest_t *digest;
+
+ const uint64_t mac_len_netorder = tor_htonll(mac_key_len);
+ const uint64_t salt_len_netorder = tor_htonll(salt_len);
+
+ tor_assert(mac_key);
+ tor_assert(salt);
+ tor_assert(encrypted);
+ tor_assert(mac_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ /* As specified in section 2.5 of proposal 224, first add the mac key
+ * then add the salt first and then the encrypted section. */
+
+ crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len);
+ crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) salt, salt_len);
+ crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len);
+ crypto_digest_get_digest(digest, (char *) mac_out, mac_len);
+ crypto_digest_free(digest);
+}
+
+/* Using a given decriptor object, build the secret input needed for the
+ * KDF and put it in the dst pointer which is an already allocated buffer
+ * of size dstlen. */
+static void
+build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
+{
+ size_t offset = 0;
+
+ tor_assert(desc);
+ tor_assert(dst);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen);
+
+ /* XXX use the destination length as the memcpy length */
+ /* Copy blinded public key. */
+ memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey,
+ sizeof(desc->plaintext_data.blinded_pubkey.pubkey));
+ offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey);
+ /* Copy subcredential. */
+ memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
+ offset += sizeof(desc->subcredential);
+ /* Copy revision counter value. */
+ set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
+ offset += sizeof(uint64_t);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
+}
+
+/* Do the KDF construction and put the resulting data in key_out which is of
+ * key_out_len length. It uses SHAKE-256 as specified in the spec. */
+static void
+build_kdf_key(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_out_len,
+ int is_superencrypted_layer)
+{
+ uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN];
+ crypto_xof_t *xof;
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+
+ /* Build the secret input for the KDF computation. */
+ build_secret_input(desc, secret_input, sizeof(secret_input));
+
+ xof = crypto_xof_new();
+ /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */
+ crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input));
+ crypto_xof_add_bytes(xof, salt, salt_len);
+
+ /* Feed in the right string constant based on the desc layer */
+ if (is_superencrypted_layer) {
+ crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_superencryption,
+ strlen(str_enc_const_superencryption));
+ } else {
+ crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_encryption,
+ strlen(str_enc_const_encryption));
+ }
+
+ /* Eat from our KDF. */
+ crypto_xof_squeeze_bytes(xof, key_out, key_out_len);
+ crypto_xof_free(xof);
+ memwipe(secret_input, 0, sizeof(secret_input));
+}
+
+/* Using the given descriptor and salt, run it through our KDF function and
+ * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out.
+ * This function can't fail. */
+static void
+build_secret_key_iv_mac(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_len,
+ uint8_t *iv_out, size_t iv_len,
+ uint8_t *mac_out, size_t mac_len,
+ int is_superencrypted_layer)
+{
+ size_t offset = 0;
+ uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN];
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+ tor_assert(iv_out);
+ tor_assert(mac_out);
+
+ build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key),
+ is_superencrypted_layer);
+ /* Copy the bytes we need for both the secret key and IV. */
+ memcpy(key_out, kdf_key, key_len);
+ offset += key_len;
+ memcpy(iv_out, kdf_key + offset, iv_len);
+ offset += iv_len;
+ memcpy(mac_out, kdf_key + offset, mac_len);
+ /* Extra precaution to make sure we are not out of bound. */
+ tor_assert((offset + mac_len) == sizeof(kdf_key));
+ memwipe(kdf_key, 0, sizeof(kdf_key));
+}
+
+/* === ENCODING === */
+
+/* Encode the given link specifier objects into a newly allocated string.
+ * This can't fail so caller can always assume a valid string being
+ * returned. */
+STATIC char *
+encode_link_specifiers(const smartlist_t *specs)
+{
+ char *encoded_b64 = NULL;
+ link_specifier_list_t *lslist = link_specifier_list_new();
+
+ tor_assert(specs);
+ /* No link specifiers is a code flow error, can't happen. */
+ tor_assert(smartlist_len(specs) > 0);
+ tor_assert(smartlist_len(specs) <= UINT8_MAX);
+
+ link_specifier_list_set_n_spec(lslist, smartlist_len(specs));
+
+ SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
+ spec) {
+ link_specifier_t *ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, spec->type);
+
+ switch (spec->type) {
+ case LS_IPV4:
+ link_specifier_set_un_ipv4_addr(ls,
+ tor_addr_to_ipv4h(&spec->u.ap.addr));
+ link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
+ /* Four bytes IPv4 and two bytes port. */
+ link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
+ sizeof(spec->u.ap.port));
+ break;
+ case LS_IPV6:
+ {
+ size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+ const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
+ uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+ memcpy(ipv6_array, in6_addr, addr_len);
+ link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
+ /* Sixteen bytes IPv6 and two bytes port. */
+ link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
+ break;
+ }
+ case LS_LEGACY_ID:
+ {
+ size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
+ uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
+ memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
+ link_specifier_set_ls_len(ls, legacy_id_len);
+ break;
+ }
+ case LS_ED25519_ID:
+ {
+ size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
+ uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
+ memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
+ link_specifier_set_ls_len(ls, ed25519_id_len);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ link_specifier_list_add_spec(lslist, ls);
+ } SMARTLIST_FOREACH_END(spec);
+
+ {
+ uint8_t *encoded;
+ ssize_t encoded_len, encoded_b64_len, ret;
+
+ encoded_len = link_specifier_list_encoded_len(lslist);
+ tor_assert(encoded_len > 0);
+ encoded = tor_malloc_zero(encoded_len);
+ ret = link_specifier_list_encode(encoded, encoded_len, lslist);
+ tor_assert(ret == encoded_len);
+
+ /* Base64 encode our binary format. Add extra NUL byte for the base64
+ * encoded value. */
+ encoded_b64_len = base64_encode_size(encoded_len, 0) + 1;
+ encoded_b64 = tor_malloc_zero(encoded_b64_len);
+ ret = base64_encode(encoded_b64, encoded_b64_len, (const char *) encoded,
+ encoded_len, 0);
+ tor_assert(ret == (encoded_b64_len - 1));
+ tor_free(encoded);
+ }
+
+ link_specifier_list_free(lslist);
+ return encoded_b64;
+}
+
+/* Encode an introduction point legacy key and certificate. Return a newly
+ * allocated string with it. On failure, return NULL. */
+static char *
+encode_legacy_key(const hs_desc_intro_point_t *ip)
+{
+ char *key_str, b64_cert[256], *encoded = NULL;
+ size_t key_str_len;
+
+ tor_assert(ip);
+
+ /* Encode cross cert. */
+ if (base64_encode(b64_cert, sizeof(b64_cert),
+ (const char *) ip->legacy.cert.encoded,
+ ip->legacy.cert.len, BASE64_ENCODE_MULTILINE) < 0) {
+ log_warn(LD_REND, "Unable to encode legacy crosscert.");
+ goto done;
+ }
+ /* Convert the encryption key to PEM format NUL terminated. */
+ if (crypto_pk_write_public_key_to_string(ip->legacy.key, &key_str,
+ &key_str_len) < 0) {
+ log_warn(LD_REND, "Unable to encode legacy encryption key.");
+ goto done;
+ }
+ tor_asprintf(&encoded,
+ "%s \n%s" /* Newline is added by the call above. */
+ "%s\n"
+ "-----BEGIN CROSSCERT-----\n"
+ "%s"
+ "-----END CROSSCERT-----",
+ str_ip_legacy_key, key_str,
+ str_ip_legacy_key_cert, b64_cert);
+ tor_free(key_str);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point encryption key and certificate. Return a newly
+ * allocated string with it. On failure, return NULL. */
+static char *
+encode_enc_key(const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL, *encoded_cert;
+ char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+
+ tor_assert(ip);
+
+ /* Base64 encode the encryption key for the "enc-key" field. */
+ if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) {
+ goto done;
+ }
+ if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) {
+ goto done;
+ }
+ tor_asprintf(&encoded,
+ "%s ntor %s\n"
+ "%s\n%s",
+ str_ip_enc_key, key_b64,
+ str_ip_enc_key_cert, encoded_cert);
+ tor_free(encoded_cert);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point onion key. Return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_onion_key(const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL;
+ char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+
+ tor_assert(ip);
+
+ /* Base64 encode the encryption key for the "onion-key" field. */
+ if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) {
+ goto done;
+ }
+ tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point object and return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_intro_point(const ed25519_public_key_t *sig_key,
+ const hs_desc_intro_point_t *ip)
+{
+ char *encoded_ip = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(ip);
+ tor_assert(sig_key);
+
+ /* Encode link specifier. */
+ {
+ char *ls_str = encode_link_specifiers(ip->link_specifiers);
+ smartlist_add_asprintf(lines, "%s %s", str_intro_point, ls_str);
+ tor_free(ls_str);
+ }
+
+ /* Onion key encoding. */
+ {
+ char *encoded_onion_key = encode_onion_key(ip);
+ if (encoded_onion_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_onion_key);
+ tor_free(encoded_onion_key);
+ }
+
+ /* Authentication key encoding. */
+ {
+ char *encoded_cert;
+ if (tor_cert_encode_ed22519(ip->auth_key_cert, &encoded_cert) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s\n%s", str_ip_auth_key, encoded_cert);
+ tor_free(encoded_cert);
+ }
+
+ /* Encryption key encoding. */
+ {
+ char *encoded_enc_key = encode_enc_key(ip);
+ if (encoded_enc_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_enc_key);
+ tor_free(encoded_enc_key);
+ }
+
+ /* Legacy key if any. */
+ if (ip->legacy.key != NULL) {
+ /* Strong requirement else the IP creation was badly done. */
+ tor_assert(ip->legacy.cert.encoded);
+ char *encoded_legacy_key = encode_legacy_key(ip);
+ if (encoded_legacy_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_legacy_key);
+ tor_free(encoded_legacy_key);
+ }
+
+ /* Join them all in one blob of text. */
+ encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL);
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return encoded_ip;
+}
+
+/* Given a source length, return the new size including padding for the
+ * plaintext encryption. */
+static size_t
+compute_padded_plaintext_length(size_t plaintext_len)
+{
+ size_t plaintext_padded_len;
+ const int padding_block_length = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE;
+
+ /* Make sure we won't overflow. */
+ tor_assert(plaintext_len <= (SIZE_T_CEILING - padding_block_length));
+
+ /* Get the extra length we need to add. For example, if srclen is 10200
+ * bytes, this will expand to (2 * 10k) == 20k thus an extra 9800 bytes. */
+ plaintext_padded_len = CEIL_DIV(plaintext_len, padding_block_length) *
+ padding_block_length;
+ /* Can never be extra careful. Make sure we are _really_ padded. */
+ tor_assert(!(plaintext_padded_len % padding_block_length));
+ return plaintext_padded_len;
+}
+
+/* Given a buffer, pad it up to the encrypted section padding requirement. Set
+ * the newly allocated string in padded_out and return the length of the
+ * padded buffer. */
+STATIC size_t
+build_plaintext_padding(const char *plaintext, size_t plaintext_len,
+ uint8_t **padded_out)
+{
+ size_t padded_len;
+ uint8_t *padded;
+
+ tor_assert(plaintext);
+ tor_assert(padded_out);
+
+ /* Allocate the final length including padding. */
+ padded_len = compute_padded_plaintext_length(plaintext_len);
+ tor_assert(padded_len >= plaintext_len);
+ padded = tor_malloc_zero(padded_len);
+
+ memcpy(padded, plaintext, plaintext_len);
+ *padded_out = padded;
+ return padded_len;
+}
+
+/* Using a key, IV and plaintext data of length plaintext_len, create the
+ * encrypted section by encrypting it and setting encrypted_out with the
+ * data. Return size of the encrypted data buffer. */
+static size_t
+build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext,
+ size_t plaintext_len, uint8_t **encrypted_out,
+ int is_superencrypted_layer)
+{
+ size_t encrypted_len;
+ uint8_t *padded_plaintext, *encrypted;
+ crypto_cipher_t *cipher;
+
+ tor_assert(key);
+ tor_assert(iv);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* If we are encrypting the middle layer of the descriptor, we need to first
+ pad the plaintext */
+ if (is_superencrypted_layer) {
+ encrypted_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ /* Extra precautions that we have a valid padding length. */
+ tor_assert(!(encrypted_len % HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE));
+ } else { /* No padding required for inner layers */
+ padded_plaintext = tor_memdup(plaintext, plaintext_len);
+ encrypted_len = plaintext_len;
+ }
+
+ /* This creates a cipher for AES. It can't fail. */
+ cipher = crypto_cipher_new_with_iv_and_bits(key, iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* We use a stream cipher so the encrypted length will be the same as the
+ * plaintext padded length. */
+ encrypted = tor_malloc_zero(encrypted_len);
+ /* This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) encrypted,
+ (const char *) padded_plaintext, encrypted_len);
+ *encrypted_out = encrypted;
+ /* Cleanup. */
+ crypto_cipher_free(cipher);
+ tor_free(padded_plaintext);
+ return encrypted_len;
+}
+
+/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> to get the
+ * keys. Set encrypted_out with the encrypted data and return the length of
+ * it. <b>is_superencrypted_layer</b> is set if this is the outer encrypted
+ * layer of the descriptor. */
+static size_t
+encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
+ char **encrypted_out, int is_superencrypted_layer)
+{
+ char *final_blob;
+ size_t encrypted_len, final_blob_len, offset = 0;
+ uint8_t *encrypted;
+ uint8_t salt[HS_DESC_ENCRYPTED_SALT_LEN];
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN];
+
+ tor_assert(desc);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* Get our salt. The returned bytes are already hashed. */
+ crypto_strongest_rand(salt, sizeof(salt));
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the encryption. */
+ build_secret_key_iv_mac(desc, salt, sizeof(salt),
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key),
+ is_superencrypted_layer);
+
+ /* Build the encrypted part that is do the actual encryption. */
+ encrypted_len = build_encrypted(secret_key, secret_iv, plaintext,
+ strlen(plaintext), &encrypted,
+ is_superencrypted_layer);
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ /* This construction is specified in section 2.5 of proposal 224. */
+ final_blob_len = sizeof(salt) + encrypted_len + DIGEST256_LEN;
+ final_blob = tor_malloc_zero(final_blob_len);
+
+ /* Build the MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, sizeof(salt),
+ encrypted, encrypted_len, mac, sizeof(mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+
+ /* The salt is the first value. */
+ memcpy(final_blob, salt, sizeof(salt));
+ offset = sizeof(salt);
+ /* Second value is the encrypted data. */
+ memcpy(final_blob + offset, encrypted, encrypted_len);
+ offset += encrypted_len;
+ /* Third value is the MAC. */
+ memcpy(final_blob + offset, mac, sizeof(mac));
+ offset += sizeof(mac);
+ /* Cleanup the buffers. */
+ memwipe(salt, 0, sizeof(salt));
+ memwipe(encrypted, 0, encrypted_len);
+ tor_free(encrypted);
+ /* Extra precaution. */
+ tor_assert(offset == final_blob_len);
+
+ *encrypted_out = final_blob;
+ return final_blob_len;
+}
+
+/* Create and return a string containing a fake client-auth entry. It's the
+ * responsibility of the caller to free the returned string. This function will
+ * never fail. */
+static char *
+get_fake_auth_client_str(void)
+{
+ char *auth_client_str = NULL;
+ /* We are gonna fill these arrays with fake base64 data. They are all double
+ * the size of their binary representation to fit the base64 overhead. */
+ char client_id_b64[8*2];
+ char iv_b64[16*2];
+ char encrypted_cookie_b64[16*2];
+ int retval;
+
+ /* This is a macro to fill a field with random data and then base64 it. */
+#define FILL_WITH_FAKE_DATA_AND_BASE64(field) STMT_BEGIN \
+ crypto_rand((char *)field, sizeof(field)); \
+ retval = base64_encode_nopad(field##_b64, sizeof(field##_b64), \
+ field, sizeof(field)); \
+ tor_assert(retval > 0); \
+ STMT_END
+
+ { /* Get those fakes! */
+ uint8_t client_id[8]; /* fake client-id */
+ uint8_t iv[16]; /* fake IV (initialization vector) */
+ uint8_t encrypted_cookie[16]; /* fake encrypted cookie */
+
+ FILL_WITH_FAKE_DATA_AND_BASE64(client_id);
+ FILL_WITH_FAKE_DATA_AND_BASE64(iv);
+ FILL_WITH_FAKE_DATA_AND_BASE64(encrypted_cookie);
+ }
+
+ /* Build the final string */
+ tor_asprintf(&auth_client_str, "%s %s %s %s", str_desc_auth_client,
+ client_id_b64, iv_b64, encrypted_cookie_b64);
+
+#undef FILL_WITH_FAKE_DATA_AND_BASE64
+
+ return auth_client_str;
+}
+
+/** How many lines of "client-auth" we want in our descriptors; fake or not. */
+#define CLIENT_AUTH_ENTRIES_BLOCK_SIZE 16
+
+/** Create the "client-auth" part of the descriptor and return a
+ * newly-allocated string with it. It's the responsibility of the caller to
+ * free the returned string. */
+static char *
+get_fake_auth_client_lines(void)
+{
+ /* XXX: Client authorization is still not implemented, so all this function
+ does is make fake clients */
+ int i = 0;
+ smartlist_t *auth_client_lines = smartlist_new();
+ char *auth_client_lines_str = NULL;
+
+ /* Make a line for each fake client */
+ const int num_fake_clients = CLIENT_AUTH_ENTRIES_BLOCK_SIZE;
+ for (i = 0; i < num_fake_clients; i++) {
+ char *auth_client_str = get_fake_auth_client_str();
+ tor_assert(auth_client_str);
+ smartlist_add(auth_client_lines, auth_client_str);
+ }
+
+ /* Join all lines together to form final string */
+ auth_client_lines_str = smartlist_join_strings(auth_client_lines,
+ "\n", 1, NULL);
+ /* Cleanup the mess */
+ SMARTLIST_FOREACH(auth_client_lines, char *, a, tor_free(a));
+ smartlist_free(auth_client_lines);
+
+ return auth_client_lines_str;
+}
+
+/* Create the inner layer of the descriptor (which includes the intro points,
+ * etc.). Return a newly-allocated string with the layer plaintext, or NULL if
+ * an error occured. It's the responsibility of the caller to free the returned
+ * string. */
+static char *
+get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc)
+{
+ char *encoded_str = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ /* Build the start of the section prior to the introduction points. */
+ {
+ if (!desc->encrypted_data.create2_ntor) {
+ log_err(LD_BUG, "HS desc doesn't have recognized handshake type.");
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
+ ONION_HANDSHAKE_TYPE_NTOR);
+
+ if (desc->encrypted_data.intro_auth_types &&
+ smartlist_len(desc->encrypted_data.intro_auth_types)) {
+ /* Put the authentication-required line. */
+ char *buf = smartlist_join_strings(desc->encrypted_data.intro_auth_types,
+ " ", 0, NULL);
+ smartlist_add_asprintf(lines, "%s %s\n", str_intro_auth_required, buf);
+ tor_free(buf);
+ }
+
+ if (desc->encrypted_data.single_onion_service) {
+ smartlist_add_asprintf(lines, "%s\n", str_single_onion);
+ }
+ }
+
+ /* Build the introduction point(s) section. */
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ char *encoded_ip = encode_intro_point(&desc->plaintext_data.signing_pubkey,
+ ip);
+ if (encoded_ip == NULL) {
+ log_err(LD_BUG, "HS desc intro point is malformed.");
+ goto err;
+ }
+ smartlist_add(lines, encoded_ip);
+ } SMARTLIST_FOREACH_END(ip);
+
+ /* Build the entire encrypted data section into one encoded plaintext and
+ * then encrypt it. */
+ encoded_str = smartlist_join_strings(lines, "", 0, NULL);
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+
+ return encoded_str;
+}
+
+/* Create the middle layer of the descriptor, which includes the client auth
+ * data and the encrypted inner layer (provided as a base64 string at
+ * <b>layer2_b64_ciphertext</b>). Return a newly-allocated string with the
+ * layer plaintext, or NULL if an error occured. It's the responsibility of the
+ * caller to free the returned string. */
+static char *
+get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc,
+ const char *layer2_b64_ciphertext)
+{
+ char *layer1_str = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ /* XXX: Disclaimer: This function generates only _fake_ client auth
+ * data. Real client auth is not yet implemented, but client auth data MUST
+ * always be present in descriptors. In the future this function will be
+ * refactored to use real client auth data if they exist (#20700). */
+ (void) *desc;
+
+ /* Specify auth type */
+ smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_type, "x25519");
+
+ { /* Create fake ephemeral x25519 key */
+ char fake_key_base64[CURVE25519_BASE64_PADDED_LEN + 1];
+ curve25519_keypair_t fake_x25519_keypair;
+ if (curve25519_keypair_generate(&fake_x25519_keypair, 0) < 0) {
+ goto done;
+ }
+ if (curve25519_public_to_base64(fake_key_base64,
+ &fake_x25519_keypair.pubkey) < 0) {
+ goto done;
+ }
+ smartlist_add_asprintf(lines, "%s %s\n",
+ str_desc_auth_key, fake_key_base64);
+ /* No need to memwipe any of these fake keys. They will go unused. */
+ }
+
+ { /* Create fake auth-client lines. */
+ char *auth_client_lines = get_fake_auth_client_lines();
+ tor_assert(auth_client_lines);
+ smartlist_add(lines, auth_client_lines);
+ }
+
+ /* create encrypted section */
+ {
+ smartlist_add_asprintf(lines,
+ "%s\n"
+ "-----BEGIN MESSAGE-----\n"
+ "%s"
+ "-----END MESSAGE-----",
+ str_encrypted, layer2_b64_ciphertext);
+ }
+
+ layer1_str = smartlist_join_strings(lines, "", 0, NULL);
+
+ done:
+ SMARTLIST_FOREACH(lines, char *, a, tor_free(a));
+ smartlist_free(lines);
+
+ return layer1_str;
+}
+
+/* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before
+ * returning it. <b>desc</b> is provided to derive the encryption
+ * keys. <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the
+ * middle (superencrypted) layer of the descriptor. It's the responsibility of
+ * the caller to free the returned string. */
+static char *
+encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
+ const char *encoded_str,
+ int is_superencrypted_layer)
+{
+ char *enc_b64;
+ ssize_t enc_b64_len, ret_len, enc_len;
+ char *encrypted_blob = NULL;
+
+ enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob,
+ is_superencrypted_layer);
+ /* Get the encoded size plus a NUL terminating byte. */
+ enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1;
+ enc_b64 = tor_malloc_zero(enc_b64_len);
+ /* Base64 the encrypted blob before returning it. */
+ ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len,
+ BASE64_ENCODE_MULTILINE);
+ /* Return length doesn't count the NUL byte. */
+ tor_assert(ret_len == (enc_b64_len - 1));
+ tor_free(encrypted_blob);
+
+ return enc_b64;
+}
+
+/* Generate and encode the superencrypted portion of <b>desc</b>. This also
+ * involves generating the encrypted portion of the descriptor, and performing
+ * the superencryption. A newly allocated NUL-terminated string pointer
+ * containing the encrypted encoded blob is put in encrypted_blob_out. Return 0
+ * on success else a negative value. */
+static int
+encode_superencrypted_data(const hs_descriptor_t *desc,
+ char **encrypted_blob_out)
+{
+ int ret = -1;
+ char *layer2_str = NULL;
+ char *layer2_b64_ciphertext = NULL;
+ char *layer1_str = NULL;
+ char *layer1_b64_ciphertext = NULL;
+
+ tor_assert(desc);
+ tor_assert(encrypted_blob_out);
+
+ /* Func logic: We first create the inner layer of the descriptor (layer2).
+ * We then encrypt it and use it to create the middle layer of the descriptor
+ * (layer1). Finally we superencrypt the middle layer and return it to our
+ * caller. */
+
+ /* Create inner descriptor layer */
+ layer2_str = get_inner_encrypted_layer_plaintext(desc);
+ if (!layer2_str) {
+ goto err;
+ }
+
+ /* Encrypt and b64 the inner layer */
+ layer2_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer2_str, 0);
+ if (!layer2_b64_ciphertext) {
+ goto err;
+ }
+
+ /* Now create middle descriptor layer given the inner layer */
+ layer1_str = get_outer_encrypted_layer_plaintext(desc,layer2_b64_ciphertext);
+ if (!layer1_str) {
+ goto err;
+ }
+
+ /* Encrypt and base64 the middle layer */
+ layer1_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer1_str, 1);
+ if (!layer1_b64_ciphertext) {
+ goto err;
+ }
+
+ /* Success! */
+ ret = 0;
+
+ err:
+ tor_free(layer1_str);
+ tor_free(layer2_str);
+ tor_free(layer2_b64_ciphertext);
+
+ *encrypted_blob_out = layer1_b64_ciphertext;
+ return ret;
+}
+
+/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the
+ * newly allocated string of the encoded descriptor. On error, -1 is returned
+ * and encoded_out is untouched. */
+static int
+desc_encode_v3(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp, char **encoded_out)
+{
+ int ret = -1;
+ char *encoded_str = NULL;
+ size_t encoded_len;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(signing_kp);
+ tor_assert(encoded_out);
+ tor_assert(desc->plaintext_data.version == 3);
+
+ if (BUG(desc->subcredential == NULL)) {
+ goto err;
+ }
+
+ /* Build the non-encrypted values. */
+ {
+ char *encoded_cert;
+ /* Encode certificate then create the first line of the descriptor. */
+ if (desc->plaintext_data.signing_key_cert->cert_type
+ != CERT_TYPE_SIGNING_HS_DESC) {
+ log_err(LD_BUG, "HS descriptor signing key has an unexpected cert type "
+ "(%d)", (int) desc->plaintext_data.signing_key_cert->cert_type);
+ goto err;
+ }
+ if (tor_cert_encode_ed22519(desc->plaintext_data.signing_key_cert,
+ &encoded_cert) < 0) {
+ /* The function will print error logs. */
+ goto err;
+ }
+ /* Create the hs descriptor line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_hs_desc,
+ desc->plaintext_data.version);
+ /* Add the descriptor lifetime line (in minutes). */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_lifetime,
+ desc->plaintext_data.lifetime_sec / 60);
+ /* Create the descriptor certificate line. */
+ smartlist_add_asprintf(lines, "%s\n%s", str_desc_cert, encoded_cert);
+ tor_free(encoded_cert);
+ /* Create the revision counter line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu64, str_rev_counter,
+ desc->plaintext_data.revision_counter);
+ }
+
+ /* Build the superencrypted data section. */
+ {
+ char *enc_b64_blob=NULL;
+ if (encode_superencrypted_data(desc, &enc_b64_blob) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines,
+ "%s\n"
+ "-----BEGIN MESSAGE-----\n"
+ "%s"
+ "-----END MESSAGE-----",
+ str_superencrypted, enc_b64_blob);
+ tor_free(enc_b64_blob);
+ }
+
+ /* Join all lines in one string so we can generate a signature and append
+ * it to the descriptor. */
+ encoded_str = smartlist_join_strings(lines, "\n", 1, &encoded_len);
+
+ /* Sign all fields of the descriptor with our short term signing key. */
+ {
+ ed25519_signature_t sig;
+ char ed_sig_b64[ED25519_SIG_BASE64_LEN + 1];
+ if (ed25519_sign_prefixed(&sig,
+ (const uint8_t *) encoded_str, encoded_len,
+ str_desc_sig_prefix, signing_kp) < 0) {
+ log_warn(LD_BUG, "Can't sign encoded HS descriptor!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) {
+ log_warn(LD_BUG, "Can't base64 encode descriptor signature!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ /* Create the signature line. */
+ smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64);
+ }
+ /* Free previous string that we used so compute the signature. */
+ tor_free(encoded_str);
+ encoded_str = smartlist_join_strings(lines, "\n", 1, NULL);
+ *encoded_out = encoded_str;
+
+ if (strlen(encoded_str) >= hs_cache_get_max_descriptor_size()) {
+ log_warn(LD_GENERAL, "We just made an HS descriptor that's too big (%d)."
+ "Failing.", (int)strlen(encoded_str));
+ tor_free(encoded_str);
+ goto err;
+ }
+
+ /* XXX: Trigger a control port event. */
+
+ /* Success! */
+ ret = 0;
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return ret;
+}
+
+/* === DECODING === */
+
+/* Given an encoded string of the link specifiers, return a newly allocated
+ * list of decoded link specifiers. Return NULL on error. */
+STATIC smartlist_t *
+decode_link_specifiers(const char *encoded)
+{
+ int decoded_len;
+ size_t encoded_len, i;
+ uint8_t *decoded;
+ smartlist_t *results = NULL;
+ link_specifier_list_t *specs = NULL;
+
+ tor_assert(encoded);
+
+ encoded_len = strlen(encoded);
+ decoded = tor_malloc(encoded_len);
+ decoded_len = base64_decode((char *) decoded, encoded_len, encoded,
+ encoded_len);
+ if (decoded_len < 0) {
+ goto err;
+ }
+
+ if (link_specifier_list_parse(&specs, decoded,
+ (size_t) decoded_len) < decoded_len) {
+ goto err;
+ }
+ tor_assert(specs);
+ results = smartlist_new();
+
+ for (i = 0; i < link_specifier_list_getlen_spec(specs); i++) {
+ hs_desc_link_specifier_t *hs_spec;
+ link_specifier_t *ls = link_specifier_list_get_spec(specs, i);
+ tor_assert(ls);
+
+ hs_spec = tor_malloc_zero(sizeof(*hs_spec));
+ hs_spec->type = link_specifier_get_ls_type(ls);
+ switch (hs_spec->type) {
+ case LS_IPV4:
+ tor_addr_from_ipv4h(&hs_spec->u.ap.addr,
+ link_specifier_get_un_ipv4_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv4_port(ls);
+ break;
+ case LS_IPV6:
+ tor_addr_from_ipv6_bytes(&hs_spec->u.ap.addr, (const char *)
+ link_specifier_getarray_un_ipv6_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv6_port(ls);
+ break;
+ case LS_LEGACY_ID:
+ /* Both are known at compile time so let's make sure they are the same
+ * else we can copy memory out of bound. */
+ tor_assert(link_specifier_getlen_un_legacy_id(ls) ==
+ sizeof(hs_spec->u.legacy_id));
+ memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls),
+ sizeof(hs_spec->u.legacy_id));
+ break;
+ case LS_ED25519_ID:
+ /* Both are known at compile time so let's make sure they are the same
+ * else we can copy memory out of bound. */
+ tor_assert(link_specifier_getlen_un_ed25519_id(ls) ==
+ sizeof(hs_spec->u.ed25519_id));
+ memcpy(hs_spec->u.ed25519_id,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ sizeof(hs_spec->u.ed25519_id));
+ break;
+ default:
+ goto err;
+ }
+
+ smartlist_add(results, hs_spec);
+ }
+
+ goto done;
+ err:
+ if (results) {
+ SMARTLIST_FOREACH(results, hs_desc_link_specifier_t *, s, tor_free(s));
+ smartlist_free(results);
+ results = NULL;
+ }
+ done:
+ link_specifier_list_free(specs);
+ tor_free(decoded);
+ return results;
+}
+
+/* Given a list of authentication types, decode it and put it in the encrypted
+ * data section. Return 1 if we at least know one of the type or 0 if we know
+ * none of them. */
+static int
+decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ int match = 0;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ desc->intro_auth_types = smartlist_new();
+ smartlist_split_string(desc->intro_auth_types, list, " ", 0, 0);
+
+ /* Validate the types that we at least know about one. */
+ SMARTLIST_FOREACH_BEGIN(desc->intro_auth_types, const char *, auth) {
+ for (int idx = 0; intro_auth_types[idx].identifier; idx++) {
+ if (!strncmp(auth, intro_auth_types[idx].identifier,
+ strlen(intro_auth_types[idx].identifier))) {
+ match = 1;
+ break;
+ }
+ }
+ } SMARTLIST_FOREACH_END(auth);
+
+ return match;
+}
+
+/* Parse a space-delimited list of integers representing CREATE2 formats into
+ * the bitfield in hs_desc_encrypted_data_t. Ignore unrecognized values. */
+static void
+decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ smartlist_t *tokens;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ tokens = smartlist_new();
+ smartlist_split_string(tokens, list, " ", 0, 0);
+
+ SMARTLIST_FOREACH_BEGIN(tokens, char *, s) {
+ int ok;
+ unsigned long type = tor_parse_ulong(s, 10, 1, UINT16_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Unparseable value %s in create2 list", escaped(s));
+ continue;
+ }
+ switch (type) {
+ case ONION_HANDSHAKE_TYPE_NTOR:
+ desc->create2_ntor = 1;
+ break;
+ default:
+ /* We deliberately ignore unsupported handshake types */
+ continue;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ SMARTLIST_FOREACH(tokens, char *, s, tor_free(s));
+ smartlist_free(tokens);
+}
+
+/* Given a certificate, validate the certificate for certain conditions which
+ * are if the given type matches the cert's one, if the signing key is
+ * included and if the that key was actually used to sign the certificate.
+ *
+ * Return 1 iff if all conditions pass or 0 if one of them fails. */
+STATIC int
+cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type)
+{
+ tor_assert(log_obj_type);
+
+ if (cert == NULL) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", log_obj_type);
+ goto err;
+ }
+ if (cert->cert_type != type) {
+ log_warn(LD_REND, "Invalid cert type %02x for %s.", cert->cert_type,
+ log_obj_type);
+ goto err;
+ }
+ /* All certificate must have its signing key included. */
+ if (!cert->signing_key_included) {
+ log_warn(LD_REND, "Signing key is NOT included for %s.", log_obj_type);
+ goto err;
+ }
+ /* The following will not only check if the signature matches but also the
+ * expiration date and overall validity. */
+ if (tor_cert_checksig(cert, &cert->signing_key, time(NULL)) < 0) {
+ log_warn(LD_REND, "Invalid signature for %s.", log_obj_type);
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/* Given some binary data, try to parse it to get a certificate object. If we
+ * have a valid cert, validate it using the given wanted type. On error, print
+ * a log using the err_msg has the certificate identifier adding semantic to
+ * the log and cert_out is set to NULL. On success, 0 is returned and cert_out
+ * points to a newly allocated certificate object. */
+static int
+cert_parse_and_validate(tor_cert_t **cert_out, const char *data,
+ size_t data_len, unsigned int cert_type_wanted,
+ const char *err_msg)
+{
+ tor_cert_t *cert;
+
+ tor_assert(cert_out);
+ tor_assert(data);
+ tor_assert(err_msg);
+
+ /* Parse certificate. */
+ cert = tor_cert_parse((const uint8_t *) data, data_len);
+ if (!cert) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", err_msg);
+ goto err;
+ }
+
+ /* Validate certificate. */
+ if (!cert_is_valid(cert, cert_type_wanted, err_msg)) {
+ goto err;
+ }
+
+ *cert_out = cert;
+ return 0;
+
+ err:
+ tor_cert_free(cert);
+ *cert_out = NULL;
+ return -1;
+}
+
+/* Return true iff the given length of the encrypted data of a descriptor
+ * passes validation. */
+STATIC int
+encrypted_data_length_is_valid(size_t len)
+{
+ /* Make sure there is enough data for the salt and the mac. The equality is
+ there to ensure that there is at least one byte of encrypted data. */
+ if (len <= HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN) {
+ log_warn(LD_REND, "Length of descriptor's encrypted data is too small. "
+ "Got %lu but minimum value is %d",
+ (unsigned long)len, HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/** Decrypt an encrypted descriptor layer at <b>encrypted_blob</b> of size
+ * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to
+ * generate the right decryption keys; set <b>decrypted_out</b> to the
+ * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter
+ * encrypted layer of the descriptor. */
+static size_t
+decrypt_desc_layer(const hs_descriptor_t *desc,
+ const uint8_t *encrypted_blob,
+ size_t encrypted_blob_size,
+ int is_superencrypted_layer,
+ char **decrypted_out)
+{
+ uint8_t *decrypted = NULL;
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN];
+ const uint8_t *salt, *encrypted, *desc_mac;
+ size_t encrypted_len, result_len = 0;
+
+ tor_assert(decrypted_out);
+ tor_assert(desc);
+ tor_assert(encrypted_blob);
+
+ /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC .
+ * Make sure we have enough space for all these things. */
+ if (!encrypted_data_length_is_valid(encrypted_blob_size)) {
+ goto err;
+ }
+
+ /* Start of the blob thus the salt. */
+ salt = encrypted_blob;
+
+ /* Next is the encrypted data. */
+ encrypted = encrypted_blob + HS_DESC_ENCRYPTED_SALT_LEN;
+ encrypted_len = encrypted_blob_size -
+ (HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+ tor_assert(encrypted_len > 0); /* guaranteed by the check above */
+
+ /* And last comes the MAC. */
+ desc_mac = encrypted_blob + encrypted_blob_size - DIGEST256_LEN;
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the decryption. */
+ build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key),
+ is_superencrypted_layer);
+
+ /* Build MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ encrypted, encrypted_len, our_mac, sizeof(our_mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+ /* Verify MAC; MAC is H(mac_key || salt || encrypted)
+ *
+ * This is a critical check that is making sure the computed MAC matches the
+ * one in the descriptor. */
+ if (!tor_memeq(our_mac, desc_mac, sizeof(our_mac))) {
+ log_warn(LD_REND, "Encrypted service descriptor MAC check failed");
+ goto err;
+ }
+
+ {
+ /* Decrypt. Here we are assured that the encrypted length is valid for
+ * decryption. */
+ crypto_cipher_t *cipher;
+
+ cipher = crypto_cipher_new_with_iv_and_bits(secret_key, secret_iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* Extra byte for the NUL terminated byte. */
+ decrypted = tor_malloc_zero(encrypted_len + 1);
+ crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted, encrypted_len);
+ crypto_cipher_free(cipher);
+ }
+
+ {
+ /* Adjust length to remove NUL padding bytes */
+ uint8_t *end = memchr(decrypted, 0, encrypted_len);
+ result_len = encrypted_len;
+ if (end) {
+ result_len = end - decrypted;
+ }
+ }
+
+ /* Make sure to NUL terminate the string. */
+ decrypted[encrypted_len] = '\0';
+ *decrypted_out = (char *) decrypted;
+ goto done;
+
+ err:
+ if (decrypted) {
+ tor_free(decrypted);
+ }
+ *decrypted_out = NULL;
+ result_len = 0;
+
+ done:
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ return result_len;
+}
+
+/* Basic validation that the superencrypted client auth portion of the
+ * descriptor is well-formed and recognized. Return True if so, otherwise
+ * return False. */
+static int
+superencrypted_auth_data_is_valid(smartlist_t *tokens)
+{
+ /* XXX: This is just basic validation for now. When we implement client auth,
+ we can refactor this function so that it actually parses and saves the
+ data. */
+
+ { /* verify desc auth type */
+ const directory_token_t *tok;
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE);
+ tor_assert(tok->n_args >= 1);
+ if (strcmp(tok->args[0], "x25519")) {
+ log_warn(LD_DIR, "Unrecognized desc auth type");
+ return 0;
+ }
+ }
+
+ { /* verify desc auth key */
+ const directory_token_t *tok;
+ curve25519_public_key_t k;
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY);
+ tor_assert(tok->n_args >= 1);
+ if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
+ log_warn(LD_DIR, "Bogus desc auth key in HS desc");
+ return 0;
+ }
+ }
+
+ /* verify desc auth client items */
+ SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, tok) {
+ if (tok->tp == R3_DESC_AUTH_CLIENT) {
+ tor_assert(tok->n_args >= 3);
+ }
+ } SMARTLIST_FOREACH_END(tok);
+
+ return 1;
+}
+
+/* Parse <b>message</b>, the plaintext of the superencrypted portion of an HS
+ * descriptor. Set <b>encrypted_out</b> to the encrypted blob, and return its
+ * size */
+STATIC size_t
+decode_superencrypted(const char *message, size_t message_len,
+ uint8_t **encrypted_out)
+{
+ int retval = 0;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len, tokens,
+ hs_desc_superencrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Superencrypted portion is not parseable");
+ goto err;
+ }
+
+ /* Do some rudimentary validation of the authentication data */
+ if (!superencrypted_auth_data_is_valid(tokens)) {
+ log_warn(LD_REND, "Invalid auth data");
+ goto err;
+ }
+
+ /* Extract the encrypted data section. */
+ {
+ const directory_token_t *tok;
+ tok = find_by_keyword(tokens, R3_ENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Desc superencrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ tor_assert(tok->object_size <= INT_MAX);
+ *encrypted_out = tor_memdup(tok->object_body, tok->object_size);
+ retval = (int) tok->object_size;
+ }
+
+ err:
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ if (area) {
+ memarea_drop_all(area);
+ }
+
+ return retval;
+}
+
+/* Decrypt both the superencrypted and the encrypted section of the descriptor
+ * using the given descriptor object <b>desc</b>. A newly allocated NUL
+ * terminated string is put in decrypted_out which contains the inner encrypted
+ * layer of the descriptor. Return the length of decrypted_out on success else
+ * 0 is returned and decrypted_out is set to NULL. */
+static size_t
+desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out)
+{
+ size_t decrypted_len = 0;
+ size_t encrypted_len = 0;
+ size_t superencrypted_len = 0;
+ char *superencrypted_plaintext = NULL;
+ uint8_t *encrypted_blob = NULL;
+
+ /** Function logic: This function takes us from the descriptor header to the
+ * inner encrypted layer, by decrypting and decoding the middle descriptor
+ * layer. In the end we return the contents of the inner encrypted layer to
+ * our caller. */
+
+ /* 1. Decrypt middle layer of descriptor */
+ superencrypted_len = decrypt_desc_layer(desc,
+ desc->plaintext_data.superencrypted_blob,
+ desc->plaintext_data.superencrypted_blob_size,
+ 1,
+ &superencrypted_plaintext);
+ if (!superencrypted_len) {
+ log_warn(LD_REND, "Decrypting superencrypted desc failed.");
+ goto err;
+ }
+ tor_assert(superencrypted_plaintext);
+
+ /* 2. Parse "superencrypted" */
+ encrypted_len = decode_superencrypted(superencrypted_plaintext,
+ superencrypted_len,
+ &encrypted_blob);
+ if (!encrypted_len) {
+ log_warn(LD_REND, "Decrypting encrypted desc failed.");
+ goto err;
+ }
+ tor_assert(encrypted_blob);
+
+ /* 3. Decrypt "encrypted" and set decrypted_out */
+ char *decrypted_desc;
+ decrypted_len = decrypt_desc_layer(desc,
+ encrypted_blob, encrypted_len,
+ 0, &decrypted_desc);
+ if (!decrypted_len) {
+ log_warn(LD_REND, "Decrypting encrypted desc failed.");
+ goto err;
+ }
+ tor_assert(decrypted_desc);
+
+ *decrypted_out = decrypted_desc;
+
+ err:
+ tor_free(superencrypted_plaintext);
+ tor_free(encrypted_blob);
+
+ return decrypted_len;
+}
+
+/* Given the token tok for an intro point legacy key, the list of tokens, the
+ * introduction point ip being decoded and the descriptor desc from which it
+ * comes from, decode the legacy key and set the intro point object. Return 0
+ * on success else -1 on failure. */
+static int
+decode_intro_legacy_key(const directory_token_t *tok,
+ smartlist_t *tokens,
+ hs_desc_intro_point_t *ip,
+ const hs_descriptor_t *desc)
+{
+ tor_assert(tok);
+ tor_assert(tokens);
+ tor_assert(ip);
+ tor_assert(desc);
+
+ if (!crypto_pk_public_exponent_ok(tok->key)) {
+ log_warn(LD_REND, "Introduction point legacy key is invalid");
+ goto err;
+ }
+ ip->legacy.key = crypto_pk_dup_key(tok->key);
+ /* Extract the legacy cross certification cert which MUST be present if we
+ * have a legacy key. */
+ tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY_CERT);
+ if (!tok) {
+ log_warn(LD_REND, "Introduction point legacy key cert is missing");
+ goto err;
+ }
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "CROSSCERT")) {
+ /* Info level because this might be an unknown field that we should
+ * ignore. */
+ log_info(LD_REND, "Introduction point legacy encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ /* Keep a copy of the certificate. */
+ ip->legacy.cert.encoded = tor_memdup(tok->object_body, tok->object_size);
+ ip->legacy.cert.len = tok->object_size;
+ /* The check on the expiration date is for the entire lifetime of a
+ * certificate which is 24 hours. However, a descriptor has a maximum
+ * lifetime of 12 hours meaning we have a 12h difference between the two
+ * which ultimately accomodate the clock skewed client. */
+ if (rsa_ed25519_crosscert_check(ip->legacy.cert.encoded,
+ ip->legacy.cert.len, ip->legacy.key,
+ &desc->plaintext_data.signing_pubkey,
+ approx_time() - HS_DESC_CERT_LIFETIME)) {
+ log_warn(LD_REND, "Unable to check cross-certification on the "
+ "introduction point legacy encryption key.");
+ ip->cross_certified = 0;
+ goto err;
+ }
+
+ /* Success. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Dig into the descriptor <b>tokens</b> to find the onion key we should use
+ * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it
+ * was found and well-formed, otherwise return -1 in case of errors. */
+static int
+set_intro_point_onion_key(curve25519_public_key_t *onion_key_out,
+ const smartlist_t *tokens)
+{
+ int retval = -1;
+ smartlist_t *onion_keys = NULL;
+
+ tor_assert(onion_key_out);
+
+ onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY);
+ if (!onion_keys) {
+ log_warn(LD_REND, "Descriptor did not contain intro onion keys");
+ goto err;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) {
+ /* This field is using GE(2) so for possible forward compatibility, we
+ * accept more fields but must be at least 2. */
+ tor_assert(tok->n_args >= 2);
+
+ /* Try to find an ntor key, it's the only recognized type right now */
+ if (!strcmp(tok->args[0], "ntor")) {
+ if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor onion-key is invalid");
+ goto err;
+ }
+ /* Got the onion key! Set the appropriate retval */
+ retval = 0;
+ }
+ } SMARTLIST_FOREACH_END(tok);
+
+ /* Log an error if we didn't find it :( */
+ if (retval < 0) {
+ log_warn(LD_REND, "Descriptor did not contain ntor onion keys");
+ }
+
+ err:
+ smartlist_free(onion_keys);
+ return retval;
+}
+
+/* Given the start of a section and the end of it, decode a single
+ * introduction point from that section. Return a newly allocated introduction
+ * point object containing the decoded data. Return NULL if the section can't
+ * be decoded. */
+STATIC hs_desc_intro_point_t *
+decode_introduction_point(const hs_descriptor_t *desc, const char *start)
+{
+ hs_desc_intro_point_t *ip = NULL;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ const directory_token_t *tok;
+
+ tor_assert(desc);
+ tor_assert(start);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, start, start + strlen(start),
+ tokens, hs_desc_intro_point_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Introduction point is not parseable");
+ goto err;
+ }
+
+ /* Ok we seem to have a well formed section containing enough tokens to
+ * parse. Allocate our IP object and try to populate it. */
+ ip = hs_desc_intro_point_new();
+
+ /* "introduction-point" SP link-specifiers NL */
+ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT);
+ tor_assert(tok->n_args == 1);
+ /* Our constructor creates this list by default so free it. */
+ smartlist_free(ip->link_specifiers);
+ ip->link_specifiers = decode_link_specifiers(tok->args[0]);
+ if (!ip->link_specifiers) {
+ log_warn(LD_REND, "Introduction point has invalid link specifiers");
+ goto err;
+ }
+
+ /* "onion-key" SP ntor SP key NL */
+ if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) {
+ goto err;
+ }
+
+ /* "auth-key" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Unexpected object type for introduction auth key");
+ goto err;
+ }
+ /* Parse cert and do some validation. */
+ if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY,
+ "introduction point auth-key") < 0) {
+ goto err;
+ }
+ /* Validate authentication certificate with descriptor signing key. */
+ if (tor_cert_checksig(ip->auth_key_cert,
+ &desc->plaintext_data.signing_pubkey, 0) < 0) {
+ log_warn(LD_REND, "Invalid authentication key signature");
+ goto err;
+ }
+
+ /* Exactly one "enc-key" SP "ntor" SP key NL */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY);
+ if (!strcmp(tok->args[0], "ntor")) {
+ /* This field is using GE(2) so for possible forward compatibility, we
+ * accept more fields but must be at least 2. */
+ tor_assert(tok->n_args >= 2);
+
+ if (curve25519_public_from_base64(&ip->enc_key, tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor enc-key is invalid");
+ goto err;
+ }
+ } else {
+ /* Unknown key type so we can't use that introduction point. */
+ log_warn(LD_REND, "Introduction point encryption key is unrecognized.");
+ goto err;
+ }
+
+ /* Exactly once "enc-key-cert" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERT);
+ tor_assert(tok->object_body);
+ /* Do the cross certification. */
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Introduction point ntor encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ if (cert_parse_and_validate(&ip->enc_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS,
+ "introduction point enc-key-cert") < 0) {
+ goto err;
+ }
+ if (tor_cert_checksig(ip->enc_key_cert,
+ &desc->plaintext_data.signing_pubkey, 0) < 0) {
+ log_warn(LD_REND, "Invalid encryption key signature");
+ goto err;
+ }
+ /* It is successfully cross certified. Flag the object. */
+ ip->cross_certified = 1;
+
+ /* Do we have a "legacy-key" SP key NL ?*/
+ tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY);
+ if (tok) {
+ if (decode_intro_legacy_key(tok, tokens, ip, desc) < 0) {
+ goto err;
+ }
+ }
+
+ /* Introduction point has been parsed successfully. */
+ goto done;
+
+ err:
+ hs_desc_intro_point_free(ip);
+ ip = NULL;
+
+ done:
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ if (area) {
+ memarea_drop_all(area);
+ }
+
+ return ip;
+}
+
+/* Given a descriptor string at <b>data</b>, decode all possible introduction
+ * points that we can find. Add the introduction point object to desc_enc as we
+ * find them. This function can't fail and it is possible that zero
+ * introduction points can be decoded. */
+static void
+decode_intro_points(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_enc,
+ const char *data)
+{
+ smartlist_t *chunked_desc = smartlist_new();
+ smartlist_t *intro_points = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(desc_enc);
+ tor_assert(data);
+ tor_assert(desc_enc->intro_points);
+
+ /* Take the desc string, and extract the intro point substrings out of it */
+ {
+ /* Split the descriptor string using the intro point header as delimiter */
+ smartlist_split_string(chunked_desc, data, str_intro_point_start, 0, 0);
+
+ /* Check if there are actually any intro points included. The first chunk
+ * should be other descriptor fields (e.g. create2-formats), so it's not an
+ * intro point. */
+ if (smartlist_len(chunked_desc) < 2) {
+ goto done;
+ }
+ }
+
+ /* Take the intro point substrings, and prepare them for parsing */
+ {
+ int i = 0;
+ /* Prepend the introduction-point header to all the chunks, since
+ smartlist_split_string() devoured it. */
+ SMARTLIST_FOREACH_BEGIN(chunked_desc, char *, chunk) {
+ /* Ignore first chunk. It's other descriptor fields. */
+ if (i++ == 0) {
+ continue;
+ }
+
+ smartlist_add_asprintf(intro_points, "%s %s", str_intro_point, chunk);
+ } SMARTLIST_FOREACH_END(chunk);
+ }
+
+ /* Parse the intro points! */
+ SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) {
+ hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point);
+ if (!ip) {
+ /* Malformed introduction point section. We'll ignore this introduction
+ * point and continue parsing. New or unknown fields are possible for
+ * forward compatibility. */
+ continue;
+ }
+ smartlist_add(desc_enc->intro_points, ip);
+ } SMARTLIST_FOREACH_END(intro_point);
+
+ done:
+ SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a));
+ smartlist_free(chunked_desc);
+ SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a));
+ smartlist_free(intro_points);
+}
+/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded
+ * descriptor in encoded_desc validates the descriptor content. */
+STATIC int
+desc_sig_is_valid(const char *b64_sig,
+ const ed25519_public_key_t *signing_pubkey,
+ const char *encoded_desc, size_t encoded_len)
+{
+ int ret = 0;
+ ed25519_signature_t sig;
+ const char *sig_start;
+
+ tor_assert(b64_sig);
+ tor_assert(signing_pubkey);
+ tor_assert(encoded_desc);
+ /* Verifying nothing won't end well :). */
+ tor_assert(encoded_len > 0);
+
+ /* Signature length check. */
+ if (strlen(b64_sig) != ED25519_SIG_BASE64_LEN) {
+ log_warn(LD_REND, "Service descriptor has an invalid signature length."
+ "Exptected %d but got %lu",
+ ED25519_SIG_BASE64_LEN, (unsigned long) strlen(b64_sig));
+ goto err;
+ }
+
+ /* First, convert base64 blob to an ed25519 signature. */
+ if (ed25519_signature_from_base64(&sig, b64_sig) != 0) {
+ log_warn(LD_REND, "Service descriptor does not contain a valid "
+ "signature");
+ goto err;
+ }
+
+ /* Find the start of signature. */
+ sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature);
+ /* Getting here means the token parsing worked for the signature so if we
+ * can't find the start of the signature, we have a code flow issue. */
+ if (BUG(!sig_start)) {
+ goto err;
+ }
+ /* Skip newline, it has to go in the signature check. */
+ sig_start++;
+
+ /* Validate signature with the full body of the descriptor. */
+ if (ed25519_checksig_prefixed(&sig,
+ (const uint8_t *) encoded_desc,
+ sig_start - encoded_desc,
+ str_desc_sig_prefix,
+ signing_pubkey) != 0) {
+ log_warn(LD_REND, "Invalid signature on service descriptor");
+ goto err;
+ }
+ /* Valid signature! All is good. */
+ ret = 1;
+
+ err:
+ return ret;
+}
+
+/* Decode descriptor plaintext data for version 3. Given a list of tokens, an
+ * allocated plaintext object that will be populated and the encoded
+ * descriptor with its length. The last one is needed for signature
+ * verification. Unknown tokens are simply ignored so this won't error on
+ * unknowns but requires that all v3 token be present and valid.
+ *
+ * Return 0 on success else a negative value. */
+static int
+desc_decode_plaintext_v3(smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc, size_t encoded_len)
+{
+ int ok;
+ directory_token_t *tok;
+
+ tor_assert(tokens);
+ tor_assert(desc);
+ /* Version higher could still use this function to decode most of the
+ * descriptor and then they decode the extra part. */
+ tor_assert(desc->version >= 3);
+
+ /* Descriptor lifetime parsing. */
+ tok = find_by_keyword(tokens, R3_DESC_LIFETIME);
+ tor_assert(tok->n_args == 1);
+ desc->lifetime_sec = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor lifetime value is invalid");
+ goto err;
+ }
+ /* Put it from minute to second. */
+ desc->lifetime_sec *= 60;
+ if (desc->lifetime_sec > HS_DESC_MAX_LIFETIME) {
+ log_warn(LD_REND, "Service descriptor lifetime is too big. "
+ "Got %" PRIu32 " but max is %d",
+ desc->lifetime_sec, HS_DESC_MAX_LIFETIME);
+ goto err;
+ }
+
+ /* Descriptor signing certificate. */
+ tok = find_by_keyword(tokens, R3_DESC_SIGNING_CERT);
+ tor_assert(tok->object_body);
+ /* Expecting a prop220 cert with the signing key extension, which contains
+ * the blinded public key. */
+ if (strcmp(tok->object_type, "ED25519 CERT") != 0) {
+ log_warn(LD_REND, "Service descriptor signing cert wrong type (%s)",
+ escaped(tok->object_type));
+ goto err;
+ }
+ if (cert_parse_and_validate(&desc->signing_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_SIGNING_HS_DESC,
+ "service descriptor signing key") < 0) {
+ goto err;
+ }
+
+ /* Copy the public keys into signing_pubkey and blinded_pubkey */
+ memcpy(&desc->signing_pubkey, &desc->signing_key_cert->signed_key,
+ sizeof(ed25519_public_key_t));
+ memcpy(&desc->blinded_pubkey, &desc->signing_key_cert->signing_key,
+ sizeof(ed25519_public_key_t));
+
+ /* Extract revision counter value. */
+ tok = find_by_keyword(tokens, R3_REVISION_COUNTER);
+ tor_assert(tok->n_args == 1);
+ desc->revision_counter = tor_parse_uint64(tok->args[0], 10, 0,
+ UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor revision-counter is invalid");
+ goto err;
+ }
+
+ /* Extract the encrypted data section. */
+ tok = find_by_keyword(tokens, R3_SUPERENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Service descriptor encrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ desc->superencrypted_blob = tor_memdup(tok->object_body, tok->object_size);
+ desc->superencrypted_blob_size = tok->object_size;
+
+ /* Extract signature and verify it. */
+ tok = find_by_keyword(tokens, R3_SIGNATURE);
+ tor_assert(tok->n_args == 1);
+ /* First arg here is the actual encoded signature. */
+ if (!desc_sig_is_valid(tok->args[0], &desc->signing_pubkey,
+ encoded_desc, encoded_len)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Decode the version 3 encrypted section of the given descriptor desc. The
+ * desc_encrypted_out will be populated with the decoded data. Return 0 on
+ * success else -1. */
+static int
+desc_decode_encrypted_v3(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted_out)
+{
+ int result = -1;
+ char *message = NULL;
+ size_t message_len;
+ memarea_t *area = NULL;
+ directory_token_t *tok;
+ smartlist_t *tokens = NULL;
+
+ tor_assert(desc);
+ tor_assert(desc_encrypted_out);
+
+ /* Decrypt the superencrypted data that is located in the plaintext section
+ * in the descriptor as a blob of bytes. */
+ message_len = desc_decrypt_all(desc, &message);
+ if (!message_len) {
+ log_warn(LD_REND, "Service descriptor decryption failed.");
+ goto err;
+ }
+ tor_assert(message);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len,
+ tokens, hs_desc_encrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Encrypted service descriptor is not parseable.");
+ goto err;
+ }
+
+ /* CREATE2 supported cell format. It's mandatory. */
+ tok = find_by_keyword(tokens, R3_CREATE2_FORMATS);
+ tor_assert(tok);
+ decode_create2_list(desc_encrypted_out, tok->args[0]);
+ /* Must support ntor according to the specification */
+ if (!desc_encrypted_out->create2_ntor) {
+ log_warn(LD_REND, "Service create2-formats does not include ntor.");
+ goto err;
+ }
+
+ /* Authentication type. It's optional but only once. */
+ tok = find_opt_by_keyword(tokens, R3_INTRO_AUTH_REQUIRED);
+ if (tok) {
+ if (!decode_auth_type(desc_encrypted_out, tok->args[0])) {
+ log_warn(LD_REND, "Service descriptor authentication type has "
+ "invalid entry(ies).");
+ goto err;
+ }
+ }
+
+ /* Is this service a single onion service? */
+ tok = find_opt_by_keyword(tokens, R3_SINGLE_ONION_SERVICE);
+ if (tok) {
+ desc_encrypted_out->single_onion_service = 1;
+ }
+
+ /* Initialize the descriptor's introduction point list before we start
+ * decoding. Having 0 intro point is valid. Then decode them all. */
+ desc_encrypted_out->intro_points = smartlist_new();
+ decode_intro_points(desc, desc_encrypted_out, message);
+
+ /* Validation of maximum introduction points allowed. */
+ if (smartlist_len(desc_encrypted_out->intro_points) >
+ HS_CONFIG_V3_MAX_INTRO_POINTS) {
+ log_warn(LD_REND, "Service descriptor contains too many introduction "
+ "points. Maximum allowed is %d but we have %d",
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ smartlist_len(desc_encrypted_out->intro_points));
+ goto err;
+ }
+
+ /* NOTE: Unknown fields are allowed because this function could be used to
+ * decode other descriptor version. */
+
+ result = 0;
+ goto done;
+
+ err:
+ tor_assert(result < 0);
+ desc_encrypted_data_free_contents(desc_encrypted_out);
+
+ done:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ if (message) {
+ tor_free(message);
+ }
+ return result;
+}
+
+/* Table of encrypted decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_encrypted_handlers[])(
+ const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_encrypted_v3,
+};
+
+/* Decode the encrypted data section of the given descriptor and store the
+ * data in the given encrypted data object. Return 0 on success else a
+ * negative value on error. */
+int
+hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted)
+{
+ int ret;
+ uint32_t version;
+
+ tor_assert(desc);
+ /* Ease our life a bit. */
+ version = desc->plaintext_data.version;
+ tor_assert(desc_encrypted);
+ /* Calling this function without an encrypted blob to parse is a code flow
+ * error. The plaintext parsing should never succeed in the first place
+ * without an encrypted section. */
+ tor_assert(desc->plaintext_data.superencrypted_blob);
+ /* Let's make sure we have a supported version as well. By correctly parsing
+ * the plaintext, this should not fail. */
+ if (BUG(!hs_desc_is_supported_version(version))) {
+ ret = -1;
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_encrypted_handlers) >= version);
+ tor_assert(decode_encrypted_handlers[version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_encrypted_handlers[version](desc, desc_encrypted);
+ if (ret < 0) {
+ goto err;
+ }
+
+ err:
+ return ret;
+}
+
+/* Table of plaintext decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_plaintext_handlers[])(
+ smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc,
+ size_t encoded_len) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_plaintext_v3,
+};
+
+/* Fully decode the given descriptor plaintext and store the data in the
+ * plaintext data object. Returns 0 on success else a negative value. */
+int
+hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext)
+{
+ int ok = 0, ret = -1;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ size_t encoded_len;
+ directory_token_t *tok;
+
+ tor_assert(encoded);
+ tor_assert(plaintext);
+
+ /* Check that descriptor is within size limits. */
+ encoded_len = strlen(encoded);
+ if (encoded_len >= hs_cache_get_max_descriptor_size()) {
+ log_warn(LD_REND, "Service descriptor is too big (%lu bytes)",
+ (unsigned long) encoded_len);
+ goto err;
+ }
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ /* Tokenize the descriptor so we can start to parse it. */
+ if (tokenize_string(area, encoded, encoded + encoded_len, tokens,
+ hs_desc_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Service descriptor is not parseable");
+ goto err;
+ }
+
+ /* Get the version of the descriptor which is the first mandatory field of
+ * the descriptor. From there, we'll decode the right descriptor version. */
+ tok = find_by_keyword(tokens, R_HS_DESCRIPTOR);
+ tor_assert(tok->n_args == 1);
+ plaintext->version = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor has unparseable version %s",
+ escaped(tok->args[0]));
+ goto err;
+ }
+ if (!hs_desc_is_supported_version(plaintext->version)) {
+ log_warn(LD_REND, "Service descriptor has unsupported version %" PRIu32,
+ plaintext->version);
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_plaintext_handlers) >= plaintext->version);
+ tor_assert(decode_plaintext_handlers[plaintext->version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_plaintext_handlers[plaintext->version](tokens, plaintext,
+ encoded, encoded_len);
+ if (ret < 0) {
+ goto err;
+ }
+ /* Success. Descriptor has been populated with the data. */
+ ret = 0;
+
+ err:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ return ret;
+}
+
+/* Fully decode an encoded descriptor and set a newly allocated descriptor
+ * object in desc_out. Subcredentials are used if not NULL else it's ignored.
+ *
+ * Return 0 on success. A negative value is returned on error and desc_out is
+ * set to NULL. */
+int
+hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out)
+{
+ int ret = -1;
+ hs_descriptor_t *desc;
+
+ tor_assert(encoded);
+
+ desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+
+ /* Subcredentials are optional. */
+ if (BUG(!subcredential)) {
+ log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!");
+ goto err;
+ }
+
+ memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+
+ ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ if (desc_out) {
+ *desc_out = desc;
+ } else {
+ hs_descriptor_free(desc);
+ }
+ return ret;
+
+ err:
+ hs_descriptor_free(desc);
+ if (desc_out) {
+ *desc_out = NULL;
+ }
+
+ tor_assert(ret < 0);
+ return ret;
+}
+
+/* Table of encode function version specific. The functions are indexed by the
+ * version number so v3 callback is at index 3 in the array. */
+static int
+ (*encode_handlers[])(
+ const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_encode_v3,
+};
+
+/* Encode the given descriptor desc including signing with the given key pair
+ * signing_kp. On success, encoded_out points to a newly allocated NUL
+ * terminated string that contains the encoded descriptor as a string.
+ *
+ * Return 0 on success and encoded_out is a valid pointer. On error, -1 is
+ * returned and encoded_out is set to NULL. */
+int
+hs_desc_encode_descriptor(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out)
+{
+ int ret = -1;
+ uint32_t version;
+
+ tor_assert(desc);
+ tor_assert(encoded_out);
+
+ /* Make sure we support the version of the descriptor format. */
+ version = desc->plaintext_data.version;
+ if (!hs_desc_is_supported_version(version)) {
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(encode_handlers) >= version);
+ tor_assert(encode_handlers[version]);
+
+ ret = encode_handlers[version](desc, signing_kp, encoded_out);
+ if (ret < 0) {
+ goto err;
+ }
+
+ /* Try to decode what we just encoded. Symmetry is nice! */
+ ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL);
+ if (BUG(ret < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ *encoded_out = NULL;
+ return ret;
+}
+
+/* Free the descriptor plaintext data object. */
+void
+hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc)
+{
+ desc_plaintext_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the descriptor encrypted data object. */
+void
+hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc)
+{
+ desc_encrypted_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the given descriptor object. */
+void
+hs_descriptor_free(hs_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ desc_plaintext_data_free_contents(&desc->plaintext_data);
+ desc_encrypted_data_free_contents(&desc->encrypted_data);
+ tor_free(desc);
+}
+
+/* Return the size in bytes of the given plaintext data object. A sizeof() is
+ * not enough because the object contains pointers and the encrypted blob.
+ * This is particularly useful for our OOM subsystem that tracks the HSDir
+ * cache size for instance. */
+size_t
+hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
+{
+ tor_assert(data);
+ return (sizeof(*data) + sizeof(*data->signing_key_cert) +
+ data->superencrypted_blob_size);
+}
+
+/* Return a newly allocated descriptor intro point. */
+hs_desc_intro_point_t *
+hs_desc_intro_point_new(void)
+{
+ hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
+ ip->link_specifiers = smartlist_new();
+ return ip;
+}
+
+/* Free a descriptor intro point object. */
+void
+hs_desc_intro_point_free(hs_desc_intro_point_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ if (ip->link_specifiers) {
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
+ ls, hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ }
+ tor_cert_free(ip->auth_key_cert);
+ tor_cert_free(ip->enc_key_cert);
+ crypto_pk_free(ip->legacy.key);
+ tor_free(ip->legacy.cert.encoded);
+ tor_free(ip);
+}
+
+/* Free the given descriptor link specifier. */
+void
+hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls)
+{
+ if (ls == NULL) {
+ return;
+ }
+ tor_free(ls);
+}
+
+/* Return a newly allocated descriptor link specifier using the given extend
+ * info and requested type. Return NULL on error. */
+hs_desc_link_specifier_t *
+hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type)
+{
+ hs_desc_link_specifier_t *ls = NULL;
+
+ tor_assert(info);
+
+ ls = tor_malloc_zero(sizeof(*ls));
+ ls->type = type;
+ switch (ls->type) {
+ case LS_IPV4:
+ if (info->addr.family != AF_INET) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_IPV6:
+ if (info->addr.family != AF_INET6) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_LEGACY_ID:
+ /* Bug out if the identity digest is not set */
+ if (BUG(tor_mem_is_zero(info->identity_digest,
+ sizeof(info->identity_digest)))) {
+ goto err;
+ }
+ memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id));
+ break;
+ case LS_ED25519_ID:
+ /* ed25519 keys are optional for intro points */
+ if (ed25519_public_key_is_zero(&info->ed_identity)) {
+ goto err;
+ }
+ memcpy(ls->u.ed25519_id, info->ed_identity.pubkey,
+ sizeof(ls->u.ed25519_id));
+ break;
+ default:
+ /* Unknown type is code flow error. */
+ tor_assert(0);
+ }
+
+ return ls;
+ err:
+ tor_free(ls);
+ return NULL;
+}
+
+/* From the given descriptor, remove and free every introduction point. */
+void
+hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
+{
+ smartlist_t *ips;
+
+ tor_assert(desc);
+
+ ips = desc->encrypted_data.intro_points;
+ if (ips) {
+ SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *,
+ ip, hs_desc_intro_point_free(ip));
+ smartlist_clear(ips);
+ }
+}
+
diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h
new file mode 100644
index 0000000000..fa211d3917
--- /dev/null
+++ b/src/or/hs_descriptor.h
@@ -0,0 +1,254 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.h
+ * \brief Header file for hs_descriptor.c
+ **/
+
+#ifndef TOR_HS_DESCRIPTOR_H
+#define TOR_HS_DESCRIPTOR_H
+
+#include <stdint.h>
+
+#include "or.h"
+#include "address.h"
+#include "container.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+#include "torcert.h"
+
+/* The earliest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
+/* The latest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3
+
+/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours
+ * which is 180 minutes or 10800 seconds. */
+#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60)
+/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours
+ * which is 720 minutes or 43200 seconds. */
+#define HS_DESC_MAX_LIFETIME (12 * 60 * 60)
+/* Lifetime of certificate in the descriptor. This defines the lifetime of the
+ * descriptor signing key and the cross certification cert of that key. */
+#define HS_DESC_CERT_LIFETIME (36 * 60 * 60)
+/* Length of the salt needed for the encrypted section of a descriptor. */
+#define HS_DESC_ENCRYPTED_SALT_LEN 16
+/* Length of the secret input needed for the KDF construction which derives
+ * the encryption key for the encrypted data section of the descriptor. This
+ * adds up to 68 bytes being the blinded key, hashed subcredential and
+ * revision counter. */
+#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \
+ ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t)
+/* Length of the KDF output value which is the length of the secret key,
+ * the secret IV and MAC key length which is the length of H() output. */
+#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \
+ CIPHER256_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN
+/* Pad plaintext of superencrypted data section before encryption so that its
+ * length is a multiple of this value. */
+#define HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE 10000
+/* Maximum length in bytes of a full hidden service descriptor. */
+#define HS_DESC_MAX_LEN 50000 /* 50kb max size */
+
+/* Key length for the descriptor symmetric encryption. As specified in the
+ * protocol, we use AES-256 for the encrypted section of the descriptor. The
+ * following is the length in bytes and the bit size. */
+#define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN
+#define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8)
+
+/* Type of authentication in the descriptor. */
+typedef enum {
+ HS_DESC_AUTH_ED25519 = 1
+} hs_desc_auth_type_t;
+
+/* Link specifier object that contains information on how to extend to the
+ * relay that is the address, port and handshake type. */
+typedef struct hs_desc_link_specifier_t {
+ /* Indicate the type of link specifier. See trunnel ed25519_cert
+ * specification. */
+ uint8_t type;
+
+ /* It must be one of these types, can't be more than one. */
+ union {
+ /* IP address and port of the relay use to extend. */
+ tor_addr_port_t ap;
+ /* Legacy identity. A 20-byte SHA1 identity fingerprint. */
+ uint8_t legacy_id[DIGEST_LEN];
+ /* ed25519 identity. A 32-byte key. */
+ uint8_t ed25519_id[ED25519_PUBKEY_LEN];
+ } u;
+} hs_desc_link_specifier_t;
+
+/* Introduction point information located in a descriptor. */
+typedef struct hs_desc_intro_point_t {
+ /* Link specifier(s) which details how to extend to the relay. This list
+ * contains hs_desc_link_specifier_t object. It MUST have at least one. */
+ smartlist_t *link_specifiers;
+
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
+ /* Authentication key used to establish the introduction point circuit and
+ * cross-certifies the blinded public key for the replica thus signed by
+ * the blinded key and in turn signs it. */
+ tor_cert_t *auth_key_cert;
+
+ /* Encryption key for the "ntor" type. */
+ curve25519_public_key_t enc_key;
+
+ /* Certificate cross certifying the descriptor signing key by the encryption
+ * curve25519 key. This certificate contains the signing key and is of type
+ * CERT_TYPE_CROSS_HS_IP_KEYS [0B]. */
+ tor_cert_t *enc_key_cert;
+
+ /* (Optional): If this introduction point is a legacy one that is version <=
+ * 0.2.9.x (HSIntro=3), we use this extra key for the intro point to be able
+ * to relay the cells to the service correctly. */
+ struct {
+ /* RSA public key. */
+ crypto_pk_t *key;
+
+ /* Cross certified cert with the descriptor signing key (RSA->Ed). Because
+ * of the cross certification API, we need to keep the certificate binary
+ * blob and its length in order to properly encode it after. */
+ struct {
+ uint8_t *encoded;
+ size_t len;
+ } cert;
+ } legacy;
+
+ /* True iff the introduction point has passed the cross certification. Upon
+ * decoding an intro point, this must be true. */
+ unsigned int cross_certified : 1;
+} hs_desc_intro_point_t;
+
+/* The encrypted data section of a descriptor. Obviously the data in this is
+ * in plaintext but encrypted once encoded. */
+typedef struct hs_desc_encrypted_data_t {
+ /* Bitfield of CREATE2 cell supported formats. The only currently supported
+ * format is ntor. */
+ unsigned int create2_ntor : 1;
+
+ /* A list of authentication types that a client must at least support one
+ * in order to contact the service. Contains NULL terminated strings. */
+ smartlist_t *intro_auth_types;
+
+ /* Is this descriptor a single onion service? */
+ unsigned int single_onion_service : 1;
+
+ /* A list of intro points. Contains hs_desc_intro_point_t objects. */
+ smartlist_t *intro_points;
+} hs_desc_encrypted_data_t;
+
+/* Plaintext data that is unencrypted information of the descriptor. */
+typedef struct hs_desc_plaintext_data_t {
+ /* Version of the descriptor format. Spec specifies this field as a
+ * positive integer. */
+ uint32_t version;
+
+ /* The lifetime of the descriptor in seconds. */
+ uint32_t lifetime_sec;
+
+ /* Certificate with the short-term ed22519 descriptor signing key for the
+ * replica which is signed by the blinded public key for that replica. */
+ tor_cert_t *signing_key_cert;
+
+ /* Signing public key which is used to sign the descriptor. Same public key
+ * as in the signing key certificate. */
+ ed25519_public_key_t signing_pubkey;
+
+ /* Blinded public key used for this descriptor derived from the master
+ * identity key and generated for a specific replica number. */
+ ed25519_public_key_t blinded_pubkey;
+
+ /* Revision counter is incremented at each upload, regardless of whether
+ * the descriptor has changed. This avoids leaking whether the descriptor
+ * has changed. Spec specifies this as a 8 bytes positive integer. */
+ uint64_t revision_counter;
+
+ /* Decoding only: The b64-decoded superencrypted blob from the descriptor */
+ uint8_t *superencrypted_blob;
+
+ /* Decoding only: Size of the superencrypted_blob */
+ size_t superencrypted_blob_size;
+} hs_desc_plaintext_data_t;
+
+/* Service descriptor in its decoded form. */
+typedef struct hs_descriptor_t {
+ /* Contains the plaintext part of the descriptor. */
+ hs_desc_plaintext_data_t plaintext_data;
+
+ /* The following contains what's in the encrypted part of the descriptor.
+ * It's only encrypted in the encoded version of the descriptor thus the
+ * data contained in that object is in plaintext. */
+ hs_desc_encrypted_data_t encrypted_data;
+
+ /* Subcredentials of a service, used by the client and service to decrypt
+ * the encrypted data. */
+ uint8_t subcredential[DIGEST256_LEN];
+} hs_descriptor_t;
+
+/* Return true iff the given descriptor format version is supported. */
+static inline int
+hs_desc_is_supported_version(uint32_t version)
+{
+ if (version < HS_DESC_SUPPORTED_FORMAT_VERSION_MIN ||
+ version > HS_DESC_SUPPORTED_FORMAT_VERSION_MAX) {
+ return 0;
+ }
+ return 1;
+}
+
+/* Public API. */
+
+void hs_descriptor_free(hs_descriptor_t *desc);
+void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc);
+void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc);
+
+void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls);
+hs_desc_link_specifier_t *hs_desc_link_specifier_new(
+ const extend_info_t *info, uint8_t type);
+void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
+
+int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out);
+
+int hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out);
+int hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext);
+int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_out);
+
+size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
+
+hs_desc_intro_point_t *hs_desc_intro_point_new(void);
+void hs_desc_intro_point_free(hs_desc_intro_point_t *ip);
+
+#ifdef HS_DESCRIPTOR_PRIVATE
+
+/* Encoding. */
+STATIC char *encode_link_specifiers(const smartlist_t *specs);
+STATIC size_t build_plaintext_padding(const char *plaintext,
+ size_t plaintext_len,
+ uint8_t **padded_out);
+/* Decoding. */
+STATIC smartlist_t *decode_link_specifiers(const char *encoded);
+STATIC hs_desc_intro_point_t *decode_introduction_point(
+ const hs_descriptor_t *desc,
+ const char *text);
+STATIC int encrypted_data_length_is_valid(size_t len);
+STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type,
+ const char *log_obj_type);
+STATIC int desc_sig_is_valid(const char *b64_sig,
+ const ed25519_public_key_t *signing_pubkey,
+ const char *encoded_desc, size_t encoded_len);
+STATIC size_t decode_superencrypted(const char *message, size_t message_len,
+ uint8_t **encrypted_out);
+#endif /* HS_DESCRIPTOR_PRIVATE */
+
+#endif /* TOR_HS_DESCRIPTOR_H */
+
diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c
new file mode 100644
index 0000000000..e69350d82e
--- /dev/null
+++ b/src/or/hs_ident.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.c
+ * \brief Contains circuit and connection identifier code for the whole HS
+ * subsytem.
+ **/
+
+#include "hs_ident.h"
+
+/* Return a newly allocated circuit identifier. The given public key is copied
+ * identity_pk into the identifier. */
+hs_ident_circuit_t *
+hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type)
+{
+ tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
+ circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ ident->circuit_type = circuit_type;
+ return ident;
+}
+
+/* Free the given circuit identifier. */
+void
+hs_ident_circuit_free(hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_circuit_t));
+ tor_free(ident);
+}
+
+/* For a given circuit identifier src, return a newly allocated copy of it.
+ * This can't fail. */
+hs_ident_circuit_t *
+hs_ident_circuit_dup(const hs_ident_circuit_t *src)
+{
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* For a given directory connection identifier src, return a newly allocated
+ * copy of it. This can't fail. */
+hs_ident_dir_conn_t *
+hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
+{
+ hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* Free the given directory connection identifier. */
+void
+hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
+ tor_free(ident);
+}
+
+/* Return a newly allocated edge connection identifier. The given public key
+ * identity_pk is copied into the identifier. */
+hs_ident_edge_conn_t *
+hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
+{
+ hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ return ident;
+}
+
+/* Free the given edge connection identifier. */
+void
+hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
+ tor_free(ident);
+}
+
diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h
new file mode 100644
index 0000000000..e259fde54d
--- /dev/null
+++ b/src/or/hs_ident.h
@@ -0,0 +1,130 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.h
+ * \brief Header file containing circuit and connection identifier data for
+ * the whole HS subsytem.
+ *
+ * \details
+ * This interface is used to uniquely identify a hidden service on a circuit
+ * or connection using the service identity public key. Once the circuit or
+ * connection subsystem calls in the hidden service one, we use those
+ * identifiers to lookup the corresponding objects like service, intro point
+ * and descriptor.
+ *
+ * Furthermore, the circuit identifier holds cryptographic material needed for
+ * the e2e encryption on the rendezvous circuit which is set once the
+ * rendezvous circuit has opened and ready to be used.
+ **/
+
+#ifndef TOR_HS_IDENT_H
+#define TOR_HS_IDENT_H
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_common.h"
+
+/* Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define HS_REND_COOKIE_LEN DIGEST_LEN
+
+/* Type of circuit an hs_ident_t object is associated with. */
+typedef enum {
+ HS_IDENT_CIRCUIT_INTRO = 1,
+ HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
+} hs_ident_circuit_type_t;
+
+/* Client and service side circuit identifier that is used for hidden service
+ * circuit establishment. Not all fields contain data, it depends on the
+ * circuit purpose. This is attached to an origin_circuit_t. All fields are
+ * used by both client and service. */
+typedef struct hs_ident_circuit_t {
+ /* (All circuit) The public key used to uniquely identify the service. It is
+ * the one found in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* (All circuit) The type of circuit this identifier is attached to.
+ * Accessors of the fields in this object assert non fatal on this circuit
+ * type. In other words, if a rendezvous field is being accessed, the
+ * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
+ * set when an object is initialized in its constructor. */
+ hs_ident_circuit_type_t circuit_type;
+
+ /* (All circuit) Introduction point authentication key. It's also needed on
+ * the rendezvous circuit for the ntor handshake. It's used as the unique key
+ * of the introduction point so it should not be shared between multiple
+ * intro points. */
+ ed25519_public_key_t intro_auth_pk;
+
+ /* (Only client rendezvous circuit) Introduction point encryption public
+ * key. We keep it in the rendezvous identifier for the ntor handshake. */
+ curve25519_public_key_t intro_enc_pk;
+
+ /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the
+ * service with an INTRODUCE1 cell and used by the service in an
+ * RENDEZVOUS1 cell. */
+ uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN];
+
+ /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the
+ * RENDEZVOUS1 cell of the service. The construction is as follows:
+ * SERVER_PK [32 bytes]
+ * AUTH_MAC [32 bytes]
+ */
+ uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ /* (Only client rendezvous circuit) Client ephemeral keypair needed for the
+ * e2e encryption with the service. */
+ curve25519_keypair_t rendezvous_client_kp;
+
+ /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for
+ * the e2e encryption with the client on the circuit. */
+ uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN];
+
+ /* (Only rendezvous circuit) Number of streams associated with this
+ * rendezvous circuit. We track this because there is a check on a maximum
+ * value. */
+ uint64_t num_rdv_streams;
+} hs_ident_circuit_t;
+
+/* Client and service side directory connection identifier used for a
+ * directory connection to identify which service is being queried. This is
+ * attached to a dir_connection_t. */
+typedef struct hs_ident_dir_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_dir_conn_t;
+
+/* Client and service side edge connection identifier used for an edge
+ * connection to identify which service is being queried. This is attached to
+ * a edge_connection_t. */
+typedef struct hs_ident_edge_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_edge_conn_t;
+
+/* Circuit identifier API. */
+hs_ident_circuit_t *hs_ident_circuit_new(
+ const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type);
+void hs_ident_circuit_free(hs_ident_circuit_t *ident);
+hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src);
+
+/* Directory connection identifier API. */
+hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
+void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident);
+
+/* Edge connection identifier API. */
+hs_ident_edge_conn_t *hs_ident_edge_conn_new(
+ const ed25519_public_key_t *identity_pk);
+void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
+
+#endif /* TOR_HS_IDENT_H */
+
diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c
new file mode 100644
index 0000000000..cb4d6c02e5
--- /dev/null
+++ b/src/or/hs_intropoint.c
@@ -0,0 +1,609 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.c
+ * \brief Implement next generation introductions point functionality
+ **/
+
+#define HS_INTROPOINT_PRIVATE
+
+#include "or.h"
+#include "config.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "relay.h"
+#include "rendmid.h"
+#include "rephist.h"
+
+/* Trunnel */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+
+#include "hs_circuitmap.h"
+#include "hs_descriptor.h"
+#include "hs_intropoint.h"
+#include "hs_common.h"
+
+/** Extract the authentication key from an ESTABLISH_INTRO or INTRODUCE1 using
+ * the given <b>cell_type</b> from <b>cell</b> and place it in
+ * <b>auth_key_out</b>. */
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell)
+{
+ size_t auth_key_len;
+ const uint8_t *key_array;
+
+ tor_assert(auth_key_out);
+ tor_assert(cell);
+
+ switch (cell_type) {
+ case RELAY_COMMAND_ESTABLISH_INTRO:
+ {
+ const trn_cell_establish_intro_t *c_cell = cell;
+ key_array = trn_cell_establish_intro_getconstarray_auth_key(c_cell);
+ auth_key_len = trn_cell_establish_intro_getlen_auth_key(c_cell);
+ break;
+ }
+ case RELAY_COMMAND_INTRODUCE1:
+ {
+ const trn_cell_introduce1_t *c_cell = cell;
+ key_array = trn_cell_introduce1_getconstarray_auth_key(cell);
+ auth_key_len = trn_cell_introduce1_getlen_auth_key(c_cell);
+ break;
+ }
+ default:
+ /* Getting here is really bad as it means we got a unknown cell type from
+ * this file where every call has an hardcoded value. */
+ tor_assert(0); /* LCOV_EXCL_LINE */
+ }
+ tor_assert(key_array);
+ tor_assert(auth_key_len == sizeof(auth_key_out->pubkey));
+ memcpy(auth_key_out->pubkey, key_array, auth_key_len);
+}
+
+/** We received an ESTABLISH_INTRO <b>cell</b>. Verify its signature and MAC,
+ * given <b>circuit_key_material</b>. Return 0 on success else -1 on error. */
+STATIC int
+verify_establish_intro_cell(const trn_cell_establish_intro_t *cell,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len)
+{
+ /* We only reach this function if the first byte of the cell is 0x02 which
+ * means that auth_key_type is of ed25519 type, hence this check should
+ * always pass. See hs_intro_received_establish_intro(). */
+ if (BUG(cell->auth_key_type != HS_INTRO_AUTH_KEY_TYPE_ED25519)) {
+ return -1;
+ }
+
+ /* Make sure the auth key length is of the right size for this type. For
+ * EXTRA safety, we check both the size of the array and the length which
+ * must be the same. Safety first!*/
+ if (trn_cell_establish_intro_getlen_auth_key(cell) != ED25519_PUBKEY_LEN ||
+ trn_cell_establish_intro_get_auth_key_len(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO auth key length is invalid");
+ return -1;
+ }
+
+ const uint8_t *msg = cell->start_cell;
+
+ /* Verify the sig */
+ {
+ ed25519_signature_t sig_struct;
+ const uint8_t *sig_array =
+ trn_cell_establish_intro_getconstarray_sig(cell);
+
+ /* Make sure the signature length is of the right size. For EXTRA safety,
+ * we check both the size of the array and the length which must be the
+ * same. Safety first!*/
+ if (trn_cell_establish_intro_getlen_sig(cell) != sizeof(sig_struct.sig) ||
+ trn_cell_establish_intro_get_sig_len(cell) != sizeof(sig_struct.sig)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO sig len is invalid");
+ return -1;
+ }
+ /* We are now sure that sig_len is of the right size. */
+ memcpy(sig_struct.sig, sig_array, cell->sig_len);
+
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, cell);
+
+ const size_t sig_msg_len = cell->end_sig_fields - msg;
+ int sig_mismatch = ed25519_checksig_prefixed(&sig_struct,
+ msg, sig_msg_len,
+ ESTABLISH_INTRO_SIG_PREFIX,
+ &auth_key);
+ if (sig_mismatch) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO signature not as expected");
+ return -1;
+ }
+ }
+
+ /* Verify the MAC */
+ {
+ const size_t auth_msg_len = cell->end_mac_fields - msg;
+ uint8_t mac[DIGEST256_LEN];
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ circuit_key_material, circuit_key_material_len,
+ msg, auth_msg_len);
+ if (tor_memneq(mac, cell->handshake_mac, sizeof(mac))) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO handshake_auth not as expected");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Send an INTRO_ESTABLISHED cell to <b>circ</b>. */
+MOCK_IMPL(int,
+hs_intro_send_intro_established_cell,(or_circuit_t *circ))
+{
+ int ret;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ trn_cell_intro_established_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Build the cell payload. */
+ cell = trn_cell_intro_established_new();
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_intro_established_set_extensions(cell, ext);
+ /* Encode the cell to binary format. */
+ encoded_len = trn_cell_intro_established_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = trn_cell_intro_established_encode(encoded_cell, encoded_len,
+ cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRO_ESTABLISHED,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ trn_cell_intro_established_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's
+ * well-formed and passed our verifications. Perform appropriate actions to
+ * establish an intro point. */
+static int
+handle_verified_establish_intro_cell(or_circuit_t *circ,
+ const trn_cell_establish_intro_t *parsed_cell)
+{
+ /* Get the auth key of this intro point */
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO,
+ parsed_cell);
+
+ /* Then notify the hidden service that the intro point is established by
+ sending an INTRO_ESTABLISHED cell */
+ if (hs_intro_send_intro_established_cell(circ)) {
+ log_warn(LD_PROTOCOL, "Couldn't send INTRO_ESTABLISHED cell.");
+ return -1;
+ }
+
+ /* Associate intro point auth key with this circuit. */
+ hs_circuitmap_register_intro_circ_v3_relay_side(circ, &auth_key);
+ /* Repurpose this circuit into an intro circuit. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
+
+ return 0;
+}
+
+/** We just received an ESTABLISH_INTRO cell in <b>circ</b> with payload in
+ * <b>request</b>. Handle it by making <b>circ</b> an intro circuit. Return 0
+ * if everything went well, or -1 if there were errors. */
+static int
+handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int cell_ok, retval = -1;
+ trn_cell_establish_intro_t *parsed_cell = NULL;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ log_info(LD_REND, "Received an ESTABLISH_INTRO request on circuit %" PRIu32,
+ circ->p_circ_id);
+
+ /* Check that the circuit is in shape to become an intro point */
+ if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
+ goto err;
+ }
+
+ /* Parse the cell */
+ ssize_t parsing_result = trn_cell_establish_intro_parse(&parsed_cell,
+ request, request_len);
+ if (parsing_result < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s ESTABLISH_INTRO cell.",
+ parsing_result == -1 ? "invalid" : "truncated");
+ goto err;
+ }
+
+ cell_ok = verify_establish_intro_cell(parsed_cell,
+ (uint8_t *) circ->rend_circ_nonce,
+ sizeof(circ->rend_circ_nonce));
+ if (cell_ok < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Failed to verify ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* This cell is legit. Take the appropriate actions. */
+ cell_ok = handle_verified_establish_intro_cell(circ, parsed_cell);
+ if (cell_ok < 0) {
+ goto err;
+ }
+
+ /* We are done! */
+ retval = 0;
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+
+ done:
+ trn_cell_establish_intro_free(parsed_cell);
+ return retval;
+}
+
+/* Return True if circuit is suitable for being an intro circuit. */
+static int
+circuit_is_suitable_intro_point(const or_circuit_t *circ,
+ const char *log_cell_type_str)
+{
+ tor_assert(circ);
+ tor_assert(log_cell_type_str);
+
+ /* Basic circuit state sanity checks. */
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_OR) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-OR circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ if (circ->base_.n_chan) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-edge circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ /* Suitable. */
+ return 1;
+}
+
+/* Return True if circuit is suitable for being service-side intro circuit. */
+int
+hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ)
+{
+ return circuit_is_suitable_intro_point(circ, "ESTABLISH_INTRO");
+}
+
+/* We just received an ESTABLISH_INTRO cell in <b>circ</b>. Figure out of it's
+ * a legacy or a next gen cell, and pass it to the appropriate handler. */
+int
+hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ tor_assert(circ);
+ tor_assert(request);
+
+ if (request_len == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Empty ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* Using the first byte of the cell, figure out the version of
+ * ESTABLISH_INTRO and pass it to the appropriate cell handler */
+ const uint8_t first_byte = request[0];
+ switch (first_byte) {
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY0:
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY1:
+ return rend_mid_establish_intro_legacy(circ, request, request_len);
+ case HS_INTRO_AUTH_KEY_TYPE_ED25519:
+ return handle_establish_intro(circ, request, request_len);
+ default:
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unrecognized AUTH_KEY_TYPE %u.", first_byte);
+ goto err;
+ }
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Send an INTRODUCE_ACK cell onto the circuit <b>circ</b> with the status
+ * value in <b>status</b>. Depending on the status, it can be ACK or a NACK.
+ * Return 0 on success else a negative value on error which will close the
+ * circuit. */
+static int
+send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status)
+{
+ int ret = -1;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ trn_cell_introduce_ack_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Setup the INTRODUCE_ACK cell. We have no extensions so the N_EXTENSIONS
+ * field is set to 0 by default with a new object. */
+ cell = trn_cell_introduce_ack_new();
+ ret = trn_cell_introduce_ack_set_status(cell, status);
+ /* We have no cell extensions in an INTRODUCE_ACK cell. */
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce_ack_set_extensions(cell, ext);
+ /* A wrong status is a very bad code flow error as this value is controlled
+ * by the code in this file and not an external input. This means we use a
+ * code that is not known by the trunnel ABI. */
+ tor_assert(ret == 0);
+ /* Encode the payload. We should never fail to get the encoded length. */
+ encoded_len = trn_cell_introduce_ack_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = trn_cell_introduce_ack_encode(encoded_cell, encoded_len, cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRODUCE_ACK,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ trn_cell_introduce_ack_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/* Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a
+ * negative value for an invalid cell that should be NACKed. */
+STATIC int
+validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell)
+{
+ size_t legacy_key_id_len;
+ const uint8_t *legacy_key_id;
+
+ tor_assert(cell);
+
+ /* This code path SHOULD NEVER be reached if the cell is a legacy type so
+ * safety net here. The legacy ID must be zeroes in this case. */
+ legacy_key_id_len = trn_cell_introduce1_getlen_legacy_key_id(cell);
+ legacy_key_id = trn_cell_introduce1_getconstarray_legacy_key_id(cell);
+ if (BUG(!tor_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) {
+ goto invalid;
+ }
+
+ /* The auth key of an INTRODUCE1 should be of type ed25519 thus leading to a
+ * known fixed length as well. */
+ if (trn_cell_introduce1_get_auth_key_type(cell) !=
+ HS_INTRO_AUTH_KEY_TYPE_ED25519) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key type. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (trn_cell_introduce1_get_auth_key_len(cell) != ED25519_PUBKEY_LEN ||
+ trn_cell_introduce1_getlen_auth_key(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (trn_cell_introduce1_getlen_encrypted(cell) == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell encrypted length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* We just received a non legacy INTRODUCE1 cell on <b>client_circ</b> with
+ * the payload in <b>request</b> of size <b>request_len</b>. Return 0 if
+ * everything went well, or -1 if an error occured. This function is in charge
+ * of sending back an INTRODUCE_ACK cell and will close client_circ on error.
+ */
+STATIC int
+handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret = -1;
+ or_circuit_t *service_circ;
+ trn_cell_introduce1_t *parsed_cell;
+ hs_intro_ack_status_t status = HS_INTRO_ACK_STATUS_SUCCESS;
+
+ tor_assert(client_circ);
+ tor_assert(request);
+
+ /* Parse cell. Note that we can only parse the non encrypted section for
+ * which we'll use the authentication key to find the service introduction
+ * circuit and relay the cell on it. */
+ ssize_t cell_size = trn_cell_introduce1_parse(&parsed_cell, request,
+ request_len);
+ if (cell_size < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s INTRODUCE1 cell. Responding with NACK.",
+ cell_size == -1 ? "invalid" : "truncated");
+ /* Inform client that the INTRODUCE1 has a bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Once parsed validate the cell format. */
+ if (validate_introduce1_parsed_cell(parsed_cell) < 0) {
+ /* Inform client that the INTRODUCE1 has bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Find introduction circuit through our circuit map. */
+ {
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_INTRODUCE1, parsed_cell);
+ service_circ = hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key);
+ if (service_circ == NULL) {
+ char b64_key[ED25519_BASE64_LEN + 1];
+ ed25519_public_to_base64(b64_key, &auth_key);
+ log_info(LD_REND, "No intro circuit found for INTRODUCE1 cell "
+ "with auth key %s from circuit %" PRIu32 ". "
+ "Responding with NACK.",
+ safe_str(b64_key), client_circ->p_circ_id);
+ /* Inform the client that we don't know the requested service ID. */
+ status = HS_INTRO_ACK_STATUS_UNKNOWN_ID;
+ goto send_ack;
+ }
+ }
+
+ /* Relay the cell to the service on its intro circuit with an INTRODUCE2
+ * cell which is the same exact payload. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
+ RELAY_COMMAND_INTRODUCE2,
+ (char *) request, request_len, NULL)) {
+ log_warn(LD_PROTOCOL, "Unable to send INTRODUCE2 cell to the service.");
+ /* Inform the client that we can't relay the cell. */
+ status = HS_INTRO_ACK_STATUS_CANT_RELAY;
+ goto send_ack;
+ }
+
+ /* Success! Send an INTRODUCE_ACK success status onto the client circuit. */
+ status = HS_INTRO_ACK_STATUS_SUCCESS;
+ ret = 0;
+
+ send_ack:
+ /* Send INTRODUCE_ACK or INTRODUCE_NACK to client */
+ if (send_introduce_ack_cell(client_circ, status) < 0) {
+ log_warn(LD_PROTOCOL, "Unable to send an INTRODUCE ACK status %d "
+ "to client.", status);
+ /* Circuit has been closed on failure of transmission. */
+ goto done;
+ }
+ if (status != HS_INTRO_ACK_STATUS_SUCCESS) {
+ /* We just sent a NACK that is a non success status code so close the
+ * circuit because it's not useful to keep it open. Remember, a client can
+ * only send one INTRODUCE1 cell on a circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(client_circ), END_CIRC_REASON_INTERNAL);
+ }
+ done:
+ trn_cell_introduce1_free(parsed_cell);
+ return ret;
+}
+
+/* Identify if the encoded cell we just received is a legacy one or not. The
+ * <b>request</b> should be at least DIGEST_LEN bytes long. */
+STATIC int
+introduce1_cell_is_legacy(const uint8_t *request)
+{
+ tor_assert(request);
+
+ /* If the first 20 bytes of the cell (DIGEST_LEN) are NOT zeroes, it
+ * indicates a legacy cell (v2). */
+ if (!tor_mem_is_zero((const char *) request, DIGEST_LEN)) {
+ /* Legacy cell. */
+ return 1;
+ }
+ /* Not a legacy cell. */
+ return 0;
+}
+
+/* Return true iff the circuit <b>circ</b> is suitable for receiving an
+ * INTRODUCE1 cell. */
+STATIC int
+circuit_is_suitable_for_introduce1(const or_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Is this circuit an intro point circuit? */
+ if (!circuit_is_suitable_intro_point(circ, "INTRODUCE1")) {
+ return 0;
+ }
+
+ if (circ->already_received_introduce1) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Blocking multiple introductions on the same circuit. "
+ "Someone might be trying to attack a hidden service through "
+ "this relay.");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* We just received an INTRODUCE1 cell on <b>circ</b>. Figure out which type
+ * it is and pass it to the appropriate handler. Return 0 on success else a
+ * negative value and the circuit is closed. */
+int
+hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ /* A cell that can't hold a DIGEST_LEN is invalid as we need to check if
+ * it's a legacy cell or not using the first DIGEST_LEN bytes. */
+ if (request_len < DIGEST_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Invalid INTRODUCE1 cell length.");
+ goto err;
+ }
+
+ /* Make sure we have a circuit that can have an INTRODUCE1 cell on it. */
+ if (!circuit_is_suitable_for_introduce1(circ)) {
+ /* We do not send a NACK because the circuit is not suitable for any kind
+ * of response or transmission as it's a violation of the protocol. */
+ goto err;
+ }
+ /* Mark the circuit that we got this cell. None are allowed after this as a
+ * DoS mitigation since one circuit with one client can hammer a service. */
+ circ->already_received_introduce1 = 1;
+
+ /* We are sure here to have at least DIGEST_LEN bytes. */
+ if (introduce1_cell_is_legacy(request)) {
+ /* Handle a legacy cell. */
+ ret = rend_mid_introduce_legacy(circ, request, request_len);
+ } else {
+ /* Handle a non legacy cell. */
+ ret = handle_introduce1(circ, request, request_len);
+ }
+ return ret;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Clear memory allocated by the given intropoint object ip (but don't free the
+ * object itself). */
+void
+hs_intropoint_clear(hs_intropoint_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ tor_cert_free(ip->auth_key_cert);
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls,
+ hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ memset(ip, 0, sizeof(hs_intropoint_t));
+}
+
diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h
new file mode 100644
index 0000000000..5c77f07ec3
--- /dev/null
+++ b/src/or/hs_intropoint.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.h
+ * \brief Header file for hs_intropoint.c.
+ **/
+
+#ifndef TOR_HS_INTRO_H
+#define TOR_HS_INTRO_H
+
+#include "crypto_curve25519.h"
+#include "torcert.h"
+
+/* Authentication key type in an ESTABLISH_INTRO cell. */
+typedef enum {
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00,
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02,
+} hs_intro_auth_key_type_t;
+
+/* INTRODUCE_ACK status code. */
+typedef enum {
+ HS_INTRO_ACK_STATUS_SUCCESS = 0x0000,
+ HS_INTRO_ACK_STATUS_UNKNOWN_ID = 0x0001,
+ HS_INTRO_ACK_STATUS_BAD_FORMAT = 0x0002,
+ HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003,
+} hs_intro_ack_status_t;
+
+/* Object containing introduction point common data between the service and
+ * the client side. */
+typedef struct hs_intropoint_t {
+ /* Does this intro point only supports legacy ID ?. */
+ unsigned int is_only_legacy : 1;
+
+ /* Authentication key certificate from the descriptor. */
+ tor_cert_t *auth_key_cert;
+ /* A list of link specifier. */
+ smartlist_t *link_specifiers;
+} hs_intropoint_t;
+
+int hs_intro_received_establish_intro(or_circuit_t *circ,
+ const uint8_t *request,
+ size_t request_len);
+int hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
+
+MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ));
+
+/* also used by rendservice.c */
+int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ);
+
+hs_intropoint_t *hs_intro_new(void);
+void hs_intropoint_clear(hs_intropoint_t *ip);
+
+#ifdef HS_INTROPOINT_PRIVATE
+
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+
+STATIC int
+verify_establish_intro_cell(const trn_cell_establish_intro_t *out,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len);
+
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell);
+
+STATIC int introduce1_cell_is_legacy(const uint8_t *request);
+STATIC int handle_introduce1(or_circuit_t *client_circ,
+ const uint8_t *request, size_t request_len);
+STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell);
+STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ);
+
+#endif /* HS_INTROPOINT_PRIVATE */
+
+#endif /* TOR_HS_INTRO_H */
+
diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c
new file mode 100644
index 0000000000..a416bc46c3
--- /dev/null
+++ b/src/or/hs_ntor.c
@@ -0,0 +1,618 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/** \file hs_ntor.c
+ * \brief Implements the ntor variant used in Tor hidden services.
+ *
+ * \details
+ * This module handles the variant of the ntor handshake that is documented in
+ * section [NTOR-WITH-EXTRA-DATA] of rend-spec-ng.txt .
+ *
+ * The functions in this file provide an API that should be used when sending
+ * or receiving INTRODUCE1/RENDEZVOUS1 cells to generate the various key
+ * material required to create and handle those cells.
+ *
+ * In the case of INTRODUCE1 it provides encryption and MAC keys to
+ * encode/decode the encrypted blob (see hs_ntor_intro_cell_keys_t). The
+ * relevant pub functions are hs_ntor_{client,service}_get_introduce1_keys().
+ *
+ * In the case of RENDEZVOUS1 it calculates the MAC required to authenticate
+ * the cell, and also provides the key seed that is used to derive the crypto
+ * material for rendezvous encryption (see hs_ntor_rend_cell_keys_t). The
+ * relevant pub functions are hs_ntor_{client,service}_get_rendezvous1_keys().
+ * It also provides a function (hs_ntor_circuit_key_expansion()) that does the
+ * rendezvous key expansion to setup end-to-end rend circuit keys.
+ */
+
+#include "or.h"
+#include "hs_ntor.h"
+
+/* String constants used by the ntor HS protocol */
+#define PROTOID "tor-hs-ntor-curve25519-sha3-256-1"
+#define PROTOID_LEN (sizeof(PROTOID) - 1)
+#define SERVER_STR "Server"
+#define SERVER_STR_LEN (sizeof(SERVER_STR) - 1)
+
+/* Protocol-specific tweaks to our crypto inputs */
+#define T_HSENC PROTOID ":hs_key_extract"
+#define T_HSENC_LEN (sizeof(T_HSENC) - 1)
+#define T_HSVERIFY PROTOID ":hs_verify"
+#define T_HSMAC PROTOID ":hs_mac"
+#define M_HSEXPAND PROTOID ":hs_key_expand"
+#define M_HSEXPAND_LEN (sizeof(M_HSEXPAND) - 1)
+
+/************************* Helper functions: *******************************/
+
+/** Helper macro: copy <b>len</b> bytes from <b>inp</b> to <b>ptr</b> and
+ *advance <b>ptr</b> by the number of bytes copied. Stolen from onion_ntor.c */
+#define APPEND(ptr, inp, len) \
+ STMT_BEGIN { \
+ memcpy(ptr, (inp), (len)); \
+ ptr += len; \
+ } STMT_END
+
+/* Length of EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID */
+#define REND_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN * 2 + \
+ ED25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN)
+/* Length of auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" */
+#define REND_AUTH_INPUT_LEN (DIGEST256_LEN + ED25519_PUBKEY_LEN + \
+ CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN + SERVER_STR_LEN)
+
+/** Helper function: Compute the last part of the HS ntor handshake which
+ * derives key material necessary to create and handle RENDEZVOUS1
+ * cells. Function used by both client and service. The actual calculations is
+ * as follows:
+ *
+ * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
+ * verify = MAC(rend_secret_hs_input, t_hsverify)
+ * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
+ * auth_input_mac = MAC(auth_input, t_hsmac)
+ *
+ * where in the above, AUTH_KEY is <b>intro_auth_pubkey</b>, B is
+ * <b>intro_enc_pubkey</b>, Y is <b>service_ephemeral_rend_pubkey</b>, and X
+ * is <b>client_ephemeral_enc_pubkey</b>. The provided
+ * <b>rend_secret_hs_input</b> is of size REND_SECRET_HS_INPUT_LEN.
+ *
+ * The final results of NTOR_KEY_SEED and auth_input_mac are placed in
+ * <b>hs_ntor_rend_cell_keys_out</b>. Return 0 if everything went fine. */
+static int
+get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input,
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_public_key_t *service_ephemeral_rend_pubkey,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out)
+{
+ int bad = 0;
+ uint8_t ntor_key_seed[DIGEST256_LEN];
+ uint8_t ntor_verify[DIGEST256_LEN];
+ uint8_t rend_auth_input[REND_AUTH_INPUT_LEN];
+ uint8_t rend_cell_auth[DIGEST256_LEN];
+ uint8_t *ptr;
+
+ /* Let's build NTOR_KEY_SEED */
+ crypto_mac_sha3_256(ntor_key_seed, sizeof(ntor_key_seed),
+ rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN,
+ (const uint8_t *)T_HSENC, strlen(T_HSENC));
+ bad |= safe_mem_is_zero(ntor_key_seed, DIGEST256_LEN);
+
+ /* Let's build ntor_verify */
+ crypto_mac_sha3_256(ntor_verify, sizeof(ntor_verify),
+ rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN,
+ (const uint8_t *)T_HSVERIFY, strlen(T_HSVERIFY));
+ bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN);
+
+ /* Let's build auth_input: */
+ ptr = rend_auth_input;
+ /* Append ntor_verify */
+ APPEND(ptr, ntor_verify, sizeof(ntor_verify));
+ /* Append AUTH_KEY */
+ APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN);
+ /* Append B */
+ APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append Y */
+ APPEND(ptr,
+ service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append X */
+ APPEND(ptr,
+ client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append PROTOID */
+ APPEND(ptr, PROTOID, strlen(PROTOID));
+ /* Append "Server" */
+ APPEND(ptr, SERVER_STR, strlen(SERVER_STR));
+ tor_assert(ptr == rend_auth_input + sizeof(rend_auth_input));
+
+ /* Let's build auth_input_mac that goes in RENDEZVOUS1 cell */
+ crypto_mac_sha3_256(rend_cell_auth, sizeof(rend_cell_auth),
+ rend_auth_input, sizeof(rend_auth_input),
+ (const uint8_t *)T_HSMAC, strlen(T_HSMAC));
+ bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN);
+
+ { /* Get the computed RENDEZVOUS1 material! */
+ memcpy(&hs_ntor_rend_cell_keys_out->rend_cell_auth_mac,
+ rend_cell_auth, DIGEST256_LEN);
+ memcpy(&hs_ntor_rend_cell_keys_out->ntor_key_seed,
+ ntor_key_seed, DIGEST256_LEN);
+ }
+
+ memwipe(rend_cell_auth, 0, sizeof(rend_cell_auth));
+ memwipe(rend_auth_input, 0, sizeof(rend_auth_input));
+ memwipe(ntor_key_seed, 0, sizeof(ntor_key_seed));
+
+ return bad;
+}
+
+/** Length of secret_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID */
+#define INTRO_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN +ED25519_PUBKEY_LEN +\
+ CURVE25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN + PROTOID_LEN)
+/* Length of info = m_hsexpand | subcredential */
+#define INFO_BLOB_LEN (M_HSEXPAND_LEN + DIGEST256_LEN)
+/* Length of KDF input = intro_secret_hs_input | t_hsenc | info */
+#define KDF_INPUT_LEN (INTRO_SECRET_HS_INPUT_LEN + T_HSENC_LEN + INFO_BLOB_LEN)
+
+/** Helper function: Compute the part of the HS ntor handshake that generates
+ * key material for creating and handling INTRODUCE1 cells. Function used
+ * by both client and service. Specifically, calculate the following:
+ *
+ * info = m_hsexpand | subcredential
+ * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
+ * ENC_KEY = hs_keys[0:S_KEY_LEN]
+ * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
+ *
+ * where intro_secret_hs_input is <b>secret_input</b> (of size
+ * INTRO_SECRET_HS_INPUT_LEN), and <b>subcredential</b> is of size
+ * DIGEST256_LEN.
+ *
+ * If everything went well, fill <b>hs_ntor_intro_cell_keys_out</b> with the
+ * necessary key material, and return 0. */
+static void
+get_introduce1_key_material(const uint8_t *secret_input,
+ const uint8_t *subcredential,
+ hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
+{
+ uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN];
+ uint8_t info_blob[INFO_BLOB_LEN];
+ uint8_t kdf_input[KDF_INPUT_LEN];
+ crypto_xof_t *xof;
+ uint8_t *ptr;
+
+ /* Let's build info */
+ ptr = info_blob;
+ APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
+ APPEND(ptr, subcredential, DIGEST256_LEN);
+ tor_assert(ptr == info_blob + sizeof(info_blob));
+
+ /* Let's build the input to the KDF */
+ ptr = kdf_input;
+ APPEND(ptr, secret_input, INTRO_SECRET_HS_INPUT_LEN);
+ APPEND(ptr, T_HSENC, strlen(T_HSENC));
+ APPEND(ptr, info_blob, sizeof(info_blob));
+ tor_assert(ptr == kdf_input + sizeof(kdf_input));
+
+ /* Now we need to run kdf_input over SHAKE-256 */
+ xof = crypto_xof_new();
+ crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
+ crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)) ;
+ crypto_xof_free(xof);
+
+ { /* Get the keys */
+ memcpy(&hs_ntor_intro_cell_keys_out->enc_key, keystream,CIPHER256_KEY_LEN);
+ memcpy(&hs_ntor_intro_cell_keys_out->mac_key,
+ keystream+CIPHER256_KEY_LEN, DIGEST256_LEN);
+ }
+
+ memwipe(keystream, 0, sizeof(keystream));
+ memwipe(kdf_input, 0, sizeof(kdf_input));
+}
+
+/** Helper function: Calculate the 'intro_secret_hs_input' element used by the
+ * HS ntor handshake and place it in <b>secret_input_out</b>. This function is
+ * used by both client and service code.
+ *
+ * For the client-side it looks like this:
+ *
+ * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
+ *
+ * whereas for the service-side it looks like this:
+ *
+ * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID
+ *
+ * In this function, <b>dh_result</b> carries the EXP() result (and has size
+ * CURVE25519_OUTPUT_LEN) <b>intro_auth_pubkey</b> is AUTH_KEY,
+ * <b>client_ephemeral_enc_pubkey</b> is X, and <b>intro_enc_pubkey</b> is B.
+ */
+static void
+get_intro_secret_hs_input(const uint8_t *dh_result,
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ uint8_t *secret_input_out)
+{
+ uint8_t *ptr;
+
+ /* Append EXP() */
+ ptr = secret_input_out;
+ APPEND(ptr, dh_result, CURVE25519_OUTPUT_LEN);
+ /* Append AUTH_KEY */
+ APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN);
+ /* Append X */
+ APPEND(ptr, client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append B */
+ APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append PROTOID */
+ APPEND(ptr, PROTOID, strlen(PROTOID));
+ tor_assert(ptr == secret_input_out + INTRO_SECRET_HS_INPUT_LEN);
+}
+
+/** Calculate the 'rend_secret_hs_input' element used by the HS ntor handshake
+ * and place it in <b>rend_secret_hs_input_out</b>. This function is used by
+ * both client and service code.
+ *
+ * The computation on the client side is:
+ * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
+ * whereas on the service side it is:
+ * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID
+ *
+ * where:
+ * <b>dh_result1</b> and <b>dh_result2</b> carry the two EXP() results (of size
+ * CURVE25519_OUTPUT_LEN)
+ * <b>intro_auth_pubkey</b> is AUTH_KEY,
+ * <b>intro_enc_pubkey</b> is B,
+ * <b>client_ephemeral_enc_pubkey</b> is X, and
+ * <b>service_ephemeral_rend_pubkey</b> is Y.
+ */
+static void
+get_rend_secret_hs_input(const uint8_t *dh_result1, const uint8_t *dh_result2,
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ const curve25519_public_key_t *service_ephemeral_rend_pubkey,
+ uint8_t *rend_secret_hs_input_out)
+{
+ uint8_t *ptr;
+
+ ptr = rend_secret_hs_input_out;
+ /* Append the first EXP() */
+ APPEND(ptr, dh_result1, CURVE25519_OUTPUT_LEN);
+ /* Append the other EXP() */
+ APPEND(ptr, dh_result2, CURVE25519_OUTPUT_LEN);
+ /* Append AUTH_KEY */
+ APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN);
+ /* Append B */
+ APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append X */
+ APPEND(ptr,
+ client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append Y */
+ APPEND(ptr,
+ service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN);
+ /* Append PROTOID */
+ APPEND(ptr, PROTOID, strlen(PROTOID));
+ tor_assert(ptr == rend_secret_hs_input_out + REND_SECRET_HS_INPUT_LEN);
+}
+
+/************************* Public functions: *******************************/
+
+/* Public function: Do the appropriate ntor calculations and derive the keys
+ * needed to encrypt and authenticate INTRODUCE1 cells. Return 0 and place the
+ * final key material in <b>hs_ntor_intro_cell_keys_out</b> if everything went
+ * well, otherwise return -1;
+ *
+ * The relevant calculations are as follows:
+ *
+ * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID
+ * info = m_hsexpand | subcredential
+ * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
+ * ENC_KEY = hs_keys[0:S_KEY_LEN]
+ * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
+ *
+ * where:
+ * <b>intro_auth_pubkey</b> is AUTH_KEY (found in HS descriptor),
+ * <b>intro_enc_pubkey</b> is B (also found in HS descriptor),
+ * <b>client_ephemeral_enc_keypair</b> is freshly generated keypair (x,X)
+ * <b>subcredential</b> is the hidden service subcredential (of size
+ * DIGEST256_LEN). */
+int
+hs_ntor_client_get_introduce1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_keypair_t *client_ephemeral_enc_keypair,
+ const uint8_t *subcredential,
+ hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
+{
+ int bad = 0;
+ uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN];
+ uint8_t dh_result[CURVE25519_OUTPUT_LEN];
+
+ tor_assert(intro_auth_pubkey);
+ tor_assert(intro_enc_pubkey);
+ tor_assert(client_ephemeral_enc_keypair);
+ tor_assert(subcredential);
+ tor_assert(hs_ntor_intro_cell_keys_out);
+
+ /* Calculate EXP(B,x) */
+ curve25519_handshake(dh_result,
+ &client_ephemeral_enc_keypair->seckey,
+ intro_enc_pubkey);
+ bad |= safe_mem_is_zero(dh_result, CURVE25519_OUTPUT_LEN);
+
+ /* Get intro_secret_hs_input */
+ get_intro_secret_hs_input(dh_result, intro_auth_pubkey,
+ &client_ephemeral_enc_keypair->pubkey,
+ intro_enc_pubkey, secret_input);
+ bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN);
+
+ /* Get ENC_KEY and MAC_KEY! */
+ get_introduce1_key_material(secret_input, subcredential,
+ hs_ntor_intro_cell_keys_out);
+
+ /* Cleanup */
+ memwipe(secret_input, 0, sizeof(secret_input));
+ if (bad) {
+ memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ }
+
+ return bad ? -1 : 0;
+}
+
+/* Public function: Do the appropriate ntor calculations and derive the keys
+ * needed to verify RENDEZVOUS1 cells and encrypt further rendezvous
+ * traffic. Return 0 and place the final key material in
+ * <b>hs_ntor_rend_cell_keys_out</b> if everything went well, else return -1.
+ *
+ * The relevant calculations are as follows:
+ *
+ * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID
+ * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
+ * verify = MAC(rend_secret_hs_input, t_hsverify)
+ * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
+ * auth_input_mac = MAC(auth_input, t_hsmac)
+ *
+ * where:
+ * <b>intro_auth_pubkey</b> is AUTH_KEY (found in HS descriptor),
+ * <b>client_ephemeral_enc_keypair</b> is freshly generated keypair (x,X)
+ * <b>intro_enc_pubkey</b> is B (also found in HS descriptor),
+ * <b>service_ephemeral_rend_pubkey</b> is Y (SERVER_PK in RENDEZVOUS1 cell) */
+int
+hs_ntor_client_get_rendezvous1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *client_ephemeral_enc_keypair,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_public_key_t *service_ephemeral_rend_pubkey,
+ hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out)
+{
+ int bad = 0;
+ uint8_t rend_secret_hs_input[REND_SECRET_HS_INPUT_LEN];
+ uint8_t dh_result1[CURVE25519_OUTPUT_LEN];
+ uint8_t dh_result2[CURVE25519_OUTPUT_LEN];
+
+ tor_assert(intro_auth_pubkey);
+ tor_assert(client_ephemeral_enc_keypair);
+ tor_assert(intro_enc_pubkey);
+ tor_assert(service_ephemeral_rend_pubkey);
+ tor_assert(hs_ntor_rend_cell_keys_out);
+
+ /* Compute EXP(Y, x) */
+ curve25519_handshake(dh_result1,
+ &client_ephemeral_enc_keypair->seckey,
+ service_ephemeral_rend_pubkey);
+ bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN);
+
+ /* Compute EXP(B, x) */
+ curve25519_handshake(dh_result2,
+ &client_ephemeral_enc_keypair->seckey,
+ intro_enc_pubkey);
+ bad |= safe_mem_is_zero(dh_result2, CURVE25519_OUTPUT_LEN);
+
+ /* Get rend_secret_hs_input */
+ get_rend_secret_hs_input(dh_result1, dh_result2,
+ intro_auth_pubkey, intro_enc_pubkey,
+ &client_ephemeral_enc_keypair->pubkey,
+ service_ephemeral_rend_pubkey,
+ rend_secret_hs_input);
+
+ /* Get NTOR_KEY_SEED and the auth_input MAC */
+ bad |= get_rendezvous1_key_material(rend_secret_hs_input,
+ intro_auth_pubkey,
+ intro_enc_pubkey,
+ service_ephemeral_rend_pubkey,
+ &client_ephemeral_enc_keypair->pubkey,
+ hs_ntor_rend_cell_keys_out);
+
+ memwipe(rend_secret_hs_input, 0, sizeof(rend_secret_hs_input));
+ if (bad) {
+ memwipe(hs_ntor_rend_cell_keys_out, 0, sizeof(hs_ntor_rend_cell_keys_t));
+ }
+
+ return bad ? -1 : 0;
+}
+
+/* Public function: Do the appropriate ntor calculations and derive the keys
+ * needed to decrypt and verify INTRODUCE1 cells. Return 0 and place the final
+ * key material in <b>hs_ntor_intro_cell_keys_out</b> if everything went well,
+ * otherwise return -1;
+ *
+ * The relevant calculations are as follows:
+ *
+ * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID
+ * info = m_hsexpand | subcredential
+ * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN)
+ * HS_DEC_KEY = hs_keys[0:S_KEY_LEN]
+ * HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN]
+ *
+ * where:
+ * <b>intro_auth_pubkey</b> is AUTH_KEY (introduction point auth key),
+ * <b>intro_enc_keypair</b> is (b,B) (introduction point encryption keypair),
+ * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell),
+ * <b>subcredential</b> is the HS subcredential (of size DIGEST256_LEN) */
+int
+hs_ntor_service_get_introduce1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *intro_enc_keypair,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ const uint8_t *subcredential,
+ hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
+{
+ int bad = 0;
+ uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN];
+ uint8_t dh_result[CURVE25519_OUTPUT_LEN];
+
+ tor_assert(intro_auth_pubkey);
+ tor_assert(intro_enc_keypair);
+ tor_assert(client_ephemeral_enc_pubkey);
+ tor_assert(subcredential);
+ tor_assert(hs_ntor_intro_cell_keys_out);
+
+ /* Compute EXP(X, b) */
+ curve25519_handshake(dh_result,
+ &intro_enc_keypair->seckey,
+ client_ephemeral_enc_pubkey);
+ bad |= safe_mem_is_zero(dh_result, CURVE25519_OUTPUT_LEN);
+
+ /* Get intro_secret_hs_input */
+ get_intro_secret_hs_input(dh_result, intro_auth_pubkey,
+ client_ephemeral_enc_pubkey,
+ &intro_enc_keypair->pubkey,
+ secret_input);
+ bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN);
+
+ /* Get ENC_KEY and MAC_KEY! */
+ get_introduce1_key_material(secret_input, subcredential,
+ hs_ntor_intro_cell_keys_out);
+
+ memwipe(secret_input, 0, sizeof(secret_input));
+ if (bad) {
+ memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ }
+
+ return bad ? -1 : 0;
+}
+
+/* Public function: Do the appropriate ntor calculations and derive the keys
+ * needed to create and authenticate RENDEZVOUS1 cells. Return 0 and place the
+ * final key material in <b>hs_ntor_rend_cell_keys_out</b> if all went fine,
+ * return -1 if error happened.
+ *
+ * The relevant calculations are as follows:
+ *
+ * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID
+ * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc)
+ * verify = MAC(rend_secret_hs_input, t_hsverify)
+ * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server"
+ * auth_input_mac = MAC(auth_input, t_hsmac)
+ *
+ * where:
+ * <b>intro_auth_pubkey</b> is AUTH_KEY (intro point auth key),
+ * <b>intro_enc_keypair</b> is (b,B) (intro point enc keypair)
+ * <b>service_ephemeral_rend_keypair</b> is a fresh (y,Y) keypair
+ * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell) */
+int
+hs_ntor_service_get_rendezvous1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *intro_enc_keypair,
+ const curve25519_keypair_t *service_ephemeral_rend_keypair,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out)
+{
+ int bad = 0;
+ uint8_t rend_secret_hs_input[REND_SECRET_HS_INPUT_LEN];
+ uint8_t dh_result1[CURVE25519_OUTPUT_LEN];
+ uint8_t dh_result2[CURVE25519_OUTPUT_LEN];
+
+ tor_assert(intro_auth_pubkey);
+ tor_assert(intro_enc_keypair);
+ tor_assert(service_ephemeral_rend_keypair);
+ tor_assert(client_ephemeral_enc_pubkey);
+ tor_assert(hs_ntor_rend_cell_keys_out);
+
+ /* Compute EXP(X, y) */
+ curve25519_handshake(dh_result1,
+ &service_ephemeral_rend_keypair->seckey,
+ client_ephemeral_enc_pubkey);
+ bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN);
+
+ /* Compute EXP(X, b) */
+ curve25519_handshake(dh_result2,
+ &intro_enc_keypair->seckey,
+ client_ephemeral_enc_pubkey);
+ bad |= safe_mem_is_zero(dh_result2, CURVE25519_OUTPUT_LEN);
+
+ /* Get rend_secret_hs_input */
+ get_rend_secret_hs_input(dh_result1, dh_result2,
+ intro_auth_pubkey,
+ &intro_enc_keypair->pubkey,
+ client_ephemeral_enc_pubkey,
+ &service_ephemeral_rend_keypair->pubkey,
+ rend_secret_hs_input);
+
+ /* Get NTOR_KEY_SEED and AUTH_INPUT_MAC! */
+ bad |= get_rendezvous1_key_material(rend_secret_hs_input,
+ intro_auth_pubkey,
+ &intro_enc_keypair->pubkey,
+ &service_ephemeral_rend_keypair->pubkey,
+ client_ephemeral_enc_pubkey,
+ hs_ntor_rend_cell_keys_out);
+
+ memwipe(rend_secret_hs_input, 0, sizeof(rend_secret_hs_input));
+ if (bad) {
+ memwipe(hs_ntor_rend_cell_keys_out, 0, sizeof(hs_ntor_rend_cell_keys_t));
+ }
+
+ return bad ? -1 : 0;
+}
+
+/** Given a received RENDEZVOUS2 MAC in <b>mac</b> (of length DIGEST256_LEN),
+ * and the RENDEZVOUS1 key material in <b>hs_ntor_rend_cell_keys</b>, return 1
+ * if the MAC is good, otherwise return 0. */
+int
+hs_ntor_client_rendezvous2_mac_is_good(
+ const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,
+ const uint8_t *rcvd_mac)
+{
+ tor_assert(rcvd_mac);
+ tor_assert(hs_ntor_rend_cell_keys);
+
+ return tor_memeq(hs_ntor_rend_cell_keys->rend_cell_auth_mac,
+ rcvd_mac, DIGEST256_LEN);
+}
+
+/* Input length to KDF for key expansion */
+#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN)
+
+/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size
+ * DIGEST256_LEN), do the circuit key expansion as specified by section
+ * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be
+ * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN).
+ *
+ * Return 0 if things went well, else return -1. */
+int
+hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len)
+{
+ uint8_t *ptr;
+ uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN];
+ crypto_xof_t *xof;
+
+ /* Sanity checks on lengths to make sure we are good */
+ if (BUG(seed_len != DIGEST256_LEN)) {
+ return -1;
+ }
+ if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ }
+
+ /* Let's build the input to the KDF */
+ ptr = kdf_input;
+ APPEND(ptr, ntor_key_seed, DIGEST256_LEN);
+ APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
+ tor_assert(ptr == kdf_input + sizeof(kdf_input));
+
+ /* Generate the keys */
+ xof = crypto_xof_new();
+ crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
+ crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN);
+ crypto_xof_free(xof);
+
+ return 0;
+}
+
diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h
new file mode 100644
index 0000000000..d07bff8cf0
--- /dev/null
+++ b/src/or/hs_ntor.h
@@ -0,0 +1,67 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_HS_NTOR_H
+#define TOR_HS_NTOR_H
+
+#include "or.h"
+
+/* Output length of KDF for key expansion */
+#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \
+ (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2)
+
+/* Key material needed to encode/decode INTRODUCE1 cells */
+typedef struct {
+ /* Key used for encryption of encrypted INTRODUCE1 blob */
+ uint8_t enc_key[CIPHER256_KEY_LEN];
+ /* MAC key used to protect encrypted INTRODUCE1 blob */
+ uint8_t mac_key[DIGEST256_LEN];
+} hs_ntor_intro_cell_keys_t;
+
+/* Key material needed to encode/decode RENDEZVOUS1 cells */
+typedef struct {
+ /* This is the MAC of the HANDSHAKE_INFO field */
+ uint8_t rend_cell_auth_mac[DIGEST256_LEN];
+ /* This is the key seed used to derive further rendezvous crypto keys as
+ * detailed in section 4.2.1 of rend-spec-ng.txt. */
+ uint8_t ntor_key_seed[DIGEST256_LEN];
+} hs_ntor_rend_cell_keys_t;
+
+int hs_ntor_client_get_introduce1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_keypair_t *client_ephemeral_enc_keypair,
+ const uint8_t *subcredential,
+ hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
+
+int hs_ntor_client_get_rendezvous1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *client_ephemeral_enc_keypair,
+ const curve25519_public_key_t *intro_enc_pubkey,
+ const curve25519_public_key_t *service_ephemeral_rend_pubkey,
+ hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
+
+int hs_ntor_service_get_introduce1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *intro_enc_keypair,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ const uint8_t *subcredential,
+ hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
+
+int hs_ntor_service_get_rendezvous1_keys(
+ const ed25519_public_key_t *intro_auth_pubkey,
+ const curve25519_keypair_t *intro_enc_keypair,
+ const curve25519_keypair_t *service_ephemeral_rend_keypair,
+ const curve25519_public_key_t *client_ephemeral_enc_pubkey,
+ hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
+
+int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len);
+
+int hs_ntor_client_rendezvous2_mac_is_good(
+ const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,
+ const uint8_t *rcvd_mac);
+
+#endif
+
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
new file mode 100644
index 0000000000..cb3a49cb8a
--- /dev/null
+++ b/src/or/hs_service.c
@@ -0,0 +1,3087 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service functionality
+ **/
+
+#define HS_SERVICE_PRIVATE
+
+#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "directory.h"
+#include "main.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "router.h"
+#include "routerkeys.h"
+#include "routerlist.h"
+#include "statefile.h"
+
+#include "hs_circuit.h"
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_circuit.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+#include "hs_intropoint.h"
+#include "hs_service.h"
+
+/* Trunnel */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* Helper macro. Iterate over every service in the global map. The var is the
+ * name of the service pointer. */
+#define FOR_EACH_SERVICE_BEGIN(var) \
+ STMT_BEGIN \
+ hs_service_t **var##_iter, *var; \
+ HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \
+ var = *var##_iter;
+#define FOR_EACH_SERVICE_END } STMT_END ;
+
+/* Helper macro. Iterate over both current and previous descriptor of a
+ * service. The var is the name of the descriptor pointer. This macro skips
+ * any descriptor object of the service that is NULL. */
+#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \
+ STMT_BEGIN \
+ hs_service_descriptor_t *var; \
+ for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \
+ ++var ## _loop_idx) { \
+ (var ## _loop_idx == 0) ? (var = service->desc_current) : \
+ (var = service->desc_next); \
+ if (var == NULL) continue;
+#define FOR_EACH_DESCRIPTOR_END } STMT_END ;
+
+/* Onion service directory file names. */
+static const char fname_keyfile_prefix[] = "hs_ed25519";
+static const char fname_hostname[] = "hostname";
+static const char address_tld[] = "onion";
+
+/* Staging list of service object. When configuring service, we add them to
+ * this list considered a staging area and they will get added to our global
+ * map once the keys have been loaded. These two steps are seperated because
+ * loading keys requires that we are an actual running tor process. */
+static smartlist_t *hs_service_staging_list;
+
+static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc);
+
+/* Helper: Function to compare two objects in the service map. Return 1 if the
+ * two service have the same master public identity key. */
+static inline int
+hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second)
+{
+ tor_assert(first);
+ tor_assert(second);
+ /* Simple key compare. */
+ return ed25519_pubkey_eq(&first->keys.identity_pk,
+ &second->keys.identity_pk);
+}
+
+/* Helper: Function for the service hash table code below. The key used is the
+ * master public identity key which is ultimately the onion address. */
+static inline unsigned int
+hs_service_ht_hash(const hs_service_t *service)
+{
+ tor_assert(service);
+ return (unsigned int) siphash24g(service->keys.identity_pk.pubkey,
+ sizeof(service->keys.identity_pk.pubkey));
+}
+
+/* This is _the_ global hash map of hidden services which indexed the service
+ * contained in it by master public identity key which is roughly the onion
+ * address of the service. */
+static struct hs_service_ht *hs_service_map;
+
+/* Register the service hash table. */
+HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */
+ hs_service_t, /* Object contained in the map. */
+ hs_service_node, /* The name of the HT_ENTRY member. */
+ hs_service_ht_hash, /* Hashing function. */
+ hs_service_ht_eq) /* Compare function for objects. */
+
+HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node,
+ hs_service_ht_hash, hs_service_ht_eq,
+ 0.6, tor_reallocarray, tor_free_)
+
+/* Query the given service map with a public key and return a service object
+ * if found else NULL. It is also possible to set a directory path in the
+ * search query. If pk is NULL, then it will be set to zero indicating the
+ * hash table to compare the directory path instead. */
+STATIC hs_service_t *
+find_service(hs_service_ht *map, const ed25519_public_key_t *pk)
+{
+ hs_service_t dummy_service;
+ tor_assert(map);
+ tor_assert(pk);
+ memset(&dummy_service, 0, sizeof(dummy_service));
+ ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk);
+ return HT_FIND(hs_service_ht, map, &dummy_service);
+}
+
+/* Register the given service in the given map. If the service already exists
+ * in the map, -1 is returned. On success, 0 is returned and the service
+ * ownership has been transfered to the global map. */
+STATIC int
+register_service(hs_service_ht *map, hs_service_t *service)
+{
+ tor_assert(map);
+ tor_assert(service);
+ tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk));
+
+ if (find_service(map, &service->keys.identity_pk)) {
+ /* Existing service with the same key. Do not register it. */
+ return -1;
+ }
+ /* Taking ownership of the object at this point. */
+ HT_INSERT(hs_service_ht, map, service);
+ return 0;
+}
+
+/* Remove a given service from the given map. If service is NULL or the
+ * service key is unset, return gracefully. */
+STATIC void
+remove_service(hs_service_ht *map, hs_service_t *service)
+{
+ hs_service_t *elm;
+
+ tor_assert(map);
+
+ /* Ignore if no service or key is zero. */
+ if (BUG(service == NULL) ||
+ BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) {
+ return;
+ }
+
+ elm = HT_REMOVE(hs_service_ht, map, service);
+ if (elm) {
+ tor_assert(elm == service);
+ } else {
+ log_warn(LD_BUG, "Could not find service in the global map "
+ "while removing service %s",
+ escaped(service->config.directory_path));
+ }
+}
+
+/* Set the default values for a service configuration object <b>c</b>. */
+static void
+set_service_default_config(hs_service_config_t *c,
+ const or_options_t *options)
+{
+ (void) options;
+ tor_assert(c);
+ c->ports = smartlist_new();
+ c->directory_path = NULL;
+ c->max_streams_per_rdv_circuit = 0;
+ c->max_streams_close_circuit = 0;
+ c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
+ c->allow_unknown_ports = 0;
+ c->is_single_onion = 0;
+ c->dir_group_readable = 0;
+ c->is_ephemeral = 0;
+}
+
+/* From a service configuration object config, clear everything from it
+ * meaning free allocated pointers and reset the values. */
+static void
+service_clear_config(hs_service_config_t *config)
+{
+ if (config == NULL) {
+ return;
+ }
+ tor_free(config->directory_path);
+ if (config->ports) {
+ SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p,
+ rend_service_port_config_free(p););
+ smartlist_free(config->ports);
+ }
+ memset(config, 0, sizeof(*config));
+}
+
+/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_min_introduce2(void)
+{
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_introduce2",
+ INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
+}
+
+/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_max_introduce2(void)
+{
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_introduce2",
+ INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
+}
+
+/* Return the minimum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_min_lifetime(void)
+{
+#define MIN_INTRO_POINT_LIFETIME_TESTING 10
+ if (get_options()->TestingTorNetwork) {
+ return MIN_INTRO_POINT_LIFETIME_TESTING;
+ }
+
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_lifetime",
+ INTRO_POINT_LIFETIME_MIN_SECONDS,
+ 0, INT32_MAX);
+}
+
+/* Return the maximum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_max_lifetime(void)
+{
+#define MAX_INTRO_POINT_LIFETIME_TESTING 30
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_LIFETIME_TESTING;
+ }
+
+ /* The [0, 2147483647] range is quite large to accomodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_lifetime",
+ INTRO_POINT_LIFETIME_MAX_SECONDS,
+ 0, INT32_MAX);
+}
+
+/* Return the number of extra introduction point defined by a consensus
+ * parameter or the default value. */
+static int32_t
+get_intro_point_num_extra(void)
+{
+ /* The [0, 128] range bounds the number of extra introduction point allowed.
+ * Above 128 intro points, it's getting a bit crazy. */
+ return networkstatus_get_param(NULL, "hs_intro_num_extra",
+ NUM_INTRO_POINTS_EXTRA, 0, 128);
+}
+
+/* Helper: Function that needs to return 1 for the HT for each loop which
+ * frees every service in an hash map. */
+static int
+ht_free_service_(struct hs_service_t *service, void *data)
+{
+ (void) data;
+ hs_service_free(service);
+ /* This function MUST return 1 so the given object is then removed from the
+ * service map leading to this free of the object being safe. */
+ return 1;
+}
+
+/* Free every service that can be found in the global map. Once done, clear
+ * and free the global map. */
+static void
+service_free_all(void)
+{
+ if (hs_service_map) {
+ /* The free helper function returns 1 so this is safe. */
+ hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL);
+ HT_CLEAR(hs_service_ht, hs_service_map);
+ tor_free(hs_service_map);
+ hs_service_map = NULL;
+ }
+
+ if (hs_service_staging_list) {
+ /* Cleanup staging list. */
+ SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s,
+ hs_service_free(s));
+ smartlist_free(hs_service_staging_list);
+ hs_service_staging_list = NULL;
+ }
+}
+
+/* Free a given service intro point object. */
+STATIC void
+service_intro_point_free(hs_service_intro_point_t *ip)
+{
+ if (!ip) {
+ return;
+ }
+ memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp));
+ memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp));
+ crypto_pk_free(ip->legacy_key);
+ replaycache_free(ip->replay_cache);
+ hs_intropoint_clear(&ip->base);
+ tor_free(ip);
+}
+
+/* Helper: free an hs_service_intro_point_t object. This function is used by
+ * digest256map_free() which requires a void * pointer. */
+static void
+service_intro_point_free_(void *obj)
+{
+ service_intro_point_free(obj);
+}
+
+/* Return a newly allocated service intro point and fully initialized from the
+ * given extend_info_t ei if non NULL. If is_legacy is true, we also generate
+ * the legacy key. On error, NULL is returned. */
+STATIC hs_service_intro_point_t *
+service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
+{
+ hs_desc_link_specifier_t *ls;
+ hs_service_intro_point_t *ip;
+
+ ip = tor_malloc_zero(sizeof(*ip));
+ /* We'll create the key material. No need for extra strong, those are short
+ * term keys. */
+ ed25519_keypair_generate(&ip->auth_key_kp, 0);
+
+ { /* Set introduce2 max cells limit */
+ int32_t min_introduce2_cells = get_intro_point_min_introduce2();
+ int32_t max_introduce2_cells = get_intro_point_max_introduce2();
+ if (BUG(max_introduce2_cells < min_introduce2_cells)) {
+ goto err;
+ }
+ ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells,
+ max_introduce2_cells);
+ }
+ { /* Set intro point lifetime */
+ int32_t intro_point_min_lifetime = get_intro_point_min_lifetime();
+ int32_t intro_point_max_lifetime = get_intro_point_max_lifetime();
+ if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) {
+ goto err;
+ }
+ ip->time_to_expire = time(NULL) +
+ crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime);
+ }
+
+ ip->replay_cache = replaycache_new(0, 0);
+
+ /* Initialize the base object. We don't need the certificate object. */
+ ip->base.link_specifiers = smartlist_new();
+
+ /* Generate the encryption key for this intro point. */
+ curve25519_keypair_generate(&ip->enc_key_kp, 0);
+ /* Figure out if this chosen node supports v3 or is legacy only. */
+ if (is_legacy) {
+ ip->base.is_only_legacy = 1;
+ /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */
+ ip->legacy_key = crypto_pk_new();
+ if (crypto_pk_generate_key(ip->legacy_key) < 0) {
+ goto err;
+ }
+ }
+
+ if (ei == NULL) {
+ goto done;
+ }
+
+ /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are
+ * mandatory. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV4);
+ /* It is impossible to have an extend info object without a v4. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID);
+ /* It is impossible to have an extend info object without an identity
+ * digest. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ /* ed25519 identity key is optional for intro points */
+ ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* IPv6 is optional. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV6);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* Finally, copy onion key from the extend_info_t object. */
+ memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key));
+
+ done:
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ return NULL;
+}
+
+/* Add the given intro point object to the given intro point map. The intro
+ * point MUST have its RSA encryption key set if this is a legacy type or the
+ * authentication key set otherwise. */
+STATIC void
+service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip)
+{
+ hs_service_intro_point_t *old_ip_entry;
+
+ tor_assert(map);
+ tor_assert(ip);
+
+ old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip);
+ /* Make sure we didn't just try to double-add an intro point */
+ tor_assert_nonfatal(!old_ip_entry);
+}
+
+/* For a given service, remove the intro point from that service's descriptors
+ * (check both current and next descriptor) */
+STATIC void
+service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ tor_assert(service);
+ tor_assert(ip);
+
+ /* Trying all descriptors. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We'll try to remove the descriptor on both descriptors which is not
+ * very expensive to do instead of doing loopup + remove. */
+ digest256map_remove(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For a given service and authentication key, return the intro point or NULL
+ * if not found. This will check both descriptors in the service. */
+STATIC hs_service_intro_point_t *
+service_intro_point_find(const hs_service_t *service,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(service);
+ tor_assert(auth_key);
+
+ /* Trying all descriptors to find the right intro point.
+ *
+ * Even if we use the same node as intro point in both descriptors, the node
+ * will have a different intro auth key for each descriptor since we generate
+ * a new one everytime we pick an intro point.
+ *
+ * After #22893 gets implemented, intro points will be moved to be
+ * per-service instead of per-descriptor so this function will need to
+ * change.
+ */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if ((ip = digest256map_get(desc->intro_points.map,
+ auth_key->pubkey)) != NULL) {
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return ip;
+}
+
+/* For a given service and intro point, return the descriptor for which the
+ * intro point is assigned to. NULL is returned if not found. */
+STATIC hs_service_descriptor_t *
+service_desc_find_by_intro(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_service_descriptor_t *descp = NULL;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if (digest256map_get(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey)) {
+ descp = desc;
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return descp;
+}
+
+/* From a circuit identifier, get all the possible objects associated with the
+ * ident. If not NULL, service, ip or desc are set if the object can be found.
+ * They are untouched if they can't be found.
+ *
+ * This is an helper function because we do those lookups often so it's more
+ * convenient to simply call this functions to get all the things at once. */
+STATIC void
+get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service, hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc)
+{
+ hs_service_t *s;
+
+ tor_assert(ident);
+
+ /* Get service object from the circuit identifier. */
+ s = find_service(hs_service_map, &ident->identity_pk);
+ if (s && service) {
+ *service = s;
+ }
+
+ /* From the service object, get the intro point object of that circuit. The
+ * following will query both descriptors intro points list. */
+ if (s && ip) {
+ *ip = service_intro_point_find(s, &ident->intro_auth_pk);
+ }
+
+ /* Get the descriptor for this introduction point and service. */
+ if (s && ip && *ip && desc) {
+ *desc = service_desc_find_by_intro(s, *ip);
+ }
+}
+
+/* From a given intro point, return the first link specifier of type
+ * encountered in the link specifier list. Return NULL if it can't be found.
+ *
+ * The caller does NOT have ownership of the object, the intro point does. */
+static hs_desc_link_specifier_t *
+get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type)
+{
+ hs_desc_link_specifier_t *lnk_spec = NULL;
+
+ tor_assert(ip);
+
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ hs_desc_link_specifier_t *, ls) {
+ if (ls->type == type) {
+ lnk_spec = ls;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ end:
+ return lnk_spec;
+}
+
+/* Given a service intro point, return the node_t associated to it. This can
+ * return NULL if the given intro point has no legacy ID or if the node can't
+ * be found in the consensus. */
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip)
+{
+ const hs_desc_link_specifier_t *ls;
+
+ tor_assert(ip);
+
+ ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ if (BUG(!ls)) {
+ return NULL;
+ }
+ /* XXX In the future, we want to only use the ed25519 ID (#22173). */
+ return node_get_by_id((const char *) ls->u.legacy_id);
+}
+
+/* Given a service intro point, return the extend_info_t for it. This can
+ * return NULL if the node can't be found for the intro point or the extend
+ * info can't be created for the found node. If direct_conn is set, the extend
+ * info is validated on if we can connect directly. */
+static extend_info_t *
+get_extend_info_from_intro_point(const hs_service_intro_point_t *ip,
+ unsigned int direct_conn)
+{
+ extend_info_t *info = NULL;
+ const node_t *node;
+
+ tor_assert(ip);
+
+ node = get_node_from_intro_point(ip);
+ if (node == NULL) {
+ /* This can happen if the relay serving as intro point has been removed
+ * from the consensus. In that case, the intro point will be removed from
+ * the descriptor during the scheduled events. */
+ goto end;
+ }
+
+ /* In the case of a direct connection (single onion service), it is possible
+ * our firewall policy won't allow it so this can return a NULL value. */
+ info = extend_info_from_node(node, direct_conn);
+
+ end:
+ return info;
+}
+
+/* Return the number of introduction points that are established for the
+ * given descriptor. */
+static unsigned int
+count_desc_circuit_established(const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ count += ip->circuit_established;
+ } DIGEST256MAP_FOREACH_END;
+
+ return count;
+}
+
+/* Close all rendezvous circuits for the given service. */
+static void
+close_service_rp_circuits(hs_service_t *service)
+{
+ origin_circuit_t *ocirc = NULL;
+
+ tor_assert(service);
+
+ /* The reason we go over all circuit instead of using the circuitmap API is
+ * because most hidden service circuits are rendezvous circuits so there is
+ * no real improvement at getting all rendezvous circuits from the
+ * circuitmap and then going over them all to find the right ones.
+ * Furthermore, another option would have been to keep a list of RP cookies
+ * for a service but it creates an engineering complexity since we don't
+ * have a "RP circuit closed" event to clean it up properly so we avoid a
+ * memory DoS possibility. */
+
+ while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) {
+ /* Only close circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, it is removed from the
+ * hs circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ }
+}
+
+/* Close the circuit(s) for the given map of introduction points. */
+static void
+close_intro_circuits(hs_service_intropoints_t *intro_points)
+{
+ tor_assert(intro_points);
+
+ DIGEST256MAP_FOREACH(intro_points->map, key,
+ const hs_service_intro_point_t *, ip) {
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, the circuit is removed
+ * from the HS circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Close all introduction circuits for the given service. */
+static void
+close_service_intro_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ close_intro_circuits(&desc->intro_points);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Close any circuits related to the given service. */
+static void
+close_service_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* Only support for version >= 3. */
+ if (BUG(service->config.version < HS_VERSION_THREE)) {
+ return;
+ }
+ /* Close intro points. */
+ close_service_intro_circuits(service);
+ /* Close rendezvous points. */
+ close_service_rp_circuits(service);
+}
+
+/* Move introduction points from the src descriptor to the dst descriptor. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_descriptor_intro_points(hs_service_descriptor_t *src,
+ hs_service_descriptor_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ digest256map_free(dst->intro_points.map, service_intro_point_free_);
+ dst->intro_points.map = src->intro_points.map;
+ /* Nullify the source. */
+ src->intro_points.map = NULL;
+}
+
+/* Move introduction points from the src service to the dst service. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_intro_points(hs_service_t *src, hs_service_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ if (src->desc_current && dst->desc_current) {
+ move_descriptor_intro_points(src->desc_current, dst->desc_current);
+ }
+ if (src->desc_next && dst->desc_next) {
+ move_descriptor_intro_points(src->desc_next, dst->desc_next);
+ }
+}
+
+/* Move every ephemeral services from the src service map to the dst service
+ * map. It is possible that a service can't be register to the dst map which
+ * won't stop the process of moving them all but will trigger a log warn. */
+static void
+move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst)
+{
+ hs_service_t **iter, **next;
+
+ tor_assert(src);
+ tor_assert(dst);
+
+ /* Iterate over the map to find ephemeral service and move them to the other
+ * map. We loop using this method to have a safe removal process. */
+ for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) {
+ hs_service_t *s = *iter;
+ if (!s->config.is_ephemeral) {
+ /* Yeah, we are in a very manual loop :). */
+ next = HT_NEXT(hs_service_ht, src, iter);
+ continue;
+ }
+ /* Remove service from map and then register to it to the other map.
+ * Reminder that "*iter" and "s" are the same thing. */
+ next = HT_NEXT_RMV(hs_service_ht, src, iter);
+ if (register_service(dst, s) < 0) {
+ log_warn(LD_BUG, "Ephemeral service key is already being used. "
+ "Skipping.");
+ }
+ }
+}
+
+/* Return a const string of the directory path escaped. If this is an
+ * ephemeral service, it returns "[EPHEMERAL]". This can only be called from
+ * the main thread because escaped() uses a static variable. */
+static const char *
+service_escaped_dir(const hs_service_t *s)
+{
+ return (s->config.is_ephemeral) ? "[EPHEMERAL]" :
+ escaped(s->config.directory_path);
+}
+
+/* Register services that are in the staging list. Once this function returns,
+ * the global service map will be set with the right content and all non
+ * surviving services will be cleaned up. */
+static void
+register_all_services(void)
+{
+ struct hs_service_ht *new_service_map;
+
+ tor_assert(hs_service_staging_list);
+
+ /* We'll save us some allocation and computing time. */
+ if (smartlist_len(hs_service_staging_list) == 0) {
+ return;
+ }
+
+ /* Allocate a new map that will replace the current one. */
+ new_service_map = tor_malloc_zero(sizeof(*new_service_map));
+ HT_INIT(hs_service_ht, new_service_map);
+
+ /* First step is to transfer all ephemeral services from the current global
+ * map to the new one we are constructing. We do not prune ephemeral
+ * services as the only way to kill them is by deleting it from the control
+ * port or stopping the tor daemon. */
+ move_ephemeral_services(hs_service_map, new_service_map);
+
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) {
+ hs_service_t *s;
+
+ /* Check if that service is already in our global map and if so, we'll
+ * transfer the intro points to it. */
+ s = find_service(hs_service_map, &snew->keys.identity_pk);
+ if (s) {
+ /* Pass ownership of intro points from s (the current service) to snew
+ * (the newly configured one). */
+ move_intro_points(s, snew);
+ /* Remove the service from the global map because after this, we need to
+ * go over the remaining service in that map that aren't surviving the
+ * reload to close their circuits. */
+ remove_service(hs_service_map, s);
+ }
+ /* Great, this service is now ready to be added to our new map. */
+ if (BUG(register_service(new_service_map, snew) < 0)) {
+ /* This should never happen because prior to registration, we validate
+ * every service against the entire set. Not being able to register a
+ * service means we failed to validate correctly. In that case, don't
+ * break tor and ignore the service but tell user. */
+ log_warn(LD_BUG, "Unable to register service with directory %s",
+ service_escaped_dir(snew));
+ SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew);
+ hs_service_free(snew);
+ }
+ } SMARTLIST_FOREACH_END(snew);
+
+ /* Close any circuits associated with the non surviving services. Every
+ * service in the current global map are roaming. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ close_service_circuits(service);
+ } FOR_EACH_SERVICE_END;
+
+ /* Time to make the switch. We'll clear the staging list because its content
+ * has now changed ownership to the map. */
+ smartlist_clear(hs_service_staging_list);
+ service_free_all();
+ hs_service_map = new_service_map;
+}
+
+/* Write the onion address of a given service to the given filename fname_ in
+ * the service directory. Return 0 on success else -1 on error. */
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_)
+{
+ int ret = -1;
+ char *fname = NULL;
+ char *addr_buf = NULL;
+
+ tor_assert(service);
+ tor_assert(fname_);
+
+ /* Construct the full address with the onion tld and write the hostname file
+ * to disk. */
+ tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld);
+ /* Notice here that we use the given "fname_". */
+ fname = hs_path_from_filename(service->config.directory_path, fname_);
+ if (write_str_to_file(fname, addr_buf, 0) < 0) {
+ log_warn(LD_REND, "Could not write onion address to hostname file %s",
+ escaped(fname));
+ goto end;
+ }
+
+#ifndef _WIN32
+ if (service->config.dir_group_readable) {
+ /* Mode to 0640. */
+ if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) {
+ log_warn(LD_FS, "Unable to make onion service hostname file %s "
+ "group-readable.", escaped(fname));
+ }
+ }
+#endif /* _WIN32 */
+
+ /* Success. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ tor_free(addr_buf);
+ return ret;
+}
+
+/* Load and/or generate private keys for the given service. On success, the
+ * hostname file will be written to disk along with the master private key iff
+ * the service is not configured for offline keys. Return 0 on success else -1
+ * on failure. */
+static int
+load_service_keys(hs_service_t *service)
+{
+ int ret = -1;
+ char *fname = NULL;
+ ed25519_keypair_t *kp;
+ const hs_service_config_t *config;
+
+ tor_assert(service);
+
+ config = &service->config;
+
+ /* Create and fix permission on service directory. We are about to write
+ * files to that directory so make sure it exists and has the right
+ * permissions. We do this here because at this stage we know that Tor is
+ * actually running and the service we have has been validated. */
+ if (BUG(hs_check_service_private_dir(get_options()->User,
+ config->directory_path,
+ config->dir_group_readable, 1) < 0)) {
+ goto end;
+ }
+
+ /* Try to load the keys from file or generate it if not found. */
+ fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix);
+ /* Don't ask for key creation, we want to know if we were able to load it or
+ * we had to generate it. Better logging! */
+ kp = ed_key_init_from_file(fname, 0, LOG_INFO, NULL, 0, 0, 0, NULL);
+ if (!kp) {
+ log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname);
+ /* We'll now try to generate the keys and for it we want the strongest
+ * randomness for it. The keypair will be written in different files. */
+ uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG |
+ INIT_ED_KEY_SPLIT;
+ kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0,
+ NULL);
+ if (!kp) {
+ log_warn(LD_REND, "Unable to generate keys and save in %s.", fname);
+ goto end;
+ }
+ }
+
+ /* Copy loaded or generated keys to service object. */
+ ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey);
+ memcpy(&service->keys.identity_sk, &kp->seckey,
+ sizeof(service->keys.identity_sk));
+ /* This does a proper memory wipe. */
+ ed25519_keypair_free(kp);
+
+ /* Build onion address from the newly loaded keys. */
+ tor_assert(service->config.version <= UINT8_MAX);
+ hs_build_address(&service->keys.identity_pk,
+ (uint8_t) service->config.version,
+ service->onion_address);
+
+ /* Write onion address to hostname file. */
+ if (write_address_to_file(service, fname_hostname) < 0) {
+ goto end;
+ }
+
+ /* Succes. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ return ret;
+}
+
+/* Free a given service descriptor object and all key material is wiped. */
+STATIC void
+service_descriptor_free(hs_service_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+ hs_descriptor_free(desc->desc);
+ memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp));
+ memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp));
+ SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+ smartlist_free(desc->hsdir_missing_info);
+ /* Cleanup all intro points. */
+ digest256map_free(desc->intro_points.map, service_intro_point_free_);
+ digestmap_free(desc->intro_points.failed_id, tor_free_);
+ tor_free(desc);
+}
+
+/* Return a newly allocated service descriptor object. */
+STATIC hs_service_descriptor_t *
+service_descriptor_new(void)
+{
+ hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc));
+ sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+ /* Initialize the intro points map. */
+ sdesc->intro_points.map = digest256map_new();
+ sdesc->intro_points.failed_id = digestmap_new();
+ sdesc->hsdir_missing_info = smartlist_new();
+ return sdesc;
+}
+
+/* From the given service, remove all expired failing intro points for each
+ * descriptor. */
+static void
+remove_expired_failing_intro(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the failing intro points list. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) {
+ time_t failure_time = *t;
+ if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) {
+ MAP_DEL_CURRENT(key);
+ tor_free(t);
+ }
+ } DIGESTMAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For the given descriptor desc, put all node_t object found from its failing
+ * intro point list and put them in the given node_list. */
+static void
+setup_intro_point_exclude_list(const hs_service_descriptor_t *desc,
+ smartlist_t *node_list)
+{
+ tor_assert(desc);
+ tor_assert(node_list);
+
+ DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) {
+ (void) t; /* Make gcc happy. */
+ const node_t *node = node_get_by_id(key);
+ if (node) {
+ smartlist_add(node_list, (void *) node);
+ }
+ } DIGESTMAP_FOREACH_END;
+}
+
+/* For the given failing intro point ip, we add its time of failure to the
+ * failed map and index it by identity digest (legacy ID) in the descriptor
+ * desc failed id map. */
+static void
+remember_failing_intro_point(const hs_service_intro_point_t *ip,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ time_t *time_of_failure, *prev_ptr;
+ const hs_desc_link_specifier_t *legacy_ls;
+
+ tor_assert(ip);
+ tor_assert(desc);
+
+ time_of_failure = tor_malloc_zero(sizeof(time_t));
+ *time_of_failure = now;
+ legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ tor_assert(legacy_ls);
+ prev_ptr = digestmap_set(desc->intro_points.failed_id,
+ (const char *) legacy_ls->u.legacy_id,
+ time_of_failure);
+ tor_free(prev_ptr);
+}
+
+/* Copy the descriptor link specifier object from src to dst. */
+static void
+link_specifier_copy(hs_desc_link_specifier_t *dst,
+ const hs_desc_link_specifier_t *src)
+{
+ tor_assert(dst);
+ tor_assert(src);
+ memcpy(dst, src, sizeof(hs_desc_link_specifier_t));
+}
+
+/* Using a given descriptor signing keypair signing_kp, a service intro point
+ * object ip and the time now, setup the content of an already allocated
+ * descriptor intro desc_ip.
+ *
+ * Return 0 on success else a negative value. */
+static int
+setup_desc_intro_point(const ed25519_keypair_t *signing_kp,
+ const hs_service_intro_point_t *ip,
+ time_t now, hs_desc_intro_point_t *desc_ip)
+{
+ int ret = -1;
+ time_t nearest_hour = now - (now % 3600);
+
+ tor_assert(signing_kp);
+ tor_assert(ip);
+ tor_assert(desc_ip);
+
+ /* Copy the onion key. */
+ memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key));
+
+ /* Key and certificate material. */
+ desc_ip->auth_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_AUTH_HS_IP_KEY,
+ &ip->auth_key_kp.pubkey,
+ nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->auth_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create intro point auth-key certificate");
+ goto done;
+ }
+
+ /* Copy link specifier(s). */
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ const hs_desc_link_specifier_t *, ls) {
+ hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup));
+ link_specifier_copy(dup, ls);
+ smartlist_add(desc_ip->link_specifiers, dup);
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* For a legacy intro point, we'll use an RSA/ed cross certificate. */
+ if (ip->base.is_only_legacy) {
+ desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key);
+ /* Create cross certification cert. */
+ ssize_t cert_len = tor_make_rsa_ed25519_crosscert(
+ &signing_kp->pubkey,
+ desc_ip->legacy.key,
+ nearest_hour + HS_DESC_CERT_LIFETIME,
+ &desc_ip->legacy.cert.encoded);
+ if (cert_len < 0) {
+ log_warn(LD_REND, "Unable to create enc key legacy cross cert.");
+ goto done;
+ }
+ desc_ip->legacy.cert.len = cert_len;
+ }
+
+ /* Encryption key and its cross certificate. */
+ {
+ ed25519_public_key_t ed25519_pubkey;
+
+ /* Use the public curve25519 key. */
+ memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey,
+ sizeof(desc_ip->enc_key));
+ /* The following can't fail. */
+ ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey,
+ &ip->enc_key_kp.pubkey,
+ 0);
+ desc_ip->enc_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_CROSS_HS_IP_KEYS,
+ &ed25519_pubkey, nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->enc_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create enc key curve25519 cross cert.");
+ goto done;
+ }
+ }
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* Using the given descriptor from the given service, build the descriptor
+ * intro point list so we can then encode the descriptor for publication. This
+ * function does not pick intro points, they have to be in the descriptor
+ * current map. Cryptographic material (keys) must be initialized in the
+ * descriptor for this function to make sense. */
+static void
+build_desc_intro_points(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Ease our life. */
+ encrypted = &desc->desc->encrypted_data;
+ /* Cleanup intro points, we are about to set them from scratch. */
+ hs_descriptor_clear_intro_points(desc->desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new();
+ if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) {
+ hs_desc_intro_point_free(desc_ip);
+ continue;
+ }
+ /* We have a valid descriptor intro point. Add it to the list. */
+ smartlist_add(encrypted->intro_points, desc_ip);
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Populate the descriptor encrypted section fomr the given service object.
+ * This will generate a valid list of introduction points that can be used
+ * after for circuit creation. Return 0 on success else -1 on error. */
+static int
+build_service_desc_encrypted(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ encrypted = &desc->desc->encrypted_data;
+
+ encrypted->create2_ntor = 1;
+ encrypted->single_onion_service = service->config.is_single_onion;
+
+ /* Setup introduction points from what we have in the service. */
+ if (encrypted->intro_points == NULL) {
+ encrypted->intro_points = smartlist_new();
+ }
+ /* We do NOT build introduction point yet, we only do that once the circuit
+ * have been opened. Until we have the right number of introduction points,
+ * we do not encode anything in the descriptor. */
+
+ /* XXX: Support client authorization (#20700). */
+ encrypted->intro_auth_types = NULL;
+ return 0;
+}
+
+/* Populare the descriptor plaintext section from the given service object.
+ * The caller must make sure that the keys in the descriptors are valid that
+ * is are non-zero. Return 0 on success else -1 on error. */
+static int
+build_service_desc_plaintext(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ int ret = -1;
+ hs_desc_plaintext_data_t *plaintext;
+
+ tor_assert(service);
+ tor_assert(desc);
+ /* XXX: Use a "assert_desc_ok()" ? */
+ tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp,
+ sizeof(desc->blinded_kp)));
+ tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp,
+ sizeof(desc->signing_kp)));
+
+ /* Set the subcredential. */
+ hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
+ desc->desc->subcredential);
+
+ plaintext = &desc->desc->plaintext_data;
+
+ plaintext->version = service->config.version;
+ plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME;
+ plaintext->signing_key_cert =
+ tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC,
+ &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (plaintext->signing_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create descriptor signing certificate for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ /* Copy public key material to go in the descriptor. */
+ ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey);
+ ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey);
+ /* Success. */
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+/* For the given service and descriptor object, create the key material which
+ * is the blinded keypair and the descriptor signing keypair. Return 0 on
+ * success else -1 on error where the generated keys MUST be ignored. */
+static int
+build_service_desc_keys(const hs_service_t *service,
+ hs_service_descriptor_t *desc,
+ uint64_t time_period_num)
+{
+ int ret = 0;
+ ed25519_keypair_t kp;
+
+ tor_assert(desc);
+ tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk,
+ ED25519_PUBKEY_LEN));
+
+ /* XXX: Support offline key feature (#18098). */
+
+ /* Copy the identity keys to the keypair so we can use it to create the
+ * blinded key. */
+ memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey));
+ memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey));
+ /* Build blinded keypair for this time period. */
+ hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp);
+ /* Let's not keep too much traces of our keys in memory. */
+ memwipe(&kp, 0, sizeof(kp));
+
+ /* No need for extra strong, this is a temporary key only for this
+ * descriptor. Nothing long term. */
+ if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) {
+ log_warn(LD_REND, "Can't generate descriptor signing keypair for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* Given a service and the current time, build a descriptor for the service.
+ * This function does not pick introduction point, this needs to be done by
+ * the update function. On success, desc_out will point to the newly allocated
+ * descriptor object.
+ *
+ * This can error if we are unable to create keys or certificate. */
+static void
+build_service_descriptor(hs_service_t *service, time_t now,
+ uint64_t time_period_num,
+ hs_service_descriptor_t **desc_out)
+{
+ char *encoded_desc;
+ hs_service_descriptor_t *desc;
+
+ tor_assert(service);
+ tor_assert(desc_out);
+
+ desc = service_descriptor_new();
+ desc->time_period_num = time_period_num;
+
+ /* Create the needed keys so we can setup the descriptor content. */
+ if (build_service_desc_keys(service, desc, time_period_num) < 0) {
+ goto err;
+ }
+ /* Setup plaintext descriptor content. */
+ if (build_service_desc_plaintext(service, desc, now) < 0) {
+ goto err;
+ }
+ /* Setup encrypted descriptor content. */
+ if (build_service_desc_encrypted(service, desc) < 0) {
+ goto err;
+ }
+
+ /* Set the revision counter for this descriptor */
+ set_descriptor_revision_counter(desc->desc);
+
+ /* Let's make sure that we've created a descriptor that can actually be
+ * encoded properly. This function also checks if the encoded output is
+ * decodable after. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto err;
+ }
+ tor_free(encoded_desc);
+
+ /* Assign newly built descriptor to the next slot. */
+ *desc_out = desc;
+ return;
+
+ err:
+ service_descriptor_free(desc);
+}
+
+/* Build descriptors for each service if needed. There are conditions to build
+ * a descriptor which are details in the function. */
+STATIC void
+build_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ if (service->desc_current == NULL) {
+ /* This means we just booted up because else this descriptor will never
+ * be NULL as it should always point to the descriptor that was in
+ * desc_next after rotation. */
+ build_service_descriptor(service, now, hs_get_time_period_num(now),
+ &service->desc_current);
+
+ log_info(LD_REND, "Hidden service %s current descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ /* A next descriptor to NULL indicate that we need to build a fresh one if
+ * we are in the overlap period for the _next_ time period since it means
+ * we either just booted or we just rotated our descriptors. */
+ if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) {
+ build_service_descriptor(service, now, hs_get_next_time_period_num(now),
+ &service->desc_next);
+ log_info(LD_REND, "Hidden service %s next descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Randomly pick a node to become an introduction point but not present in the
+ * given exclude_nodes list. The chosen node is put in the exclude list
+ * regardless of success or not because in case of failure, the node is simply
+ * unsusable from that point on. If direct_conn is set, try to pick a node
+ * that our local firewall/policy allows to directly connect to and if not,
+ * fallback to a normal 3-hop node. Return a newly allocated service intro
+ * point ready to be used for encoding. NULL on error. */
+static hs_service_intro_point_t *
+pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
+{
+ const node_t *node;
+ extend_info_t *info = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ /* Normal 3-hop introduction point flags. */
+ router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC;
+ /* Single onion flags. */
+ router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN;
+
+ node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes,
+ direct_conn ? direct_flags : flags);
+ if (node == NULL && direct_conn) {
+ /* Unable to find a node for direct connection, let's fall back to a
+ * normal 3-hop node. */
+ node = router_choose_random_node(exclude_nodes,
+ get_options()->ExcludeNodes, flags);
+ }
+ if (!node) {
+ goto err;
+ }
+
+ /* We have a suitable node, add it to the exclude list. We do this *before*
+ * we can validate the extend information because even in case of failure,
+ * we don't want to use that node anymore. */
+ smartlist_add(exclude_nodes, (void *) node);
+
+ /* We do this to ease our life but also this call makes appropriate checks
+ * of the node object such as validating ntor support for instance. */
+ info = extend_info_from_node(node, direct_conn);
+ if (BUG(info == NULL)) {
+ goto err;
+ }
+
+ /* Let's do a basic sanity check here so that we don't end up advertising the
+ * ed25519 identity key of relays that don't actually support the link
+ * protocol */
+ if (!node_supports_ed25519_link_authentication(node)) {
+ tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity));
+ }
+
+ /* Create our objects and populate them with the node information. */
+ ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node));
+ if (ip == NULL) {
+ goto err;
+ }
+ extend_info_free(info);
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ extend_info_free(info);
+ return NULL;
+}
+
+/* For a given descriptor from the given service, pick any needed intro points
+ * and update the current map with those newly picked intro points. Return the
+ * number node that might have been added to the descriptor current map. */
+static unsigned int
+pick_needed_intro_points(hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ int i = 0, num_needed_ip;
+ smartlist_t *exclude_nodes = smartlist_new();
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Compute how many intro points we actually need to open. */
+ num_needed_ip = service->config.num_intro_points -
+ digest256map_size(desc->intro_points.map);
+ if (BUG(num_needed_ip < 0)) {
+ /* Let's not make tor freak out here and just skip this. */
+ goto done;
+ }
+
+ /* We want to end up with config.num_intro_points intro points, but if we
+ * have no intro points at all (chances are they all cycled or we are
+ * starting up), we launch get_intro_point_num_extra() extra circuits and
+ * use the first config.num_intro_points that complete. See proposal #155,
+ * section 4 for the rationale of this which is purely for performance.
+ *
+ * The ones after the first config.num_intro_points will be converted to
+ * 'General' internal circuits and then we'll drop them from the list of
+ * intro points. */
+ if (digest256map_size(desc->intro_points.map) == 0) {
+ num_needed_ip += get_intro_point_num_extra();
+ }
+
+ /* Build an exclude list of nodes of our intro point(s). The expiring intro
+ * points are OK to pick again because this is afterall a concept of round
+ * robin so they are considered valid nodes to pick again. */
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *intro_node = get_node_from_intro_point(ip);
+ if (intro_node) {
+ smartlist_add(exclude_nodes, (void*)intro_node);
+ }
+ } DIGEST256MAP_FOREACH_END;
+ /* Also, add the failing intro points that our descriptor encounteered in
+ * the exclude node list. */
+ setup_intro_point_exclude_list(desc, exclude_nodes);
+
+ for (i = 0; i < num_needed_ip; i++) {
+ hs_service_intro_point_t *ip;
+
+ /* This function will add the picked intro point node to the exclude nodes
+ * list so we don't pick the same one at the next iteration. */
+ ip = pick_intro_point(service->config.is_single_onion, exclude_nodes);
+ if (ip == NULL) {
+ /* If we end up unable to pick an introduction point it is because we
+ * can't find suitable node and calling this again is highly unlikely to
+ * give us a valid node all of the sudden. */
+ log_info(LD_REND, "Unable to find a suitable node to be an "
+ "introduction point for service %s.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ /* Valid intro point object, add it to the descriptor current map. */
+ service_intro_point_add(desc->intro_points.map, ip);
+ }
+ /* We've successfully picked all our needed intro points thus none are
+ * missing which will tell our upload process to expect the number of
+ * circuits to be the number of configured intro points circuits and not the
+ * number of intro points object that we have. */
+ desc->missing_intro_points = 0;
+
+ /* Success. */
+ done:
+ /* We don't have ownership of the node_t object in this list. */
+ smartlist_free(exclude_nodes);
+ return i;
+}
+
+/* Update the given descriptor from the given service. The possible update
+ * actions includes:
+ * - Picking missing intro points if needed.
+ * - Incrementing the revision counter if needed.
+ */
+static void
+update_service_descriptor(hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(desc->desc);
+
+ num_intro_points = digest256map_size(desc->intro_points.map);
+
+ /* Pick any missing introduction point(s). */
+ if (num_intro_points < service->config.num_intro_points) {
+ unsigned int num_new_intro_points = pick_needed_intro_points(service,
+ desc);
+ if (num_new_intro_points != 0) {
+ log_info(LD_REND, "Service %s just picked %u intro points and wanted "
+ "%u. It currently has %d intro points. "
+ "Launching ESTABLISH_INTRO circuit shortly.",
+ safe_str_client(service->onion_address),
+ num_new_intro_points,
+ service->config.num_intro_points - num_intro_points,
+ num_intro_points);
+ /* We'll build those introduction point into the descriptor once we have
+ * confirmation that the circuits are opened and ready. However,
+ * indicate that this descriptor should be uploaded from now on. */
+ desc->next_upload_time = now;
+ }
+ /* Were we able to pick all the intro points we needed? If not, we'll
+ * flag the descriptor that it's missing intro points because it
+ * couldn't pick enough which will trigger a descriptor upload. */
+ if ((num_new_intro_points + num_intro_points) <
+ service->config.num_intro_points) {
+ desc->missing_intro_points = 1;
+ }
+ }
+}
+
+/* Update descriptors for each service if needed. */
+STATIC void
+update_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We'll try to update each descriptor that is if certain conditions apply
+ * in order for the descriptor to be updated. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ update_service_descriptor(service, desc, now);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Return true iff the given intro point has expired that is it has been used
+ * for too long or we've reached our max seen INTRODUCE2 cell. */
+STATIC int
+intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now)
+{
+ tor_assert(ip);
+
+ if (ip->introduce2_count >= ip->introduce2_max) {
+ goto expired;
+ }
+
+ if (ip->time_to_expire <= now) {
+ goto expired;
+ }
+
+ /* Not expiring. */
+ return 0;
+ expired:
+ return 1;
+}
+
+/* Go over the given set of intro points for each service and remove any
+ * invalid ones. The conditions for removal are:
+ *
+ * - The node doesn't exists anymore (not in consensus)
+ * OR
+ * - The intro point maximum circuit retry count has been reached and no
+ * circuit can be found associated with it.
+ * OR
+ * - The intro point has expired and we should pick a new one.
+ *
+ * If an intro point is removed, the circuit (if any) is immediately close.
+ * If a circuit can't be found, the intro point is kept if it hasn't reached
+ * its maximum circuit retry value and thus should be retried. */
+static void
+cleanup_intro_points(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the intro points. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Go over the current intro points we have, make sure they are still
+ * valid and remove any of them that aren't. */
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *node = get_node_from_intro_point(ip);
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ int has_expired = intro_point_should_expire(ip, now);
+
+ /* We cleanup an intro point if it has expired or if we do not know the
+ * node_t anymore (removed from our latest consensus) or if we've
+ * reached the maximum number of retry with a non existing circuit. */
+ if (has_expired || node == NULL ||
+ ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ /* Remove intro point from descriptor map. We'll add it to the failed
+ * map if we retried it too many times. */
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+
+ /* XXX: Legacy code does NOT do that, it keeps the circuit open until
+ * a new descriptor is uploaded and then closed all expiring intro
+ * point circuit. Here, we close immediately and because we just
+ * discarded the intro point, a new one will be selected, a new
+ * descriptor created and uploaded. There is no difference to an
+ * attacker between the timing of a new consensus and intro point
+ * rotation (possibly?). */
+ if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) {
+ /* After this, no new cells will be handled on the circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ continue;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/** We just entered overlap period and we need to rotate our <b>service</b>
+ * descriptors */
+static void
+rotate_service_descriptors(hs_service_t *service)
+{
+ if (service->desc_current) {
+ /* Close all IP circuits for the descriptor. */
+ close_intro_circuits(&service->desc_current->intro_points);
+ /* We don't need this one anymore, we won't serve any clients coming with
+ * this service descriptor. */
+ service_descriptor_free(service->desc_current);
+ }
+ /* The next one become the current one and emptying the next will trigger
+ * a descriptor creation for it. */
+ service->desc_current = service->desc_next;
+ service->desc_next = NULL;
+}
+
+/* Rotate descriptors for each service if needed. If we are just entering
+ * the overlap period, rotate them that is point the previous descriptor to
+ * the current and cleanup the previous one. A non existing current
+ * descriptor will trigger a descriptor build for the next time period. */
+STATIC void
+rotate_all_descriptors(time_t now)
+{
+ /* XXX We rotate all our service descriptors at once. In the future it might
+ * be wise, to rotate service descriptors independently to hide that all
+ * those descriptors are on the same tor instance */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We are _not_ in the overlap period so skip rotation. */
+ if (!hs_overlap_mode_is_active(NULL, now)) {
+ service->state.in_overlap_period = 0;
+ continue;
+ }
+ /* We've entered the overlap period already so skip rotation. */
+ if (service->state.in_overlap_period) {
+ continue;
+ }
+ /* It's the first time the service encounters the overlap period so flag
+ * it in order to make sure we don't rotate at next check. */
+ service->state.in_overlap_period = 1;
+
+ /* If we have a next descriptor lined up, rotate the descriptors so that it
+ * becomes current. */
+ if (service->desc_next) {
+ rotate_service_descriptors(service);
+ }
+ log_info(LD_REND, "We've just entered the overlap period. Service %s "
+ "descriptors have been rotated!",
+ safe_str_client(service->onion_address));
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all our services are up
+ * to date and ready for the other scheduled events. This includes looking at
+ * the introduction points status and descriptor rotation time. */
+STATIC void
+run_housekeeping_event(time_t now)
+{
+ /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are
+ * simply moving things around or removing uneeded elements. */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Cleanup invalid intro points from the service descriptor. */
+ cleanup_intro_points(service, now);
+
+ /* Remove expired failing intro point from the descriptor failed list. We
+ * reset them at each INTRO_CIRC_RETRY_PERIOD. */
+ remove_expired_failing_intro(service, now);
+
+ /* At this point, the service is now ready to go through the scheduled
+ * events guaranteeing a valid state. Intro points might be missing from
+ * the descriptors after the cleanup but the update/build process will
+ * make sure we pick those missing ones. */
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all descriptors are up to
+ * date. Once this returns, each service descriptor needs to be considered for
+ * new introduction circuits and then for upload. */
+static void
+run_build_descriptor_event(time_t now)
+{
+ /* For v2 services, this step happens in the upload event. */
+
+ /* Run v3+ events. */
+ /* We start by rotating the descriptors only if needed. */
+ rotate_all_descriptors(now);
+
+ /* Then, we'll try to build new descriptors that we might need. The
+ * condition is that the next descriptor is non existing because it has
+ * been rotated or we just started up. */
+ build_all_descriptors(now);
+
+ /* Finally, we'll check if we should update the descriptors. Missing
+ * introduction points will be picked in this function which is useful for
+ * newly built descriptors. */
+ update_all_descriptors(now);
+}
+
+/* For the given service, launch any intro point circuits that could be
+ * needed. This considers every descriptor of the service. */
+static void
+launch_intro_point_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* For both descriptors, try to launch any missing introduction point
+ * circuits using the current map. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Keep a ref on if we need a direct connection. We use this often. */
+ unsigned int direct_conn = service->config.is_single_onion;
+
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ extend_info_t *ei;
+
+ /* Skip the intro point that already has an existing circuit
+ * (established or not). */
+ if (hs_circ_service_get_intro_circ(ip)) {
+ continue;
+ }
+
+ ei = get_extend_info_from_intro_point(ip, direct_conn);
+ if (ei == NULL) {
+ if (!direct_conn) {
+ /* In case of a multi-hop connection, it should never happen that we
+ * can't get the extend info from the node. Avoid connection and
+ * remove intro point from descriptor in order to recover from this
+ * potential bug. */
+ tor_assert_nonfatal(ei);
+ }
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+ continue;
+ }
+
+ /* Launch a circuit to the intro point. */
+ ip->circuit_retries++;
+ if (hs_circ_launch_intro_point(service, ip, ei) < 0) {
+ log_warn(LD_REND, "Unable to launch intro circuit to node %s "
+ "for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+ /* Intro point will be retried if possible after this. */
+ }
+ extend_info_free(ei);
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Don't try to build more than this many circuits before giving up for a
+ * while. Dynamically calculated based on the configured number of intro
+ * points for the given service and how many descriptor exists. The default
+ * use case of 3 introduction points and two descriptors will allow 28
+ * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */
+static unsigned int
+get_max_intro_circ_per_period(const hs_service_t *service)
+{
+ unsigned int count = 0;
+ unsigned int multiplier = 0;
+ unsigned int num_wanted_ip;
+
+ tor_assert(service);
+ tor_assert(service->config.num_intro_points <=
+ HS_CONFIG_V3_MAX_INTRO_POINTS);
+
+/* For a testing network, allow to do it for the maximum amount so circuit
+ * creation and rotation and so on can actually be tested without limit. */
+#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING;
+ }
+
+ num_wanted_ip = service->config.num_intro_points;
+
+ /* The calculation is as follow. We have a number of intro points that we
+ * want configured as a torrc option (num_intro_points). We then add an
+ * extra value so we can launch multiple circuits at once and pick the
+ * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll
+ * pick 5 intros and launch 5 circuits. */
+ count += (num_wanted_ip + get_intro_point_num_extra());
+
+ /* Then we add the number of retries that is possible to do for each intro
+ * point. If we want 3 intros, we'll allow 3 times the number of possible
+ * retry. */
+ count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES);
+
+ /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we
+ * have none. */
+ multiplier += (service->desc_current) ? 1 : 0;
+ multiplier += (service->desc_next) ? 1 : 0;
+
+ return (count * multiplier);
+}
+
+/* For the given service, return 1 if the service is allowed to launch more
+ * introduction circuits else 0 if the maximum has been reached for the retry
+ * period of INTRO_CIRC_RETRY_PERIOD. */
+STATIC int
+can_service_launch_intro_circuit(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* Consider the intro circuit retry period of the service. */
+ if (now > (service->state.intro_circ_retry_started_time +
+ INTRO_CIRC_RETRY_PERIOD)) {
+ service->state.intro_circ_retry_started_time = now;
+ service->state.num_intro_circ_launched = 0;
+ goto allow;
+ }
+ /* Check if we can still launch more circuits in this period. */
+ if (service->state.num_intro_circ_launched <=
+ get_max_intro_circ_per_period(service)) {
+ goto allow;
+ }
+
+ /* Rate limit log that we've reached our circuit creation limit. */
+ {
+ char *msg;
+ time_t elapsed_time = now - service->state.intro_circ_retry_started_time;
+ static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD);
+ if ((msg = rate_limit_log(&rlimit, now))) {
+ log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit "
+ "of %u per %d seconds. It launched %u circuits in "
+ "the last %ld seconds. Will retry in %ld seconds.",
+ safe_str_client(service->onion_address),
+ get_max_intro_circ_per_period(service),
+ INTRO_CIRC_RETRY_PERIOD,
+ service->state.num_intro_circ_launched, elapsed_time,
+ INTRO_CIRC_RETRY_PERIOD - elapsed_time);
+ tor_free(msg);
+ }
+ }
+
+ /* Not allow. */
+ return 0;
+ allow:
+ return 1;
+}
+
+/* Scheduled event run from the main loop. Make sure we have all the circuits
+ * we need for each service. */
+static void
+run_build_circuit_event(time_t now)
+{
+ /* Make sure we can actually have enough information or able to build
+ * internal circuits as required by services. */
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
+ !have_completed_a_circuit()) {
+ return;
+ }
+
+ /* Run v2 check. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_intro_points(now);
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* For introduction circuit, we need to make sure we don't stress too much
+ * circuit creation so make sure this service is respecting that limit. */
+ if (can_service_launch_intro_circuit(service, now)) {
+ /* Launch intro point circuits if needed. */
+ launch_intro_point_circuits(service);
+ /* Once the circuits have opened, we'll make sure to update the
+ * descriptor intro point list and cleanup any extraneous. */
+ }
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the given
+ * hidden service directory. This does nothing if PublishHidServDescriptors
+ * is false. */
+static void
+upload_descriptor_to_hsdir(const hs_service_t *service,
+ hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+ char version_str[4] = {0}, *encoded_desc = NULL;
+ directory_request_t *dir_req;
+ hs_ident_dir_conn_t ident;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(hsdir);
+
+ memset(&ident, 0, sizeof(ident));
+
+ /* Let's avoid doing that if tor is configured to not publish. */
+ if (!get_options()->PublishHidServDescriptors) {
+ log_info(LD_REND, "Service %s not publishing descriptor. "
+ "PublishHidServDescriptors is set to 1.",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+
+ /* First of all, we'll encode the descriptor. This should NEVER fail but
+ * just in case, let's make sure we have an actual usable descriptor. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto end;
+ }
+
+ /* Setup the connection identifier. */
+ ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk);
+ /* This is our resource when uploading which is used to construct the URL
+ * with the version number: "/tor/hs/<version>/publish". */
+ tor_snprintf(version_str, sizeof(version_str), "%u",
+ service->config.version);
+
+ /* Build the directory request for this HSDir. */
+ dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC);
+ directory_request_set_routerstatus(dir_req, hsdir->rs);
+ directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS);
+ directory_request_set_resource(dir_req, version_str);
+ directory_request_set_payload(dir_req, encoded_desc,
+ strlen(encoded_desc));
+ /* The ident object is copied over the directory connection object once
+ * the directory request is initiated. */
+ directory_request_upload_set_hs_ident(dir_req, &ident);
+
+ /* Initiate the directory request to the hsdir.*/
+ directory_initiate_request(dir_req);
+ directory_request_free(dir_req);
+
+ /* Logging so we know where it was sent. */
+ {
+ int is_next_desc = (service->desc_next == desc);
+ const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next :
+ hsdir->hsdir_index->current;
+ log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64
+ " initiated upload request to %s with index %s",
+ safe_str_client(service->onion_address),
+ (is_next_desc) ? "next" : "current",
+ desc->desc->plaintext_data.revision_counter,
+ safe_str_client(node_describe(hsdir)),
+ safe_str_client(hex_str((const char *) index, 32)));
+ }
+
+ /* XXX: Inform control port of the upload event (#20699). */
+ end:
+ tor_free(encoded_desc);
+ return;
+}
+
+/** Return a newly-allocated string for our state file which contains revision
+ * counter information for <b>desc</b>. The format is:
+ *
+ * HidServRevCounter <blinded_pubkey> <rev_counter>
+ */
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc)
+{
+ char *state_str = NULL;
+ char blinded_pubkey_b64[ED25519_BASE64_LEN+1];
+ uint64_t rev_counter = desc->desc->plaintext_data.revision_counter;
+ const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey;
+
+ /* Turn the blinded key into b64 so that we save it on state */
+ tor_assert(blinded_pubkey);
+ if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) {
+ goto done;
+ }
+
+ /* Format is: <blinded key> <rev counter> */
+ tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter);
+
+ log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!",
+ rev_counter, blinded_pubkey_b64);
+
+ done:
+ return state_str;
+}
+
+/** Update HS descriptor revision counters in our state by removing the old
+ * ones and writing down the ones that are currently active. */
+static void
+update_revision_counters_in_state(void)
+{
+ config_line_t *lines = NULL;
+ config_line_t **nextline = &lines;
+ or_state_t *state = get_or_state();
+
+ /* Prepare our state structure with the rev counters */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We don't want to save zero counters */
+ if (desc->desc->plaintext_data.revision_counter == 0) {
+ continue;
+ }
+
+ *nextline = tor_malloc_zero(sizeof(config_line_t));
+ (*nextline)->key = tor_strdup("HidServRevCounter");
+ (*nextline)->value = encode_desc_rev_counter_for_state(desc);
+ nextline = &(*nextline)->next;
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+
+ /* Remove the old rev counters, and replace them with the new ones */
+ config_free_lines(state->HidServRevCounter);
+ state->HidServRevCounter = lines;
+
+ /* Set the state as dirty since we just edited it */
+ if (!get_options()->AvoidDiskWrites) {
+ or_state_mark_dirty(state, 0);
+ }
+}
+
+/** Scan the string <b>state_line</b> for the revision counter of the service
+ * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the
+ * line is relevant to this service, and return the cached revision
+ * counter. Else set <b>service_found_out</b> to False. */
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out)
+{
+ smartlist_t *items = NULL;
+ int ok;
+ ed25519_public_key_t pubkey_in_state;
+ uint64_t rev_counter = 0;
+
+ tor_assert(service_found_out);
+ tor_assert(state_line);
+ tor_assert(blinded_pubkey);
+
+ /* Assume that the line is not for this service */
+ *service_found_out = 0;
+
+ /* Start parsing the state line */
+ items = smartlist_new();
+ smartlist_split_string(items, state_line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring.");
+ goto done;
+ }
+
+ char *b64_key_str = smartlist_get(items, 0);
+ char *saved_rev_counter_str = smartlist_get(items, 1);
+
+ /* Parse blinded key to check if it's for this hidden service */
+ if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) {
+ log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring.");
+ goto done;
+ }
+ /* State line not for this hidden service */
+ if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) {
+ goto done;
+ }
+
+ rev_counter = tor_parse_uint64(saved_rev_counter_str,
+ 10, 0, UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring.");
+ goto done;
+ }
+
+ /* Since we got this far, the line was for this service */
+ *service_found_out = 1;
+
+ log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64,
+ b64_key_str, rev_counter);
+
+ done:
+ tor_assert(items);
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+
+ return rev_counter;
+}
+
+/** Dig into our state file and find the current revision counter for the
+ * service with blinded key <b>blinded_pubkey</b>. If no revision counter is
+ * found, return 0. */
+static uint64_t
+get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey)
+{
+ or_state_t *state = get_or_state();
+ config_line_t *line;
+
+ /* Set default value for rev counters (if not found) to 0 */
+ uint64_t final_rev_counter = 0;
+
+ for (line = state->HidServRevCounter ; line ; line = line->next) {
+ int service_found = 0;
+ uint64_t rev_counter = 0;
+
+ tor_assert(!strcmp(line->key, "HidServRevCounter"));
+
+ /* Scan all the HidServRevCounter lines till we find the line for this
+ service: */
+ rev_counter = check_state_line_for_service_rev_counter(line->value,
+ blinded_pubkey,
+ &service_found);
+ if (service_found) {
+ final_rev_counter = rev_counter;
+ goto done;
+ }
+ }
+
+ done:
+ return final_rev_counter;
+}
+
+/** Update the value of the revision counter for <b>hs_desc</b> and save it on
+ our state file. */
+static void
+increment_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ /* Increment the revision counter of <b>hs_desc</b> so the next update (which
+ * will trigger an upload) will have the right value. We do this at this
+ * stage to only do it once because a descriptor can have many updates before
+ * being uploaded. By doing it at upload, we are sure to only increment by 1
+ * and thus avoid leaking how many operations we made on the descriptor from
+ * the previous one before uploading. */
+ rev_counter++;
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+
+ update_revision_counters_in_state();
+}
+
+/** Set the revision counter in <b>hs_desc</b>, using the state file to find
+ * the current counter value if it exists. */
+static void
+set_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the
+ * responsible hidden service directories. If for_next_period is true, the set
+ * of directories are selected using the next hsdir_index. This does nothing
+ * if PublishHidServDescriptors is false. */
+static void
+upload_descriptor_to_all(const hs_service_t *service,
+ hs_service_descriptor_t *desc, int for_next_period)
+{
+ smartlist_t *responsible_dirs = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Get our list of responsible HSDir. */
+ responsible_dirs = smartlist_new();
+ /* The parameter 0 means that we aren't a client so tell the function to use
+ * the spread store consensus paremeter. */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ for_next_period, 0, responsible_dirs);
+
+ /* For each responsible HSDir we have, initiate an upload command. */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *,
+ hsdir_rs) {
+ const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest);
+ /* Getting responsible hsdir implies that the node_t object exists for the
+ * routerstatus_t found in the consensus else we have a problem. */
+ tor_assert(hsdir_node);
+ /* Do not upload to an HSDir we don't have a descriptor for. */
+ if (!node_has_descriptor(hsdir_node)) {
+ log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading "
+ "descriptor. We'll try later once we have it.",
+ safe_str_client(node_describe(hsdir_node)));
+ /* Once we get new directory information, this HSDir will be retried if
+ * we ever get the descriptor. */
+ smartlist_add(desc->hsdir_missing_info,
+ tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN));
+ continue;
+ }
+
+ /* Upload this descriptor to the chosen directory. */
+ upload_descriptor_to_hsdir(service, desc, hsdir_node);
+ } SMARTLIST_FOREACH_END(hsdir_rs);
+
+ /* Set the next upload time for this descriptor. Even if we are configured
+ * to not upload, we still want to follow the right cycle of life for this
+ * descriptor. */
+ desc->next_upload_time =
+ (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN,
+ HS_SERVICE_NEXT_UPLOAD_TIME_MAX));
+ {
+ char fmt_next_time[ISO_TIME_LEN+1];
+ format_local_iso_time(fmt_next_time, desc->next_upload_time);
+ log_debug(LD_REND, "Service %s set to upload a descriptor at %s",
+ safe_str_client(service->onion_address), fmt_next_time);
+ }
+
+ /* Update the revision counter of this descriptor */
+ increment_descriptor_revision_counter(desc->desc);
+
+ smartlist_free(responsible_dirs);
+ return;
+}
+
+/* Return 1 if the given descriptor from the given service can be uploaded
+ * else return 0 if it can not. */
+static int
+should_service_upload_descriptor(const hs_service_t *service,
+ const hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* If this descriptors has missing intro points that is that it couldn't get
+ * them all when it was time to pick them, it means that we should upload
+ * instead of waiting an arbitrary amount of time breaking the service.
+ * Else, if we have no missing intro points, we use the value taken from the
+ * service configuration. */
+ if (desc->missing_intro_points) {
+ num_intro_points = digest256map_size(desc->intro_points.map);
+ } else {
+ num_intro_points = service->config.num_intro_points;
+ }
+
+ /* This means we tried to pick intro points but couldn't get any so do not
+ * upload descriptor in this case. We need at least one for the service to
+ * be reachable. */
+ if (desc->missing_intro_points && num_intro_points == 0) {
+ goto cannot;
+ }
+
+ /* Check if all our introduction circuit have been established for all the
+ * intro points we have selected. */
+ if (count_desc_circuit_established(desc) != num_intro_points) {
+ goto cannot;
+ }
+
+ /* Is it the right time to upload? */
+ if (desc->next_upload_time > now) {
+ goto cannot;
+ }
+
+ /* Can upload! */
+ return 1;
+ cannot:
+ return 0;
+}
+
+/* Scheduled event run from the main loop. Try to upload the descriptor for
+ * each service. */
+STATIC void
+run_upload_descriptor_event(time_t now)
+{
+ /* v2 services use the same function for descriptor creation and upload so
+ * we do everything here because the intro circuits were checked before. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_upload(now);
+ rend_consider_descriptor_republication();
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ int for_next_period = 0;
+
+ /* Can this descriptor be uploaed? */
+ if (!should_service_upload_descriptor(service, desc, now)) {
+ continue;
+ }
+
+ log_info(LD_REND, "Initiating upload for hidden service %s descriptor "
+ "for service %s with %u/%u introduction points%s.",
+ (desc == service->desc_current) ? "current" : "next",
+ safe_str_client(service->onion_address),
+ digest256map_size(desc->intro_points.map),
+ service->config.num_intro_points,
+ (desc->missing_intro_points) ? " (couldn't pick more)" : "");
+
+ /* At this point, we have to upload the descriptor so start by building
+ * the intro points descriptor section which we are now sure to be
+ * accurate because all circuits have been established. */
+ build_desc_intro_points(service, desc, now);
+
+ /* If the service is in the overlap period and this descriptor is the
+ * next one, it has to be uploaded for the next time period meaning
+ * we'll use the next node_t hsdir_index to pick the HSDirs. */
+ if (desc == service->desc_next) {
+ for_next_period = 1;
+ }
+ upload_descriptor_to_all(service, desc, for_next_period);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Called when the introduction point circuit is done building and ready to be
+ * used. */
+static void
+service_intro_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ /* Let's do some basic sanity checking of the circ state */
+ if (BUG(!circ->cpath)) {
+ return;
+ }
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ return;
+ }
+ if (BUG(!circ->hs_ident)) {
+ return;
+ }
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ log_warn(LD_REND, "Unknown introduction point auth key on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* We can't have an IP object without a descriptor. */
+ tor_assert(desc);
+
+ if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) {
+ /* Getting here means that the circuit has been re-purposed because we
+ * have enough intro circuit opened. Remove the IP from the service. */
+ service_intro_point_remove(service, ip);
+ service_intro_point_free(ip);
+ }
+
+ goto done;
+
+ err:
+ /* Close circuit, we can't use it. */
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* Called when a rendezvous circuit is done building and ready to be used. */
+static void
+service_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(circ->cpath);
+ /* Getting here means this is a v3 rendezvous circuit. */
+ tor_assert(circ->hs_ident);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Declare the circuit dirty to avoid reuse, and for path-bias */
+ if (!TO_CIRCUIT(circ)->timestamp_dirty)
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(circ);
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the rendezvous "
+ "circuit %u with cookie %s. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN));
+ goto err;
+ }
+
+ /* If the cell can't be sent, the circuit will be closed within this
+ * function. */
+ hs_circ_service_rp_has_opened(service, circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just
+ * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given
+ * introduction circuit. Return 0 on success else a negative value. */
+static int
+service_handle_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ /* We need the service and intro point for this cell. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, NULL);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_REND, "Introduction circuit established without an intro "
+ "point object on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. On success, the ip object and circuit purpose is updated to
+ * reflect the fact that the introduction circuit is established. */
+ if (hs_circ_handle_intro_established(service, ip, circ, payload,
+ payload_len) < 0) {
+ goto err;
+ }
+
+ /* Flag that we have an established circuit for this intro point. This value
+ * is what indicates the upload scheduled event if we are ready to build the
+ * intro point into the descriptor and upload. */
+ ip->circuit_established = 1;
+
+ log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell "
+ "on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* We just received an INTRODUCE2 cell on the established introduction circuit
+ * circ. Handle the cell and return 0 on success else a negative value. */
+static int
+service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
+
+ /* We'll need every object associated with this circuit. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_BUG, "Unknown service identity key %s when handling "
+ "an INTRODUCE2 cell on circuit %u",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_BUG, "Unknown introduction auth key when handling "
+ "an INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* If we have an IP object, we MUST have a descriptor object. */
+ tor_assert(desc);
+
+ /* The following will parse, decode and launch the rendezvous point circuit.
+ * Both current and legacy cells are handled. */
+ if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential,
+ payload, payload_len) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/* For a given service and a descriptor of that service, consider retrying to
+ * upload the descriptor to any directories from which we had missing
+ * information when originally tried to be uploaded. This is called when our
+ * directory information has changed. */
+static void
+consider_hsdir_upload_retry(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ smartlist_t *responsible_dirs = NULL;
+ smartlist_t *still_missing_dirs = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ responsible_dirs = smartlist_new();
+ still_missing_dirs = smartlist_new();
+
+ /* We first need to get responsible directories from the latest consensus so
+ * we can then make sure that the node that we were missing information for
+ * is still responsible for this descriptor. */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ service->desc_next == desc, 0, responsible_dirs);
+
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) {
+ const node_t *node;
+ const char *id = rs->identity_digest;
+ if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) {
+ continue;
+ }
+ /* We do need a node_t object and descriptor to perform an upload. If
+ * found, we remove the id from the missing dir list else we add it to the
+ * still missing dir list to keep track of id that are still missing. */
+ node = node_get_by_id(id);
+ if (node && node_has_descriptor(node)) {
+ upload_descriptor_to_hsdir(service, desc, node);
+ smartlist_remove(desc->hsdir_missing_info, id);
+ } else {
+ smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN));
+ }
+ } SMARTLIST_FOREACH_END(rs);
+
+ /* Switch the still missing dir list with the current missing dir list in
+ * the descriptor. It is possible that the list ends up empty which is what
+ * we want if we have no more missing dir. */
+ SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+ smartlist_free(desc->hsdir_missing_info);
+ desc->hsdir_missing_info = still_missing_dirs;
+
+ /* No ownership of the routerstatus_t object in this list. */
+ smartlist_free(responsible_dirs);
+}
+
+/* Add to list every filename used by service. This is used by the sandbox
+ * subsystem. */
+static void
+service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list)
+{
+ const char *s_dir;
+ char fname[128] = {0};
+
+ tor_assert(service);
+ tor_assert(list);
+
+ /* Ease our life. */
+ s_dir = service->config.directory_path;
+ /* The hostname file. */
+ smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname));
+ /* The key files splitted in two. */
+ tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+ tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Return the number of service we have configured and usable. */
+unsigned int
+hs_service_get_num_services(void)
+{
+ if (hs_service_map == NULL) {
+ return 0;
+ }
+ return HT_SIZE(hs_service_map);
+}
+
+/* Called once an introduction circuit is closed. If the circuit doesn't have
+ * a v3 identifier, it is ignored. */
+void
+hs_service_intro_circ_has_closed(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ if (circ->hs_ident == NULL) {
+ /* This is not a v3 circuit, ignore. */
+ goto end;
+ }
+
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+ if (service == NULL) {
+ log_warn(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on intro circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto end;
+ }
+ if (ip == NULL) {
+ /* The introduction point object has already been removed probably by our
+ * cleanup process so ignore. */
+ goto end;
+ }
+ /* Can't have an intro point object without a descriptor. */
+ tor_assert(desc);
+
+ /* Circuit disappeared so make sure the intro point is updated. By
+ * keeping the object in the descriptor, we'll be able to retry. */
+ ip->circuit_established = 0;
+
+ /* We've retried too many times, remember it as a failed intro point so we
+ * don't pick it up again. It will be retried in INTRO_CIRC_RETRY_PERIOD
+ * seconds. */
+ if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ remember_failing_intro_point(ip, desc, approx_time());
+ service_intro_point_remove(service, ip);
+ service_intro_point_free(ip);
+ }
+
+ end:
+ return;
+}
+
+/* Given conn, a rendezvous edge connection acting as an exit stream, look up
+ * the hidden service for the circuit circ, and look up the port and address
+ * based on the connection port. Assign the actual connection address.
+ *
+ * Return 0 on success. Return -1 on failure and the caller should NOT close
+ * the circuit. Return -2 on failure and the caller MUST close the circuit for
+ * security reasons. */
+int
+hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(conn);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ tor_assert(circ->hs_ident);
+
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on rendezvous circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* We want the caller to close the circuit because it's not a valid
+ * service so no danger. Attempting to bruteforce the entire key space by
+ * opening circuits to learn which service is being hosted here is
+ * impractical. */
+ goto err_close;
+ }
+
+ /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if
+ * this circuit will exceed the limit. */
+ if (service->config.max_streams_per_rdv_circuit > 0 &&
+ (circ->hs_ident->num_rdv_streams >=
+ service->config.max_streams_per_rdv_circuit)) {
+#define MAX_STREAM_WARN_INTERVAL 600
+ static struct ratelim_t stream_ratelim =
+ RATELIM_INIT(MAX_STREAM_WARN_INTERVAL);
+ log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND,
+ "Maximum streams per circuit limit reached on "
+ "rendezvous circuit %u for service %s. Circuit has "
+ "%" PRIu64 " out of %" PRIu64 " streams. %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ service->onion_address,
+ circ->hs_ident->num_rdv_streams,
+ service->config.max_streams_per_rdv_circuit,
+ service->config.max_streams_close_circuit ?
+ "Closing circuit" : "Ignoring open stream request");
+ if (service->config.max_streams_close_circuit) {
+ /* Service explicitly configured to close immediately. */
+ goto err_close;
+ }
+ /* Exceeding the limit makes tor silently ignore the stream creation
+ * request and keep the circuit open. */
+ goto err_no_close;
+ }
+
+ /* Find a virtual port of that service mathcing the one in the connection if
+ * succesful, set the address in the connection. */
+ if (hs_set_conn_addr_port(service->config.ports, conn) < 0) {
+ log_info(LD_REND, "No virtual port mapping exists for port %d for "
+ "hidden service %s.",
+ TO_CONN(conn)->port, service->onion_address);
+ if (service->config.allow_unknown_ports) {
+ /* Service explicitly allow connection to unknown ports so close right
+ * away because we do not care about port mapping. */
+ goto err_close;
+ }
+ /* If the service didn't explicitly allow it, we do NOT close the circuit
+ * here to raise the bar in terms of performance for port mapping. */
+ goto err_no_close;
+ }
+
+ /* Success. */
+ return 0;
+ err_close:
+ /* Indicate the caller that the circuit should be closed. */
+ return -2;
+ err_no_close:
+ /* Indicate the caller to NOT close the circuit. */
+ return -1;
+}
+
+/* Add to file_list every filename used by a configured hidden service, and to
+ * dir_list every directory path used by a configured hidden service. This is
+ * used by the sandbox subsystem to whitelist those. */
+void
+hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list)
+{
+ tor_assert(file_list);
+ tor_assert(dir_list);
+
+ /* Add files and dirs for legacy services. */
+ rend_services_add_filenames_to_lists(file_list, dir_list);
+
+ /* Add files and dirs for v3+. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Skip ephemeral service, they don't touch the disk. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ service_add_fnames_to_list(service, file_list);
+ smartlist_add_strdup(dir_list, service->config.directory_path);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Called when our internal view of the directory has changed. We might have
+ * new descriptors for hidden service directories that we didn't have before
+ * so try them if it's the case. */
+void
+hs_service_dir_info_changed(void)
+{
+ /* For each service we have, check every descriptor and consider retrying to
+ * upload it to directories that we might have had missing information
+ * previously that is missing a router descriptor. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* This cleans up the descriptor missing hsdir information list if a
+ * successful upload is made or if any of the directory aren't
+ * responsible anymore for the service descriptor. */
+ consider_hsdir_upload_retry(service, desc);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and
+ * launch a circuit to the rendezvous point. */
+int
+hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Do some initial validation and logging before we parse the cell */
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto done;
+ }
+
+ if (circ->hs_ident) {
+ ret = service_handle_introduce2(circ, payload, payload_len);
+ } else {
+ ret = rend_service_receive_introduction(circ, payload, payload_len);
+ }
+
+ done:
+ return ret;
+}
+
+/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an
+ * established introduction point. Return 0 on success else a negative value
+ * and the circuit is closed. */
+int
+hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto err;
+ }
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ if (circ->hs_ident) {
+ ret = service_handle_intro_established(circ, payload, payload_len);
+ } else {
+ ret = rend_service_intro_established(circ, payload, payload_len);
+ }
+
+ if (ret < 0) {
+ goto err;
+ }
+ return 0;
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Called when any kind of hidden service circuit is done building thus
+ * opened. This is the entry point from the circuit subsystem. */
+void
+hs_service_circuit_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ if (circ->hs_ident) {
+ service_intro_circ_has_opened(circ);
+ } else {
+ rend_service_intro_has_opened(circ);
+ }
+ break;
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ if (circ->hs_ident) {
+ service_rendezvous_circ_has_opened(circ);
+ } else {
+ rend_service_rendezvous_has_opened(circ);
+ }
+ break;
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Load and/or generate keys for all onion services including the client
+ * authorization if any. Return 0 on success, -1 on failure. */
+int
+hs_service_load_all_keys(void)
+{
+ /* Load v2 service keys if we have v2. */
+ if (rend_num_services() != 0) {
+ if (rend_service_load_all_keys(NULL) < 0) {
+ goto err;
+ }
+ }
+
+ /* Load or/and generate them for v3+. */
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) {
+ /* Ignore ephemeral service, they already have their keys set. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ log_info(LD_REND, "Loading v3 onion service keys from %s",
+ service_escaped_dir(service));
+ if (load_service_keys(service) < 0) {
+ goto err;
+ }
+ /* XXX: Load/Generate client authorization keys. (#20700) */
+ } SMARTLIST_FOREACH_END(service);
+
+ /* Final step, the staging list contains service in a quiescent state that
+ * is ready to be used. Register them to the global map. Once this is over,
+ * the staging list will be cleaned up. */
+ register_all_services();
+
+ /* All keys have been loaded successfully. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Put all service object in the given service list. After this, the caller
+ * looses ownership of every elements in the list and responsible to free the
+ * list pointer. */
+void
+hs_service_stage_services(const smartlist_t *service_list)
+{
+ tor_assert(service_list);
+ /* This list is freed at registration time but this function can be called
+ * multiple time. */
+ if (hs_service_staging_list == NULL) {
+ hs_service_staging_list = smartlist_new();
+ }
+ /* Add all service object to our staging list. Caller is responsible for
+ * freeing the service_list. */
+ smartlist_add_all(hs_service_staging_list, service_list);
+}
+
+/* Allocate and initilize a service object. The service configuration will
+ * contain the default values. Return the newly allocated object pointer. This
+ * function can't fail. */
+hs_service_t *
+hs_service_new(const or_options_t *options)
+{
+ hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
+ /* Set default configuration value. */
+ set_service_default_config(&service->config, options);
+ /* Set the default service version. */
+ service->config.version = HS_SERVICE_DEFAULT_VERSION;
+ /* Allocate the CLIENT_PK replay cache in service state. */
+ service->state.replay_cache_rend_cookie =
+ replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL);
+ return service;
+}
+
+/* Free the given <b>service</b> object and all its content. This function
+ * also takes care of wiping service keys from memory. It is safe to pass a
+ * NULL pointer. */
+void
+hs_service_free(hs_service_t *service)
+{
+ if (service == NULL) {
+ return;
+ }
+
+ /* Free descriptors. Go over both descriptor with this loop. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ service_descriptor_free(desc);
+ } FOR_EACH_DESCRIPTOR_END;
+
+ /* Free service configuration. */
+ service_clear_config(&service->config);
+
+ /* Free replay cache from state. */
+ if (service->state.replay_cache_rend_cookie) {
+ replaycache_free(service->state.replay_cache_rend_cookie);
+ }
+
+ /* Wipe service keys. */
+ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
+
+ tor_free(service);
+}
+
+/* Periodic callback. Entry point from the main loop to the HS service
+ * subsystem. This is call every second. This is skipped if tor can't build a
+ * circuit or the network is disabled. */
+void
+hs_service_run_scheduled_events(time_t now)
+{
+ /* First thing we'll do here is to make sure our services are in a
+ * quiescent state for the scheduled events. */
+ run_housekeeping_event(now);
+
+ /* Order matters here. We first make sure the descriptor object for each
+ * service contains the latest data. Once done, we check if we need to open
+ * new introduction circuit. Finally, we try to upload the descriptor for
+ * each service. */
+
+ /* Make sure descriptors are up to date. */
+ run_build_descriptor_event(now);
+ /* Make sure services have enough circuits. */
+ run_build_circuit_event(now);
+ /* Upload the descriptors if needed/possible. */
+ run_upload_descriptor_event(now);
+}
+
+/* Initialize the service HS subsystem. */
+void
+hs_service_init(void)
+{
+ /* Should never be called twice. */
+ tor_assert(!hs_service_map);
+ tor_assert(!hs_service_staging_list);
+
+ /* v2 specific. */
+ rend_service_init();
+
+ hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht));
+ HT_INIT(hs_service_ht, hs_service_map);
+
+ hs_service_staging_list = smartlist_new();
+}
+
+/* Release all global storage of the hidden service subsystem. */
+void
+hs_service_free_all(void)
+{
+ rend_service_free_all();
+ service_free_all();
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global service map size. Only used by unit test. */
+STATIC unsigned int
+get_hs_service_map_size(void)
+{
+ return HT_SIZE(hs_service_map);
+}
+
+/* Return the staging list size. Only used by unit test. */
+STATIC int
+get_hs_service_staging_list_size(void)
+{
+ return smartlist_len(hs_service_staging_list);
+}
+
+STATIC hs_service_ht *
+get_hs_service_map(void)
+{
+ return hs_service_map;
+}
+
+STATIC hs_service_t *
+get_first_service(void)
+{
+ hs_service_t **obj = HT_START(hs_service_ht, hs_service_map);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return *obj;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/hs_service.h b/src/or/hs_service.h
new file mode 100644
index 0000000000..8d613d23ed
--- /dev/null
+++ b/src/or/hs_service.h
@@ -0,0 +1,346 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.h
+ * \brief Header file containing service data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_SERVICE_H
+#define TOR_HS_SERVICE_H
+
+#include "crypto_curve25519.h"
+#include "crypto_ed25519.h"
+#include "replaycache.h"
+
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+#include "hs_intropoint.h"
+
+/* Trunnel */
+#include "hs/cell_establish_intro.h"
+
+/* When loading and configuring a service, this is the default version it will
+ * be configured for as it is possible that no HiddenServiceVersion is
+ * present. */
+#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO
+
+/* As described in the specification, service publishes their next descriptor
+ * at a random time between those two values (in seconds). */
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60)
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
+
+/* Service side introduction point. */
+typedef struct hs_service_intro_point_t {
+ /* Top level intropoint "shared" data between client/service. */
+ hs_intropoint_t base;
+
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
+ /* Authentication keypair used to create the authentication certificate
+ * which is published in the descriptor. */
+ ed25519_keypair_t auth_key_kp;
+
+ /* Encryption keypair for the "ntor" type. */
+ curve25519_keypair_t enc_key_kp;
+
+ /* Legacy key if that intro point doesn't support v3. This should be used if
+ * the base object legacy flag is set. */
+ crypto_pk_t *legacy_key;
+
+ /* Amount of INTRODUCE2 cell accepted from this intro point. */
+ uint64_t introduce2_count;
+
+ /* Maximum number of INTRODUCE2 cell this intro point should accept. */
+ uint64_t introduce2_max;
+
+ /* The time at which this intro point should expire and stop being used. */
+ time_t time_to_expire;
+
+ /* The amount of circuit creation we've made to this intro point. This is
+ * incremented every time we do a circuit relaunch on this intro point which
+ * is triggered when the circuit dies but the node is still in the
+ * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */
+ uint32_t circuit_retries;
+
+ /* Set if this intro point has an established circuit. */
+ unsigned int circuit_established : 1;
+
+ /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the
+ * circuit associated with this intro point has received. This is used to
+ * prevent replay attacks. */
+ replaycache_t *replay_cache;
+} hs_service_intro_point_t;
+
+/* Object handling introduction points of a service. */
+typedef struct hs_service_intropoints_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t retry_period_started;
+
+ /* Number of circuit we've launched during a single retry period. */
+ unsigned int num_circuits_launched;
+
+ /* Contains the current hs_service_intro_point_t objects indexed by
+ * authentication public key. */
+ digest256map_t *map;
+
+ /* Contains node's identity key digest that were introduction point for this
+ * descriptor but were retried to many times. We keep those so we avoid
+ * re-picking them over and over for a circuit retry period.
+ * XXX: Once we have #22173, change this to only use ed25519 identity. */
+ digestmap_t *failed_id;
+} hs_service_intropoints_t;
+
+/* Representation of a service descriptor. */
+typedef struct hs_service_descriptor_t {
+ /* Decoded descriptor. This object is used for encoding when the service
+ * publishes the descriptor. */
+ hs_descriptor_t *desc;
+
+ /* Descriptor signing keypair. */
+ ed25519_keypair_t signing_kp;
+
+ /* Blinded keypair derived from the master identity public key. */
+ ed25519_keypair_t blinded_kp;
+
+ /* When is the next time when we should upload the descriptor. */
+ time_t next_upload_time;
+
+ /* Introduction points assign to this descriptor which contains
+ * hs_service_intropoints_t object indexed by authentication key (the RSA
+ * key if the node is legacy). */
+ hs_service_intropoints_t intro_points;
+
+ /* The time period number this descriptor has been created for. */
+ uint64_t time_period_num;
+
+ /* True iff we have missing intro points for this descriptor because we
+ * couldn't pick any nodes. */
+ unsigned int missing_intro_points : 1;
+
+ /* List of identity digests for hidden service directories to which we
+ * couldn't upload this descriptor because we didn't have its router
+ * descriptor at the time. If this list is non-empty, only the relays in this
+ * list are re-tried to upload this descriptor when our directory information
+ * have been updated. */
+ smartlist_t *hsdir_missing_info;
+} hs_service_descriptor_t;
+
+/* Service key material. */
+typedef struct hs_service_keys_t {
+ /* Master identify public key. */
+ ed25519_public_key_t identity_pk;
+ /* Master identity private key. */
+ ed25519_secret_key_t identity_sk;
+ /* True iff the key is kept offline which means the identity_sk MUST not be
+ * used in that case. */
+ unsigned int is_identify_key_offline : 1;
+} hs_service_keys_t;
+
+/* Service configuration. The following are set from the torrc options either
+ * set by the configuration file or by the control port. Nothing else should
+ * change those values. */
+typedef struct hs_service_config_t {
+ /* Protocol version of the service. Specified by HiddenServiceVersion
+ * option. */
+ uint32_t version;
+
+ /* List of rend_service_port_config_t */
+ smartlist_t *ports;
+
+ /* Path on the filesystem where the service persistent data is stored. NULL
+ * if the service is ephemeral. Specified by HiddenServiceDir option. */
+ char *directory_path;
+
+ /* The maximum number of simultaneous streams per rendezvous circuit that
+ * are allowed to be created. No limit if 0. Specified by
+ * HiddenServiceMaxStreams option. */
+ uint64_t max_streams_per_rdv_circuit;
+
+ /* If true, we close circuits that exceed the max_streams_per_rdv_circuit
+ * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */
+ unsigned int max_streams_close_circuit : 1;
+
+ /* How many introduction points this service has. Specified by
+ * HiddenServiceNumIntroductionPoints option. */
+ unsigned int num_intro_points;
+
+ /* True iff we allow request made on unknown ports. Specified by
+ * HiddenServiceAllowUnknownPorts option. */
+ unsigned int allow_unknown_ports : 1;
+
+ /* If true, this service is a Single Onion Service. Specified by
+ * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */
+ unsigned int is_single_onion : 1;
+
+ /* If true, allow group read permissions on the directory_path. Specified by
+ * HiddenServiceDirGroupReadable option. */
+ unsigned int dir_group_readable : 1;
+
+ /* Is this service ephemeral? */
+ unsigned int is_ephemeral : 1;
+} hs_service_config_t;
+
+/* Service state. */
+typedef struct hs_service_state_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t intro_circ_retry_started_time;
+
+ /* Number of circuit we've launched during a single retry period. This
+ * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */
+ unsigned int num_intro_circ_launched;
+
+ /* Indicate that the service has entered the overlap period. We use this
+ * flag to check for descriptor rotation. */
+ unsigned int in_overlap_period : 1;
+
+ /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect
+ * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point
+ * through two or more different introduction points; when they do, this
+ * keeps us from launching multiple simultaneous attempts to connect to the
+ * same rend point. */
+ replaycache_t *replay_cache_rend_cookie;
+} hs_service_state_t;
+
+/* Representation of a service running on this tor instance. */
+typedef struct hs_service_t {
+ /* Onion address base32 encoded and NUL terminated. We keep it for logging
+ * purposes so we don't have to build it everytime. */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ /* Hashtable node: use to look up the service by its master public identity
+ * key in the service global map. */
+ HT_ENTRY(hs_service_t) hs_service_node;
+
+ /* Service state which contains various flags and counters. */
+ hs_service_state_t state;
+
+ /* Key material of the service. */
+ hs_service_keys_t keys;
+
+ /* Configuration of the service. */
+ hs_service_config_t config;
+
+ /* Current descriptor. */
+ hs_service_descriptor_t *desc_current;
+ /* Next descriptor that we need for the overlap period for which we have to
+ * keep two sets of opened introduction point circuits. */
+ hs_service_descriptor_t *desc_next;
+
+ /* XXX: Credential (client auth.) #20700. */
+
+} hs_service_t;
+
+/* For the service global hash map, we define a specific type for it which
+ * will make it safe to use and specific to some controlled parameters such as
+ * the hashing function and how to compare services. */
+typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht;
+
+/* API */
+
+/* Global initializer and cleanup function. */
+void hs_service_init(void);
+void hs_service_free_all(void);
+
+/* Service new/free functions. */
+hs_service_t *hs_service_new(const or_options_t *options);
+void hs_service_free(hs_service_t *service);
+
+unsigned int hs_service_get_num_services(void);
+void hs_service_stage_services(const smartlist_t *service_list);
+int hs_service_load_all_keys(void);
+void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list);
+int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn);
+
+void hs_service_dir_info_changed(void);
+void hs_service_run_scheduled_events(time_t now);
+void hs_service_circuit_has_opened(origin_circuit_t *circ);
+int hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_service_receive_introduce2(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+
+void hs_service_intro_circ_has_closed(origin_circuit_t *circ);
+
+#ifdef HS_SERVICE_PRIVATE
+
+#ifdef TOR_UNIT_TESTS
+
+/* Useful getters for unit tests. */
+STATIC unsigned int get_hs_service_map_size(void);
+STATIC int get_hs_service_staging_list_size(void);
+STATIC hs_service_ht *get_hs_service_map(void);
+STATIC hs_service_t *get_first_service(void);
+
+/* Service accessors. */
+STATIC hs_service_t *find_service(hs_service_ht *map,
+ const ed25519_public_key_t *pk);
+STATIC void remove_service(hs_service_ht *map, hs_service_t *service);
+STATIC int register_service(hs_service_ht *map, hs_service_t *service);
+/* Service introduction point functions. */
+STATIC hs_service_intro_point_t *service_intro_point_new(
+ const extend_info_t *ei,
+ unsigned int is_legacy);
+STATIC void service_intro_point_free(hs_service_intro_point_t *ip);
+STATIC void service_intro_point_add(digest256map_t *map,
+ hs_service_intro_point_t *ip);
+STATIC void service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+STATIC hs_service_intro_point_t *service_intro_point_find(
+ const hs_service_t *service,
+ const ed25519_public_key_t *auth_key);
+STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
+ const hs_service_t *service,
+ const hs_ident_circuit_t *ident);
+/* Service descriptor functions. */
+STATIC hs_service_descriptor_t *service_descriptor_new(void);
+STATIC hs_service_descriptor_t *service_desc_find_by_intro(
+ const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+/* Helper functions. */
+STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service,
+ hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc);
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip);
+STATIC int can_service_launch_intro_circuit(hs_service_t *service,
+ time_t now);
+STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now);
+STATIC void run_housekeeping_event(time_t now);
+STATIC void rotate_all_descriptors(time_t now);
+STATIC void build_all_descriptors(time_t now);
+STATIC void update_all_descriptors(time_t now);
+STATIC void run_upload_descriptor_event(time_t now);
+
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc);
+
+STATIC void service_descriptor_free(hs_service_descriptor_t *desc);
+
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out);
+
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* HS_SERVICE_PRIVATE */
+
+#endif /* TOR_HS_SERVICE_H */
+
diff --git a/src/or/include.am b/src/or/include.am
index 712ae18406..8db5be095a 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -17,16 +17,12 @@ 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/bridges.c \
src/or/buffers.c \
src/or/channel.c \
+ src/or/channelpadding.c \
src/or/channeltls.c \
src/or/circpathbias.c \
src/or/circuitbuild.c \
@@ -41,6 +37,9 @@ LIBTOR_A_SOURCES = \
src/or/connection.c \
src/or/connection_edge.c \
src/or/connection_or.c \
+ src/or/conscache.c \
+ src/or/consdiff.c \
+ src/or/consdiffmgr.c \
src/or/control.c \
src/or/cpuworker.c \
src/or/dircollate.c \
@@ -54,6 +53,18 @@ LIBTOR_A_SOURCES = \
src/or/entrynodes.c \
src/or/ext_orport.c \
src/or/hibernate.c \
+ src/or/hs_cache.c \
+ src/or/hs_cell.c \
+ src/or/hs_circuit.c \
+ src/or/hs_circuitmap.c \
+ src/or/hs_client.c \
+ src/or/hs_common.c \
+ src/or/hs_config.c \
+ src/or/hs_descriptor.c \
+ src/or/hs_ident.c \
+ src/or/hs_intropoint.c \
+ src/or/hs_ntor.c \
+ src/or/hs_service.c \
src/or/keypin.c \
src/or/main.c \
src/or/microdesc.c \
@@ -62,8 +73,12 @@ 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/parsecommon.c \
src/or/periodic.c \
+ src/or/protover.c \
src/or/policies.c \
src/or/reasons.c \
src/or/relay.c \
@@ -84,7 +99,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,11 +123,14 @@ 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 \
+ src/trace/libor-trace.a \
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \
- @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@
+ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \
+ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \
+ $(rust_ldadd)
if COVERAGE_ENABLED
src_or_tor_cov_SOURCES = src/or/tor_main.c
@@ -121,16 +138,20 @@ 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@ \
- @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@
+ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \
+ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@
endif
ORHEADERS = \
src/or/addressmap.h \
+ src/or/bridges.h \
src/or/buffers.h \
src/or/channel.h \
+ src/or/channelpadding.h \
src/or/channeltls.h \
src/or/circpathbias.h \
src/or/circuitbuild.h \
@@ -145,6 +166,9 @@ ORHEADERS = \
src/or/connection.h \
src/or/connection_edge.h \
src/or/connection_or.h \
+ src/or/conscache.h \
+ src/or/consdiff.h \
+ src/or/consdiffmgr.h \
src/or/control.h \
src/or/cpuworker.h \
src/or/dircollate.h \
@@ -154,13 +178,24 @@ 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 \
src/or/geoip.h \
src/or/entrynodes.h \
src/or/hibernate.h \
+ src/or/hs_cache.h \
+ src/or/hs_cell.h \
+ src/or/hs_config.h \
+ src/or/hs_circuit.h \
+ src/or/hs_circuitmap.h \
+ src/or/hs_client.h \
+ src/or/hs_common.h \
+ src/or/hs_descriptor.h \
+ src/or/hs_ident.h \
+ src/or/hs_intropoint.h \
+ src/or/hs_ntor.h \
+ src/or/hs_service.h \
src/or/keypin.h \
src/or/main.h \
src/or/microdesc.h \
@@ -172,9 +207,13 @@ 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/parsecommon.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 +238,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..1698dc184f 100644
--- a/src/or/keypin.c
+++ b/src/or/keypin.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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/keypin.h b/src/or/keypin.h
index 673f24d9e3..2564f5befb 100644
--- a/src/or/keypin.h
+++ b/src/or/keypin.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_KEYPIN_H
diff --git a/src/or/main.c b/src/or/main.c
index fba9799a60..86fdb93282 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -1,31 +1,72 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file main.c
* \brief Toplevel module. Handles signals, multiplexes between
* connections, implements main loop, and drives scheduled events.
+ *
+ * For the main loop itself; see run_main_loop_once(). It invokes the rest of
+ * Tor mostly through Libevent callbacks. Libevent callbacks can happen when
+ * a timer elapses, a signal is received, a socket is ready to read or write,
+ * or an event is manually activated.
+ *
+ * Most events in Tor are driven from these callbacks:
+ * <ul>
+ * <li>conn_read_callback() and conn_write_callback() here, which are
+ * invoked when a socket is ready to read or write respectively.
+ * <li>signal_callback(), which handles incoming signals.
+ * </ul>
+ * Other events are used for specific purposes, or for building more complex
+ * control structures. If you search for usage of tor_libevent_new(), you
+ * will find all the events that we construct in Tor.
+ *
+ * Tor has numerous housekeeping operations that need to happen
+ * regularly. They are handled in different ways:
+ * <ul>
+ * <li>The most frequent operations are handled after every read or write
+ * event, at the end of connection_handle_read() and
+ * connection_handle_write().
+ *
+ * <li>The next most frequent operations happen after each invocation of the
+ * main loop, in run_main_loop_once().
+ *
+ * <li>Once per second, we run all of the operations listed in
+ * second_elapsed_callback(), and in its child, run_scheduled_events().
+ *
+ * <li>Once-a-second operations are handled in second_elapsed_callback().
+ *
+ * <li>More infrequent operations take place based on the periodic event
+ * driver in periodic.c . These are stored in the periodic_events[]
+ * table.
+ * </ul>
+ *
**/
#define MAIN_PRIVATE
#include "or.h"
#include "addressmap.h"
#include "backtrace.h"
+#include "bridges.h"
#include "buffers.h"
#include "channel.h"
#include "channeltls.h"
+#include "channelpadding.h"
#include "circuitbuild.h"
#include "circuitlist.h"
#include "circuituse.h"
#include "command.h"
+#include "compat_rust.h"
+#include "compress.h"
#include "config.h"
#include "confparse.h"
#include "connection.h"
#include "connection_edge.h"
#include "connection_or.h"
+#include "consdiffmgr.h"
#include "control.h"
#include "cpuworker.h"
#include "crypto_s2k.h"
@@ -37,6 +78,8 @@
#include "entrynodes.h"
#include "geoip.h"
#include "hibernate.h"
+#include "hs_cache.h"
+#include "hs_circuitmap.h"
#include "keypin.h"
#include "main.h"
#include "microdesc.h"
@@ -46,6 +89,7 @@
#include "onion.h"
#include "periodic.h"
#include "policies.h"
+#include "protover.h"
#include "transports.h"
#include "relay.h"
#include "rendclient.h"
@@ -57,26 +101,18 @@
#include "routerlist.h"
#include "routerparse.h"
#include "scheduler.h"
+#include "shared_random.h"
#include "statefile.h"
#include "status.h"
#include "util_process.h"
#include "ext_orport.h"
#ifdef USE_DMALLOC
#include <dmalloc.h>
-#include <openssl/crypto.h>
#endif
#include "memarea.h"
#include "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 +140,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 +153,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;
@@ -146,7 +179,7 @@ static int signewnym_is_pending = 0;
static unsigned newnym_epoch = 0;
/** Smartlist of all open connections. */
-static smartlist_t *connection_array = NULL;
+STATIC smartlist_t *connection_array = NULL;
/** List of connections that have been marked for close and need to be freed
* and removed from connection_array. */
static smartlist_t *closeable_connection_lst = NULL;
@@ -172,9 +205,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 +221,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 +263,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 +294,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);
}
@@ -422,7 +367,7 @@ connection_unlink(connection_t *conn)
}
if (conn->type == CONN_TYPE_OR) {
if (!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest))
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
/* connection_unlink() can only get called if the connection
* was already on the closeable list, and it got there by
* connection_mark_for_close(), which was called from
@@ -479,8 +424,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 +453,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 +470,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));
}
@@ -558,7 +489,7 @@ connection_check_event(connection_t *conn, struct event *ev)
*/
bad = ev != NULL;
} else {
- /* Everytyhing else should have an underlying socket, or a linked
+ /* Everything else should have an underlying socket, or a linked
* connection (which is also tracked with a read_event/write_event pair).
*/
bad = ev == NULL;
@@ -589,11 +520,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 +542,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 +565,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 +575,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 +598,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;
}
@@ -726,6 +633,19 @@ connection_should_read_from_linked_conn(connection_t *conn)
return 0;
}
+/** If we called event_base_loop() and told it to never stop until it
+ * runs out of events, now we've changed our mind: tell it we want it to
+ * finish. */
+void
+tell_event_loop_to_finish(void)
+{
+ if (!called_loop_once) {
+ struct timeval tv = { 0, 0 };
+ tor_event_base_loopexit(tor_libevent_get_base(), &tv);
+ called_loop_once = 1; /* hack to avoid adding more exit events */
+ }
+}
+
/** Helper: Tell the main loop to begin reading bytes into <b>conn</b> from
* its linked connection, if it is not doing so already. Called by
* connection_start_reading and connection_start_writing as appropriate. */
@@ -738,14 +658,10 @@ connection_start_reading_from_linked_conn(connection_t *conn)
if (!conn->active_on_link) {
conn->active_on_link = 1;
smartlist_add(active_linked_connection_lst, conn);
- if (!called_loop_once) {
- /* This is the first event on the list; we won't be in LOOP_ONCE mode,
- * so we need to make sure that the event_base_loop() actually exits at
- * the end of its run through the current connections and lets us
- * activate read events for linked connections. */
- struct timeval tv = { 0, 0 };
- tor_event_base_loopexit(tor_libevent_get_base(), &tv);
- }
+ /* make sure that the event_base_loop() function exits at
+ * the end of its run through the current connections, so we can
+ * activate read events for linked connections. */
+ tell_event_loop_to_finish();
} else {
tor_assert(smartlist_contains(active_linked_connection_lst, conn));
}
@@ -787,6 +703,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
@@ -879,21 +812,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);
@@ -903,7 +821,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
@@ -962,7 +879,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
@@ -988,9 +905,6 @@ conn_close_if_marked(int i)
}
}
-#ifdef USE_BUFFEREVENTS
- unlink:
-#endif
connection_unlink(conn); /* unlink, remove, free */
return 1;
}
@@ -1069,7 +983,13 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs)
/* if we have enough dir info, then update our guard status with
* whatever we just learned. */
- entry_guards_compute_status(options, now);
+ int invalidate_circs = guards_update_all();
+
+ if (invalidate_circs) {
+ circuit_mark_all_unused_circs();
+ circuit_mark_all_dirty_circs_as_unusable();
+ }
+
/* Don't even bother trying to get extrainfo until the rest of our
* directory info is up-to-date */
if (options->DownloadExtraInfo)
@@ -1136,11 +1056,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);
@@ -1182,8 +1098,9 @@ run_connection_housekeeping(int i, time_t now)
} else if (!have_any_circuits &&
now - or_conn->idle_timeout >=
chan->timestamp_last_had_circuits) {
- log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) "
- "[no circuits for %d; timeout %d; %scanonical].",
+ log_info(LD_OR,"Expiring non-used OR connection "U64_FORMAT" to fd %d "
+ "(%s:%d) [no circuits for %d; timeout %d; %scanonical].",
+ U64_PRINTF_ARG(chan->global_identifier),
(int)conn->s, conn->address, conn->port,
(int)(now - chan->timestamp_last_had_circuits),
or_conn->idle_timeout,
@@ -1206,6 +1123,8 @@ run_connection_housekeeping(int i, time_t now)
memset(&cell,0,sizeof(cell_t));
cell.command = CELL_PADDING;
connection_or_write_cell_to_buf(&cell, or_conn);
+ } else {
+ channelpadding_decide_to_pad_channel(chan);
}
}
@@ -1248,9 +1167,9 @@ static int periodic_events_initialized = 0;
#define CALLBACK(name) \
static int name ## _callback(time_t, const or_options_t *)
CALLBACK(rotate_onion_key);
+CALLBACK(check_onion_keys_expiry_time);
CALLBACK(check_ed_keys);
CALLBACK(launch_descriptor_fetches);
-CALLBACK(reset_descriptor_failures);
CALLBACK(rotate_x509_certificate);
CALLBACK(add_entropy);
CALLBACK(launch_reachability_tests);
@@ -1272,6 +1191,10 @@ CALLBACK(check_dns_honesty);
CALLBACK(write_bridge_ns);
CALLBACK(check_fw_helper_app);
CALLBACK(heartbeat);
+CALLBACK(clean_consdiffmgr);
+CALLBACK(reset_padding_counts);
+CALLBACK(check_canonical_channels);
+CALLBACK(hs_service);
#undef CALLBACK
@@ -1280,9 +1203,9 @@ CALLBACK(heartbeat);
static periodic_event_item_t periodic_events[] = {
CALLBACK(rotate_onion_key),
+ CALLBACK(check_onion_keys_expiry_time),
CALLBACK(check_ed_keys),
CALLBACK(launch_descriptor_fetches),
- CALLBACK(reset_descriptor_failures),
CALLBACK(rotate_x509_certificate),
CALLBACK(add_entropy),
CALLBACK(launch_reachability_tests),
@@ -1304,6 +1227,10 @@ static periodic_event_item_t periodic_events[] = {
CALLBACK(write_bridge_ns),
CALLBACK(check_fw_helper_app),
CALLBACK(heartbeat),
+ CALLBACK(clean_consdiffmgr),
+ CALLBACK(reset_padding_counts),
+ CALLBACK(check_canonical_channels),
+ CALLBACK(hs_service),
END_OF_PERIODIC_EVENTS
};
#undef CALLBACK
@@ -1472,6 +1399,9 @@ run_scheduled_events(time_t now)
/* 0c. If we've deferred log messages for the controller, handle them now */
flush_pending_log_callbacks();
+ /* Maybe enough time elapsed for us to reconsider a circuit. */
+ circuit_upgrade_circuits_from_guard_wait();
+
if (options->UseBridges && !options->DisableNetwork) {
fetch_bridge_descriptors(options, now);
}
@@ -1484,17 +1414,6 @@ run_scheduled_events(time_t now)
dirvote_act(options, now);
}
- /* 2d. Cleanup excess consensus bootstrap connections every second.
- * connection_dir_close_consensus_conn_if_extra() closes some connections
- * that are clearly excess, but this check is more thorough.
- * This only closes connections if there is more than one consensus
- * connection, and at least one of those connections is already downloading
- * (during bootstrap), or connecting (just after the bootstrap consensus is
- * downloaded).
- * It won't close any consensus connections initiated after bootstrap,
- * because those attempts are made one at a time. */
- connection_dir_close_extra_consensus_conns();
-
/* 3a. Every second, we examine pending circuits and prune the
* ones which have been pending for more than a few seconds.
* We do this before step 4, so it can try building more if
@@ -1503,6 +1422,7 @@ run_scheduled_events(time_t now)
/* (If our circuit build timeout can ever become lower than a second (which
* it can't, currently), we should do this more often.) */
circuit_expire_building();
+ circuit_expire_waiting_for_better_guard();
/* 3b. Also look at pending streams and prune the ones that 'began'
* a long time ago but haven't gotten a 'connected' yet.
@@ -1527,8 +1447,14 @@ run_scheduled_events(time_t now)
circuit_expire_old_circs_as_needed(now);
}
+ if (!net_is_disabled()) {
+ /* This is usually redundant with circuit_build_needed_circs() above,
+ * but it is very fast when there is no work to do. */
+ connection_ap_attach_pending(0);
+ }
+
/* 5. We do housekeeping for each connection... */
- connection_or_set_bad_connections(NULL, 0);
+ channel_update_bad_for_new_circs(NULL, 0);
int i;
for (i=0;i<smartlist_len(connection_array);i++) {
run_connection_housekeeping(i, now);
@@ -1537,12 +1463,6 @@ run_scheduled_events(time_t now)
/* 6. And remove any marked circuits... */
circuit_close_all_marked();
- /* 7. And upload service descriptors if necessary. */
- if (have_completed_a_circuit() && !net_is_disabled()) {
- rend_consider_services_upload(now);
- rend_consider_descriptor_republication();
- }
-
/* 8. and blow away any connections that need to die. have to do this now,
* because if we marked a conn for close and left its socket -1, then
* we'll pass it to poll/select and bad things will happen.
@@ -1560,19 +1480,26 @@ run_scheduled_events(time_t now)
/* 11b. check pending unconfigured managed proxies */
if (!net_is_disabled() && pt_proxies_configuration_pending())
pt_configure_remaining_proxies();
+
+ /* 12. launch diff computations. (This is free if there are none to
+ * launch.) */
+ if (server_mode(options)) {
+ consdiffmgr_rescan();
+ }
}
+/* Periodic callback: rotate the onion keys after the period defined by the
+ * "onion-key-rotation-days" consensus parameter, shut down and restart all
+ * cpuworkers, and update our descriptor if necessary.
+ */
static int
rotate_onion_key_callback(time_t now, const or_options_t *options)
{
- /* 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys,
- * shut down and restart all cpuworkers, and update the directory if
- * necessary.
- */
if (server_mode(options)) {
- time_t rotation_time = get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME;
+ int onion_key_lifetime = get_onion_key_lifetime();
+ time_t rotation_time = get_onion_key_set_at()+onion_key_lifetime;
if (rotation_time > now) {
- return safe_timer_diff(now, rotation_time);
+ return ONION_KEY_CONSENSUS_CHECK_INTERVAL;
}
log_info(LD_GENERAL,"Rotating onion key.");
@@ -1583,21 +1510,49 @@ rotate_onion_key_callback(time_t now, const or_options_t *options)
}
if (advertised_server_mode() && !options->DisableNetwork)
router_upload_dir_desc_to_dirservers(0);
- return MIN_ONION_KEY_LIFETIME;
+ return ONION_KEY_CONSENSUS_CHECK_INTERVAL;
}
return PERIODIC_EVENT_NO_UPDATE;
}
+/* Period callback: Check if our old onion keys are still valid after the
+ * period of time defined by the consensus parameter
+ * "onion-key-grace-period-days", otherwise expire them by setting them to
+ * NULL.
+ */
+static int
+check_onion_keys_expiry_time_callback(time_t now, const or_options_t *options)
+{
+ if (server_mode(options)) {
+ int onion_key_grace_period = get_onion_key_grace_period();
+ time_t expiry_time = get_onion_key_set_at()+onion_key_grace_period;
+ if (expiry_time > now) {
+ return ONION_KEY_CONSENSUS_CHECK_INTERVAL;
+ }
+
+ log_info(LD_GENERAL, "Expiring old onion keys.");
+ expire_old_onion_keys();
+ cpuworkers_rotate_keyinfo();
+ return ONION_KEY_CONSENSUS_CHECK_INTERVAL;
+ }
+
+ return PERIODIC_EVENT_NO_UPDATE;
+}
+
+/* Periodic callback: Every 30 seconds, check whether it's time to make new
+ * Ed25519 subkeys.
+ */
static int
check_ed_keys_callback(time_t now, const or_options_t *options)
{
if (server_mode(options)) {
if (should_make_new_ed_keys(options, now)) {
- if (load_ed_keys(options, now) < 0 ||
- generate_ed_link_cert(options, now)) {
+ int new_signing_key = load_ed_keys(options, now);
+ if (new_signing_key < 0 ||
+ generate_ed_link_cert(options, now, new_signing_key > 0)) {
log_err(LD_OR, "Unable to update Ed25519 keys! Exiting.");
tor_cleanup();
- exit(0);
+ exit(1);
}
}
return 30;
@@ -1605,6 +1560,11 @@ check_ed_keys_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: Every {LAZY,GREEDY}_DESCRIPTOR_RETRY_INTERVAL,
+ * see about fetching descriptors, microdescriptors, and extrainfo
+ * documents.
+ */
static int
launch_descriptor_fetches_callback(time_t now, const or_options_t *options)
{
@@ -1619,15 +1579,10 @@ launch_descriptor_fetches_callback(time_t now, const or_options_t *options)
return GREEDY_DESCRIPTOR_RETRY_INTERVAL;
}
-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;
-}
-
+/**
+ * Periodic event: Rotate our X.509 certificates and TLS keys once every
+ * MAX_SSL_KEY_LIFETIME_INTERNAL.
+ */
static int
rotate_x509_certificate_callback(time_t now, const or_options_t *options)
{
@@ -1646,6 +1601,11 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options)
log_err(LD_BUG, "Error reinitializing TLS context");
tor_assert_unreached();
}
+ if (generate_ed_link_cert(options, now, 1)) {
+ log_err(LD_OR, "Unable to update Ed25519->TLS link certificate for "
+ "new TLS context.");
+ tor_assert_unreached();
+ }
/* We also make sure to rotate the TLS connections themselves if they've
* been up for too long -- but that's done via is_bad_for_new_circs in
@@ -1653,6 +1613,10 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options)
return MAX_SSL_KEY_LIFETIME_INTERNAL;
}
+/**
+ * Periodic callback: once an hour, grab some more entropy from the
+ * kernel and feed it to our CSPRNG.
+ **/
static int
add_entropy_callback(time_t now, const or_options_t *options)
{
@@ -1669,6 +1633,10 @@ add_entropy_callback(time_t now, const or_options_t *options)
return ENTROPY_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, make sure we test
+ * the routers on the network for reachability.
+ */
static int
launch_reachability_tests_callback(time_t now, const or_options_t *options)
{
@@ -1680,6 +1648,10 @@ launch_reachability_tests_callback(time_t now, const or_options_t *options)
return REACHABILITY_TEST_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, discount the stability
+ * information (and other rephist information) that's older.
+ */
static int
downrate_stability_callback(time_t now, const or_options_t *options)
{
@@ -1691,6 +1663,10 @@ downrate_stability_callback(time_t now, const or_options_t *options)
return safe_timer_diff(now, next);
}
+/**
+ * Periodic callback: if we're an authority, record our measured stability
+ * information from rephist in an mtbf file.
+ */
static int
save_stability_callback(time_t now, const or_options_t *options)
{
@@ -1703,6 +1679,10 @@ save_stability_callback(time_t now, const or_options_t *options)
return SAVE_STABILITY_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, check on our authority
+ * certificate (the one that authenticates our authority signing key).
+ */
static int
check_authority_cert_callback(time_t now, const or_options_t *options)
{
@@ -1715,18 +1695,21 @@ check_authority_cert_callback(time_t now, const or_options_t *options)
return CHECK_V3_CERTIFICATE_INTERVAL;
}
+/**
+ * Periodic callback: If our consensus is too old, recalculate whether
+ * we can actually use it.
+ */
static int
check_expired_networkstatus_callback(time_t now, const or_options_t *options)
{
(void)options;
- /* 1f. Check whether our networkstatus has expired.
- */
+ /* Check whether our networkstatus has expired. */
networkstatus_t *ns = networkstatus_get_latest_consensus();
/*XXXX RD: This value needs to be the same as REASONABLY_LIVE_TIME in
* networkstatus_get_reasonably_live_consensus(), but that value is way
* way too high. Arma: is the bridge issue there resolved yet? -NM */
#define NS_EXPIRY_SLOP (24*60*60)
- if (ns && ns->valid_until < now+NS_EXPIRY_SLOP &&
+ if (ns && ns->valid_until < (now - NS_EXPIRY_SLOP) &&
router_have_minimum_dir_info()) {
router_dir_info_changed();
}
@@ -1734,6 +1717,9 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options)
return CHECK_EXPIRED_NS_INTERVAL;
}
+/**
+ * Periodic callback: Write statistics to disk if appropriate.
+ */
static int
write_stats_file_callback(time_t now, const or_options_t *options)
{
@@ -1781,6 +1767,31 @@ write_stats_file_callback(time_t now, const or_options_t *options)
return safe_timer_diff(now, next_time_to_write_stats_files);
}
+#define CHANNEL_CHECK_INTERVAL (60*60)
+static int
+check_canonical_channels_callback(time_t now, const or_options_t *options)
+{
+ (void)now;
+ if (public_server_mode(options))
+ channel_check_for_duplicates();
+
+ return CHANNEL_CHECK_INTERVAL;
+}
+
+static int
+reset_padding_counts_callback(time_t now, const or_options_t *options)
+{
+ if (options->PaddingStatistics) {
+ rep_hist_prep_published_padding_counts(now);
+ }
+
+ rep_hist_reset_padding_counts();
+ return REPHIST_CELL_PADDING_COUNTS_INTERVAL;
+}
+
+/**
+ * Periodic callback: Write bridge statistics to disk if appropriate.
+ */
static int
record_bridge_stats_callback(time_t now, const or_options_t *options)
{
@@ -1808,6 +1819,9 @@ record_bridge_stats_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: Clean in-memory caches every once in a while
+ */
static int
clean_caches_callback(time_t now, const or_options_t *options)
{
@@ -1815,12 +1829,16 @@ clean_caches_callback(time_t now, const or_options_t *options)
rep_history_clean(now - options->RephistTrackTime);
rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
rend_cache_clean(now, REND_CACHE_TYPE_SERVICE);
- rend_cache_clean_v2_descs_as_dir(now, 0);
+ hs_cache_clean_as_dir(now);
microdesc_cache_rebuild(NULL, 0);
#define CLEAN_CACHES_INTERVAL (30*60)
return CLEAN_CACHES_INTERVAL;
}
+/**
+ * Periodic callback: Clean the cache of failed hidden service lookups
+ * frequently.
+ */
static int
rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
{
@@ -1832,20 +1850,21 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
return 30;
}
+/**
+ * Periodic callback: If we're a server and initializing dns failed, retry.
+ */
static int
retry_dns_callback(time_t now, const or_options_t *options)
{
(void)now;
#define RETRY_DNS_INTERVAL (10*60)
- /* If we're a server and initializing dns failed, retry periodically. */
if (server_mode(options) && has_dns_init_failed())
dns_init();
return RETRY_DNS_INTERVAL;
}
- /* 2. Periodically, we consider force-uploading our descriptor
- * (if we've passed our internal checks). */
-
+/** Periodic callback: consider rebuilding or and re-uploading our descriptor
+ * (if we've passed our internal checks). */
static int
check_descriptor_callback(time_t now, const or_options_t *options)
{
@@ -1872,6 +1891,11 @@ check_descriptor_callback(time_t now, const or_options_t *options)
return CHECK_DESCRIPTOR_INTERVAL;
}
+/**
+ * Periodic callback: check whether we're reachable (as a relay), and
+ * whether our bandwidth has changed enough that we need to
+ * publish a new descriptor.
+ */
static int
check_for_reachability_bw_callback(time_t now, const or_options_t *options)
{
@@ -1908,13 +1932,13 @@ check_for_reachability_bw_callback(time_t now, const or_options_t *options)
return CHECK_DESCRIPTOR_INTERVAL;
}
+/**
+ * Periodic event: once a minute, (or every second if TestingTorNetwork, or
+ * during client bootstrap), check whether we want to download any
+ * networkstatus documents. */
static int
fetch_networkstatus_callback(time_t now, const or_options_t *options)
{
- /* 2c. Every minute (or every second if TestingTorNetwork, or during
- * client bootstrap), check whether we want to download any networkstatus
- * documents. */
-
/* How often do we check whether we should download network status
* documents? */
const int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping(
@@ -1936,12 +1960,13 @@ fetch_networkstatus_callback(time_t now, const or_options_t *options)
return networkstatus_dl_check_interval;
}
+/**
+ * Periodic callback: Every 60 seconds, we relaunch listeners if any died. */
static int
retry_listeners_callback(time_t now, const or_options_t *options)
{
(void)now;
(void)options;
- /* 3d. And every 60 seconds, we relaunch listeners if any died. */
if (!net_is_disabled()) {
retry_all_listeners(NULL, NULL, 0);
return 60;
@@ -1949,6 +1974,9 @@ retry_listeners_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: as a server, see if we have any old unused circuits
+ * that should be expired */
static int
expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options)
{
@@ -1958,6 +1986,10 @@ expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options)
return 11;
}
+/**
+ * Periodic event: if we're an exit, see if our DNS server is telling us
+ * obvious lies.
+ */
static int
check_dns_honesty_callback(time_t now, const or_options_t *options)
{
@@ -1980,6 +2012,10 @@ check_dns_honesty_callback(time_t now, const or_options_t *options)
return 12*3600 + crypto_rand_int(12*3600);
}
+/**
+ * Periodic callback: if we're the bridge authority, write a networkstatus
+ * file to disk.
+ */
static int
write_bridge_ns_callback(time_t now, const or_options_t *options)
{
@@ -1992,6 +2028,9 @@ write_bridge_ns_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: poke the tor-fw-helper app if we're using one.
+ */
static int
check_fw_helper_app_callback(time_t now, const or_options_t *options)
{
@@ -2015,19 +2054,69 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options)
return PORT_FORWARDING_CHECK_INTERVAL;
}
+/**
+ * Periodic callback: write the heartbeat message in the logs.
+ *
+ * If writing the heartbeat message to the logs fails for some reason, retry
+ * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds.
+ */
static int
heartbeat_callback(time_t now, const or_options_t *options)
{
static int first = 1;
- /* 12. write the heartbeat message */
+
+ /* Check if heartbeat is disabled */
+ if (!options->HeartbeatPeriod) {
+ return PERIODIC_EVENT_NO_UPDATE;
+ }
+
+ /* Skip the first one. */
if (first) {
- first = 0; /* Skip the first one. */
+ first = 0;
+ return options->HeartbeatPeriod;
+ }
+
+ /* Write the heartbeat message */
+ if (log_heartbeat(now) == 0) {
+ return options->HeartbeatPeriod;
} else {
- log_heartbeat(now);
+ /* If we couldn't write the heartbeat log message, try again in the minimum
+ * interval of time. */
+ return MIN_HEARTBEAT_PERIOD;
}
- /* XXXX This isn't such a good way to handle possible changes in the
- * callback event */
- return options->HeartbeatPeriod;
+}
+
+#define CDM_CLEAN_CALLBACK_INTERVAL 600
+static int
+clean_consdiffmgr_callback(time_t now, const or_options_t *options)
+{
+ (void)now;
+ if (server_mode(options)) {
+ consdiffmgr_cleanup();
+ }
+ return CDM_CLEAN_CALLBACK_INTERVAL;
+}
+
+/*
+ * Periodic callback: Run scheduled events for HS service. This is called
+ * every second.
+ */
+static int
+hs_service_callback(time_t now, const or_options_t *options)
+{
+ (void) options;
+
+ /* We need to at least be able to build circuits and that we actually have
+ * a working network. */
+ if (!have_completed_a_circuit() || net_is_disabled()) {
+ goto end;
+ }
+
+ hs_service_run_scheduled_events(now);
+
+ end:
+ /* Every 1 second. */
+ return 1;
}
/** Timer: used to invoke second_elapsed_callback() once per second. */
@@ -2059,25 +2148,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();
@@ -2094,7 +2168,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) {
/* every 20 minutes, check and complain if necessary */
const routerinfo_t *me = router_get_my_routerinfo();
- if (me && !check_whether_orport_reachable()) {
+ if (me && !check_whether_orport_reachable(options)) {
char *address = tor_dup_ip(me->addr);
log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that "
"its ORPort is reachable. Relays do not publish descriptors "
@@ -2107,7 +2181,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg)
tor_free(address);
}
- if (me && !check_whether_dirport_reachable()) {
+ if (me && !check_whether_dirport_reachable(options)) {
char *address = tor_dup_ip(me->addr);
log_warn(LD_CONFIG,
"Your server (%s:%d) has not managed to confirm that its "
@@ -2149,12 +2223,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)
{
@@ -2198,7 +2271,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
@@ -2225,8 +2297,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) {
@@ -2244,8 +2316,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");
}
@@ -2310,7 +2382,7 @@ do_hup(void)
tor_free(msg);
}
}
- if (authdir_mode_handles_descs(options, -1)) {
+ if (authdir_mode(options)) {
/* reload the approved-routers file */
if (dirserv_load_fingerprint_file() < 0) {
/* warnings are logged from dirserv_load_fingerprint_file() directly */
@@ -2337,8 +2409,9 @@ do_hup(void)
/* Maybe we've been given a new ed25519 key or certificate?
*/
time_t now = approx_time();
- if (load_ed_keys(options, now) < 0 ||
- generate_ed_link_cert(options, now)) {
+ int new_signing_key = load_ed_keys(options, now);
+ if (new_signing_key < 0 ||
+ generate_ed_link_cert(options, now, new_signing_key > 0)) {
log_warn(LD_OR, "Problem reloading Ed25519 keys; still using old keys.");
}
@@ -2374,14 +2447,9 @@ 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);
+ monotime_init();
+ timers_initialize();
/* load the private keys, if we're supposed to have them, and set up the
* TLS context. */
@@ -2394,10 +2462,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);
@@ -2451,6 +2517,14 @@ do_main_loop(void)
/* launch cpuworkers. Need to do this *after* we've read the onion key. */
cpu_init();
}
+ consdiffmgr_enable_background_compression();
+
+ /* Setup shared random protocol subsystem. */
+ if (authdir_mode_v3(get_options())) {
+ if (sr_init(1) < 0) {
+ return -1;
+ }
+ }
/* set up once-a-second callback. */
if (! second_timer) {
@@ -2487,7 +2561,6 @@ do_main_loop(void)
}
#endif
-#ifndef USE_BUFFEREVENTS
if (!refill_timer) {
struct timeval refill_interval;
int msecs = get_options()->TokenBucketRefillInterval;
@@ -2501,7 +2574,6 @@ do_main_loop(void)
NULL);
tor_assert(refill_timer);
}
-#endif
#ifdef HAVE_SYSTEMD
{
@@ -2536,19 +2608,26 @@ run_main_loop_once(void)
/* Make it easier to tell whether libevent failure is our fault or not. */
errno = 0;
#endif
- /* All active linked conns should get their read events activated. */
+
+ /* All active linked conns should get their read events activated,
+ * so that libevent knows to run their callbacks. */
SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn,
event_active(conn->read_event, EV_READ, 1));
called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0;
+ /* Make sure we know (about) what time it is. */
update_approx_time(time(NULL));
- /* poll until we have an event, or the second ends, or until we have
- * some active linked connections to trigger events for. */
+ /* Here it is: the main loop. Here we tell Libevent to poll until we have
+ * an event, or the second ends, or until we have some active linked
+ * connections to trigger events for. Libevent will wait till one
+ * of these happens, then run all the appropriate callbacks. */
loop_result = event_base_loop(tor_libevent_get_base(),
called_loop_once ? EVLOOP_ONCE : 0);
- /* let catch() handle things like ^c, and otherwise don't worry about it */
+ /* Oh, the loop failed. That might be an error that we need to
+ * catch, but more likely, it's just an interrupted poll() call or something,
+ * and we should try again. */
if (loop_result < 0) {
int e = tor_socket_errno(-1);
/* let the program survive things like ^z */
@@ -2571,9 +2650,17 @@ run_main_loop_once(void)
}
}
- /* This will be pretty fast if nothing new is pending. Note that this gets
- * called once per libevent loop, which will make it happen once per group
- * of events that fire, or once per second. */
+ /* And here is where we put callbacks that happen "every time the event loop
+ * runs." They must be very fast, or else the whole Tor process will get
+ * slowed down.
+ *
+ * Note that this gets called once per libevent loop, which will make it
+ * happen once per group of events that fire, or once per second. */
+
+ /* If there are any pending client connections, try attaching them to
+ * circuits (if we can.) This will be pretty fast if nothing new is
+ * pending.
+ */
connection_ap_attach_pending(0);
return 1;
@@ -2694,9 +2781,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.
*/
@@ -2817,7 +2901,6 @@ dumpstats(int severity)
rep_hist_dump_stats(now,severity);
rend_service_dump_stats(severity);
- dump_pk_ops(severity);
dump_distinct_digest_count(severity);
}
@@ -2956,6 +3039,8 @@ tor_init(int argc, char *argv[])
rend_cache_init();
addressmap_init(); /* Init the client dns cache. Do it always, since it's
* cheap. */
+ /* Initialize the HS subsystem. */
+ hs_init();
{
/* We search for the "quiet" option first, since it decides whether we
@@ -2998,18 +3083,18 @@ 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, Zlib %s, Liblzma %s, and Libzstd %s.", version,
get_uname(),
tor_libevent_get_version_str(),
crypto_openssl_get_version_str(),
- tor_zlib_get_version_str());
+ tor_compress_supports_method(ZLIB_METHOD) ?
+ tor_compress_version_str(ZLIB_METHOD) : "N/A",
+ tor_compress_supports_method(LZMA_METHOD) ?
+ tor_compress_version_str(LZMA_METHOD) : "N/A",
+ tor_compress_supports_method(ZSTD_METHOD) ?
+ tor_compress_version_str(ZSTD_METHOD) : "N/A");
log_notice(LD_GENERAL, "Tor can't help you if you use it wrong! "
"Learn how to be safe at "
@@ -3020,10 +3105,14 @@ 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
+ {
+ rust_str_t rust_str = rust_welcome_string();
+ const char *s = rust_str_get(rust_str);
+ if (strlen(s) > 0) {
+ log_notice(LD_GENERAL, "%s", s);
+ }
+ rust_str_free(rust_str);
+ }
if (network_init()<0) {
log_err(LD_BUG,"Error initializing network; exiting.");
@@ -3036,15 +3125,25 @@ tor_init(int argc, char *argv[])
return -1;
}
+ /* The options are now initialised */
+ const or_options_t *options = get_options();
+
+ /* Initialize channelpadding parameters to defaults until we get
+ * a consensus */
+ channelpadding_new_consensus_params(NULL);
+
+ /* Initialize predicted ports list after loading options */
+ predicted_ports_init();
+
#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;
}
@@ -3053,6 +3152,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;
}
@@ -3090,7 +3192,7 @@ try_locking(const or_options_t *options, int err_if_locked)
r = try_locking(options, 0);
if (r<0) {
log_err(LD_GENERAL, "No, it's still there. Exiting.");
- exit(0);
+ exit(1);
}
return r;
}
@@ -3138,7 +3240,6 @@ tor_free_all(int postfork)
networkstatus_free_all();
addressmap_free_all();
dirserv_free_all();
- rend_service_free_all();
rend_cache_free_all();
rend_service_authorization_free_all();
rep_hist_free_all();
@@ -3154,9 +3255,14 @@ 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();
+ bridges_free_all();
+ consdiffmgr_free_all();
+ hs_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();
@@ -3177,9 +3283,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();
@@ -3188,6 +3292,7 @@ tor_free_all(int postfork)
if (!postfork) {
escaped(NULL);
esc_router_info(NULL);
+ clean_up_backtrace_handler();
logs_free_all(); /* free log strings. do this last so logs keep working. */
}
}
@@ -3218,10 +3323,16 @@ 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();
}
+
+ timers_shutdown();
+
#ifdef USE_DMALLOC
dmalloc_log_stats();
#endif
@@ -3376,6 +3487,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");
@@ -3388,7 +3500,7 @@ sandbox_init_filter(void)
if (options->BridgeAuthoritativeDir)
OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp");
- if (authdir_mode_handles_descs(options, -1))
+ if (authdir_mode(options))
OPEN_DATADIR("approved-routers");
if (options->ServerDNSResolvConfFile)
@@ -3428,6 +3540,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");
@@ -3459,7 +3572,7 @@ sandbox_init_filter(void)
{
smartlist_t *files = smartlist_new();
smartlist_t *dirs = smartlist_new();
- rend_services_add_filenames_to_lists(files, dirs);
+ hs_service_lists_fnames_for_sandbox(files, dirs);
SMARTLIST_FOREACH(files, char *, file_name, {
char *tmp_name = NULL;
tor_asprintf(&tmp_name, "%s.tmp", file_name);
@@ -3468,6 +3581,7 @@ sandbox_init_filter(void)
/* steals references */
sandbox_cfg_allow_open_filename(&cfg, file_name);
sandbox_cfg_allow_open_filename(&cfg, tmp_name);
+ tor_free(file_name);
});
SMARTLIST_FOREACH(dirs, char *, dir, {
/* steals reference */
@@ -3533,6 +3647,7 @@ sandbox_init_filter(void)
OPEN_DATADIR2_SUFFIX("stats", "exit-stats", ".tmp");
OPEN_DATADIR2_SUFFIX("stats", "buffer-stats", ".tmp");
OPEN_DATADIR2_SUFFIX("stats", "conn-stats", ".tmp");
+ OPEN_DATADIR2_SUFFIX("stats", "hidserv-stats", ".tmp");
OPEN_DATADIR("approved-routers");
OPEN_DATADIR_SUFFIX("fingerprint", ".tmp");
@@ -3571,8 +3686,11 @@ sandbox_init_filter(void)
get_datadir_fname2("keys", "secret_onion_key_ntor.old"));
STAT_DATADIR("keys");
+ OPEN_DATADIR("stats");
STAT_DATADIR("stats");
STAT_DATADIR2("stats", "dirreq-stats");
+
+ consdiffmgr_register_with_sandbox(&cfg);
}
init_addrinfo();
@@ -3589,6 +3707,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. */
@@ -3597,7 +3720,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
@@ -3605,13 +3731,15 @@ tor_main(int argc, char *argv[])
update_approx_time(time(NULL));
tor_threads_init();
+ tor_compress_init();
init_logging(0);
+ monotime_init();
#ifdef USE_DMALLOC
{
/* Instruct OpenSSL to use our internal wrappers for malloc,
realloc and free. */
- int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_);
- tor_assert(r);
+ int r = crypto_use_tor_alloc_functions();
+ tor_assert(r == 0);
}
#endif
#ifdef NT_SERVICE
@@ -3647,7 +3775,12 @@ tor_main(int argc, char *argv[])
result = do_main_loop();
break;
case CMD_KEYGEN:
- result = load_ed_keys(get_options(), time(NULL));
+ result = load_ed_keys(get_options(), time(NULL)) < 0;
+ break;
+ case CMD_KEY_EXPIRATION:
+ init_keys();
+ result = log_cert_expiration();
+ result = 0;
break;
case CMD_LIST_FINGERPRINT:
result = do_list_fingerprint();
diff --git a/src/or/main.h b/src/or/main.h
index ad865b8124..57aa372750 100644
--- a/src/or/main.h
+++ b/src/or/main.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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));
@@ -45,8 +45,12 @@ int connection_is_writing(connection_t *conn);
MOCK_DECL(void,connection_stop_writing,(connection_t *conn));
MOCK_DECL(void,connection_start_writing,(connection_t *conn));
+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);
@@ -75,11 +79,22 @@ 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);
STATIC void initialize_periodic_events(void);
STATIC void teardown_periodic_events(void);
+#ifdef TOR_UNIT_TESTS
+extern smartlist_t *connection_array;
+#endif
#endif
#endif
diff --git a/src/or/microdesc.c b/src/or/microdesc.c
index 299042995b..18a6fbded7 100644
--- a/src/or/microdesc.c
+++ b/src/or/microdesc.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009-2016, The Tor Project, Inc. */
+/* Copyright (c) 2009-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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,
@@ -803,18 +804,6 @@ microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache, const char *d)
return md;
}
-/** Return the mean size of decriptors added to <b>cache</b> since it was last
- * cleared. Used to estimate the size of large downloads. */
-size_t
-microdesc_average_size(microdesc_cache_t *cache)
-{
- if (!cache)
- cache = get_microdesc_cache();
- if (!cache->n_seen)
- return 512;
- return (size_t)(cache->total_len_seen / cache->n_seen);
-}
-
/** Return a smartlist of all the sha256 digest of the microdescriptors that
* are listed in <b>ns</b> but not present in <b>cache</b>. Returns pointers
* to internals of <b>ns</b>; you should not free the members of the resulting
@@ -887,7 +876,7 @@ update_microdesc_downloads(time_t now)
smartlist_free(missing);
}
-/** For every microdescriptor listed in the current microdecriptor consensus,
+/** For every microdescriptor listed in the current microdescriptor consensus,
* update its last_listed field to be at least as recent as the publication
* time of the current microdescriptor consensus.
*/
@@ -916,20 +905,9 @@ update_microdescs_from_networkstatus(time_t now)
int
we_use_microdescriptors_for_circuits(const or_options_t *options)
{
- int ret = options->UseMicrodescriptors;
- if (ret == -1) {
- /* UseMicrodescriptors is "auto"; we need to decide: */
- /* If we are configured to use bridges and none of our bridges
- * know what a microdescriptor is, the answer is no. */
- if (options->UseBridges && !any_bridge_supports_microdescriptors())
- 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
- * a partitioning issue here where bridges differ from clients. */
- ret = !server_mode(options) && !options->FetchUselessDescriptors;
- }
- return ret;
+ if (options->UseMicrodescriptors == 0)
+ return 0; /* the user explicitly picked no */
+ return 1; /* yes and auto both mean yes */
}
/** Return true iff we should try to download microdescriptors at all. */
@@ -955,8 +933,8 @@ we_fetch_router_descriptors(const or_options_t *options)
}
/** Return the consensus flavor we actually want to use to build circuits. */
-int
-usable_consensus_flavor(void)
+MOCK_IMPL(int,
+usable_consensus_flavor,(void))
{
if (we_use_microdescriptors_for_circuits(get_options())) {
return FLAV_MICRODESC;
diff --git a/src/or/microdesc.h b/src/or/microdesc.h
index 0675e233d6..943873066e 100644
--- a/src/or/microdesc.h
+++ b/src/or/microdesc.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -32,8 +32,6 @@ void microdesc_cache_clear(microdesc_cache_t *cache);
microdesc_t *microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache,
const char *d);
-size_t microdesc_average_size(microdesc_cache_t *cache);
-
smartlist_t *microdesc_list_missing_digest256(networkstatus_t *ns,
microdesc_cache_t *cache,
int downloadable_only,
@@ -47,7 +45,7 @@ void microdesc_free_all(void);
void update_microdesc_downloads(time_t now);
void update_microdescs_from_networkstatus(time_t now);
-int usable_consensus_flavor(void);
+MOCK_DECL(int, usable_consensus_flavor,(void));
int we_fetch_microdescriptors(const or_options_t *options);
int we_fetch_router_descriptors(const or_options_t *options);
int we_use_microdescriptors_for_circuits(const or_options_t *options);
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 51c985e85e..69bff55cff 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -1,17 +1,44 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file networkstatus.c
- * \brief Functions and structures for handling network status documents as a
- * client or cache.
+ * \brief Functions and structures for handling networkstatus documents as a
+ * client or as a directory cache.
+ *
+ * A consensus networkstatus object is created by the directory
+ * authorities. It authenticates a set of network parameters--most
+ * importantly, the list of all the relays in the network. This list
+ * of relays is represented as an array of routerstatus_t objects.
+ *
+ * There are currently two flavors of consensus. With the older "NS"
+ * flavor, each relay is associated with a digest of its router
+ * descriptor. Tor instances that use this consensus keep the list of
+ * router descriptors as routerinfo_t objects stored and managed in
+ * routerlist.c. With the newer "microdesc" flavor, each relay is
+ * associated with a digest of the microdescriptor that the authorities
+ * made for it. These are stored and managed in microdesc.c. Information
+ * about the router is divided between the the networkstatus and the
+ * microdescriptor according to the general rule that microdescriptors
+ * should hold information that changes much less frequently than the
+ * information in the networkstatus.
+ *
+ * Modern clients use microdescriptor networkstatuses. Directory caches
+ * need to keep both kinds of networkstatus document, so they can serve them.
+ *
+ * This module manages fetching, holding, storing, updating, and
+ * validating networkstatus objects. The download-and-validate process
+ * is slightly complicated by the fact that the keys you need to
+ * validate a consensus are stored in the authority certificates, which
+ * you might not have yet when you download the consensus.
*/
#define NETWORKSTATUS_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "channel.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
@@ -19,6 +46,7 @@
#include "config.h"
#include "connection.h"
#include "connection_or.h"
+#include "consdiffmgr.h"
#include "control.h"
#include "directory.h"
#include "dirserv.h"
@@ -28,11 +56,15 @@
#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"
+#include "channelpadding.h"
/** Map from lowercase nickname to identity digest of named server, if any. */
static strmap_t *named_server_map = NULL;
@@ -40,21 +72,13 @@ 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;
+STATIC networkstatus_t *current_ns_consensus = NULL;
-/** Most recently received and validated v3 "microdec"-flavored consensus
+/** Most recently received and validated v3 "microdesc"-flavored consensus
* network status. */
-static networkstatus_t *current_md_consensus = NULL;
+STATIC networkstatus_t *current_md_consensus = NULL;
/** A v3 consensus networkstatus that we've received, but which we don't
* have enough certificates to be happy about. */
@@ -86,9 +110,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 +129,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
@@ -121,18 +145,18 @@ static int have_warned_about_new_version = 0;
static void routerstatus_list_update_named_server_map(void);
static void update_consensus_bootstrap_multiple_downloads(
time_t now,
- const or_options_t *options,
- int we_are_bootstrapping);
+ 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;
@@ -146,6 +170,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]);
@@ -153,55 +180,77 @@ networkstatus_reset_download_failures(void)
download_status_reset(&consensus_bootstrap_dl_status[i]);
}
+/**
+ * Read and and return the cached consensus of type <b>flavorname</b>. If
+ * <b>unverified</b> is false, get the one we haven't verified. Return NULL if
+ * the file isn't there. */
+static char *
+networkstatus_read_cached_consensus_impl(int flav,
+ const char *flavorname,
+ int unverified_consensus)
+{
+ char buf[128];
+ const char *prefix;
+ if (unverified_consensus) {
+ prefix = "unverified";
+ } else {
+ prefix = "cached";
+ }
+ if (flav == FLAV_NS) {
+ tor_snprintf(buf, sizeof(buf), "%s-consensus", prefix);
+ } else {
+ tor_snprintf(buf, sizeof(buf), "%s-%s-consensus", prefix, flavorname);
+ }
+
+ char *filename = get_datadir_fname(buf);
+ char *result = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
+ tor_free(filename);
+ return result;
+}
+
+/** Return a new string containing the current cached consensus of flavor
+ * <b>flavorname</b>. */
+char *
+networkstatus_read_cached_consensus(const char *flavorname)
+ {
+ int flav = networkstatus_parse_flavor_name(flavorname);
+ if (flav < 0)
+ return NULL;
+ return networkstatus_read_cached_consensus_impl(flav, flavorname, 0);
+}
+
/** Read every cached v3 consensus networkstatus from the disk. */
int
router_reload_consensus_networkstatus(void)
{
- char *filename;
- char *s;
const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS;
int flav;
/* FFFF Suppress warnings if cached consensus is bad? */
for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
- char buf[128];
const char *flavor = networkstatus_get_flavor_name(flav);
- if (flav == FLAV_NS) {
- filename = get_datadir_fname("cached-consensus");
- } else {
- tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor);
- filename = get_datadir_fname(buf);
- }
- s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
+ char *s = networkstatus_read_cached_consensus_impl(flav, flavor, 0);
if (s) {
- if (networkstatus_set_current_consensus(s, flavor, flags) < -1) {
- log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
- flavor, filename);
+ if (networkstatus_set_current_consensus(s, flavor, flags, NULL) < -1) {
+ log_warn(LD_FS, "Couldn't load consensus %s networkstatus from cache",
+ flavor);
}
tor_free(s);
}
- tor_free(filename);
- if (flav == FLAV_NS) {
- filename = get_datadir_fname("unverified-consensus");
- } else {
- tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor);
- filename = get_datadir_fname(buf);
- }
-
- s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
+ s = networkstatus_read_cached_consensus_impl(flav, flavor, 1);
if (s) {
if (networkstatus_set_current_consensus(s, flavor,
- flags|NSSET_WAS_WAITING_FOR_CERTS)) {
- log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
- flavor, filename);
- }
+ flags|NSSET_WAS_WAITING_FOR_CERTS,
+ NULL)) {
+ log_info(LD_FS, "Couldn't load unverified consensus %s networkstatus "
+ "from cache", flavor);
+ }
tor_free(s);
}
- tor_free(filename);
}
- if (!current_consensus) {
+ if (!networkstatus_get_latest_consensus()) {
if (!named_server_map)
named_server_map = strmap_new();
if (!unnamed_server_map)
@@ -217,13 +266,14 @@ router_reload_consensus_networkstatus(void)
}
/** Free all storage held by the vote_routerstatus object <b>rs</b>. */
-STATIC void
+void
vote_routerstatus_free(vote_routerstatus_t *rs)
{
vote_microdesc_hash_t *h, *next;
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);
@@ -270,6 +320,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);
@@ -320,6 +375,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);
}
@@ -634,7 +697,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) {
@@ -659,6 +722,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 *,
@@ -678,9 +778,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);
}
@@ -736,8 +838,11 @@ networkstatus_nickname_is_unnamed(const char *nickname)
#define NONAUTHORITY_NS_CACHE_INTERVAL (60*60)
/** Return true iff, given the options listed in <b>options</b>, <b>flavor</b>
- * is the flavor of a consensus networkstatus that we would like to fetch. */
-static int
+ * is the flavor of a consensus networkstatus that we would like to fetch.
+ *
+ * For certificate fetches, use we_want_to_fetch_unknown_auth_certs, and
+ * for serving fetched documents, use directory_caches_dir_info. */
+int
we_want_to_fetch_flavor(const or_options_t *options, int flavor)
{
if (flavor < 0 || flavor > N_CONSENSUS_FLAVORS) {
@@ -759,13 +864,42 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor)
return flavor == usable_consensus_flavor();
}
+/** Return true iff, given the options listed in <b>options</b>, we would like
+ * to fetch and store unknown authority certificates.
+ *
+ * For consensus and descriptor fetches, use we_want_to_fetch_flavor, and
+ * for serving fetched certificates, use directory_caches_unknown_auth_certs.
+ */
+int
+we_want_to_fetch_unknown_auth_certs(const or_options_t *options)
+{
+ if (authdir_mode_v3(options) ||
+ directory_caches_unknown_auth_certs((options))) {
+ /* We want to serve all certs to others, regardless if we would use
+ * them ourselves. */
+ return 1;
+ }
+ if (options->FetchUselessDescriptors) {
+ /* Unknown certificates are definitely useless. */
+ return 1;
+ }
+ /* Otherwise, don't fetch unknown certificates. */
+ return 0;
+}
+
/** How long will we hang onto a possibly live consensus for which we're
* fetching certs before we check whether there is a better one? */
#define DELAY_WHILE_FETCHING_CERTS (20*60)
+/** 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)
@@ -779,11 +913,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;
}
}
@@ -792,26 +929,6 @@ check_consensus_waiting_for_certs(int flavor, time_t now,
return 0;
}
-/* Return the maximum download tries for a consensus, based on options and
- * whether we_are_bootstrapping. */
-static int
-consensus_max_download_tries(const or_options_t *options,
- int we_are_bootstrapping)
-{
- int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks(options);
-
- if (we_are_bootstrapping) {
- if (use_fallbacks) {
- return options->ClientBootstrapConsensusMaxDownloadTries;
- } else {
- return
- options->ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries;
- }
- }
-
- return options->TestingConsensusMaxDownloadTries;
-}
-
/** If we want to download a fresh consensus, launch a new download as
* appropriate. */
static void
@@ -848,7 +965,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;
}
@@ -865,29 +982,14 @@ update_consensus_networkstatus_downloads(time_t now)
&& i == usable_consensus_flavor()) {
/* Check if we're already downloading a usable consensus */
- int consens_conn_count =
- connection_dir_count_by_purpose_and_resource(
- DIR_PURPOSE_FETCH_CONSENSUS,
- resource);
- int connect_consens_conn_count =
- connection_dir_count_by_purpose_resource_and_state(
- DIR_PURPOSE_FETCH_CONSENSUS,
- resource,
- DIR_CONN_STATE_CONNECTING);
-
- /* If not all connections are "connecting", then some are
- * downloading. We want to have at most one downloading at a time. */
- if (connect_consens_conn_count < consens_conn_count) {
+ if (networkstatus_consensus_is_already_downloading(resource))
continue;
- }
/* Make multiple connections for a bootstrap consensus download. */
- update_consensus_bootstrap_multiple_downloads(now, options,
- we_are_bootstrapping);
+ update_consensus_bootstrap_multiple_downloads(now, options);
} else {
/* Check if we failed downloading a consensus too recently */
- int max_dl_tries = consensus_max_download_tries(options,
- we_are_bootstrapping);
+ int max_dl_tries = options->TestingConsensusMaxDownloadTries;
/* Let's make sure we remembered to update consensus_dl_status */
tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS);
@@ -922,12 +1024,16 @@ static void
update_consensus_bootstrap_attempt_downloads(
time_t now,
const or_options_t *options,
- int we_are_bootstrapping,
download_status_t *dls,
download_want_authority_t want_authority)
{
- int max_dl_tries = consensus_max_download_tries(options,
- we_are_bootstrapping);
+ int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks(options);
+ int max_dl_tries = options->ClientBootstrapConsensusMaxDownloadTries;
+ if (!use_fallbacks) {
+ max_dl_tries =
+ options->ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries;
+ }
+
const char *resource = networkstatus_get_flavor_name(
usable_consensus_flavor());
@@ -960,8 +1066,7 @@ update_consensus_bootstrap_attempt_downloads(
*/
static void
update_consensus_bootstrap_multiple_downloads(time_t now,
- const or_options_t *options,
- int we_are_bootstrapping)
+ const or_options_t *options)
{
const int usable_flavor = usable_consensus_flavor();
@@ -970,12 +1075,6 @@ update_consensus_bootstrap_multiple_downloads(time_t now,
return;
}
- /* If we've managed to validate a usable consensus, don't make additional
- * connections. */
- if (!we_are_bootstrapping) {
- return;
- }
-
/* Launch concurrent consensus download attempt(s) based on the mirror and
* authority schedules. Try the mirror first - this makes it slightly more
* likely that we'll connect to the fallback first, and then end the
@@ -994,8 +1093,7 @@ update_consensus_bootstrap_multiple_downloads(time_t now,
if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_f)) {
/* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */
- update_consensus_bootstrap_attempt_downloads(now, options,
- we_are_bootstrapping, dls_f,
+ update_consensus_bootstrap_attempt_downloads(now, options, dls_f,
DL_WANT_ANY_DIRSERVER);
}
}
@@ -1005,8 +1103,7 @@ update_consensus_bootstrap_multiple_downloads(time_t now,
&consensus_bootstrap_dl_status[CONSENSUS_BOOTSTRAP_SOURCE_AUTHORITY];
if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_a)) {
- update_consensus_bootstrap_attempt_downloads(now, options,
- we_are_bootstrapping, dls_a,
+ update_consensus_bootstrap_attempt_downloads(now, options, dls_a,
DL_WANT_AUTHORITY);
}
}
@@ -1201,13 +1298,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
@@ -1219,12 +1316,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
@@ -1244,17 +1390,50 @@ 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;
+ networkstatus_t *ns = networkstatus_get_latest_consensus();
+ if (ns && networkstatus_is_live(ns, now))
+ return ns;
else
return NULL;
}
+/** Given a consensus in <b>ns</b>, return true iff currently live and
+ * unexpired. */
+int
+networkstatus_is_live(const networkstatus_t *ns, time_t now)
+{
+ return (ns->valid_after <= now && now <= ns->valid_until);
+}
+
+/** Determine if <b>consensus</b> is valid or expired recently enough that
+ * we can still use it.
+ *
+ * Return 1 if the consensus is reasonably live, or 0 if it is too old.
+ */
+int
+networkstatus_consensus_reasonably_live(const networkstatus_t *consensus,
+ time_t now)
+{
+ if (BUG(!consensus))
+ return 0;
+
+ return networkstatus_valid_until_is_reasonably_live(consensus->valid_until,
+ now);
+}
+
+/** As networkstatus_consensus_reasonably_live, but takes a valid_until
+ * time rather than an entire consensus. */
+int
+networkstatus_valid_until_is_reasonably_live(time_t valid_until,
+ time_t now)
+{
+#define REASONABLY_LIVE_TIME (24*60*60)
+ return (now <= valid_until + REASONABLY_LIVE_TIME);
+}
+
/* XXXX remove this in favor of get_live_consensus. But actually,
* leave something like it for bridge users, who need to not totally
* lose if they spend a while fetching a new consensus. */
@@ -1263,27 +1442,44 @@ networkstatus_get_live_consensus(time_t now)
networkstatus_t *
networkstatus_get_reasonably_live_consensus(time_t now, int flavor)
{
-#define REASONABLY_LIVE_TIME (24*60*60)
networkstatus_t *consensus =
networkstatus_get_latest_consensus_by_flavor(flavor);
if (consensus &&
consensus->valid_after <= now &&
- now <= consensus->valid_until+REASONABLY_LIVE_TIME)
+ networkstatus_consensus_reasonably_live(consensus, now))
return consensus;
else
return NULL;
}
-/** Check if we're bootstrapping a consensus download. This means that we are
- * only using the authorities and fallback directory mirrors to download the
- * consensus flavour we'll use. */
-int
-networkstatus_consensus_is_bootstrapping(time_t now)
-{
- /* If we don't have a consensus, we must still be bootstrapping */
- return !networkstatus_get_reasonably_live_consensus(
- now,
- usable_consensus_flavor());
+/** Check if we need to download a consensus during tor's bootstrap phase.
+ * If we have no consensus, or our consensus is unusably old, return 1.
+ * As soon as we have received a consensus, return 0, even if we don't have
+ * enough certificates to validate it.
+ * If a fallback directory gives us a consensus we can never get certs for,
+ * check_consensus_waiting_for_certs() will wait 20 minutes before failing
+ * the cert downloads. After that, a new consensus will be fetched from a
+ * randomly chosen fallback. */
+MOCK_IMPL(int,
+networkstatus_consensus_is_bootstrapping,(time_t now))
+{
+ /* If we have a validated, reasonably live consensus, we're not
+ * bootstrapping a consensus at all. */
+ if (networkstatus_get_reasonably_live_consensus(
+ now,
+ usable_consensus_flavor())) {
+ return 0;
+ }
+
+ /* If we have a consensus, but we're waiting for certificates,
+ * we're not waiting for a consensus download while bootstrapping. */
+ if (consensus_is_waiting_for_certs()) {
+ return 0;
+ }
+
+ /* If we have no consensus, or our consensus is very old, we are
+ * bootstrapping, and we need to download a consensus. */
+ return 1;
}
/** Check if we can use multiple directories for a consensus download.
@@ -1300,8 +1496,8 @@ networkstatus_consensus_can_use_multiple_directories(
/** Check if we can use fallback directory mirrors for a consensus download.
* If we have fallbacks and don't want to fetch from the authorities,
* we can use them. */
-int
-networkstatus_consensus_can_use_extra_fallbacks(const or_options_t *options)
+MOCK_IMPL(int,
+networkstatus_consensus_can_use_extra_fallbacks,(const or_options_t *options))
{
/* The list length comparisons are a quick way to check if we have any
* non-authority fallback directories. If we ever have any authorities that
@@ -1315,61 +1511,39 @@ networkstatus_consensus_can_use_extra_fallbacks(const or_options_t *options)
> smartlist_len(router_get_trusted_dir_servers())));
}
-/* Check if there is more than 1 consensus connection retrieving the usable
- * consensus flavor. If so, return 1, if not, return 0.
- *
- * During normal operation, Tor only makes one consensus download
- * connection. But clients can make multiple simultaneous consensus
- * connections to improve bootstrap speed and reliability.
- *
- * If there is more than one connection, we must have connections left
- * over from bootstrapping. However, some of the connections may have
- * completed and been cleaned up, so it is not sufficient to check the
- * return value of this function to see if a client could make multiple
- * bootstrap connections. Use
- * networkstatus_consensus_can_use_multiple_directories()
- * and networkstatus_consensus_is_bootstrapping(). */
+/* Is there a consensus fetch for flavor <b>resource</b> that's far
+ * enough along to be attached to a circuit? */
int
-networkstatus_consensus_has_excess_connections(void)
-{
- const char *usable_resource = networkstatus_get_flavor_name(
- usable_consensus_flavor());
- const int consens_conn_usable_count =
- connection_dir_count_by_purpose_and_resource(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource);
- /* The maximum number of connections we want downloading a usable consensus
- * Always 1, whether bootstrapping or not. */
- const int max_expected_consens_conn_usable_count = 1;
-
- if (consens_conn_usable_count > max_expected_consens_conn_usable_count) {
- return 1;
- }
-
- return 0;
-}
-
-/* Is tor currently downloading a consensus of the usable flavor? */
-int
-networkstatus_consensus_is_downloading_usable_flavor(void)
-{
- const char *usable_resource = networkstatus_get_flavor_name(
- usable_consensus_flavor());
- const int consens_conn_usable_count =
- connection_dir_count_by_purpose_and_resource(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource);
-
- const int connect_consens_conn_usable_count =
- connection_dir_count_by_purpose_resource_and_state(
- DIR_PURPOSE_FETCH_CONSENSUS,
- usable_resource,
- DIR_CONN_STATE_CONNECTING);
- if (connect_consens_conn_usable_count < consens_conn_usable_count) {
- return 1;
- }
+networkstatus_consensus_is_already_downloading(const char *resource)
+{
+ int answer = 0;
+
+ /* First, get a list of all the dir conns that are fetching a consensus,
+ * fetching *this* consensus, and are in state "reading" (meaning they
+ * have already flushed their request onto the socks connection). */
+ smartlist_t *fetching_conns =
+ connection_dir_list_by_purpose_resource_and_state(
+ DIR_PURPOSE_FETCH_CONSENSUS, resource, DIR_CONN_STATE_CLIENT_READING);
+
+ /* Then, walk through each conn, to see if its linked socks connection
+ * is in an attached state. We have to check this separately, since with
+ * the optimistic data feature, fetches can send their request to the
+ * socks connection and go into state 'reading', even before they're
+ * attached to any circuit. */
+ SMARTLIST_FOREACH_BEGIN(fetching_conns, dir_connection_t *, dirconn) {
+ /* Do any of these other dir conns have a linked socks conn that is
+ * attached to a circuit already? */
+ connection_t *base = TO_CONN(dirconn);
+ if (base->linked_conn &&
+ base->linked_conn->type == CONN_TYPE_AP &&
+ !AP_CONN_STATE_IS_UNATTACHED(base->linked_conn->state)) {
+ answer = 1;
+ break; /* stop looping, because we know the answer will be yes */
+ }
+ } SMARTLIST_FOREACH_END(dirconn);
+ smartlist_free(fetching_conns);
- return 0;
+ return answer;
}
/** Given two router status entries for the same router identity, return 1 if
@@ -1394,8 +1568,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
@@ -1494,6 +1669,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.
@@ -1505,6 +1740,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.
*
@@ -1514,7 +1753,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;
@@ -1532,6 +1772,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. */
@@ -1547,6 +1788,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) {
@@ -1559,9 +1810,9 @@ networkstatus_set_current_consensus(const char *consensus,
}
if (flav != usable_consensus_flavor() &&
- !directory_caches_dir_info(options)) {
- /* This consensus is totally boring to us: we won't use it, and we won't
- * serve it. Drop it. */
+ !we_want_to_fetch_flavor(options, flav)) {
+ /* This consensus is totally boring to us: we won't use it, we didn't want
+ * it, and we won't serve it. Drop it. */
goto done;
}
@@ -1636,7 +1887,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;
@@ -1672,18 +1923,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);
- if (flav == usable_consensus_flavor()) {
- notify_control_networkstatus_changed(current_consensus, c);
+ const int is_usable_flavor = flav == usable_consensus_flavor();
+
+ if (is_usable_flavor) {
+ 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;
@@ -1719,27 +1977,19 @@ networkstatus_set_current_consensus(const char *consensus,
}
}
- /* Reset the failure count only if this consensus is actually valid. */
- if (c->valid_after <= now && now <= c->valid_until) {
- download_status_reset(&consensus_dl_status[flav]);
- } else {
- if (!from_cache)
- download_status_failed(&consensus_dl_status[flav], 0);
- }
+ if (is_usable_flavor) {
+ nodelist_set_consensus(c);
- if (flav == usable_consensus_flavor()) {
/* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/
update_consensus_networkstatus_fetch_time(now);
- nodelist_set_consensus(current_consensus);
-
dirvote_recalculate_timing(options, now);
routerstatus_list_update_named_server_map();
/* Update ewma and adjust policy if needed; first cache the old value */
old_ewma_enabled = cell_ewma_enabled();
/* Change the cell EWMA settings */
- 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);
@@ -1748,19 +1998,32 @@ 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);
+ channelpadding_new_consensus_params(c);
}
- if (directory_caches_dir_info(options)) {
+ /* Reset the failure count only if this consensus is actually valid. */
+ if (c->valid_after <= now && now <= c->valid_until) {
+ download_status_reset(&consensus_dl_status[flav]);
+ } else {
+ if (!from_cache)
+ download_status_failed(&consensus_dl_status[flav], 0);
+ }
+
+ if (we_want_to_fetch_flavor(options, flav)) {
dirserv_set_cached_consensus_networkstatus(consensus,
flavor,
&c->digests,
+ c->digest_sha3_as_signed,
c->valid_after);
+ if (server_mode(get_options())) {
+ consdiffmgr_add_consensus(consensus, c);
+ }
}
if (!from_cache) {
@@ -1797,9 +2060,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) {
@@ -1811,7 +2079,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);
}
}
@@ -1889,15 +2158,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));
@@ -1917,7 +2187,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;
@@ -1986,7 +2256,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
@@ -2044,15 +2314,24 @@ networkstatus_dump_bridge_status_to_file(time_t now)
char *fname = NULL;
char *thresholds = NULL;
char *published_thresholds_and_status = NULL;
- routerlist_t *rl = router_get_routerlist();
char published[ISO_TIME_LEN+1];
+ const routerinfo_t *me = router_get_my_routerinfo();
+ char fingerprint[FINGERPRINT_LEN+1];
+ char *fingerprint_line = NULL;
+ if (me && crypto_pk_get_fingerprint(me->identity_pkey,
+ fingerprint, 0) >= 0) {
+ tor_asprintf(&fingerprint_line, "fingerprint %s\n", fingerprint);
+ } else {
+ log_warn(LD_BUG, "Error computing fingerprint for bridge status.");
+ }
format_iso_time(published, now);
- dirserv_compute_bridge_flag_thresholds(rl->routers);
+ dirserv_compute_bridge_flag_thresholds();
thresholds = dirserv_get_flag_thresholds_line();
tor_asprintf(&published_thresholds_and_status,
- "published %s\nflag-thresholds %s\n%s",
- published, thresholds, status);
+ "published %s\nflag-thresholds %s\n%s%s",
+ published, thresholds, fingerprint_line ? fingerprint_line : "",
+ status);
tor_asprintf(&fname, "%s"PATH_SEPARATOR"networkstatus-bridges",
options->DataDirectory);
write_str_to_file(fname,published_thresholds_and_status,0);
@@ -2060,6 +2339,7 @@ networkstatus_dump_bridge_status_to_file(time_t now)
tor_free(published_thresholds_and_status);
tor_free(fname);
tor_free(status);
+ tor_free(fingerprint_line);
}
/* DOCDOC get_net_param_from_list */
@@ -2121,6 +2401,25 @@ networkstatus_get_param(const networkstatus_t *ns, const char *param_name,
}
/**
+ * As networkstatus_get_param(), but check torrc_value before checking the
+ * consensus. If torrc_value is in-range, then return it instead of the
+ * value from the consensus.
+ */
+int32_t
+networkstatus_get_overridable_param(const networkstatus_t *ns,
+ int32_t torrc_value,
+ const char *param_name,
+ int32_t default_val,
+ int32_t min_val, int32_t max_val)
+{
+ if (torrc_value >= min_val && torrc_value <= max_val)
+ return torrc_value;
+ else
+ return networkstatus_get_param(
+ ns, param_name, default_val, min_val, max_val);
+}
+
+/**
* Retrieve the consensus parameter that governs the
* fixed-point precision of our network balancing 'bandwidth-weights'
* (which are themselves integer consensus values). We divide them
@@ -2196,23 +2495,23 @@ networkstatus_parse_flavor_name(const char *flavname)
* running, or otherwise not a descriptor that we would make any
* use of even if we had it. Else return 1. */
int
-client_would_use_router(const routerstatus_t *rs, time_t now,
- const or_options_t *options)
+client_would_use_router(const routerstatus_t *rs, time_t now)
{
- if (!rs->is_flagged_running && !options->FetchUselessDescriptors) {
+ if (!rs->is_flagged_running) {
/* If we had this router descriptor, we wouldn't even bother using it.
- * But, if we want to have a complete list, fetch it anyway. */
- return 0;
- }
- if (rs->published_on + options->TestingEstimatedDescriptorPropagationTime
- > now) {
- /* Most caches probably don't have this descriptor yet. */
+ * (Fetching and storing depends on by we_want_to_fetch_flavor().) */
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;
}
@@ -2228,14 +2527,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));
@@ -2250,7 +2549,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;
}
@@ -2296,6 +2595,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 f2f8af5c6b..f9320747d2 100644
--- a/src/or/networkstatus.h
+++ b/src/or/networkstatus.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -16,6 +16,7 @@
void networkstatus_reset_warnings(void);
void networkstatus_reset_download_failures(void);
+char *networkstatus_read_cached_consensus(const char *flavorname);
int router_reload_consensus_networkstatus(void);
void routerstatus_free(routerstatus_t *rs);
void networkstatus_vote_free(networkstatus_t *ns);
@@ -38,6 +39,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));
@@ -55,6 +67,8 @@ const routerstatus_t *router_get_consensus_status_by_nickname(
int warn_if_unnamed);
const char *networkstatus_get_router_digest_by_nickname(const char *nickname);
int networkstatus_nickname_is_unnamed(const char *nickname);
+int we_want_to_fetch_flavor(const or_options_t *options, int flavor);
+int we_want_to_fetch_unknown_auth_certs(const or_options_t *options);
void networkstatus_consensus_download_failed(int status_code,
const char *flavname);
void update_consensus_networkstatus_fetch_time(time_t now);
@@ -62,21 +76,24 @@ int should_delay_dir_fetches(const or_options_t *options,const char **msg_out);
void update_networkstatus_downloads(time_t now);
void update_certificate_downloads(time_t now);
int consensus_is_waiting_for_certs(void);
-int client_would_use_router(const routerstatus_t *rs, time_t now,
- const or_options_t *options);
-networkstatus_t *networkstatus_get_latest_consensus(void);
+int client_would_use_router(const routerstatus_t *rs, time_t now);
+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));
+int networkstatus_is_live(const networkstatus_t *ns, time_t now);
+int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus,
+ time_t now);
+int networkstatus_valid_until_is_reasonably_live(time_t valid_until,
+ time_t now);
networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now,
int flavor);
-int networkstatus_consensus_is_bootstrapping(time_t now);
+MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now));
int networkstatus_consensus_can_use_multiple_directories(
const or_options_t *options);
-int networkstatus_consensus_can_use_extra_fallbacks(
- const or_options_t *options);
-int networkstatus_consensus_has_excess_connections(void);
-int networkstatus_consensus_is_downloading_usable_flavor(void);
+MOCK_DECL(int, networkstatus_consensus_can_use_extra_fallbacks,(
+ const or_options_t *options));
+int networkstatus_consensus_is_already_downloading(const char *resource);
#define NSSET_FROM_CACHE 1
#define NSSET_WAS_WAITING_FOR_CERTS 2
@@ -85,8 +102,9 @@ int networkstatus_consensus_is_downloading_usable_flavor(void);
#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);
@@ -100,6 +118,11 @@ int32_t networkstatus_get_param(const networkstatus_t *ns,
const char *param_name,
int32_t default_val, int32_t min_val,
int32_t max_val);
+int32_t networkstatus_get_overridable_param(const networkstatus_t *ns,
+ int32_t torrc_value,
+ const char *param_name,
+ int32_t default_val,
+ int32_t min_val, int32_t max_val);
int getinfo_helper_networkstatus(control_connection_t *conn,
const char *question, char **answer,
const char **errmsg);
@@ -112,12 +135,15 @@ document_signature_t *document_signature_dup(const document_signature_t *sig);
void networkstatus_free_all(void);
int networkstatus_get_weight_scale_param(networkstatus_t *ns);
+void vote_routerstatus_free(vote_routerstatus_t *rs);
+
#ifdef NETWORKSTATUS_PRIVATE
-STATIC void vote_routerstatus_free(vote_routerstatus_t *rs);
#ifdef TOR_UNIT_TESTS
STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
const char *flavor);
-#endif // TOR_UNIT_TESTS
+extern networkstatus_t *current_ns_consensus;
+extern networkstatus_t *current_md_consensus;
+#endif
#endif
#endif
diff --git a/src/or/nodelist.c b/src/or/nodelist.c
index abbb82dfdd..0fcaea626d 100644
--- a/src/or/nodelist.c
+++ b/src/or/nodelist.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -10,6 +10,32 @@
* \brief Structures and functions for tracking what we know about the routers
* on the Tor network, and correlating information from networkstatus,
* routerinfo, and microdescs.
+ *
+ * The key structure here is node_t: that's the canonical way to refer
+ * to a Tor relay that we might want to build a circuit through. Every
+ * node_t has either a routerinfo_t, or a routerstatus_t from the current
+ * networkstatus consensus. If it has a routerstatus_t, it will also
+ * need to have a microdesc_t before you can use it for circuits.
+ *
+ * The nodelist_t is a global singleton that maps identities to node_t
+ * objects. Access them with the node_get_*() functions. The nodelist_t
+ * is maintained by calls throughout the codebase
+ *
+ * Generally, other code should not have to reach inside a node_t to
+ * see what information it has. Instead, you should call one of the
+ * many accessor functions that works on a generic node_t. If there
+ * isn't one that does what you need, it's better to make such a function,
+ * and then use it.
+ *
+ * For historical reasons, some of the functions that select a node_t
+ * from the list of all usable node_t objects are in the routerlist.c
+ * module, since they originally selected a routerinfo_t. (TODO: They
+ * should move!)
+ *
+ * (TODO: Perhaps someday we should abstract the remaining ways of
+ * talking about a relay to also be node_t instances. Those would be
+ * routerstatus_t as used for directory requests, and dir_server_t as
+ * used for authorities and fallback directories.)
*/
#include "or.h"
@@ -17,16 +43,21 @@
#include "config.h"
#include "control.h"
#include "dirserv.h"
+#include "entrynodes.h"
#include "geoip.h"
+#include "hs_common.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
+#include "protover.h"
#include "rendservice.h"
#include "router.h"
#include "routerlist.h"
+#include "routerparse.h"
#include "routerset.h"
+#include "torcert.h"
#include <string.h>
@@ -45,7 +76,6 @@ static void count_usable_descriptors(int *num_present,
int *num_usable,
smartlist_t *descs_out,
const networkstatus_t *consensus,
- const or_options_t *options,
time_t now,
routerset_t *in_set,
usable_descriptor_t exit_only);
@@ -77,7 +107,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_)
@@ -136,12 +166,78 @@ node_get_or_create(const char *identity_digest)
smartlist_add(the_nodelist->nodes, node);
node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1;
+ node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t));
node->country = -1;
return node;
}
+/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index
+ * for the node, both current and next if possible. This can only fails if the
+ * node_t ed25519 identity key can't be found which would be a bug. */
+static void
+node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
+{
+ time_t now = approx_time();
+ const ed25519_public_key_t *node_identity_pk;
+ uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL;
+ uint64_t next_time_period_num, current_time_period_num;
+
+ tor_assert(node);
+ tor_assert(ns);
+
+ if (!networkstatus_is_live(ns, now)) {
+ log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus.");
+ goto done;
+ }
+
+ node_identity_pk = node_get_ed25519_id(node);
+ if (node_identity_pk == NULL) {
+ log_debug(LD_GENERAL, "ed25519 identity public key not found when "
+ "trying to build the hsdir indexes for node %s",
+ node_describe(node));
+ goto done;
+ }
+
+ /* Get the current and next time period number, we might use them both. */
+ current_time_period_num = hs_get_time_period_num(now);
+ next_time_period_num = hs_get_next_time_period_num(now);
+
+ if (hs_overlap_mode_is_active(ns, now)) {
+ /* We are in overlap mode, this means that our consensus has just cycled
+ * from current SRV to previous SRV so for the _next_ upcoming time
+ * period, we have to use the current SRV and use the previous SRV for the
+ * current time period. If the current or previous SRV can't be found, the
+ * disaster one is returned. */
+ next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns);
+ /* The following can be confusing so again, in overlap mode, we use our
+ * previous SRV for our _current_ hsdir index. */
+ current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns);
+ } else {
+ /* If NOT in overlap mode, we only need to compute the current hsdir index
+ * for the ongoing time period and thus the current SRV. If it can't be
+ * found, the disaster one is returned. */
+ current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns);
+ }
+
+ /* Build the current hsdir index. */
+ hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv,
+ current_time_period_num, node->hsdir_index->current);
+ if (next_hsdir_index_srv) {
+ /* Build the next hsdir index if we have a next SRV that we can use. */
+ hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv,
+ next_time_period_num, node->hsdir_index->next);
+ } else {
+ memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next));
+ }
+
+ done:
+ tor_free(current_hsdir_index_srv);
+ tor_free(next_hsdir_index_srv);
+ return;
+}
+
/** Called when a node's address changes. */
static void
node_addrs_changed(node_t *node)
@@ -188,6 +284,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out)
dirserv_set_node_flags_from_authoritative_status(node, status);
}
+ /* Setting the HSDir index requires the ed25519 identity key which can
+ * only be found either in the ri or md. This is why this is called here.
+ * Only nodes supporting HSDir=2 protocol version needs this index. */
+ if (node->rs && node->rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node,
+ networkstatus_get_latest_consensus());
+ }
+
return node;
}
@@ -218,6 +322,12 @@ nodelist_add_microdesc(microdesc_t *md)
node->md->held_by_nodes--;
node->md = md;
md->held_by_nodes++;
+ /* Setting the HSDir index requires the ed25519 identity key which can
+ * only be found either in the ri or md. This is why this is called here.
+ * Only nodes supporting HSDir=2 protocol version needs this index. */
+ if (rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node, ns);
+ }
}
return node;
}
@@ -255,6 +365,9 @@ nodelist_set_consensus(networkstatus_t *ns)
}
}
+ if (rs->supports_v3_hsdir) {
+ node_set_hsdir_index(node, ns);
+ }
node_set_country(node);
/* If we're not an authdir, believe others. */
@@ -382,6 +495,7 @@ node_free(node_t *node)
if (node->md)
node->md->held_by_nodes--;
tor_assert(node->nodelist_idx == -1);
+ tor_free(node->hsdir_index);
tor_free(node);
}
@@ -542,13 +656,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;
@@ -594,17 +710,15 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
"but none is listed as Named in the directory consensus. "
"Choosing one arbitrarily.", nickname);
}
- } else if (smartlist_len(matches)>1 && warn_if_unnamed) {
+ } else if (smartlist_len(matches)==1 && warn_if_unnamed) {
char fp[HEX_DIGEST_LEN+1];
node_t *node = smartlist_get(matches, 0);
- if (node->name_lookup_warned) {
+ if (! node->name_lookup_warned) {
base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN);
log_warn(LD_CONFIG,
- "You specified a server \"%s\" by name, but the directory "
- "authorities do not have any key registered for this "
- "nickname -- so it could be used by any server, not just "
- "the one you meant. "
- "To make sure you get the same server in the future, refer "
+ "You specified a relay \"%s\" by name, but nicknames can be "
+ "used by any relay, not just the one you meant. "
+ "To make sure you get the same relay in the future, refer "
"to it by key, as \"$%s\".", nickname, fp);
node->name_lookup_warned = 1;
}
@@ -618,6 +732,124 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
}
}
+/** Return the Ed25519 identity key for the provided node, or NULL if it
+ * doesn't have one. */
+const ed25519_public_key_t *
+node_get_ed25519_id(const node_t *node)
+{
+ if (node->ri) {
+ if (node->ri->cache_info.signing_key_cert) {
+ const ed25519_public_key_t *pk =
+ &node->ri->cache_info.signing_key_cert->signing_key;
+ if (BUG(ed25519_public_key_is_zero(pk)))
+ goto try_the_md;
+ return pk;
+ }
+ }
+ try_the_md:
+ if (node->md) {
+ if (node->md->ed25519_identity_pkey) {
+ return node->md->ed25519_identity_pkey;
+ }
+ }
+ return NULL;
+}
+
+/** Return true iff this node's Ed25519 identity matches <b>id</b>.
+ * (An absent Ed25519 identity matches NULL or zero.) */
+int
+node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id)
+{
+ const ed25519_public_key_t *node_id = node_get_ed25519_id(node);
+ if (node_id == NULL || ed25519_public_key_is_zero(node_id)) {
+ return id == NULL || ed25519_public_key_is_zero(id);
+ } else {
+ return id && ed25519_pubkey_eq(node_id, id);
+ }
+}
+
+/** Return true iff <b>node</b> supports authenticating itself
+ * by ed25519 ID during the link handshake in a way that we can understand
+ * when we probe it. */
+int
+node_supports_ed25519_link_authentication(const node_t *node)
+{
+ /* XXXX Oh hm. What if some day in the future there are link handshake
+ * versions that aren't 3 but which are ed25519 */
+ if (! node_get_ed25519_id(node))
+ return 0;
+ if (node->ri) {
+ const char *protos = node->ri->protocol_list;
+ if (protos == NULL)
+ return 0;
+ return protocol_list_supports_protocol(protos, PRT_LINKAUTH, 3);
+ }
+ if (node->rs) {
+ return node->rs->supports_ed25519_link_handshake;
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
+/** Return true iff <b>node</b> supports the hidden service directory version
+ * 3 protocol (proposal 224). */
+int
+node_supports_v3_hsdir(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_v3_hsdir;
+ }
+ if (node->ri) {
+ if (node->ri->protocol_list == NULL) {
+ return 0;
+ }
+ /* Bug #22447 forces us to filter on tor version:
+ * If platform is a Tor version, and older than 0.3.0.8, return False.
+ * Else, obey the protocol list. */
+ if (node->ri->platform) {
+ if (!strcmpstart(node->ri->platform, "Tor ") &&
+ !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) {
+ return 0;
+ }
+ }
+ return protocol_list_supports_protocol(node->ri->protocol_list,
+ PRT_HSDIR, PROTOVER_HSDIR_V3);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
+/** Return true iff <b>node</b> supports ed25519 authentication as an hidden
+ * service introduction point.*/
+int
+node_supports_ed25519_hs_intro(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_ed25519_hs_intro;
+ }
+ if (node->ri) {
+ if (node->ri->protocol_list == NULL) {
+ return 0;
+ }
+ return protocol_list_supports_protocol(node->ri->protocol_list,
+ PRT_HSINTRO, PROTOVER_HS_INTRO_V3);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
+/** Return the RSA ID key's SHA1 digest for the provided node. */
+const uint8_t *
+node_get_rsa_id_digest(const node_t *node)
+{
+ tor_assert(node);
+ return (const uint8_t*)node->identity;
+}
+
/** Return the nickname of <b>node</b>, or NULL if we can't find one. */
const char *
node_get_nickname(const node_t *node)
@@ -913,16 +1145,6 @@ node_get_platform(const node_t *node)
return NULL;
}
-/** Return <b>node</b>'s time of publication, or 0 if we don't have one. */
-time_t
-node_get_published_on(const node_t *node)
-{
- if (node->ri)
- return node->ri->cache_info.published_on;
- else
- return 0;
-}
-
/** Return true iff <b>node</b> is one representing this router. */
int
node_is_me(const node_t *node)
@@ -1029,6 +1251,9 @@ node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
+ /* 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 */
@@ -1059,9 +1284,11 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
- /* Prefer routerstatus over microdesc for consistency with the
- * fascist_firewall_* functions. Also check if the address or port are valid,
- * and try another alternative if they are not. */
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address.
+ * Prefer rs over md for consistency with the fascist_firewall_* functions.
+ * Check if the address or port are valid, and try another alternative
+ * if they are not. */
if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr,
node->ri->ipv6_orport, 0)) {
@@ -1121,6 +1348,9 @@ node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address. */
+
RETURN_IPV4_AP(node->ri, dir_port, ap_out);
RETURN_IPV4_AP(node->rs, dir_port, ap_out);
/* Microdescriptors only have an IPv6 address */
@@ -1153,8 +1383,11 @@ node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
- /* Check if the address or port are valid, and try another alternative if
- * they are not. Note that microdescriptors have no dir_port. */
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address.
+ * Prefer rs over md for consistency with the fascist_firewall_* functions.
+ * Check if the address or port are valid, and try another alternative
+ * if they are not. */
/* Assume IPv4 and IPv6 dirports are the same */
if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr,
@@ -1171,14 +1404,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;
}
@@ -1210,7 +1467,7 @@ nodelist_refresh_countries(void)
/** Return true iff router1 and router2 have similar enough network addresses
* that we should treat them as being in the same family */
-static inline int
+int
addrs_in_same_network_family(const tor_addr_t *a1,
const tor_addr_t *a2)
{
@@ -1517,8 +1774,8 @@ router_have_minimum_dir_info(void)
* this can cause router_have_consensus_path() to be set to
* CONSENSUS_PATH_EXIT, even if there are no nodes with accept exit policies.
*/
-consensus_path_type_t
-router_have_consensus_path(void)
+MOCK_IMPL(consensus_path_type_t,
+router_have_consensus_path, (void))
{
return have_consensus_path;
}
@@ -1557,7 +1814,7 @@ static void
count_usable_descriptors(int *num_present, int *num_usable,
smartlist_t *descs_out,
const networkstatus_t *consensus,
- const or_options_t *options, time_t now,
+ time_t now,
routerset_t *in_set,
usable_descriptor_t exit_only)
{
@@ -1574,7 +1831,7 @@ count_usable_descriptors(int *num_present, int *num_usable,
continue;
if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1))
continue;
- if (client_would_use_router(rs, now, options)) {
+ if (client_would_use_router(rs, now)) {
const char * const digest = rs->descriptor_digest;
int present;
++*num_usable; /* the consensus says we want it. */
@@ -1607,9 +1864,9 @@ count_usable_descriptors(int *num_present, int *num_usable,
* If **<b>status_out</b> is present, allocate a new string and print the
* available percentages of guard, middle, and exit nodes to it, noting
* whether there are exits in the consensus.
- * If there are no guards in the consensus,
- * we treat the exit fraction as 100%.
- */
+ * If there are no exits in the consensus, we treat the exit fraction as 100%,
+ * but set router_have_consensus_path() so that we can only build internal
+ * paths. */
static double
compute_frac_paths_available(const networkstatus_t *consensus,
const or_options_t *options, time_t now,
@@ -1628,10 +1885,10 @@ compute_frac_paths_available(const networkstatus_t *consensus,
const int authdir = authdir_mode_v3(options);
count_usable_descriptors(num_present_out, num_usable_out,
- mid, consensus, options, now, NULL,
+ mid, consensus, now, NULL,
USABLE_DESCRIPTOR_ALL);
if (options->EntryNodes) {
- count_usable_descriptors(&np, &nu, guards, consensus, options, now,
+ count_usable_descriptors(&np, &nu, guards, consensus, now,
options->EntryNodes, USABLE_DESCRIPTOR_ALL);
} else {
SMARTLIST_FOREACH(mid, const node_t *, node, {
@@ -1652,7 +1909,7 @@ compute_frac_paths_available(const networkstatus_t *consensus,
* an unavoidable feature of forcing authorities to declare
* certain nodes as exits.
*/
- count_usable_descriptors(&np, &nu, exits, consensus, options, now,
+ count_usable_descriptors(&np, &nu, exits, consensus, now,
NULL, USABLE_DESCRIPTOR_EXIT_ONLY);
log_debug(LD_NET,
"%s: %d present, %d usable",
@@ -1701,7 +1958,7 @@ compute_frac_paths_available(const networkstatus_t *consensus,
smartlist_t *myexits_unflagged = smartlist_new();
/* All nodes with exit flag in ExitNodes option */
- count_usable_descriptors(&np, &nu, myexits, consensus, options, now,
+ count_usable_descriptors(&np, &nu, myexits, consensus, now,
options->ExitNodes, USABLE_DESCRIPTOR_EXIT_ONLY);
log_debug(LD_NET,
"%s: %d present, %d usable",
@@ -1712,7 +1969,7 @@ compute_frac_paths_available(const networkstatus_t *consensus,
/* Now compute the nodes in the ExitNodes option where which we don't know
* what their exit policy is, or we know it permits something. */
count_usable_descriptors(&np, &nu, myexits_unflagged,
- consensus, options, now,
+ consensus, now,
options->ExitNodes, USABLE_DESCRIPTOR_ALL);
log_debug(LD_NET,
"%s: %d present, %d usable",
@@ -1859,6 +2116,13 @@ update_router_have_minimum_dir_info(void)
using_md = consensus->flavor == FLAV_MICRODESC;
+ if (! entry_guards_have_enough_dir_info_to_build_circuits()) {
+ strlcpy(dir_info_status, "We're missing descriptors for some of our "
+ "primary entry guards", sizeof(dir_info_status));
+ res = 0;
+ goto done;
+ }
+
/* Check fraction of available paths */
{
char *status = NULL;
diff --git a/src/or/nodelist.h b/src/or/nodelist.h
index 71a91e107f..405b79d820 100644
--- a/src/or/nodelist.h
+++ b/src/or/nodelist.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -53,8 +53,14 @@ const char *node_get_platform(const node_t *node);
uint32_t node_get_prim_addr_ipv4h(const node_t *node);
void node_get_address_string(const node_t *node, char *cp, size_t len);
long node_get_declared_uptime(const node_t *node);
-time_t node_get_published_on(const node_t *node);
const smartlist_t *node_get_declared_family(const node_t *node);
+const ed25519_public_key_t *node_get_ed25519_id(const node_t *node);
+int node_ed25519_id_matches(const node_t *node,
+ const ed25519_public_key_t *id);
+int node_supports_ed25519_link_authentication(const node_t *node);
+int node_supports_v3_hsdir(const node_t *node);
+int node_supports_ed25519_hs_intro(const node_t *node);
+const uint8_t *node_get_rsa_id_digest(const node_t *node);
int node_has_ipv6_addr(const node_t *node);
int node_has_ipv6_orport(const node_t *node);
@@ -89,6 +95,8 @@ int node_is_unreliable(const node_t *router, int need_uptime,
int router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port,
int need_uptime);
void router_set_status(const char *digest, int up);
+int addrs_in_same_network_family(const tor_addr_t *a1,
+ const tor_addr_t *a2);
/** router_have_minimum_dir_info tests to see if we have enough
* descriptor information to create circuits.
@@ -118,7 +126,8 @@ typedef enum {
* create exit and internal paths, circuits, streams, ... */
CONSENSUS_PATH_EXIT = 1
} consensus_path_type_t;
-consensus_path_type_t router_have_consensus_path(void);
+
+MOCK_DECL(consensus_path_type_t, router_have_consensus_path, (void));
void router_dir_info_changed(void);
const char *get_dir_info_status_string(void);
diff --git a/src/or/ntmain.c b/src/or/ntmain.c
index ded0e0d307..7a85ba9708 100644
--- a/src/or/ntmain.c
+++ b/src/or/ntmain.c
@@ -1,12 +1,20 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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();
}
@@ -324,9 +329,10 @@ nt_service_main(void)
case CMD_VERIFY_CONFIG:
case CMD_DUMP_CONFIG:
case CMD_KEYGEN:
+ case CMD_KEY_EXPIRATION:
log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, "
- "--hash-password, --keygen, --dump-config, or --verify-config) "
- "in NT service.");
+ "--hash-password, --keygen, --dump-config, --verify-config, "
+ "or --key-expiration) in NT service.");
break;
case CMD_RUN_UNITTESTS:
default:
diff --git a/src/or/ntmain.h b/src/or/ntmain.h
index 31bf38c62c..4b771b1828 100644
--- a/src/or/ntmain.h
+++ b/src/or/ntmain.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/onion.c b/src/or/onion.c
index 4bed7ae226..a98b97cb1d 100644
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@ -1,16 +1,69 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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"
@@ -23,6 +76,9 @@
#include "rephist.h"
#include "router.h"
+// trunnel
+#include "ed25519_cert.h"
+
/** Type for a linked list of circuits that are waiting for a free CPU worker
* to process a waiting onion handshake. */
typedef struct onion_queue_t {
@@ -38,9 +94,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 +107,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 +186,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 +238,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 +366,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 +455,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 +496,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 +507,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 +581,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;
@@ -804,13 +876,114 @@ check_extend_cell(const extend_cell_t *cell)
return check_create_cell(&cell->create_cell, 1);
}
-/** Protocol constants for specifier types in EXTEND2
- * @{
- */
-#define SPECTYPE_IPV4 0
-#define SPECTYPE_IPV6 1
-#define SPECTYPE_LEGACY_ID 2
-/** @} */
+static int
+extend_cell_from_extend1_cell_body(extend_cell_t *cell_out,
+ const extend1_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ memset(cell_out, 0, sizeof(*cell_out));
+ tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
+ tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
+
+ cell_out->cell_type = RELAY_COMMAND_EXTEND;
+ tor_addr_from_ipv4h(&cell_out->orport_ipv4.addr, cell->ipv4addr);
+ cell_out->orport_ipv4.port = cell->port;
+ if (tor_memeq(cell->onionskin, NTOR_CREATE_MAGIC, 16)) {
+ cell_out->create_cell.cell_type = CELL_CREATE2;
+ cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_NTOR;
+ cell_out->create_cell.handshake_len = NTOR_ONIONSKIN_LEN;
+ memcpy(cell_out->create_cell.onionskin, cell->onionskin + 16,
+ NTOR_ONIONSKIN_LEN);
+ } else {
+ cell_out->create_cell.cell_type = CELL_CREATE;
+ cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_TAP;
+ cell_out->create_cell.handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN;
+ memcpy(cell_out->create_cell.onionskin, cell->onionskin,
+ TAP_ONIONSKIN_CHALLENGE_LEN);
+ }
+ memcpy(cell_out->node_id, cell->identity, DIGEST_LEN);
+ return 0;
+}
+
+static int
+create_cell_from_create2_cell_body(create_cell_t *cell_out,
+ const create2_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ memset(cell_out, 0, sizeof(create_cell_t));
+ if (BUG(cell->handshake_len > sizeof(cell_out->onionskin))) {
+ /* This should be impossible because there just isn't enough room in the
+ * input cell to make the handshake_len this large and provide a
+ * handshake_data to match. */
+ return -1;
+ }
+
+ cell_out->cell_type = CELL_CREATE2;
+ cell_out->handshake_type = cell->handshake_type;
+ cell_out->handshake_len = cell->handshake_len;
+ memcpy(cell_out->onionskin,
+ create2_cell_body_getconstarray_handshake_data(cell),
+ cell->handshake_len);
+ return 0;
+}
+
+static int
+extend_cell_from_extend2_cell_body(extend_cell_t *cell_out,
+ const extend2_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ int found_ipv4 = 0, found_ipv6 = 0, found_rsa_id = 0, found_ed_id = 0;
+ memset(cell_out, 0, sizeof(*cell_out));
+ tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
+ tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
+ cell_out->cell_type = RELAY_COMMAND_EXTEND2;
+
+ unsigned i;
+ for (i = 0; i < cell->n_spec; ++i) {
+ const link_specifier_t *ls = extend2_cell_body_getconst_ls(cell, i);
+ switch (ls->ls_type) {
+ case LS_IPV4:
+ if (found_ipv4)
+ continue;
+ found_ipv4 = 1;
+ tor_addr_from_ipv4h(&cell_out->orport_ipv4.addr, ls->un_ipv4_addr);
+ cell_out->orport_ipv4.port = ls->un_ipv4_port;
+ break;
+ case LS_IPV6:
+ if (found_ipv6)
+ continue;
+ found_ipv6 = 1;
+ tor_addr_from_ipv6_bytes(&cell_out->orport_ipv6.addr,
+ (const char *)ls->un_ipv6_addr);
+ cell_out->orport_ipv6.port = ls->un_ipv6_port;
+ break;
+ case LS_LEGACY_ID:
+ if (found_rsa_id)
+ return -1;
+ found_rsa_id = 1;
+ memcpy(cell_out->node_id, ls->un_legacy_id, 20);
+ break;
+ case LS_ED25519_ID:
+ if (found_ed_id)
+ return -1;
+ found_ed_id = 1;
+ memcpy(cell_out->ed_pubkey.pubkey, ls->un_ed25519_id, 32);
+ break;
+ default:
+ /* Ignore this, whatever it is. */
+ break;
+ }
+ }
+
+ if (!found_rsa_id || !found_ipv4) /* These are mandatory */
+ return -1;
+
+ return create_cell_from_create2_cell_body(&cell_out->create_cell,
+ cell->create2);
+}
/** Parse an EXTEND or EXTEND2 cell (according to <b>command</b>) from the
* <b>payload_length</b> bytes of <b>payload</b> into <b>cell_out</b>. Return
@@ -819,101 +992,44 @@ int
extend_cell_parse(extend_cell_t *cell_out, const uint8_t command,
const uint8_t *payload, size_t payload_length)
{
- const uint8_t *eop;
- memset(cell_out, 0, sizeof(*cell_out));
+ tor_assert(cell_out);
+ tor_assert(payload);
+
if (payload_length > RELAY_PAYLOAD_SIZE)
return -1;
- eop = payload + payload_length;
switch (command) {
case RELAY_COMMAND_EXTEND:
{
- if (payload_length != 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN)
+ extend1_cell_body_t *cell = NULL;
+ if (extend1_cell_body_parse(&cell, payload, payload_length)<0 ||
+ cell == NULL) {
+ if (cell)
+ extend1_cell_body_free(cell);
return -1;
-
- cell_out->cell_type = RELAY_COMMAND_EXTEND;
- tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr, get_uint32(payload));
- cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4));
- tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
- if (tor_memeq(payload + 6, NTOR_CREATE_MAGIC, 16)) {
- cell_out->create_cell.cell_type = CELL_CREATE2;
- cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_NTOR;
- cell_out->create_cell.handshake_len = NTOR_ONIONSKIN_LEN;
- memcpy(cell_out->create_cell.onionskin, payload + 22,
- NTOR_ONIONSKIN_LEN);
- } else {
- cell_out->create_cell.cell_type = CELL_CREATE;
- cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_TAP;
- cell_out->create_cell.handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN;
- memcpy(cell_out->create_cell.onionskin, payload + 6,
- TAP_ONIONSKIN_CHALLENGE_LEN);
}
- memcpy(cell_out->node_id, payload + 6 + TAP_ONIONSKIN_CHALLENGE_LEN,
- DIGEST_LEN);
- break;
+ int r = extend_cell_from_extend1_cell_body(cell_out, cell);
+ extend1_cell_body_free(cell);
+ if (r < 0)
+ return r;
}
+ break;
case RELAY_COMMAND_EXTEND2:
{
- uint8_t n_specs, spectype, speclen;
- int i;
- int found_ipv4 = 0, found_ipv6 = 0, found_id = 0;
- tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
- tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
-
- if (payload_length == 0)
+ extend2_cell_body_t *cell = NULL;
+ if (extend2_cell_body_parse(&cell, payload, payload_length) < 0 ||
+ cell == NULL) {
+ if (cell)
+ extend2_cell_body_free(cell);
return -1;
-
- cell_out->cell_type = RELAY_COMMAND_EXTEND2;
- n_specs = *payload++;
- /* Parse the specifiers. We'll only take the first IPv4 and first IPv6
- * address, and the node ID, and ignore everything else */
- for (i = 0; i < n_specs; ++i) {
- if (eop - payload < 2)
- return -1;
- spectype = payload[0];
- speclen = payload[1];
- payload += 2;
- if (eop - payload < speclen)
- return -1;
- switch (spectype) {
- case SPECTYPE_IPV4:
- if (speclen != 6)
- return -1;
- if (!found_ipv4) {
- tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr,
- get_uint32(payload));
- cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4));
- found_ipv4 = 1;
- }
- break;
- case SPECTYPE_IPV6:
- if (speclen != 18)
- return -1;
- if (!found_ipv6) {
- tor_addr_from_ipv6_bytes(&cell_out->orport_ipv6.addr,
- (const char*)payload);
- cell_out->orport_ipv6.port = ntohs(get_uint16(payload+16));
- found_ipv6 = 1;
- }
- break;
- case SPECTYPE_LEGACY_ID:
- if (speclen != 20)
- return -1;
- if (found_id)
- return -1;
- memcpy(cell_out->node_id, payload, 20);
- found_id = 1;
- break;
- }
- payload += speclen;
}
- if (!found_id || !found_ipv4)
- return -1;
- if (parse_create2_payload(&cell_out->create_cell,payload,eop-payload)<0)
- return -1;
- break;
+ int r = extend_cell_from_extend2_cell_body(cell_out, cell);
+ extend2_cell_body_free(cell);
+ if (r < 0)
+ return r;
}
+ break;
default:
return -1;
}
@@ -925,6 +1041,7 @@ extend_cell_parse(extend_cell_t *cell_out, const uint8_t command,
static int
check_extended_cell(const extended_cell_t *cell)
{
+ tor_assert(cell);
if (cell->created_cell.cell_type == CELL_CREATED) {
if (cell->cell_type != RELAY_COMMAND_EXTENDED)
return -1;
@@ -946,6 +1063,9 @@ extended_cell_parse(extended_cell_t *cell_out,
const uint8_t command, const uint8_t *payload,
size_t payload_len)
{
+ tor_assert(cell_out);
+ tor_assert(payload);
+
memset(cell_out, 0, sizeof(*cell_out));
if (payload_len > RELAY_PAYLOAD_SIZE)
return -1;
@@ -1062,6 +1182,21 @@ created_cell_format(cell_t *cell_out, const created_cell_t *cell_in)
return 0;
}
+/** Return true iff we are configured (by torrc or by the networkstatus
+ * parameters) to use Ed25519 identities in our Extend2 cells. */
+static int
+should_include_ed25519_id_extend_cells(const networkstatus_t *ns,
+ const or_options_t *options)
+{
+ if (options->ExtendByEd25519ID != -1)
+ return options->ExtendByEd25519ID; /* The user has an opinion. */
+
+ return (int) networkstatus_get_param(ns, "ExtendByEd25519ID",
+ 0 /* default */,
+ 0 /* min */,
+ 1 /*max*/);
+}
+
/** Format the EXTEND{,2} cell in <b>cell_in</b>, storing its relay payload in
* <b>payload_out</b>, the number of bytes used in *<b>len_out</b>, and the
* relay command in *<b>command_out</b>. The <b>payload_out</b> must have
@@ -1070,12 +1205,11 @@ int
extend_cell_format(uint8_t *command_out, uint16_t *len_out,
uint8_t *payload_out, const extend_cell_t *cell_in)
{
- uint8_t *p, *eop;
+ uint8_t *p;
if (check_extend_cell(cell_in) < 0)
return -1;
p = payload_out;
- eop = payload_out + RELAY_PAYLOAD_SIZE;
memset(p, 0, RELAY_PAYLOAD_SIZE);
@@ -1098,33 +1232,56 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out,
break;
case RELAY_COMMAND_EXTEND2:
{
- uint8_t n = 2;
+ uint8_t n_specifiers = 2;
*command_out = RELAY_COMMAND_EXTEND2;
-
- *p++ = n; /* 2 identifiers */
- *p++ = SPECTYPE_IPV4; /* First is IPV4. */
- *p++ = 6; /* It's 6 bytes long. */
- set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr));
- set_uint16(p+4, htons(cell_in->orport_ipv4.port));
- p += 6;
- *p++ = SPECTYPE_LEGACY_ID; /* Next is an identity digest. */
- *p++ = 20; /* It's 20 bytes long */
- memcpy(p, cell_in->node_id, DIGEST_LEN);
- p += 20;
-
- /* Now we can send the handshake */
- set_uint16(p, htons(cell_in->create_cell.handshake_type));
- set_uint16(p+2, htons(cell_in->create_cell.handshake_len));
- p += 4;
-
- if (cell_in->create_cell.handshake_len > eop - p)
- return -1;
-
- memcpy(p, cell_in->create_cell.onionskin,
+ extend2_cell_body_t *cell = extend2_cell_body_new();
+ link_specifier_t *ls;
+ {
+ /* IPv4 specifier first. */
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_IPV4;
+ ls->ls_len = 6;
+ ls->un_ipv4_addr = tor_addr_to_ipv4h(&cell_in->orport_ipv4.addr);
+ ls->un_ipv4_port = cell_in->orport_ipv4.port;
+ }
+ {
+ /* Then RSA id */
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_LEGACY_ID;
+ ls->ls_len = DIGEST_LEN;
+ memcpy(ls->un_legacy_id, cell_in->node_id, DIGEST_LEN);
+ }
+ if (should_include_ed25519_id_extend_cells(NULL, get_options()) &&
+ !ed25519_public_key_is_zero(&cell_in->ed_pubkey)) {
+ /* Then, maybe, the ed25519 id! */
+ ++n_specifiers;
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_ED25519_ID;
+ ls->ls_len = 32;
+ memcpy(ls->un_ed25519_id, cell_in->ed_pubkey.pubkey, 32);
+ }
+ cell->n_spec = n_specifiers;
+
+ /* Now, the handshake */
+ cell->create2 = create2_cell_body_new();
+ cell->create2->handshake_type = cell_in->create_cell.handshake_type;
+ cell->create2->handshake_len = cell_in->create_cell.handshake_len;
+ create2_cell_body_setlen_handshake_data(cell->create2,
+ cell_in->create_cell.handshake_len);
+ memcpy(create2_cell_body_getarray_handshake_data(cell->create2),
+ cell_in->create_cell.onionskin,
cell_in->create_cell.handshake_len);
- p += cell_in->create_cell.handshake_len;
- *len_out = p - payload_out;
+ ssize_t len_encoded = extend2_cell_body_encode(
+ payload_out, RELAY_PAYLOAD_SIZE,
+ cell);
+ extend2_cell_body_free(cell);
+ if (len_encoded < 0 || len_encoded > UINT16_MAX)
+ return -1;
+ *len_out = (uint16_t) len_encoded;
}
break;
default:
diff --git a/src/or/onion.h b/src/or/onion.h
index 0275fa00d2..37a7b08cb6 100644
--- a/src/or/onion.h
+++ b/src/or/onion.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -85,6 +85,8 @@ typedef struct extend_cell_t {
tor_addr_port_t orport_ipv6;
/** Identity fingerprint of the node we're conecting to.*/
uint8_t node_id[DIGEST_LEN];
+ /** Ed25519 public identity key. Zero if not set. */
+ ed25519_public_key_t ed_pubkey;
/** The "create cell" embedded in this extend cell. Note that unlike the
* create cells we generate ourself, this once can have a handshake type we
* don't recognize. */
diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c
index 1f79860596..146943a273 100644
--- a/src/or/onion_fast.c
+++ b/src/or/onion_fast.c
@@ -1,12 +1,30 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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_fast.h b/src/or/onion_fast.h
index b9626002c3..b31f8e9492 100644
--- a/src/or/onion_fast.h
+++ b/src/or/onion_fast.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c
index 9f97a4cfbe..902260b54b 100644
--- a/src/or/onion_ntor.c
+++ b/src/or/onion_ntor.c
@@ -1,10 +1,21 @@
-/* Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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_ntor.h b/src/or/onion_ntor.h
index f637b437fd..158c499de4 100644
--- a/src/or/onion_ntor.h
+++ b/src/or/onion_ntor.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_ONION_NTOR_H
diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c
index bfd472351f..c71fa236ed 100644
--- a/src/or/onion_tap.c
+++ b/src/or/onion_tap.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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"
@@ -60,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key,
if (crypto_dh_get_public(dh, challenge, dhbytes))
goto err;
- note_crypto_pk_op(ENC_ONIONSKIN);
-
/* set meeting point, meeting cookie, etc here. Leave zero for now. */
- if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out,
+ if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out,
TAP_ONIONSKIN_CHALLENGE_LEN,
challenge, DH_KEY_LEN,
PK_PKCS1_OAEP_PADDING, 1)<0)
@@ -74,9 +84,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,
@@ -108,8 +122,7 @@ onion_skin_TAP_server_handshake(
k = i==0?private_key:prev_private_key;
if (!k)
break;
- note_crypto_pk_op(DEC_ONIONSKIN);
- len = crypto_pk_private_hybrid_decrypt(k, challenge,
+ len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge,
TAP_ONIONSKIN_CHALLENGE_LEN,
onion_skin,
TAP_ONIONSKIN_CHALLENGE_LEN,
@@ -130,12 +143,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_EXCL_STOP */
}
key_material_len = DIGEST_LEN+key_out_len;
diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h
index a2880f6e98..bd625231f4 100644
--- a/src/or/onion_tap.h
+++ b/src/or/onion_tap.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/or.h b/src/or/or.h
index 08e1f9ba11..ff11c72790 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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,26 +66,22 @@
#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"
#include "torlog.h"
#include "container.h"
-#include "torgzip.h"
+#include "compress.h"
#include "address.h"
#include "compat_libevent.h"
#include "ht.h"
+#include "confline.h"
#include "replaycache.h"
#include "crypto_curve25519.h"
#include "crypto_ed25519.h"
#include "tor_queue.h"
#include "util_format.h"
+#include "hs_circuitmap.h"
/* These signals are defined to help handle_control_signal work.
*/
@@ -128,6 +116,9 @@
#define NON_ANONYMOUS_MODE_ENABLED 1
#endif
+/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */
+#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_))
+
/** Length of longest allowable configured nickname. */
#define MAX_NICKNAME_LEN 19
/** Length of a router identity encoded as a hexadecimal digest, plus
@@ -157,20 +148,27 @@
/** 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)
+/** Minimum lifetime for an onion key in days. */
+#define MIN_ONION_KEY_LIFETIME_DAYS (1)
+
+/** Maximum lifetime for an onion key in days. */
+#define MAX_ONION_KEY_LIFETIME_DAYS (90)
+
+/** Default lifetime for an onion key in days. */
+#define DEFAULT_ONION_KEY_LIFETIME_DAYS (28)
+
+/** Minimum grace period for acceptance of an onion key in days.
+ * The maximum value is defined in proposal #274 as being the current network
+ * consensus parameter for "onion-key-rotation-days". */
+#define MIN_ONION_KEY_GRACE_PERIOD_DAYS (1)
+
+/** Default grace period for acceptance of an onion key in days. */
+#define DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS (7)
+
+/** How often we should check the network consensus if it is time to rotate or
+ * expire onion keys. */
+#define ONION_KEY_CONSENSUS_CHECK_INTERVAL (60*60)
+
/** How often do we rotate TLS contexts? */
#define MAX_SSL_KEY_LIFETIME_INTERNAL (2*60*60)
@@ -423,14 +421,20 @@ typedef enum {
#define DIR_PURPOSE_FETCH_RENDDESC_V2 18
/** A connection to a directory server: download a microdescriptor. */
#define DIR_PURPOSE_FETCH_MICRODESC 19
-#define DIR_PURPOSE_MAX_ 19
-
-/** True iff <b>p</b> is a purpose corresponding to uploading data to a
- * directory server. */
+/** A connection to a hidden service directory: upload a v3 descriptor. */
+#define DIR_PURPOSE_UPLOAD_HSDESC 20
+/** A connection to a hidden service directory: fetch a v3 descriptor. */
+#define DIR_PURPOSE_FETCH_HSDESC 21
+#define DIR_PURPOSE_MAX_ 21
+
+/** True iff <b>p</b> is a purpose corresponding to uploading
+ * data to a directory server. */
#define DIR_PURPOSE_IS_UPLOAD(p) \
((p)==DIR_PURPOSE_UPLOAD_DIR || \
(p)==DIR_PURPOSE_UPLOAD_VOTE || \
- (p)==DIR_PURPOSE_UPLOAD_SIGNATURES)
+ (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
+ (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \
+ (p)==DIR_PURPOSE_UPLOAD_HSDESC)
#define EXIT_PURPOSE_MIN_ 1
/** This exit stream wants to do an ordinary connect. */
@@ -449,8 +453,12 @@ typedef enum {
/** Circuit state: I'd like to deliver a create, but my n_chan is still
* connecting. */
#define CIRCUIT_STATE_CHAN_WAIT 2
+/** Circuit state: the circuit is open but we don't want to actually use it
+ * until we find out if a better guard will be available.
+ */
+#define CIRCUIT_STATE_GUARD_WAIT 3
/** Circuit state: onionskin(s) processed, ready to send/receive cells. */
-#define CIRCUIT_STATE_OPEN 3
+#define CIRCUIT_STATE_OPEN 4
#define CIRCUIT_PURPOSE_MIN_ 1
@@ -784,7 +792,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;
@@ -793,6 +801,24 @@ typedef struct rend_service_authorization_t {
* establishment. Not all fields contain data depending on where this struct
* is used. */
typedef struct rend_data_t {
+ /* Hidden service protocol version of this base object. */
+ uint32_t version;
+
+ /** List of HSDir fingerprints on which this request has been sent to. This
+ * contains binary identity digest of the directory of size DIGEST_LEN. */
+ smartlist_t *hsdirs_fp;
+
+ /** Rendezvous cookie used by both, client and service. */
+ char rend_cookie[REND_COOKIE_LEN];
+
+ /** Number of streams associated with this rendezvous circuit. */
+ int nr_streams;
+} rend_data_t;
+
+typedef struct rend_data_v2_t {
+ /* Rendezvous base data. */
+ rend_data_t base_;
+
/** Onion address (without the .onion part) that a client requests. */
char onion_address[REND_SERVICE_ID_LEN_BASE32+1];
@@ -814,17 +840,23 @@ typedef struct rend_data_t {
/** Hash of the hidden service's PK used by a service. */
char rend_pk_digest[DIGEST_LEN];
+} rend_data_v2_t;
- /** Rendezvous cookie used by both, client and service. */
- char rend_cookie[REND_COOKIE_LEN];
+/* From a base rend_data_t object <b>d</d>, return the v2 object. */
+static inline
+rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
+{
+ tor_assert(d);
+ tor_assert(d->version == 2);
+ return DOWNCAST(rend_data_v2_t, d);
+}
- /** List of HSDir fingerprints on which this request has been sent to.
- * This contains binary identity digest of the directory. */
- smartlist_t *hsdirs_fp;
-
- /** Number of streams associated with this rendezvous circuit. */
- int nr_streams;
-} rend_data_t;
+/* Stub because we can't include hs_ident.h. */
+struct hs_ident_edge_conn_t;
+struct hs_ident_dir_conn_t;
+struct hs_ident_circuit_t;
+/* Stub because we can't include hs_common.h. */
+struct hsdir_index_t;
/** Time interval for tracking replays of DH public keys received in
* INTRODUCE2 cells. Used only to avoid launching multiple
@@ -876,6 +908,7 @@ typedef enum {
#define CELL_RELAY_EARLY 9
#define CELL_CREATE2 10
#define CELL_CREATED2 11
+#define CELL_PADDING_NEGOTIATE 12
#define CELL_VPADDING 128
#define CELL_CERTS 129
@@ -1145,11 +1178,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: */
@@ -1168,6 +1198,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?
@@ -1288,10 +1320,6 @@ 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? */
int socket_family; /**< Address family of this connection's socket. Usually
@@ -1367,13 +1395,30 @@ typedef struct listener_connection_t {
#define OR_CERT_TYPE_RSA_ED_CROSSCERT 7
/**@}*/
-/** The one currently supported type of AUTHENTICATE cell. It contains
+/** The first supported type of AUTHENTICATE cell. It contains
* a bunch of structures signed with an RSA1024 key. The signed
* structures include a HMAC using negotiated TLS secrets, and a digest
* of all cells sent or received before the AUTHENTICATE cell (including
* the random server-generated AUTH_CHALLENGE cell).
*/
#define AUTHTYPE_RSA_SHA256_TLSSECRET 1
+/** As AUTHTYPE_RSA_SHA256_TLSSECRET, but instead of using the
+ * negotiated TLS secrets, uses exported keying material from the TLS
+ * session as described in RFC 5705.
+ *
+ * Not used by today's tors, since everything that supports this
+ * also supports ED25519_SHA3_5705, which is better.
+ **/
+#define AUTHTYPE_RSA_SHA256_RFC5705 2
+/** As AUTHTYPE_RSA_SHA256_RFC5705, but uses an Ed25519 identity key to
+ * authenticate. */
+#define AUTHTYPE_ED25519_SHA256_RFC5705 3
+/*
+ * NOTE: authchallenge_type_is_better() relies on these AUTHTYPE codes
+ * being sorted in order of preference. If we someday add one with
+ * a higher numerical value that we don't like as much, we should revise
+ * authchallenge_type_is_better().
+ */
/** The length of the part of the AUTHENTICATE cell body that the client and
* server can generate independently (when using RSA_SHA256_TLSSECRET). It
@@ -1384,6 +1429,34 @@ typedef struct listener_connection_t {
* signs. */
#define V3_AUTH_BODY_LEN (V3_AUTH_FIXED_PART_LEN + 8 + 16)
+/** Structure to hold all the certificates we've received on an OR connection
+ */
+typedef struct or_handshake_certs_t {
+ /** True iff we originated this connection. */
+ int started_here;
+ /** The cert for the 'auth' RSA key that's supposed to sign the AUTHENTICATE
+ * cell. Signed with the RSA identity key. */
+ tor_x509_cert_t *auth_cert;
+ /** The cert for the 'link' RSA key that was used to negotiate the TLS
+ * connection. Signed with the RSA identity key. */
+ tor_x509_cert_t *link_cert;
+ /** A self-signed identity certificate: the RSA identity key signed
+ * with itself. */
+ tor_x509_cert_t *id_cert;
+ /** The Ed25519 signing key, signed with the Ed25519 identity key. */
+ struct tor_cert_st *ed_id_sign;
+ /** A digest of the X509 link certificate for the TLS connection, signed
+ * with the Ed25519 siging key. */
+ struct tor_cert_st *ed_sign_link;
+ /** The Ed25519 authentication key (that's supposed to sign an AUTHENTICATE
+ * cell) , signed with the Ed25519 siging key. */
+ struct tor_cert_st *ed_sign_auth;
+ /** The Ed25519 identity key, crosssigned with the RSA identity key. */
+ uint8_t *ed_rsa_crosscert;
+ /** The length of <b>ed_rsa_crosscert</b> in bytes */
+ size_t ed_rsa_crosscert_len;
+} or_handshake_certs_t;
+
/** Stores flags and information related to the portion of a v2/v3 Tor OR
* connection handshake that happens after the TLS handshake is finished.
*/
@@ -1404,10 +1477,18 @@ typedef struct or_handshake_state_t {
/* True iff we've received valid authentication to some identity. */
unsigned int authenticated : 1;
+ unsigned int authenticated_rsa : 1;
+ unsigned int authenticated_ed25519 : 1;
/* True iff we have sent a netinfo cell */
unsigned int sent_netinfo : 1;
+ /** The signing->ed25519 link certificate corresponding to the x509
+ * certificate we used on the TLS connection (if this is a server-side
+ * connection). We make a copy of this here to prevent a race condition
+ * caused by TLS context rotation. */
+ struct tor_cert_st *own_link_cert;
+
/** True iff we should feed outgoing cells into digest_sent and
* digest_received respectively.
*
@@ -1421,9 +1502,12 @@ typedef struct or_handshake_state_t {
unsigned int digest_received_data : 1;
/**@}*/
- /** Identity digest that we have received and authenticated for our peer
+ /** Identity RSA digest that we have received and authenticated for our peer
* on this connection. */
- uint8_t authenticated_peer_id[DIGEST_LEN];
+ uint8_t authenticated_rsa_peer_id[DIGEST_LEN];
+ /** Identity Ed25519 public key that we have received and authenticated for
+ * our peer on this connection. */
+ ed25519_public_key_t authenticated_ed25519_peer_id;
/** Digests of the cells that we have sent or received as part of a V3
* handshake. Used for making and checking AUTHENTICATE cells.
@@ -1436,14 +1520,8 @@ typedef struct or_handshake_state_t {
/** Certificates that a connection initiator sent us in a CERTS cell; we're
* holding on to them until we get an AUTHENTICATE cell.
- *
- * @{
*/
- /** The cert for the key that's supposed to sign the AUTHENTICATE cell */
- tor_x509_cert_t *auth_cert;
- /** A self-signed identity certificate */
- tor_x509_cert_t *id_cert;
- /**@}*/
+ or_handshake_certs_t *certs;
} or_handshake_state_t;
/** Length of Extended ORPort connection identifier. */
@@ -1505,10 +1583,6 @@ typedef struct or_connection_t {
* NETINFO cell listed the address we're connected to as recognized. */
unsigned int is_canonical:1;
- /** True iff we have decided that the other end of this connection
- * is a client. Connections with this flag set should never be used
- * to satisfy an EXTEND request. */
- unsigned int is_connection_with_client:1;
/** True iff this is an outgoing connection. */
unsigned int is_outgoing:1;
unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */
@@ -1531,20 +1605,11 @@ 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. */
/** Last emptied read token bucket in msec since midnight; only used if
* TB_EMPTY events are enabled. */
uint32_t read_emptied_time;
@@ -1580,6 +1645,11 @@ typedef struct edge_connection_t {
* an exit)? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for edge connections. Used by the HS
+ * client-side code to identify client SOCKS connections and by the
+ * service-side code to match HS circuits with their streams. */
+ struct hs_ident_edge_conn_t *hs_ident;
+
uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit
* connection. Exit connections only. */
uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell
@@ -1622,6 +1692,8 @@ typedef struct entry_connection_t {
edge_connection_t edge_;
/** Nickname of planned exit node -- used with .exit support. */
+ /* XXX prop220: we need to make chosen_exit_name able to encode Ed IDs too.
+ * That's logically part of the UI parts for prop220 though. */
char *chosen_exit_name;
socks_request_t *socks_request; /**< SOCKS structure describing request (AP
@@ -1647,11 +1719,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. */
@@ -1701,14 +1773,6 @@ typedef struct entry_connection_t {
unsigned int is_socks_socket:1;
} entry_connection_t;
-typedef enum {
- DIR_SPOOL_NONE=0, DIR_SPOOL_SERVER_BY_DIGEST, DIR_SPOOL_SERVER_BY_FP,
- DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP,
- DIR_SPOOL_CACHED_DIR, DIR_SPOOL_NETWORKSTATUS,
- DIR_SPOOL_MICRODESC, /* NOTE: if we add another entry, add another bit. */
-} dir_spool_source_t;
-#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t)
-
/** Subtype of connection_t for an "directory connection" -- that is, an HTTP
* connection to retrieve or serve directory material. */
typedef struct dir_connection_t {
@@ -1717,33 +1781,34 @@ typedef struct dir_connection_t {
/** Which 'resource' did we ask the directory for? This is typically the part
* of the URL string that defines, relative to the directory conn purpose,
* what thing we want. For example, in router descriptor downloads by
- * descriptor digest, it contains "d/", then one ore more +-separated
+ * descriptor digest, it contains "d/", then one or more +-separated
* fingerprints.
**/
char *requested_resource;
unsigned int dirconn_direct:1; /**< Is this dirconn direct, or via Tor? */
- /* Used only for server sides of some dir connections, to implement
- * "spooling" of directory material to the outbuf. Otherwise, we'd have
- * to append everything to the outbuf in one enormous chunk. */
- /** What exactly are we spooling right now? */
- dir_spool_source_bitfield_t dir_spool_src : 3;
-
/** If we're fetching descriptors, what router purpose shall we assign
* to them? */
uint8_t router_purpose;
- /** List of fingerprints for networkstatuses or descriptors to be spooled. */
- smartlist_t *fingerprint_stack;
- /** A cached_dir_t object that we're currently spooling out */
- struct cached_dir_t *cached_dir;
- /** The current offset into cached_dir. */
- off_t cached_dir_offset;
- /** The zlib object doing on-the-fly compression for spooled data. */
- tor_zlib_state_t *zlib_state;
+
+ /** List of spooled_resource_t for objects that we're spooling. We use
+ * it from back to front. */
+ smartlist_t *spool;
+ /** The compression object doing on-the-fly compression for spooled data. */
+ tor_compress_state_t *compress_state;
/** What rendezvous service are we querying for? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for dir connections: Used by HS
+ client-side code to fetch HS descriptors, and by the service-side code to
+ upload descriptors. */
+ struct hs_ident_dir_conn_t *hs_ident;
+
+ /** If this is a one-hop connection, tracks the state of the directory guard
+ * for this connection (if any). */
+ struct circuit_guard_state_t *guard_state;
+
char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
* the directory server's signing key. */
@@ -1751,6 +1816,14 @@ typedef struct dir_connection_t {
* that's going away and being used on channels instead. The dirserver still
* needs this for the incoming side, so it's moved here. */
uint64_t dirreq_id;
+
+#ifdef MEASUREMENTS_21206
+ /** Number of RELAY_DATA cells received. */
+ uint32_t data_cells_received;
+
+ /** Number of RELAY_DATA cells sent. */
+ uint32_t data_cells_sent;
+#endif
} dir_connection_t;
/** Subtype of connection_t for an connection to a controller. */
@@ -1787,8 +1860,6 @@ typedef struct control_connection_t {
/** Cast a connection_t subtype pointer to a connection_t **/
#define TO_CONN(c) (&(((c)->base_)))
-/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */
-#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_))
/** Cast a entry_connection_t subtype pointer to a edge_connection_t **/
#define ENTRY_TO_EDGE_CONN(c) (&(((c))->edge_))
@@ -1854,51 +1925,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,
@@ -1936,11 +1962,13 @@ typedef struct addr_policy_t {
* compressed form. */
typedef struct cached_dir_t {
char *dir; /**< Contents of this object, NUL-terminated. */
- char *dir_z; /**< Compressed contents of this object. */
+ char *dir_compressed; /**< Compressed contents of this object. */
size_t dir_len; /**< Length of <b>dir</b> (not counting its NUL). */
- size_t dir_z_len; /**< Length of <b>dir_z</b>. */
+ size_t dir_compressed_len; /**< Length of <b>dir_compressed</b>. */
time_t published; /**< When was this object published. */
common_digests_t digests; /**< Digests of this object (networkstatus only) */
+ /** Sha3 digest (also ns only) */
+ uint8_t digest_sha3_as_signed[DIGEST256_LEN];
int refcnt; /**< Reference count for this cached_dir_t. */
} cached_dir_t;
@@ -1995,6 +2023,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
@@ -2041,6 +2078,17 @@ 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?
+ * Increment on failure schedules
+ * always use exponential backoff. */
+ uint8_t last_backoff_position; /**< number of attempts/failures, depending
+ * on increment_on, when we last recalculated
+ * the delay. Only updated if backoff
+ * == 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. */
@@ -2070,6 +2118,10 @@ typedef struct signed_descriptor_t {
time_t published_on;
/** For routerdescs only: digest of the corresponding extrainfo. */
char extra_info_digest[DIGEST_LEN];
+ /** For routerdescs only: A SHA256-digest of the extrainfo (if any) */
+ char extra_info_digest256[DIGEST256_LEN];
+ /** Certificate for ed25519 signing key. */
+ struct tor_cert_st *signing_key_cert;
/** For routerdescs only: Status of downloading the corresponding
* extrainfo. */
download_status_t ei_dl_status;
@@ -2101,8 +2153,6 @@ typedef int16_t country_t;
/** Information about another onion router in the network. */
typedef struct {
signed_descriptor_t cache_info;
- /** A SHA256-digest of the extrainfo (if any) */
- char extra_info_digest256[DIGEST256_LEN];
char *nickname; /**< Human-readable OR name. */
uint32_t addr; /**< IPv4 address of OR, in host order. */
@@ -2120,14 +2170,15 @@ typedef struct {
crypto_pk_t *identity_pkey; /**< Public RSA key for signing. */
/** Public curve25519 key for onions */
curve25519_public_key_t *onion_curve25519_pkey;
- /** Certificate for ed25519 signing key */
- struct tor_cert_st *signing_key_cert;
/** What's the earliest expiration time on all the certs in this
* routerinfo? */
time_t cert_expiration_time;
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? */
@@ -2197,8 +2248,6 @@ typedef struct extrainfo_t {
uint8_t digest256[DIGEST256_LEN];
/** The router's nickname. */
char nickname[MAX_NICKNAME_LEN+1];
- /** Certificate for ed25519 signing key */
- struct tor_cert_st *signing_key_cert;
/** True iff we found the right key for this extra-info, verified the
* signature, and found it to be bad. */
unsigned int bad_sig : 1;
@@ -2220,7 +2269,7 @@ typedef struct routerstatus_t {
/** Digest of the router's most recent descriptor or microdescriptor.
* If it's a descriptor, we only use the first DIGEST_LEN bytes. */
char descriptor_digest[DIGEST256_LEN];
- uint32_t addr; /**< IPv4 address for this router. */
+ uint32_t addr; /**< IPv4 address for this router, in host order. */
uint16_t or_port; /**< OR port for this router. */
uint16_t dir_port; /**< Directory port for this router. */
tor_addr_t ipv6_addr; /**< IPv6 address for this router. */
@@ -2247,14 +2296,27 @@ 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 or protocol list that allows it to
+ * accept EXTEND2 cells */
+ unsigned int supports_extend2_cells: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 protocol list that allows it to negotiate
+ * ed25519 identity keys on a link handshake. */
+ unsigned int supports_ed25519_link_handshake:1;
+
+ /** True iff this router has a protocol list that allows it to be an
+ * introduction point supporting ed25519 authentication key which is part of
+ * the v3 protocol detailed in proposal 224. This requires HSIntro=4. */
+ unsigned int supports_ed25519_hs_intro : 1;
+
+ /** True iff this router has a protocol list that allows it to be an hidden
+ * service directory supporting version 3 as seen in proposal 224. This
+ * requires HSDir=2. */
+ unsigned int supports_v3_hsdir : 1;
unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */
unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */
@@ -2418,9 +2480,6 @@ typedef struct node_t {
/** Local info: we treat this node as if it rejects everything */
unsigned int rejects_all:1;
- /** Local info: this node is in our list of guards */
- unsigned int using_as_guard:1;
-
/* Local info: derived. */
/** True if the IPv6 OR port is preferred over the IPv4 OR port.
@@ -2438,6 +2497,10 @@ typedef struct node_t {
time_t last_reachable; /* IPv4. */
time_t last_reachable6; /* IPv6. */
+ /* Hidden service directory index data. This is used by a service or client
+ * in order to know what's the hs directory index for this node at the time
+ * the consensus is set. */
+ struct hsdir_index_t *hsdir_index;
} node_t;
/** Linked list of microdesc hash lines for a single router in a directory
@@ -2462,6 +2525,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. */
@@ -2515,6 +2580,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,
@@ -2567,6 +2644,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;
@@ -2588,6 +2675,9 @@ typedef struct networkstatus_t {
/** Digests of this document, as signed. */
common_digests_t digests;
+ /** A SHA3-256 digest of the document, not including signatures: used for
+ * consensus diffs */
+ uint8_t digest_sha3_as_signed[DIGEST256_LEN];
/** List of router statuses, sorted by identity digest. For a vote,
* the elements are vote_routerstatus_t; for a consensus, the elements
@@ -2597,6 +2687,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
@@ -2673,7 +2766,10 @@ typedef struct {
typedef struct extend_info_t {
char nickname[MAX_HEX_NICKNAME_LEN+1]; /**< This router's nickname for
* display. */
- char identity_digest[DIGEST_LEN]; /**< Hash of this router's identity key. */
+ /** Hash of this router's RSA identity key. */
+ char identity_digest[DIGEST_LEN];
+ /** Ed25519 identity for this router, if any. */
+ ed25519_public_key_t ed_identity;
uint16_t port; /**< OR port. */
tor_addr_t addr; /**< IP address. */
crypto_pk_t *onion_key; /**< Current onionskin key. */
@@ -2965,17 +3061,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.
@@ -2990,11 +3086,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;
@@ -3015,6 +3111,13 @@ typedef struct circuit_t {
* circuit's queues; used only if CELL_STATS events are enabled and
* cleared after being sent to control port. */
smartlist_t *testing_cell_stats;
+
+ /** If set, points to an HS token that this circuit might be carrying.
+ * Used by the HS circuitmap. */
+ hs_token_t *hs_token;
+ /** Hashtable node: used to look up the circuit by its HS token using the HS
+ circuitmap. */
+ HT_ENTRY(circuit_t) hs_circuitmap_node;
} circuit_t;
/** Largest number of relay_early cells that we can send on a given
@@ -3111,6 +3214,19 @@ typedef struct origin_circuit_t {
/** Holds all rendezvous data on either client or service side. */
rend_data_t *rend_data;
+ /** Holds hidden service identifier on either client or service side. This
+ * is for both introduction and rendezvous circuit. */
+ struct hs_ident_circuit_t *hs_ident;
+
+ /** Holds the data that the entry guard system uses to track the
+ * status of the guard this circuit is using, and thereby to determine
+ * whether this circuit can be used. */
+ struct circuit_guard_state_t *guard_state;
+
+ /** Index into global_origin_circuit_list for this circuit. -1 if not
+ * present. */
+ int global_origin_circuit_list_idx;
+
/** How many more relay_early cells can we send on this circuit, according
* to the specification? */
unsigned int remaining_relay_early_cells : 4;
@@ -3254,6 +3370,13 @@ typedef struct origin_circuit_t {
* adjust_exit_policy_from_exitpolicy_failure.
*/
smartlist_t *prepend_policy;
+
+ /** How long do we wait before closing this circuit if it remains
+ * completely idle after it was built, in seconds? This value
+ * is randomized on a per-circuit basis from CircuitsAvailableTimoeut
+ * to 2*CircuitsAvailableTimoeut. */
+ int circuit_idle_timeout;
+
} origin_circuit_t;
struct onion_queue_t;
@@ -3315,8 +3438,6 @@ typedef struct or_circuit_t {
* is not marked for close. */
struct or_circuit_t *rend_splice;
- struct or_circuit_rendinfo_s *rendinfo;
-
/** Stores KH for the handshake. */
char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */
@@ -3350,25 +3471,11 @@ typedef struct or_circuit_t {
uint32_t max_middle_cells;
} or_circuit_t;
-typedef struct or_circuit_rendinfo_s {
-
#if REND_COOKIE_LEN != DIGEST_LEN
#error "The REND_TOKEN_LEN macro assumes REND_COOKIE_LEN == DIGEST_LEN"
#endif
#define REND_TOKEN_LEN DIGEST_LEN
- /** A hash of location-hidden service's PK if purpose is INTRO_POINT, or a
- * rendezvous cookie if purpose is REND_POINT_WAITING. Filled with zeroes
- * otherwise.
- */
- char rend_token[REND_TOKEN_LEN];
-
- /** True if this is a rendezvous point circuit; false if this is an
- * introduction point. */
- unsigned is_rend_circ;
-
-} or_circuit_rendinfo_t;
-
/** Convert a circuit subtype to a circuit_t. */
#define TO_CIRCUIT(x) (&((x)->base_))
@@ -3411,15 +3518,6 @@ static inline const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT(
return DOWNCAST(origin_circuit_t, x);
}
-/** Bitfield type: things that we're willing to use invalid routers for. */
-typedef enum invalid_router_usage_t {
- ALLOW_INVALID_ENTRY =1,
- ALLOW_INVALID_EXIT =2,
- ALLOW_INVALID_MIDDLE =4,
- ALLOW_INVALID_RENDEZVOUS =8,
- ALLOW_INVALID_INTRODUCTION=16,
-} invalid_router_usage_t;
-
/* limits for TCP send and recv buffer size used for constrained sockets */
#define MIN_CONSTRAINED_TCP_BUFFER 2048
#define MAX_CONSTRAINED_TCP_BUFFER 262144 /* 256k */
@@ -3481,33 +3579,18 @@ typedef struct port_cfg_t {
char unix_addr[FLEXIBLE_ARRAY_MEMBER];
} port_cfg_t;
-/** Ordinary configuration line. */
-#define CONFIG_LINE_NORMAL 0
-/** Appends to previous configuration for the same option, even if we
- * would ordinary replace it. */
-#define CONFIG_LINE_APPEND 1
-/* Removes all previous configuration for an option. */
-#define CONFIG_LINE_CLEAR 2
-
-/** A linked list of lines in a config file. */
-typedef struct config_line_t {
- char *key;
- char *value;
- struct config_line_t *next;
- /** What special treatment (if any) does this line require? */
- unsigned int command:2;
- /** If true, subsequent assignments to this linelist should replace
- * it, not extend it. Set only on the first item in a linelist in an
- * or_options_t. */
- unsigned int fragile:1;
-} config_line_t;
-
typedef struct routerset_t routerset_t;
/** A magic value for the (Socks|OR|...)Port options below, telling Tor
* to pick its own port. */
#define CFG_AUTO_PORT 0xc4005e
+/** Enumeration of outbound address configuration types:
+ * Exit-only, OR-only, or both */
+typedef enum {OUTBOUND_ADDR_EXIT, OUTBOUND_ADDR_OR,
+ OUTBOUND_ADDR_EXIT_AND_OR,
+ OUTBOUND_ADDR_MAX} outbound_addr_t;
+
/** Configuration options for a Tor process. */
typedef struct {
uint32_t magic_;
@@ -3516,7 +3599,8 @@ typedef struct {
enum {
CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD,
CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG,
- CMD_KEYGEN
+ CMD_KEYGEN,
+ CMD_KEY_EXPIRATION,
} command;
char *command_arg; /**< Argument for command-line option. */
@@ -3560,35 +3644,26 @@ typedef struct {
int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our
* process for all current and future memory. */
- /** List of "entry", "middle", "exit", "introduction", "rendezvous". */
- smartlist_t *AllowInvalidNodes;
- /** 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. */
- config_line_t *SocksListenAddress;
- /** Addresses to bind for listening for transparent pf/netfilter
- * connections. */
- config_line_t *TransListenAddress;
- /** Addresses to bind for listening for transparent natd connections */
- config_line_t *NATDListenAddress;
- /** Addresses to bind for listening for SOCKS connections. */
- config_line_t *DNSListenAddress;
- /** Addresses to bind for listening for OR connections. */
- config_line_t *ORListenAddress;
- /** Addresses to bind for listening for directory connections. */
- config_line_t *DirListenAddress;
- /** Addresses to bind for listening for control connections. */
- config_line_t *ControlListenAddress;
/** Local address to bind outbound sockets */
config_line_t *OutboundBindAddress;
- /** IPv4 address derived from OutboundBindAddress. */
- tor_addr_t OutboundBindAddressIPv4_;
- /** IPv6 address derived from OutboundBindAddress. */
- tor_addr_t OutboundBindAddressIPv6_;
+ /** Local address to bind outbound relay sockets */
+ config_line_t *OutboundBindAddressOR;
+ /** Local address to bind outbound exit sockets */
+ config_line_t *OutboundBindAddressExit;
+ /** Addresses derived from the various OutboundBindAddress lines.
+ * [][0] is IPv4, [][1] is IPv6
+ */
+ tor_addr_t OutboundBindAddresses[OUTBOUND_ADDR_MAX][2];
/** Directory server only: which versions of
* Tor should we tell users to run? */
config_line_t *RecommendedVersions;
@@ -3600,7 +3675,6 @@ typedef struct {
/** Whether routers accept EXTEND cells to routers with private IPs. */
int ExtendAllowPrivateAddresses;
char *User; /**< Name of user to run Tor as. */
- char *Group; /**< Name of group to run Tor as. */
config_line_t *ORPort_lines; /**< Ports to listen on for OR connections. */
/** Ports to listen on for extended OR connections. */
config_line_t *ExtORPort_lines;
@@ -3640,9 +3714,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.
*
* @{
*/
@@ -3699,6 +3777,15 @@ typedef struct {
int AvoidDiskWrites; /**< Boolean: should we never cache things to disk?
* Not used yet. */
int ClientOnly; /**< Boolean: should we never evolve into a server role? */
+
+ int ReducedConnectionPadding; /**< Boolean: Should we try to keep connections
+ open shorter and pad them less against
+ connection-level traffic analysis? */
+ /** Autobool: if auto, then connection padding will be negotiated by client
+ * and server. If 0, it will be fully disabled. If 1, the client will still
+ * pad to the server regardless of server support. */
+ int ConnectionPadding;
+
/** To what authority types do we publish our descriptor? Choices are
* "v1", "v2", "v3", "bridge", or "". */
smartlist_t *PublishServerDescriptor;
@@ -3724,17 +3811,32 @@ typedef struct {
/** A routerset that should be used when picking RPs for HS circuits. */
routerset_t *Tor2webRendezvousPoints;
- /** Close hidden service client circuits immediately when they reach
- * the normal circuit-build timeout, even if they have already sent
- * an INTRODUCE1 cell on its way to the service. */
- int CloseHSClientCircuitsImmediatelyOnTimeout;
-
- /** Close hidden-service-side rendezvous circuits immediately when
- * 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
@@ -3784,12 +3886,13 @@ 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. */
- int CircuitIdleTimeout; /**< Cull open clean circuits that were born
- * at least this many seconds ago. */
+ int CircuitsAvailableTimeout; /**< Try to have an open circuit for at
+ least this long after last activity */
int CircuitStreamTimeout; /**< If non-zero, detach streams from circuits
* and try a new circuit if the stream has been
* waiting for this many seconds. If zero, use
@@ -3799,16 +3902,12 @@ typedef struct {
* a new one? */
int MaxCircuitDirtiness; /**< Never use circs that were first used more than
this interval ago. */
- int PredictedPortsRelevanceTime; /** How long after we've requested a
- * connection for a given port, do we want
- * to continue to pick exits that support
- * that port? */
uint64_t BandwidthRate; /**< How much bandwidth, on average, are we willing
* to use in a second? */
uint64_t BandwidthBurst; /**< How much bandwidth, at maximum, are we willing
* to use in a second? */
uint64_t MaxAdvertisedBandwidth; /**< How much bandwidth are we willing to
- * tell people we have? */
+ * tell other nodes we have? */
uint64_t RelayBandwidthRate; /**< How much bandwidth, on average, are we
* willing to use for all relayed conns? */
uint64_t RelayBandwidthBurst; /**< How much bandwidth, at maximum, will we
@@ -3816,8 +3915,6 @@ typedef struct {
uint64_t PerConnBWRate; /**< Long-term bw on a single TLS conn, if set. */
uint64_t PerConnBWBurst; /**< Allowed burst on a single TLS conn, if set. */
int NumCPUs; /**< How many CPUs should we try to use? */
-//int RunTesting; /**< If true, create testing circuits to measure how well the
-// * other ORs are running. */
config_line_t *RendConfigLines; /**< List of configuration lines
* for rendezvous services. */
config_line_t *HidServAuth; /**< List of configuration lines for client-side
@@ -3868,7 +3965,8 @@ typedef struct {
/** If set, use these bridge authorities and not the default one. */
config_line_t *AlternateBridgeAuthority;
- char *MyFamily; /**< Declared family for this OR. */
+ config_line_t *MyFamily_lines; /**< Declared family for this OR. */
+ config_line_t *MyFamily; /**< Declared family for this OR, normalized */
config_line_t *NodeFamilies; /**< List of config lines for
* node families */
smartlist_t *NodeFamilySets; /**< List of parsed NodeFamilies values. */
@@ -3894,9 +3992,6 @@ typedef struct {
* and vote for all other exits as good. */
int AuthDirMaxServersPerAddr; /**< Do not permit more than this
* number of servers per IP address. */
- int AuthDirMaxServersPerAuthAddr; /**< Do not permit more than this
- * number of servers per IP address shared
- * with an authority. */
int AuthDirHasIPv6Connectivity; /**< Boolean: are we on IPv6? */
int AuthDirPinKeys; /**< Boolean: Do we enforce key-pinning? */
@@ -3970,11 +4065,17 @@ 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? */
/** If 1, we use any guardfraction information we see in the
* consensus. If 0, we don't. If -1, let the consensus parameter
@@ -3984,8 +4085,6 @@ typedef struct {
int NumDirectoryGuards; /**< How many dir guards do we try to establish?
* If 0, use value from NumEntryGuards. */
int RephistTrackTime; /**< How many seconds do we keep rephist info? */
- int FastFirstHopPK; /**< If Tor believes it is safe, should we save a third
- * of our PK time by sending CREATE_FAST cells? */
/** Should we always fetch our dir info on the mirror schedule (which
* means directly from the authorities) no matter our other config? */
int FetchDirInfoEarly;
@@ -4041,16 +4140,6 @@ typedef struct {
* if we are a cache). For authorities, this is always true. */
int DownloadExtraInfo;
- /** If true, and we are acting as a relay, allow exit circuits even when
- * we are the first hop of a circuit. */
- int AllowSingleHopExits;
- /** If true, don't allow relays with AllowSingleHopExits=1 to be used in
- * circuits that we build. */
- int ExcludeSingleHopRelays;
- /** If true, and the controller tells us to use a one-hop circuit, and the
- * exit allows it, we use it. */
- int AllowSingleHopCircuits;
-
/** If true, we convert "www.google.com.foo.exit" addresses on the
* socks/trans/natd ports into "www.google.com" addresses that
* exit from the node "foo". Disabled by default since attacking
@@ -4058,10 +4147,6 @@ typedef struct {
* selection. */
int AllowDotExit;
- /** If true, we will warn if a user gives us only an IP address
- * instead of a hostname. */
- int WarnUnsafeSocks;
-
/** If true, we're configured to collect statistics on clients
* requesting network statuses from us as directory. */
int DirReqStatistics_option;
@@ -4078,11 +4163,18 @@ typedef struct {
/** If true, the user wants us to collect cell statistics. */
int CellStatistics;
+ /** If true, the user wants us to collect padding statistics. */
+ int PaddingStatistics;
+
/** If true, the user wants us to collect statistics as entry node. */
int EntryStatistics;
/** If true, the user wants us to collect statistics as hidden service
* directory, introduction point, or rendezvous point. */
+ int HiddenServiceStatistics_option;
+ /** Internal variable to remember whether we're actually acting on
+ * HiddenServiceStatistics_option -- yes if it's set and we're a server,
+ * else no. */
int HiddenServiceStatistics;
/** If true, include statistics file contents in extra-info documents. */
@@ -4281,8 +4373,7 @@ typedef struct {
int TestingDirAuthVoteGuardIsStrict;
/** Relays in a testing network which should be voted HSDir
- * regardless of uptime and DirPort.
- * Respects VoteOnHidServDirectoriesV2. */
+ * regardless of uptime and DirPort. */
routerset_t *TestingDirAuthVoteHSDir;
int TestingDirAuthVoteHSDirIsStrict;
@@ -4328,12 +4419,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. */
@@ -4357,11 +4442,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;
@@ -4425,11 +4505,6 @@ typedef struct {
int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */
- 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;
@@ -4460,7 +4535,7 @@ typedef struct {
* XXXX Eventually, the default will be 0. */
int ExitRelay;
- /** For how long (seconds) do we declare our singning keys to be valid? */
+ /** For how long (seconds) do we declare our signing keys to be valid? */
int SigningKeyLifetime;
/** For how long (seconds) do we declare our link keys to be valid? */
int TestingLinkCertLifetime;
@@ -4490,6 +4565,37 @@ 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;
+
+ /** Autobool: Should we include Ed25519 identities in extend2 cells?
+ * If -1, we should do whatever the consensus parameter says. */
+ int ExtendByEd25519ID;
+
+ /** Bool (default: 1): When testing routerinfos as a directory authority,
+ * do we enforce Ed25519 identity match? */
+ /* NOTE: remove this option someday. */
+ int AuthDirTestEd25519LinkKeys;
+
+ /** Bool (default: 0): Tells if a %include was used on torrc */
+ int IncludeUsed;
+
+ /** The seconds after expiration which we as a relay should keep old
+ * consensuses around so that we can generate diffs from them. If 0,
+ * use the default. */
+ int MaxConsensusAgeForDiffs;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
@@ -4513,11 +4619,17 @@ typedef struct {
uint64_t AccountingBytesAtSoftLimit;
uint64_t AccountingExpectedUsage;
- /** A list of Entry Guard-related configuration lines. */
+ /** A list of Entry Guard-related configuration lines. (pre-prop271) */
config_line_t *EntryGuards;
+ /** A list of guard-related configuration lines. (post-prop271) */
+ config_line_t *Guard;
+
config_line_t *TransportProxies;
+ /** Cached revision counters for active hidden services on this host */
+ config_line_t *HidServRevCounter;
+
/** These fields hold information on the history of bandwidth usage for
* servers. The "Ends" fields hold the time when we last updated the
* bandwidth usage. The "Interval" fields hold the granularity, in seconds,
@@ -4712,7 +4824,7 @@ typedef uint32_t build_time_t;
double circuit_build_times_quantile_cutoff(void);
/** How often in seconds should we build a test circuit */
-#define CBT_DEFAULT_TEST_FREQUENCY 60
+#define CBT_DEFAULT_TEST_FREQUENCY 10
#define CBT_MIN_TEST_FREQUENCY 1
#define CBT_MAX_TEST_FREQUENCY INT32_MAX
@@ -5039,7 +5151,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;
@@ -5067,12 +5179,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
@@ -5084,7 +5196,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. */
@@ -5200,7 +5313,8 @@ typedef struct dir_server_t {
* address information from published? */
routerstatus_t fake_status; /**< Used when we need to pass this trusted
- * dir_server_t to directory_initiate_command_*
+ * dir_server_t to
+ * directory_request_set_routerstatus.
* as a routerstatus_t. Not updated by the
* router-status management code!
**/
@@ -5240,10 +5354,6 @@ typedef struct dir_server_t {
*/
#define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4)
-/** This node is to be chosen as a directory guard, so don't choose any
- * node that's currently a guard. */
-#define PDS_FOR_GUARD (1<<5)
-
/** Possible ways to weight routers when choosing one randomly. See
* routerlist_sl_choose_by_bandwidth() for more information.*/
typedef enum bandwidth_weight_rule_t {
@@ -5257,20 +5367,20 @@ typedef enum {
CRN_NEED_UPTIME = 1<<0,
CRN_NEED_CAPACITY = 1<<1,
CRN_NEED_GUARD = 1<<2,
- CRN_ALLOW_INVALID = 1<<3,
/* XXXX not used, apparently. */
CRN_WEIGHT_AS_EXIT = 1<<5,
CRN_NEED_DESC = 1<<6,
/* On clients, only provide nodes that satisfy ClientPreferIPv6OR */
- CRN_PREF_ADDR = 1<<7
+ CRN_PREF_ADDR = 1<<7,
+ /* On clients, only provide nodes that we can connect to directly, based on
+ * our firewall rules */
+ CRN_DIRECT_CONN = 1<<8
} router_crn_flags_t;
/** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */
typedef enum was_router_added_t {
/* Router was added successfully. */
ROUTER_ADDED_SUCCESSFULLY = 1,
- /* Router descriptor was added with warnings to submitter. */
- ROUTER_ADDED_NOTIFY_GENERATOR = 0,
/* Extrainfo document was rejected because no corresponding router
* descriptor was found OR router descriptor was rejected because
* it was incompatible with its extrainfo document. */
diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c
new file mode 100644
index 0000000000..6b5359303a
--- /dev/null
+++ b/src/or/parsecommon.c
@@ -0,0 +1,450 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file parsecommon.c
+ * \brief Common code to parse and validate various type of descriptors.
+ **/
+
+#include "parsecommon.h"
+#include "torlog.h"
+#include "util_format.h"
+
+#define MIN_ANNOTATION A_PURPOSE
+#define MAX_ANNOTATION A_UNKNOWN_
+
+#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz)
+#define ALLOC(sz) memarea_alloc(area,sz)
+#define STRDUP(str) memarea_strdup(area,str)
+#define STRNDUP(str,n) memarea_strndup(area,(str),(n))
+
+#define RET_ERR(msg) \
+ STMT_BEGIN \
+ if (tok) token_clear(tok); \
+ tok = ALLOC_ZERO(sizeof(directory_token_t)); \
+ tok->tp = ERR_; \
+ tok->error = STRDUP(msg); \
+ goto done_tokenizing; \
+ STMT_END
+
+/** Free all resources allocated for <b>tok</b> */
+void
+token_clear(directory_token_t *tok)
+{
+ if (tok->key)
+ crypto_pk_free(tok->key);
+}
+
+/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
+ * them to <b>out</b>. Parse according to the token rules in <b>table</b>.
+ * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the
+ * entire string.
+ */
+int
+tokenize_string(memarea_t *area,
+ const char *start, const char *end, smartlist_t *out,
+ token_rule_t *table, int flags)
+{
+ const char **s;
+ directory_token_t *tok = NULL;
+ int counts[NIL_];
+ int i;
+ int first_nonannotation;
+ int prev_len = smartlist_len(out);
+ tor_assert(area);
+
+ s = &start;
+ if (!end) {
+ end = start+strlen(start);
+ } else {
+ /* it's only meaningful to check for nuls if we got an end-of-string ptr */
+ if (memchr(start, '\0', end-start)) {
+ log_warn(LD_DIR, "parse error: internal NUL character.");
+ return -1;
+ }
+ }
+ for (i = 0; i < NIL_; ++i)
+ counts[i] = 0;
+
+ SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]);
+
+ while (*s < end && (!tok || tok->tp != EOF_)) {
+ tok = get_next_token(area, s, end, table);
+ if (tok->tp == ERR_) {
+ log_warn(LD_DIR, "parse error: %s", tok->error);
+ token_clear(tok);
+ return -1;
+ }
+ ++counts[tok->tp];
+ smartlist_add(out, tok);
+ *s = eat_whitespace_eos(*s, end);
+ }
+
+ if (flags & TS_NOCHECK)
+ return 0;
+
+ if ((flags & TS_ANNOTATIONS_OK)) {
+ first_nonannotation = -1;
+ for (i = 0; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) {
+ first_nonannotation = i;
+ break;
+ }
+ }
+ if (first_nonannotation < 0) {
+ log_warn(LD_DIR, "parse error: item contains only annotations");
+ return -1;
+ }
+ for (i=first_nonannotation; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
+ log_warn(LD_DIR, "parse error: Annotations mixed with keywords");
+ return -1;
+ }
+ }
+ if ((flags & TS_NO_NEW_ANNOTATIONS)) {
+ if (first_nonannotation != prev_len) {
+ log_warn(LD_DIR, "parse error: Unexpected annotations.");
+ return -1;
+ }
+ }
+ } else {
+ for (i=0; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
+ log_warn(LD_DIR, "parse error: no annotations allowed.");
+ return -1;
+ }
+ }
+ first_nonannotation = 0;
+ }
+ for (i = 0; table[i].t; ++i) {
+ if (counts[table[i].v] < table[i].min_cnt) {
+ log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t);
+ return -1;
+ }
+ if (counts[table[i].v] > table[i].max_cnt) {
+ log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t);
+ return -1;
+ }
+ if (table[i].pos & AT_START) {
+ if (smartlist_len(out) < 1 ||
+ (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) {
+ log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t);
+ return -1;
+ }
+ }
+ if (table[i].pos & AT_END) {
+ if (smartlist_len(out) < 1 ||
+ (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) {
+ log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/** Helper: parse space-separated arguments from the string <b>s</b> ending at
+ * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the
+ * number of parsed elements into the n_args field of <b>tok</b>. Allocate
+ * all storage in <b>area</b>. Return the number of arguments parsed, or
+ * return -1 if there was an insanely high number of arguments. */
+static inline int
+get_token_arguments(memarea_t *area, directory_token_t *tok,
+ const char *s, const char *eol)
+{
+/** Largest number of arguments we'll accept to any token, ever. */
+#define MAX_ARGS 512
+ char *mem = memarea_strndup(area, s, eol-s);
+ char *cp = mem;
+ int j = 0;
+ char *args[MAX_ARGS];
+ while (*cp) {
+ if (j == MAX_ARGS)
+ return -1;
+ args[j++] = cp;
+ cp = (char*)find_whitespace(cp);
+ if (!cp || !*cp)
+ break; /* End of the line. */
+ *cp++ = '\0';
+ cp = (char*)eat_whitespace(cp);
+ }
+ tok->n_args = j;
+ tok->args = memarea_memdup(area, args, j*sizeof(char*));
+ return j;
+#undef MAX_ARGS
+}
+
+/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys
+ * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>.
+ * Return <b>tok</b> on success, or a new ERR_ token if the token didn't
+ * conform to the syntax we wanted.
+ **/
+static inline directory_token_t *
+token_check_object(memarea_t *area, const char *kwd,
+ directory_token_t *tok, obj_syntax o_syn)
+{
+ char ebuf[128];
+ switch (o_syn) {
+ case NO_OBJ:
+ /* No object is allowed for this token. */
+ if (tok->object_body) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ if (tok->key) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ case NEED_OBJ:
+ /* There must be a (non-key) object. */
+ if (!tok->object_body) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ case NEED_KEY_1024: /* There must be a 1024-bit public key. */
+ case NEED_SKEY_1024: /* There must be a 1024-bit private key. */
+ if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits",
+ kwd, crypto_pk_num_bits(tok->key));
+ RET_ERR(ebuf);
+ }
+ /* fall through */
+ case NEED_KEY: /* There must be some kind of key. */
+ if (!tok->key) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ if (o_syn != NEED_SKEY_1024) {
+ if (crypto_pk_key_is_private(tok->key)) {
+ tor_snprintf(ebuf, sizeof(ebuf),
+ "Private key given for %s, which wants a public key", kwd);
+ RET_ERR(ebuf);
+ }
+ } else { /* o_syn == NEED_SKEY_1024 */
+ if (!crypto_pk_key_is_private(tok->key)) {
+ tor_snprintf(ebuf, sizeof(ebuf),
+ "Public key given for %s, which wants a private key", kwd);
+ RET_ERR(ebuf);
+ }
+ }
+ break;
+ case OBJ_OK:
+ /* Anything goes with this token. */
+ break;
+ }
+
+ done_tokenizing:
+ return tok;
+}
+
+/** Helper function: read the next token from *s, advance *s to the end of the
+ * token, and return the parsed token. Parse *<b>s</b> according to the list
+ * of tokens in <b>table</b>.
+ */
+directory_token_t *
+get_next_token(memarea_t *area,
+ const char **s, const char *eos, token_rule_t *table)
+{
+ /** Reject any object at least this big; it is probably an overflow, an
+ * attack, a bug, or some other nonsense. */
+#define MAX_UNPARSED_OBJECT_SIZE (128*1024)
+ /** Reject any line at least this big; it is probably an overflow, an
+ * attack, a bug, or some other nonsense. */
+#define MAX_LINE_LENGTH (128*1024)
+
+ const char *next, *eol, *obstart;
+ size_t obname_len;
+ int i;
+ directory_token_t *tok;
+ obj_syntax o_syn = NO_OBJ;
+ char ebuf[128];
+ const char *kwd = "";
+
+ tor_assert(area);
+ tok = ALLOC_ZERO(sizeof(directory_token_t));
+ tok->tp = ERR_;
+
+ /* Set *s to first token, eol to end-of-line, next to after first token */
+ *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */
+ tor_assert(eos >= *s);
+ eol = memchr(*s, '\n', eos-*s);
+ if (!eol)
+ eol = eos;
+ if (eol - *s > MAX_LINE_LENGTH) {
+ RET_ERR("Line far too long");
+ }
+
+ next = find_whitespace_eos(*s, eol);
+
+ if (!strcmp_len(*s, "opt", next-*s)) {
+ /* Skip past an "opt" at the start of the line. */
+ *s = eat_whitespace_eos_no_nl(next, eol);
+ next = find_whitespace_eos(*s, eol);
+ } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */
+ RET_ERR("Unexpected EOF");
+ }
+
+ /* Search the table for the appropriate entry. (I tried a binary search
+ * instead, but it wasn't any faster.) */
+ for (i = 0; table[i].t ; ++i) {
+ if (!strcmp_len(*s, table[i].t, next-*s)) {
+ /* We've found the keyword. */
+ kwd = table[i].t;
+ tok->tp = table[i].v;
+ o_syn = table[i].os;
+ *s = eat_whitespace_eos_no_nl(next, eol);
+ /* We go ahead whether there are arguments or not, so that tok->args is
+ * always set if we want arguments. */
+ if (table[i].concat_args) {
+ /* The keyword takes the line as a single argument */
+ tok->args = ALLOC(sizeof(char*));
+ tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */
+ tok->n_args = 1;
+ } else {
+ /* This keyword takes multiple arguments. */
+ if (get_token_arguments(area, tok, *s, eol)<0) {
+ tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd);
+ RET_ERR(ebuf);
+ }
+ *s = eol;
+ }
+ if (tok->n_args < table[i].min_args) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd);
+ RET_ERR(ebuf);
+ } else if (tok->n_args > table[i].max_args) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ }
+ }
+
+ if (tok->tp == ERR_) {
+ /* No keyword matched; call it an "K_opt" or "A_unrecognized" */
+ if (*s < eol && **s == '@')
+ tok->tp = A_UNKNOWN_;
+ else
+ tok->tp = K_OPT;
+ tok->args = ALLOC(sizeof(char*));
+ tok->args[0] = STRNDUP(*s, eol-*s);
+ tok->n_args = 1;
+ o_syn = OBJ_OK;
+ }
+
+ /* Check whether there's an object present */
+ *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */
+ tor_assert(eos >= *s);
+ eol = memchr(*s, '\n', eos-*s);
+ if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */
+ goto check_object;
+
+ obstart = *s; /* Set obstart to start of object spec */
+ if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */
+ strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */
+ (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */
+ RET_ERR("Malformed object: bad begin line");
+ }
+ tok->object_type = STRNDUP(*s+11, eol-*s-16);
+ obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */
+ *s = eol+1; /* Set *s to possible start of object data (could be eos) */
+
+ /* Go to the end of the object */
+ next = tor_memstr(*s, eos-*s, "-----END ");
+ if (!next) {
+ RET_ERR("Malformed object: missing object end line");
+ }
+ tor_assert(eos >= next);
+ eol = memchr(next, '\n', eos-next);
+ if (!eol) /* end-of-line marker, or eos if there's no '\n' */
+ eol = eos;
+ /* Validate the ending tag, which should be 9 + NAME + 5 + eol */
+ if ((size_t)(eol-next) != 9+obname_len+5 ||
+ strcmp_len(next+9, tok->object_type, obname_len) ||
+ strcmp_len(eol-5, "-----", 5)) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s",
+ tok->object_type);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ RET_ERR(ebuf);
+ }
+ if (next - *s > MAX_UNPARSED_OBJECT_SIZE)
+ RET_ERR("Couldn't parse object: missing footer or object much too big.");
+
+ if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */
+ tok->key = crypto_pk_new();
+ if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart))
+ RET_ERR("Couldn't parse public key.");
+ } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */
+ tok->key = crypto_pk_new();
+ if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart))
+ RET_ERR("Couldn't parse private key.");
+ } else { /* If it's something else, try to base64-decode it */
+ int r;
+ tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */
+ r = base64_decode(tok->object_body, next-*s, *s, next-*s);
+ if (r<0)
+ RET_ERR("Malformed object: bad base64-encoded data");
+ tok->object_size = r;
+ }
+ *s = eol;
+
+ check_object:
+ tok = token_check_object(area, kwd, tok, o_syn);
+
+ done_tokenizing:
+ return tok;
+
+#undef RET_ERR
+#undef ALLOC
+#undef ALLOC_ZERO
+#undef STRDUP
+#undef STRNDUP
+}
+
+/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail
+ * with an assert if no such keyword is found.
+ */
+directory_token_t *
+find_by_keyword_(smartlist_t *s, directory_keyword keyword,
+ const char *keyword_as_string)
+{
+ directory_token_t *tok = find_opt_by_keyword(s, keyword);
+ if (PREDICT_UNLIKELY(!tok)) {
+ log_err(LD_BUG, "Missing %s [%d] in directory object that should have "
+ "been validated. Internal error.", keyword_as_string, (int)keyword);
+ tor_assert(tok);
+ }
+ return tok;
+}
+
+/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
+ * NULL if no such keyword is found.
+ */
+directory_token_t *
+find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
+{
+ SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t);
+ return NULL;
+}
+
+/** If there are any directory_token_t entries in <b>s</b> whose keyword is
+ * <b>k</b>, return a newly allocated smartlist_t containing all such entries,
+ * in the same order in which they occur in <b>s</b>. Otherwise return
+ * NULL. */
+smartlist_t *
+find_all_by_keyword(const smartlist_t *s, directory_keyword k)
+{
+ smartlist_t *out = NULL;
+ SMARTLIST_FOREACH(s, directory_token_t *, t,
+ if (t->tp == k) {
+ if (!out)
+ out = smartlist_new();
+ smartlist_add(out, t);
+ });
+ return out;
+}
+
diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h
new file mode 100644
index 0000000000..5e5f9f4db6
--- /dev/null
+++ b/src/or/parsecommon.h
@@ -0,0 +1,322 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file parsecommon.h
+ * \brief Header file for parsecommon.c
+ **/
+
+#ifndef TOR_PARSECOMMON_H
+#define TOR_PARSECOMMON_H
+
+#include "container.h"
+#include "crypto.h"
+#include "memarea.h"
+
+/** Enumeration of possible token types. The ones starting with K_ correspond
+* to directory 'keywords'. A_ is for an annotation, R or C is related to
+* hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
+* end-of-file marker, and NIL_ is used to encode not-a-token.
+*/
+typedef enum {
+ K_ACCEPT = 0,
+ K_ACCEPT6,
+ K_DIRECTORY_SIGNATURE,
+ K_RECOMMENDED_SOFTWARE,
+ K_REJECT,
+ K_REJECT6,
+ K_ROUTER,
+ K_SIGNED_DIRECTORY,
+ K_SIGNING_KEY,
+ K_ONION_KEY,
+ K_ONION_KEY_NTOR,
+ K_ROUTER_SIGNATURE,
+ K_PUBLISHED,
+ K_RUNNING_ROUTERS,
+ K_ROUTER_STATUS,
+ K_PLATFORM,
+ K_PROTO,
+ K_OPT,
+ K_BANDWIDTH,
+ K_CONTACT,
+ K_NETWORK_STATUS,
+ K_UPTIME,
+ K_DIR_SIGNING_KEY,
+ K_FAMILY,
+ K_FINGERPRINT,
+ K_HIBERNATING,
+ K_READ_HISTORY,
+ K_WRITE_HISTORY,
+ K_NETWORK_STATUS_VERSION,
+ K_DIR_SOURCE,
+ 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,
+ K_P6,
+ K_R,
+ K_A,
+ K_S,
+ K_V,
+ K_W,
+ K_M,
+ K_EXTRA_INFO,
+ K_EXTRA_INFO_DIGEST,
+ K_CACHES_EXTRA_INFO,
+ K_HIDDEN_SERVICE_DIR,
+ K_ALLOW_SINGLE_HOP_EXITS,
+ K_IPV6_POLICY,
+ K_ROUTER_SIG_ED25519,
+ K_IDENTITY_ED25519,
+ K_MASTER_KEY_ED25519,
+ K_ONION_KEY_CROSSCERT,
+ K_NTOR_ONION_KEY_CROSSCERT,
+
+ K_DIRREQ_END,
+ K_DIRREQ_V2_IPS,
+ K_DIRREQ_V3_IPS,
+ K_DIRREQ_V2_REQS,
+ K_DIRREQ_V3_REQS,
+ K_DIRREQ_V2_SHARE,
+ K_DIRREQ_V3_SHARE,
+ K_DIRREQ_V2_RESP,
+ K_DIRREQ_V3_RESP,
+ K_DIRREQ_V2_DIR,
+ K_DIRREQ_V3_DIR,
+ K_DIRREQ_V2_TUN,
+ K_DIRREQ_V3_TUN,
+ K_ENTRY_END,
+ K_ENTRY_IPS,
+ K_CELL_END,
+ K_CELL_PROCESSED,
+ K_CELL_QUEUED,
+ K_CELL_TIME,
+ K_CELL_CIRCS,
+ K_EXIT_END,
+ K_EXIT_WRITTEN,
+ K_EXIT_READ,
+ K_EXIT_OPENED,
+
+ K_DIR_KEY_CERTIFICATE_VERSION,
+ K_DIR_IDENTITY_KEY,
+ K_DIR_KEY_PUBLISHED,
+ K_DIR_KEY_EXPIRES,
+ K_DIR_KEY_CERTIFICATION,
+ K_DIR_KEY_CROSSCERT,
+ K_DIR_ADDRESS,
+ K_DIR_TUNNELLED,
+
+ K_VOTE_STATUS,
+ K_VALID_AFTER,
+ K_FRESH_UNTIL,
+ K_VALID_UNTIL,
+ K_VOTING_DELAY,
+
+ K_KNOWN_FLAGS,
+ K_PARAMS,
+ K_BW_WEIGHTS,
+ K_VOTE_DIGEST,
+ K_CONSENSUS_DIGEST,
+ K_ADDITIONAL_DIGEST,
+ K_ADDITIONAL_SIGNATURE,
+ K_CONSENSUS_METHODS,
+ 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,
+ A_LAST_LISTED,
+ A_UNKNOWN_,
+
+ R_RENDEZVOUS_SERVICE_DESCRIPTOR,
+ R_VERSION,
+ R_PERMANENT_KEY,
+ R_SECRET_ID_PART,
+ R_PUBLICATION_TIME,
+ R_PROTOCOL_VERSIONS,
+ R_INTRODUCTION_POINTS,
+ R_SIGNATURE,
+
+ R_HS_DESCRIPTOR, /* From version 3, this MUST be generic to all future
+ descriptor versions thus making it R_. */
+ R3_DESC_LIFETIME,
+ R3_DESC_SIGNING_CERT,
+ R3_REVISION_COUNTER,
+ R3_SUPERENCRYPTED,
+ R3_SIGNATURE,
+ R3_CREATE2_FORMATS,
+ R3_INTRO_AUTH_REQUIRED,
+ R3_SINGLE_ONION_SERVICE,
+ R3_INTRODUCTION_POINT,
+ R3_INTRO_ONION_KEY,
+ R3_INTRO_AUTH_KEY,
+ R3_INTRO_ENC_KEY,
+ R3_INTRO_ENC_KEY_CERT,
+ R3_INTRO_LEGACY_KEY,
+ R3_INTRO_LEGACY_KEY_CERT,
+ R3_DESC_AUTH_TYPE,
+ R3_DESC_AUTH_KEY,
+ R3_DESC_AUTH_CLIENT,
+ R3_ENCRYPTED,
+
+ R_IPO_IDENTIFIER,
+ R_IPO_IP_ADDRESS,
+ R_IPO_ONION_PORT,
+ R_IPO_ONION_KEY,
+ R_IPO_SERVICE_KEY,
+
+ C_CLIENT_NAME,
+ C_DESCRIPTOR_COOKIE,
+ C_CLIENT_KEY,
+
+ ERR_,
+ EOF_,
+ NIL_
+} directory_keyword;
+
+/** Structure to hold a single directory token.
+ *
+ * We parse a directory by breaking it into "tokens", each consisting
+ * of a keyword, a line full of arguments, and a binary object. The
+ * arguments and object are both optional, depending on the keyword
+ * type.
+ *
+ * This structure is only allocated in memareas; do not allocate it on
+ * the heap, or token_clear() won't work.
+ */
+typedef struct directory_token_t {
+ directory_keyword tp; /**< Type of the token. */
+ int n_args:30; /**< Number of elements in args */
+ char **args; /**< Array of arguments from keyword line. */
+
+ char *object_type; /**< -----BEGIN [object_type]-----*/
+ size_t object_size; /**< Bytes in object_body */
+ char *object_body; /**< Contents of object, base64-decoded. */
+
+ crypto_pk_t *key; /**< For public keys only. Heap-allocated. */
+
+ char *error; /**< For ERR_ tokens only. */
+} directory_token_t;
+
+/** We use a table of rules to decide how to parse each token type. */
+
+/** Rules for whether the keyword needs an object. */
+typedef enum {
+ NO_OBJ, /**< No object, ever. */
+ NEED_OBJ, /**< Object is required. */
+ NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */
+ NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */
+ NEED_KEY, /**< Object is required, and must be a public key. */
+ OBJ_OK, /**< Object is optional. */
+} obj_syntax;
+
+#define AT_START 1
+#define AT_END 2
+
+#define TS_ANNOTATIONS_OK 1
+#define TS_NOCHECK 2
+#define TS_NO_NEW_ANNOTATIONS 4
+
+/**
+ * @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 }
+/** An item with no restrictions: used for obsolete document types */
+#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
+/** An item with no restrictions on multiplicity or location. */
+#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
+/** An item that must appear exactly once */
+#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 }
+/** An item that must appear exactly once, at the start of the document */
+#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 }
+/** An item that must appear exactly once, at the end of the document */
+#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 }
+/** An item that must appear one or more times */
+#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 }
+/** An item that must appear no more than once */
+#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 }
+/** 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. */
+#define ARGS 0,INT_MAX,0
+/** Argument multiplicity: no arguments. */
+#define NO_ARGS 0,0,0
+/** Argument multiplicity: concatenate all arguments. */
+#define CONCAT_ARGS 1,1,1
+/** Argument multiplicity: at least <b>n</b> arguments. */
+#define GE(n) n,INT_MAX,0
+/** Argument multiplicity: exactly <b>n</b> arguments. */
+#define EQ(n) n,n,0
+/**@}*/
+
+/** Determines the parsing rules for a single token type. */
+typedef struct token_rule_t {
+ /** The string value of the keyword identifying the type of item. */
+ const char *t;
+ /** The corresponding directory_keyword enum. */
+ directory_keyword v;
+ /** Minimum number of arguments for this item */
+ int min_args;
+ /** Maximum number of arguments for this item */
+ int max_args;
+ /** If true, we concatenate all arguments for this item into a single
+ * string. */
+ int concat_args;
+ /** Requirements on object syntax for this item. */
+ obj_syntax os;
+ /** Lowest number of times this item may appear in a document. */
+ int min_cnt;
+ /** Highest number of times this item may appear in a document. */
+ int max_cnt;
+ /** One or more of AT_START/AT_END to limit where the item may appear in a
+ * document. */
+ int pos;
+ /** True iff this token is an annotation. */
+ int is_annotation;
+} token_rule_t;
+
+void token_clear(directory_token_t *tok);
+
+int tokenize_string(memarea_t *area,
+ const char *start, const char *end,
+ smartlist_t *out,
+ token_rule_t *table,
+ int flags);
+directory_token_t *get_next_token(memarea_t *area,
+ const char **s,
+ const char *eos,
+ token_rule_t *table);
+
+directory_token_t *find_by_keyword_(smartlist_t *s,
+ directory_keyword keyword,
+ const char *keyword_str);
+
+#define find_by_keyword(s, keyword) \
+ find_by_keyword_((s), (keyword), #keyword)
+
+directory_token_t *find_opt_by_keyword(smartlist_t *s,
+ directory_keyword keyword);
+smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k);
+
+#endif /* TOR_PARSECOMMON_H */
+
diff --git a/src/or/periodic.c b/src/or/periodic.c
index 057fcf672e..6896b41c86 100644
--- a/src/or/periodic.c
+++ b/src/or/periodic.c
@@ -1,10 +1,14 @@
-/* Copyright (c) 2015-2016, The Tor Project, Inc. */
+/* Copyright (c) 2015-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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/periodic.h b/src/or/periodic.h
index 021bb4ef5c..88d00cc7e9 100644
--- a/src/or/periodic.h
+++ b/src/or/periodic.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Tor Project, Inc. */
+/* Copyright (c) 2015-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_PERIODIC_H
diff --git a/src/or/policies.c b/src/or/policies.c
index 2703d7edef..4c24bfbc32 100644
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@ -1,11 +1,18 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file policies.c
* \brief Code to parse and use address policies and exit policies.
+ *
+ * We have two key kinds of address policy: full and compressed. A full
+ * policy is an array of accept/reject patterns, to be applied in order.
+ * A short policy is simply a list of ports. This module handles both
+ * kinds, including generic functions to apply them to addresses, and
+ * also including code to manage the global policies that we apply to
+ * incoming and outgoing connections.
**/
#define POLICIES_PRIVATE
@@ -13,6 +20,7 @@
#include "or.h"
#include "config.h"
#include "dirserv.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
@@ -274,30 +282,24 @@ 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)))) {
- log_warn(LD_CONFIG, "You have configured tor to use IPv6 "
- "(ClientUseIPv6 1 or UseBridges 1), but "
+ && (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 or prefer IPv6 "
+ "(or UseBridges 1), but "
"ReachableAddresses, ReachableORAddresses, or "
"ReachableDirAddresses reject all IPv6 addresses. "
"Tor will not connect using IPv6.");
@@ -315,10 +317,8 @@ firewall_is_fascist_impl(void)
const or_options_t *options = get_options();
/* Assume every non-bridge relay has an IPv4 address.
* Clients which use bridges may only know the IPv6 address of their
- * bridge. */
- return (options->ClientUseIPv4 == 0
- || (!fascist_firewall_use_ipv6(options)
- && options->UseBridges == 1));
+ * bridge, but they will connect regardless of the ClientUseIPv6 setting. */
+ return options->ClientUseIPv4 == 0;
}
/** Return true iff the firewall options, including ClientUseIPv4 0 and
@@ -425,6 +425,9 @@ fascist_firewall_allows_address(const tor_addr_t *addr,
}
/** Is this client configured to use IPv6?
+ * Returns true if the client might use IPv6 for some of its connections
+ * (including dual-stack and IPv6-only clients), and false if it will never
+ * use IPv6 for any connections.
* Use node_ipv6_or/dir_preferred() when checking a specific node and OR/Dir
* port: it supports bridge client per-node IPv6 preferences.
*/
@@ -432,9 +435,11 @@ int
fascist_firewall_use_ipv6(const or_options_t *options)
{
/* Clients use IPv6 if it's set, or they use bridges, or they don't use
- * IPv4 */
- return (options->ClientUseIPv6 == 1 || options->UseBridges == 1
- || options->ClientUseIPv4 == 0);
+ * IPv4, or they prefer it.
+ * ClientPreferIPv6DirPort is deprecated, but check it anyway. */
+ return (options->ClientUseIPv6 == 1 || options->ClientUseIPv4 == 0 ||
+ options->ClientPreferIPv6ORPort == 1 ||
+ options->ClientPreferIPv6DirPort == 1 || options->UseBridges == 1);
}
/** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and
@@ -618,7 +623,7 @@ fascist_firewall_allows_ri_impl(const routerinfo_t *ri,
pref_ipv6);
}
-/** Like fascist_firewall_allows_rs, but doesn't consult the node. */
+/** Like fascist_firewall_allows_rs, but takes pref_ipv6. */
static int
fascist_firewall_allows_rs_impl(const routerstatus_t *rs,
firewall_connection_t fw_connection,
@@ -636,10 +641,11 @@ fascist_firewall_allows_rs_impl(const routerstatus_t *rs,
}
/** Like fascist_firewall_allows_base(), but takes rs.
- * Consults the corresponding node, then falls back to rs if node is NULL.
- * This should only happen when there's no valid consensus, and rs doesn't
- * correspond to a bridge client's bridge.
- */
+ * When rs is a fake_status from a dir_server_t, it can have a reachable
+ * address, even when the corresponding node does not.
+ * nodes can be missing addresses when there's no consensus (IPv4 and IPv6),
+ * or when there is a microdescriptor consensus, but no microdescriptors
+ * (microdescriptors have IPv6, the microdesc consensus does not). */
int
fascist_firewall_allows_rs(const routerstatus_t *rs,
firewall_connection_t fw_connection, int pref_only)
@@ -648,21 +654,15 @@ fascist_firewall_allows_rs(const routerstatus_t *rs,
return 0;
}
- const node_t *node = node_get_by_id(rs->identity_digest);
-
- if (node) {
- return fascist_firewall_allows_node(node, fw_connection, pref_only);
- } else {
- /* There's no node-specific IPv6 preference, so use the generic IPv6
- * preference instead. */
- const or_options_t *options = get_options();
- int pref_ipv6 = (fw_connection == FIREWALL_OR_CONNECTION
- ? fascist_firewall_prefer_ipv6_orport(options)
- : fascist_firewall_prefer_ipv6_dirport(options));
+ /* We don't have access to the node-specific IPv6 preference, so use the
+ * generic IPv6 preference instead. */
+ const or_options_t *options = get_options();
+ int pref_ipv6 = (fw_connection == FIREWALL_OR_CONNECTION
+ ? fascist_firewall_prefer_ipv6_orport(options)
+ : fascist_firewall_prefer_ipv6_dirport(options));
- return fascist_firewall_allows_rs_impl(rs, fw_connection, pref_only,
- pref_ipv6);
- }
+ return fascist_firewall_allows_rs_impl(rs, fw_connection, pref_only,
+ pref_ipv6);
}
/** Return true iff we think our firewall will let us make a connection to
@@ -742,8 +742,7 @@ fascist_firewall_allows_dir_server(const dir_server_t *ds,
/* A dir_server_t always has a fake_status. As long as it has the same
* addresses/ports in both fake_status and dir_server_t, this works fine.
* (See #17867.)
- * This function relies on fascist_firewall_choose_address_rs looking up the
- * node if it can, because that will get the latest info for the relay. */
+ * fascist_firewall_allows_rs only checks the addresses in fake_status. */
return fascist_firewall_allows_rs(&ds->fake_status, fw_connection,
pref_only);
}
@@ -893,6 +892,33 @@ fascist_firewall_choose_address_ipv4h(uint32_t ipv4h_addr,
pref_ipv6, ap);
}
+/* The microdescriptor consensus has no IPv6 addresses in rs: they are in
+ * the microdescriptors. This means we can't rely on the node's IPv6 address
+ * until its microdescriptor is available (when using microdescs).
+ * But for bridges, rewrite_node_address_for_bridge() updates node->ri with
+ * the configured address, so we can trust bridge addresses.
+ * (Bridges could gain an IPv6 address if their microdescriptor arrives, but
+ * this will never be their preferred address: that is in the config.)
+ * Returns true if the node needs a microdescriptor for its IPv6 address, and
+ * false if the addresses in the node are already up-to-date.
+ */
+static int
+node_awaiting_ipv6(const or_options_t* options, const node_t *node)
+{
+ tor_assert(node);
+
+ /* There's no point waiting for an IPv6 address if we'd never use it */
+ if (!fascist_firewall_use_ipv6(options)) {
+ return 0;
+ }
+
+ /* We are waiting if we_use_microdescriptors_for_circuits() and we have no
+ * md. Bridges have a ri based on their config. They would never use the
+ * address from their md, so there's no need to wait for it. */
+ return (!node->md && we_use_microdescriptors_for_circuits(options) &&
+ !node->ri);
+}
+
/** Like fascist_firewall_choose_address_base(), but takes <b>rs</b>.
* Consults the corresponding node, then falls back to rs if node is NULL.
* This should only happen when there's no valid consensus, and rs doesn't
@@ -909,15 +935,15 @@ fascist_firewall_choose_address_rs(const routerstatus_t *rs,
tor_assert(ap);
+ const or_options_t *options = get_options();
const node_t *node = node_get_by_id(rs->identity_digest);
- if (node) {
+ if (node && !node_awaiting_ipv6(options, node)) {
return fascist_firewall_choose_address_node(node, fw_connection, pref_only,
ap);
} else {
/* There's no node-specific IPv6 preference, so use the generic IPv6
* preference instead. */
- const or_options_t *options = get_options();
int pref_ipv6 = (fw_connection == FIREWALL_OR_CONNECTION
? fascist_firewall_prefer_ipv6_orport(options)
: fascist_firewall_prefer_ipv6_dirport(options));
@@ -951,6 +977,18 @@ fascist_firewall_choose_address_node(const node_t *node,
node_assert_ok(node);
+ /* Calling fascist_firewall_choose_address_node() when the node is missing
+ * IPv6 information breaks IPv6-only clients.
+ * If the node is a hard-coded fallback directory or authority, call
+ * fascist_firewall_choose_address_rs() on the fake (hard-coded) routerstatus
+ * for the node.
+ * If it is not hard-coded, check that the node has a microdescriptor, full
+ * descriptor (routerinfo), or is one of our configured bridges before
+ * calling this function. */
+ if (BUG(node_awaiting_ipv6(get_options(), node))) {
+ return 0;
+ }
+
const int pref_ipv6_node = (fw_connection == FIREWALL_OR_CONNECTION
? node_ipv6_or_preferred(node)
: node_ipv6_dir_preferred(node));
@@ -1090,8 +1128,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 &&
@@ -1210,48 +1248,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. */
@@ -1267,7 +1305,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> */
@@ -1318,7 +1356,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;
}
@@ -1843,10 +1881,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
@@ -1874,13 +1920,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;
@@ -1908,8 +1957,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.
@@ -1922,12 +1977,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);
}
@@ -1962,10 +2019,10 @@ policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr)
}
}
-/** Helper function that adds copies of
- * or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as
- * long as or_options is non-NULL, and the addresses are not
- * tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist.
+/** Helper function that adds copies of or_options->OutboundBindAddresses
+ * to a smartlist as tor_addr_t *, as long as or_options is non-NULL, and
+ * the addresses are not tor_addr_is_null(), by passing them to
+ * policies_add_addr_to_smartlist.
*
* The caller is responsible for freeing all the tor_addr_t* in the smartlist.
*/
@@ -1974,10 +2031,14 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list,
const or_options_t *or_options)
{
if (or_options) {
- policies_copy_addr_to_smartlist(addr_list,
- &or_options->OutboundBindAddressIPv4_);
- policies_copy_addr_to_smartlist(addr_list,
- &or_options->OutboundBindAddressIPv6_);
+ for (int i=0;i<OUTBOUND_ADDR_MAX;i++) {
+ for (int j=0;j<2;j++) {
+ if (!tor_addr_is_null(&or_options->OutboundBindAddresses[i][j])) {
+ policies_copy_addr_to_smartlist(addr_list,
+ &or_options->OutboundBindAddresses[i][j]);
+ }
+ }
+ }
}
}
@@ -1993,10 +2054,11 @@ 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 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
- * it to the list of configured addresses.
+ * If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true:
+ * - if or_options->OutboundBindAddresses[][0] (=IPv4) is not the null
+ * tor_addr_t, add it to the list of configured addresses.
+ * - if or_options->OutboundBindAddresses[][1] (=IPv6) is not the null
+ * tor_addr_t, add it to the list of configured addresses.
*
* If <b>or_options->BridgeRelay</b> is false, append entries of default
* Tor exit policy into <b>result</b> smartlist.
@@ -2036,11 +2098,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);
@@ -2096,8 +2167,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. */
@@ -2131,13 +2204,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)) {
@@ -2150,7 +2226,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
@@ -2277,7 +2353,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.
*/
@@ -2310,53 +2405,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) {
@@ -2377,7 +2521,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);
@@ -2411,7 +2556,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);
@@ -2436,9 +2580,9 @@ policy_summarize(smartlist_t *policy, sa_family_t family)
tor_snprintf(buf, sizeof(buf), "%d-%d", start_prt, AT(i)->prt_max);
if (AT(i)->accepted)
- smartlist_add(accepts, tor_strdup(buf));
+ smartlist_add_strdup(accepts, buf);
else
- smartlist_add(rejects, tor_strdup(buf));
+ smartlist_add_strdup(rejects, buf);
if (last)
break;
@@ -2587,7 +2731,7 @@ parse_short_policy(const char *summary)
}
{
- size_t size = STRUCT_OFFSET(short_policy_t, entries) +
+ size_t size = offsetof(short_policy_t, entries) +
sizeof(short_policy_entry_t)*(n_entries);
result = tor_malloc_zero(size);
@@ -2600,8 +2744,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)
{
@@ -2619,7 +2762,7 @@ write_short_policy(const short_policy_t *policy)
smartlist_add_asprintf(sl, "%d-%d", e->min_port, e->max_port);
}
if (i < policy->n_entries-1)
- smartlist_add(sl, tor_strdup(","));
+ smartlist_add_strdup(sl, ",");
}
answer = smartlist_join_strings(sl, "", 0, NULL);
SMARTLIST_FOREACH(sl, char *, a, tor_free(a));
@@ -2644,7 +2787,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);
@@ -2664,9 +2807,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
@@ -2679,7 +2822,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;
@@ -2820,7 +2963,8 @@ getinfo_helper_policies(control_connection_t *conn,
return -1;
}
- if (!options->ExitPolicyRejectPrivate) {
+ if (!options->ExitPolicyRejectPrivate &&
+ !options->ExitPolicyRejectLocalInterfaces) {
*answer = tor_strdup("");
return 0;
}
@@ -2829,16 +2973,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..ce08d497e9 100644
--- a/src/or/policies.h
+++ b/src/or/policies.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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..1a3e69be10
--- /dev/null
+++ b/src/or/protover.c
@@ -0,0 +1,737 @@
+/* Copyright (c) 2016-2017, 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-2 "
+ "HSDir=1-2 "
+ "HSIntro=3-4 "
+ "HSRend=1-2 "
+ "Link=1-4 "
+ "LinkAuth=1,3 "
+ "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_strdup(chunks, 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_strdup(chunks, " ");
+
+ /* 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 (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..2066aeec72
--- /dev/null
+++ b/src/or/protover.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2016-2017, 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"
+
+/** The protover version number that signifies HSDir support for HSv3 */
+#define PROTOVER_HSDIR_V3 2
+/** The protover version number that signifies HSv3 intro point support */
+#define PROTOVER_HS_INTRO_V3 4
+
+/** 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..e6c325f1b3 100644
--- a/src/or/reasons.c
+++ b/src/or/reasons.c
@@ -1,11 +1,17 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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/reasons.h b/src/or/reasons.h
index 2e12c93728..1cadf4e89e 100644
--- a/src/or/reasons.h
+++ b/src/or/reasons.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/relay.c b/src/or/relay.c
index fb8c8e74d6..18ccc65b80 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -1,13 +1,48 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file relay.c
* \brief Handle relay cell encryption/decryption, plus packaging and
* receiving from circuits, plus queuing on circuits.
+ *
+ * This is a core modules that makes Tor work. It's responsible for
+ * dealing with RELAY cells (the ones that travel more than one hop along a
+ * circuit), by:
+ * <ul>
+ * <li>constructing relays cells,
+ * <li>encrypting relay cells,
+ * <li>decrypting relay cells,
+ * <li>demultiplexing relay cells as they arrive on a connection,
+ * <li>queueing relay cells for retransmission,
+ * <li>or handling relay cells that are for us to receive (as an exit or a
+ * client).
+ * </ul>
+ *
+ * RELAY cells are generated throughout the code at the client or relay side,
+ * using relay_send_command_from_edge() or one of the functions like
+ * connection_edge_send_command() that calls it. Of particular interest is
+ * connection_edge_package_raw_inbuf(), which takes information that has
+ * arrived on an edge connection socket, and packages it as a RELAY_DATA cell
+ * -- this is how information is actually sent across the Tor network. The
+ * cryptography for these functions is handled deep in
+ * circuit_package_relay_cell(), which either adds a single layer of
+ * encryption (if we're an exit), or multiple layers (if we're the origin of
+ * the circuit). After construction and encryption, the RELAY cells are
+ * passed to append_cell_to_circuit_queue(), which queues them for
+ * transmission and tells the circuitmux (see circuitmux.c) that the circuit
+ * is waiting to send something.
+ *
+ * Incoming RELAY cells arrive at circuit_receive_relay_cell(), called from
+ * command.c. There they are decrypted and, if they are for us, are passed to
+ * connection_edge_process_relay_cell(). If they're not for us, they're
+ * re-queued for retransmission again with append_cell_to_circuit_queue().
+ *
+ * The connection_edge_process_relay_cell() function handles all the different
+ * types of relay cells, launching requests or transmitting data as needed.
**/
#define RELAY_PRIVATE
@@ -19,12 +54,14 @@
#include "circuitbuild.h"
#include "circuitlist.h"
#include "circuituse.h"
+#include "compress.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
#include "geoip.h"
+#include "hs_cache.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -38,6 +75,7 @@
#include "routerlist.h"
#include "routerparse.h"
#include "scheduler.h"
+#include "rephist.h"
static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell,
cell_direction_t cell_direction,
@@ -146,18 +184,88 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell)
/** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b>
* (in place).
*
- * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt.
- *
- * Returns 0.
+ * Note that we use the same operation for encrypting and for decrypting.
*/
-static int
-relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in,
- int encrypt_mode)
+static void
+relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in)
{
- (void)encrypt_mode;
crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE);
+}
- return 0;
+/**
+ * Update channel usage state based on the type of relay cell and
+ * circuit properties.
+ *
+ * This is needed to determine if a client channel is being
+ * used for application traffic, and if a relay channel is being
+ * used for multihop circuits and application traffic. The decision
+ * to pad in channelpadding.c depends upon this info (as well as
+ * consensus parameters) to decide what channels to pad.
+ */
+static void
+circuit_update_channel_usage(circuit_t *circ, cell_t *cell)
+{
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ /*
+ * The client state was first set much earlier in
+ * circuit_send_next_onion_skin(), so we can start padding as early as
+ * possible.
+ *
+ * However, if padding turns out to be expensive, we may want to not do
+ * it until actual application traffic starts flowing (which is controlled
+ * via consensus param nf_pad_before_usage).
+ *
+ * So: If we're an origin circuit and we've created a full length circuit,
+ * then any CELL_RELAY cell means application data. Increase the usage
+ * state of the channel to indicate this.
+ *
+ * We want to wait for CELL_RELAY specifically here, so we know that
+ * the channel was definitely being used for data and not for extends.
+ * By default, we pad as soon as a channel has been used for *any*
+ * circuits, so this state is irrelevant to the padding decision in
+ * the default case. However, if padding turns out to be expensive,
+ * we would like the ability to avoid padding until we're absolutely
+ * sure that a channel is used for enough application data to be worth
+ * padding.
+ *
+ * (So it does not matter that CELL_RELAY_EARLY can actually contain
+ * application data. This is only a load reducing option and that edge
+ * case does not matter if we're desperately trying to reduce overhead
+ * anyway. See also consensus parameter nf_pad_before_usage).
+ */
+ if (BUG(!circ->n_chan))
+ return;
+
+ if (circ->n_chan->channel_usage == CHANNEL_USED_FOR_FULL_CIRCS &&
+ cell->command == CELL_RELAY) {
+ circ->n_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC;
+ }
+ } else {
+ /* If we're a relay circuit, the question is more complicated. Basically:
+ * we only want to pad connections that carry multihop (anonymous)
+ * circuits.
+ *
+ * We assume we're more than one hop if either the previous hop
+ * is not a client, or if the previous hop is a client and there's
+ * a next hop. Then, circuit traffic starts at RELAY_EARLY, and
+ * user application traffic starts when we see RELAY cells.
+ */
+ or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
+
+ if (BUG(!or_circ->p_chan))
+ return;
+
+ if (!channel_is_client(or_circ->p_chan) ||
+ (channel_is_client(or_circ->p_chan) && circ->n_chan)) {
+ if (cell->command == CELL_RELAY_EARLY) {
+ if (or_circ->p_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) {
+ or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
+ }
+ } else if (cell->command == CELL_RELAY) {
+ or_circ->p_chan->channel_usage = CHANNEL_USED_FOR_USER_TRAFFIC;
+ }
+ }
+ }
}
/** Receive a relay cell:
@@ -189,10 +297,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
return 0;
if (relay_crypt(circ, cell, cell_direction, &layer_hint, &recognized) < 0) {
- log_warn(LD_BUG,"relay crypt failed. Dropping connection.");
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "relay crypt failed. Dropping connection.");
return -END_CIRC_REASON_INTERNAL;
}
+ circuit_update_channel_usage(circ, cell);
+
if (recognized) {
edge_connection_t *conn = NULL;
@@ -221,8 +332,13 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
log_debug(LD_OR,"Sending to origin.");
if ((reason = connection_edge_process_relay_cell(cell, circ, conn,
layer_hint)) < 0) {
- log_warn(LD_OR,
- "connection_edge_process_relay_cell (at origin) failed.");
+ /* If a client is trying to connect to unknown hidden service port,
+ * END_CIRC_AT_ORIGIN is sent back so we can then close the circuit.
+ * Do not log warn as this is an expected behavior for a service. */
+ if (reason != END_CIRC_AT_ORIGIN) {
+ log_warn(LD_OR,
+ "connection_edge_process_relay_cell (at origin) failed.");
+ }
return reason;
}
}
@@ -255,12 +371,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");
@@ -327,8 +443,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
do { /* Remember: cpath is in forward order, that is, first hop first. */
tor_assert(thishop);
- if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0)
- return -1;
+ /* decrypt one layer */
+ relay_crypt_one_payload(thishop->b_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -345,19 +461,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
log_fn(LOG_PROTOCOL_WARN, LD_OR,
"Incoming cell at client not recognized. Closing.");
return -1;
- } else { /* we're in the middle. Just one crypt. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto,
- cell->payload, 1) < 0)
- return -1;
-// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not "
-// "the client.");
+ } else {
+ /* We're in the middle. Encrypt one layer. */
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload);
}
} else /* cell_direction == CELL_DIRECTION_OUT */ {
- /* we're in the middle. Just one crypt. */
+ /* We're in the middle. Decrypt one layer. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto,
- cell->payload, 0) < 0)
- return -1;
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -403,11 +514,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
/* moving from farthest to nearest hop */
do {
tor_assert(thishop);
- /* XXXX RD This is a bug, right? */
- log_debug(LD_OR,"crypting a layer of the relay cell.");
- if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) {
- return -1;
- }
+ log_debug(LD_OR,"encrypting a layer of the relay cell.");
+ relay_crypt_one_payload(thishop->f_crypto, cell->payload);
thishop = thishop->prev;
} while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev);
@@ -424,8 +532,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
or_circ = TO_OR_CIRCUIT(circ);
chan = or_circ->p_chan;
relay_set_digest(or_circ->p_digest, cell);
- if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0)
- return -1;
+ /* encrypt one layer */
+ relay_crypt_one_payload(or_circ->p_crypto, cell->payload);
}
++stats_n_relay_cells_relayed;
@@ -559,11 +667,11 @@ relay_command_to_string(uint8_t command)
* If you can't send the cell, mark the circuit for close and return -1. Else
* return 0.
*/
-int
-relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
- uint8_t relay_command, const char *payload,
- size_t payload_len, crypt_path_t *cpath_layer,
- const char *filename, int lineno)
+MOCK_IMPL(int,
+relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
+ uint8_t relay_command, const char *payload,
+ size_t payload_len, crypt_path_t *cpath_layer,
+ const char *filename, int lineno))
{
cell_t cell;
relay_header_t rh;
@@ -575,14 +683,14 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
memset(&cell, 0, sizeof(cell_t));
cell.command = CELL_RELAY;
- if (cpath_layer) {
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ tor_assert(cpath_layer);
cell.circ_id = circ->n_circ_id;
cell_direction = CELL_DIRECTION_OUT;
- } else if (! CIRCUIT_IS_ORIGIN(circ)) {
+ } else {
+ tor_assert(! cpath_layer);
cell.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id;
cell_direction = CELL_DIRECTION_IN;
- } else {
- return -1;
}
memset(&rh, 0, sizeof(rh));
@@ -596,6 +704,9 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
log_debug(LD_OR,"delivering %d cell %s.", relay_command,
cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward");
+ if (relay_command == RELAY_COMMAND_DROP)
+ rep_hist_padding_count_write(PADDING_TYPE_DROP);
+
/* If we are sending an END cell and this circuit is used for a tunneled
* directory request, advance its state. */
if (relay_command == RELAY_COMMAND_END && circ->dirreq_id)
@@ -696,6 +807,16 @@ connection_edge_send_command(edge_connection_t *fromconn,
return -1;
}
+#ifdef MEASUREMENTS_21206
+ /* Keep track of the number of RELAY_DATA cells sent for directory
+ * connections. */
+ connection_t *linked_conn = TO_CONN(fromconn)->linked_conn;
+
+ if (linked_conn && linked_conn->type == CONN_TYPE_DIR) {
+ ++(TO_DIR_CONN(linked_conn)->data_cells_sent);
+ }
+#endif
+
return relay_send_command_from_edge(fromconn->stream_id, circ,
relay_command, payload,
payload_len, cpath_layer);
@@ -859,6 +980,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 +1496,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;
}
@@ -1477,6 +1599,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
switch (rh.command) {
case RELAY_COMMAND_DROP:
+ rep_hist_padding_count_read(PADDING_TYPE_DROP);
// log_info(domain,"Got a relay-level padding cell. Dropping.");
return 0;
case RELAY_COMMAND_BEGIN:
@@ -1499,7 +1622,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
"Begin cell for known stream. Dropping.");
return 0;
}
- if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
+ if (rh.command == RELAY_COMMAND_BEGIN_DIR &&
+ circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED) {
/* Assign this circuit and its app-ward OR connection a unique ID,
* so that we can measure download times. The local edge and dir
* connection will be assigned the same ID when they are created
@@ -1549,6 +1673,16 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE),
rh.length, TO_CONN(conn));
+#ifdef MEASUREMENTS_21206
+ /* Count number of RELAY_DATA cells received on a linked directory
+ * connection. */
+ connection_t *linked_conn = TO_CONN(conn)->linked_conn;
+
+ if (linked_conn && linked_conn->type == CONN_TYPE_DIR) {
+ ++(TO_DIR_CONN(linked_conn)->data_cells_received);
+ }
+#endif
+
if (!optimistic_data) {
/* Only send a SENDME if we're not getting optimistic data; otherwise
* a SENDME could arrive before the CONNECTED.
@@ -1876,7 +2010,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));
@@ -1904,9 +2038,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 {
@@ -1921,8 +2055,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,
@@ -2320,14 +2454,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);
}
@@ -2394,7 +2526,7 @@ cell_queues_check_size(void)
{
size_t alloc = cell_queues_get_total_allocation();
alloc += buf_get_total_allocation();
- alloc += tor_zlib_get_total_allocation();
+ alloc += tor_compress_get_total_allocation();
const size_t rend_cache_total = rend_cache_get_total_allocation();
alloc += rend_cache_total;
if (alloc >= get_options()->MaxMemInQueues_low_threshold) {
@@ -2406,9 +2538,7 @@ cell_queues_check_size(void)
if (rend_cache_total > get_options()->MaxMemInQueues / 5) {
const size_t bytes_to_remove =
rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10);
- rend_cache_clean_v2_descs_as_dir(time(NULL), bytes_to_remove);
- alloc -= rend_cache_total;
- alloc += rend_cache_get_total_allocation();
+ alloc -= hs_cache_handle_oom(time(NULL), bytes_to_remove);
}
circuits_handle_oom(alloc);
return 1;
@@ -2456,7 +2586,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;
}
@@ -2525,7 +2655,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;
@@ -2615,6 +2745,15 @@ channel_flush_from_first_active_circuit, (channel_t *chan, int max))
}
/* Circuitmux told us this was active, so it should have cells */
+ if (/*BUG(*/ queue->n == 0 /*)*/) {
+ log_warn(LD_BUG, "Found a supposedly active circuit with no cells "
+ "to send. Trying to recover.");
+ circuitmux_set_num_cells(cmux, circ, 0);
+ if (! circ->marked_for_close)
+ circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL);
+ continue;
+ }
+
tor_assert(queue->n > 0);
/*
@@ -2628,9 +2767,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/relay.h b/src/or/relay.h
index e15551ca51..a160cd5551 100644
--- a/src/or/relay.h
+++ b/src/or/relay.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -20,10 +20,13 @@ int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
void relay_header_pack(uint8_t *dest, const relay_header_t *src);
void relay_header_unpack(relay_header_t *dest, const uint8_t *src);
-int relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
+MOCK_DECL(int,
+relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
uint8_t relay_command, const char *payload,
size_t payload_len, crypt_path_t *cpath_layer,
- const char *filename, int lineno);
+ const char *filename, int lineno));
+/* Indicates to relay_send_command_from_edge() that it is a control cell. */
+#define CONTROL_CELL_ID 0
#define relay_send_command_from_edge(stream_id, circ, relay_command, payload, \
payload_len, cpath_layer) \
relay_send_command_from_edge_((stream_id), (circ), (relay_command), \
diff --git a/src/or/rendcache.c b/src/or/rendcache.c
index f8206cd53b..11b60b36a1 100644
--- a/src/or/rendcache.c
+++ b/src/or/rendcache.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Tor Project, Inc. */
+/* Copyright (c) 2015-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -86,7 +86,7 @@ rend_cache_get_total_allocation(void)
}
/** Decrement the total bytes attributed to the rendezvous cache by n. */
-STATIC void
+void
rend_cache_decrement_allocation(size_t n)
{
static int have_underflowed = 0;
@@ -103,7 +103,7 @@ rend_cache_decrement_allocation(size_t n)
}
/** Increase the total bytes attributed to the rendezvous cache by n. */
-STATIC void
+void
rend_cache_increment_allocation(size_t n)
{
static int have_overflowed = 0;
@@ -462,45 +462,36 @@ rend_cache_intro_failure_note(rend_intro_point_failure_t failure,
}
/** Remove all old v2 descriptors and those for which this hidden service
- * directory is not responsible for any more.
- *
- * If at all possible, remove at least <b>force_remove</b> bytes of data.
- */
-void
-rend_cache_clean_v2_descs_as_dir(time_t now, size_t force_remove)
+ * directory is not responsible for any more. The cutoff is the time limit for
+ * which we want to keep the cache entry. In other words, any entry created
+ * before will be removed. */
+size_t
+rend_cache_clean_v2_descs_as_dir(time_t cutoff)
{
digestmap_iter_t *iter;
- time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW;
- const int LAST_SERVED_CUTOFF_STEP = 1800;
- time_t last_served_cutoff = cutoff;
size_t bytes_removed = 0;
- do {
- for (iter = digestmap_iter_init(rend_cache_v2_dir);
- !digestmap_iter_done(iter); ) {
- const char *key;
- void *val;
- rend_cache_entry_t *ent;
- digestmap_iter_get(iter, &key, &val);
- ent = val;
- if (ent->parsed->timestamp < cutoff ||
- ent->last_served < last_served_cutoff) {
- char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
- base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN);
- log_info(LD_REND, "Removing descriptor with ID '%s' from cache",
- safe_str_client(key_base32));
- bytes_removed += rend_cache_entry_allocation(ent);
- iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter);
- rend_cache_entry_free(ent);
- } else {
- iter = digestmap_iter_next(rend_cache_v2_dir, iter);
- }
+
+ for (iter = digestmap_iter_init(rend_cache_v2_dir);
+ !digestmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ rend_cache_entry_t *ent;
+ digestmap_iter_get(iter, &key, &val);
+ ent = val;
+ if (ent->parsed->timestamp < cutoff) {
+ char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+ base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN);
+ log_info(LD_REND, "Removing descriptor with ID '%s' from cache",
+ safe_str_client(key_base32));
+ bytes_removed += rend_cache_entry_allocation(ent);
+ iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter);
+ rend_cache_entry_free(ent);
+ } else {
+ iter = digestmap_iter_next(rend_cache_v2_dir, iter);
}
+ }
- /* In case we didn't remove enough bytes, advance the cutoff a little. */
- last_served_cutoff += LAST_SERVED_CUTOFF_STEP;
- if (last_served_cutoff > now)
- break;
- } while (bytes_removed < force_remove);
+ return bytes_removed;
}
/** Lookup in the client cache the given service ID <b>query</b> for
@@ -849,6 +840,8 @@ rend_cache_store_v2_desc_as_client(const char *desc,
char want_desc_id[DIGEST_LEN];
rend_cache_entry_t *e;
int retval = -1;
+ rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query);
+
tor_assert(rend_cache);
tor_assert(desc);
tor_assert(desc_id_base32);
@@ -874,11 +867,11 @@ rend_cache_store_v2_desc_as_client(const char *desc,
log_warn(LD_REND, "Couldn't compute service ID.");
goto err;
}
- if (rend_query->onion_address[0] != '\0' &&
- strcmp(rend_query->onion_address, service_id)) {
+ if (rend_data->onion_address[0] != '\0' &&
+ strcmp(rend_data->onion_address, service_id)) {
log_warn(LD_REND, "Received service descriptor for service ID %s; "
"expected descriptor for service ID %s.",
- service_id, safe_str(rend_query->onion_address));
+ service_id, safe_str(rend_data->onion_address));
goto err;
}
if (tor_memneq(desc_id, want_desc_id, DIGEST_LEN)) {
@@ -890,14 +883,14 @@ rend_cache_store_v2_desc_as_client(const char *desc,
/* Decode/decrypt introduction points. */
if (intro_content && intro_size > 0) {
int n_intro_points;
- if (rend_query->auth_type != REND_NO_AUTH &&
- !tor_mem_is_zero(rend_query->descriptor_cookie,
- sizeof(rend_query->descriptor_cookie))) {
+ if (rend_data->auth_type != REND_NO_AUTH &&
+ !tor_mem_is_zero(rend_data->descriptor_cookie,
+ sizeof(rend_data->descriptor_cookie))) {
char *ipos_decrypted = NULL;
size_t ipos_decrypted_size;
if (rend_decrypt_introduction_points(&ipos_decrypted,
&ipos_decrypted_size,
- rend_query->descriptor_cookie,
+ rend_data->descriptor_cookie,
intro_content,
intro_size) < 0) {
log_warn(LD_REND, "Failed to decrypt introduction points. We are "
@@ -915,7 +908,9 @@ rend_cache_store_v2_desc_as_client(const char *desc,
if (n_intro_points <= 0) {
log_warn(LD_REND, "Failed to parse introduction points. Either the "
"service has published a corrupt descriptor or you have "
- "provided invalid authorization data.");
+ "provided invalid authorization data, or (maybe!) the "
+ "server is deliberately serving broken data in an attempt "
+ "to crash you with bug 21018.");
goto err;
} else if (n_intro_points > MAX_INTRO_POINTS) {
log_warn(LD_REND, "Found too many introduction points on a hidden "
@@ -956,25 +951,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..1bd3be2243 100644
--- a/src/or/rendcache.h
+++ b/src/or/rendcache.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Tor Project, Inc. */
+/* Copyright (c) 2015-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -53,10 +53,17 @@ typedef enum {
REND_CACHE_TYPE_SERVICE = 2,
} rend_cache_type_t;
+/* Return maximum lifetime in seconds of a cache entry. */
+static inline time_t
+rend_cache_max_entry_lifetime(void)
+{
+ return REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW;
+}
+
void rend_cache_init(void);
void rend_cache_clean(time_t now, rend_cache_type_t cache_type);
void rend_cache_failure_clean(time_t now);
-void rend_cache_clean_v2_descs_as_dir(time_t now, size_t min_to_remove);
+size_t rend_cache_clean_v2_descs_as_dir(time_t cutoff);
void rend_cache_purge(void);
void rend_cache_free_all(void);
int rend_cache_lookup_entry(const char *query, int version,
@@ -77,6 +84,8 @@ void rend_cache_intro_failure_note(rend_intro_point_failure_t failure,
const uint8_t *identity,
const char *service_id);
void rend_cache_failure_purge(void);
+void rend_cache_decrement_allocation(size_t n);
+void rend_cache_increment_allocation(size_t n);
#ifdef RENDCACHE_PRIVATE
@@ -89,8 +98,6 @@ STATIC int cache_failure_intro_lookup(const uint8_t *identity,
const char *service_id,
rend_cache_failure_intro_t
**intro_entry);
-STATIC void rend_cache_decrement_allocation(size_t n);
-STATIC void rend_cache_increment_allocation(size_t n);
STATIC rend_cache_failure_intro_t *rend_cache_failure_intro_entry_new(
rend_intro_point_failure_t failure);
STATIC rend_cache_failure_t *rend_cache_failure_entry_new(void);
@@ -102,6 +109,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..e47e1ef639 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -1,5 +1,5 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -16,6 +16,8 @@
#include "connection.h"
#include "connection_edge.h"
#include "directory.h"
+#include "hs_common.h"
+#include "hs_circuit.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -104,7 +106,7 @@ rend_client_reextend_intro_circuit(origin_circuit_t *circ)
if (!extend_info) {
log_warn(LD_REND,
"No usable introduction points left for %s. Closing.",
- safe_str_client(circ->rend_data->onion_address));
+ safe_str_client(rend_data_get_address(circ->rend_data)));
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return -1;
}
@@ -134,6 +136,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];
@@ -143,20 +146,19 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
off_t dh_offset;
crypto_pk_t *intro_key = NULL;
int status = 0;
+ const char *onion_address;
tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY);
tor_assert(introcirc->rend_data);
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
+ tor_assert(!rend_cmp_service_ids(rend_data_get_address(introcirc->rend_data),
+ rend_data_get_address(rendcirc->rend_data)));
+ assert_circ_anonymity_ok(introcirc, options);
+ assert_circ_anonymity_ok(rendcirc, options);
+ onion_address = rend_data_get_address(introcirc->rend_data);
- r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
- &entry);
+ r = rend_cache_lookup_entry(onion_address, -1, &entry);
/* An invalid onion address is not possible else we have a big issue. */
tor_assert(r != -EINVAL);
if (r < 0 || !rend_client_any_intro_points_usable(entry)) {
@@ -165,14 +167,13 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
log_info(LD_REND,
"query %s didn't have valid rend desc in cache. "
"Refetching descriptor.",
- safe_str_client(introcirc->rend_data->onion_address));
+ safe_str_client(onion_address));
rend_client_refetch_v2_renddesc(introcirc->rend_data);
{
connection_t *conn;
while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
- AP_CONN_STATE_CIRCUIT_WAIT,
- introcirc->rend_data->onion_address))) {
+ AP_CONN_STATE_CIRCUIT_WAIT, onion_address))) {
connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
conn->state = AP_CONN_STATE_RENDDESC_WAIT;
}
@@ -196,7 +197,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
log_info(LD_REND, "Could not find intro key for %s at %s; we "
"have a v2 rend desc with %d intro points. "
"Trying a different intro point...",
- safe_str_client(introcirc->rend_data->onion_address),
+ safe_str_client(onion_address),
safe_str_client(extend_info_describe(
introcirc->build_state->chosen_exit)),
smartlist_len(entry->parsed->intro_nodes));
@@ -236,11 +237,12 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
/* If version is 3, write (optional) auth data and timestamp. */
if (entry->parsed->protocols & (1<<3)) {
tmp[0] = 3; /* version 3 of the cell format */
- tmp[1] = (uint8_t)introcirc->rend_data->auth_type; /* auth type, if any */
+ /* auth type, if any */
+ tmp[1] = (uint8_t) TO_REND_DATA_V2(introcirc->rend_data)->auth_type;
v3_shift = 1;
- if (introcirc->rend_data->auth_type != REND_NO_AUTH) {
+ if (tmp[1] != REND_NO_AUTH) {
set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
- memcpy(tmp+4, introcirc->rend_data->descriptor_cookie,
+ memcpy(tmp+4, TO_REND_DATA_V2(introcirc->rend_data)->descriptor_cookie,
REND_DESC_COOKIE_LEN);
v3_shift += 2+REND_DESC_COOKIE_LEN;
}
@@ -284,10 +286,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
goto perm_err;
}
- note_crypto_pk_op(REND_CLIENT);
- /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
+ /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg,
* to avoid buffer overflows? */
- r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
+ r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
sizeof(payload)-DIGEST_LEN,
tmp,
(int)(dh_offset+DH_KEY_LEN),
@@ -360,7 +361,7 @@ rend_client_rendcirc_has_opened(origin_circuit_t *circ)
* Called to close other intro circuits we launched in parallel.
*/
static void
-rend_client_close_other_intros(const char *onion_address)
+rend_client_close_other_intros(const uint8_t *rend_pk_digest)
{
/* abort parallel intro circs, if any */
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, c) {
@@ -369,8 +370,7 @@ rend_client_close_other_intros(const char *onion_address)
!c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c);
if (oc->rend_data &&
- !rend_cmp_service_ids(onion_address,
- oc->rend_data->onion_address)) {
+ rend_circuit_pk_digest_eq(oc, rend_pk_digest)) {
log_info(LD_REND|LD_CIRC, "Closing introduction circuit %d that we "
"built in parallel (Purpose %d).", oc->global_identifier,
c->purpose);
@@ -387,6 +387,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 +399,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 +416,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
@@ -434,7 +432,8 @@ rend_client_introduction_acked(origin_circuit_t *circ,
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
/* close any other intros launched in parallel */
- rend_client_close_other_intros(circ->rend_data->onion_address);
+ rend_client_close_other_intros(rend_data_get_pk_digest(circ->rend_data,
+ NULL));
} else {
/* It's a NAK; the introduction point didn't relay our request. */
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
@@ -443,7 +442,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
* If none remain, refetch the service descriptor.
*/
log_info(LD_REND, "Got nack for %s from %s...",
- safe_str_client(circ->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(circ->rend_data)),
safe_str_client(extend_info_describe(circ->build_state->chosen_exit)));
if (rend_client_report_intro_point_failure(circ->build_state->chosen_exit,
circ->rend_data,
@@ -469,6 +468,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 +526,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 +548,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 +588,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 +651,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;
@@ -680,13 +696,15 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32)
* in the case that no hidden service directory is left to ask for the
* descriptor, return 0, and in case of a failure -1. */
static int
-directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
+directory_get_from_hs_dir(const char *desc_id,
+ const rend_data_t *rend_query,
routerstatus_t *rs_hsdir)
{
routerstatus_t *hs_dir = rs_hsdir;
char *hsdir_fp;
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+ const rend_data_v2_t *rend_data;
#ifdef ENABLE_TOR2WEB_MODE
const int tor2web_mode = get_options()->Tor2webMode;
const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS;
@@ -695,6 +713,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
#endif
tor_assert(desc_id);
+ tor_assert(rend_query);
+ rend_data = TO_REND_DATA_V2(rend_query);
base32_encode(desc_id_base32, sizeof(desc_id_base32),
desc_id, DIGEST_LEN);
@@ -704,6 +724,9 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
hs_dir = pick_hsdir(desc_id, desc_id_base32);
if (!hs_dir) {
/* No suitable hs dir can be found, stop right now. */
+ control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR");
+ control_event_hs_descriptor_content(rend_data_get_address(rend_query),
+ desc_id_base32, NULL, NULL);
return 0;
}
}
@@ -717,12 +740,16 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
/* Encode descriptor cookie for logging purposes. Also, if the cookie is
* malformed, no fetch is triggered thus this needs to be done before the
* fetch request. */
- if (rend_query->auth_type != REND_NO_AUTH) {
+ if (rend_data->auth_type != REND_NO_AUTH) {
if (base64_encode(descriptor_cookie_base64,
sizeof(descriptor_cookie_base64),
- rend_query->descriptor_cookie, REND_DESC_COOKIE_LEN,
+ rend_data->descriptor_cookie,
+ REND_DESC_COOKIE_LEN,
0)<0) {
log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
+ control_event_hs_descriptor_failed(rend_query, hsdir_fp, "BAD_DESC");
+ control_event_hs_descriptor_content(rend_data_get_address(rend_query),
+ desc_id_base32, hsdir_fp, NULL);
return 0;
}
/* Remove == signs. */
@@ -735,20 +762,22 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
/* Send fetch request. (Pass query and possibly descriptor cookie so that
* they can be written to the directory connection and be referred to when
* the response arrives. */
- directory_initiate_command_routerstatus_rend(hs_dir,
- DIR_PURPOSE_FETCH_RENDDESC_V2,
- ROUTER_PURPOSE_GENERAL,
- how_to_fetch,
- desc_id_base32,
- NULL, 0, 0,
- rend_query);
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_FETCH_RENDDESC_V2);
+ directory_request_set_routerstatus(req, hs_dir);
+ directory_request_set_indirection(req, how_to_fetch);
+ directory_request_set_resource(req, desc_id_base32);
+ directory_request_set_rend_query(req, rend_query);
+ directory_initiate_request(req);
+ directory_request_free(req);
+
log_info(LD_REND, "Sending fetch request for v2 descriptor for "
"service '%s' with descriptor ID '%s', auth type %d, "
"and descriptor cookie '%s' to hidden service "
"directory %s",
- rend_query->onion_address, desc_id_base32,
- rend_query->auth_type,
- (rend_query->auth_type == REND_NO_AUTH ? "[none]" :
+ rend_data->onion_address, desc_id_base32,
+ rend_data->auth_type,
+ (rend_data->auth_type == REND_NO_AUTH ? "[none]" :
escaped_safe_str_client(descriptor_cookie_base64)),
routerstatus_describe(hs_dir));
control_event_hs_descriptor_requested(rend_query,
@@ -763,8 +792,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
* On success, 1 is returned. If no hidden service is left to ask, return 0.
* On error, -1 is returned. */
static int
-fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query,
- smartlist_t *hsdirs)
+fetch_v2_desc_by_descid(const char *desc_id,
+ const rend_data_t *rend_query, smartlist_t *hsdirs)
{
int ret;
@@ -797,13 +826,12 @@ fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query,
* On success, 1 is returned. If no hidden service is left to ask, return 0.
* On error, -1 is returned. */
static int
-fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
+fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs)
{
char descriptor_id[DIGEST_LEN];
int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
int i, tries_left, ret;
-
- tor_assert(query);
+ rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query);
/* Randomly iterate over the replicas until a descriptor can be fetched
* from one of the consecutive nodes, or no options are left. */
@@ -813,13 +841,14 @@ 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];
-
- ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address,
- query->auth_type == REND_STEALTH_AUTH ?
- query->descriptor_cookie : NULL,
+ 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,
+ rend_data->onion_address,
+ rend_data->auth_type == REND_STEALTH_AUTH ?
+ rend_data->descriptor_cookie : NULL,
time(NULL), chosen_replica);
if (ret < 0) {
/* Normally, on failure the descriptor_id is untouched but let's be
@@ -827,18 +856,18 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
goto end;
}
- if (tor_memcmp(descriptor_id, query->descriptor_id[chosen_replica],
+ if (tor_memcmp(descriptor_id, rend_data->descriptor_id[chosen_replica],
sizeof(descriptor_id)) != 0) {
/* Not equal from what we currently have so purge the last hid serv
* request cache and update the descriptor ID with the new value. */
purge_hid_serv_from_last_hid_serv_requests(
- query->descriptor_id[chosen_replica]);
- memcpy(query->descriptor_id[chosen_replica], descriptor_id,
- sizeof(query->descriptor_id[chosen_replica]));
+ rend_data->descriptor_id[chosen_replica]);
+ memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id,
+ sizeof(rend_data->descriptor_id[chosen_replica]));
}
/* Trigger the fetch with the computed descriptor ID. */
- ret = fetch_v2_desc_by_descid(descriptor_id, query, hsdirs);
+ ret = fetch_v2_desc_by_descid(descriptor_id, rend_query, hsdirs);
if (ret != 0) {
/* Either on success or failure, as long as we tried a fetch we are
* done here. */
@@ -866,16 +895,23 @@ int
rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs)
{
int ret;
+ rend_data_v2_t *rend_data;
+ const char *onion_address;
tor_assert(query);
+ /* Get the version 2 data structure of the query. */
+ rend_data = TO_REND_DATA_V2(query);
+ onion_address = rend_data_get_address(query);
+
/* Depending on what's available in the rend data query object, we will
* trigger a fetch by HS address or using a descriptor ID. */
- if (query->onion_address[0] != '\0') {
+ if (onion_address[0] != '\0') {
ret = fetch_v2_desc_by_addr(query, hsdirs);
- } else if (!tor_digest_is_zero(query->desc_id_fetch)) {
- ret = fetch_v2_desc_by_descid(query->desc_id_fetch, query, hsdirs);
+ } else if (!tor_digest_is_zero(rend_data->desc_id_fetch)) {
+ ret = fetch_v2_desc_by_descid(rend_data->desc_id_fetch, query,
+ hsdirs);
} else {
/* Query data is invalid. */
ret = -1;
@@ -893,23 +929,24 @@ void
rend_client_refetch_v2_renddesc(rend_data_t *rend_query)
{
rend_cache_entry_t *e = NULL;
+ const char *onion_address = rend_data_get_address(rend_query);
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 &&
+ if (rend_cache_lookup_entry(onion_address, -1, &e) == 0 &&
rend_client_any_intro_points_usable(e)) {
log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
"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));
+ safe_str_client(onion_address));
rend_client_fetch_v2_desc(rend_query, NULL);
/* We don't need to look the error code because either on failure or
@@ -945,7 +982,7 @@ rend_client_cancel_descriptor_fetches(void)
} else {
log_debug(LD_REND, "Marking for close dir conn fetching "
"rendezvous descriptor for service %s",
- safe_str(rd->onion_address));
+ safe_str(rend_data_get_address(rd)));
}
connection_mark_for_close(conn);
}
@@ -975,25 +1012,26 @@ rend_client_cancel_descriptor_fetches(void)
*/
int
rend_client_report_intro_point_failure(extend_info_t *failed_intro,
- rend_data_t *rend_query,
+ rend_data_t *rend_data,
unsigned int failure_type)
{
int i, r;
rend_cache_entry_t *ent;
connection_t *conn;
+ const char *onion_address = rend_data_get_address(rend_data);
- r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
+ r = rend_cache_lookup_entry(onion_address, -1, &ent);
if (r < 0) {
/* Either invalid onion address or cache entry not found. */
switch (-r) {
case EINVAL:
log_warn(LD_BUG, "Malformed service ID %s.",
- escaped_safe_str_client(rend_query->onion_address));
+ escaped_safe_str_client(onion_address));
return -1;
case ENOENT:
log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
- escaped_safe_str_client(rend_query->onion_address));
- rend_client_refetch_v2_renddesc(rend_query);
+ escaped_safe_str_client(onion_address));
+ rend_client_refetch_v2_renddesc(rend_data);
return 0;
default:
log_warn(LD_BUG, "Unknown cache lookup returned code: %d", r);
@@ -1017,7 +1055,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
case INTRO_POINT_FAILURE_GENERIC:
rend_cache_intro_failure_note(failure_type,
(uint8_t *)failed_intro->identity_digest,
- rend_query->onion_address);
+ onion_address);
rend_intro_point_free(intro);
smartlist_del(ent->parsed->intro_nodes, i);
break;
@@ -1035,8 +1073,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
if (zap_intro_point) {
rend_cache_intro_failure_note(
failure_type,
- (uint8_t *) failed_intro->identity_digest,
- rend_query->onion_address);
+ (uint8_t *) failed_intro->identity_digest, onion_address);
rend_intro_point_free(intro);
smartlist_del(ent->parsed->intro_nodes, i);
}
@@ -1050,14 +1087,14 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
if (! rend_client_any_intro_points_usable(ent)) {
log_info(LD_REND,
"No more intro points remain for %s. Re-fetching descriptor.",
- escaped_safe_str_client(rend_query->onion_address));
- rend_client_refetch_v2_renddesc(rend_query);
+ escaped_safe_str_client(onion_address));
+ rend_client_refetch_v2_renddesc(rend_data);
/* move all pending streams back to renddesc_wait */
/* NOTE: We can now do this faster, if we use pending_entry_connections */
while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
AP_CONN_STATE_CIRCUIT_WAIT,
- rend_query->onion_address))) {
+ onion_address))) {
connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
conn->state = AP_CONN_STATE_RENDDESC_WAIT;
}
@@ -1066,7 +1103,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
}
log_info(LD_REND,"%d options left for %s.",
smartlist_len(ent->parsed->intro_nodes),
- escaped_safe_str_client(rend_query->onion_address));
+ escaped_safe_str_client(onion_address));
return 1;
}
@@ -1099,7 +1136,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
@@ -1113,9 +1150,6 @@ int
rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
size_t request_len)
{
- crypt_path_t *hop;
- char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
-
if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
|| !circ->build_state->pending_final_cpath) {
@@ -1133,55 +1167,13 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
- /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
- * handshake...*/
- tor_assert(circ->build_state);
- tor_assert(circ->build_state->pending_final_cpath);
- hop = circ->build_state->pending_final_cpath;
- tor_assert(hop->rend_dh_handshake_state);
- if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
- hop->rend_dh_handshake_state, (char*)request,
- DH_KEY_LEN,
- keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
- log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
goto err;
}
- /* ... and set up cpath. */
- if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0)
- goto err;
-
- /* Check whether the digest is right... */
- if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) {
- log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
- goto err;
- }
-
- crypto_dh_free(hop->rend_dh_handshake_state);
- hop->rend_dh_handshake_state = NULL;
-
- /* All is well. Extend the circuit. */
- circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED);
- hop->state = CPATH_STATE_OPEN;
- /* set the windows to default. these are the windows
- * that the client thinks the service has.
- */
- hop->package_window = circuit_initial_package_window();
- hop->deliver_window = CIRCWINDOW_START;
-
- /* Now that this circuit has finished connecting to its destination,
- * make sure circuit_get_open_circ_or_launch is willing to return it
- * so we can actually use it. */
- circ->hs_circ_has_timed_out = 0;
-
- onion_append_to_cpath(&circ->cpath, hop);
- circ->build_state->pending_final_cpath = NULL; /* prevent double-free */
-
- circuit_try_attaching_streams(circ);
-
- memwipe(keys, 0, sizeof(keys));
return 0;
+
err:
- memwipe(keys, 0, sizeof(keys));
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
return -1;
}
@@ -1207,10 +1199,11 @@ rend_client_desc_trynow(const char *query)
rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
if (!rend_data)
continue;
- if (rend_cmp_service_ids(query, rend_data->onion_address))
+ const char *onion_address = rend_data_get_address(rend_data);
+ if (rend_cmp_service_ids(query, onion_address))
continue;
assert_connection_ok(base_conn, now);
- if (rend_cache_lookup_entry(rend_data->onion_address, -1,
+ if (rend_cache_lookup_entry(onion_address, -1,
&entry) == 0 &&
rend_client_any_intro_points_usable(entry)) {
/* either this fetch worked, or it failed but there was a
@@ -1245,11 +1238,12 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
{
unsigned int have_onion = 0;
rend_cache_entry_t *cache_entry = NULL;
+ const char *onion_address = rend_data_get_address(rend_data);
+ rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data);
- if (*rend_data->onion_address != '\0') {
+ if (onion_address[0] != '\0') {
/* Ignore return value; we find an entry, or we don't. */
- (void) rend_cache_lookup_entry(rend_data->onion_address, -1,
- &cache_entry);
+ (void) rend_cache_lookup_entry(onion_address, -1, &cache_entry);
have_onion = 1;
}
@@ -1263,17 +1257,17 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
/* Remove the HS's entries in last_hid_serv_requests. */
if (have_onion) {
unsigned int replica;
- for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id);
+ for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id);
replica++) {
- const char *desc_id = rend_data->descriptor_id[replica];
+ const char *desc_id = rend_data_v2->descriptor_id[replica];
purge_hid_serv_from_last_hid_serv_requests(desc_id);
}
log_info(LD_REND, "Connection attempt for %s has ended; "
"cleaning up temporary state.",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(onion_address));
} else {
/* We only have an ID for a fetch. Probably used by HSFETCH. */
- purge_hid_serv_from_last_hid_serv_requests(rend_data->desc_id_fetch);
+ purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch);
}
}
@@ -1287,12 +1281,13 @@ rend_client_get_random_intro(const rend_data_t *rend_query)
int ret;
extend_info_t *result;
rend_cache_entry_t *entry;
+ const char *onion_address = rend_data_get_address(rend_query);
- ret = rend_cache_lookup_entry(rend_query->onion_address, -1, &entry);
+ ret = rend_cache_lookup_entry(onion_address, -1, &entry);
if (ret < 0 || !rend_client_any_intro_points_usable(entry)) {
log_warn(LD_REND,
"Query '%s' didn't have valid rend desc in cache. Failing.",
- safe_str_client(rend_query->onion_address));
+ safe_str_client(onion_address));
/* XXX: Should we refetch the descriptor here if the IPs are not usable
* anymore ?. */
return NULL;
@@ -1351,40 +1346,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 +1441,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 +1473,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);
+ 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;
}
- /* 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);
- 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 +1502,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..ff0f4084fd 100644
--- a/src/or/rendclient.h
+++ b/src/or/rendclient.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -27,7 +27,7 @@ void rend_client_cancel_descriptor_fetches(void);
void rend_client_purge_last_hid_serv_requests(void);
int rend_client_report_intro_point_failure(extend_info_t *failed_intro,
- rend_data_t *rend_query,
+ rend_data_t *rend_data,
unsigned int failure_type);
int rend_client_rendezvous_acked(origin_circuit_t *circ,
@@ -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 b927486b61..8b555a3164 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -1,5 +1,5 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -8,13 +8,17 @@
* introducers, services, clients, and rendezvous points.
**/
+#define RENDCOMMON_PRIVATE
+
#include "or.h"
#include "circuitbuild.h"
#include "config.h"
#include "control.h"
+#include "hs_common.h"
#include "rendclient.h"
#include "rendcommon.h"
#include "rendmid.h"
+#include "hs_intropoint.h"
#include "rendservice.h"
#include "rephist.h"
#include "router.h"
@@ -393,7 +397,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out,
/** Attempt to parse the given <b>desc_str</b> and return true if this
* succeeds, false otherwise. */
-static int
+STATIC int
rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc)
{
rend_service_descriptor_t *test_parsed = NULL;
@@ -720,6 +724,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
@@ -745,7 +765,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
switch (command) {
case RELAY_COMMAND_ESTABLISH_INTRO:
if (or_circ)
- r = rend_mid_establish_intro(or_circ,payload,length);
+ r = hs_intro_received_establish_intro(or_circ,payload,length);
break;
case RELAY_COMMAND_ESTABLISH_RENDEZVOUS:
if (or_circ)
@@ -753,11 +773,11 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
break;
case RELAY_COMMAND_INTRODUCE1:
if (or_circ)
- r = rend_mid_introduce(or_circ,payload,length);
+ r = hs_intro_received_introduce1(or_circ,payload,length);
break;
case RELAY_COMMAND_INTRODUCE2:
if (origin_circ)
- r = rend_service_receive_introduction(origin_circ,payload,length);
+ r = hs_service_receive_introduce2(origin_circ,payload,length);
break;
case RELAY_COMMAND_INTRODUCE_ACK:
if (origin_circ)
@@ -773,7 +793,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
break;
case RELAY_COMMAND_INTRO_ESTABLISHED:
if (origin_circ)
- r = rend_service_intro_established(origin_circ,payload,length);
+ r = hs_service_receive_intro_established(origin_circ,payload,length);
break;
case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
if (origin_circ)
@@ -788,124 +808,6 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
command);
}
-/** Allocate and return a new rend_data_t with the same
- * contents as <b>query</b>. */
-rend_data_t *
-rend_data_dup(const rend_data_t *data)
-{
- rend_data_t *data_dup;
- tor_assert(data);
- data_dup = tor_memdup(data, sizeof(rend_data_t));
- data_dup->hsdirs_fp = smartlist_new();
- SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp,
- smartlist_add(data_dup->hsdirs_fp,
- tor_memdup(fp, DIGEST_LEN)));
- return data_dup;
-}
-
-/** Compute descriptor ID for each replicas and save them. A valid onion
- * address must be present in the <b>rend_data</b>.
- *
- * Return 0 on success else -1. */
-static int
-compute_desc_id(rend_data_t *rend_data)
-{
- int ret = 0;
- unsigned replica;
- time_t now = time(NULL);
-
- tor_assert(rend_data);
-
- /* Compute descriptor ID for each replicas. */
- for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id);
- replica++) {
- ret = rend_compute_v2_desc_id(rend_data->descriptor_id[replica],
- rend_data->onion_address,
- rend_data->descriptor_cookie,
- now, replica);
- if (ret < 0) {
- goto end;
- }
- }
-
- end:
- return ret;
-}
-
-/** Allocate and initialize a rend_data_t object for a service using the
- * given arguments. Only the <b>onion_address</b> is not optional.
- *
- * Return a valid rend_data_t pointer. */
-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)
-{
- rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data));
-
- /* We need at least one else the call is wrong. */
- tor_assert(onion_address != NULL);
-
- if (pk_digest) {
- memcpy(rend_data->rend_pk_digest, pk_digest,
- sizeof(rend_data->rend_pk_digest));
- }
- if (cookie) {
- memcpy(rend_data->rend_cookie, cookie,
- sizeof(rend_data->rend_cookie));
- }
-
- strlcpy(rend_data->onion_address, onion_address,
- sizeof(rend_data->onion_address));
- rend_data->auth_type = auth_type;
- /* Won't be used but still need to initialize it for rend_data dup and
- * free. */
- rend_data->hsdirs_fp = smartlist_new();
-
- return rend_data;
-}
-
-/** Allocate and initialize a rend_data_t object for a client request using
- * the given arguments. Either an onion address or a descriptor ID is
- * needed. Both can be given but only the onion address will be used to make
- * the descriptor fetch.
- *
- * Return a valid rend_data_t pointer or NULL on error meaning the
- * descriptor IDs couldn't be computed from the given data. */
-rend_data_t *
-rend_data_client_create(const char *onion_address, const char *desc_id,
- const char *cookie, rend_auth_type_t auth_type)
-{
- rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data));
-
- /* We need at least one else the call is wrong. */
- tor_assert(onion_address != NULL || desc_id != NULL);
-
- if (cookie) {
- memcpy(rend_data->descriptor_cookie, cookie,
- sizeof(rend_data->descriptor_cookie));
- }
- if (desc_id) {
- memcpy(rend_data->desc_id_fetch, desc_id,
- sizeof(rend_data->desc_id_fetch));
- }
- if (onion_address) {
- strlcpy(rend_data->onion_address, onion_address,
- sizeof(rend_data->onion_address));
- if (compute_desc_id(rend_data) < 0) {
- goto error;
- }
- }
-
- rend_data->auth_type = auth_type;
- rend_data->hsdirs_fp = smartlist_new();
-
- return rend_data;
-
- error:
- rend_data_free(rend_data);
- return NULL;
-}
-
/** Determine the routers that are responsible for <b>id</b> (binary) and
* add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>.
* Return -1 if we're returning an empty smartlist, else return 0.
@@ -941,3 +843,191 @@ 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));
+ }
+}
+
+/* Return 1 iff the given <b>digest</b> of a permenanent hidden service key is
+ * equal to the digest in the origin circuit <b>ocirc</b> of its rend data .
+ * If the rend data doesn't exist, 0 is returned. This function is agnostic to
+ * the rend data version. */
+int
+rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc,
+ const uint8_t *digest)
+{
+ size_t rend_pk_digest_len;
+ const uint8_t *rend_pk_digest;
+
+ tor_assert(ocirc);
+ tor_assert(digest);
+
+ if (ocirc->rend_data == NULL) {
+ goto no_match;
+ }
+
+ rend_pk_digest = rend_data_get_pk_digest(ocirc->rend_data,
+ &rend_pk_digest_len);
+ if (tor_memeq(rend_pk_digest, digest, rend_pk_digest_len)) {
+ goto match;
+ }
+ no_match:
+ return 0;
+ match:
+ return 1;
+}
+
diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h
index d67552e405..292f9277e8 100644
--- a/src/or/rendcommon.h
+++ b/src/or/rendcommon.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -18,19 +18,6 @@ typedef enum rend_intro_point_failure_t {
INTRO_POINT_FAILURE_UNREACHABLE = 2,
} rend_intro_point_failure_t;
-/** Free all storage associated with <b>data</b> */
-static inline void
-rend_data_free(rend_data_t *data)
-{
- if (!data) {
- return;
- }
- /* Cleanup the HSDir identity digest. */
- SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d));
- smartlist_free(data->hsdirs_fp);
- tor_free(data);
-}
-
int rend_cmp_service_ids(const char *one, const char *two);
void rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
@@ -45,6 +32,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,
@@ -59,14 +47,28 @@ void rend_get_descriptor_id_bytes(char *descriptor_id_out,
int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
const char *id);
-rend_data_t *rend_data_dup(const rend_data_t *data);
-rend_data_t *rend_data_client_create(const char *onion_address,
- const char *desc_id,
- const char *cookie,
- rend_auth_type_t auth_type);
-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);
+int rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc,
+ const uint8_t *digest);
+
+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);
+
+#ifdef RENDCOMMON_PRIVATE
+
+STATIC int
+rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc);
+
+#endif
+
#endif
diff --git a/src/or/rendmid.c b/src/or/rendmid.c
index a33ad92966..66d2f93113 100644
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@ -1,5 +1,5 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -11,16 +11,19 @@
#include "circuitlist.h"
#include "circuituse.h"
#include "config.h"
+#include "crypto.h"
#include "relay.h"
#include "rendmid.h"
#include "rephist.h"
+#include "hs_circuitmap.h"
+#include "hs_intropoint.h"
/** Respond to an ESTABLISH_INTRO cell by checking the signed data and
* setting the circuit's purpose and service pk digest.
*/
int
-rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
- size_t request_len)
+rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
{
crypto_pk_t *pk = NULL;
char buf[DIGEST_LEN+9];
@@ -32,15 +35,14 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
int reason = END_CIRC_REASON_INTERNAL;
log_info(LD_REND,
- "Received an ESTABLISH_INTRO request on circuit %u",
+ "Received a legacy ESTABLISH_INTRO request on circuit %u",
(unsigned) circ->p_circ_id);
- if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) {
- log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Rejecting ESTABLISH_INTRO on non-OR or non-edge circuit.");
+ if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
reason = END_CIRC_REASON_TORPROTOCOL;
goto err;
}
+
if (request_len < 2+DIGEST_LEN)
goto truncated;
/* First 2 bytes: length of asn1-encoded key. */
@@ -69,7 +71,6 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
goto err;
}
/* Rest of body: signature of previous data */
- note_crypto_pk_op(REND_MID);
if (crypto_pk_public_checksig_digest(pk,
(char*)request, 2+asn1len+DIGEST_LEN,
(char*)(request+2+DIGEST_LEN+asn1len),
@@ -94,7 +95,8 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
/* Close any other intro circuits with the same pk. */
c = NULL;
- while ((c = circuit_get_intro_point((const uint8_t *)pk_digest))) {
+ while ((c = hs_circuitmap_get_intro_circ_v2_relay_side(
+ (const uint8_t *)pk_digest))) {
log_info(LD_REND, "Replacing old circuit for service %s",
safe_str(serviceid));
circuit_mark_for_close(TO_CIRCUIT(c), END_CIRC_REASON_FINISHED);
@@ -102,16 +104,14 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
}
/* Acknowledge the request. */
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
- RELAY_COMMAND_INTRO_ESTABLISHED,
- "", 0, NULL)<0) {
+ if (hs_intro_send_intro_established_cell(circ) < 0) {
log_info(LD_GENERAL, "Couldn't send INTRO_ESTABLISHED cell.");
- goto err;
+ goto err_no_close;
}
/* Now, set up this circuit. */
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
- circuit_set_intro_point_digest(circ, (uint8_t *)pk_digest);
+ hs_circuitmap_register_intro_circ_v2_relay_side(circ, (uint8_t *)pk_digest);
log_info(LD_REND,
"Established introduction point on circuit %u for service %s",
@@ -122,8 +122,9 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
log_warn(LD_PROTOCOL, "Rejecting truncated ESTABLISH_INTRO cell.");
reason = END_CIRC_REASON_TORPROTOCOL;
err:
- if (pk) crypto_pk_free(pk);
circuit_mark_for_close(TO_CIRCUIT(circ), reason);
+ err_no_close:
+ if (pk) crypto_pk_free(pk);
return -1;
}
@@ -132,8 +133,8 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
* INTRODUCE2 cell.
*/
int
-rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
- size_t request_len)
+rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
{
or_circuit_t *intro_circ;
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
@@ -142,26 +143,10 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
log_info(LD_REND, "Received an INTRODUCE1 request on circuit %u",
(unsigned)circ->p_circ_id);
- if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) {
- log_warn(LD_PROTOCOL,
- "Rejecting INTRODUCE1 on non-OR or non-edge circuit %u.",
- (unsigned)circ->p_circ_id);
- goto err;
- }
-
- /* We have already done an introduction on this circuit but we just
- received a request for another one. We block it since this might
- be an attempt to DoS a hidden service (#15515). */
- if (circ->already_received_introduce1) {
- log_fn(LOG_PROTOCOL_WARN, LD_REND,
- "Blocking multiple introductions on the same circuit. "
- "Someone might be trying to attack a hidden service through "
- "this relay.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
- return -1;
- }
-
- circ->already_received_introduce1 = 1;
+ /* At this point, we know that the circuit is valid for an INTRODUCE1
+ * because the validation has been made before calling this function. */
+ tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_OR);
+ tor_assert(!circ->base_.n_chan);
/* We could change this to MAX_HEX_NICKNAME_LEN now that 0.0.9.x is
* obsolete; however, there isn't much reason to do so, and we're going
@@ -180,7 +165,8 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
/* The first 20 bytes are all we look at: they have a hash of the service's
* PK. */
- intro_circ = circuit_get_intro_point((const uint8_t*)request);
+ intro_circ = hs_circuitmap_get_intro_circ_v2_relay_side(
+ (const uint8_t*)request);
if (!intro_circ) {
log_info(LD_REND,
"No intro circ found for INTRODUCE1 cell (%s) from circuit %u; "
@@ -201,14 +187,15 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
(char*)request, request_len, NULL)) {
log_warn(LD_GENERAL,
"Unable to send INTRODUCE2 cell to Tor client.");
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
/* And send an ack down the client's circuit. Empty body means succeeded. */
if (relay_send_command_from_edge(0,TO_CIRCUIT(circ),
RELAY_COMMAND_INTRODUCE_ACK,
NULL,0,NULL)) {
log_warn(LD_GENERAL, "Unable to send INTRODUCE_ACK cell to Tor client.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ /* Stop right now, the circuit has been closed. */
return -1;
}
@@ -220,8 +207,6 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
RELAY_COMMAND_INTRODUCE_ACK,
nak_body, 1, NULL)) {
log_warn(LD_GENERAL, "Unable to send NAK to Tor client.");
- /* Is this right? */
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
}
return -1;
}
@@ -258,7 +243,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
goto err;
}
- if (circuit_get_rendezvous(request)) {
+ if (hs_circuitmap_get_rend_circ_relay_side(request)) {
log_warn(LD_PROTOCOL,
"Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS.");
goto err;
@@ -269,12 +254,12 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
RELAY_COMMAND_RENDEZVOUS_ESTABLISHED,
"", 0, NULL)<0) {
log_warn(LD_PROTOCOL, "Couldn't send RENDEZVOUS_ESTABLISHED cell.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING);
- circuit_set_rendezvous_cookie(circ, request);
+ hs_circuitmap_register_rend_circ_relay_side(circ, request);
base16_encode(hexid,9,(char*)request,4);
@@ -309,7 +294,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);
@@ -323,7 +308,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
"Got request for rendezvous from circuit %u to cookie %s.",
(unsigned)circ->p_circ_id, hexid);
- rend_circ = circuit_get_rendezvous(request);
+ rend_circ = hs_circuitmap_get_rend_circ_relay_side(request);
if (!rend_circ) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.",
@@ -346,7 +331,8 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
log_warn(LD_GENERAL,
"Unable to send RENDEZVOUS2 cell to client on circuit %u.",
(unsigned)rend_circ->p_circ_id);
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
/* Join the circuits. */
@@ -357,7 +343,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_ESTABLISHED);
circuit_change_purpose(TO_CIRCUIT(rend_circ),
CIRCUIT_PURPOSE_REND_ESTABLISHED);
- circuit_set_rendezvous_cookie(circ, NULL);
+ hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
rend_circ->rend_splice = circ;
circ->rend_splice = rend_circ;
diff --git a/src/or/rendmid.h b/src/or/rendmid.h
index 10d1287085..daf9e2885e 100644
--- a/src/or/rendmid.h
+++ b/src/or/rendmid.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -12,10 +12,10 @@
#ifndef TOR_RENDMID_H
#define TOR_RENDMID_H
-int rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
- size_t request_len);
-int rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
- size_t request_len);
+int rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
+int rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
int rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
size_t request_len);
int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index 39e5831589..a205b00c6b 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -1,5 +1,5 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -17,9 +17,12 @@
#include "config.h"
#include "control.h"
#include "directory.h"
+#include "hs_common.h"
+#include "hs_config.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
+#include "policies.h"
#include "rendclient.h"
#include "rendcommon.h"
#include "rendservice.h"
@@ -71,111 +74,81 @@ static ssize_t rend_service_parse_intro_for_v3(
size_t plaintext_len,
char **err_msg_out);
-/** Represents the mapping from a virtual port of a rendezvous service to
- * a real port on some IP.
+static int rend_service_check_private_dir(const or_options_t *options,
+ const rend_service_t *s,
+ int create);
+static const smartlist_t* rend_get_service_list(
+ const smartlist_t* substitute_service_list);
+static smartlist_t* rend_get_service_list_mutable(
+ smartlist_t* substitute_service_list);
+static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted);
+
+/* 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";
+
+/** A list of rend_service_t's for services run on this OP. */
+static smartlist_t *rend_service_list = NULL;
+/** A list of rend_service_t's for services run on this OP which is used as a
+ * staging area before they are put in the main list in order to prune dying
+ * service on config reload. */
+static smartlist_t *rend_service_staging_list = NULL;
+
+/* Like rend_get_service_list_mutable, but returns a read-only list. */
+static const smartlist_t*
+rend_get_service_list(const smartlist_t* substitute_service_list)
+{
+ /* It is safe to cast away the const here, because
+ * rend_get_service_list_mutable does not actually modify the list */
+ return rend_get_service_list_mutable((smartlist_t*)substitute_service_list);
+}
+
+/* Return a mutable list of hidden services.
+ * If substitute_service_list is not NULL, return it.
+ * Otherwise, check if the global rend_service_list is non-NULL, and if so,
+ * return it.
+ * Otherwise, log a BUG message and return NULL.
+ * */
+static smartlist_t*
+rend_get_service_list_mutable(smartlist_t* substitute_service_list)
+{
+ if (substitute_service_list) {
+ return substitute_service_list;
+ }
+
+ /* If no special service list is provided, then just use the global one. */
+
+ if (BUG(!rend_service_list)) {
+ /* No global HS list, which is a programmer error. */
+ return NULL;
+ }
+
+ return rend_service_list;
+}
+
+/** Tells if onion service <b>s</b> is ephemeral.
*/
-struct rend_service_port_config_s {
- /* The incoming HS virtual port we're mapping */
- uint16_t virtual_port;
- /* Is this an AF_UNIX port? */
- unsigned int is_unix_addr:1;
- /* The outgoing TCP port to use, if !is_unix_addr */
- uint16_t real_port;
- /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
- tor_addr_t real_addr;
- /* The socket path to connect to, if is_unix_addr */
- char unix_addr[FLEXIBLE_ARRAY_MEMBER];
-};
-
-/** Try to maintain this many intro points per service by default. */
-#define NUM_INTRO_POINTS_DEFAULT 3
-/** Maximum number of intro points per service. */
-#define NUM_INTRO_POINTS_MAX 10
-/** Number of extra intro points we launch if our set of intro nodes is
- * empty. See proposal 155, section 4. */
-#define NUM_INTRO_POINTS_EXTRA 2
-
-/** If we can't build our intro circuits, don't retry for this long. */
-#define INTRO_CIRC_RETRY_PERIOD (60*5)
-/** Don't try to build more than this many circuits before giving up
- * for a while.*/
-#define MAX_INTRO_CIRCS_PER_PERIOD 10
-/** How many times will a hidden service operator attempt to connect to
- * a requested rendezvous point before giving up? */
-#define MAX_REND_FAILURES 1
-/** How many seconds should we spend trying to connect to a requested
- * 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;
+static unsigned int
+rend_service_is_ephemeral(const struct rend_service_t *s)
+{
+ return (s->directory == NULL);
+}
/** Returns a escaped string representation of the service, <b>s</b>.
*/
static const char *
rend_service_escaped_dir(const struct rend_service_t *s)
{
- return (s->directory) ? escaped(s->directory) : "[EPHEMERAL]";
+ return rend_service_is_ephemeral(s) ? "[EPHEMERAL]" : escaped(s->directory);
}
-/** A list of rend_service_t's for services run on this OP.
- */
-static smartlist_t *rend_service_list = NULL;
-
/** Return the number of rendezvous services we have configured. */
int
-num_rend_services(void)
+rend_num_services(void)
{
if (!rend_service_list)
return 0;
@@ -183,14 +156,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 +179,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) {
@@ -240,120 +216,139 @@ rend_service_free(rend_service_t *service)
tor_free(service);
}
-/** Release all the storage held in rend_service_list.
- */
+/* Release all the storage held in rend_service_staging_list. */
+void
+rend_service_free_staging_list(void)
+{
+ if (rend_service_staging_list) {
+ SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_staging_list);
+ rend_service_staging_list = NULL;
+ }
+}
+
+/** Release all the storage held in both rend_service_list and
+ * rend_service_staging_list. */
void
rend_service_free_all(void)
{
- if (!rend_service_list)
- return;
+ if (rend_service_list) {
+ SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_list);
+ rend_service_list = NULL;
+ }
+ rend_service_free_staging_list();
+}
- SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
- rend_service_free(ptr));
- smartlist_free(rend_service_list);
- rend_service_list = NULL;
+/* Initialize the subsystem. */
+void
+rend_service_init(void)
+{
+ tor_assert(!rend_service_list);
+ tor_assert(!rend_service_staging_list);
+
+ rend_service_list = smartlist_new();
+ rend_service_staging_list = smartlist_new();
}
-/** Validate <b>service</b> and add it to rend_service_list if possible.
- * Return 0 on success. On failure, free <b>service</b> and return -1.
- */
+/* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there
+ * is no duplicate entry for the given service object. Return 0 if valid else
+ * -1 if not.*/
static int
-rend_add_service(rend_service_t *service)
+rend_validate_service(const smartlist_t *service_list,
+ const rend_service_t *service)
{
- int i;
- rend_service_port_config_t *p;
-
- service->intro_nodes = smartlist_new();
- service->expiring_nodes = smartlist_new();
+ tor_assert(service_list);
+ tor_assert(service);
if (service->max_streams_per_circuit < 0) {
log_warn(LD_CONFIG, "Hidden service (%s) configured with negative max "
- "streams per circuit; ignoring.",
+ "streams per circuit.",
rend_service_escaped_dir(service));
- rend_service_free(service);
- return -1;
+ goto invalid;
}
if (service->max_streams_close_circuit < 0 ||
service->max_streams_close_circuit > 1) {
log_warn(LD_CONFIG, "Hidden service (%s) configured with invalid "
- "max streams handling; ignoring.",
+ "max streams handling.",
rend_service_escaped_dir(service));
- rend_service_free(service);
- return -1;
+ goto invalid;
}
if (service->auth_type != REND_NO_AUTH &&
- smartlist_len(service->clients) == 0) {
- log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no "
- "clients; ignoring.",
+ (!service->clients || smartlist_len(service->clients) == 0)) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but "
+ "no clients.",
rend_service_escaped_dir(service));
- rend_service_free(service);
- return -1;
+ goto invalid;
}
- if (!smartlist_len(service->ports)) {
- log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; "
- "ignoring.",
+ if (!service->ports || !smartlist_len(service->ports)) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
rend_service_escaped_dir(service));
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 0;
+ invalid:
+ return -1;
+}
+
+/** 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(smartlist_t *service_list, rend_service_t *service)
+{
+ int i;
+ rend_service_port_config_t *p;
+
+ tor_assert(service);
+
+ smartlist_t *s_list = rend_get_service_list_mutable(service_list);
+ /* We must have a service list, even if it's a temporary one, so we can
+ * check for duplicate services */
+ if (BUG(!s_list)) {
rend_service_free(service);
return -1;
- } else {
- int dupe = 0;
- /* XXX This duplicate check has two problems:
- *
- * a) It's O(n^2), but the same comment from the bottom of
- * rend_config_services() should apply.
- *
- * b) We only compare directory paths as strings, so we can't
- * detect two distinct paths that specify the same directory
- * (which can arise from symlinks, case-insensitivity, bind
- * mounts, etc.).
- *
- * It also can't detect that two separate Tor instances are trying
- * to use the same HiddenServiceDir; for that, we would need a
- * lock file. But this is enough to detect a simple mistake that
- * at least one person has actually made.
- */
- if (service->directory != NULL) { /* Skip dupe for ephemeral services. */
- SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
- dupe = dupe ||
- !strcmp(ptr->directory, service->directory));
- if (dupe) {
- log_warn(LD_REND, "Another hidden service is already configured for "
- "directory %s, ignoring.",
- rend_service_escaped_dir(service));
- rend_service_free(service);
- return -1;
- }
- }
- smartlist_add(rend_service_list, service);
- log_debug(LD_REND,"Configuring service with directory \"%s\"",
- service->directory);
- for (i = 0; i < smartlist_len(service->ports); ++i) {
- p = smartlist_get(service->ports, i);
- if (!(p->is_unix_addr)) {
- log_debug(LD_REND,
- "Service maps port %d to %s",
- p->virtual_port,
- fmt_addrport(&p->real_addr, p->real_port));
- } else {
+ }
+
+ service->intro_nodes = smartlist_new();
+ service->expiring_nodes = smartlist_new();
+
+ log_debug(LD_REND,"Configuring service with directory %s",
+ rend_service_escaped_dir(service));
+ for (i = 0; i < smartlist_len(service->ports); ++i) {
+ p = smartlist_get(service->ports, i);
+ if (!(p->is_unix_addr)) {
+ log_debug(LD_REND,
+ "Service maps port %d to %s",
+ p->virtual_port,
+ fmt_addrport(&p->real_addr, p->real_port));
+ } else {
#ifdef HAVE_SYS_UN_H
- log_debug(LD_REND,
- "Service maps port %d to socket at \"%s\"",
- p->virtual_port, p->unix_addr);
+ log_debug(LD_REND,
+ "Service maps port %d to socket at \"%s\"",
+ p->virtual_port, p->unix_addr);
#else
- log_debug(LD_REND,
- "Service maps port %d to an AF_UNIX socket, but we "
- "have no AF_UNIX support on this platform. This is "
- "probably a bug.",
- p->virtual_port);
+ log_warn(LD_BUG,
+ "Service maps port %d to an AF_UNIX socket, but we "
+ "have no AF_UNIX support on this platform. This is "
+ "probably a bug.",
+ p->virtual_port);
+ rend_service_free(service);
+ return -1;
#endif /* defined(HAVE_SYS_UN_H) */
- }
}
- return 0;
}
- /* NOTREACHED */
+ /* The service passed all the checks */
+ tor_assert(s_list);
+ smartlist_add(s_list, service);
+ return 0;
}
/** Return a new rend_service_port_config_t with its path set to
@@ -372,9 +367,9 @@ rend_service_port_config_new(const char *socket_path)
return conf;
}
-/** Parses a real-port to virtual-port mapping separated by the provided
- * separator and returns a new rend_service_port_config_t, or NULL and an
- * optional error string on failure.
+/** Parses a virtual-port to real-port/socket mapping separated by
+ * the provided separator and returns a new rend_service_port_config_t,
+ * or NULL and an optional error string on failure.
*
* The format is: VirtualPort SEP (IP|RealPort|IP:RealPort|'socket':path)?
*
@@ -389,31 +384,26 @@ 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) {
- if (err_msg_out)
- err_msg = tor_strdup("Bad syntax in hidden service port configuration.");
-
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
+ if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) {
+ 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)
- tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service "
+ tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service "
"port configuration", escaped(smartlist_get(sl,0)));
goto err;
}
-
if (smartlist_len(sl) == 1) {
/* No addr:port part; use default. */
realport = virtport;
@@ -421,25 +411,24 @@ 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 */
if (tor_addr_port_lookup(addrport, &addr, &p)<0) {
- if (err_msg_out)
- err_msg = tor_strdup("Unparseable address in hidden service port "
- "configuration.");
-
+ err_msg = tor_strdup("Unparseable address in hidden service port "
+ "configuration.");
goto err;
}
realport = p?p:virtport;
@@ -447,11 +436,9 @@ rend_service_parse_port_config(const char *string, const char *sep,
/* No addr:port, no addr -- must be port. */
realport = (int)tor_parse_long(addrport, 10, 1, 65535, NULL, NULL);
if (!realport) {
- if (err_msg_out)
- tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in "
- "hidden service port configuration.",
- escaped(addrport));
-
+ tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in "
+ "hidden service port configuration.",
+ escaped(addrport));
goto err;
}
tor_addr_from_ipv4h(&addr, 0x7F000001u); /* Default to 127.0.0.1 */
@@ -469,10 +456,14 @@ rend_service_parse_port_config(const char *string, const char *sep,
}
err:
- if (err_msg_out) *err_msg_out = err_msg;
+ tor_free(addrport);
+ if (err_msg_out != NULL) {
+ *err_msg_out = err_msg;
+ } else {
+ tor_free(err_msg);
+ }
SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
smartlist_free(sl);
- if (socket_path) tor_free(socket_path);
return result;
}
@@ -484,129 +475,219 @@ rend_service_port_config_free(rend_service_port_config_t *p)
tor_free(p);
}
-/** Set up rend_service_list, based on the values of HiddenServiceDir and
- * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on
- * failure. (If <b>validate_only</b> is set, parse, warn and return as
- * normal, but don't actually change the configured services.)
- */
+/* Helper: Actual implementation of the pruning on reload which we've
+ * decoupled in order to make the unit test workeable without ugly hacks.
+ * Furthermore, this function does NOT free any memory but will nullify the
+ * temporary list pointer whatever happens. */
+STATIC void
+rend_service_prune_list_impl_(void)
+{
+ origin_circuit_t *ocirc = NULL;
+ smartlist_t *surviving_services, *old_service_list, *new_service_list;
+
+ /* When pruning our current service list, we must have a staging list that
+ * contains what we want to check else it's a code flow error. */
+ tor_assert(rend_service_staging_list);
+
+ /* We are about to prune the current list of its dead service so set the
+ * semantic for that list to be the "old" one. */
+ old_service_list = rend_service_list;
+ /* The staging list is now the "new" list so set this semantic. */
+ new_service_list = rend_service_staging_list;
+ /* After this, whatever happens, we'll use our new list. */
+ rend_service_list = new_service_list;
+ /* Finally, nullify the staging list pointer as we don't need it anymore
+ * and it needs to be NULL before the next reload. */
+ rend_service_staging_list = NULL;
+ /* Nothing to prune if we have no service list so stop right away. */
+ if (!old_service_list) {
+ return;
+ }
+
+ /* This contains all _existing_ services that survives the relaod that is
+ * that haven't been removed from the configuration. The difference between
+ * this list and the new service list is that the new list can possibly
+ * contain newly configured service that have no introduction points opened
+ * yet nor key material loaded or generated. */
+ surviving_services = smartlist_new();
+
+ /* Preserve the existing ephemeral services.
+ *
+ * This is the ephemeral service equivalent of the "Copy introduction
+ * points to new services" block, except there's no copy required since
+ * the service structure isn't regenerated.
+ *
+ * After this is done, all ephemeral services will be:
+ * * Removed from old_service_list, so the equivalent non-ephemeral code
+ * will not attempt to preserve them.
+ * * Added to the new_service_list (that previously only had the
+ * services listed in the configuration).
+ * * Added to surviving_services, which is the list of services that
+ * will NOT have their intro point closed.
+ */
+ SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
+ if (rend_service_is_ephemeral(old)) {
+ SMARTLIST_DEL_CURRENT(old_service_list, old);
+ smartlist_add(surviving_services, old);
+ smartlist_add(new_service_list, old);
+ }
+ } SMARTLIST_FOREACH_END(old);
+
+ /* Copy introduction points to new services. This is O(n^2), but it's only
+ * called on reconfigure, so it's ok performance wise. */
+ SMARTLIST_FOREACH_BEGIN(new_service_list, rend_service_t *, new) {
+ SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
+ /* Skip ephemeral services as we only want to copy introduction points
+ * from current services to newly configured one that already exists.
+ * The same directory means it's the same service. */
+ if (rend_service_is_ephemeral(new) || rend_service_is_ephemeral(old) ||
+ strcmp(old->directory, new->directory)) {
+ continue;
+ }
+ smartlist_add_all(new->intro_nodes, old->intro_nodes);
+ smartlist_clear(old->intro_nodes);
+ smartlist_add_all(new->expiring_nodes, old->expiring_nodes);
+ smartlist_clear(old->expiring_nodes);
+ /* This regular service will survive the closing IPs step after. */
+ smartlist_add(surviving_services, old);
+ break;
+ } SMARTLIST_FOREACH_END(old);
+ } SMARTLIST_FOREACH_END(new);
+
+ /* For every service introduction circuit we can find, see if we have a
+ * matching surviving configured service. If not, close the circuit. */
+ while ((ocirc = circuit_get_next_service_intro_circ(ocirc))) {
+ int keep_it = 0;
+ tor_assert(ocirc->rend_data);
+ SMARTLIST_FOREACH_BEGIN(surviving_services, const rend_service_t *, s) {
+ if (rend_circuit_pk_digest_eq(ocirc, (uint8_t *) s->pk_digest)) {
+ /* Keep this circuit as we have a matching configured service. */
+ keep_it = 1;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(s);
+ if (keep_it) {
+ continue;
+ }
+ log_info(LD_REND, "Closing intro point %s for service %s.",
+ safe_str_client(extend_info_describe(
+ ocirc->build_state->chosen_exit)),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)));
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ smartlist_free(surviving_services);
+}
+
+/* Try to prune our main service list using the temporary one that we just
+ * loaded and parsed successfully. The pruning process decides which onion
+ * services to keep and which to discard after a reload. */
+void
+rend_service_prune_list(void)
+{
+ smartlist_t *old_service_list = rend_service_list;
+ /* Don't try to prune anything if we have no staging list. */
+ if (!rend_service_staging_list) {
+ return;
+ }
+ rend_service_prune_list_impl_();
+ if (old_service_list) {
+ /* Every remaining service in the old list have been removed from the
+ * configuration so clean them up safely. */
+ SMARTLIST_FOREACH(old_service_list, rend_service_t *, s,
+ rend_service_free(s));
+ smartlist_free(old_service_list);
+ }
+}
+
+/* Copy all the relevant data that the hs_service object contains over to the
+ * rend_service_t object. The reason to do so is because when configuring a
+ * service, we go through a generic handler that creates an hs_service_t
+ * object which so we have to copy the parsed values to a rend service object
+ * which is version 2 specific. */
+static void
+service_config_shadow_copy(rend_service_t *service,
+ hs_service_config_t *config)
+{
+ tor_assert(service);
+ tor_assert(config);
+
+ service->directory = tor_strdup(config->directory_path);
+ service->dir_group_readable = config->dir_group_readable;
+ service->allow_unknown_ports = config->allow_unknown_ports;
+ /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535)
+ * if the code flow is right so this cast is safe. But just in case, we'll
+ * check it. */
+ service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit;
+ if (BUG(config->max_streams_per_rdv_circuit >
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) {
+ service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT;
+ }
+ service->max_streams_close_circuit = config->max_streams_close_circuit;
+ service->n_intro_points_wanted = config->num_intro_points;
+ /* Switching ownership of the ports to the rend service object. */
+ smartlist_add_all(service->ports, config->ports);
+ smartlist_free(config->ports);
+ config->ports = NULL;
+}
+
+/* Parse the hidden service configuration starting at <b>line_</b> using the
+ * already configured generic service configuration in <b>config</b>. This
+ * function will translate the config object to a rend_service_t and add it to
+ * the temporary list if valid. If <b>validate_only</b> is set, parse, warn
+ * and return as normal but don't actually add the service to the list. */
int
-rend_config_services(const or_options_t *options, int validate_only)
+rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config)
{
- config_line_t *line;
+ const config_line_t *line;
rend_service_t *service = NULL;
- rend_service_port_config_t *portcfg;
- smartlist_t *old_service_list = NULL;
- int ok = 0;
- if (!validate_only) {
- old_service_list = rend_service_list;
- rend_service_list = smartlist_new();
+ /* line_ can be NULL which would mean that the service configuration only
+ * have one line that is the directory directive. */
+ tor_assert(options);
+ tor_assert(config);
+
+ /* Use the staging service list so that we can check then do the pruning
+ * process using the main list at the end. */
+ if (rend_service_staging_list == NULL) {
+ rend_service_staging_list = smartlist_new();
}
- for (line = options->RendConfigLines; line; line = line->next) {
+ /* Initialize service. */
+ service = tor_malloc_zero(sizeof(rend_service_t));
+ service->intro_period_started = time(NULL);
+ service->ports = smartlist_new();
+ /* From the hs_service object which has been used to load the generic
+ * options, we'll copy over the useful data to the rend_service_t object. */
+ service_config_shadow_copy(service, config);
+
+ for (line = line_; line; line = line->next) {
if (!strcasecmp(line->key, "HiddenServiceDir")) {
- if (service) { /* register the one we just finished parsing */
- if (validate_only)
- rend_service_free(service);
- else
- rend_add_service(service);
- }
- service = tor_malloc_zero(sizeof(rend_service_t));
- service->directory = tor_strdup(line->value);
- service->ports = smartlist_new();
- service->intro_period_started = time(NULL);
- service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
- continue;
- }
- if (!service) {
- log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
- line->key);
- rend_service_free(service);
- return -1;
+ /* We just hit the next hidden service, stop right now. */
+ break;
}
- if (!strcasecmp(line->key, "HiddenServicePort")) {
- char *err_msg = NULL;
- portcfg = rend_service_parse_port_config(line->value, " ", &err_msg);
- if (!portcfg) {
- if (err_msg)
- log_warn(LD_CONFIG, "%s", err_msg);
- tor_free(err_msg);
- rend_service_free(service);
- return -1;
- }
- tor_assert(!err_msg);
- smartlist_add(service->ports, portcfg);
- } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
- service->allow_unknown_ports = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s",
- line->value);
- rend_service_free(service);
- return -1;
- }
- log_info(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts=%d for %s",
- (int)service->allow_unknown_ports, service->directory);
- } else if (!strcasecmp(line->key,
- "HiddenServiceDirGroupReadable")) {
- service->dir_group_readable = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceDirGroupReadable should be 0 or 1, not %s",
- line->value);
- rend_service_free(service);
- return -1;
- }
- log_info(LD_CONFIG,
- "HiddenServiceDirGroupReadable=%d for %s",
- service->dir_group_readable, service->directory);
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
- service->max_streams_per_circuit = (int)tor_parse_long(line->value,
- 10, 0, 65535, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreams should be between 0 and %d, not %s",
- 65535, line->value);
- rend_service_free(service);
- return -1;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreams=%d for %s",
- service->max_streams_per_circuit, service->directory);
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
- service->max_streams_close_circuit = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, "
- "not %s",
- line->value);
- rend_service_free(service);
- return -1;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit=%d for %s",
- (int)service->max_streams_close_circuit, service->directory);
- } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ int ok = 0;
+ /* Those are specific defaults for version 2. */
service->n_intro_points_wanted =
(unsigned int) tor_parse_long(line->value, 10,
- NUM_INTRO_POINTS_DEFAULT,
- NUM_INTRO_POINTS_MAX, &ok, NULL);
+ 0, NUM_INTRO_POINTS_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_CONFIG,
"HiddenServiceNumIntroductionPoints "
"should be between %d and %d, not %s",
- NUM_INTRO_POINTS_DEFAULT, NUM_INTRO_POINTS_MAX,
- line->value);
- rend_service_free(service);
- return -1;
+ 0, NUM_INTRO_POINTS_MAX, line->value);
+ goto err;
}
log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s",
- service->n_intro_points_wanted, service->directory);
- } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
+ service->n_intro_points_wanted, escaped(service->directory));
+ continue;
+ }
+ if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
/* Parse auth type and comma-separated list of client names and add a
* rend_authorized_client_t for each client to the service's list
* of authorized clients. */
@@ -616,8 +697,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (service->auth_type != REND_NO_AUTH) {
log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient "
"lines for a single service.");
- rend_service_free(service);
- return -1;
+ goto err;
}
type_names_split = smartlist_new();
smartlist_split_string(type_names_split, line->value, " ", 0, 2);
@@ -626,8 +706,7 @@ rend_config_services(const or_options_t *options, int validate_only)
"should have been prevented when parsing the "
"configuration.");
smartlist_free(type_names_split);
- rend_service_free(service);
- return -1;
+ goto err;
}
authname = smartlist_get(type_names_split, 0);
if (!strcasecmp(authname, "basic")) {
@@ -641,8 +720,7 @@ rend_config_services(const or_options_t *options, int validate_only)
(char *) smartlist_get(type_names_split, 0));
SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
smartlist_free(type_names_split);
- rend_service_free(service);
- return -1;
+ goto err;
}
service->clients = smartlist_new();
if (smartlist_len(type_names_split) < 2) {
@@ -671,26 +749,15 @@ 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;
+ goto err;
}
client = tor_malloc_zero(sizeof(rend_authorized_client_t));
client->client_name = tor_strdup(client_name);
@@ -712,129 +779,42 @@ rend_config_services(const or_options_t *options, int validate_only)
smartlist_len(service->clients),
service->auth_type == REND_BASIC_AUTH ? 512 : 16,
service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
- rend_service_free(service);
- return -1;
- }
- } else {
- tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
- if (strcmp(line->value, "2")) {
- log_warn(LD_CONFIG,
- "The only supported HiddenServiceVersion is 2.");
- rend_service_free(service);
- return -1;
+ goto err;
}
+ continue;
}
}
- 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);
- }
- }
-
- /* If this is a reload and there were hidden services configured before,
- * keep the introduction points that are still needed and close the
- * other ones. */
- if (old_service_list && !validate_only) {
- smartlist_t *surviving_services = smartlist_new();
-
- /* Preserve the existing ephemeral services.
- *
- * This is the ephemeral service equivalent of the "Copy introduction
- * points to new services" block, except there's no copy required since
- * the service structure isn't regenerated.
- *
- * After this is done, all ephemeral services will be:
- * * Removed from old_service_list, so the equivalent non-ephemeral code
- * will not attempt to preserve them.
- * * Added to the new rend_service_list (that previously only had the
- * services listed in the configuration).
- * * Added to surviving_services, which is the list of services that
- * will NOT have their intro point closed.
- */
- SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, {
- if (!old->directory) {
- SMARTLIST_DEL_CURRENT(old_service_list, old);
- smartlist_add(surviving_services, old);
- smartlist_add(rend_service_list, old);
- }
- });
+ /* Validate the service just parsed. */
+ if (rend_validate_service(rend_service_staging_list, service) < 0) {
+ /* Service is in the staging list so don't try to free it. */
+ goto err;
+ }
- /* Copy introduction points to new services. */
- /* XXXX This is O(n^2), but it's only called on reconfigure, so it's
- * probably ok? */
- SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, new) {
- SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
- if (new->directory && old->directory &&
- !strcmp(old->directory, new->directory)) {
- smartlist_add_all(new->intro_nodes, old->intro_nodes);
- smartlist_clear(old->intro_nodes);
- smartlist_add_all(new->expiring_nodes, old->expiring_nodes);
- smartlist_clear(old->expiring_nodes);
- smartlist_add(surviving_services, old);
- break;
- }
- } SMARTLIST_FOREACH_END(old);
- } SMARTLIST_FOREACH_END(new);
-
- /* Close introduction circuits of services we don't serve anymore. */
- /* XXXX it would be nicer if we had a nicer abstraction to use here,
- * so we could just iterate over the list of services to close, but
- * once again, this isn't critical-path code. */
- 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);
- int keep_it = 0;
- tor_assert(oc->rend_data);
- SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, {
- if (tor_memeq(ptr->pk_digest, oc->rend_data->rend_pk_digest,
- DIGEST_LEN)) {
- keep_it = 1;
- break;
- }
- });
- if (keep_it)
- continue;
- log_info(LD_REND, "Closing intro point %s for service %s.",
- safe_str_client(extend_info_describe(
- oc->build_state->chosen_exit)),
- oc->rend_data->onion_address);
- circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
- /* XXXX Is there another reason we should use here? */
- }
- }
- SMARTLIST_FOREACH_END(circ);
- smartlist_free(surviving_services);
- SMARTLIST_FOREACH(old_service_list, rend_service_t *, ptr,
- rend_service_free(ptr));
- smartlist_free(old_service_list);
+ /* Add it to the temporary list which we will use to prune our current
+ * list if any after configuring all services. */
+ if (rend_add_service(rend_service_staging_list, service) < 0) {
+ /* The object has been freed on error already. */
+ service = NULL;
+ goto err;
}
return 0;
+ err:
+ rend_service_free(service);
+ return -1;
}
-/** 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 +823,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 +834,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 +851,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 +874,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);
@@ -910,7 +899,7 @@ rend_service_del_ephemeral(const char *service_id)
"removal.");
return -1;
}
- if (s->directory) {
+ if (!rend_service_is_ephemeral(s)) {
log_warn(LD_CONFIG, "Requested non-ephemeral Onion Service for removal.");
return -1;
}
@@ -923,17 +912,17 @@ 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);
tor_assert(oc->rend_data);
- if (!tor_memeq(s->pk_digest, oc->rend_data->rend_pk_digest, DIGEST_LEN))
+ if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) {
continue;
+ }
log_debug(LD_REND, "Closing intro point %s for service %s.",
safe_str_client(extend_info_describe(
oc->build_state->chosen_exit)),
- oc->rend_data->onion_address);
+ rend_data_get_address(oc->rend_data));
circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
}
} SMARTLIST_FOREACH_END(circ);
@@ -945,6 +934,38 @@ rend_service_del_ephemeral(const char *service_id)
return 0;
}
+/* There can be 1 second's delay due to second_elapsed_callback, and perhaps
+ * another few seconds due to blocking calls. */
+#define INTRO_CIRC_RETRY_PERIOD_SLOP 10
+
+/** Log information about the intro point creation rate and current intro
+ * points for service, upgrading the log level from min_severity to warn if
+ * we have stopped launching new intro point circuits. */
+static void
+rend_log_intro_limit(const rend_service_t *service, int min_severity)
+{
+ int exceeded_limit = (service->n_intro_circuits_launched >=
+ rend_max_intro_circs_per_period(
+ service->n_intro_points_wanted));
+ int severity = min_severity;
+ /* We stopped creating circuits */
+ if (exceeded_limit) {
+ severity = LOG_WARN;
+ }
+ time_t intro_period_elapsed = time(NULL) - service->intro_period_started;
+ tor_assert_nonfatal(intro_period_elapsed >= 0);
+ log_fn(severity, LD_REND, "Hidden service %s %s %d intro points in the last "
+ "%d seconds. Intro circuit launches are limited to %d per %d "
+ "seconds.",
+ service->service_id,
+ exceeded_limit ? "exceeded launch limit with" : "launched",
+ service->n_intro_circuits_launched,
+ (int)intro_period_elapsed,
+ rend_max_intro_circs_per_period(service->n_intro_points_wanted),
+ INTRO_CIRC_RETRY_PERIOD);
+ rend_service_dump_stats(severity);
+}
+
/** Replace the old value of <b>service</b>-\>desc with one that reflects
* the other fields in service.
*/
@@ -952,7 +973,6 @@ static void
rend_service_update_descriptor(rend_service_t *service)
{
rend_service_descriptor_t *d;
- origin_circuit_t *circ;
int i;
rend_service_descriptor_free(service->desc);
@@ -973,9 +993,10 @@ rend_service_update_descriptor(rend_service_t *service)
/* This intro point won't be listed in the descriptor... */
intro_svc->listed_in_last_desc = 0;
- circ = find_intro_circuit(intro_svc, service->pk_digest);
- if (!circ || circ->base_.purpose != CIRCUIT_PURPOSE_S_INTRO) {
- /* This intro point's circuit isn't finished yet. Don't list it. */
+ /* circuit_established is set in rend_service_intro_established(), and
+ * checked every second in rend_consider_services_intro_points(), so it's
+ * safe to use it here */
+ if (!intro_svc->circuit_established) {
continue;
}
@@ -997,19 +1018,271 @@ rend_service_update_descriptor(rend_service_t *service)
intro_svc->time_published = time(NULL);
}
}
+
+ /* Check that we have the right number of intro points */
+ unsigned int have_intro = (unsigned int)smartlist_len(d->intro_nodes);
+ if (have_intro != service->n_intro_points_wanted) {
+ int severity;
+ /* Getting less than we wanted or more than we're allowed is serious */
+ if (have_intro < service->n_intro_points_wanted ||
+ have_intro > NUM_INTRO_POINTS_MAX) {
+ severity = LOG_WARN;
+ } else {
+ /* Getting more than we wanted is weird, but less of a problem */
+ severity = LOG_NOTICE;
+ }
+ log_fn(severity, LD_REND, "Hidden service %s wanted %d intro points, but "
+ "descriptor was updated with %d instead.",
+ service->service_id,
+ service->n_intro_points_wanted, have_intro);
+ /* Now log an informative message about how we might have got here. */
+ rend_log_intro_limit(service, severity);
+ }
+}
+
+/* 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)
+{
+ tor_assert(service->directory);
+ return hs_path_from_filename(service->directory, file_name);
+}
+
+/* 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</b> 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 (rend_service_is_ephemeral(service)) {
+ 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 (BUG(rend_service_is_ephemeral(s))) {
+ return -1;
+ }
+
+ /* Service is expected to have a directory */
+ if (BUG(!s->directory)) {
+ return -1;
+ }
+
+ /* 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 (rend_service_is_ephemeral(service)) {
+ 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(hs_check_service_private_dir(options->User, service->directory,
+ service->dir_group_readable, 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-anonymous 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));
+
+ /* Ephemeral services aren't allowed in non-anonymous mode */
+ if (BUG(rend_service_is_ephemeral(s))) {
+ return -1;
+ }
+
+ /* Service is expected to have a directory */
+ if (BUG(!s->directory)) {
+ return -1;
+ }
+
+ 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) {
+ /* Use service_list for unit tests */
+ const smartlist_t *s_list = rend_get_service_list(service_list);
+ if (BUG(!s_list)) {
+ return -1;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(s_list, rend_service_t *, s) {
if (s->private_key)
continue;
- log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
- s->directory);
+ log_info(LD_REND, "Loading hidden-service keys from %s",
+ rend_service_escaped_dir(s));
if (rend_service_load_keys(s) < 0)
return -1;
@@ -1025,12 +1298,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,
@@ -1043,9 +1314,9 @@ rend_services_add_filenames_to_lists(smartlist_t *open_lst,
if (!rend_service_list)
return;
SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) {
- if (s->directory) {
+ if (!rend_service_is_ephemeral(s)) {
rend_service_add_filenames_to_list(open_lst, s);
- smartlist_add(stat_lst, tor_strdup(s->directory));
+ smartlist_add_strdup(stat_lst, s->directory);
}
} SMARTLIST_FOREACH_END(s);
}
@@ -1068,61 +1339,108 @@ 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. */
+/** 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_load_keys(rend_service_t *s)
+rend_service_check_private_dir(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;
-
- if (s->dir_group_readable) {
- check_opts |= CPD_GROUP_READ;
+ /* Passing a NULL service is a bug */
+ if (BUG(!s)) {
+ return -1;
}
+
/* Check/create directory */
- if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) {
+ if (hs_check_service_private_dir(options->User, s->directory,
+ s->dir_group_readable, create) < 0) {
return -1;
}
-#ifndef _WIN32
- if (s->dir_group_readable) {
- /* Only new dirs created get new opts, also enforce group read. */
- if (chmod(s->directory, 0750)) {
- log_warn(LD_FS,"Unable to make %s group-readable.", s->directory);
+
+ /* 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;
}
}
-#endif
+
+ 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];
+
+ /* Create the directory if needed which will also poison it in case of
+ * single onion service. */
+ if (rend_service_check_private_dir(get_options(), s, 1) < 0)
+ goto err;
/* Load key */
- 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);
- return -1;
- }
+ 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 +1451,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 +1475,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 +1526,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 +1592,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 +1602,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 +1639,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;
}
@@ -1363,24 +1678,6 @@ rend_service_get_by_service_id(const char *id)
return NULL;
}
-/** Return 1 if any virtual port in <b>service</b> wants a circuit
- * to have good uptime. Else return 0.
- */
-static int
-rend_service_requires_uptime(rend_service_t *service)
-{
- int i;
- rend_service_port_config_t *p;
-
- for (i=0; i < smartlist_len(service->ports); ++i) {
- p = smartlist_get(service->ports, i);
- if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
- p->virtual_port))
- return 1;
- }
- return 0;
-}
-
/** Check client authorization of a given <b>descriptor_cookie</b> of
* length <b>cookie_len</b> for <b>service</b>. Return 1 for success
* and 0 for failure. */
@@ -1429,6 +1726,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
******/
@@ -1445,7 +1767,8 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
int status = 0, result;
const or_options_t *options = get_options();
char *err_msg = NULL;
- const char *stage_descr = NULL;
+ int err_msg_severity = LOG_WARN;
+ const char *stage_descr = NULL, *rend_pk_digest;
int reason = END_CIRC_REASON_TORPROTOCOL;
/* Service/circuit/key stuff we can learn before parsing */
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
@@ -1477,18 +1800,17 @@ 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);
+ /* XXX: This is version 2 specific (only one supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
/* We'll use this in a bazillion log messages */
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
/* look up service depending on circuit. */
- service =
- rend_service_get_by_pk_digest(circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_BUG,
"Internal error: Got an INTRODUCE2 cell on an intro "
@@ -1596,8 +1918,10 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
/* Find the rendezvous point */
rp = find_rp_for_intro(parsed_req, &err_msg);
- if (!rp)
+ if (!rp) {
+ err_msg_severity = LOG_PROTOCOL_WARN;
goto log_error;
+ }
/* Check if we'd refuse to talk to this router */
if (options->StrictNodes &&
@@ -1671,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
goto err;
}
- circ_needs_uptime = rend_service_requires_uptime(service);
+ circ_needs_uptime = hs_service_requires_uptime_circ(service->ports);
/* help predict this next time */
rep_hist_note_used_internal(now, circ_needs_uptime, 1);
@@ -1681,6 +2005,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);
@@ -1704,8 +2033,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
/* Fill in the circuit's state. */
launched->rend_data =
- rend_data_service_create(service->service_id,
- circuit->rend_data->rend_pk_digest,
+ rend_data_service_create(service->service_id, rend_pk_digest,
parsed_req->rc, service->auth_type);
launched->build_state->service_pending_final_cpath_ref =
@@ -1719,7 +2047,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
cpath->rend_dh_handshake_state = dh;
dh = NULL;
- if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
+ if (circuit_init_cpath_crypto(cpath,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 1, 0)<0)
goto err;
memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN);
@@ -1735,7 +2065,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
}
}
- log_warn(LD_REND, "%s on circ %u", err_msg,
+ log_fn(err_msg_severity, LD_REND, "%s on circ %u", err_msg,
(unsigned)circuit->base_.n_circ_id);
err:
status = -1;
@@ -1793,11 +2123,14 @@ 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,
- "Could build extend_info_t for router %s named "
+ "Couldn't build extend_info_t for router %s named "
"in INTRODUCE2 cell",
escaped_safe_str_client(rp_nickname));
}
@@ -1818,6 +2151,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. */
@@ -1833,8 +2170,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro,
goto done;
err:
- if (err_msg_out) *err_msg_out = err_msg;
- else tor_free(err_msg);
+ if (err_msg_out)
+ *err_msg_out = err_msg;
+ else
+ tor_free(err_msg);
done:
return rp;
@@ -2358,10 +2697,8 @@ rend_service_decrypt_intro(
}
/* Decrypt the encrypted part */
-
- note_crypto_pk_op(REND_SERVER);
result =
- crypto_pk_private_hybrid_decrypt(
+ crypto_pk_obsolete_private_hybrid_decrypt(
key, (char *)buf, sizeof(buf),
(const char *)(intro->ciphertext), intro->ciphertext_len,
PK_PKCS1_OAEP_PADDING, 1);
@@ -2555,29 +2892,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
- /* Don't relaunch the same rend circ twice. */
- if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) {
- log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; "
- "not relaunching it again.",
- oldcirc->build_state ?
- safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
- : "*unknown*");
- return;
- }
- oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1;
-
- if (!oldcirc->build_state ||
- oldcirc->build_state->failure_count > MAX_REND_FAILURES ||
- oldcirc->build_state->expiry_time < time(NULL)) {
- log_info(LD_REND,
- "Attempt to build circuit to %s for rendezvous has failed "
- "too many times or expired; giving up.",
- oldcirc->build_state ?
- safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
- : "*unknown*");
- return;
- }
-
oldstate = oldcirc->build_state;
tor_assert(oldstate);
@@ -2590,6 +2904,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);
@@ -2618,26 +2936,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));
@@ -2648,6 +3012,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;
}
@@ -2679,15 +3044,67 @@ count_intro_point_circuits(const rend_service_t *service)
circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
if (oc->rend_data &&
- !rend_cmp_service_ids(service->service_id,
- oc->rend_data->onion_address))
+ rend_circuit_pk_digest_eq(oc, (uint8_t *) service->pk_digest)) {
num_ipos++;
+ }
}
}
SMARTLIST_FOREACH_END(circ);
return num_ipos;
}
+/* Given a buffer of at least RELAY_PAYLOAD_SIZE bytes in <b>cell_body_out</b>,
+ write the body of a legacy ESTABLISH_INTRO cell in it. Use <b>intro_key</b>
+ as the intro point auth key, and <b>rend_circ_nonce</b> as the circuit
+ crypto material. On success, fill <b>cell_body_out</b> and return the number
+ of bytes written. On fail, return -1.
+ */
+ssize_t
+rend_service_encode_establish_intro_cell(char *cell_body_out,
+ size_t cell_body_out_len,
+ crypto_pk_t *intro_key,
+ const char *rend_circ_nonce)
+{
+ int retval = -1;
+ int r;
+ int len = 0;
+ char auth[DIGEST_LEN + 9];
+
+ tor_assert(intro_key);
+ tor_assert(rend_circ_nonce);
+
+ /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
+ r = crypto_pk_asn1_encode(intro_key, cell_body_out+2,
+ RELAY_PAYLOAD_SIZE-2);
+ if (r < 0) {
+ log_warn(LD_BUG, "Internal error; failed to establish intro point.");
+ goto err;
+ }
+ len = r;
+ set_uint16(cell_body_out, htons((uint16_t)len));
+ len += 2;
+ memcpy(auth, rend_circ_nonce, DIGEST_LEN);
+ memcpy(auth+DIGEST_LEN, "INTRODUCE", 9);
+ if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9))
+ goto err;
+ len += 20;
+ r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len,
+ cell_body_out_len - len,
+ cell_body_out, len);
+ if (r<0) {
+ log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
+ goto err;
+ }
+ len += r;
+
+ retval = len;
+
+ err:
+ memwipe(auth, 0, sizeof(auth));
+
+ return retval;
+}
+
/** Called when we're done building a circuit to an introduction point:
* sends a RELAY_ESTABLISH_INTRO cell.
*/
@@ -2695,26 +3112,23 @@ void
rend_service_intro_has_opened(origin_circuit_t *circuit)
{
rend_service_t *service;
- size_t len;
- int r;
char buf[RELAY_PAYLOAD_SIZE];
- char auth[DIGEST_LEN + 9];
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
+ unsigned int expiring_nodes_len, num_ip_circuits, valid_ip_circuits = 0;
int reason = END_CIRC_REASON_TORPROTOCOL;
- crypto_pk_t *intro_key;
+ const char *rend_pk_digest;
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);
+ /* XXX: This is version 2 specific (only on supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %u.",
safe_str_client(serviceid), (unsigned)circuit->base_.n_circ_id);
@@ -2722,13 +3136,22 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
goto err;
}
+ /* Take the current amount of expiring nodes and the current amount of IP
+ * circuits and compute how many valid IP circuits we have. */
+ expiring_nodes_len = (unsigned int) smartlist_len(service->expiring_nodes);
+ num_ip_circuits = count_intro_point_circuits(service);
+ /* Let's avoid an underflow. The valid_ip_circuits is initialized to 0 in
+ * case this condition turns out false because it means that all circuits
+ * are expiring so we need to keep this circuit. */
+ if (num_ip_circuits > expiring_nodes_len) {
+ valid_ip_circuits = num_ip_circuits - expiring_nodes_len;
+ }
+
/* If we already have enough introduction circuits for this service,
* redefine this one as a general circuit or close it, depending.
- * Substract the amount of expiring nodes here since the circuits are
+ * Substract the amount of expiring nodes here because the circuits are
* still opened. */
- if ((count_intro_point_circuits(service) -
- smartlist_len(service->expiring_nodes)) >
- service->n_intro_points_wanted) {
+ if (valid_ip_circuits > service->n_intro_points_wanted) {
const or_options_t *options = get_options();
/* Remove the intro point associated with this circuit, it's being
* repurposed or closed thus cleanup memory. */
@@ -2755,9 +3178,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_C_GENERAL);
{
- rend_data_t *rend_data = circuit->rend_data;
+ rend_data_free(circuit->rend_data);
circuit->rend_data = NULL;
- rend_data_free(rend_data);
}
{
crypto_pk_t *intro_key = circuit->intro_key;
@@ -2773,43 +3195,27 @@ 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;
- /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
- r = crypto_pk_asn1_encode(intro_key, buf+2,
- RELAY_PAYLOAD_SIZE-2);
- if (r < 0) {
- log_warn(LD_BUG, "Internal error; failed to establish intro point.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
- }
- len = r;
- set_uint16(buf, htons((uint16_t)len));
- len += 2;
- memcpy(auth, circuit->cpath->prev->rend_circ_nonce, DIGEST_LEN);
- memcpy(auth+DIGEST_LEN, "INTRODUCE", 9);
- if (crypto_digest(buf+len, auth, DIGEST_LEN+9))
- goto err;
- len += 20;
- note_crypto_pk_op(REND_SERVER);
- r = crypto_pk_private_sign_digest(intro_key, buf+len, sizeof(buf)-len,
- buf, len);
- if (r<0) {
- log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
- }
- len += r;
+ /* Send the ESTABLISH_INTRO cell */
+ {
+ ssize_t len;
+ len = rend_service_encode_establish_intro_cell(buf, sizeof(buf),
+ circuit->intro_key,
+ circuit->cpath->prev->rend_circ_nonce);
+ if (len < 0) {
+ reason = END_CIRC_REASON_INTERNAL;
+ goto err;
+ }
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
- RELAY_COMMAND_ESTABLISH_INTRO,
- buf, len, circuit->cpath->prev)<0) {
- log_info(LD_GENERAL,
+ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
+ RELAY_COMMAND_ESTABLISH_INTRO,
+ buf, len, circuit->cpath->prev)<0) {
+ log_info(LD_GENERAL,
"Couldn't send introduction request for service %s on circuit %u",
serviceid, (unsigned)circuit->base_.n_circ_id);
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ goto done;
+ }
}
/* We've attempted to use this circuit */
@@ -2821,7 +3227,6 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
circuit_mark_for_close(TO_CIRCUIT(circuit), reason);
done:
memwipe(buf, 0, sizeof(buf));
- memwipe(auth, 0, sizeof(auth));
memwipe(serviceid, 0, sizeof(serviceid));
return;
@@ -2840,15 +3245,17 @@ rend_service_intro_established(origin_circuit_t *circuit,
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
(void) request;
(void) request_len;
+ tor_assert(circuit->rend_data);
+ /* XXX: This is version 2 specific (only supported one for now). */
+ const char *rend_pk_digest =
+ (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
if (circuit->base_.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
log_warn(LD_PROTOCOL,
"received INTRO_ESTABLISHED cell on non-intro circuit.");
goto err;
}
- tor_assert(circuit->rend_data);
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Unknown service on introduction circuit %u.",
(unsigned)circuit->base_.n_circ_id);
@@ -2871,7 +3278,7 @@ rend_service_intro_established(origin_circuit_t *circuit,
circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_S_INTRO);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
log_info(LD_REND,
"Received INTRO_ESTABLISHED cell on circuit %u for service %s",
(unsigned)circuit->base_.n_circ_id, serviceid);
@@ -2898,15 +3305,19 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
char hexcookie[9];
int reason;
+ const char *rend_cookie, *rend_pk_digest;
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);
+ /* XXX: This is version 2 specific (only one supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data,
+ NULL);
+ rend_cookie = circuit->rend_data->rend_cookie;
+
/* Declare the circuit dirty to avoid reuse, and for path-bias */
if (!circuit->base_.timestamp_dirty)
circuit->base_.timestamp_dirty = time(NULL);
@@ -2916,14 +3327,15 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
hop = circuit->build_state->service_pending_final_cpath_ref->cpath;
- base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4);
+ base16_encode(hexcookie,9, rend_cookie,4);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
log_info(LD_REND,
"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
@@ -2946,8 +3358,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
circuit->build_state->pending_final_cpath = hop;
circuit->build_state->service_pending_final_cpath_ref->cpath = NULL;
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
"rendezvous circuit.");
@@ -2956,7 +3367,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
}
/* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
- memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN);
+ memcpy(buf, rend_cookie, REND_COOKIE_LEN);
if (crypto_dh_get_public(hop->rend_dh_handshake_state,
buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
log_warn(LD_GENERAL,"Couldn't get DH public key.");
@@ -2972,8 +3383,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN,
circuit->cpath->prev)<0) {
log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ goto done;
}
crypto_dh_free(hop->rend_dh_handshake_state);
@@ -3020,8 +3430,8 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
origin_circuit_t *circ = NULL;
tor_assert(intro);
- while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest,
- CIRCUIT_PURPOSE_S_INTRO))) {
+ while ((circ = circuit_get_next_by_pk_and_purpose(circ,
+ (uint8_t *) pk_digest, CIRCUIT_PURPOSE_S_INTRO))) {
if (tor_memeq(circ->build_state->chosen_exit->identity_digest,
intro->extend_info->identity_digest, DIGEST_LEN) &&
circ->rend_data) {
@@ -3030,8 +3440,9 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
}
circ = NULL;
- while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest,
- CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
+ while ((circ = circuit_get_next_by_pk_and_purpose(circ,
+ (uint8_t *) pk_digest,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
if (tor_memeq(circ->build_state->chosen_exit->identity_digest,
intro->extend_info->identity_digest, DIGEST_LEN) &&
circ->rend_data) {
@@ -3070,7 +3481,7 @@ find_intro_point(origin_circuit_t *circ)
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
tor_assert(circ->rend_data);
- serviceid = circ->rend_data->onion_address;
+ serviceid = rend_data_get_address(circ->rend_data);
SMARTLIST_FOREACH(rend_service_list, rend_service_t *, s,
if (tor_memeq(s->service_id, serviceid, REND_SERVICE_ID_LEN_BASE32)) {
@@ -3146,13 +3557,16 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc,
* request. Lookup is made in rend_service_desc_has_uploaded(). */
rend_data = rend_data_client_create(service_id, desc->desc_id, NULL,
REND_NO_AUTH);
- directory_initiate_command_routerstatus_rend(hs_dir,
- DIR_PURPOSE_UPLOAD_RENDDESC_V2,
- ROUTER_PURPOSE_GENERAL,
- DIRIND_ANONYMOUS, NULL,
- desc->desc_str,
- strlen(desc->desc_str),
- 0, rend_data);
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_UPLOAD_RENDDESC_V2);
+ directory_request_set_routerstatus(req, hs_dir);
+ directory_request_set_indirection(req, DIRIND_ANONYMOUS);
+ directory_request_set_payload(req,
+ desc->desc_str, strlen(desc->desc_str));
+ directory_request_set_rend_query(req, rend_data);
+ directory_initiate_request(req);
+ directory_request_free(req);
+
rend_data_free(rend_data);
base32_encode(desc_id_base32, sizeof(desc_id_base32),
desc->desc_id, DIGEST_LEN);
@@ -3384,6 +3798,19 @@ remove_invalid_intro_points(rend_service_t *service,
{
tor_assert(service);
+ /* Remove any expired nodes that doesn't have a circuit. */
+ SMARTLIST_FOREACH_BEGIN(service->expiring_nodes, rend_intro_point_t *,
+ intro) {
+ origin_circuit_t *intro_circ =
+ find_intro_circuit(intro, service->pk_digest);
+ if (intro_circ) {
+ continue;
+ }
+ /* No more circuit, cleanup the into point object. */
+ SMARTLIST_DEL_CURRENT(service->expiring_nodes, intro);
+ rend_intro_point_free(intro);
+ } SMARTLIST_FOREACH_END(intro);
+
SMARTLIST_FOREACH_BEGIN(service->intro_nodes, rend_intro_point_t *,
intro) {
/* Find the introduction point node object. */
@@ -3455,13 +3882,14 @@ void
rend_service_desc_has_uploaded(const rend_data_t *rend_data)
{
rend_service_t *service;
+ const char *onion_address;
tor_assert(rend_data);
- service = rend_service_get_by_service_id(rend_data->onion_address);
+ onion_address = rend_data_get_address(rend_data);
+
+ service = rend_service_get_by_service_id(onion_address);
if (service == NULL) {
- log_warn(LD_REND, "Service %s not found after descriptor upload",
- safe_str_client(rend_data->onion_address));
return;
}
@@ -3478,6 +3906,18 @@ rend_service_desc_has_uploaded(const rend_data_t *rend_data)
} SMARTLIST_FOREACH_END(intro);
}
+/** Don't try to build more than this many circuits before giving up
+ * for a while. Dynamically calculated based on the configured number of
+ * introduction points for the service, n_intro_points_wanted. */
+static int
+rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted)
+{
+ /* Allow all but one of the initial connections to fail and be
+ * retried. (If all fail, we *want* to wait, because something is broken.) */
+ tor_assert(n_intro_points_wanted <= NUM_INTRO_POINTS_MAX);
+ return (int)(2*n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA);
+}
+
/** For every service, check how many intro points it currently has, and:
* - Invalidate introdution points based on specific criteria, see
* remove_invalid_intro_points comments.
@@ -3487,11 +3927,13 @@ rend_service_desc_has_uploaded(const rend_data_t *rend_data)
* This is called once a second by the main loop.
*/
void
-rend_consider_services_intro_points(void)
+rend_consider_services_intro_points(time_t now)
{
int i;
- time_t now;
const or_options_t *options = get_options();
+ /* Are we in single onion mode? */
+ const int allow_direct = rend_service_allow_non_anonymous_connection(
+ get_options());
/* List of nodes we need to _exclude_ when choosing a new node to
* establish an intro point to. */
smartlist_t *exclude_nodes;
@@ -3504,7 +3946,6 @@ rend_consider_services_intro_points(void)
exclude_nodes = smartlist_new();
retry_nodes = smartlist_new();
- now = time(NULL);
SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) {
int r;
@@ -3519,23 +3960,29 @@ rend_consider_services_intro_points(void)
smartlist_clear(exclude_nodes);
smartlist_clear(retry_nodes);
+ /* Cleanup the invalid intro points and save the node objects, if any,
+ * in the exclude_nodes and retry_nodes lists. */
+ remove_invalid_intro_points(service, exclude_nodes, retry_nodes, now);
+
/* This retry period is important here so we don't stress circuit
* creation. */
+
if (now > service->intro_period_started + INTRO_CIRC_RETRY_PERIOD) {
- /* One period has elapsed; we can try building circuits again. */
+ /* One period has elapsed:
+ * - if we stopped, we can try building circuits again,
+ * - if we haven't, we reset the circuit creation counts. */
+ rend_log_intro_limit(service, LOG_INFO);
service->intro_period_started = now;
service->n_intro_circuits_launched = 0;
} else if (service->n_intro_circuits_launched >=
- MAX_INTRO_CIRCS_PER_PERIOD) {
+ rend_max_intro_circs_per_period(
+ service->n_intro_points_wanted)) {
/* We have failed too many times in this period; wait for the next
- * one before we try again. */
+ * one before we try to initiate any more connections. */
+ rend_log_intro_limit(service, LOG_WARN);
continue;
}
- /* Cleanup the invalid intro points and save the node objects, if apply,
- * in the exclude_nodes and retry_nodes list. */
- remove_invalid_intro_points(service, exclude_nodes, retry_nodes, now);
-
/* Let's try to rebuild circuit on the nodes we want to retry on. */
SMARTLIST_FOREACH_BEGIN(retry_nodes, rend_intro_point_t *, intro) {
r = rend_service_launch_establish_intro(service, intro);
@@ -3555,17 +4002,17 @@ rend_consider_services_intro_points(void)
/* Avoid mismatched signed comparaison below. */
intro_nodes_len = (unsigned int) smartlist_len(service->intro_nodes);
- /* Quiescent state, no node expiring and we have more or the amount of
- * wanted node for this service. Proceed to the next service. Could be
- * more because we launch two preemptive circuits if our intro nodes
- * list is empty. */
- if (smartlist_len(service->expiring_nodes) == 0 &&
- intro_nodes_len >= service->n_intro_points_wanted) {
+ /* Quiescent state, we have more or the equal amount of wanted node for
+ * this service. Proceed to the next service. We can have more nodes
+ * because we launch extra preemptive circuits if our intro nodes list was
+ * originally empty for performance reasons. */
+ if (intro_nodes_len >= service->n_intro_points_wanted) {
continue;
}
- /* Number of intro points we want to open which is the wanted amount
- * minus the current amount of valid nodes. */
+ /* Number of intro points we want to open which is the wanted amount minus
+ * the current amount of valid nodes. We know that this won't underflow
+ * because of the check above. */
n_intro_points_to_open = service->n_intro_points_wanted - intro_nodes_len;
if (intro_nodes_len == 0) {
/* We want to end up with n_intro_points_wanted intro points, but if
@@ -3585,10 +4032,24 @@ rend_consider_services_intro_points(void)
const node_t *node;
rend_intro_point_t *intro;
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; "
@@ -3598,11 +4059,17 @@ 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);
+ if (BUG(intro->extend_info == NULL)) {
+ break;
+ }
intro->intro_key = crypto_pk_new();
const int fail = crypto_pk_generate_key(intro->intro_key);
tor_assert(!fail);
@@ -3647,8 +4114,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);
@@ -3659,6 +4127,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 =
@@ -3731,8 +4205,8 @@ rend_service_dump_stats(int severity)
for (i=0; i < smartlist_len(rend_service_list); ++i) {
service = smartlist_get(rend_service_list, i);
- tor_log(severity, LD_GENERAL, "Service configured in \"%s\":",
- service->directory);
+ tor_log(severity, LD_GENERAL, "Service configured in %s:",
+ rend_service_escaped_dir(service));
for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
intro = smartlist_get(service->intro_nodes, j);
safe_name = safe_str_client(intro->extend_info->nickname);
@@ -3749,60 +4223,6 @@ rend_service_dump_stats(int severity)
}
}
-#ifdef HAVE_SYS_UN_H
-
-/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
- * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
- * else return -ENOSYS if AF_UNIX is not supported (see function in the
- * #else statement below). */
-static int
-add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
-{
- tor_assert(ports);
- tor_assert(p);
- tor_assert(p->is_unix_addr);
-
- smartlist_add(ports, p);
- return 0;
-}
-
-/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
- * on success else return -ENOSYS if AF_UNIX is not supported (see function
- * in the #else statement below). */
-static int
-set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
-{
- tor_assert(conn);
- tor_assert(p);
- tor_assert(p->is_unix_addr);
-
- conn->base_.socket_family = AF_UNIX;
- tor_addr_make_unspec(&conn->base_.addr);
- conn->base_.port = 1;
- conn->base_.address = tor_strdup(p->unix_addr);
- return 0;
-}
-
-#else /* defined(HAVE_SYS_UN_H) */
-
-static int
-set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
-{
- (void) conn;
- (void) p;
- return -ENOSYS;
-}
-
-static int
-add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
-{
- (void) ports;
- (void) p;
- return -ENOSYS;
-}
-
-#endif /* HAVE_SYS_UN_H */
-
/** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for
* 'circ', and look up the port and address based on conn-\>port.
* Assign the actual conn-\>addr and conn-\>port. Return -2 on failure
@@ -3815,17 +4235,16 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
{
rend_service_t *service;
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
- smartlist_t *matching_ports;
- rend_service_port_config_t *chosen_port;
- unsigned int warn_once = 0;
+ const char *rend_pk_digest;
tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
tor_assert(circ->rend_data);
log_debug(LD_REND,"beginning to hunt for addr/port");
+ /* XXX: This is version 2 specific (only one supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circ->rend_data, NULL);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
- service = rend_service_get_by_pk_digest(
- circ->rend_data->rend_pk_digest);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
"rendezvous circuit %u; closing.",
@@ -3851,41 +4270,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
return service->max_streams_close_circuit ? -2 : -1;
}
}
- matching_ports = smartlist_new();
- SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
- {
- if (conn->base_.port != p->virtual_port) {
- continue;
- }
- if (!(p->is_unix_addr)) {
- smartlist_add(matching_ports, p);
- } else {
- if (add_unix_port(matching_ports, p)) {
- if (!warn_once) {
- /* Unix port not supported so warn only once. */
- log_warn(LD_REND,
- "Saw AF_UNIX virtual port mapping for port %d on service "
- "%s, which is unsupported on this platform. Ignoring it.",
- conn->base_.port, serviceid);
- }
- warn_once++;
- }
- }
- });
- chosen_port = smartlist_choose(matching_ports);
- smartlist_free(matching_ports);
- if (chosen_port) {
- if (!(chosen_port->is_unix_addr)) {
- /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
- tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr);
- conn->base_.port = chosen_port->real_port;
- } else {
- if (set_unix_port(conn, chosen_port)) {
- /* Simply impossible to end up here else we were able to add a Unix
- * port without AF_UNIX support... ? */
- tor_assert(0);
- }
- }
+
+ if (hs_set_conn_addr_port(service->ports, conn) == 0) {
+ /* Successfully set the port to the connection. We are done. */
return 0;
}
@@ -3899,3 +4286,67 @@ 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;
+}
+
+#ifdef TOR_UNIT_TESTS
+
+STATIC void
+set_rend_service_list(smartlist_t *new_list)
+{
+ rend_service_list = new_list;
+}
+
+STATIC void
+set_rend_rend_service_staging_list(smartlist_t *new_list)
+{
+ rend_service_staging_list = new_list;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index 101b37e18d..ed1044f04a 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -13,11 +13,9 @@
#define TOR_RENDSERVICE_H
#include "or.h"
+#include "hs_service.h"
typedef struct rend_intro_cell_s rend_intro_cell_t;
-typedef struct rend_service_port_config_s rend_service_port_config_t;
-
-#ifdef RENDSERVICE_PRIVATE
/* This can be used for both INTRODUCE1 and INTRODUCE2 */
@@ -63,14 +61,90 @@ struct rend_intro_cell_s {
uint8_t dh[DH_KEY_LEN];
};
-#endif
+#ifdef RENDSERVICE_PRIVATE
+
+/** 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_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);
+#ifdef TOR_UNIT_TESTS
-int num_rend_services(void);
-int rend_config_services(const or_options_t *options, int validate_only);
-int rend_service_load_all_keys(void);
+STATIC void set_rend_service_list(smartlist_t *new_list);
+STATIC void set_rend_rend_service_staging_list(smartlist_t *new_list);
+STATIC void rend_service_prune_list_impl_(void);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* RENDSERVICE_PRIVATE */
+
+int rend_num_services(void);
+int rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config);
+void rend_service_prune_list(void);
+void rend_service_free_staging_list(void);
+int rend_service_load_all_keys(const smartlist_t *service_list);
void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
smartlist_t *stat_lst);
-void rend_consider_services_intro_points(void);
+void rend_consider_services_intro_points(time_t now);
void rend_consider_services_upload(time_t now);
void rend_hsdir_routers_changed(void);
void rend_consider_descriptor_republication(void);
@@ -93,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request,
char **err_msg_out);
int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro,
char **err_msg_out);
+ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out,
+ size_t cell_body_out_len,
+ crypto_pk_t *intro_key,
+ const char *rend_circ_nonce);
int rend_service_validate_intro_late(const rend_intro_cell_t *intro,
char **err_msg_out);
void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);
@@ -100,14 +178,18 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn,
origin_circuit_t *circ);
void rend_service_dump_stats(int severity);
void rend_service_free_all(void);
+void rend_service_init(void);
rend_service_port_config_t *rend_service_parse_port_config(const char *string,
const char *sep,
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 +200,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 +210,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 b94ad29650..e65b93fa76 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -1,13 +1,77 @@
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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"
@@ -20,9 +84,13 @@
#include "router.h"
#include "routerlist.h"
#include "ht.h"
+#include "channelpadding.h"
+
+#include "channelpadding.h"
+#include "connection_or.h"
static void bw_arrays_init(void);
-static void predicted_ports_init(void);
+static void predicted_ports_alloc(void);
/** Total number of bytes currently allocated in fields used by rephist.c. */
uint64_t rephist_total_alloc=0;
@@ -101,6 +169,44 @@ typedef struct or_history_t {
digestmap_t *link_history_map;
} or_history_t;
+/**
+ * This structure holds accounting needed to calculate the padding overhead.
+ */
+typedef struct padding_counts_t {
+ /** Total number of cells we have received, including padding */
+ uint64_t read_cell_count;
+ /** Total number of cells we have sent, including padding */
+ uint64_t write_cell_count;
+ /** Total number of CELL_PADDING cells we have received */
+ uint64_t read_pad_cell_count;
+ /** Total number of CELL_PADDING cells we have sent */
+ uint64_t write_pad_cell_count;
+ /** Total number of read cells on padding-enabled conns */
+ uint64_t enabled_read_cell_count;
+ /** Total number of sent cells on padding-enabled conns */
+ uint64_t enabled_write_cell_count;
+ /** Total number of read CELL_PADDING cells on padding-enabled cons */
+ uint64_t enabled_read_pad_cell_count;
+ /** Total number of sent CELL_PADDING cells on padding-enabled cons */
+ uint64_t enabled_write_pad_cell_count;
+ /** Total number of RELAY_DROP cells we have received */
+ uint64_t read_drop_cell_count;
+ /** Total number of RELAY_DROP cells we have sent */
+ uint64_t write_drop_cell_count;
+ /** The maximum number of padding timers we've seen in 24 hours */
+ uint64_t maximum_chanpad_timers;
+ /** When did we first copy padding_current into padding_published? */
+ char first_published_at[ISO_TIME_LEN+1];
+} padding_counts_t;
+
+/** Holds the current values of our padding statistics.
+ * It is not published until it is transferred to padding_published. */
+static padding_counts_t padding_current;
+
+/** Remains fixed for a 24 hour period, and then is replaced
+ * by a redacted copy of padding_current */
+static padding_counts_t padding_published;
+
/** When did we last multiply all routers' weighted_run_length and
* total_run_weights by STABILITY_ALPHA? */
static time_t stability_last_downrated = 0;
@@ -200,7 +306,7 @@ rep_hist_init(void)
{
history_map = digestmap_new();
bw_arrays_init();
- predicted_ports_init();
+ predicted_ports_alloc();
}
/** Helper: note that we are no longer connected to the router with history
@@ -604,7 +710,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 +849,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 +1181,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;
}
@@ -1692,6 +1800,40 @@ typedef struct predicted_port_t {
/** A list of port numbers that have been used recently. */
static smartlist_t *predicted_ports_list=NULL;
+/** How long do we keep predicting circuits? */
+static int prediction_timeout=0;
+/** When was the last time we added a prediction entry (HS or port) */
+static time_t last_prediction_add_time=0;
+
+/**
+ * How much time left until we stop predicting circuits?
+ */
+int
+predicted_ports_prediction_time_remaining(time_t now)
+{
+ time_t idle_delta = now - last_prediction_add_time;
+
+ /* Protect against overflow of return value. This can happen if the clock
+ * jumps backwards in time. Update the last prediction time (aka last
+ * active time) to prevent it. This update is preferable to using monotonic
+ * time because it prevents clock jumps into the past from simply causing
+ * very long idle timeouts while the monotonic time stands still. */
+ if (last_prediction_add_time > now) {
+ last_prediction_add_time = now;
+ idle_delta = 0;
+ }
+
+ /* Protect against underflow of the return value. This can happen for very
+ * large periods of inactivity/system sleep. */
+ if (idle_delta > prediction_timeout)
+ return 0;
+
+ if (BUG((prediction_timeout - idle_delta) > INT_MAX)) {
+ return INT_MAX;
+ }
+
+ return (int)(prediction_timeout - idle_delta);
+}
/** We just got an application request for a connection with
* port <b>port</b>. Remember it for the future, so we can keep
@@ -1701,21 +1843,40 @@ static void
add_predicted_port(time_t now, uint16_t port)
{
predicted_port_t *pp = tor_malloc(sizeof(predicted_port_t));
+
+ // If the list is empty, re-randomize predicted ports lifetime
+ if (!any_predicted_circuits(now)) {
+ prediction_timeout = channelpadding_get_circuits_available_timeout();
+ }
+
+ last_prediction_add_time = now;
+
+ log_info(LD_CIRC,
+ "New port prediction added. Will continue predictive circ building "
+ "for %d more seconds.",
+ predicted_ports_prediction_time_remaining(now));
+
pp->port = port;
pp->time = now;
rephist_total_alloc += sizeof(*pp);
smartlist_add(predicted_ports_list, pp);
}
-/** Initialize whatever memory and structs are needed for predicting
+/**
+ * Allocate whatever memory and structs are needed for predicting
* which ports will be used. Also seed it with port 80, so we'll build
* circuits on start-up.
*/
static void
-predicted_ports_init(void)
+predicted_ports_alloc(void)
{
predicted_ports_list = smartlist_new();
- add_predicted_port(time(NULL), 80); /* add one to kickstart us */
+}
+
+void
+predicted_ports_init(void)
+{
+ add_predicted_port(time(NULL), 443); // Add a port to get us started
}
/** Free whatever memory is needed for predicting which ports will
@@ -1746,6 +1907,12 @@ rep_hist_note_used_port(time_t now, uint16_t port)
SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) {
if (pp->port == port) {
pp->time = now;
+
+ last_prediction_add_time = now;
+ log_info(LD_CIRC,
+ "New port prediction added. Will continue predictive circ "
+ "building for %d more seconds.",
+ predicted_ports_prediction_time_remaining(now));
return;
}
} SMARTLIST_FOREACH_END(pp);
@@ -1762,7 +1929,8 @@ rep_hist_get_predicted_ports(time_t now)
int predicted_circs_relevance_time;
smartlist_t *out = smartlist_new();
tor_assert(predicted_ports_list);
- predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime;
+
+ predicted_circs_relevance_time = prediction_timeout;
/* clean out obsolete entries */
SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) {
@@ -1822,6 +1990,18 @@ static time_t predicted_internal_capacity_time = 0;
void
rep_hist_note_used_internal(time_t now, int need_uptime, int need_capacity)
{
+ // If the list is empty, re-randomize predicted ports lifetime
+ if (!any_predicted_circuits(now)) {
+ prediction_timeout = channelpadding_get_circuits_available_timeout();
+ }
+
+ last_prediction_add_time = now;
+
+ log_info(LD_CIRC,
+ "New port prediction added. Will continue predictive circ building "
+ "for %d more seconds.",
+ predicted_ports_prediction_time_remaining(now));
+
predicted_internal_time = now;
if (need_uptime)
predicted_internal_uptime_time = now;
@@ -1835,7 +2015,8 @@ rep_hist_get_predicted_internal(time_t now, int *need_uptime,
int *need_capacity)
{
int predicted_circs_relevance_time;
- predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime;
+
+ predicted_circs_relevance_time = prediction_timeout;
if (!predicted_internal_time) { /* initialize it */
predicted_internal_time = now;
@@ -1857,7 +2038,7 @@ int
any_predicted_circuits(time_t now)
{
int predicted_circs_relevance_time;
- predicted_circs_relevance_time = get_options()->PredictedPortsRelevanceTime;
+ predicted_circs_relevance_time = prediction_timeout;
return smartlist_len(predicted_ports_list) ||
predicted_internal_time + predicted_circs_relevance_time >= now;
@@ -1867,118 +2048,22 @@ any_predicted_circuits(time_t now)
int
rep_hist_circbuilding_dormant(time_t now)
{
+ const or_options_t *options = get_options();
+
if (any_predicted_circuits(now))
return 0;
/* see if we'll still need to build testing circuits */
- if (server_mode(get_options()) &&
- (!check_whether_orport_reachable() || !circuit_enough_testing_circs()))
+ if (server_mode(options) &&
+ (!check_whether_orport_reachable(options) ||
+ !circuit_enough_testing_circs()))
return 0;
- if (!check_whether_dirport_reachable())
+ if (!check_whether_dirport_reachable(options))
return 0;
return 1;
}
-/** Structure to track how many times we've done each public key operation. */
-static struct {
- /** How many directory objects have we signed? */
- unsigned long n_signed_dir_objs;
- /** How many routerdescs have we signed? */
- unsigned long n_signed_routerdescs;
- /** How many directory objects have we verified? */
- unsigned long n_verified_dir_objs;
- /** How many routerdescs have we verified */
- unsigned long n_verified_routerdescs;
- /** How many onionskins have we encrypted to build circuits? */
- unsigned long n_onionskins_encrypted;
- /** How many onionskins have we decrypted to do circuit build requests? */
- unsigned long n_onionskins_decrypted;
- /** How many times have we done the TLS handshake as a client? */
- unsigned long n_tls_client_handshakes;
- /** How many times have we done the TLS handshake as a server? */
- unsigned long n_tls_server_handshakes;
- /** How many PK operations have we done as a hidden service client? */
- unsigned long n_rend_client_ops;
- /** How many PK operations have we done as a hidden service midpoint? */
- unsigned long n_rend_mid_ops;
- /** How many PK operations have we done as a hidden service provider? */
- unsigned long n_rend_server_ops;
-} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0};
-
-/** Increment the count of the number of times we've done <b>operation</b>. */
-void
-note_crypto_pk_op(pk_op_t operation)
-{
- switch (operation)
- {
- case SIGN_DIR:
- pk_op_counts.n_signed_dir_objs++;
- break;
- case SIGN_RTR:
- pk_op_counts.n_signed_routerdescs++;
- break;
- case VERIFY_DIR:
- pk_op_counts.n_verified_dir_objs++;
- break;
- case VERIFY_RTR:
- pk_op_counts.n_verified_routerdescs++;
- break;
- case ENC_ONIONSKIN:
- pk_op_counts.n_onionskins_encrypted++;
- break;
- case DEC_ONIONSKIN:
- pk_op_counts.n_onionskins_decrypted++;
- break;
- case TLS_HANDSHAKE_C:
- pk_op_counts.n_tls_client_handshakes++;
- break;
- case TLS_HANDSHAKE_S:
- pk_op_counts.n_tls_server_handshakes++;
- break;
- case REND_CLIENT:
- pk_op_counts.n_rend_client_ops++;
- break;
- case REND_MID:
- pk_op_counts.n_rend_mid_ops++;
- break;
- case REND_SERVER:
- pk_op_counts.n_rend_server_ops++;
- break;
- default:
- log_warn(LD_BUG, "Unknown pk operation %d", operation);
- }
-}
-
-/** Log the number of times we've done each public/private-key operation. */
-void
-dump_pk_ops(int severity)
-{
- tor_log(severity, LD_HIST,
- "PK operations: %lu directory objects signed, "
- "%lu directory objects verified, "
- "%lu routerdescs signed, "
- "%lu routerdescs verified, "
- "%lu onionskins encrypted, "
- "%lu onionskins decrypted, "
- "%lu client-side TLS handshakes, "
- "%lu server-side TLS handshakes, "
- "%lu rendezvous client operations, "
- "%lu rendezvous middle operations, "
- "%lu rendezvous server operations.",
- pk_op_counts.n_signed_dir_objs,
- pk_op_counts.n_verified_dir_objs,
- pk_op_counts.n_signed_routerdescs,
- pk_op_counts.n_verified_routerdescs,
- pk_op_counts.n_onionskins_encrypted,
- pk_op_counts.n_onionskins_decrypted,
- pk_op_counts.n_tls_client_handshakes,
- pk_op_counts.n_tls_server_handshakes,
- pk_op_counts.n_rend_client_ops,
- pk_op_counts.n_rend_mid_ops,
- pk_op_counts.n_rend_server_ops);
-}
-
/*** Exit port statistics ***/
/* Some constants */
@@ -2290,16 +2375,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
@@ -2369,7 +2454,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;
}
@@ -2410,15 +2495,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. */
@@ -2645,7 +2730,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)
{
@@ -2735,7 +2822,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_)
@@ -2930,7 +3017,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). */
@@ -2944,22 +3031,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. */
@@ -3071,16 +3158,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);
@@ -3135,8 +3226,7 @@ rep_hist_hs_stats_write(time_t now)
return start_of_hs_stats_interval + WRITE_STATS_INTERVAL;
}
-#define MAX_LINK_PROTO_TO_LOG 4
-static uint64_t link_proto_count[MAX_LINK_PROTO_TO_LOG+1][2];
+static uint64_t link_proto_count[MAX_LINK_PROTO+1][2];
/** Note that we negotiated link protocol version <b>link_proto</b>, on
* a connection that started here iff <b>started_here</b> is true.
@@ -3145,7 +3235,7 @@ void
rep_hist_note_negotiated_link_proto(unsigned link_proto, int started_here)
{
started_here = !!started_here; /* force to 0 or 1 */
- if (link_proto > MAX_LINK_PROTO_TO_LOG) {
+ if (link_proto > MAX_LINK_PROTO) {
log_warn(LD_BUG, "Can't log link protocol %u", link_proto);
return;
}
@@ -3153,6 +3243,165 @@ rep_hist_note_negotiated_link_proto(unsigned link_proto, int started_here)
link_proto_count[link_proto][started_here]++;
}
+/**
+ * Update the maximum count of total pending channel padding timers
+ * in this period.
+ */
+void
+rep_hist_padding_count_timers(uint64_t num_timers)
+{
+ if (num_timers > padding_current.maximum_chanpad_timers) {
+ padding_current.maximum_chanpad_timers = num_timers;
+ }
+}
+
+/**
+ * Count a cell that we sent for padding overhead statistics.
+ *
+ * RELAY_COMMAND_DROP and CELL_PADDING are accounted separately. Both should be
+ * counted for PADDING_TYPE_TOTAL.
+ */
+void
+rep_hist_padding_count_write(padding_type_t type)
+{
+ switch (type) {
+ case PADDING_TYPE_DROP:
+ padding_current.write_drop_cell_count++;
+ break;
+ case PADDING_TYPE_CELL:
+ padding_current.write_pad_cell_count++;
+ break;
+ case PADDING_TYPE_TOTAL:
+ padding_current.write_cell_count++;
+ break;
+ case PADDING_TYPE_ENABLED_TOTAL:
+ padding_current.enabled_write_cell_count++;
+ break;
+ case PADDING_TYPE_ENABLED_CELL:
+ padding_current.enabled_write_pad_cell_count++;
+ break;
+ }
+}
+
+/**
+ * Count a cell that we've received for padding overhead statistics.
+ *
+ * RELAY_COMMAND_DROP and CELL_PADDING are accounted separately. Both should be
+ * counted for PADDING_TYPE_TOTAL.
+ */
+void
+rep_hist_padding_count_read(padding_type_t type)
+{
+ switch (type) {
+ case PADDING_TYPE_DROP:
+ padding_current.read_drop_cell_count++;
+ break;
+ case PADDING_TYPE_CELL:
+ padding_current.read_pad_cell_count++;
+ break;
+ case PADDING_TYPE_TOTAL:
+ padding_current.read_cell_count++;
+ break;
+ case PADDING_TYPE_ENABLED_TOTAL:
+ padding_current.enabled_read_cell_count++;
+ break;
+ case PADDING_TYPE_ENABLED_CELL:
+ padding_current.enabled_read_pad_cell_count++;
+ break;
+ }
+}
+
+/**
+ * Reset our current padding statistics. Called once every 24 hours.
+ */
+void
+rep_hist_reset_padding_counts(void)
+{
+ memset(&padding_current, 0, sizeof(padding_current));
+}
+
+/**
+ * Copy our current cell counts into a structure for listing in our
+ * extra-info descriptor. Also perform appropriate rounding and redaction.
+ *
+ * This function is called once every 24 hours.
+ */
+#define MIN_CELL_COUNTS_TO_PUBLISH 1
+#define ROUND_CELL_COUNTS_TO 10000
+void
+rep_hist_prep_published_padding_counts(time_t now)
+{
+ memcpy(&padding_published, &padding_current, sizeof(padding_published));
+
+ if (padding_published.read_cell_count < MIN_CELL_COUNTS_TO_PUBLISH ||
+ padding_published.write_cell_count < MIN_CELL_COUNTS_TO_PUBLISH) {
+ memset(&padding_published, 0, sizeof(padding_published));
+ return;
+ }
+
+ format_iso_time(padding_published.first_published_at, now);
+#define ROUND_AND_SET_COUNT(x) (x) = round_uint64_to_next_multiple_of((x), \
+ ROUND_CELL_COUNTS_TO)
+ ROUND_AND_SET_COUNT(padding_published.read_pad_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.write_pad_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.read_drop_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.write_drop_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.write_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.read_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.enabled_read_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.enabled_read_pad_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.enabled_write_cell_count);
+ ROUND_AND_SET_COUNT(padding_published.enabled_write_pad_cell_count);
+#undef ROUND_AND_SET_COUNT
+}
+
+/**
+ * Returns an allocated string for extra-info documents for publishing
+ * padding statistics from the last 24 hour interval.
+ */
+char *
+rep_hist_get_padding_count_lines(void)
+{
+ char *result = NULL;
+
+ if (!padding_published.read_cell_count ||
+ !padding_published.write_cell_count) {
+ return NULL;
+ }
+
+ tor_asprintf(&result, "padding-counts %s (%d s)"
+ " bin-size="U64_FORMAT
+ " write-drop="U64_FORMAT
+ " write-pad="U64_FORMAT
+ " write-total="U64_FORMAT
+ " read-drop="U64_FORMAT
+ " read-pad="U64_FORMAT
+ " read-total="U64_FORMAT
+ " enabled-read-pad="U64_FORMAT
+ " enabled-read-total="U64_FORMAT
+ " enabled-write-pad="U64_FORMAT
+ " enabled-write-total="U64_FORMAT
+ " max-chanpad-timers="U64_FORMAT
+ "\n",
+ padding_published.first_published_at,
+ REPHIST_CELL_PADDING_COUNTS_INTERVAL,
+ U64_PRINTF_ARG(ROUND_CELL_COUNTS_TO),
+ U64_PRINTF_ARG(padding_published.write_drop_cell_count),
+ U64_PRINTF_ARG(padding_published.write_pad_cell_count),
+ U64_PRINTF_ARG(padding_published.write_cell_count),
+ U64_PRINTF_ARG(padding_published.read_drop_cell_count),
+ U64_PRINTF_ARG(padding_published.read_pad_cell_count),
+ U64_PRINTF_ARG(padding_published.read_cell_count),
+ U64_PRINTF_ARG(padding_published.enabled_read_pad_cell_count),
+ U64_PRINTF_ARG(padding_published.enabled_read_cell_count),
+ U64_PRINTF_ARG(padding_published.enabled_write_pad_cell_count),
+ U64_PRINTF_ARG(padding_published.enabled_write_cell_count),
+ U64_PRINTF_ARG(padding_published.maximum_chanpad_timers)
+ );
+
+ return result;
+}
+
/** Log a heartbeat message explaining how many connections of each link
* protocol version we have used.
*/
diff --git a/src/or/rephist.h b/src/or/rephist.h
index 145da97d02..8f6d46616d 100644
--- a/src/or/rephist.h
+++ b/src/or/rephist.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -48,6 +48,7 @@ double rep_hist_get_weighted_fractional_uptime(const char *id, time_t when);
long rep_hist_get_weighted_time_known(const char *id, time_t when);
int rep_hist_have_measured_enough_stability(void);
+void predicted_ports_init(void);
void rep_hist_note_used_port(time_t now, uint16_t port);
smartlist_t *rep_hist_get_predicted_ports(time_t now);
void rep_hist_remove_predicted_ports(const smartlist_t *rmv_ports);
@@ -59,9 +60,7 @@ int rep_hist_get_predicted_internal(time_t now, int *need_uptime,
int any_predicted_circuits(time_t now);
int rep_hist_circbuilding_dormant(time_t now);
-
-void note_crypto_pk_op(pk_op_t operation);
-void dump_pk_ops(int severity);
+int predicted_ports_prediction_time_remaining(time_t now);
void rep_hist_exit_stats_init(time_t now);
void rep_hist_reset_exit_stats(time_t now);
@@ -112,5 +111,37 @@ 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
+
+/**
+ * Represents the type of a cell for padding accounting
+ */
+typedef enum padding_type_t {
+ /** A RELAY_DROP cell */
+ PADDING_TYPE_DROP,
+ /** A CELL_PADDING cell */
+ PADDING_TYPE_CELL,
+ /** Total counts of padding and non-padding together */
+ PADDING_TYPE_TOTAL,
+ /** Total cell counts for all padding-enabled channels */
+ PADDING_TYPE_ENABLED_TOTAL,
+ /** CELL_PADDING counts for all padding-enabled channels */
+ PADDING_TYPE_ENABLED_CELL
+} padding_type_t;
+
+/** The amount of time over which the padding cell counts were counted */
+#define REPHIST_CELL_PADDING_COUNTS_INTERVAL (24*60*60)
+void rep_hist_padding_count_read(padding_type_t type);
+void rep_hist_padding_count_write(padding_type_t type);
+char *rep_hist_get_padding_count_lines(void);
+void rep_hist_reset_padding_counts(void);
+void rep_hist_prep_published_padding_counts(time_t now);
+void rep_hist_padding_count_timers(uint64_t num_timers);
+
#endif
diff --git a/src/or/replaycache.c b/src/or/replaycache.c
index 23a1737b18..3d42deb90a 100644
--- a/src/or/replaycache.c
+++ b/src/or/replaycache.c
@@ -1,10 +1,22 @@
- /* Copyright (c) 2012-2016, The Tor Project, Inc. */
+ /* Copyright (c) 2012-2017, 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/replaycache.h b/src/or/replaycache.h
index 64a6caf5f5..0d637939a4 100644
--- a/src/or/replaycache.h
+++ b/src/or/replaycache.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016, The Tor Project, Inc. */
+/* Copyright (c) 2012-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
diff --git a/src/or/router.c b/src/or/router.c
index 68bcf1326e..7fad572657 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define ROUTER_PRIVATE
@@ -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;
-
/************************************************************/
/*****
@@ -134,6 +148,51 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last)
tor_mutex_release(key_lock);
}
+/** Expire our old set of onion keys. This is done by setting
+ * last_curve25519_onion_key and lastonionkey to all zero's and NULL
+ * respectively.
+ *
+ * This function does not perform any grace period checks for the old onion
+ * keys.
+ */
+void
+expire_old_onion_keys(void)
+{
+ char *fname = NULL;
+
+ tor_mutex_acquire(key_lock);
+
+ /* Free lastonionkey and set it to NULL. */
+ if (lastonionkey) {
+ crypto_pk_free(lastonionkey);
+ lastonionkey = NULL;
+ }
+
+ /* We zero out the keypair. See the tor_mem_is_zero() check made in
+ * construct_ntor_key_map() below. */
+ memset(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key));
+
+ tor_mutex_release(key_lock);
+
+ fname = get_datadir_fname2("keys", "secret_onion_key.old");
+ if (file_status(fname) == FN_FILE) {
+ if (tor_unlink(fname) != 0) {
+ log_warn(LD_FS, "Couldn't unlink old onion key file %s: %s",
+ fname, strerror(errno));
+ }
+ }
+ tor_free(fname);
+
+ fname = get_datadir_fname2("keys", "secret_onion_key_ntor.old");
+ if (file_status(fname) == FN_FILE) {
+ if (tor_unlink(fname) != 0) {
+ log_warn(LD_FS, "Couldn't unlink old ntor onion key file %s: %s",
+ fname, strerror(errno));
+ }
+ }
+ tor_free(fname);
+}
+
/** Return the current secret onion key for the ntor handshake. Must only
* be called from the main thread. */
static const curve25519_keypair_t *
@@ -454,7 +513,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 +622,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;
@@ -668,6 +728,47 @@ v3_authority_check_key_expiry(void)
last_warned = now;
}
+/** Get the lifetime of an onion key in days. This value is defined by the
+ * network consesus parameter "onion-key-rotation-days". Always returns a value
+ * between <b>MIN_ONION_KEY_LIFETIME_DAYS</b> and
+ * <b>MAX_ONION_KEY_LIFETIME_DAYS</b>.
+ */
+static int
+get_onion_key_rotation_days_(void)
+{
+ return networkstatus_get_param(NULL,
+ "onion-key-rotation-days",
+ DEFAULT_ONION_KEY_LIFETIME_DAYS,
+ MIN_ONION_KEY_LIFETIME_DAYS,
+ MAX_ONION_KEY_LIFETIME_DAYS);
+}
+
+/** Get the current lifetime of an onion key in seconds. This value is defined
+ * by the network consesus parameter "onion-key-rotation-days", but the value
+ * is converted to seconds.
+ */
+int
+get_onion_key_lifetime(void)
+{
+ return get_onion_key_rotation_days_()*24*60*60;
+}
+
+/** Get the grace period of an onion key in seconds. This value is defined by
+ * the network consesus parameter "onion-key-grace-period-days", but the value
+ * is converted to seconds.
+ */
+int
+get_onion_key_grace_period(void)
+{
+ int grace_period;
+ grace_period = networkstatus_get_param(NULL,
+ "onion-key-grace-period-days",
+ DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS,
+ MIN_ONION_KEY_GRACE_PERIOD_DAYS,
+ get_onion_key_rotation_days_());
+ return grace_period*24*60*60;
+}
+
/** Set up Tor's TLS contexts, based on our configuration and keys. Return 0
* on success, and -1 on failure. */
int
@@ -678,12 +779,6 @@ router_initialize_tls_context(void)
int lifetime = options->SSLKeyLifetime;
if (public_server_mode(options))
flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER;
- if (options->TLSECGroup) {
- if (!strcasecmp(options->TLSECGroup, "P256"))
- flags |= TOR_TLS_CTX_USE_ECDHE_P256;
- else if (!strcasecmp(options->TLSECGroup, "P224"))
- flags |= TOR_TLS_CTX_USE_ECDHE_P224;
- }
if (!lifetime) { /* we should guess a good ssl cert lifetime */
/* choose between 5 and 365 days, and round to the day */
@@ -834,7 +929,12 @@ init_keys(void)
if (init_keys_common() < 0)
return -1;
/* Make sure DataDirectory exists, and is private. */
- if (check_private_dir(options->DataDirectory, CPD_CREATE, options->User)) {
+ cpd_check_t cpd_opts = CPD_CREATE;
+ if (options->DataDirectoryGroupReadable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(options->DataDirectory, cpd_opts, options->User)) {
+ log_err(LD_OR, "Can't create/check datadirectory %s",
+ options->DataDirectory);
return -1;
}
/* Check the key directory. */
@@ -886,7 +986,8 @@ init_keys(void)
}
/* 1d. Load all ed25519 keys */
- if (load_ed_keys(options,now) < 0)
+ const int new_signing_key = load_ed_keys(options,now);
+ if (new_signing_key < 0)
return -1;
/* 2. Read onion key. Make it if none is found. */
@@ -908,7 +1009,7 @@ init_keys(void)
/* We have no LastRotatedOnionKey set; either we just created the key
* or it's a holdover from 0.1.2.4-alpha-dev or earlier. In either case,
* start the clock ticking now so that we will eventually rotate it even
- * if we don't stay up for a full MIN_ONION_KEY_LIFETIME. */
+ * if we don't stay up for the full lifetime of an onion key. */
state->LastRotatedOnionKey = onionkey_set_at = now;
or_state_mark_dirty(state, options->AvoidDiskWrites ?
time(NULL)+3600 : 0);
@@ -956,7 +1057,7 @@ init_keys(void)
/* 3b. Get an ed25519 link certificate. Note that we need to do this
* after we set up the TLS context */
- if (generate_ed_link_cert(options, now) < 0) {
+ if (generate_ed_link_cert(options, now, new_signing_key > 0) < 0) {
log_err(LD_GENERAL,"Couldn't make link cert");
return -1;
}
@@ -964,7 +1065,7 @@ init_keys(void)
/* 4. Build our router descriptor. */
/* Must be called after keys are initialized. */
mydesc = router_get_my_descriptor();
- if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) {
+ if (authdir_mode_v3(options)) {
const char *m = NULL;
routerinfo_t *ri;
/* We need to add our own fingerprint so it gets recognized. */
@@ -1054,7 +1155,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;
}
@@ -1079,23 +1181,49 @@ router_reset_reachability(void)
can_reach_or_port = can_reach_dir_port = 0;
}
-/** Return 1 if ORPort is known reachable; else return 0. */
-int
-check_whether_orport_reachable(void)
+/** Return 1 if we won't do reachability checks, because:
+ * - AssumeReachable is set, or
+ * - the network is disabled.
+ * Otherwise, return 0.
+ */
+static int
+router_reachability_checks_disabled(const or_options_t *options)
{
- const or_options_t *options = get_options();
return options->AssumeReachable ||
+ net_is_disabled();
+}
+
+/** Return 0 if we need to do an ORPort reachability check, because:
+ * - no reachability check has been done yet, or
+ * - we've initiated reachability checks, but none have succeeded.
+ * Return 1 if we don't need to do an ORPort reachability check, because:
+ * - we've seen a successful reachability check, or
+ * - AssumeReachable is set, or
+ * - the network is disabled.
+ */
+int
+check_whether_orport_reachable(const or_options_t *options)
+{
+ int reach_checks_disabled = router_reachability_checks_disabled(options);
+ return reach_checks_disabled ||
can_reach_or_port;
}
-/** Return 1 if we don't have a dirport configured, or if it's reachable. */
+/** Return 0 if we need to do a DirPort reachability check, because:
+ * - no reachability check has been done yet, or
+ * - we've initiated reachability checks, but none have succeeded.
+ * Return 1 if we don't need to do a DirPort reachability check, because:
+ * - we've seen a successful reachability check, or
+ * - there is no DirPort set, or
+ * - AssumeReachable is set, or
+ * - the network is disabled.
+ */
int
-check_whether_dirport_reachable(void)
+check_whether_dirport_reachable(const or_options_t *options)
{
- const or_options_t *options = get_options();
- return !options->DirPort_set ||
- options->AssumeReachable ||
- net_is_disabled() ||
+ int reach_checks_disabled = router_reachability_checks_disabled(options) ||
+ !options->DirPort_set;
+ return reach_checks_disabled ||
can_reach_dir_port;
}
@@ -1136,9 +1264,9 @@ router_should_be_directory_server(const or_options_t *options, int dir_port)
if (accounting_is_enabled(options) &&
get_options()->AccountingRule != ACCT_IN) {
/* Don't spend bytes for directory traffic if we could end up hibernating,
- * but allow DirPort otherwise. Some people set AccountingMax because
- * they're confused or to get statistics. Directory traffic has a much
- * larger effect on output than input so there is no reason to turn it
+ * but allow DirPort otherwise. Some relay operators set AccountingMax
+ * because they're confused or to get statistics. Directory traffic has a
+ * much larger effect on output than input so there is no reason to turn it
* off if using AccountingRule in. */
int interval_length = accounting_get_interval_length();
uint32_t effective_bw = get_effective_bwrate(options);
@@ -1148,10 +1276,11 @@ router_should_be_directory_server(const or_options_t *options, int dir_port)
"seconds long. Raising to 1.");
interval_length = 1;
}
- log_info(LD_GENERAL, "Calculating whether to disable dirport: effective "
+ log_info(LD_GENERAL, "Calculating whether to advertise %s: effective "
"bwrate: %u, AccountingMax: "U64_FORMAT", "
- "accounting interval length %d", effective_bw,
- U64_PRINTF_ARG(options->AccountingMax),
+ "accounting interval length %d",
+ dir_port ? "dirport" : "begindir",
+ effective_bw, U64_PRINTF_ARG(options->AccountingMax),
interval_length);
acc_bytes = options->AccountingMax;
@@ -1199,48 +1328,85 @@ dir_server_mode(const or_options_t *options)
}
/** Look at a variety of factors, and return 0 if we don't want to
- * advertise the fact that we have a DirPort open, else return the
- * DirPort we want to advertise.
+ * advertise the fact that we have a DirPort open or begindir support, else
+ * return 1.
*
- * Log a helpful message if we change our mind about whether to publish
- * a DirPort.
+ * Where dir_port or supports_tunnelled_dir_requests are not relevant, they
+ * must be 0.
+ *
+ * Log a helpful message if we change our mind about whether to publish.
*/
static int
-decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
+decide_to_advertise_dir_impl(const or_options_t *options,
+ uint16_t dir_port,
+ int supports_tunnelled_dir_requests)
{
/* Part one: reasons to publish or not publish that aren't
* worth mentioning to the user, either because they're obvious
* or because they're normal behavior. */
- if (!dir_port) /* short circuit the rest of the function */
+ /* short circuit the rest of the function */
+ if (!dir_port && !supports_tunnelled_dir_requests)
return 0;
if (authdir_mode(options)) /* always publish */
- return dir_port;
+ return 1;
if (net_is_disabled())
return 0;
- if (!check_whether_dirport_reachable())
+ if (dir_port && !router_get_advertised_dir_port(options, dir_port))
return 0;
- if (!router_get_advertised_dir_port(options, dir_port))
+ if (supports_tunnelled_dir_requests &&
+ !router_get_advertised_or_port(options))
return 0;
- /* Part two: reasons to publish or not publish that the user
- * might find surprising. router_should_be_directory_server()
- * considers config options that make us choose not to publish. */
- return router_should_be_directory_server(options, dir_port) ? dir_port : 0;
+ /* Part two: consider config options that could make us choose to
+ * publish or not publish that the user might find surprising. */
+ return router_should_be_directory_server(options, dir_port);
+}
+
+/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to
+ * advertise the fact that we have a DirPort open, else return the
+ * DirPort we want to advertise.
+ */
+static int
+decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port)
+{
+ /* supports_tunnelled_dir_requests is not relevant, pass 0 */
+ return decide_to_advertise_dir_impl(options, dir_port, 0) ? dir_port : 0;
+}
+
+/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to
+ * advertise the fact that we support begindir requests, else return 1.
+ */
+static int
+decide_to_advertise_begindir(const or_options_t *options,
+ int supports_tunnelled_dir_requests)
+{
+ /* dir_port is not relevant, pass 0 */
+ return decide_to_advertise_dir_impl(options, 0,
+ supports_tunnelled_dir_requests);
}
/** 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));
+
+ const ed25519_public_key_t *ed_id_key;
+ if (r->cache_info.signing_key_cert)
+ ed_id_key = &r->cache_info.signing_key_cert->signing_key;
+ else
+ ed_id_key = NULL;
+
router_get_prim_orport(r, &ap);
return extend_info_new(r->nickname, r->cache_info.identity_digest,
+ ed_id_key,
r->onion_pkey, r->onion_curve25519_pkey,
&ap.addr, ap.port);
}
@@ -1260,9 +1426,9 @@ void
consider_testing_reachability(int test_or, int test_dir)
{
const routerinfo_t *me = router_get_my_routerinfo();
- int orport_reachable = check_whether_orport_reachable();
- tor_addr_t addr;
const or_options_t *options = get_options();
+ int orport_reachable = check_whether_orport_reachable(options);
+ tor_addr_t addr;
if (!me)
return;
@@ -1295,17 +1461,27 @@ consider_testing_reachability(int test_or, int test_dir)
/* XXX IPv6 self testing */
tor_addr_from_ipv4h(&addr, me->addr);
- if (test_dir && !check_whether_dirport_reachable() &&
+ if (test_dir && !check_whether_dirport_reachable(options) &&
!connection_get_by_type_addr_port_purpose(
CONN_TYPE_DIR, &addr, me->dir_port,
DIR_PURPOSE_FETCH_SERVERDESC)) {
+ tor_addr_port_t my_orport, my_dirport;
+ memcpy(&my_orport.addr, &addr, sizeof(addr));
+ memcpy(&my_dirport.addr, &addr, sizeof(addr));
+ my_orport.port = me->or_port;
+ my_dirport.port = me->dir_port;
/* ask myself, via tor, for my server descriptor. */
- directory_initiate_command(&addr, me->or_port,
- &addr, me->dir_port,
- me->cache_info.identity_digest,
- DIR_PURPOSE_FETCH_SERVERDESC,
- ROUTER_PURPOSE_GENERAL,
- DIRIND_ANON_DIRPORT, "authority.z", NULL, 0, 0);
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC);
+ directory_request_set_or_addr_port(req, &my_orport);
+ directory_request_set_dir_addr_port(req, &my_dirport);
+ directory_request_set_directory_id_digest(req,
+ me->cache_info.identity_digest);
+ // ask via an anon circuit, connecting to our dirport.
+ directory_request_set_indirection(req, DIRIND_ANON_DIRPORT);
+ directory_request_set_resource(req, "authority.z");
+ directory_initiate_request(req);
+ directory_request_free(req);
}
}
@@ -1314,18 +1490,19 @@ void
router_orport_found_reachable(void)
{
const routerinfo_t *me = router_get_my_routerinfo();
+ const or_options_t *options = get_options();
if (!can_reach_or_port && me) {
char *address = tor_dup_ip(me->addr);
log_notice(LD_OR,"Self-testing indicates your ORPort is reachable from "
"the outside. Excellent.%s",
- get_options()->PublishServerDescriptor_ != NO_DIRINFO
- && check_whether_dirport_reachable() ?
+ options->PublishServerDescriptor_ != NO_DIRINFO
+ && check_whether_dirport_reachable(options) ?
" Publishing server descriptor." : "");
can_reach_or_port = 1;
mark_my_descriptor_dirty("ORPort found reachable");
/* This is a significant enough change to upload immediately,
* at least in a test network */
- if (get_options()->TestingTorNetwork == 1) {
+ if (options->TestingTorNetwork == 1) {
reschedule_descriptor_update_check();
}
control_event_server_status(LOG_NOTICE,
@@ -1340,19 +1517,20 @@ void
router_dirport_found_reachable(void)
{
const routerinfo_t *me = router_get_my_routerinfo();
+ const or_options_t *options = get_options();
if (!can_reach_dir_port && me) {
char *address = tor_dup_ip(me->addr);
log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable "
"from the outside. Excellent.%s",
- get_options()->PublishServerDescriptor_ != NO_DIRINFO
- && check_whether_orport_reachable() ?
+ options->PublishServerDescriptor_ != NO_DIRINFO
+ && check_whether_orport_reachable(options) ?
" Publishing server descriptor." : "");
can_reach_dir_port = 1;
- if (decide_to_advertise_dirport(get_options(), me->dir_port)) {
+ if (decide_to_advertise_dirport(options, me->dir_port)) {
mark_my_descriptor_dirty("DirPort found reachable");
/* This is a significant enough change to upload immediately,
* at least in a test network */
- if (get_options()->TestingTorNetwork == 1) {
+ if (options->TestingTorNetwork == 1) {
reschedule_descriptor_update_check();
}
}
@@ -1418,32 +1596,19 @@ authdir_mode_v3(const or_options_t *options)
{
return authdir_mode(options) && options->V3AuthoritativeDir != 0;
}
-/** Return true iff we are a v3 directory authority. */
-int
-authdir_mode_any_main(const or_options_t *options)
-{
- return options->V3AuthoritativeDir;
-}
-/** Return true if we believe ourselves to be any kind of
- * authoritative directory beyond just a hidserv authority. */
-int
-authdir_mode_any_nonhidserv(const or_options_t *options)
-{
- return options->BridgeAuthoritativeDir ||
- authdir_mode_any_main(options);
-}
/** Return true iff we are an authoritative directory server that is
* authoritative about receiving and serving descriptors of type
- * <b>purpose</b> on its dirport. Use -1 for "any purpose". */
+ * <b>purpose</b> on its dirport.
+ */
int
authdir_mode_handles_descs(const or_options_t *options, int purpose)
{
- if (purpose < 0)
- return authdir_mode_any_nonhidserv(options);
+ if (BUG(purpose < 0)) /* Deprecated. */
+ return authdir_mode(options);
else if (purpose == ROUTER_PURPOSE_GENERAL)
- return authdir_mode_any_main(options);
+ return authdir_mode_v3(options);
else if (purpose == ROUTER_PURPOSE_BRIDGE)
- return (options->BridgeAuthoritativeDir);
+ return authdir_mode_bridge(options);
else
return 0;
}
@@ -1455,7 +1620,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
{
if (authdir_mode_bridge(options))
return 0;
- return authdir_mode_any_nonhidserv(options);
+ return authdir_mode(options);
}
/** Return true iff we are an authoritative directory server that
* tests reachability of the descriptors it learns about.
@@ -1463,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
int
authdir_mode_tests_reachability(const or_options_t *options)
{
- return authdir_mode_handles_descs(options, -1);
+ return authdir_mode(options);
}
/** Return true iff we believe ourselves to be a bridge authoritative
* directory server.
@@ -1480,8 +1645,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.*/
- return (options->ORPort_set || options->ORListenAddress);
+ return (options->ORPort_set);
}
/** Return true iff we are trying to be a non-bridge server.
@@ -1549,8 +1713,10 @@ proxy_mode(const or_options_t *options)
* and
* - We have ORPort set
* and
- * - We believe both our ORPort and DirPort (if present) are reachable from
+ * - We believe our ORPort and DirPort (if present) are reachable from
* the outside; or
+ * - We believe our ORPort is reachable from the outside, and we can't
+ * check our DirPort because the consensus has no exits; or
* - We are an authoritative directory server.
*/
static int
@@ -1568,8 +1734,15 @@ decide_if_publishable_server(void)
return 1;
if (!router_get_advertised_or_port(options))
return 0;
-
- return check_whether_orport_reachable() && check_whether_dirport_reachable();
+ if (!check_whether_orport_reachable(options))
+ return 0;
+ if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL) {
+ /* All set: there are no exits in the consensus (maybe this is a tiny
+ * test network), so we can't check our DirPort reachability. */
+ return 1;
+ } else {
+ return check_whether_dirport_reachable(options);
+ }
}
/** Initiate server descriptor upload as reasonable (if server is publishable,
@@ -1683,7 +1856,7 @@ static const char *desc_gen_reason = NULL;
* now. */
static time_t desc_clean_since = 0;
/** Why did we mark the descriptor dirty? */
-static const char *desc_dirty_reason = NULL;
+static const char *desc_dirty_reason = "Tor just started";
/** Boolean: do we need to regenerate the above? */
static int desc_needs_upload = 0;
@@ -1877,23 +2050,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
@@ -1913,19 +2174,23 @@ 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);
ri->addr = addr;
ri->or_port = router_get_advertised_or_port(options);
ri->dir_port = router_get_advertised_dir_port(options, 0);
- ri->supports_tunnelled_dir_requests = dir_server_mode(options) &&
- router_should_be_directory_server(options, ri->dir_port);
+ ri->supports_tunnelled_dir_requests =
+ directory_permits_begindir_requests(options);
ri->cache_info.published_on = time(NULL);
ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from
* main thread */
@@ -1943,8 +2208,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;
@@ -1970,11 +2234,14 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
routerinfo_free(ri);
return -1;
}
- ri->signing_key_cert = tor_cert_dup(get_master_signing_key_cert());
+ ri->cache_info.signing_key_cert =
+ tor_cert_dup(get_master_signing_key_cert());
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);
@@ -1991,8 +2258,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);
@@ -2002,17 +2269,15 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
}
if (options->MyFamily && ! options->BridgeRelay) {
- smartlist_t *family;
if (!warned_nonexistent_family)
warned_nonexistent_family = smartlist_new();
- family = smartlist_new();
ri->declared_family = smartlist_new();
- smartlist_split_string(family, options->MyFamily, ",",
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
- SMARTLIST_FOREACH_BEGIN(family, char *, name) {
+ config_line_t *family;
+ for (family = options->MyFamily; family; family = family->next) {
+ char *name = family->value;
const node_t *member;
if (!strcasecmp(name, options->Nickname))
- goto skip; /* Don't list ourself, that's redundant */
+ continue; /* Don't list ourself, that's redundant */
else
member = node_get_by_nickname(name, 1);
if (!member) {
@@ -2028,11 +2293,10 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
log_warn(LD_CONFIG, "There is a router named \"%s\" in my "
"declared family, but that isn't a legal nickname. "
"Skipping it.", escaped(name));
- smartlist_add(warned_nonexistent_family, tor_strdup(name));
+ smartlist_add_strdup(warned_nonexistent_family, name);
}
if (is_legal) {
- smartlist_add(ri->declared_family, name);
- name = NULL;
+ smartlist_add_strdup(ri->declared_family, name);
}
} else if (router_digest_is_me(member->identity)) {
/* Don't list ourself in our own family; that's redundant */
@@ -2046,15 +2310,11 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
if (smartlist_contains_string(warned_nonexistent_family, name))
smartlist_string_remove(warned_nonexistent_family, name);
}
- skip:
- tor_free(name);
- } SMARTLIST_FOREACH_END(name);
+ }
/* remove duplicates from the list */
smartlist_sort_strings(ri->declared_family);
smartlist_uniq_strings(ri->declared_family);
-
- smartlist_free(family);
}
/* Now generate the extrainfo. */
@@ -2062,7 +2322,9 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
ei->cache_info.is_extrainfo = 1;
strlcpy(ei->nickname, get_options()->Nickname, sizeof(ei->nickname));
ei->cache_info.published_on = ri->cache_info.published_on;
- ei->signing_key_cert = tor_cert_dup(get_master_signing_key_cert());
+ ei->cache_info.signing_key_cert =
+ tor_cert_dup(get_master_signing_key_cert());
+
memcpy(ei->cache_info.identity_digest, ri->cache_info.identity_digest,
DIGEST_LEN);
if (extrainfo_dump_to_string(&ei->cache_info.signed_descriptor_body,
@@ -2088,7 +2350,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
memcpy(ri->cache_info.extra_info_digest,
ei->cache_info.signed_descriptor_digest,
DIGEST_LEN);
- memcpy(ri->extra_info_digest256,
+ memcpy(ri->cache_info.extra_info_digest256,
ei->digest256,
DIGEST256_LEN);
} else {
@@ -2129,7 +2391,9 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
ri->cache_info.signed_descriptor_digest);
if (ei) {
- tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL));
+ tor_assert(!
+ routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
+ &ri->cache_info, NULL));
}
*r = ri;
@@ -2152,7 +2416,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()
@@ -2458,10 +2722,12 @@ router_dump_router_to_string(routerinfo_t *router,
const or_options_t *options = get_options();
smartlist_t *chunks = NULL;
char *output = NULL;
- const int emit_ed_sigs = signing_keypair && router->signing_key_cert;
+ const int emit_ed_sigs = signing_keypair &&
+ router->cache_info.signing_key_cert;
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)) {
@@ -2470,12 +2736,12 @@ router_dump_router_to_string(routerinfo_t *router,
goto err;
}
if (emit_ed_sigs) {
- if (!router->signing_key_cert->signing_key_included ||
- !ed25519_pubkey_eq(&router->signing_key_cert->signed_key,
+ if (!router->cache_info.signing_key_cert->signing_key_included ||
+ !ed25519_pubkey_eq(&router->cache_info.signing_key_cert->signed_key,
&signing_keypair->pubkey)) {
log_warn(LD_BUG, "Tried to sign a router descriptor with a mismatched "
"ed25519 key chain %d",
- router->signing_key_cert->signing_key_included);
+ router->cache_info.signing_key_cert->signing_key_included);
goto err;
}
}
@@ -2491,14 +2757,14 @@ router_dump_router_to_string(routerinfo_t *router,
char ed_cert_base64[256];
char ed_fp_base64[ED25519_BASE64_LEN+1];
if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
- (const char*)router->signing_key_cert->encoded,
- router->signing_key_cert->encoded_len,
- BASE64_ENCODE_MULTILINE) < 0) {
+ (const char*)router->cache_info.signing_key_cert->encoded,
+ router->cache_info.signing_key_cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
goto err;
}
if (ed25519_public_to_base64(ed_fp_base64,
- &router->signing_key_cert->signing_key)<0) {
+ &router->cache_info.signing_key_cert->signing_key)<0) {
log_err(LD_BUG,"Couldn't base64-encode identity key\n");
goto err;
}
@@ -2525,15 +2791,15 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* Cross-certify with RSA key */
- if (tap_key && router->signing_key_cert &&
- router->signing_key_cert->signing_key_included) {
+ if (tap_key && router->cache_info.signing_key_cert &&
+ router->cache_info.signing_key_cert->signing_key_included) {
char buf[256];
int tap_cc_len = 0;
uint8_t *tap_cc =
make_tap_onion_key_crosscert(tap_key,
- &router->signing_key_cert->signing_key,
- router->identity_pkey,
- &tap_cc_len);
+ &router->cache_info.signing_key_cert->signing_key,
+ router->identity_pkey,
+ &tap_cc_len);
if (!tap_cc) {
log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!");
goto err;
@@ -2555,16 +2821,16 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* Cross-certify with onion keys */
- if (ntor_keypair && router->signing_key_cert &&
- router->signing_key_cert->signing_key_included) {
+ if (ntor_keypair && router->cache_info.signing_key_cert &&
+ router->cache_info.signing_key_cert->signing_key_included) {
int sign = 0;
char buf[256];
/* XXXX Base the expiration date on the actual onion key expiration time?*/
tor_cert_t *cert =
make_ntor_onion_key_crosscert(ntor_keypair,
- &router->signing_key_cert->signing_key,
- router->cache_info.published_on,
- MIN_ONION_KEY_LIFETIME, &sign);
+ &router->cache_info.signing_key_cert->signing_key,
+ router->cache_info.published_on,
+ get_onion_key_lifetime(), &sign);
if (!cert) {
log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!");
goto err;
@@ -2603,9 +2869,9 @@ router_dump_router_to_string(routerinfo_t *router,
char extra_info_digest[HEX_DIGEST_LEN+1];
base16_encode(extra_info_digest, sizeof(extra_info_digest),
router->cache_info.extra_info_digest, DIGEST_LEN);
- if (!tor_digest256_is_zero(router->extra_info_digest256)) {
+ if (!tor_digest256_is_zero(router->cache_info.extra_info_digest256)) {
char d256_64[BASE64_DIGEST256_LEN+1];
- digest256_to_base64(d256_64, router->extra_info_digest256);
+ digest256_to_base64(d256_64, router->cache_info.extra_info_digest256);
tor_asprintf(&extra_info_line, "extra-info-digest %s %s\n",
extra_info_digest, d256_64);
} else {
@@ -2626,6 +2892,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();
@@ -2635,7 +2907,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"
@@ -2644,7 +2916,7 @@ router_dump_router_to_string(routerinfo_t *router,
"onion-key\n%s"
"signing-key\n%s"
"%s%s"
- "%s%s%s%s",
+ "%s%s%s",
router->nickname,
address,
router->or_port,
@@ -2652,6 +2924,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,
@@ -2666,8 +2939,7 @@ router_dump_router_to_string(routerinfo_t *router,
ntor_cc_line ? ntor_cc_line : "",
family_line,
we_are_hibernating() ? "hibernating 1\n" : "",
- "hidden-service-dir\n",
- options->AllowSingleHopExits ? "allow-single-hop-exits\n" : "");
+ "hidden-service-dir\n");
if (options->ContactInfo && strlen(options->ContactInfo)) {
const char *ci = options->ContactInfo;
@@ -2682,11 +2954,15 @@ 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'. */
if (!router->exit_policy || !smartlist_len(router->exit_policy)) {
- smartlist_add(chunks, tor_strdup("reject *:*\n"));
+ smartlist_add_strdup(chunks, "reject *:*\n");
} else if (router->exit_policy) {
char *exit_policy = router_dump_exit_policy_to_string(router,1,0);
@@ -2706,13 +2982,14 @@ router_dump_router_to_string(routerinfo_t *router,
tor_free(p6);
}
- if (router->supports_tunnelled_dir_requests) {
- smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n"));
+ if (decide_to_advertise_begindir(options,
+ router->supports_tunnelled_dir_requests)) {
+ smartlist_add_strdup(chunks, "tunnelled-dir-server\n");
}
/* Sign the descriptor with Ed25519 */
if (emit_ed_sigs) {
- smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
ED_DESC_SIGNATURE_PREFIX,
chunks, "", DIGEST_SHA256);
@@ -2728,11 +3005,10 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* Sign the descriptor with RSA */
- smartlist_add(chunks, tor_strdup("router-signature\n"));
+ smartlist_add_strdup(chunks, "router-signature\n");
crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
- note_crypto_pk_op(SIGN_RTR);
{
char *sig;
if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) {
@@ -2743,7 +3019,7 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* include a last '\n' */
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
output = smartlist_join_strings(chunks, "", 0, NULL);
@@ -2783,6 +3059,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;
}
@@ -2910,7 +3187,8 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
time_t now = time(NULL);
smartlist_t *chunks = smartlist_new();
extrainfo_t *ei_tmp = NULL;
- const int emit_ed_sigs = signing_keypair && extrainfo->signing_key_cert;
+ const int emit_ed_sigs = signing_keypair &&
+ extrainfo->cache_info.signing_key_cert;
char *ed_cert_line = NULL;
base16_encode(identity, sizeof(identity),
@@ -2918,19 +3196,19 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
format_iso_time(published, extrainfo->cache_info.published_on);
bandwidth_usage = rep_hist_get_bandwidth_lines();
if (emit_ed_sigs) {
- if (!extrainfo->signing_key_cert->signing_key_included ||
- !ed25519_pubkey_eq(&extrainfo->signing_key_cert->signed_key,
+ if (!extrainfo->cache_info.signing_key_cert->signing_key_included ||
+ !ed25519_pubkey_eq(&extrainfo->cache_info.signing_key_cert->signed_key,
&signing_keypair->pubkey)) {
log_warn(LD_BUG, "Tried to sign a extrainfo descriptor with a "
"mismatched ed25519 key chain %d",
- extrainfo->signing_key_cert->signing_key_included);
+ extrainfo->cache_info.signing_key_cert->signing_key_included);
goto err;
}
char ed_cert_base64[256];
if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
- (const char*)extrainfo->signing_key_cert->encoded,
- extrainfo->signing_key_cert->encoded_len,
- BASE64_ENCODE_MULTILINE) < 0) {
+ (const char*)extrainfo->cache_info.signing_key_cert->encoded,
+ extrainfo->cache_info.signing_key_cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
goto err;
}
@@ -2989,6 +3267,12 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
}
}
+ if (options->PaddingStatistics) {
+ contents = rep_hist_get_padding_count_lines();
+ if (contents)
+ smartlist_add(chunks, contents);
+ }
+
/* Add information about the pluggable transports we support. */
if (options->ServerTransportPlugin) {
char *pluggable_transports = pt_get_extra_info_descriptor_string();
@@ -2999,28 +3283,28 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
if (should_record_bridge_info(options) && write_stats_to_extrainfo) {
const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now);
if (bridge_stats) {
- smartlist_add(chunks, tor_strdup(bridge_stats));
+ smartlist_add_strdup(chunks, bridge_stats);
}
}
if (emit_ed_sigs) {
- char digest[DIGEST256_LEN];
- smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
- crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
+ char sha256_digest[DIGEST256_LEN];
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
+ 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);
}
- smartlist_add(chunks, tor_strdup("router-signature\n"));
+ smartlist_add_strdup(chunks, "router-signature\n");
s = smartlist_join_strings(chunks, "", 0, NULL);
while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) {
@@ -3055,7 +3339,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
"descriptor.");
goto err;
}
- smartlist_add(chunks, tor_strdup(sig));
+ smartlist_add_strdup(chunks, sig);
tor_free(s);
s = smartlist_join_strings(chunks, "", 0, NULL);
@@ -3088,7 +3372,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 5165462a13..97f331713a 100644
--- a/src/or/router.h
+++ b/src/or/router.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -27,10 +27,13 @@ crypto_pk_t *get_my_v3_authority_signing_key(void);
authority_cert_t *get_my_v3_legacy_cert(void);
crypto_pk_t *get_my_v3_legacy_signing_key(void);
void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last);
+void expire_old_onion_keys(void);
void rotate_onion_key(void);
crypto_pk_t *init_key_from_file(const char *fname, int generate,
int severity, int log_greeting);
void v3_authority_check_key_expiry(void);
+int get_onion_key_lifetime(void);
+int get_onion_key_grace_period(void);
di_digest256_map_t *construct_ntor_key_map(void);
void ntor_key_map_free(di_digest256_map_t *map);
@@ -39,8 +42,8 @@ int router_initialize_tls_context(void);
int init_keys(void);
int init_keys_client(void);
-int check_whether_orport_reachable(void);
-int check_whether_dirport_reachable(void);
+int check_whether_orport_reachable(const or_options_t *options);
+int check_whether_dirport_reachable(const or_options_t *options);
int dir_server_mode(const or_options_t *options);
void consider_testing_reachability(int test_or, int test_dir);
void router_orport_found_reachable(void);
@@ -51,8 +54,6 @@ int net_is_disabled(void);
int authdir_mode(const or_options_t *options);
int authdir_mode_v3(const or_options_t *options);
-int authdir_mode_any_main(const or_options_t *options);
-int authdir_mode_any_nonhidserv(const or_options_t *options);
int authdir_mode_handles_descs(const or_options_t *options, int purpose);
int authdir_mode_publishes_statuses(const or_options_t *options);
int authdir_mode_tests_reachability(const or_options_t *options);
@@ -91,7 +92,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..2f20758b5b 100644
--- a/src/or/routerkeys.c
+++ b/src/or/routerkeys.c
@@ -1,12 +1,17 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file routerkeys.c
*
* \brief Functions and structures to handle generating and maintaining the
- * set of keypairs necessary to be an OR. (Some of the code in router.c
- * belongs here.)
+ * set of keypairs necessary to be an OR.
+ *
+ * The keys handled here now are the Ed25519 keys that Tor relays use to sign
+ * descriptors, authenticate themselves on links, and identify one another
+ * uniquely. Other keys are maintained in router.c and rendservice.c.
+ *
+ * (TODO: The keys in router.c should go here too.)
*/
#include "or.h"
@@ -19,6 +24,7 @@
#define ENC_KEY_HEADER "Boxed Ed25519 key"
#define ENC_KEY_TAG "master"
+/* DOCDOC */
static ssize_t
do_getpass(const char *prompt, char *buf, size_t buflen,
int twice, const or_options_t *options)
@@ -48,7 +54,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));
@@ -85,6 +91,7 @@ do_getpass(const char *prompt, char *buf, size_t buflen,
return length;
}
+/* DOCDOC */
int
read_encrypted_secret_key(ed25519_secret_key_t *out,
const char *fname)
@@ -115,20 +122,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;
}
@@ -157,6 +164,7 @@ read_encrypted_secret_key(ed25519_secret_key_t *out,
return r;
}
+/* DOCDOC */
int
write_encrypted_secret_key(const ed25519_secret_key_t *key,
const char *fname)
@@ -200,6 +208,7 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key,
return r;
}
+/* DOCDOC */
static int
write_secret_key(const ed25519_secret_key_t *key, int encrypted,
const char *fname,
@@ -659,10 +668,14 @@ static tor_cert_t *auth_key_cert = NULL;
static uint8_t *rsa_ed_crosscert = NULL;
static size_t rsa_ed_crosscert_len = 0;
+static time_t rsa_ed_crosscert_expiration = 0;
/**
* Running as a server: load, reload, or refresh our ed25519 keys and
* certificates, creating and saving new ones as needed.
+ *
+ * Return -1 on failure; 0 on success if the signing key was not replaced;
+ * and 1 on success if the signing key was replaced.
*/
int
load_ed_keys(const or_options_t *options, time_t now)
@@ -675,6 +688,11 @@ load_ed_keys(const or_options_t *options, time_t now)
const tor_cert_t *check_signing_cert = NULL;
tor_cert_t *sign_cert = NULL;
tor_cert_t *auth_cert = NULL;
+ int signing_key_changed = 0;
+
+ // It is later than 1972, since otherwise there would be no C compilers.
+ // (Try to diagnose #22466.)
+ tor_assert_nonfatal(now >= 2 * 365 * 86400);
#define FAIL(msg) do { \
log_warn(LD_OR, (msg)); \
@@ -690,8 +708,10 @@ load_ed_keys(const or_options_t *options, time_t now)
tor_cert_free(cert); \
cert = (newval); \
} while (0)
+#define HAPPENS_SOON(when, interval) \
+ ((when) < now + (interval))
#define EXPIRES_SOON(cert, interval) \
- (!(cert) || (cert)->valid_until < now + (interval))
+ (!(cert) || HAPPENS_SOON((cert)->valid_until, (interval)))
/* XXXX support encrypted identity keys fully */
@@ -710,7 +730,23 @@ load_ed_keys(const or_options_t *options, time_t now)
use_signing = sign;
}
+ if (use_signing) {
+ /* We loaded a signing key with its certificate. */
+ if (! master_signing_key) {
+ /* We didn't know one before! */
+ signing_key_changed = 1;
+ } else if (! ed25519_pubkey_eq(&use_signing->pubkey,
+ &master_signing_key->pubkey) ||
+ ! tor_memeq(use_signing->seckey.seckey,
+ master_signing_key->seckey.seckey,
+ ED25519_SECKEY_LEN)) {
+ /* We loaded a different signing key than the one we knew before. */
+ signing_key_changed = 1;
+ }
+ }
+
if (!use_signing && master_signing_key) {
+ /* We couldn't load a signing key, but we already had one loaded */
check_signing_cert = signing_key_cert;
use_signing = master_signing_key;
}
@@ -733,8 +769,12 @@ load_ed_keys(const or_options_t *options, time_t now)
if (need_new_signing_key) {
log_notice(LD_OR, "It looks like I need to generate and sign a new "
- "medium-term signing key, because %s. To do that, I need to "
- "load%s the permanent master identity key.",
+ "medium-term signing key, because %s. To do that, I "
+ "need to load%s the permanent master identity key. "
+ "If the master identity key was not moved or encrypted "
+ "with a passphrase, this will be done automatically and "
+ "no further action is required. Otherwise, provide the "
+ "necessary data using 'tor --keygen' to do it manually.",
(NULL == use_signing) ? "I don't have one" :
EXPIRES_SOON(check_signing_cert, 0) ? "the one I have is expired" :
"you asked me to make one with --keygen",
@@ -742,15 +782,19 @@ load_ed_keys(const or_options_t *options, time_t now)
} else if (want_new_signing_key && !offline_master) {
log_notice(LD_OR, "It looks like I should try to generate and sign a "
"new medium-term signing key, because the one I have is "
- "going to expire soon. To do that, I'm going to have to try to "
- "load the permanent master identity key.");
+ "going to expire soon. To do that, I'm going to have to "
+ "try to load the permanent master identity key. "
+ "If the master identity key was not moved or encrypted "
+ "with a passphrase, this will be done automatically and "
+ "no further action is required. Otherwise, provide the "
+ "necessary data using 'tor --keygen' to do it manually.");
} else if (want_new_signing_key) {
log_notice(LD_OR, "It looks like I should try to generate and sign a "
"new medium-term signing key, because the one I have is "
"going to expire soon. But OfflineMasterKey is set, so I "
- "won't try to load a permanent master identity key is set. "
- "You will need to use 'tor --keygen' make a new signing key "
- "and certificate.");
+ "won't try to load a permanent master identity key. You "
+ "will need to use 'tor --keygen' to make a new signing "
+ "key and certificate.");
}
{
@@ -768,8 +812,11 @@ load_ed_keys(const or_options_t *options, time_t now)
if (options->command == CMD_KEYGEN)
flags |= INIT_ED_KEY_TRY_ENCRYPTED;
- /* Check the key directory */
- if (check_private_dir(options->DataDirectory, CPD_CREATE, options->User)) {
+ /* Check/Create the key directory */
+ cpd_check_t cpd_opts = CPD_CREATE;
+ if (options->DataDirectoryGroupReadable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(options->DataDirectory, cpd_opts, options->User)) {
log_err(LD_OR, "Can't create/check datadirectory %s",
options->DataDirectory);
goto err;
@@ -859,6 +906,7 @@ load_ed_keys(const or_options_t *options, time_t now)
if (!sign)
FAIL("Missing signing key");
use_signing = sign;
+ signing_key_changed = 1;
tor_assert(sign_cert->signing_key_included);
tor_assert(ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey));
@@ -879,17 +927,23 @@ load_ed_keys(const or_options_t *options, time_t now)
if (options->command == CMD_KEYGEN)
goto end;
- if (!rsa_ed_crosscert && server_mode(options)) {
+ if (server_mode(options) &&
+ (!rsa_ed_crosscert ||
+ HAPPENS_SOON(rsa_ed_crosscert_expiration, 30*86400))) {
uint8_t *crosscert;
+ time_t expiration = now+6*30*86400; /* 6 months in the future. */
ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey,
get_server_identity_key(),
- now+10*365*86400,/*XXXX*/
+ expiration,
&crosscert);
+ tor_free(rsa_ed_crosscert);
rsa_ed_crosscert_len = crosscert_len;
rsa_ed_crosscert = crosscert;
+ rsa_ed_crosscert_expiration = expiration;
}
if (!current_auth_key ||
+ signing_key_changed ||
EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop)) {
auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT,
now,
@@ -917,7 +971,7 @@ load_ed_keys(const or_options_t *options, time_t now)
SET_CERT(auth_key_cert, auth_cert);
}
- return 0;
+ return signing_key_changed;
err:
ed25519_keypair_free(id);
ed25519_keypair_free(sign);
@@ -927,21 +981,39 @@ load_ed_keys(const or_options_t *options, time_t now)
return -1;
}
-/* DOCDOC */
+/**
+ * Retrieve our currently-in-use Ed25519 link certificate and id certificate,
+ * and, if they would expire soon (based on the time <b>now</b>, generate new
+ * certificates (without embedding the public part of the signing key inside).
+ * If <b>force</b> is true, always generate a new certificate.
+ *
+ * The signed_key from the current id->signing certificate will be used to
+ * sign the new key within newly generated X509 certificate.
+ *
+ * Returns -1 upon error. Otherwise, returns 0 upon success (either when the
+ * current certificate is still valid, or when a new certificate was
+ * successfully generated, or no certificate was needed).
+ */
int
-generate_ed_link_cert(const or_options_t *options, time_t now)
+generate_ed_link_cert(const or_options_t *options, time_t now,
+ int force)
{
- 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) {
+ if (!server_mode(options)) {
+ /* No need to make an Ed25519->Link cert: we are a client */
+ return 0;
+ }
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 &&
+ if (force == 0 &&
+ link_cert_cert &&
! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) &&
fast_memeq(digests->d[DIGEST_SHA256], link_cert_cert->signed_key.pubkey,
DIGEST256_LEN)) {
@@ -967,6 +1039,17 @@ generate_ed_link_cert(const or_options_t *options, time_t now)
#undef SET_KEY
#undef SET_CERT
+/**
+ * Return 1 if any of the following are true:
+ *
+ * - if one of our Ed25519 signing, auth, or link certificates would expire
+ * soon w.r.t. the time <b>now</b>,
+ * - if we do not currently have a link certificate, or
+ * - if our cached Ed25519 link certificate is not same as the one we're
+ * currently using.
+ *
+ * Otherwise, returns 0.
+ */
int
should_make_new_ed_keys(const or_options_t *options, const time_t now)
{
@@ -979,12 +1062,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,
@@ -996,6 +1079,164 @@ should_make_new_ed_keys(const or_options_t *options, const time_t now)
}
#undef EXPIRES_SOON
+#undef HAPPENS_SOON
+
+#ifdef TOR_UNIT_TESTS
+/* Helper for unit tests: populate the ed25519 keys without saving or
+ * loading */
+void
+init_mock_ed_keys(const crypto_pk_t *rsa_identity_key)
+{
+ routerkeys_free_all();
+
+#define MAKEKEY(k) \
+ k = tor_malloc_zero(sizeof(*k)); \
+ if (ed25519_keypair_generate(k, 0) < 0) { \
+ log_warn(LD_BUG, "Couldn't make a keypair"); \
+ goto err; \
+ }
+ MAKEKEY(master_identity_key);
+ MAKEKEY(master_signing_key);
+ MAKEKEY(current_auth_key);
+#define MAKECERT(cert, signing, signed_, type, flags) \
+ cert = tor_cert_create(signing, \
+ type, \
+ &signed_->pubkey, \
+ time(NULL), 86400, \
+ flags); \
+ if (!cert) { \
+ log_warn(LD_BUG, "Couldn't make a %s certificate!", #cert); \
+ goto err; \
+ }
+
+ MAKECERT(signing_key_cert,
+ master_identity_key, master_signing_key, CERT_TYPE_ID_SIGNING,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ MAKECERT(auth_key_cert,
+ master_signing_key, current_auth_key, CERT_TYPE_SIGNING_AUTH, 0);
+
+ if (generate_ed_link_cert(get_options(), time(NULL), 0) < 0) {
+ log_warn(LD_BUG, "Couldn't make link certificate");
+ goto err;
+ }
+
+ rsa_ed_crosscert_len = tor_make_rsa_ed25519_crosscert(
+ &master_identity_key->pubkey,
+ rsa_identity_key,
+ time(NULL)+86400,
+ &rsa_ed_crosscert);
+
+ return;
+
+ err:
+ routerkeys_free_all();
+ tor_assert_nonfatal_unreached();
+}
+#undef MAKEKEY
+#undef MAKECERT
+#endif
+
+/**
+ * Print the ISO8601-formated <b>expiration</b> for a certificate with
+ * some <b>description</b> to stdout.
+ *
+ * For example, for a signing certificate, this might print out:
+ * signing-cert-expiry: 2017-07-25 08:30:15 UTC
+ */
+static void
+print_cert_expiration(const char *expiration,
+ const char *description)
+{
+ fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration);
+}
+
+/**
+ * Log when a certificate, <b>cert</b>, with some <b>description</b> and
+ * stored in a file named <b>fname</b>, is going to expire.
+ */
+static void
+log_ed_cert_expiration(const tor_cert_t *cert,
+ const char *description,
+ const char *fname) {
+ char expiration[ISO_TIME_LEN+1];
+
+ if (BUG(!cert)) { /* If the specified key hasn't been loaded */
+ log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.",
+ description);
+ } else {
+ format_local_iso_time(expiration, cert->valid_until);
+ log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.",
+ description, fname, expiration);
+ print_cert_expiration(expiration, description);
+ }
+}
+
+/**
+ * Log when our master signing key certificate expires. Used when tor is given
+ * the --key-expiration command-line option.
+ *
+ * Returns 0 on success and 1 on failure.
+ */
+static int
+log_master_signing_key_cert_expiration(const or_options_t *options)
+{
+ const tor_cert_t *signing_key;
+ char *fn = NULL;
+ int failed = 0;
+ time_t now = approx_time();
+
+ fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert");
+
+ /* Try to grab our cached copy of the key. */
+ signing_key = get_master_signing_key_cert();
+
+ tor_assert(server_identity_key_is_set());
+
+ /* Load our keys from disk, if necessary. */
+ if (!signing_key) {
+ failed = load_ed_keys(options, now) < 0;
+ signing_key = get_master_signing_key_cert();
+ }
+
+ /* If we do have a signing key, log the expiration time. */
+ if (signing_key) {
+ log_ed_cert_expiration(signing_key, "signing", fn);
+ } else {
+ log_warn(LD_OR, "Could not load signing key certificate from %s, so " \
+ "we couldn't learn anything about certificate expiration.", fn);
+ }
+
+ tor_free(fn);
+
+ return failed;
+}
+
+/**
+ * Log when a key certificate expires. Used when tor is given the
+ * --key-expiration command-line option.
+ *
+ * If an command argument is given, which should specify the type of
+ * key to get expiry information about (currently supported arguments
+ * are "sign"), get info about that type of certificate. Otherwise,
+ * print info about the supported arguments.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+int
+log_cert_expiration(void)
+{
+ const or_options_t *options = get_options();
+ const char *arg = options->command_arg;
+
+ if (!strcmp(arg, "sign")) {
+ return log_master_signing_key_cert_expiration(options);
+ } else {
+ fprintf(stderr, "No valid argument to --key-expiration found!\n");
+ fprintf(stderr, "Currently recognised arguments are: 'sign'\n");
+
+ return -1;
+ }
+}
const ed25519_public_key_t *
get_master_identity_key(void)
@@ -1005,6 +1246,24 @@ get_master_identity_key(void)
return &master_identity_key->pubkey;
}
+/** Return true iff <b>id</b> is our Ed25519 master identity key. */
+int
+router_ed25519_id_is_me(const ed25519_public_key_t *id)
+{
+ return id && master_identity_key &&
+ ed25519_pubkey_eq(id, &master_identity_key->pubkey);
+}
+
+#ifdef TOR_UNIT_TESTS
+/* only exists for the unit tests, since otherwise the identity key
+ * should be used to sign nothing but the signing key. */
+const ed25519_keypair_t *
+get_master_identity_keypair(void)
+{
+ return master_identity_key;
+}
+#endif
+
const ed25519_keypair_t *
get_master_signing_keypair(void)
{
@@ -1095,12 +1354,12 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it
* is, -1 if it isn't. */
-int
-check_tap_onion_key_crosscert(const uint8_t *crosscert,
- int crosscert_len,
- const crypto_pk_t *onion_pkey,
- const ed25519_public_key_t *master_id_pkey,
- const uint8_t *rsa_id_digest)
+MOCK_IMPL(int,
+check_tap_onion_key_crosscert,(const uint8_t *crosscert,
+ int crosscert_len,
+ const crypto_pk_t *onion_pkey,
+ const ed25519_public_key_t *master_id_pkey,
+ const uint8_t *rsa_id_digest))
{
uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey));
int cc_len =
@@ -1139,9 +1398,12 @@ routerkeys_free_all(void)
tor_cert_free(signing_key_cert);
tor_cert_free(link_cert_cert);
tor_cert_free(auth_key_cert);
+ tor_free(rsa_ed_crosscert);
master_identity_key = master_signing_key = NULL;
current_auth_key = NULL;
signing_key_cert = link_cert_cert = auth_key_cert = NULL;
+ rsa_ed_crosscert = NULL; // redundant
+ rsa_ed_crosscert_len = 0;
}
diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h
index be9b19aea8..0cf13e7600 100644
--- a/src/or/routerkeys.h
+++ b/src/or/routerkeys.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_ROUTERKEYS_H
@@ -45,6 +45,8 @@ const struct tor_cert_st *get_current_auth_key_cert(void);
void get_master_rsa_crosscert(const uint8_t **cert_out,
size_t *size_out);
+int router_ed25519_id_is_me(const ed25519_public_key_t *id);
+
struct tor_cert_st *make_ntor_onion_key_crosscert(
const curve25519_keypair_t *onion_key,
const ed25519_public_key_t *master_id_key,
@@ -55,16 +57,17 @@ uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
const crypto_pk_t *rsa_id_key,
int *len_out);
-int check_tap_onion_key_crosscert(const uint8_t *crosscert,
+MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert,
int crosscert_len,
const crypto_pk_t *onion_pkey,
const ed25519_public_key_t *master_id_pkey,
- const uint8_t *rsa_id_digest);
+ const uint8_t *rsa_id_digest));
+int log_cert_expiration(void);
int load_ed_keys(const or_options_t *options, time_t now);
int should_make_new_ed_keys(const or_options_t *options, const time_t now);
-int generate_ed_link_cert(const or_options_t *options, time_t now);
+int generate_ed_link_cert(const or_options_t *options, time_t now, int force);
int read_encrypted_secret_key(ed25519_secret_key_t *out,
const char *fname);
@@ -73,5 +76,10 @@ int write_encrypted_secret_key(const ed25519_secret_key_t *out,
void routerkeys_free_all(void);
+#ifdef TOR_UNIT_TESTS
+const ed25519_keypair_t *get_master_identity_keypair(void);
+void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key);
+#endif
+
#endif
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 80d01970c1..49caa875fe 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -9,11 +9,91 @@
* \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
#include "or.h"
#include "backtrace.h"
+#include "bridges.h"
#include "crypto_ed25519.h"
#include "circuitstats.h"
#include "config.h"
@@ -46,6 +126,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 +150,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);
@@ -148,6 +231,25 @@ get_n_authorities(dirinfo_type_t type)
return n;
}
+/** Initialise schedule, want_authority, and increment on in the download
+ * status dlstatus, then call download_status_reset() on it.
+ * It is safe to call this function or download_status_reset() multiple times
+ * on a new dlstatus. But it should *not* be called after a dlstatus has been
+ * used to count download attempts or failures. */
+static void
+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);
+}
+
/** Reset the download status of a specified element in a dsmap */
static void
download_status_reset_by_sk_in_cl(cert_list_t *cl, const char *digest)
@@ -168,6 +270,7 @@ download_status_reset_by_sk_in_cl(cert_list_t *cl, const char *digest)
/* Insert before we reset */
dlstatus = tor_malloc_zero(sizeof(*dlstatus));
dsmap_set(cl->dl_status_map, digest, dlstatus);
+ download_status_cert_init(dlstatus);
}
tor_assert(dlstatus);
/* Go ahead and reset it */
@@ -206,7 +309,7 @@ download_status_is_ready_by_sk_in_cl(cert_list_t *cl,
* too.
*/
dlstatus = tor_malloc_zero(sizeof(*dlstatus));
- download_status_reset(dlstatus);
+ download_status_cert_init(dlstatus);
dsmap_set(cl->dl_status_map, digest, dlstatus);
rv = 1;
}
@@ -225,7 +328,7 @@ get_cert_list(const char *id_digest)
cl = digestmap_get(trusted_dir_certs, id_digest);
if (!cl) {
cl = tor_malloc_zero(sizeof(cert_list_t));
- cl->dl_status_by_id.schedule = DL_SCHED_CONSENSUS;
+ download_status_cert_init(&cl->dl_status_by_id);
cl->certs = smartlist_new();
cl->dl_status_map = dsmap_new();
digestmap_set(trusted_dir_certs, id_digest, cl);
@@ -233,6 +336,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)
@@ -270,7 +479,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;
}
@@ -300,16 +509,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);
@@ -369,11 +582,12 @@ 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));
} else {
- int adding = directory_caches_unknown_auth_certs(get_options());
+ int adding = we_want_to_fetch_unknown_auth_certs(get_options());
log_info(LD_DIR, "%s %s certificate for unrecognized directory "
"authority with signing key %s",
adding ? "Adding" : "Not adding",
@@ -413,8 +627,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;
}
@@ -662,7 +883,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)
{
@@ -696,14 +919,85 @@ 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,
+ resource);
+
+ /* 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;
+
+ directory_request_t *req = NULL;
+ /* If we've just downloaded a consensus from a bridge, re-use that
+ * bridge */
+ if (options->UseBridges && node && node->ri && !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);
+
+ req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE);
+ directory_request_set_or_addr_port(req, &or_ap);
+ if (dir_hint)
+ directory_request_set_directory_id_digest(req, dir_hint);
+ } else if (rs) {
+ /* And if we've just downloaded a consensus from a directory, re-use that
+ * directory */
+ req = directory_request_new(DIR_PURPOSE_FETCH_CERTIFICATE);
+ directory_request_set_routerstatus(req, rs);
+ }
+
+ if (req) {
+ /* We've set up a request object -- fill in the other request fields, and
+ * send the request. */
+ directory_request_set_indirection(req, indirection);
+ directory_request_set_resource(req, resource);
+ directory_initiate_request(req);
+ directory_request_free(req);
+ 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
@@ -712,7 +1006,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
@@ -722,12 +1015,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 keep_unknown = we_want_to_fetch_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();
@@ -768,7 +1062,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 "
@@ -794,9 +1088,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
if (!smartlist_len(voter->sigs))
continue; /* This authority never signed this consensus, so don't
* go looking for a cert with key digest 0000000000. */
- if (!cache &&
+ if (!keep_unknown &&
!trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
- continue; /* We are not a cache, and we don't know this authority.*/
+ continue; /* We don't want unknown certs, and we don't know this
+ * authority.*/
/*
* If we don't know *any* cert for this authority, and a download by ID
@@ -821,8 +1116,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);
@@ -830,7 +1126,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)) {
@@ -867,12 +1163,52 @@ 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;
smartlist_t *fps = smartlist_new();
- smartlist_add(fps, tor_strdup("fp/"));
+ smartlist_add_strdup(fps, "fp/");
SMARTLIST_FOREACH_BEGIN(missing_id_digests, const char *, d) {
char *fp = NULL;
@@ -896,10 +1232,9 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
if (smartlist_len(fps) > 1) {
resource = smartlist_join_strings(fps, "", 0, NULL);
- /* XXX - do we want certs from authorities or mirrors? - teor */
- 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 */
@@ -913,7 +1248,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now)
int need_plus = 0;
smartlist_t *fp_pairs = smartlist_new();
- smartlist_add(fp_pairs, tor_strdup("fp-sk/"));
+ smartlist_add_strdup(fp_pairs, "fp-sk/");
SMARTLIST_FOREACH_BEGIN(missing_cert_digests, const fp_pair_t *, d) {
char *fp_pair = NULL;
@@ -942,10 +1277,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);
- /* XXX - do we want certs from authorities or mirrors? - teor */
- 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 */
@@ -1365,6 +1699,7 @@ router_get_trusteddirserver_by_digest(const char *digest)
* key hashes to <b>digest</b>, or NULL if no such fallback is in the list of
* fallback_dir_servers. (fallback_dir_servers is affected by the FallbackDir
* and UseDefaultFallbackDirs torrc options.)
+ * The list of fallback directories includes the list of authorities.
*/
dir_server_t *
router_get_fallback_dirserver_by_digest(const char *digest)
@@ -1388,6 +1723,7 @@ router_get_fallback_dirserver_by_digest(const char *digest)
* or 0 if no such fallback is in the list of fallback_dir_servers.
* (fallback_dir_servers is affected by the FallbackDir and
* UseDefaultFallbackDirs torrc options.)
+ * The list of fallback directories includes the list of authorities.
*/
int
router_digest_is_fallback_dir(const char *digest)
@@ -1399,8 +1735,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;
@@ -1493,43 +1829,24 @@ router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc,
return 0;
}
-/* Check if we already have a directory fetch from ds, for serverdesc
- * (including extrainfo) or microdesc documents.
+/* Check if we already have a directory fetch from the ipv4 or ipv6
+ * router, for serverdesc (including extrainfo) or microdesc documents.
* If so, return 1, if not, return 0.
*/
static int
-router_is_already_dir_fetching_ds(const dir_server_t *ds,
- int serverdesc,
- int microdesc)
+router_is_already_dir_fetching_(uint32_t ipv4_addr,
+ const tor_addr_t *ipv6_addr,
+ uint16_t dir_port,
+ int serverdesc,
+ int microdesc)
{
tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap;
/* Assume IPv6 DirPort is the same as IPv4 DirPort */
- tor_addr_from_ipv4h(&ipv4_dir_ap.addr, ds->addr);
- ipv4_dir_ap.port = ds->dir_port;
- tor_addr_copy(&ipv6_dir_ap.addr, &ds->ipv6_addr);
- ipv6_dir_ap.port = ds->dir_port;
-
- return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc)
- || router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc));
-}
-
-/* Check if we already have a directory fetch from rs, for serverdesc
- * (including extrainfo) or microdesc documents.
- * If so, return 1, if not, return 0.
- */
-static int
-router_is_already_dir_fetching_rs(const routerstatus_t *rs,
- int serverdesc,
- int microdesc)
-{
- tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap;
-
- /* Assume IPv6 DirPort is the same as IPv4 DirPort */
- tor_addr_from_ipv4h(&ipv4_dir_ap.addr, rs->addr);
- ipv4_dir_ap.port = rs->dir_port;
- tor_addr_copy(&ipv6_dir_ap.addr, &rs->ipv6_addr);
- ipv6_dir_ap.port = rs->dir_port;
+ tor_addr_from_ipv4h(&ipv4_dir_ap.addr, ipv4_addr);
+ ipv4_dir_ap.port = dir_port;
+ tor_addr_copy(&ipv6_dir_ap.addr, ipv6_addr);
+ ipv6_dir_ap.port = dir_port;
return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc)
|| router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc));
@@ -1558,20 +1875,18 @@ router_picked_poor_directory_log(const routerstatus_t *rs)
#endif
/* We couldn't find a node, or the one we have doesn't fit our preferences.
- * This might be a bug. */
+ * Sometimes this is normal, sometimes it can be a reachability issue. */
if (!rs) {
- static int logged_backtrace = 0;
- log_info(LD_BUG, "Wanted to make an outgoing directory connection, but "
- "all OR and Dir addresses for all relays were not reachable. "
- "Check ReachableAddresses, ClientUseIPv4, and similar options.");
- if (!logged_backtrace) {
- log_backtrace(LOG_INFO, LD_BUG, "Node search initiated by");
- logged_backtrace = 1;
- }
+ /* This happens a lot, so it's at debug level */
+ log_debug(LD_DIR, "Wanted to make an outgoing directory connection, but "
+ "we couldn't find a directory that fit our criteria. "
+ "Perhaps we will succeed next time with less strict criteria.");
} else if (!fascist_firewall_allows_rs(rs, FIREWALL_OR_CONNECTION, 1)
&& !fascist_firewall_allows_rs(rs, FIREWALL_DIR_CONNECTION, 1)
) {
- log_info(LD_BUG, "Selected a directory %s with non-preferred OR and Dir "
+ /* This is rare, and might be interesting to users trying to diagnose
+ * connection issues on dual-stack machines. */
+ log_info(LD_DIR, "Selected a directory %s with non-preferred OR and Dir "
"addresses for launching an outgoing connection: "
"IPv4 %s OR %d Dir %d IPv6 %s OR %d Dir %d",
routerstatus_describe(rs),
@@ -1597,11 +1912,10 @@ router_picked_poor_directory_log(const routerstatus_t *rs)
STMT_BEGIN \
if (result == NULL && try_ip_pref && options->ClientUseIPv4 \
&& fascist_firewall_use_ipv6(options) && !server_mode(options) \
- && n_not_preferred && !n_busy) { \
+ && !n_busy) { \
n_excluded = 0; \
n_busy = 0; \
try_ip_pref = 0; \
- n_not_preferred = 0; \
goto retry_label; \
} \
STMT_END \
@@ -1620,15 +1934,29 @@ router_picked_poor_directory_log(const routerstatus_t *rs)
n_excluded = 0; \
n_busy = 0; \
try_ip_pref = 1; \
- n_not_preferred = 0; \
goto retry_label; \
} \
STMT_END
+/* Common code used in the loop within router_pick_directory_server_impl and
+ * router_pick_trusteddirserver_impl.
+ *
+ * Check if the given <b>identity</b> supports extrainfo. If not, skip further
+ * checks.
+ */
+#define SKIP_MISSING_TRUSTED_EXTRAINFO(type, identity) \
+ STMT_BEGIN \
+ int is_trusted_extrainfo = router_digest_is_trusted_dir_type( \
+ (identity), EXTRAINFO_DIRINFO); \
+ if (((type) & EXTRAINFO_DIRINFO) && \
+ !router_supports_extrainfo((identity), is_trusted_extrainfo)) \
+ continue; \
+ STMT_END
+
/* 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.
@@ -1671,9 +1999,8 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
const int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL);
const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH);
const int no_microdesc_fetching = (flags & PDS_NO_EXISTING_MICRODESC_FETCH);
- const int for_guard = (flags & PDS_FOR_GUARD);
int try_excluding = 1, n_excluded = 0, n_busy = 0;
- int try_ip_pref = 1, n_not_preferred = 0;
+ int try_ip_pref = 1;
if (!consensus)
return NULL;
@@ -1687,12 +2014,13 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
overloaded_direct = smartlist_new();
overloaded_tunnel = smartlist_new();
- const int skip_or = router_skip_or_reachability(options, try_ip_pref);
- const int skip_dir = router_skip_dir_reachability(options, try_ip_pref);
+ const int skip_or_fw = router_skip_or_reachability(options, try_ip_pref);
+ const int skip_dir_fw = router_skip_dir_reachability(options, try_ip_pref);
+ const int must_have_or = directory_must_use_begindir(options);
/* Find all the running dirservers we know about. */
SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) {
- int is_trusted, is_trusted_extrainfo;
+ int is_trusted;
int is_overloaded;
const routerstatus_t *status = node->rs;
const country_t country = node->country;
@@ -1703,20 +2031,9 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
continue;
if (requireother && router_digest_is_me(node->identity))
continue;
- is_trusted = router_digest_is_trusted_dir(node->identity);
- is_trusted_extrainfo = router_digest_is_trusted_dir_type(
- node->identity, EXTRAINFO_DIRINFO);
- if ((type & EXTRAINFO_DIRINFO) &&
- !router_supports_extrainfo(node->identity, is_trusted_extrainfo))
- continue;
- /* Don't make the same node a guard twice */
- if (for_guard && node->using_as_guard) {
- continue;
- }
- /* Ensure that a directory guard is actually a guard node. */
- if (for_guard && !node->is_possible_guard) {
- continue;
- }
+
+ SKIP_MISSING_TRUSTED_EXTRAINFO(type, node->identity);
+
if (try_excluding &&
routerset_contains_routerstatus(options->ExcludeNodes, status,
country)) {
@@ -1724,14 +2041,17 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
continue;
}
- if (router_is_already_dir_fetching_rs(status,
- no_serverdesc_fetching,
- no_microdesc_fetching)) {
+ if (router_is_already_dir_fetching_(status->addr,
+ &status->ipv6_addr,
+ status->dir_port,
+ no_serverdesc_fetching,
+ no_microdesc_fetching)) {
++n_busy;
continue;
}
is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now;
+ is_trusted = router_digest_is_trusted_dir(node->identity);
/* Clients use IPv6 addresses if the server has one and the client
* prefers IPv6.
@@ -1740,18 +2060,16 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
* address for each router (if any). (To ensure correct load-balancing
* we try routers that only have one address both times.)
*/
- if (!fascistfirewall || skip_or ||
- fascist_firewall_allows_rs(status, FIREWALL_OR_CONNECTION,
- try_ip_pref))
+ if (!fascistfirewall || skip_or_fw ||
+ fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION,
+ try_ip_pref))
smartlist_add(is_trusted ? trusted_tunnel :
is_overloaded ? overloaded_tunnel : tunnel, (void*)node);
- else if (skip_dir ||
- fascist_firewall_allows_rs(status, FIREWALL_DIR_CONNECTION,
- try_ip_pref))
+ else if (!must_have_or && (skip_dir_fw ||
+ fascist_firewall_allows_node(node, FIREWALL_DIR_CONNECTION,
+ try_ip_pref)))
smartlist_add(is_trusted ? trusted_direct :
is_overloaded ? overloaded_direct : direct, (void*)node);
- else if (!tor_addr_is_null(&status->ipv6_addr))
- ++n_not_preferred;
} SMARTLIST_FOREACH_END(node);
if (smartlist_len(tunnel)) {
@@ -1799,20 +2117,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);
}
@@ -1839,7 +2160,7 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
smartlist_t *pick_from;
int n_busy = 0;
int try_excluding = 1, n_excluded = 0;
- int try_ip_pref = 1, n_not_preferred = 0;
+ int try_ip_pref = 1;
if (!sourcelist)
return NULL;
@@ -1851,8 +2172,9 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
overloaded_direct = smartlist_new();
overloaded_tunnel = smartlist_new();
- const int skip_or = router_skip_or_reachability(options, try_ip_pref);
- const int skip_dir = router_skip_dir_reachability(options, try_ip_pref);
+ const int skip_or_fw = router_skip_or_reachability(options, try_ip_pref);
+ const int skip_dir_fw = router_skip_dir_reachability(options, try_ip_pref);
+ const int must_have_or = directory_must_use_begindir(options);
SMARTLIST_FOREACH_BEGIN(sourcelist, const dir_server_t *, d)
{
@@ -1861,11 +2183,9 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
if (!d->is_running) continue;
if ((type & d->type) == 0)
continue;
- int is_trusted_extrainfo = router_digest_is_trusted_dir_type(
- d->digest, EXTRAINFO_DIRINFO);
- if ((type & EXTRAINFO_DIRINFO) &&
- !router_supports_extrainfo(d->digest, is_trusted_extrainfo))
- continue;
+
+ SKIP_MISSING_TRUSTED_EXTRAINFO(type, d->digest);
+
if (requireother && me && router_digest_is_me(d->digest))
continue;
if (try_excluding &&
@@ -1875,8 +2195,11 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
continue;
}
- if (router_is_already_dir_fetching_ds(d, no_serverdesc_fetching,
- no_microdesc_fetching)) {
+ if (router_is_already_dir_fetching_(d->addr,
+ &d->ipv6_addr,
+ d->dir_port,
+ no_serverdesc_fetching,
+ no_microdesc_fetching)) {
++n_busy;
continue;
}
@@ -1888,16 +2211,14 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
* address for each router (if any). (To ensure correct load-balancing
* we try routers that only have one address both times.)
*/
- if (!fascistfirewall || skip_or ||
+ if (!fascistfirewall || skip_or_fw ||
fascist_firewall_allows_dir_server(d, FIREWALL_OR_CONNECTION,
try_ip_pref))
smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, (void*)d);
- else if (skip_dir ||
+ else if (!must_have_or && (skip_dir_fw ||
fascist_firewall_allows_dir_server(d, FIREWALL_DIR_CONNECTION,
- try_ip_pref))
+ try_ip_pref)))
smartlist_add(is_overloaded ? overloaded_direct : direct, (void*)d);
- else if (!tor_addr_is_null(&d->ipv6_addr))
- ++n_not_preferred;
}
SMARTLIST_FOREACH_END(d);
@@ -1999,17 +2320,16 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router)
* we can pick a node for a circuit.
*/
void
-router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid,
- int need_uptime, int need_capacity,
- int need_guard, int need_desc,
- int pref_addr)
+router_add_running_nodes_to_smartlist(smartlist_t *sl, int need_uptime,
+ int need_capacity, int need_guard,
+ int need_desc, int pref_addr,
+ int direct_conn)
{
const int check_reach = !router_skip_or_reachability(get_options(),
pref_addr);
/* XXXX MOVE */
SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) {
- if (!node->is_running ||
- (!node->is_valid && !allow_invalid))
+ if (!node->is_running || !node->is_valid)
continue;
if (need_desc && !(node->ri || (node->rs && node->md)))
continue;
@@ -2017,10 +2337,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;
+ /* 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 (check_reach && !fascist_firewall_allows_node(node,
- FIREWALL_OR_CONNECTION,
- pref_addr))
+ if (direct_conn && check_reach &&
+ !fascist_firewall_allows_node(node,
+ FIREWALL_OR_CONNECTION,
+ pref_addr))
continue;
smartlist_add(sl, (void *)node);
@@ -2075,59 +2402,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;
@@ -2139,22 +2450,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
@@ -2206,17 +2503,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);
}
}
@@ -2229,14 +2530,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 ||
@@ -2318,7 +2619,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;
@@ -2405,7 +2706,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 "
@@ -2426,7 +2727,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)
@@ -2443,7 +2744,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;
@@ -2474,8 +2775,6 @@ node_sl_choose_by_bandwidth(const smartlist_t *sl,
* a minimum uptime, return one of those.
* If <b>CRN_NEED_CAPACITY</b> is set in flags, weight your choice by the
* advertised capacity of each router.
- * If <b>CRN_ALLOW_INVALID</b> is not set in flags, consider only Valid
- * routers.
* If <b>CRN_NEED_GUARD</b> is set in flags, consider only Guard routers.
* If <b>CRN_WEIGHT_AS_EXIT</b> is set in flags, we weight bandwidths as if
* picking an exit node, otherwise we weight bandwidths for picking a relay
@@ -2496,10 +2795,10 @@ router_choose_random_node(smartlist_t *excludedsmartlist,
const int need_uptime = (flags & CRN_NEED_UPTIME) != 0;
const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0;
const int need_guard = (flags & CRN_NEED_GUARD) != 0;
- const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0;
const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0;
const int need_desc = (flags & CRN_NEED_DESC) != 0;
const int pref_addr = (flags & CRN_PREF_ADDR) != 0;
+ const int direct_conn = (flags & CRN_DIRECT_CONN) != 0;
smartlist_t *sl=smartlist_new(),
*excludednodes=smartlist_new();
@@ -2511,21 +2810,19 @@ router_choose_random_node(smartlist_t *excludedsmartlist,
rule = weight_for_exit ? WEIGHT_FOR_EXIT :
(need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID);
- /* Exclude relays that allow single hop exit circuits, if the user
- * wants to (such relays might be risky) */
- if (get_options()->ExcludeSingleHopRelays) {
- SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node,
- if (node_allows_single_hop_exits(node)) {
- smartlist_add(excludednodes, node);
- });
- }
+ /* Exclude relays that allow single hop exit circuits. This is an obsolete
+ * option since 0.2.9.2-alpha and done by default in 0.3.1.0-alpha. */
+ SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node,
+ if (node_allows_single_hop_exits(node)) {
+ smartlist_add(excludednodes, node);
+ });
if ((r = routerlist_find_my_routerinfo()))
routerlist_add_node_and_family(excludednodes, r);
- router_add_running_nodes_to_smartlist(sl, allow_invalid,
- need_uptime, need_capacity,
- need_guard, need_desc, pref_addr);
+ router_add_running_nodes_to_smartlist(sl, need_uptime, need_capacity,
+ need_guard, need_desc, pref_addr,
+ direct_conn);
log_debug(LD_CIRC,
"We found %d running nodes.",
smartlist_len(sl));
@@ -2617,7 +2914,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;
}
@@ -2678,20 +2976,6 @@ router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type)
return 0;
}
-/** Return true iff <b>addr</b> is the address of one of our trusted
- * directory authorities. */
-int
-router_addr_is_trusted_dir(uint32_t addr)
-{
- if (!trusted_dir_servers)
- return 0;
- SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ent,
- if (ent->addr == addr)
- return 1;
- );
- return 0;
-}
-
/** If hexdigest is correctly formed, base16_decode it into
* digest, which must have DIGEST_LEN space in it.
* Return 0 on success, -1 on failure.
@@ -2702,7 +2986,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;
}
@@ -2757,8 +3041,8 @@ router_get_by_extrainfo_digest,(const char *digest))
/** Return the signed descriptor for the extrainfo_t in our routerlist whose
* extra-info-digest is <b>digest</b>. Return NULL if no such extra-info
* document is known. */
-signed_descriptor_t *
-extrainfo_get_by_descriptor_digest(const char *digest)
+MOCK_IMPL(signed_descriptor_t *,
+extrainfo_get_by_descriptor_digest,(const char *digest))
{
extrainfo_t *ei;
tor_assert(digest);
@@ -2876,13 +3160,14 @@ 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);
tor_free(router->onion_curve25519_pkey);
if (router->identity_pkey)
crypto_pk_free(router->identity_pkey);
- tor_cert_free(router->signing_key_cert);
+ tor_cert_free(router->cache_info.signing_key_cert);
if (router->declared_family) {
SMARTLIST_FOREACH(router->declared_family, char *, s, tor_free(s));
smartlist_free(router->declared_family);
@@ -2901,7 +3186,7 @@ extrainfo_free(extrainfo_t *extrainfo)
{
if (!extrainfo)
return;
- tor_cert_free(extrainfo->signing_key_cert);
+ tor_cert_free(extrainfo->cache_info.signing_key_cert);
tor_free(extrainfo->cache_info.signed_descriptor_body);
tor_free(extrainfo->pending_sig);
@@ -2917,11 +3202,38 @@ signed_descriptor_free(signed_descriptor_t *sd)
return;
tor_free(sd->signed_descriptor_body);
+ tor_cert_free(sd->signing_key_cert);
memset(sd, 99, sizeof(signed_descriptor_t)); /* Debug bad mem usage */
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
+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;
+ dest->routerlist_index = -1;
+}
+
/** Extract a signed_descriptor_t from a general routerinfo, and free the
* routerinfo.
*/
@@ -2931,9 +3243,7 @@ signed_descriptor_from_routerinfo(routerinfo_t *ri)
signed_descriptor_t *sd;
tor_assert(ri->purpose == ROUTER_PURPOSE_GENERAL);
sd = tor_malloc_zero(sizeof(signed_descriptor_t));
- memcpy(sd, &(ri->cache_info), sizeof(signed_descriptor_t));
- sd->routerlist_index = -1;
- ri->cache_info.signed_descriptor_body = NULL;
+ signed_descriptor_move(sd, &ri->cache_info);
routerinfo_free(ri);
return sd;
}
@@ -3111,7 +3421,7 @@ extrainfo_insert,(routerlist_t *rl, extrainfo_t *ei, int warn_if_incompatible))
"Mismatch in digest in extrainfo map.");
goto done;
}
- if (routerinfo_incompatible_with_extrainfo(ri, ei, sd,
+ if (routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei, sd,
&compatibility_error_msg)) {
char d1[HEX_DIGEST_LEN+1], d2[HEX_DIGEST_LEN+1];
r = (ri->cache_info.extrainfo_is_bogus) ?
@@ -3419,9 +3729,7 @@ routerlist_reparse_old(routerlist_t *rl, signed_descriptor_t *sd)
0, 1, NULL, NULL);
if (!ri)
return NULL;
- memcpy(&ri->cache_info, sd, sizeof(signed_descriptor_t));
- sd->signed_descriptor_body = NULL; /* Steal reference. */
- ri->cache_info.routerlist_index = -1;
+ signed_descriptor_move(&ri->cache_info, sd);
routerlist_remove_old(rl, sd, -1);
@@ -3572,7 +3880,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
router_describe(router));
*msg = "Router descriptor is not referenced by any network-status.";
- /* Only journal this desc if we'll be serving it. */
+ /* Only journal this desc if we want to keep old descriptors */
if (!from_cache && should_cache_old_descriptors())
signed_desc_append_to_journal(&router->cache_info,
&routerlist->desc_store);
@@ -3677,7 +3985,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);
@@ -4159,7 +4467,7 @@ router_load_extrainfo_from_string(const char *s, const char *eos,
ei->cache_info.identity_digest,
DIGEST_LEN);
smartlist_string_remove(requested_fingerprints, fp);
- /* We silently let people stuff us with extrainfos we didn't ask for,
+ /* We silently let relays stuff us with extrainfos we didn't ask for,
* so long as we would have wanted them anyway. Since we always fetch
* all the extrainfos we want, and we never actually act on them
* inside Tor, this should be harmless. */
@@ -4202,13 +4510,14 @@ router_load_extrainfo_from_string(const char *s, const char *eos,
smartlist_free(extrainfo_list);
}
-/** Return true iff any networkstatus includes a descriptor whose digest
- * is that of <b>desc</b>. */
+/** Return true iff the latest ns-flavored consensus includes a descriptor
+ * whose digest is that of <b>desc</b>. */
static int
signed_desc_digest_is_recognized(signed_descriptor_t *desc)
{
const routerstatus_t *rs;
- networkstatus_t *consensus = networkstatus_get_latest_consensus();
+ networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(
+ FLAV_NS);
if (consensus) {
rs = networkstatus_vote_find_entry(consensus, desc->identity_digest);
@@ -4237,6 +4546,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();
@@ -4250,7 +4563,7 @@ router_exit_policy_rejects_all(const routerinfo_t *router)
return router->policy_is_reject_star;
}
-/** Create an directory server at <b>address</b>:<b>port</b>, with OR identity
+/** Create a directory server at <b>address</b>:<b>port</b>, with OR identity
* key <b>digest</b> which has DIGEST_LEN bytes. If <b>address</b> is NULL,
* add ourself. If <b>is_authority</b>, this is a directory authority. Return
* the new directory server entry on success or NULL on failure. */
@@ -4312,10 +4625,10 @@ dir_server_new(int is_authority,
if (nickname)
tor_asprintf(&ent->description, "directory server \"%s\" at %s:%d",
- nickname, hostname, (int)dir_port);
+ nickname, hostname_, (int)dir_port);
else
tor_asprintf(&ent->description, "directory server at %s:%d",
- hostname, (int)dir_port);
+ hostname_, (int)dir_port);
ent->fake_status.addr = ent->addr;
tor_addr_copy(&ent->fake_status.ipv6_addr, &ent->ipv6_addr);
@@ -4561,9 +4874,10 @@ list_pending_fpsk_downloads(fp_pair_map_t *result)
* range.) If <b>source</b> is given, download from <b>source</b>;
* otherwise, download from an appropriate random directory server.
*/
-MOCK_IMPL(STATIC void, initiate_descriptor_downloads,
- (const routerstatus_t *source, int purpose, smartlist_t *digests,
- int lo, int hi, int pds_flags))
+MOCK_IMPL(STATIC void,
+initiate_descriptor_downloads,(const routerstatus_t *source,
+ int purpose, smartlist_t *digests,
+ int lo, int hi, int pds_flags))
{
char *resource, *cp;
int digest_len, enc_digest_len;
@@ -4615,10 +4929,11 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads,
if (source) {
/* We know which authority or directory mirror we want. */
- directory_initiate_command_routerstatus(source, purpose,
- ROUTER_PURPOSE_GENERAL,
- DIRIND_ONEHOP,
- resource, NULL, 0, 0);
+ directory_request_t *req = directory_request_new(purpose);
+ directory_request_set_routerstatus(req, source);
+ directory_request_set_resource(req, resource);
+ directory_initiate_request(req);
+ directory_request_free(req);
} else {
directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource,
pds_flags, DL_WANT_ANY_DIRSERVER);
@@ -4649,7 +4964,7 @@ max_dl_per_request(const or_options_t *options, int purpose)
}
/* If we're going to tunnel our connections, we can ask for a lot more
* in a request. */
- if (!directory_fetches_from_authorities(options)) {
+ if (directory_must_use_begindir(options)) {
max = 500;
}
return max;
@@ -4718,7 +5033,7 @@ launch_descriptor_downloads(int purpose,
}
}
- if (!authdir_mode_any_nonhidserv(options)) {
+ if (!authdir_mode(options)) {
/* If we wind up going to the authorities, we want to only open one
* connection to each authority at a time, so that we don't overload
* them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH
@@ -4740,8 +5055,9 @@ launch_descriptor_downloads(int purpose,
if (n_per_request > max_dl_per_req)
n_per_request = max_dl_per_req;
- if (n_per_request < MIN_DL_PER_REQUEST)
- n_per_request = MIN_DL_PER_REQUEST;
+ if (n_per_request < MIN_DL_PER_REQUEST) {
+ n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable);
+ }
if (n_downloadable > n_per_request)
req_plural = rtr_plural = "s";
@@ -4827,8 +5143,8 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
++n_would_reject;
continue; /* We would throw it out immediately. */
}
- if (!directory_caches_dir_info(options) &&
- !client_would_use_router(rs, now, options)) {
+ if (!we_want_to_fetch_flavor(options, consensus->flavor) &&
+ !client_would_use_router(rs, now)) {
++n_wouldnt_use;
continue; /* We would never use it ourself. */
}
@@ -4849,7 +5165,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
smartlist_add(downloadable, rs->descriptor_digest);
} SMARTLIST_FOREACH_END(rsp);
- if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)
+ if (!authdir_mode_v3(options)
&& smartlist_len(no_longer_old)) {
routerlist_t *rl = router_get_routerlist();
log_info(LD_DIR, "%d router descriptors listed in consensus are "
@@ -4890,7 +5206,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)
@@ -4901,7 +5217,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. */
@@ -5049,6 +5365,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)
@@ -5103,7 +5422,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;
@@ -5150,25 +5469,32 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2)
return 1;
}
-/** Check whether <b>ri</b> (a.k.a. sd) is a router compatible with the
- * extrainfo document
- * <b>ei</b>. If no router is compatible with <b>ei</b>, <b>ei</b> should be
+/** Check whether <b>sd</b> describes a router descriptor compatible with the
+ * extrainfo document <b>ei</b>.
+ *
+ * <b>identity_pkey</b> (which must also be provided) is RSA1024 identity key
+ * for the router. We use it to check the signature of the extrainfo document,
+ * if it has not already been checked.
+ *
+ * If no router is compatible with <b>ei</b>, <b>ei</b> should be
* dropped. Return 0 for "compatible", return 1 for "reject, and inform
* whoever uploaded <b>ei</b>, and return -1 for "reject silently.". If
* <b>msg</b> is present, set *<b>msg</b> to a description of the
* incompatibility (if any).
+ *
+ * Set the extrainfo_is_bogus field in <b>sd</b> if the digests matched
+ * but the extrainfo was nonetheless incompatible.
**/
int
-routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
+routerinfo_incompatible_with_extrainfo(const crypto_pk_t *identity_pkey,
extrainfo_t *ei,
signed_descriptor_t *sd,
const char **msg)
{
int digest_matches, digest256_matches, r=1;
- tor_assert(ri);
+ tor_assert(identity_pkey);
+ tor_assert(sd);
tor_assert(ei);
- if (!sd)
- sd = (signed_descriptor_t*)&ri->cache_info;
if (ei->bad_sig) {
if (msg) *msg = "Extrainfo signature was bad, or signed with wrong key.";
@@ -5180,27 +5506,28 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
/* Set digest256_matches to 1 if the digest is correct, or if no
* digest256 was in the ri. */
digest256_matches = tor_memeq(ei->digest256,
- ri->extra_info_digest256, DIGEST256_LEN);
+ sd->extra_info_digest256, DIGEST256_LEN);
digest256_matches |=
- tor_mem_is_zero(ri->extra_info_digest256, DIGEST256_LEN);
+ tor_mem_is_zero(sd->extra_info_digest256, DIGEST256_LEN);
/* The identity must match exactly to have been generated at the same time
* by the same router. */
- if (tor_memneq(ri->cache_info.identity_digest,
+ if (tor_memneq(sd->identity_digest,
ei->cache_info.identity_digest,
DIGEST_LEN)) {
if (msg) *msg = "Extrainfo nickname or identity did not match routerinfo";
goto err; /* different servers */
}
- if (! tor_cert_opt_eq(ri->signing_key_cert, ei->signing_key_cert)) {
+ if (! tor_cert_opt_eq(sd->signing_key_cert,
+ ei->cache_info.signing_key_cert)) {
if (msg) *msg = "Extrainfo signing key cert didn't match routerinfo";
goto err; /* different servers */
}
if (ei->pending_sig) {
char signed_digest[128];
- if (crypto_pk_public_checksig(ri->identity_pkey,
+ if (crypto_pk_public_checksig(identity_pkey,
signed_digest, sizeof(signed_digest),
ei->pending_sig, ei->pending_sig_len) != DIGEST_LEN ||
tor_memneq(signed_digest, ei->cache_info.signed_descriptor_digest,
@@ -5211,7 +5538,7 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
goto err; /* Bad signature, or no match. */
}
- ei->cache_info.send_unencrypted = ri->cache_info.send_unencrypted;
+ ei->cache_info.send_unencrypted = sd->send_unencrypted;
tor_free(ei->pending_sig);
}
@@ -5252,6 +5579,47 @@ routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
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 bc48c2087c..e0ed4e623a 100644
--- a/src/or/routerlist.h
+++ b/src/or/routerlist.h
@@ -1,6 +1,6 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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,18 +52,20 @@ 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);
-void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid,
- int need_uptime, int need_capacity,
- int need_guard, int need_desc,
- int pref_addr);
+void router_add_running_nodes_to_smartlist(smartlist_t *sl, int need_uptime,
+ int need_capacity, int need_guard,
+ int need_desc, int pref_addr,
+ int direct_conn);
const routerinfo_t *routerlist_find_my_routerinfo(void);
uint32_t router_get_advertised_bandwidth(const routerinfo_t *router);
@@ -83,14 +86,14 @@ int router_digest_is_trusted_dir_type(const char *digest,
#define router_digest_is_trusted_dir(d) \
router_digest_is_trusted_dir_type((d), NO_DIRINFO)
-int router_addr_is_trusted_dir(uint32_t addr);
int hexdigest_to_digest(const char *hexdigest, char *digest);
const routerinfo_t *router_get_by_id_digest(const char *digest);
routerinfo_t *router_get_mutable_by_digest(const char *digest);
signed_descriptor_t *router_get_by_descriptor_digest(const char *digest);
MOCK_DECL(signed_descriptor_t *,router_get_by_extrainfo_digest,
(const char *digest));
-signed_descriptor_t *extrainfo_get_by_descriptor_digest(const char *digest);
+MOCK_DECL(signed_descriptor_t *,extrainfo_get_by_descriptor_digest,
+ (const char *digest));
const char *signed_descriptor_get_body(const signed_descriptor_t *desc);
const char *signed_descriptor_get_annotations(const signed_descriptor_t *desc);
routerlist_t *router_get_routerlist(void);
@@ -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);
@@ -113,7 +124,7 @@ static int WRA_NEVER_DOWNLOADABLE(was_router_added_t s);
*/
static inline int
WRA_WAS_ADDED(was_router_added_t s) {
- return s == ROUTER_ADDED_SUCCESSFULLY || s == ROUTER_ADDED_NOTIFY_GENERATOR;
+ return s == ROUTER_ADDED_SUCCESSFULLY;
}
/** Return true iff the outcome code in <b>s</b> indicates that the descriptor
* was not added because it was either:
@@ -191,10 +202,13 @@ void update_extrainfo_downloads(time_t now);
void router_reset_descriptor_download_failures(void);
int router_differences_are_cosmetic(const routerinfo_t *r1,
const routerinfo_t *r2);
-int routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri,
+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 cec10c8f24..3449e6f6b5 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -1,12 +1,56 @@
- /* Copyright (c) 2001 Matej Pfajfar.
+/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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
@@ -16,7 +60,9 @@
#include "circuitstats.h"
#include "dirserv.h"
#include "dirvote.h"
+#include "parsecommon.h"
#include "policies.h"
+#include "protover.h"
#include "rendcommon.h"
#include "router.h"
#include "routerlist.h"
@@ -28,260 +74,14 @@
#include "routerparse.h"
#include "entrynodes.h"
#include "torcert.h"
+#include "sandbox.h"
+#include "shared_random.h"
#undef log
#include <math.h>
/****************************************************************************/
-/** Enumeration of possible token types. The ones starting with K_ correspond
- * to directory 'keywords'. A_ is for an annotation, R or C is related to
- * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
- * end-of-file marker, and NIL_ is used to encode not-a-token.
- */
-typedef enum {
- K_ACCEPT = 0,
- K_ACCEPT6,
- K_DIRECTORY_SIGNATURE,
- K_RECOMMENDED_SOFTWARE,
- K_REJECT,
- K_REJECT6,
- K_ROUTER,
- K_SIGNED_DIRECTORY,
- K_SIGNING_KEY,
- K_ONION_KEY,
- K_ONION_KEY_NTOR,
- K_ROUTER_SIGNATURE,
- K_PUBLISHED,
- K_RUNNING_ROUTERS,
- K_ROUTER_STATUS,
- K_PLATFORM,
- K_OPT,
- K_BANDWIDTH,
- K_CONTACT,
- K_NETWORK_STATUS,
- K_UPTIME,
- K_DIR_SIGNING_KEY,
- K_FAMILY,
- K_FINGERPRINT,
- K_HIBERNATING,
- K_READ_HISTORY,
- K_WRITE_HISTORY,
- K_NETWORK_STATUS_VERSION,
- K_DIR_SOURCE,
- K_DIR_OPTIONS,
- K_CLIENT_VERSIONS,
- K_SERVER_VERSIONS,
- K_OR_ADDRESS,
- K_ID,
- K_P,
- K_P6,
- K_R,
- K_A,
- K_S,
- K_V,
- K_W,
- K_M,
- K_EXTRA_INFO,
- K_EXTRA_INFO_DIGEST,
- K_CACHES_EXTRA_INFO,
- K_HIDDEN_SERVICE_DIR,
- K_ALLOW_SINGLE_HOP_EXITS,
- K_IPV6_POLICY,
- K_ROUTER_SIG_ED25519,
- K_IDENTITY_ED25519,
- K_MASTER_KEY_ED25519,
- K_ONION_KEY_CROSSCERT,
- K_NTOR_ONION_KEY_CROSSCERT,
-
- K_DIRREQ_END,
- K_DIRREQ_V2_IPS,
- K_DIRREQ_V3_IPS,
- K_DIRREQ_V2_REQS,
- K_DIRREQ_V3_REQS,
- K_DIRREQ_V2_SHARE,
- K_DIRREQ_V3_SHARE,
- K_DIRREQ_V2_RESP,
- K_DIRREQ_V3_RESP,
- K_DIRREQ_V2_DIR,
- K_DIRREQ_V3_DIR,
- K_DIRREQ_V2_TUN,
- K_DIRREQ_V3_TUN,
- K_ENTRY_END,
- K_ENTRY_IPS,
- K_CELL_END,
- K_CELL_PROCESSED,
- K_CELL_QUEUED,
- K_CELL_TIME,
- K_CELL_CIRCS,
- K_EXIT_END,
- K_EXIT_WRITTEN,
- K_EXIT_READ,
- K_EXIT_OPENED,
-
- K_DIR_KEY_CERTIFICATE_VERSION,
- K_DIR_IDENTITY_KEY,
- K_DIR_KEY_PUBLISHED,
- K_DIR_KEY_EXPIRES,
- K_DIR_KEY_CERTIFICATION,
- K_DIR_KEY_CROSSCERT,
- K_DIR_ADDRESS,
- K_DIR_TUNNELLED,
-
- K_VOTE_STATUS,
- K_VALID_AFTER,
- K_FRESH_UNTIL,
- K_VALID_UNTIL,
- K_VOTING_DELAY,
-
- K_KNOWN_FLAGS,
- K_PARAMS,
- K_BW_WEIGHTS,
- K_VOTE_DIGEST,
- K_CONSENSUS_DIGEST,
- K_ADDITIONAL_DIGEST,
- K_ADDITIONAL_SIGNATURE,
- K_CONSENSUS_METHODS,
- K_CONSENSUS_METHOD,
- K_LEGACY_DIR_KEY,
- K_DIRECTORY_FOOTER,
- K_PACKAGE,
-
- A_PURPOSE,
- A_LAST_LISTED,
- A_UNKNOWN_,
-
- R_RENDEZVOUS_SERVICE_DESCRIPTOR,
- R_VERSION,
- R_PERMANENT_KEY,
- R_SECRET_ID_PART,
- R_PUBLICATION_TIME,
- R_PROTOCOL_VERSIONS,
- R_INTRODUCTION_POINTS,
- R_SIGNATURE,
-
- R_IPO_IDENTIFIER,
- R_IPO_IP_ADDRESS,
- R_IPO_ONION_PORT,
- R_IPO_ONION_KEY,
- R_IPO_SERVICE_KEY,
-
- C_CLIENT_NAME,
- C_DESCRIPTOR_COOKIE,
- C_CLIENT_KEY,
-
- ERR_,
- EOF_,
- NIL_
-} directory_keyword;
-
-#define MIN_ANNOTATION A_PURPOSE
-#define MAX_ANNOTATION A_UNKNOWN_
-
-/** Structure to hold a single directory token.
- *
- * We parse a directory by breaking it into "tokens", each consisting
- * of a keyword, a line full of arguments, and a binary object. The
- * arguments and object are both optional, depending on the keyword
- * type.
- *
- * This structure is only allocated in memareas; do not allocate it on
- * the heap, or token_clear() won't work.
- */
-typedef struct directory_token_t {
- directory_keyword tp; /**< Type of the token. */
- int n_args:30; /**< Number of elements in args */
- char **args; /**< Array of arguments from keyword line. */
-
- char *object_type; /**< -----BEGIN [object_type]-----*/
- size_t object_size; /**< Bytes in object_body */
- char *object_body; /**< Contents of object, base64-decoded. */
-
- crypto_pk_t *key; /**< For public keys only. Heap-allocated. */
-
- char *error; /**< For ERR_ tokens only. */
-} directory_token_t;
-
-/* ********************************************************************** */
-
-/** We use a table of rules to decide how to parse each token type. */
-
-/** Rules for whether the keyword needs an object. */
-typedef enum {
- NO_OBJ, /**< No object, ever. */
- NEED_OBJ, /**< Object is required. */
- NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */
- NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */
- NEED_KEY, /**< Object is required, and must be a public key. */
- OBJ_OK, /**< Object is optional. */
-} obj_syntax;
-
-#define AT_START 1
-#define AT_END 2
-
-/** Determines the parsing rules for a single token type. */
-typedef struct token_rule_t {
- /** The string value of the keyword identifying the type of item. */
- const char *t;
- /** The corresponding directory_keyword enum. */
- directory_keyword v;
- /** Minimum number of arguments for this item */
- int min_args;
- /** Maximum number of arguments for this item */
- int max_args;
- /** If true, we concatenate all arguments for this item into a single
- * string. */
- int concat_args;
- /** Requirements on object syntax for this item. */
- obj_syntax os;
- /** Lowest number of times this item may appear in a document. */
- int min_cnt;
- /** Highest number of times this item may appear in a document. */
- int max_cnt;
- /** One or more of AT_START/AT_END to limit where the item may appear in a
- * document. */
- int pos;
- /** True iff this token is an annotation. */
- int is_annotation;
-} token_rule_t;
-
-/*
- * 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 }
-/** An item with no restrictions: used for obsolete document types */
-#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
-/** An item with no restrictions on multiplicity or location. */
-#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
-/** An item that must appear exactly once */
-#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 }
-/** An item that must appear exactly once, at the start of the document */
-#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 }
-/** An item that must appear exactly once, at the end of the document */
-#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 }
-/** An item that must appear one or more times */
-#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 }
-/** An item that must appear no more than once */
-#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 }
-/** 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. */
-#define ARGS 0,INT_MAX,0
-/* Argument multiplicity: no arguments. */
-#define NO_ARGS 0,0,0
-/* Argument multiplicity: concatenate all arguments. */
-#define CONCAT_ARGS 1,1,1
-/* Argument multiplicity: at least <b>n</b> arguments. */
-#define GE(n) n,INT_MAX,0
-/* 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[] = {
T0N("reject", K_REJECT, ARGS, NO_OBJ ),
@@ -299,6 +99,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 +176,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 +248,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 +300,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
};
@@ -532,6 +359,7 @@ static addr_policy_t *router_parse_addr_policy_private(directory_token_t *tok);
static int router_get_hash_impl_helper(const char *s, size_t s_len,
const char *start_str,
const char *end_str, char end_c,
+ int log_severity,
const char **start_out, const char **end_out);
static int router_get_hash_impl(const char *s, size_t s_len, char *digest,
const char *start_str, const char *end_str,
@@ -541,30 +369,9 @@ static int router_get_hashes_impl(const char *s, size_t s_len,
common_digests_t *digests,
const char *start_str, const char *end_str,
char end_char);
-static void token_clear(directory_token_t *tok);
-static smartlist_t *find_all_by_keyword(smartlist_t *s, directory_keyword k);
static smartlist_t *find_all_exitpolicy(smartlist_t *s);
-static directory_token_t *find_by_keyword_(smartlist_t *s,
- directory_keyword keyword,
- const char *keyword_str);
-#define find_by_keyword(s, keyword) find_by_keyword_((s), (keyword), #keyword)
-static directory_token_t *find_opt_by_keyword(smartlist_t *s,
- directory_keyword keyword);
-
-#define TS_ANNOTATIONS_OK 1
-#define TS_NOCHECK 2
-#define TS_NO_NEW_ANNOTATIONS 4
-static int tokenize_string(memarea_t *area,
- const char *start, const char *end,
- smartlist_t *out,
- token_rule_t *table,
- int flags);
-static directory_token_t *get_next_token(memarea_t *area,
- const char **s,
- const char *eos,
- token_rule_t *table);
-#define CST_CHECK_AUTHORITY (1<<0)
-#define CST_NO_CHECK_OBJTYPE (1<<1)
+
+#define CST_NO_CHECK_OBJTYPE (1<<0)
static int check_signature_token(const char *digest,
ssize_t digest_len,
directory_token_t *tok,
@@ -585,32 +392,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
-dump_desc(const char *desc, const char *type)
+MOCK_IMPL(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
@@ -635,6 +989,41 @@ router_get_router_hash(const char *s, size_t s_len, char *digest)
DIGEST_SHA1);
}
+/** Try to find the start and end of the signed portion of a networkstatus
+ * document in <b>s</b>. On success, set <b>start_out</b> to the first
+ * character of the document, and <b>end_out</b> to a position one after the
+ * final character of the signed document, and return 0. On failure, return
+ * -1. */
+int
+router_get_networkstatus_v3_signed_boundaries(const char *s,
+ const char **start_out,
+ const char **end_out)
+{
+ return router_get_hash_impl_helper(s, strlen(s),
+ "network-status-version",
+ "\ndirectory-signature",
+ ' ', LOG_INFO,
+ start_out, end_out);
+}
+
+/** Set <b>digest_out</b> to the SHA3-256 digest of the signed portion of the
+ * networkstatus vote in <b>s</b> -- or of the entirety of <b>s</b> if no
+ * signed portion can be identified. Return 0 on success, -1 on failure. */
+int
+router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
+ const char *s)
+{
+ const char *start, *end;
+ if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) {
+ start = s;
+ end = s + strlen(s);
+ }
+ tor_assert(start);
+ tor_assert(end);
+ return crypto_digest256((char*)digest_out, start, end-start,
+ DIGEST_SHA3_256);
+}
+
/** Set <b>digests</b> to all the digests of the consensus document in
* <b>s</b> */
int
@@ -819,28 +1208,15 @@ tor_version_is_obsolete(const char *myversion, const char *versionlist)
return ret;
}
-/** Return true iff <b>key</b> is allowed to sign directories.
- */
-static int
-dir_signing_key_is_trusted(crypto_pk_t *key)
+MOCK_IMPL(STATIC int,
+signed_digest_equals, (const uint8_t *d1, const uint8_t *d2, size_t len))
{
- char digest[DIGEST_LEN];
- if (!key) return 0;
- if (crypto_pk_get_digest(key, digest) < 0) {
- log_warn(LD_DIR, "Error computing dir-signing-key digest");
- return 0;
- }
- if (!router_digest_is_trusted_dir(digest)) {
- log_warn(LD_DIR, "Listed dir-signing-key is not trusted");
- return 0;
- }
- return 1;
+ return tor_memeq(d1, d2, len);
}
/** Check whether the object body of the token in <b>tok</b> has a good
- * signature for <b>digest</b> using key <b>pkey</b>. If
- * <b>CST_CHECK_AUTHORITY</b> is set, make sure that <b>pkey</b> is the key of
- * a directory authority. If <b>CST_NO_CHECK_OBJTYPE</b> is set, do not check
+ * signature for <b>digest</b> using key <b>pkey</b>.
+ * If <b>CST_NO_CHECK_OBJTYPE</b> is set, do not check
* the object type of the signature object. Use <b>doctype</b> as the type of
* the document when generating log messages. Return 0 on success, negative
* on failure.
@@ -855,7 +1231,6 @@ check_signature_token(const char *digest,
{
char *signed_digest;
size_t keysize;
- const int check_authority = (flags & CST_CHECK_AUTHORITY);
const int check_objtype = ! (flags & CST_NO_CHECK_OBJTYPE);
tor_assert(pkey);
@@ -863,12 +1238,6 @@ check_signature_token(const char *digest,
tor_assert(digest);
tor_assert(doctype);
- if (check_authority && !dir_signing_key_is_trusted(pkey)) {
- log_warn(LD_DIR, "Key on %s did not come from an authority; rejecting",
- doctype);
- return -1;
- }
-
if (check_objtype) {
if (strcmp(tok->object_type, "SIGNATURE")) {
log_warn(LD_DIR, "Bad object type on %s signature", doctype);
@@ -887,7 +1256,8 @@ check_signature_token(const char *digest,
}
// log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
// hex_str(signed_digest,4));
- if (tor_memneq(digest, signed_digest, digest_len)) {
+ if (! signed_digest_equals((const uint8_t *)digest,
+ (const uint8_t *)signed_digest, digest_len)) {
log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
tor_free(signed_digest);
return -1;
@@ -1217,11 +1587,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 +1599,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);
}
@@ -1405,7 +1775,8 @@ router_parse_entry_from_string(const char *s, const char *end,
log_warn(LD_DIR, "Couldn't parse ed25519 cert");
goto err;
}
- router->signing_key_cert = cert; /* makes sure it gets freed. */
+ /* makes sure it gets freed. */
+ router->cache_info.signing_key_cert = cert;
if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
! cert->signing_key_included) {
@@ -1452,7 +1823,8 @@ router_parse_entry_from_string(const char *s, const char *end,
if (router_get_hash_impl_helper(s, end-s, "router ",
"\nrouter-sig-ed25519",
- ' ', &signed_start, &signed_end) < 0) {
+ ' ', LOG_WARN,
+ &signed_start, &signed_end) < 0) {
log_warn(LD_DIR, "Can't find ed25519-signed portion of descriptor");
goto err;
}
@@ -1465,12 +1837,13 @@ router_parse_entry_from_string(const char *s, const char *end,
ed25519_checkable_t check[3];
int check_ok[3];
- if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+ time_t expires = TIME_MAX;
+ if (tor_cert_get_checkable_sig(&check[0], cert, NULL, &expires) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
goto err;
}
if (tor_cert_get_checkable_sig(&check[1],
- ntor_cc_cert, &ntor_cc_pk) < 0) {
+ ntor_cc_cert, &ntor_cc_pk, &expires) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
goto err;
}
@@ -1500,10 +1873,7 @@ router_parse_entry_from_string(const char *s, const char *end,
}
/* We check this before adding it to the routerlist. */
- if (cert->valid_until < ntor_cc_cert->valid_until)
- router->cert_expiration_time = cert->valid_until;
- else
- router->cert_expiration_time = ntor_cc_cert->valid_until;
+ router->cert_expiration_time = expires;
}
}
@@ -1512,7 +1882,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;
@@ -1528,6 +1899,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]);
}
@@ -1566,7 +1941,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;
@@ -1580,7 +1955,7 @@ router_parse_entry_from_string(const char *s, const char *end,
escaped(tok->args[i]));
goto err;
}
- smartlist_add(router->declared_family, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(router->declared_family, tok->args[i]);
}
}
@@ -1593,15 +1968,17 @@ 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]));
}
if (tok->n_args >= 2) {
- if (digest256_from_base64(router->extra_info_digest256, tok->args[1])
- < 0) {
+ if (digest256_from_base64(router->cache_info.extra_info_digest256,
+ tok->args[1]) < 0) {
log_warn(LD_DIR, "Invalid extra info digest256 %s",
escaped(tok->args[1]));
}
@@ -1619,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end,
}
tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
#ifdef COUNT_DISTINCT_DIGESTS
if (!verified_digests)
verified_digests = digestmap_new();
@@ -1690,6 +2066,9 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
* parse that's covered by the hash. */
int can_dl_again = 0;
+ if (BUG(s == NULL))
+ return NULL;
+
if (!end) {
end = s + strlen(s);
}
@@ -1737,7 +2116,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;
@@ -1786,7 +2165,9 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
log_warn(LD_DIR, "Couldn't parse ed25519 cert");
goto err;
}
- extrainfo->signing_key_cert = cert; /* makes sure it gets freed. */
+ /* makes sure it gets freed. */
+ extrainfo->cache_info.signing_key_cert = cert;
+
if (cert->cert_type != CERT_TYPE_ID_SIGNING ||
! cert->signing_key_included) {
log_warn(LD_DIR, "Invalid form for ed25519 cert");
@@ -1795,7 +2176,8 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
if (router_get_hash_impl_helper(s, end-s, "extra-info ",
"\nrouter-sig-ed25519",
- ' ', &signed_start, &signed_end) < 0) {
+ ' ', LOG_WARN,
+ &signed_start, &signed_end) < 0) {
log_warn(LD_DIR, "Can't find ed25519-signed portion of extrainfo");
goto err;
}
@@ -1808,7 +2190,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
ed25519_checkable_t check[2];
int check_ok[2];
- if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+ if (tor_cert_get_checkable_sig(&check[0], cert, NULL, NULL) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
goto err;
}
@@ -1848,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
}
if (key) {
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
"extra-info") < 0)
goto err;
@@ -1957,7 +2338,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;
@@ -1978,7 +2359,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) {
@@ -2163,7 +2544,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,
@@ -2202,7 +2583,7 @@ routerstatus_parse_entry_from_string(memarea_t *area,
goto err;
}
} else if (flav == FLAV_MICRODESC) {
- offset = -1; /* There is no identity digest */
+ offset = -1; /* There is no descriptor digest in an md consensus r line */
}
if (vote_rs) {
@@ -2277,6 +2658,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"))
@@ -2307,14 +2689,41 @@ 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);
+ rs->supports_ed25519_link_handshake =
+ protocol_list_supports_protocol(tok->args[0], PRT_LINKAUTH, 3);
+ rs->supports_ed25519_hs_intro =
+ protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4);
+ rs->supports_v3_hsdir =
+ protocol_list_supports_protocol(tok->args[0], PRT_HSDIR,
+ PROTOVER_HSDIR_V3);
}
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 (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) {
+ /* Bug #22447 forces us to filter on this version. */
+ if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) {
+ rs->supports_v3_hsdir = 0;
+ }
}
if (vote_rs) {
vote_rs->version = tor_strdup(tok->args[0]);
@@ -2397,6 +2806,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);
@@ -2448,7 +2861,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b)
int
networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
{
- int64_t weight_scale;
int64_t G=0, M=0, E=0, D=0, T=0;
double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
double Gtotal=0, Mtotal=0, Etotal=0;
@@ -2456,7 +2868,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
int valid = 1;
(void) consensus_method;
- weight_scale = networkstatus_get_weight_scale_param(ns);
+ const int64_t weight_scale = networkstatus_get_weight_scale_param(ns);
+ tor_assert(weight_scale >= 1);
Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
@@ -2837,6 +3250,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 vote is malformed for the shared random value(s). */
+ if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) {
+ log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s",
+ voter_identity);
+ /* 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 *
@@ -2848,9 +3389,9 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
networkstatus_voter_info_t *voter = NULL;
networkstatus_t *ns = NULL;
common_digests_t ns_digests;
+ uint8_t sha3_as_signed[DIGEST256_LEN];
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;
@@ -2862,7 +3403,8 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
if (eos_out)
*eos_out = NULL;
- if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
+ if (router_get_networkstatus_v3_hashes(s, &ns_digests) ||
+ router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) {
log_warn(LD_DIR, "Unable to compute digest of network-status");
goto err;
}
@@ -2879,6 +3421,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
ns = tor_malloc_zero(sizeof(networkstatus_t));
memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
+ memcpy(&ns->digest_sha3_as_signed, sha3_as_signed, sizeof(sha3_as_signed));
tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION);
tor_assert(tok);
@@ -2933,22 +3476,32 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS);
if (tok) {
for (i=0; i < tok->n_args; ++i)
- smartlist_add(ns->supported_methods, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->supported_methods, tok->args[i]);
} else {
- smartlist_add(ns->supported_methods, tor_strdup("1"));
+ smartlist_add_strdup(ns->supported_methods, "1");
}
} 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;
@@ -2963,14 +3516,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) {
@@ -3004,7 +3560,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
ns->package_lines = smartlist_new();
if (package_lst) {
SMARTLIST_FOREACH(package_lst, directory_token_t *, t,
- smartlist_add(ns->package_lines, tor_strdup(t->args[0])));
+ smartlist_add_strdup(ns->package_lines, t->args[0]));
}
smartlist_free(package_lst);
}
@@ -3013,7 +3569,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
ns->known_flags = smartlist_new();
inorder = 1;
for (i = 0; i < tok->n_args; ++i) {
- smartlist_add(ns->known_flags, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->known_flags, tok->args[i]);
if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) {
log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
inorder = 0;
@@ -3065,7 +3621,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
}
tor_free(last_kwd);
last_kwd = tor_strndup(tok->args[i], eq_pos);
- smartlist_add(ns->net_params, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->net_params, tok->args[i]);
}
if (!inorder) {
log_warn(LD_DIR, "params not in order");
@@ -3094,7 +3650,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;
@@ -3120,6 +3677,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)
@@ -3143,7 +3701,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;
@@ -3166,9 +3725,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;
@@ -3179,6 +3738,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();
@@ -3189,19 +3764,21 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
if (ns->type != NS_TYPE_CONSENSUS) {
vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
- rs, 0, 0))
+ rs, 0, 0)) {
smartlist_add(ns->routerstatus_list, rs);
- else {
- tor_free(rs->version);
- tor_free(rs);
+ } else {
+ vote_routerstatus_free(rs);
}
} else {
routerstatus_t *rs;
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) {
@@ -3286,7 +3863,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
goto err;
}
- smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->weight_params, tok->args[i]);
}
}
@@ -3327,7 +3904,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;
@@ -3342,7 +3920,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);
@@ -3505,7 +4084,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;
@@ -3528,8 +4107,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 "
@@ -3538,13 +4117,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;
@@ -3617,14 +4196,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;
@@ -3913,445 +4492,6 @@ assert_addr_policy_ok(smartlist_t *lst)
});
}
-/*
- * Low-level tokenizer for router descriptors and directories.
- */
-
-/** Free all resources allocated for <b>tok</b> */
-static void
-token_clear(directory_token_t *tok)
-{
- if (tok->key)
- crypto_pk_free(tok->key);
-}
-
-#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz)
-#define ALLOC(sz) memarea_alloc(area,sz)
-#define STRDUP(str) memarea_strdup(area,str)
-#define STRNDUP(str,n) memarea_strndup(area,(str),(n))
-
-#define RET_ERR(msg) \
- STMT_BEGIN \
- if (tok) token_clear(tok); \
- tok = ALLOC_ZERO(sizeof(directory_token_t)); \
- tok->tp = ERR_; \
- tok->error = STRDUP(msg); \
- goto done_tokenizing; \
- STMT_END
-
-/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys
- * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>.
- * Return <b>tok</b> on success, or a new ERR_ token if the token didn't
- * conform to the syntax we wanted.
- **/
-static inline directory_token_t *
-token_check_object(memarea_t *area, const char *kwd,
- directory_token_t *tok, obj_syntax o_syn)
-{
- char ebuf[128];
- switch (o_syn) {
- case NO_OBJ:
- /* No object is allowed for this token. */
- if (tok->object_body) {
- tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd);
- RET_ERR(ebuf);
- }
- if (tok->key) {
- tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- case NEED_OBJ:
- /* There must be a (non-key) object. */
- if (!tok->object_body) {
- tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- case NEED_KEY_1024: /* There must be a 1024-bit public key. */
- case NEED_SKEY_1024: /* There must be a 1024-bit private key. */
- if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) {
- tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits",
- kwd, crypto_pk_num_bits(tok->key));
- RET_ERR(ebuf);
- }
- /* fall through */
- case NEED_KEY: /* There must be some kind of key. */
- if (!tok->key) {
- tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd);
- RET_ERR(ebuf);
- }
- if (o_syn != NEED_SKEY_1024) {
- if (crypto_pk_key_is_private(tok->key)) {
- tor_snprintf(ebuf, sizeof(ebuf),
- "Private key given for %s, which wants a public key", kwd);
- RET_ERR(ebuf);
- }
- } else { /* o_syn == NEED_SKEY_1024 */
- if (!crypto_pk_key_is_private(tok->key)) {
- tor_snprintf(ebuf, sizeof(ebuf),
- "Public key given for %s, which wants a private key", kwd);
- RET_ERR(ebuf);
- }
- }
- break;
- case OBJ_OK:
- /* Anything goes with this token. */
- break;
- }
-
- done_tokenizing:
- return tok;
-}
-
-/** Helper: parse space-separated arguments from the string <b>s</b> ending at
- * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the
- * number of parsed elements into the n_args field of <b>tok</b>. Allocate
- * all storage in <b>area</b>. Return the number of arguments parsed, or
- * return -1 if there was an insanely high number of arguments. */
-static inline int
-get_token_arguments(memarea_t *area, directory_token_t *tok,
- const char *s, const char *eol)
-{
-/** Largest number of arguments we'll accept to any token, ever. */
-#define MAX_ARGS 512
- char *mem = memarea_strndup(area, s, eol-s);
- char *cp = mem;
- int j = 0;
- char *args[MAX_ARGS];
- while (*cp) {
- if (j == MAX_ARGS)
- return -1;
- args[j++] = cp;
- cp = (char*)find_whitespace(cp);
- if (!cp || !*cp)
- break; /* End of the line. */
- *cp++ = '\0';
- cp = (char*)eat_whitespace(cp);
- }
- tok->n_args = j;
- tok->args = memarea_memdup(area, args, j*sizeof(char*));
- return j;
-#undef MAX_ARGS
-}
-
-/** Helper function: read the next token from *s, advance *s to the end of the
- * token, and return the parsed token. Parse *<b>s</b> according to the list
- * of tokens in <b>table</b>.
- */
-static directory_token_t *
-get_next_token(memarea_t *area,
- const char **s, const char *eos, token_rule_t *table)
-{
- /** Reject any object at least this big; it is probably an overflow, an
- * attack, a bug, or some other nonsense. */
-#define MAX_UNPARSED_OBJECT_SIZE (128*1024)
- /** Reject any line at least this big; it is probably an overflow, an
- * attack, a bug, or some other nonsense. */
-#define MAX_LINE_LENGTH (128*1024)
-
- const char *next, *eol, *obstart;
- size_t obname_len;
- int i;
- directory_token_t *tok;
- obj_syntax o_syn = NO_OBJ;
- char ebuf[128];
- const char *kwd = "";
-
- tor_assert(area);
- tok = ALLOC_ZERO(sizeof(directory_token_t));
- tok->tp = ERR_;
-
- /* Set *s to first token, eol to end-of-line, next to after first token */
- *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */
- tor_assert(eos >= *s);
- eol = memchr(*s, '\n', eos-*s);
- if (!eol)
- eol = eos;
- if (eol - *s > MAX_LINE_LENGTH) {
- RET_ERR("Line far too long");
- }
-
- next = find_whitespace_eos(*s, eol);
-
- if (!strcmp_len(*s, "opt", next-*s)) {
- /* Skip past an "opt" at the start of the line. */
- *s = eat_whitespace_eos_no_nl(next, eol);
- next = find_whitespace_eos(*s, eol);
- } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */
- RET_ERR("Unexpected EOF");
- }
-
- /* Search the table for the appropriate entry. (I tried a binary search
- * instead, but it wasn't any faster.) */
- for (i = 0; table[i].t ; ++i) {
- if (!strcmp_len(*s, table[i].t, next-*s)) {
- /* We've found the keyword. */
- kwd = table[i].t;
- tok->tp = table[i].v;
- o_syn = table[i].os;
- *s = eat_whitespace_eos_no_nl(next, eol);
- /* We go ahead whether there are arguments or not, so that tok->args is
- * always set if we want arguments. */
- if (table[i].concat_args) {
- /* The keyword takes the line as a single argument */
- tok->args = ALLOC(sizeof(char*));
- tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */
- tok->n_args = 1;
- } else {
- /* This keyword takes multiple arguments. */
- if (get_token_arguments(area, tok, *s, eol)<0) {
- tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd);
- RET_ERR(ebuf);
- }
- *s = eol;
- }
- if (tok->n_args < table[i].min_args) {
- tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd);
- RET_ERR(ebuf);
- } else if (tok->n_args > table[i].max_args) {
- tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- }
- }
-
- if (tok->tp == ERR_) {
- /* No keyword matched; call it an "K_opt" or "A_unrecognized" */
- if (**s == '@')
- tok->tp = A_UNKNOWN_;
- else
- tok->tp = K_OPT;
- tok->args = ALLOC(sizeof(char*));
- tok->args[0] = STRNDUP(*s, eol-*s);
- tok->n_args = 1;
- o_syn = OBJ_OK;
- }
-
- /* Check whether there's an object present */
- *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */
- tor_assert(eos >= *s);
- eol = memchr(*s, '\n', eos-*s);
- if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */
- goto check_object;
-
- obstart = *s; /* Set obstart to start of object spec */
- if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */
- strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */
- (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */
- RET_ERR("Malformed object: bad begin line");
- }
- tok->object_type = STRNDUP(*s+11, eol-*s-16);
- obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */
- *s = eol+1; /* Set *s to possible start of object data (could be eos) */
-
- /* Go to the end of the object */
- next = tor_memstr(*s, eos-*s, "-----END ");
- if (!next) {
- RET_ERR("Malformed object: missing object end line");
- }
- tor_assert(eos >= next);
- eol = memchr(next, '\n', eos-next);
- if (!eol) /* end-of-line marker, or eos if there's no '\n' */
- eol = eos;
- /* Validate the ending tag, which should be 9 + NAME + 5 + eol */
- if ((size_t)(eol-next) != 9+obname_len+5 ||
- strcmp_len(next+9, tok->object_type, obname_len) ||
- strcmp_len(eol-5, "-----", 5)) {
- tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s",
- tok->object_type);
- ebuf[sizeof(ebuf)-1] = '\0';
- RET_ERR(ebuf);
- }
- if (next - *s > MAX_UNPARSED_OBJECT_SIZE)
- RET_ERR("Couldn't parse object: missing footer or object much too big.");
-
- if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */
- tok->key = crypto_pk_new();
- if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart))
- RET_ERR("Couldn't parse public key.");
- } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */
- tok->key = crypto_pk_new();
- if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart))
- RET_ERR("Couldn't parse private key.");
- } else { /* If it's something else, try to base64-decode it */
- int r;
- tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */
- r = base64_decode(tok->object_body, next-*s, *s, next-*s);
- if (r<0)
- RET_ERR("Malformed object: bad base64-encoded data");
- tok->object_size = r;
- }
- *s = eol;
-
- check_object:
- tok = token_check_object(area, kwd, tok, o_syn);
-
- done_tokenizing:
- return tok;
-
-#undef RET_ERR
-#undef ALLOC
-#undef ALLOC_ZERO
-#undef STRDUP
-#undef STRNDUP
-}
-
-/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
- * them to <b>out</b>. Parse according to the token rules in <b>table</b>.
- * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the
- * entire string.
- */
-static int
-tokenize_string(memarea_t *area,
- const char *start, const char *end, smartlist_t *out,
- token_rule_t *table, int flags)
-{
- const char **s;
- directory_token_t *tok = NULL;
- int counts[NIL_];
- int i;
- int first_nonannotation;
- int prev_len = smartlist_len(out);
- tor_assert(area);
-
- s = &start;
- if (!end) {
- end = start+strlen(start);
- } else {
- /* it's only meaningful to check for nuls if we got an end-of-string ptr */
- if (memchr(start, '\0', end-start)) {
- log_warn(LD_DIR, "parse error: internal NUL character.");
- return -1;
- }
- }
- for (i = 0; i < NIL_; ++i)
- counts[i] = 0;
-
- SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]);
-
- while (*s < end && (!tok || tok->tp != EOF_)) {
- tok = get_next_token(area, s, end, table);
- if (tok->tp == ERR_) {
- log_warn(LD_DIR, "parse error: %s", tok->error);
- token_clear(tok);
- return -1;
- }
- ++counts[tok->tp];
- smartlist_add(out, tok);
- *s = eat_whitespace_eos(*s, end);
- }
-
- if (flags & TS_NOCHECK)
- return 0;
-
- if ((flags & TS_ANNOTATIONS_OK)) {
- first_nonannotation = -1;
- for (i = 0; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) {
- first_nonannotation = i;
- break;
- }
- }
- if (first_nonannotation < 0) {
- log_warn(LD_DIR, "parse error: item contains only annotations");
- return -1;
- }
- for (i=first_nonannotation; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
- log_warn(LD_DIR, "parse error: Annotations mixed with keywords");
- return -1;
- }
- }
- if ((flags & TS_NO_NEW_ANNOTATIONS)) {
- if (first_nonannotation != prev_len) {
- log_warn(LD_DIR, "parse error: Unexpected annotations.");
- return -1;
- }
- }
- } else {
- for (i=0; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
- log_warn(LD_DIR, "parse error: no annotations allowed.");
- return -1;
- }
- }
- first_nonannotation = 0;
- }
- for (i = 0; table[i].t; ++i) {
- if (counts[table[i].v] < table[i].min_cnt) {
- log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t);
- return -1;
- }
- if (counts[table[i].v] > table[i].max_cnt) {
- log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t);
- return -1;
- }
- if (table[i].pos & AT_START) {
- if (smartlist_len(out) < 1 ||
- (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) {
- log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t);
- return -1;
- }
- }
- if (table[i].pos & AT_END) {
- if (smartlist_len(out) < 1 ||
- (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) {
- log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t);
- return -1;
- }
- }
- }
- return 0;
-}
-
-/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
- * NULL if no such keyword is found.
- */
-static directory_token_t *
-find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
-{
- SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t);
- return NULL;
-}
-
-/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail
- * with an assert if no such keyword is found.
- */
-static directory_token_t *
-find_by_keyword_(smartlist_t *s, directory_keyword keyword,
- const char *keyword_as_string)
-{
- directory_token_t *tok = find_opt_by_keyword(s, keyword);
- if (PREDICT_UNLIKELY(!tok)) {
- log_err(LD_BUG, "Missing %s [%d] in directory object that should have "
- "been validated. Internal error.", keyword_as_string, (int)keyword);
- tor_assert(tok);
- }
- return tok;
-}
-
-/** If there are any directory_token_t entries in <b>s</b> whose keyword is
- * <b>k</b>, return a newly allocated smartlist_t containing all such entries,
- * in the same order in which they occur in <b>s</b>. Otherwise return
- * NULL. */
-static smartlist_t *
-find_all_by_keyword(smartlist_t *s, directory_keyword k)
-{
- smartlist_t *out = NULL;
- SMARTLIST_FOREACH(s, directory_token_t *, t,
- if (t->tp == k) {
- if (!out)
- out = smartlist_new();
- smartlist_add(out, t);
- });
- return out;
-}
-
/** Return a newly allocated smartlist of all accept or reject tokens in
* <b>s</b>.
*/
@@ -4377,16 +4517,18 @@ static int
router_get_hash_impl_helper(const char *s, size_t s_len,
const char *start_str,
const char *end_str, char end_c,
+ int log_severity,
const char **start_out, const char **end_out)
{
const char *start, *end;
start = tor_memstr(s, s_len, start_str);
if (!start) {
- log_warn(LD_DIR,"couldn't find start of hashed material \"%s\"",start_str);
+ log_fn(log_severity,LD_DIR,
+ "couldn't find start of hashed material \"%s\"",start_str);
return -1;
}
if (start != s && *(start-1) != '\n') {
- log_warn(LD_DIR,
+ log_fn(log_severity,LD_DIR,
"first occurrence of \"%s\" is not at the start of a line",
start_str);
return -1;
@@ -4394,12 +4536,14 @@ router_get_hash_impl_helper(const char *s, size_t s_len,
end = tor_memstr(start+strlen(start_str),
s_len - (start-s) - strlen(start_str), end_str);
if (!end) {
- log_warn(LD_DIR,"couldn't find end of hashed material \"%s\"",end_str);
+ log_fn(log_severity,LD_DIR,
+ "couldn't find end of hashed material \"%s\"",end_str);
return -1;
}
end = memchr(end+strlen(end_str), end_c, s_len - (end-s) - strlen(end_str));
if (!end) {
- log_warn(LD_DIR,"couldn't find EOL");
+ log_fn(log_severity,LD_DIR,
+ "couldn't find EOL");
return -1;
}
++end;
@@ -4423,17 +4567,28 @@ router_get_hash_impl(const char *s, size_t s_len, char *digest,
digest_algorithm_t alg)
{
const char *start=NULL, *end=NULL;
- if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
+ if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,LOG_WARN,
&start,&end)<0)
return -1;
+ return router_compute_hash_final(digest, start, end-start, alg);
+}
+
+/** Compute the digest of the <b>len</b>-byte directory object at
+ * <b>start</b>, using <b>alg</b>. Store the result in <b>digest</b>, which
+ * must be long enough to hold it. */
+MOCK_IMPL(STATIC int,
+router_compute_hash_final,(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg))
+{
if (alg == DIGEST_SHA1) {
- if (crypto_digest(digest, start, end-start)) {
+ if (crypto_digest(digest, start, len) < 0) {
log_warn(LD_BUG,"couldn't compute digest");
return -1;
}
} else {
- if (crypto_digest256(digest, start, end-start, alg)) {
+ if (crypto_digest256(digest, start, len, alg) < 0) {
log_warn(LD_BUG,"couldn't compute digest");
return -1;
}
@@ -4449,7 +4604,7 @@ router_get_hashes_impl(const char *s, size_t s_len, common_digests_t *digests,
const char *end_str, char end_c)
{
const char *start=NULL, *end=NULL;
- if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
+ if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,LOG_WARN,
&start,&end)<0)
return -1;
@@ -4619,11 +4774,13 @@ microdescs_parse_from_string(const char *s, const char *eos,
if (!strcmp(t->args[0], "ed25519")) {
if (md->ed25519_identity_pkey) {
log_warn(LD_DIR, "Extra ed25519 key in microdesc");
+ smartlist_free(id_lines);
goto next;
}
ed25519_public_key_t k;
if (ed25519_public_from_base64(&k, t->args[1])<0) {
log_warn(LD_DIR, "Bogus ed25519 key in microdesc");
+ smartlist_free(id_lines);
goto next;
}
md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k));
@@ -4649,7 +4806,7 @@ microdescs_parse_from_string(const char *s, const char *eos,
escaped(tok->args[i]));
goto next;
}
- smartlist_add(md->family, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(md->family, tok->args[i]);
}
}
@@ -4685,40 +4842,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:
@@ -4740,6 +4935,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 ]
*/
@@ -4755,7 +4951,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; \
@@ -4826,7 +5026,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;
@@ -4845,26 +5045,37 @@ tor_version_compare(tor_version_t *a, tor_version_t *b)
int i;
tor_assert(a);
tor_assert(b);
- if ((i = a->major - b->major))
- return i;
- else if ((i = a->minor - b->minor))
- return i;
- else if ((i = a->micro - b->micro))
- return i;
- else if ((i = a->status - b->status))
- return i;
- else if ((i = a->patchlevel - b->patchlevel))
- return i;
- else if ((i = strcmp(a->status_tag, b->status_tag)))
- return i;
- else if ((i = a->svn_revision - b->svn_revision))
- return i;
- else if ((i = a->git_tag_len - b->git_tag_len))
- return i;
- else if (a->git_tag_len)
- return fast_memcmp(a->git_tag, b->git_tag, a->git_tag_len);
+
+ /* We take this approach to comparison to ensure the same (bogus!) behavior
+ * on all inputs as we would have seen before bug #21278 was fixed. The
+ * only important difference here is that this method doesn't cause
+ * a signed integer underflow.
+ */
+#define CMP(field) do { \
+ unsigned aval = (unsigned) a->field; \
+ unsigned bval = (unsigned) b->field; \
+ int result = (int) (aval - bval); \
+ if (result < 0) \
+ return -1; \
+ else if (result > 0) \
+ return 1; \
+ } while (0)
+
+ CMP(major);
+ CMP(minor);
+ CMP(micro);
+ CMP(status);
+ CMP(patchlevel);
+ if ((i = strcmp(a->status_tag, b->status_tag)))
+ return i;
+ CMP(svn_revision);
+ CMP(git_tag_len);
+ if (a->git_tag_len)
+ return fast_memcmp(a->git_tag, b->git_tag, a->git_tag_len);
else
- return 0;
+ return 0;
+
+#undef CMP
}
/** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
@@ -4971,7 +5182,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.",
@@ -5042,7 +5253,8 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
* descriptor. */
tok = find_by_keyword(tokens, R_PUBLICATION_TIME);
tor_assert(tok->n_args == 1);
- if (parse_iso_time_(tok->args[0], &result->timestamp, strict_time_fmt) < 0) {
+ if (parse_iso_time_(tok->args[0], &result->timestamp,
+ strict_time_fmt, 0) < 0) {
log_warn(LD_REND, "Invalid publication time: '%s'", tok->args[0]);
goto err;
}
@@ -5081,7 +5293,6 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
}
/* Parse and verify signature. */
tok = find_by_keyword(tokens, R_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
"v2 rendezvous service descriptor") < 0)
goto err;
@@ -5168,7 +5379,7 @@ rend_decrypt_introduction_points(char **ipos_decrypted,
crypto_cipher_free(cipher);
len = ipos_encrypted_size - 2 - client_entries_len - CIPHER_IV_LEN;
- dec = tor_malloc(len);
+ dec = tor_malloc_zero(len + 1);
declen = crypto_cipher_decrypt_with_iv(session_key, dec, len,
ipos_encrypted + 2 + client_entries_len,
ipos_encrypted_size - 2 - client_entries_len);
@@ -5200,7 +5411,7 @@ rend_decrypt_introduction_points(char **ipos_decrypted,
"small.");
return -1;
}
- dec = tor_malloc_zero(ipos_encrypted_size - CIPHER_IV_LEN - 1);
+ dec = tor_malloc_zero(ipos_encrypted_size - CIPHER_IV_LEN - 1 + 1);
declen = crypto_cipher_decrypt_with_iv(descriptor_cookie, dec,
ipos_encrypted_size -
@@ -5371,6 +5582,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();
@@ -5380,8 +5592,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)
@@ -5410,12 +5620,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. */
@@ -5437,23 +5645,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]));
+ 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;
}
- /* 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]));
- goto err;
- }
- memcpy(parsed_entry->descriptor_cookie, descriptor_cookie_tmp,
- REND_DESC_COOKIE_LEN);
}
result = strmap_size(parsed_clients);
goto done;
@@ -5468,3 +5666,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..088f773c5e 100644
--- a/src/or/routerparse.h
+++ b/src/or/routerparse.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -16,6 +16,11 @@ int router_get_router_hash(const char *s, size_t s_len, char *digest);
int router_get_dir_hash(const char *s, char *digest);
int router_get_networkstatus_v3_hashes(const char *s,
common_digests_t *digests);
+int router_get_networkstatus_v3_signed_boundaries(const char *s,
+ const char **start_out,
+ const char **end_out);
+int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
+ const char *s);
int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest);
#define DIROBJ_MAX_SIG_LEN 256
char *router_get_dirobj_signature(const char *digest,
@@ -45,6 +50,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 +93,46 @@ 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_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);
+MOCK_DECL(STATIC void,dump_desc,(const char *desc, const char *type));
+MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg));
+MOCK_DECL(STATIC int, signed_digest_equals,
+ (const uint8_t *d1, const uint8_t *d2, size_t len));
#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..4906c6a51d 100644
--- a/src/or/routerset.c
+++ b/src/or/routerset.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -9,11 +9,26 @@
*
* \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
#include "or.h"
+#include "bridges.h"
#include "geoip.h"
#include "nodelist.h"
#include "policies.h"
@@ -248,12 +263,12 @@ routerset_add_unknown_ccs(routerset_t **setp, int only_if_some_cc_set)
geoip_get_country("A1") >= 0;
if (add_unknown) {
- smartlist_add(set->country_names, tor_strdup("??"));
- smartlist_add(set->list, tor_strdup("{??}"));
+ smartlist_add_strdup(set->country_names, "??");
+ smartlist_add_strdup(set->list, "{??}");
}
if (add_a1) {
- smartlist_add(set->country_names, tor_strdup("a1"));
- smartlist_add(set->list, tor_strdup("{a1}"));
+ smartlist_add_strdup(set->country_names, "a1");
+ smartlist_add_strdup(set->list, "{a1}");
}
if (add_unknown || add_a1) {
@@ -320,6 +335,18 @@ routerset_contains_node(const routerset_t *set, const node_t *node)
return 0;
}
+/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */
+int
+routerset_contains_bridge(const routerset_t *set, const bridge_info_t *bridge)
+{
+ const char *id = (const char*)bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
+
+ tor_assert(addrport);
+ return routerset_contains(set, &addrport->addr, addrport->port,
+ NULL, id, -1);
+}
+
/** Add every known node_t that is a member of <b>routerset</b> to
* <b>out</b>, but never add any that are part of <b>excludeset</b>.
* If <b>running_only</b>, only add the running ones. */
diff --git a/src/or/routerset.h b/src/or/routerset.h
index c2f7205c3e..a63677b471 100644
--- a/src/or/routerset.h
+++ b/src/or/routerset.h
@@ -1,6 +1,6 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -26,8 +26,11 @@ int routerset_contains_routerstatus(const routerset_t *set,
country_t country);
int routerset_contains_extendinfo(const routerset_t *set,
const extend_info_t *ei);
-
+struct bridge_info_t;
+int routerset_contains_bridge(const routerset_t *set,
+ const struct bridge_info_t *bridge);
int routerset_contains_node(const routerset_t *set, const node_t *node);
+
void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
const routerset_t *excludeset,
int running_only);
diff --git a/src/or/scheduler.c b/src/or/scheduler.c
index 8e4810b199..0d31c7d58c 100644
--- a/src/or/scheduler.c
+++ b/src/or/scheduler.c
@@ -1,11 +1,6 @@
-/* * Copyright (c) 2013-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2013-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
-/**
- * \file scheduler.c
- * \brief Relay scheduling system
- **/
-
#include "or.h"
#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */
@@ -15,11 +10,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
@@ -36,66 +27,102 @@ static uint32_t sched_q_high_water = 32768;
static uint32_t sched_max_flush_cells = 16;
-/*
- * Write scheduling works by keeping track of which channels can
+/**
+ * \file scheduler.c
+ * \brief Channel scheduling system: decides which channels should send and
+ * receive when.
+ *
+ * This module implements a scheduler algorithm, to decide
+ * which channels should send/receive when.
+ *
+ * The earliest versions of Tor approximated a kind of round-robin system
+ * among active connections, but only approximated it.
+ *
+ * Now, write scheduling works by keeping track of which channels can
* accept cells, and have cells to write. From the scheduler's perspective,
* a channel can be in four possible states:
*
- * 1.) Not open for writes, no cells to send
- * - Not much to do here, and the channel will have scheduler_state ==
- * SCHED_CHAN_IDLE
- * - Transitions from:
- * - Open for writes/has cells by simultaneously draining all circuit
+ * <ol>
+ * <li>
+ * Not open for writes, no cells to send.
+ * <ul><li> Not much to do here, and the channel will have scheduler_state
+ * == SCHED_CHAN_IDLE
+ * <li> Transitions from:
+ * <ul>
+ * <li>Open for writes/has cells by simultaneously draining all circuit
* queues and filling the output buffer.
- * - Transitions to:
- * - Not open for writes/has cells by arrival of cells on an attached
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li> Not open for writes/has cells by arrival of cells on an attached
* circuit (this would be driven from append_cell_to_circuit_queue())
- * - Open for writes/no cells by a channel type specific path;
+ * <li> Open for writes/no cells by a channel type specific path;
* driven from connection_or_flushed_some() for channel_tls_t.
+ * </ul>
+ * </ul>
*
- * 2.) Open for writes, no cells to send
- * - Not much here either; this will be the state an idle but open channel
- * can be expected to settle in. It will have scheduler_state ==
- * SCHED_CHAN_WAITING_FOR_CELLS
- * - Transitions from:
- * - Not open for writes/no cells by flushing some of the output
+ * <li> Open for writes, no cells to send
+ * <ul>
+ * <li>Not much here either; this will be the state an idle but open
+ * channel can be expected to settle in. It will have scheduler_state
+ * == SCHED_CHAN_WAITING_FOR_CELLS
+ * <li> Transitions from:
+ * <ul>
+ * <li>Not open for writes/no cells by flushing some of the output
* buffer.
- * - Open for writes/has cells by the scheduler moving cells from
+ * <li>Open for writes/has cells by the scheduler moving cells from
* circuit queues to channel output queue, but not having enough
* to fill the output queue.
- * - Transitions to:
- * - Open for writes/has cells by arrival of new cells on an attached
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Open for writes/has cells by arrival of new cells on an attached
* circuit, in append_cell_to_circuit_queue()
+ * </ul>
+ * </ul>
*
- * 3.) Not open for writes, cells to send
- * - This is the state of a busy circuit limited by output bandwidth;
+ * <li>Not open for writes, cells to send
+ * <ul>
+ * <li>This is the state of a busy circuit limited by output bandwidth;
* cells have piled up in the circuit queues waiting to be relayed.
* The channel will have scheduler_state == SCHED_CHAN_WAITING_TO_WRITE.
- * - Transitions from:
- * - Not open for writes/no cells by arrival of cells on an attached
+ * <li> Transitions from:
+ * <ul>
+ * <li>Not open for writes/no cells by arrival of cells on an attached
* circuit
- * - Open for writes/has cells by filling an output buffer without
+ * <li> Open for writes/has cells by filling an output buffer without
* draining all cells from attached circuits
- * - Transitions to:
- * - Opens for writes/has cells by draining some of the output buffer
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Opens for writes/has cells by draining some of the output buffer
* via the connection_or_flushed_some() path (for channel_tls_t).
+ * </ul>
+ * </ul>
*
- * 4.) Open for writes, cells to send
- * - This connection is ready to relay some cells and waiting for
+ * <li>Open for writes, cells to send
+ * <ul>
+ * <li>This connection is ready to relay some cells and waiting for
* the scheduler to choose it. The channel will have scheduler_state ==
* SCHED_CHAN_PENDING.
- * - Transitions from:
- * - Not open for writes/has cells by the connection_or_flushed_some()
+ * <li>Transitions from:
+ * <ul>
+ * <li> Not open for writes/has cells by the connection_or_flushed_some()
* path
- * - Open for writes/no cells by the append_cell_to_circuit_queue()
+ * <li> Open for writes/no cells by the append_cell_to_circuit_queue()
* path
- * - Transitions to:
- * - Not open for writes/no cells by draining all circuit queues and
- * simultaneously filling the output buffer.
- * - Not open for writes/has cells by writing enough cells to fill the
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Not open for writes/no cells by draining all circuit queues and
+ * simultaneously filling the output buffer.
+ * <li>Not open for writes/has cells by writing enough cells to fill the
* output buffer
- * - Open for writes/no cells by draining all attached circuit queues
+ * <li>Open for writes/no cells by draining all attached circuit queues
* without also filling the output buffer
+ * </ul>
+ * </ul>
+ * </ol>
*
* Other event-driven parts of the code move channels between these scheduling
* states by calling scheduler functions; the scheduler only runs on open-for-
@@ -255,7 +282,7 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan))
*/
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE;
log_debug(LD_SCHED,
@@ -297,7 +324,7 @@ scheduler_channel_has_waiting_cells,(channel_t *chan))
chan->scheduler_state = SCHED_CHAN_PENDING;
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
log_debug(LD_SCHED,
"Channel " U64_FORMAT " at %p went from waiting_for_cells "
@@ -373,7 +400,7 @@ scheduler_release_channel,(channel_t *chan))
if (chan->scheduler_state == SCHED_CHAN_PENDING) {
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
@@ -403,7 +430,7 @@ scheduler_run, (void))
/* Pop off a channel */
chan = smartlist_pqueue_pop(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx));
+ offsetof(channel_t, sched_heap_idx));
tor_assert(chan);
/* Figure out how many cells we can write */
@@ -500,13 +527,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);
+ offsetof(channel_t, sched_heap_idx),
+ readd_chan);
+ } SMARTLIST_FOREACH_END(readd_chan);
smartlist_free(to_readd);
}
@@ -554,7 +581,7 @@ scheduler_channel_wants_writes(channel_t *chan)
*/
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_PENDING;
log_debug(LD_SCHED,
@@ -597,11 +624,11 @@ scheduler_touch_channel(channel_t *chan)
/* Remove and re-add it */
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
/* else no-op, since it isn't in the queue */
diff --git a/src/or/scheduler.h b/src/or/scheduler.h
index 94a44a0aa3..e29c13de7e 100644
--- a/src/or/scheduler.h
+++ b/src/or/scheduler.h
@@ -1,4 +1,4 @@
-/* * Copyright (c) 2013-2016, The Tor Project, Inc. */
+/* * Copyright (c) 2013-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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..e4ee64139a
--- /dev/null
+++ b/src/or/shared_random.c
@@ -0,0 +1,1451 @@
+/* Copyright (c) 2016-2017, 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) < 0) {
+ /* 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;
+ char b64_decoded[SR_COMMIT_LEN];
+
+ 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;
+ char b64_decoded[SR_REVEAL_LEN];
+
+ 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;
+}
+
+/* Convert a given srv object to a string for the control port. This doesn't
+ * fail and the srv object MUST be valid. */
+static char *
+srv_to_control_string(const sr_srv_t *srv)
+{
+ char *srv_str;
+ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+ tor_assert(srv);
+
+ sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv);
+ tor_asprintf(&srv_str, "%s", srv_hash_encoded);
+ return srv_str;
+}
+
+/* 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) < 0) {
+ 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) < 0) {
+ 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();
+}
+
+/* Return the current SRV string representation for the control port. Return a
+ * newly allocated string on success containing the value else "" if not found
+ * or if we don't have a valid consensus yet. */
+char *
+sr_get_current_for_control(void)
+{
+ char *srv_str;
+ const networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (c && c->sr_info.current_srv) {
+ srv_str = srv_to_control_string(c->sr_info.current_srv);
+ } else {
+ srv_str = tor_strdup("");
+ }
+ return srv_str;
+}
+
+/* Return the previous SRV string representation for the control port. Return
+ * a newly allocated string on success containing the value else "" if not
+ * found or if we don't have a valid consensus yet. */
+char *
+sr_get_previous_for_control(void)
+{
+ char *srv_str;
+ const networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (c && c->sr_info.previous_srv) {
+ srv_str = srv_to_control_string(c->sr_info.previous_srv);
+ } else {
+ srv_str = tor_strdup("");
+ }
+ return srv_str;
+}
+
+/* Return current shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_current(const networkstatus_t *ns)
+{
+ const networkstatus_t *consensus;
+
+ /* Use provided ns else get a live one */
+ if (ns) {
+ consensus = ns;
+ } else {
+ consensus = networkstatus_get_live_consensus(approx_time());
+ }
+ /* Ideally we would never be asked for an SRV without a live consensus. Make
+ * sure this assumption is correct. */
+ tor_assert_nonfatal(consensus);
+
+ if (consensus) {
+ return consensus->sr_info.current_srv;
+ }
+ return NULL;
+}
+
+/* Return previous shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_previous(const networkstatus_t *ns)
+{
+ const networkstatus_t *consensus;
+
+ /* Use provided ns else get a live one */
+ if (ns) {
+ consensus = ns;
+ } else {
+ consensus = networkstatus_get_live_consensus(approx_time());
+ }
+ /* Ideally we would never be asked for an SRV without a live consensus. Make
+ * sure this assumption is correct. */
+ tor_assert_nonfatal(consensus);
+
+ if (consensus) {
+ return consensus->sr_info.previous_srv;
+ }
+ return NULL;
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Set the global value of number of SRV agreements so the test can play
+ * 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..76d5b95422
--- /dev/null
+++ b/src/or/shared_random.h
@@ -0,0 +1,172 @@
+/* Copyright (c) 2016-2017, 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 (BASE64_LEN(SR_COMMIT_LEN))
+/* 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 (BASE64_LEN(SR_REVEAL_LEN))
+/* 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 (BASE64_LEN(DIGEST256_LEN))
+
+/* 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);
+
+char *sr_get_current_for_control(void);
+char *sr_get_previous_for_control(void);
+
+const sr_srv_t *sr_get_current(const networkstatus_t *ns);
+const sr_srv_t *sr_get_previous(const networkstatus_t *ns);
+
+#ifdef SHARED_RANDOM_PRIVATE
+
+/* Encode */
+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..d7ae05e895
--- /dev/null
+++ b/src/or/shared_random_state.c
@@ -0,0 +1,1395 @@
+/* Copyright (c) 2016-2017, 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, offsetof(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,
+ offsetof(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,
+ offsetof(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 start time of the current SR protocol run. For example, if the
+ * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this
+ * function should return 23/06/2017 00:00:00. */
+time_t
+sr_state_get_start_time_of_current_protocol_run(time_t now)
+{
+ int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ int voting_interval = get_voting_interval();
+ /* Find the time the current round started. */
+ time_t beginning_of_current_round = get_start_time_of_current_round(now);
+
+ /* Get current SR protocol round */
+ int current_round = (now / voting_interval) % total_rounds;
+
+ /* Get start time by subtracting the time elapsed from the beginning of the
+ protocol run */
+ time_t time_elapsed_since_start_of_run = current_round * voting_interval;
+ return beginning_of_current_round - time_elapsed_since_start_of_run;
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol phase
+ * (e.g. the commit phase). */
+unsigned int
+sr_state_get_phase_duration(void)
+{
+ return SHARED_RANDOM_N_ROUNDS * get_voting_interval();
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol run */
+unsigned int
+sr_state_get_protocol_run_duration(void)
+{
+ int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+ return total_protocol_rounds * get_voting_interval();
+}
+
+/* Return the time we should expire the state file created at <b>now</b>.
+ * 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..837fa75392
--- /dev/null
+++ b/src/or/shared_random_state.h
@@ -0,0 +1,152 @@
+/* Copyright (c) 2016-2017, 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);
+
+time_t sr_state_get_start_time_of_current_protocol_run(time_t now);
+unsigned int sr_state_get_phase_duration(void);
+unsigned int sr_state_get_protocol_run_duration(void);
+
+#ifdef SHARED_RANDOM_STATE_PRIVATE
+
+STATIC int disk_state_load_from_disk_impl(const char *fname);
+
+STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after);
+STATIC time_t get_start_time_of_current_round(time_t now);
+
+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..18111771da 100644
--- a/src/or/statefile.c
+++ b/src/or/statefile.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -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
@@ -38,7 +55,7 @@ static config_abbrev_t state_abbrevs_[] = {
/*XXXX these next two are duplicates or near-duplicates from config.c */
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -68,6 +85,8 @@ static config_var_t state_vars_[] = {
VAR("TransportProxy", LINELIST_S, TransportProxies, NULL),
V(TransportProxies, LINELIST_V, NULL),
+ V(HidServRevCounter, LINELIST, NULL),
+
V(BWHistoryReadEnds, ISOTIME, NULL),
V(BWHistoryReadInterval, UINT, "900"),
V(BWHistoryReadValues, CSV, ""),
@@ -85,6 +104,8 @@ static config_var_t state_vars_[] = {
V(BWHistoryDirWriteValues, CSV, ""),
V(BWHistoryDirWriteMaxima, CSV, ""),
+ V(Guard, LINELIST, NULL),
+
V(TorVersion, STRING, NULL),
V(LastRotatedOnionKey, ISOTIME, NULL),
@@ -112,15 +133,16 @@ static int or_state_validate_cb(void *old_options, void *options,
/** "Extra" variable in the state that receives lines we can't parse. This
* lets us preserve options from versions of Tor newer than us. */
static config_var_t state_extra_var = {
- "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL
+ "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL
};
/** Configuration format for or_state_t. */
static const config_format_t state_format = {
sizeof(or_state_t),
OR_STATE_MAGIC,
- STRUCT_OFFSET(or_state_t, magic_),
+ offsetof(or_state_t, magic_),
state_abbrevs_,
+ NULL,
state_vars_,
or_state_validate_cb,
&state_extra_var,
@@ -349,7 +371,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/statefile.h b/src/or/statefile.h
index b13743481d..10c09324bc 100644
--- a/src/or/statefile.h
+++ b/src/or/statefile.h
@@ -1,7 +1,7 @@
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_STATEFILE_H
diff --git a/src/or/status.c b/src/or/status.c
index 749cee4edf..f7be41e412 100644
--- a/src/or/status.c
+++ b/src/or/status.c
@@ -1,9 +1,15 @@
-/* Copyright (c) 2010-2016, The Tor Project, Inc. */
+/* Copyright (c) 2010-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \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/status.h b/src/or/status.h
index b97e835037..c1a0033ce0 100644
--- a/src/or/status.h
+++ b/src/or/status.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2016, The Tor Project, Inc. */
+/* Copyright (c) 2010-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_STATUS_H
diff --git a/src/or/tor_main.c b/src/or/tor_main.c
index ac32eef559..a3a8838602 100644
--- a/src/or/tor_main.c
+++ b/src/or/tor_main.c
@@ -1,8 +1,10 @@
/* Copyright 2001-2004 Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, 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/torcert.c b/src/or/torcert.c
index a6a33c675a..69b157446a 100644
--- a/src/or/torcert.c
+++ b/src/or/torcert.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -6,8 +6,27 @@
*
* \brief Implementation for ed25519-signed certificates as used in the Tor
* protocol.
+ *
+ * This certificate format is designed to be simple and compact; it's
+ * documented in tor-spec.txt in the torspec.git repository. All of the
+ * certificates in this format are signed with an Ed25519 key; the
+ * contents themselves may be another Ed25519 key, a digest of a
+ * RSA key, or some other material.
+ *
+ * In this module there is also support for a crooss-certification of
+ * Ed25519 identities using (older) RSA1024 identities.
+ *
+ * Tor uses other types of certificate too, beyond those described in this
+ * module. Notably, our use of TLS requires us to touch X.509 certificates,
+ * even though sensible people would stay away from those. Our X.509
+ * certificates are represented with tor_x509_cert_t, and implemented in
+ * tortls.c. We also have a separate certificate type that authorities
+ * use to authenticate their RSA signing keys with their RSA identity keys:
+ * that one is authority_cert_t, and it's mostly handled in routerlist.c.
*/
+#include "or.h"
+#include "config.h"
#include "crypto.h"
#include "torcert.h"
#include "ed25519_cert.h"
@@ -137,7 +156,12 @@ tor_cert_parse(const uint8_t *encoded, const size_t len)
cert->encoded_len = len;
memcpy(cert->signed_key.pubkey, parsed->certified_key, 32);
- cert->valid_until = parsed->exp_field * 3600;
+ int64_t valid_until_64 = ((int64_t)parsed->exp_field) * 3600;
+#if SIZEOF_TIME_T < SIZEOF_INT64_T
+ if (valid_until_64 > TIME_MAX)
+ valid_until_64 = TIME_MAX - 1;
+#endif
+ cert->valid_until = (time_t) valid_until_64;
cert->cert_type = parsed->cert_type;
for (unsigned i = 0; i < ed25519_cert_getlen_ext(parsed); ++i) {
@@ -164,11 +188,17 @@ tor_cert_parse(const uint8_t *encoded, const size_t len)
}
/** Fill in <b>checkable_out</b> with the information needed to check
- * the signature on <b>cert</b> with <b>pubkey</b>. */
+ * the signature on <b>cert</b> with <b>pubkey</b>.
+ *
+ * On success, if <b>expiration_out</b> is provided, and it is some time
+ * _after_ the expiration time of this certificate, set it to the
+ * expiration time of this certificate.
+ */
int
tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
const tor_cert_t *cert,
- const ed25519_public_key_t *pubkey)
+ const ed25519_public_key_t *pubkey,
+ time_t *expiration_out)
{
if (! pubkey) {
if (cert->signing_key_included)
@@ -185,6 +215,10 @@ tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
memcpy(checkable_out->signature.sig,
cert->encoded + signed_len, ED25519_SIG_LEN);
+ if (expiration_out) {
+ *expiration_out = MIN(*expiration_out, cert->valid_until);
+ }
+
return 0;
}
@@ -199,14 +233,15 @@ tor_cert_checksig(tor_cert_t *cert,
{
ed25519_checkable_t checkable;
int okay;
+ time_t expires = TIME_MAX;
- if (now && now > cert->valid_until) {
- cert->cert_expired = 1;
+ if (tor_cert_get_checkable_sig(&checkable, cert, pubkey, &expires) < 0)
return -1;
- }
- if (tor_cert_get_checkable_sig(&checkable, cert, pubkey) < 0)
+ if (now && now > expires) {
+ cert->cert_expired = 1;
return -1;
+ }
if (ed25519_checksig_batch(&okay, &checkable, 1) < 0) {
cert->sig_bad = 1;
@@ -255,6 +290,8 @@ tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2)
return tor_cert_eq(cert1, cert2);
}
+#define RSA_ED_CROSSCERT_PREFIX "Tor TLS RSA/Ed25519 cross-certificate"
+
/** Create new cross-certification object to certify <b>ed_key</b> as the
* master ed25519 identity key for the RSA identity key <b>rsa_key</b>.
* Allocates and stores the encoded certificate in *<b>cert</b>, and returns
@@ -265,6 +302,10 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
time_t expires,
uint8_t **cert)
{
+ // It is later than 1985, since otherwise there would be no C89
+ // compilers. (Try to diagnose #22466.)
+ tor_assert_nonfatal(expires >= 15 * 365 * 86400);
+
uint8_t *res;
rsa_ed_crosscert_t *cc = rsa_ed_crosscert_new();
@@ -279,11 +320,21 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
ssize_t sz = rsa_ed_crosscert_encode(res, alloc_sz, cc);
tor_assert(sz > 0 && sz <= alloc_sz);
+ crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+ crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX,
+ strlen(RSA_ED_CROSSCERT_PREFIX));
+
const int signed_part_len = 32 + 4;
+ crypto_digest_add_bytes(d, (char*)res, signed_part_len);
+
+ uint8_t digest[DIGEST256_LEN];
+ crypto_digest_get_digest(d, (char*)digest, sizeof(digest));
+ crypto_digest_free(d);
+
int siglen = crypto_pk_private_sign(rsa_key,
(char*)rsa_ed_crosscert_getarray_sig(cc),
rsa_ed_crosscert_getlen_sig(cc),
- (char*)res, signed_part_len);
+ (char*)digest, sizeof(digest));
tor_assert(siglen > 0 && siglen <= (int)crypto_pk_keysize(rsa_key));
tor_assert(siglen <= UINT8_MAX);
cc->sig_len = siglen;
@@ -295,3 +346,350 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
return sz;
}
+/**
+ * Check whether the <b>crosscert_len</b> byte certificate in <b>crosscert</b>
+ * is in fact a correct cross-certification of <b>master_key</b> using
+ * the RSA key <b>rsa_id_key</b>.
+ *
+ * Also reject the certificate if it expired before
+ * <b>reject_if_expired_before</b>.
+ *
+ * Return 0 on success, negative on failure.
+ */
+int
+rsa_ed25519_crosscert_check(const uint8_t *crosscert,
+ const size_t crosscert_len,
+ const crypto_pk_t *rsa_id_key,
+ const ed25519_public_key_t *master_key,
+ const time_t reject_if_expired_before)
+{
+ rsa_ed_crosscert_t *cc = NULL;
+ int rv;
+
+#define ERR(code, s) \
+ do { \
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \
+ "Received a bad RSA->Ed25519 crosscert: %s", \
+ (s)); \
+ rv = (code); \
+ goto err; \
+ } while (0)
+
+ if (BUG(crypto_pk_keysize(rsa_id_key) > PK_BYTES))
+ return -1;
+
+ if (BUG(!crosscert))
+ return -1;
+
+ ssize_t parsed_len = rsa_ed_crosscert_parse(&cc, crosscert, crosscert_len);
+ if (parsed_len < 0 || crosscert_len != (size_t)parsed_len) {
+ ERR(-2, "Unparseable or overlong crosscert");
+ }
+
+ if (tor_memneq(rsa_ed_crosscert_getarray_ed_key(cc),
+ master_key->pubkey,
+ ED25519_PUBKEY_LEN)) {
+ ERR(-3, "Crosscert did not match Ed25519 key");
+ }
+
+ const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc);
+ const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600;
+
+ if (reject_if_expired_before < 0 ||
+ expiration_time < (uint64_t)reject_if_expired_before) {
+ ERR(-4, "Crosscert is expired");
+ }
+
+ const uint8_t *eos = rsa_ed_crosscert_get_end_of_signed(cc);
+ const uint8_t *sig = rsa_ed_crosscert_getarray_sig(cc);
+ const uint8_t siglen = rsa_ed_crosscert_get_sig_len(cc);
+ tor_assert(eos >= crosscert);
+ tor_assert((size_t)(eos - crosscert) <= crosscert_len);
+ tor_assert(siglen == rsa_ed_crosscert_getlen_sig(cc));
+
+ /* Compute the digest */
+ uint8_t digest[DIGEST256_LEN];
+ crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+ crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX,
+ strlen(RSA_ED_CROSSCERT_PREFIX));
+ crypto_digest_add_bytes(d, (char*)crosscert, eos-crosscert);
+ crypto_digest_get_digest(d, (char*)digest, sizeof(digest));
+ crypto_digest_free(d);
+
+ /* Now check the signature */
+ uint8_t signed_[PK_BYTES];
+ int signed_len = crypto_pk_public_checksig(rsa_id_key,
+ (char*)signed_, sizeof(signed_),
+ (char*)sig, siglen);
+ if (signed_len < DIGEST256_LEN) {
+ ERR(-5, "Bad signature, or length of signed data not as expected");
+ }
+
+ if (tor_memneq(digest, signed_, DIGEST256_LEN)) {
+ ERR(-6, "The signature was good, but it didn't match the data");
+ }
+
+ rv = 0;
+ err:
+ rsa_ed_crosscert_free(cc);
+ return rv;
+}
+
+/** Construct and return a new empty or_handshake_certs object */
+or_handshake_certs_t *
+or_handshake_certs_new(void)
+{
+ return tor_malloc_zero(sizeof(or_handshake_certs_t));
+}
+
+/** Release all storage held in <b>certs</b> */
+void
+or_handshake_certs_free(or_handshake_certs_t *certs)
+{
+ if (!certs)
+ return;
+
+ tor_x509_cert_free(certs->auth_cert);
+ tor_x509_cert_free(certs->link_cert);
+ tor_x509_cert_free(certs->id_cert);
+
+ tor_cert_free(certs->ed_id_sign);
+ tor_cert_free(certs->ed_sign_link);
+ tor_cert_free(certs->ed_sign_auth);
+ tor_free(certs->ed_rsa_crosscert);
+
+ memwipe(certs, 0xBD, sizeof(*certs));
+ tor_free(certs);
+}
+
+#undef ERR
+#define ERR(s) \
+ do { \
+ log_fn(severity, LD_PROTOCOL, \
+ "Received a bad CERTS cell: %s", \
+ (s)); \
+ return 0; \
+ } while (0)
+
+int
+or_handshake_certs_rsa_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now)
+{
+ tor_x509_cert_t *link_cert = certs->link_cert;
+ tor_x509_cert_t *auth_cert = certs->auth_cert;
+ tor_x509_cert_t *id_cert = certs->id_cert;
+
+ if (certs->started_here) {
+ if (! (id_cert && link_cert))
+ ERR("The certs we wanted (ID, Link) were missing");
+ if (! tor_tls_cert_matches_key(tls, link_cert))
+ ERR("The link certificate didn't match the TLS public key");
+ if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, now, 0))
+ ERR("The link certificate was not valid");
+ if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, now, 1))
+ ERR("The ID certificate was not valid");
+ } else {
+ if (! (id_cert && auth_cert))
+ ERR("The certs we wanted (ID, Auth) were missing");
+ if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, now, 1))
+ ERR("The authentication certificate was not valid");
+ if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, now, 1))
+ ERR("The ID certificate was not valid");
+ }
+
+ return 1;
+}
+
+/** Check all the ed25519 certificates in <b>certs</b> against each other, and
+ * against the peer certificate in <b>tls</b> if appropriate. On success,
+ * return 0; on failure, return a negative value and warn at level
+ * <b>severity</b> */
+int
+or_handshake_certs_ed25519_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now)
+{
+ ed25519_checkable_t check[10];
+ unsigned n_checkable = 0;
+ time_t expiration = TIME_MAX;
+
+#define ADDCERT(cert, pk) \
+ do { \
+ tor_assert(n_checkable < ARRAY_LENGTH(check)); \
+ if (tor_cert_get_checkable_sig(&check[n_checkable++], cert, pk, \
+ &expiration) < 0) \
+ ERR("Could not get checkable cert."); \
+ } while (0)
+
+ if (! certs->ed_id_sign || !certs->ed_id_sign->signing_key_included) {
+ ERR("No Ed25519 signing key");
+ }
+ ADDCERT(certs->ed_id_sign, NULL);
+
+ if (certs->started_here) {
+ if (! certs->ed_sign_link)
+ ERR("No Ed25519 link key");
+ {
+ /* check for a match with the TLS cert. */
+ tor_x509_cert_t *peer_cert = tor_tls_get_peer_cert(tls);
+ if (BUG(!peer_cert)) {
+ /* This is a bug, because if we got to this point, we are a connection
+ * that was initiated here, and we completed a TLS handshake. The
+ * other side *must* have given us a certificate! */
+ ERR("No x509 peer cert"); // LCOV_EXCL_LINE
+ }
+ const common_digests_t *peer_cert_digests =
+ tor_x509_cert_get_cert_digests(peer_cert);
+ int okay = tor_memeq(peer_cert_digests->d[DIGEST_SHA256],
+ certs->ed_sign_link->signed_key.pubkey,
+ DIGEST256_LEN);
+ tor_x509_cert_free(peer_cert);
+ if (!okay)
+ ERR("Link certificate does not match TLS certificate");
+ }
+
+ ADDCERT(certs->ed_sign_link, &certs->ed_id_sign->signed_key);
+
+ } else {
+ if (! certs->ed_sign_auth)
+ ERR("No Ed25519 link authentication key");
+ ADDCERT(certs->ed_sign_auth, &certs->ed_id_sign->signed_key);
+ }
+
+ if (expiration < now) {
+ ERR("At least one certificate expired.");
+ }
+
+ /* Okay, we've gotten ready to check all the Ed25519 certificates.
+ * Now, we are going to check the RSA certificate's cross-certification
+ * with the ED certificates.
+ *
+ * FFFF In the future, we might want to make this optional.
+ */
+
+ tor_x509_cert_t *rsa_id_cert = certs->id_cert;
+ if (!rsa_id_cert) {
+ ERR("Missing legacy RSA ID certificate");
+ }
+ if (! tor_tls_cert_is_valid(severity, rsa_id_cert, rsa_id_cert, now, 1)) {
+ ERR("The legacy RSA ID certificate was not valid");
+ }
+ if (! certs->ed_rsa_crosscert) {
+ ERR("Missing RSA->Ed25519 crosscert");
+ }
+ crypto_pk_t *rsa_id_key = tor_tls_cert_get_key(rsa_id_cert);
+ if (!rsa_id_key) {
+ ERR("RSA ID cert had no RSA key");
+ }
+
+ if (rsa_ed25519_crosscert_check(certs->ed_rsa_crosscert,
+ certs->ed_rsa_crosscert_len,
+ rsa_id_key,
+ &certs->ed_id_sign->signing_key,
+ now) < 0) {
+ crypto_pk_free(rsa_id_key);
+ ERR("Invalid RSA->Ed25519 crosscert");
+ }
+ crypto_pk_free(rsa_id_key);
+ rsa_id_key = NULL;
+
+ /* FFFF We could save a little time in the client case by queueing
+ * this batch to check it later, along with the signature from the
+ * AUTHENTICATE cell. That will change our data flow a bit, though,
+ * so I say "postpone". */
+
+ if (ed25519_checksig_batch(NULL, check, n_checkable) < 0) {
+ ERR("At least one Ed25519 certificate was badly signed");
+ }
+
+ return 1;
+}
+
+/**
+ * Check the Ed certificates and/or the RSA certificates, as appropriate. If
+ * we obtained an Ed25519 identity, set *ed_id_out. If we obtained an RSA
+ * identity, set *rs_id_out. Otherwise, set them both to NULL.
+ */
+void
+or_handshake_certs_check_both(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now,
+ const ed25519_public_key_t **ed_id_out,
+ const common_digests_t **rsa_id_out)
+{
+ tor_assert(ed_id_out);
+ tor_assert(rsa_id_out);
+
+ *ed_id_out = NULL;
+ *rsa_id_out = NULL;
+
+ if (certs->ed_id_sign) {
+ if (or_handshake_certs_ed25519_ok(severity, certs, tls, now)) {
+ tor_assert(certs->ed_id_sign);
+ tor_assert(certs->id_cert);
+
+ *ed_id_out = &certs->ed_id_sign->signing_key;
+ *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert);
+
+ /* If we reached this point, we did not look at any of the
+ * subsidiary RSA certificates, so we'd better just remove them.
+ */
+ tor_x509_cert_free(certs->link_cert);
+ tor_x509_cert_free(certs->auth_cert);
+ certs->link_cert = certs->auth_cert = NULL;
+ }
+ /* We do _not_ fall through here. If you provided us Ed25519
+ * certificates, we expect to verify them! */
+ } else {
+ /* No ed25519 keys given in the CERTS cell */
+ if (or_handshake_certs_rsa_ok(severity, certs, tls, now)) {
+ *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert);
+ }
+ }
+}
+
+/* === ENCODING === */
+
+/* Encode the ed25519 certificate <b>cert</b> and put the newly allocated
+ * string in <b>cert_str_out</b>. Return 0 on success else a negative value. */
+int
+tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out)
+{
+ int ret = -1;
+ char *ed_cert_b64 = NULL;
+ size_t ed_cert_b64_len;
+
+ tor_assert(cert);
+ tor_assert(cert_str_out);
+
+ /* Get the encoded size and add the NUL byte. */
+ ed_cert_b64_len = base64_encode_size(cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) + 1;
+ ed_cert_b64 = tor_malloc_zero(ed_cert_b64_len);
+
+ /* Base64 encode the encoded certificate. */
+ if (base64_encode(ed_cert_b64, ed_cert_b64_len,
+ (const char *) cert->encoded, cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
+ log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!");
+ goto err;
+ }
+
+ /* Put everything together in a NUL terminated string. */
+ tor_asprintf(cert_str_out,
+ "-----BEGIN ED25519 CERT-----\n"
+ "%s"
+ "-----END ED25519 CERT-----",
+ ed_cert_b64);
+ /* Success! */
+ ret = 0;
+
+ err:
+ tor_free(ed_cert_b64);
+ return ret;
+}
+
diff --git a/src/or/torcert.h b/src/or/torcert.h
index 9c819c0abb..51f7665f1e 100644
--- a/src/or/torcert.h
+++ b/src/or/torcert.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Tor Project, Inc. */
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TORCERT_H_INCLUDED
@@ -6,12 +6,15 @@
#include "crypto_ed25519.h"
-#define SIGNED_KEY_TYPE_ED25519 0x01
+#define SIGNED_KEY_TYPE_ED25519 0x01
-#define CERT_TYPE_ID_SIGNING 0x04
-#define CERT_TYPE_SIGNING_LINK 0x05
-#define CERT_TYPE_SIGNING_AUTH 0x06
-#define CERT_TYPE_ONION_ID 0x0A
+#define CERT_TYPE_ID_SIGNING 0x04
+#define CERT_TYPE_SIGNING_LINK 0x05
+#define CERT_TYPE_SIGNING_AUTH 0x06
+#define CERT_TYPE_SIGNING_HS_DESC 0x08
+#define CERT_TYPE_AUTH_HS_IP_KEY 0x09
+#define CERT_TYPE_ONION_ID 0x0A
+#define CERT_TYPE_CROSS_HS_IP_KEYS 0x0B
#define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1
@@ -57,8 +60,9 @@ tor_cert_t *tor_cert_parse(const uint8_t *cert, size_t certlen);
void tor_cert_free(tor_cert_t *cert);
int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
- const tor_cert_t *out,
- const ed25519_public_key_t *pubkey);
+ const tor_cert_t *out,
+ const ed25519_public_key_t *pubkey,
+ time_t *expiration_out);
int tor_cert_checksig(tor_cert_t *cert,
const ed25519_public_key_t *pubkey, time_t now);
@@ -71,6 +75,30 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
const crypto_pk_t *rsa_key,
time_t expires,
uint8_t **cert);
+int rsa_ed25519_crosscert_check(const uint8_t *crosscert,
+ const size_t crosscert_len,
+ const crypto_pk_t *rsa_id_key,
+ const ed25519_public_key_t *master_key,
+ const time_t reject_if_expired_before);
+
+or_handshake_certs_t *or_handshake_certs_new(void);
+void or_handshake_certs_free(or_handshake_certs_t *certs);
+int or_handshake_certs_rsa_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now);
+int or_handshake_certs_ed25519_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now);
+void or_handshake_certs_check_both(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now,
+ const ed25519_public_key_t **ed_id_out,
+ const common_digests_t **rsa_id_out);
+
+int tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out);
#endif
diff --git a/src/or/transports.c b/src/or/transports.c
index 1b8b1e678c..31849a8d15 100644
--- a/src/or/transports.c
+++ b/src/or/transports.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2016, The Tor Project, Inc. */
+/* Copyright (c) 2011-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -91,13 +91,13 @@
#define PT_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "config.h"
#include "circuitbuild.h"
#include "transports.h"
#include "util.h"
#include "router.h"
#include "statefile.h"
-#include "entrynodes.h"
#include "connection_or.h"
#include "ext_orport.h"
#include "control.h"
@@ -430,7 +430,7 @@ add_transport_to_proxy(const char *transport, managed_proxy_t *mp)
{
tor_assert(mp->transports_to_launch);
if (!smartlist_contains_string(mp->transports_to_launch, transport))
- smartlist_add(mp->transports_to_launch, tor_strdup(transport));
+ smartlist_add_strdup(mp->transports_to_launch, transport);
}
/** Called when a SIGHUP occurs. Returns true if managed proxy
@@ -480,7 +480,6 @@ proxy_needs_restart(const managed_proxy_t *mp)
* preparations and then flag its state so that it will be relaunched
* in the next tick. */
static void
-
proxy_prepare_for_restart(managed_proxy_t *mp)
{
transport_t *t_tmp = NULL;
@@ -1270,7 +1269,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)
@@ -1322,7 +1321,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
tor_free(state_tmp);
}
- smartlist_add(envs, tor_strdup("TOR_PT_MANAGED_TRANSPORT_VER=1"));
+ smartlist_add_strdup(envs, "TOR_PT_MANAGED_TRANSPORT_VER=1");
{
char *transports_to_launch =
@@ -1363,7 +1362,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 +1424,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 +1435,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 +1459,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 +1472,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 +1489,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 +1610,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);
diff --git a/src/or/transports.h b/src/or/transports.h
index 7de90dcbec..44a9626e50 100644
--- a/src/or/transports.h
+++ b/src/or/transports.h
@@ -1,6 +1,6 @@
/* Copyright (c) 2003-2004, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2016, The Tor Project, Inc. */
+ * Copyright (c) 2007-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**