diff options
Diffstat (limited to 'src/or')
181 files changed, 45026 insertions, 13392 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 33fd7e0f4a..7e92633601 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 */ /** @@ -213,8 +213,8 @@ addressmap_clear_excluded_trackexithosts(const or_options_t *options) while (dot > target && *dot != '.') dot--; if (*dot == '.') dot++; - nodename = tor_strndup(dot, len-5-(dot-target));; - node = node_get_by_nickname(nodename, 0); + nodename = tor_strndup(dot, len-5-(dot-target)); + node = node_get_by_nickname(nodename, NNF_NO_WARN_UNNAMED); tor_free(nodename); if (!node || (allow_nodes && !routerset_contains_node(allow_nodes, node)) || @@ -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) { @@ -802,7 +814,7 @@ parse_virtual_addr_network(const char *val, sa_family_t family, ipv6?"IPv6":""); return -1; } -#endif +#endif /* 0 */ if (bits > max_prefix_bits) { if (msg) @@ -1032,7 +1044,7 @@ addressmap_register_virtual_address(int type, char *new_address) safe_str_client(*addrp), safe_str_client(new_address)); } -#endif +#endif /* 0 */ return *addrp; } diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 67648d0518..1544b76e10 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 @@ -59,7 +59,7 @@ typedef struct virtual_addr_conf_t { STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out); -#endif +#endif /* defined(ADDRESSMAP_PRIVATE) */ -#endif +#endif /* !defined(TOR_ADDRESSMAP_H) */ diff --git a/src/or/bridges.c b/src/or/bridges.c new file mode 100644 index 0000000000..0b1bbbd158 --- /dev/null +++ b/src/or/bridges.c @@ -0,0 +1,888 @@ +/* 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); +static void rewrite_node_address_for_bridge(const bridge_info_t *bridge, + node_t *node); + +/** A list of configured bridges. Whenever we actually get a descriptor + * for one, we add it as an entry guard. Note that the order of bridges + * 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 /* 0 */ + 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->fetch_status.increment_on = DL_SCHED_INCREMENT_ATTEMPT; + /* We can't reset the bridge's download status here, because UseBridges + * might be 0 now, and it might be changed to 1 much later. */ + b->socks_args = bridge_line->socks_args; + if (!bridge_list) + bridge_list = smartlist_new(); + + 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; + } + + /* If we already have a node_t for this bridge, rewrite its address now. */ + node_t *node = node_get_mutable_by_id(bridge->identity); + if (node) { + rewrite_node_address_for_bridge(bridge, node); + } + + tor_addr_port_t bridge_addrport; + memcpy(&bridge_addrport.addr, &bridge->addr, sizeof(tor_addr_t)); + bridge_addrport.port = bridge->port; + + 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) + { + /* This resets the download status on first use */ + if (!download_status_is_ready(&bridge->fetch_status, now, + IMPOSSIBLE_TO_DOWNLOAD)) + continue; /* don't bother, no need to retry yet */ + 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 the next attempt + * we can't increment after a failure, because sometimes we use the + * bridge authority, and sometimes we use the bridge direct */ + download_status_increment_attempt( + &bridge->fetch_status, + safe_str_client(fmt_and_decorate_addr(&bridge->addr)), + now); + + can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && + num_bridge_auths; + 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) { + /* Retry directory downloads whenever we get a bridge descriptor: + * - when bootstrapping, and + * - when we aren't sure if any of our bridges are reachable. + * Keep on retrying until we have at least one reachable bridge. */ + int first = num_bridges_usable(0) < 1; + bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); + time_t now = time(NULL); + router_set_status(ri->cache_info.identity_digest, 1); + + 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) { + /* This schedules the re-fetch at a constant interval, which produces + * a pattern of bridge traffic. But it's better than trying all + * configured briges several times in the first few minutes. */ + download_status_reset(&bridge->fetch_status); + } + + node = node_get_mutable_by_id(ri->cache_info.identity_digest); + tor_assert(node); + 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)); + /* If we didn't have a reachable bridge before this one, try directory + * documents again. */ + if (first) { + routerlist_retry_directory_downloads(now); + } + } + } +} + +/** 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..54a6250259 --- /dev/null +++ b/src/or/bridges.h @@ -0,0 +1,69 @@ +/* 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); +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 /* !defined(TOR_BRIDGES_H) */ + diff --git a/src/or/buffers.c b/src/or/buffers.c deleted file mode 100644 index 89382d1d8e..0000000000 --- a/src/or/buffers.c +++ /dev/null @@ -1,2063 +0,0 @@ -/* 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. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.c - * \brief Implements a generic buffer interface. - * - * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush - * from memory, sockets, file descriptors, TLS connections, or another buf_t. - * Buffers are implemented as linked lists of memory chunks. - * - * All socket-backed and TLS-based connection_t objects have a pair of - * buffers: one for incoming data, and one for outcoming data. These are fed - * and drained from functions in connection.c, trigged by events that are - * monitored in main.c. - * - * This module has basic support for reading and writing on buf_t objects. It - * also contains specialized functions for handling particular protocols - * on a buf_t backend, including SOCKS (used in connection_edge.c), Tor cells - * (used in connection_or.c and channeltls.c), HTTP (used in directory.c), and - * line-oriented communication (used in control.c). - **/ -#define BUFFERS_PRIVATE -#include "or.h" -#include "addressmap.h" -#include "buffers.h" -#include "config.h" -#include "connection_edge.h" -#include "connection_or.h" -#include "control.h" -#include "reasons.h" -#include "ext_orport.h" -#include "util.h" -#include "torlog.h" -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -//#define PARANOIA - -#ifdef PARANOIA -/** Helper: If PARANOIA is defined, assert that the buffer in local variable - * <b>buf</b> is well-formed. */ -#define check() STMT_BEGIN assert_buf_ok(buf); STMT_END -#else -#define check() STMT_NIL -#endif - -/* Implementation notes: - * - * After flirting with memmove, and dallying with ring-buffers, we're finally - * getting up to speed with the 1970s and implementing buffers as a linked - * list of small chunks. Each buffer has such a list; data is removed from - * the head of the list, and added at the tail. The list is singly linked, - * and the buffer keeps a pointer to the head and the tail. - * - * Every chunk, except the tail, contains at least one byte of data. Data in - * each chunk is contiguous. - * - * When you need to treat the first N characters on a buffer as a contiguous - * string, use the buf_pullup function to make them so. Don't do this more - * than necessary. - * - * The major free Unix kernels have handled buffers like this since, like, - * forever. - */ - -static void socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason); - -static int parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out); -static int parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out); - -/* Chunk manipulation functions */ - -#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) - -/* We leave this many NUL bytes at the end of the buffer. */ -#define SENTINEL_LEN 4 - -/* Header size plus NUL bytes at the end */ -#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) - -/** Return the number of bytes needed to allocate a chunk to hold - * <b>memlen</b> bytes. */ -#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) -/** Return the number of usable bytes in a chunk allocated with - * malloc(<b>memlen</b>). */ -#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) - -#define DEBUG_SENTINEL - -#ifdef DEBUG_SENTINEL -#define DBG_S(s) s -#else -#define DBG_S(s) (void)0 -#endif - -#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) - -/** Return the next character in <b>chunk</b> onto which data can be appended. - * If the chunk is full, this might be off the end of chunk->mem. */ -static inline char * -CHUNK_WRITE_PTR(chunk_t *chunk) -{ - return chunk->data + chunk->datalen; -} - -/** Return the number of bytes that can be written onto <b>chunk</b> without - * running out of space. */ -static inline size_t -CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) -{ - return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); -} - -/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, - * to free up space at the end. */ -static inline void -chunk_repack(chunk_t *chunk) -{ - if (chunk->datalen && chunk->data != &chunk->mem[0]) { - memmove(chunk->mem, chunk->data, chunk->datalen); - } - chunk->data = &chunk->mem[0]; -} - -/** Keep track of total size of allocated chunks for consistency asserts */ -static size_t total_bytes_allocated_in_chunks = 0; -static void -buf_chunk_free_unchecked(chunk_t *chunk) -{ - if (!chunk) - return; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); -#endif - tor_assert(total_bytes_allocated_in_chunks >= - CHUNK_ALLOC_SIZE(chunk->memlen)); - total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); - tor_free(chunk); -} -static inline chunk_t * -chunk_new_with_alloc_size(size_t alloc) -{ - chunk_t *ch; - ch = tor_malloc(alloc); - ch->next = NULL; - ch->datalen = 0; -#ifdef DEBUG_CHUNK_ALLOC - ch->DBG_alloc = alloc; -#endif - ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); - total_bytes_allocated_in_chunks += alloc; - ch->data = &ch->mem[0]; - CHUNK_SET_SENTINEL(ch, alloc); - return ch; -} - -/** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a - * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ -static inline chunk_t * -chunk_grow(chunk_t *chunk, size_t sz) -{ - off_t offset; - const size_t memlen_orig = chunk->memlen; - const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); - const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); - tor_assert(sz > chunk->memlen); - offset = chunk->data - chunk->mem; - chunk = tor_realloc(chunk, new_alloc); - chunk->memlen = sz; - chunk->data = chunk->mem + offset; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(chunk->DBG_alloc == orig_alloc); - chunk->DBG_alloc = new_alloc; -#endif - total_bytes_allocated_in_chunks += new_alloc - orig_alloc; - CHUNK_SET_SENTINEL(chunk, new_alloc); - return chunk; -} - -/** If a read onto the end of a chunk would be smaller than this number, then - * just start a new chunk. */ -#define MIN_READ_LEN 8 -/** Every chunk should take up at least this many bytes. */ -#define MIN_CHUNK_ALLOC 256 -/** No chunk should take up more than this many bytes. */ -#define MAX_CHUNK_ALLOC 65536 - -/** Return the allocation size we'd like to use to hold <b>target</b> - * bytes. */ -STATIC size_t -preferred_chunk_size(size_t target) -{ - tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); - if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) - return CHUNK_ALLOC_SIZE(target); - size_t sz = MIN_CHUNK_ALLOC; - while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { - sz <<= 1; - } - return sz; -} - -/** Collapse data from the first N chunks from <b>buf</b> into buf->head, - * growing it as necessary, until buf->head has the first <b>bytes</b> bytes - * of data from the buffer, or until buf->head has all the data in <b>buf</b>. - */ -STATIC void -buf_pullup(buf_t *buf, size_t bytes) -{ - chunk_t *dest, *src; - size_t capacity; - if (!buf->head) - return; - - check(); - if (buf->datalen < bytes) - bytes = buf->datalen; - - capacity = bytes; - if (buf->head->datalen >= bytes) - return; - - if (buf->head->memlen >= capacity) { - /* We don't need to grow the first chunk, but we might need to repack it.*/ - size_t needed = capacity - buf->head->datalen; - if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) - chunk_repack(buf->head); - tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); - } else { - chunk_t *newhead; - size_t newsize; - /* We need to grow the chunk. */ - chunk_repack(buf->head); - newsize = CHUNK_SIZE_WITH_ALLOC(preferred_chunk_size(capacity)); - newhead = chunk_grow(buf->head, newsize); - tor_assert(newhead->memlen >= capacity); - if (newhead != buf->head) { - if (buf->tail == buf->head) - buf->tail = newhead; - buf->head = newhead; - } - } - - dest = buf->head; - while (dest->datalen < bytes) { - size_t n = bytes - dest->datalen; - src = dest->next; - tor_assert(src); - if (n >= src->datalen) { - memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); - dest->datalen += src->datalen; - dest->next = src->next; - if (buf->tail == src) - buf->tail = dest; - buf_chunk_free_unchecked(src); - } else { - memcpy(CHUNK_WRITE_PTR(dest), src->data, n); - dest->datalen += n; - src->data += n; - src->datalen -= n; - tor_assert(dest->datalen == bytes); - } - } - - check(); -} - -#ifdef TOR_UNIT_TESTS -void -buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) -{ - if (!buf || !buf->head) { - *cp = NULL; - *sz = 0; - } else { - *cp = buf->head->data; - *sz = buf->head->datalen; - } -} -#endif - -/** Remove the first <b>n</b> bytes from buf. */ -static inline void -buf_remove_from_front(buf_t *buf, size_t n) -{ - tor_assert(buf->datalen >= n); - while (n) { - tor_assert(buf->head); - if (buf->head->datalen > n) { - buf->head->datalen -= n; - buf->head->data += n; - buf->datalen -= n; - return; - } else { - chunk_t *victim = buf->head; - n -= victim->datalen; - buf->datalen -= victim->datalen; - buf->head = victim->next; - if (buf->tail == victim) - buf->tail = NULL; - buf_chunk_free_unchecked(victim); - } - } - check(); -} - -/** Create and return a new buf with default chunk capacity <b>size</b>. - */ -buf_t * -buf_new_with_capacity(size_t size) -{ - buf_t *b = buf_new(); - b->default_chunk_size = preferred_chunk_size(size); - return b; -} - -/** Allocate and return a new buffer with default capacity. */ -buf_t * -buf_new(void) -{ - buf_t *buf = tor_malloc_zero(sizeof(buf_t)); - buf->magic = BUFFER_MAGIC; - buf->default_chunk_size = 4096; - return buf; -} - -size_t -buf_get_default_chunk_size(const buf_t *buf) -{ - return buf->default_chunk_size; -} - -/** Remove all data from <b>buf</b>. */ -void -buf_clear(buf_t *buf) -{ - chunk_t *chunk, *next; - buf->datalen = 0; - for (chunk = buf->head; chunk; chunk = next) { - next = chunk->next; - buf_chunk_free_unchecked(chunk); - } - buf->head = buf->tail = NULL; -} - -/** Return the number of bytes stored in <b>buf</b> */ -MOCK_IMPL(size_t, -buf_datalen, (const buf_t *buf)) -{ - return buf->datalen; -} - -/** Return the total length of all chunks used in <b>buf</b>. */ -size_t -buf_allocation(const buf_t *buf) -{ - size_t total = 0; - const chunk_t *chunk; - for (chunk = buf->head; chunk; chunk = chunk->next) { - total += CHUNK_ALLOC_SIZE(chunk->memlen); - } - return total; -} - -/** Return the number of bytes that can be added to <b>buf</b> without - * performing any additional allocation. */ -size_t -buf_slack(const buf_t *buf) -{ - if (!buf->tail) - return 0; - else - return CHUNK_REMAINING_CAPACITY(buf->tail); -} - -/** Release storage held by <b>buf</b>. */ -void -buf_free(buf_t *buf) -{ - if (!buf) - return; - - buf_clear(buf); - buf->magic = 0xdeadbeef; - tor_free(buf); -} - -/** Return a new copy of <b>in_chunk</b> */ -static chunk_t * -chunk_copy(const chunk_t *in_chunk) -{ - chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); - total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); -#ifdef DEBUG_CHUNK_ALLOC - newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); -#endif - newch->next = NULL; - if (in_chunk->data) { - off_t offset = in_chunk->data - in_chunk->mem; - newch->data = newch->mem + offset; - } - return newch; -} - -/** Return a new copy of <b>buf</b> */ -buf_t * -buf_copy(const buf_t *buf) -{ - chunk_t *ch; - buf_t *out = buf_new(); - out->default_chunk_size = buf->default_chunk_size; - for (ch = buf->head; ch; ch = ch->next) { - chunk_t *newch = chunk_copy(ch); - if (out->tail) { - out->tail->next = newch; - out->tail = newch; - } else { - out->head = out->tail = newch; - } - } - out->datalen = buf->datalen; - return out; -} - -/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to - * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger - * than MAX_CHUNK_ALLOC. */ -static chunk_t * -buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) -{ - chunk_t *chunk; - - if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { - chunk = chunk_new_with_alloc_size(buf->default_chunk_size); - } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { - chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); - } else { - chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity)); - } - - chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec(); - - if (buf->tail) { - tor_assert(buf->head); - buf->tail->next = chunk; - buf->tail = chunk; - } else { - tor_assert(!buf->head); - buf->head = buf->tail = chunk; - } - check(); - return chunk; -} - -/** Return the age of the oldest chunk in the buffer <b>buf</b>, in - * milliseconds. Requires the current monotonic time, in truncated msec, - * as its input <b>now</b>. - */ -uint32_t -buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) -{ - if (buf->head) { - return now - buf->head->inserted_time; - } else { - return 0; - } -} - -size_t -buf_get_total_allocation(void) -{ - return total_bytes_allocated_in_chunks; -} - -/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into - * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set - * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, - * and the number of bytes read otherwise. */ -static inline int -read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, - int *reached_eof, int *socket_error) -{ - ssize_t read_result; - if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) - at_most = CHUNK_REMAINING_CAPACITY(chunk); - read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); - - if (read_result < 0) { - int e = tor_socket_errno(fd); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); -#endif - *socket_error = e; - return -1; - } - return 0; /* would block. */ - } else if (read_result == 0) { - log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); - *reached_eof = 1; - return 0; - } else { /* actually got bytes. */ - buf->datalen += read_result; - chunk->datalen += read_result; - log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, - (int)buf->datalen); - tor_assert(read_result < INT_MAX); - return (int)read_result; - } -} - -/** As read_to_chunk(), but return (negative) error code on error, blocking, - * or TLS, and the number of bytes read otherwise. */ -static inline int -read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, - size_t at_most) -{ - int read_result; - - tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); - read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); - if (read_result < 0) - return read_result; - buf->datalen += read_result; - chunk->datalen += read_result; - return read_result; -} - -/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most - * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 - * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on - * error; else return the number of bytes read. - */ -/* XXXX indicate "read blocked" somehow? */ -int -read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes read" are not mutually exclusive. - */ - int r = 0; - size_t total_read = 0; - - check(); - tor_assert(reached_eof); - tor_assert(SOCKET_OK(s)); - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) { /* eof, block, or no more to read. */ - break; - } - } - return (int)total_read; -} - -/** As read_to_buf, but reads from a TLS connection, and returns a TLS - * status value rather than the number of bytes read. - * - * Using TLS on OR connections complicates matters in two ways. - * - * First, a TLS stream has its own read buffer independent of the - * connection's read buffer. (TLS needs to read an entire frame from - * the network before it can decrypt any data. Thus, trying to read 1 - * byte from TLS can require that several KB be read from the network - * and decrypted. The extra data is stored in TLS's decrypt buffer.) - * Because the data hasn't been read by Tor (it's still inside the TLS), - * this means that sometimes a connection "has stuff to read" even when - * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is - * used in connection.c to detect TLS objects with non-empty internal - * buffers and read from them again. - * - * Second, the TLS stream's events do not correspond directly to network - * events: sometimes, before a TLS stream can read, the network must be - * ready to write -- or vice versa. - */ -int -read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) -{ - int r = 0; - size_t total_read = 0; - - check_no_tls_errors(); - - check(); - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk_tls(buf, chunk, tls, readlen); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) /* eof, block, or no more to read. */ - break; - } - return (int)total_read; -} - -/** Helper for flush_buf(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct - * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, 0 on blocking, -1 on failure. - */ -static inline int -flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, - size_t *buf_flushlen) -{ - ssize_t write_result; - - if (sz > chunk->datalen) - sz = chunk->datalen; - write_result = tor_socket_send(s, chunk->data, sz, 0); - - if (write_result < 0) { - int e = tor_socket_errno(s); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); -#endif - return -1; - } - log_debug(LD_NET,"write() would block, returning."); - return 0; - } else { - *buf_flushlen -= write_result; - buf_remove_from_front(buf, write_result); - tor_assert(write_result < INT_MAX); - return (int)write_result; - } -} - -/** Helper for flush_buf_tls(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write - * more if there is a forced pending write size.) On success, deduct the - * bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, and a TOR_TLS error code on failure or blocking. - */ -static inline int -flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, - size_t sz, size_t *buf_flushlen) -{ - int r; - size_t forced; - char *data; - - forced = tor_tls_get_forced_write_size(tls); - if (forced > sz) - sz = forced; - if (chunk) { - data = chunk->data; - tor_assert(sz <= chunk->datalen); - } else { - data = NULL; - tor_assert(sz == 0); - } - r = tor_tls_write(tls, data, sz); - if (r < 0) - return r; - if (*buf_flushlen > (size_t)r) - *buf_flushlen -= r; - else - *buf_flushlen = 0; - buf_remove_from_front(buf, r); - log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", - r,(int)*buf_flushlen,(int)buf->datalen); - return r; -} - -/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most - * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by - * the number of bytes actually written, and remove the written bytes - * from the buffer. Return the number of bytes written on success, - * -1 on failure. Return 0 if write() would block. - */ -int -flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes flushed" are not mutually exclusive. - */ - int r; - size_t flushed = 0; - tor_assert(buf_flushlen); - tor_assert(SOCKET_OK(s)); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(sz <= *buf_flushlen); - - check(); - while (sz) { - size_t flushlen0; - tor_assert(buf->head); - if (buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - - r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ - break; - } - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** As flush_buf(), but writes data to a TLS connection. Can write more than - * <b>flushlen</b> bytes. - */ -int -flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t flushlen, - size_t *buf_flushlen) -{ - int r; - size_t flushed = 0; - ssize_t sz; - tor_assert(buf_flushlen); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(flushlen <= *buf_flushlen); - sz = (ssize_t) flushlen; - - /* we want to let tls write even if flushlen is zero, because it might - * have a partial record pending */ - check_no_tls_errors(); - - check(); - do { - size_t flushlen0; - if (buf->head) { - if ((ssize_t)buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - } else { - flushlen0 = 0; - } - - r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0) /* Can't flush any more now. */ - break; - } while (sz > 0); - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** Append <b>string_len</b> bytes from <b>string</b> to the end of - * <b>buf</b>. - * - * Return the new length of the buffer on success, -1 on failure. - */ -int -write_to_buf(const char *string, size_t string_len, buf_t *buf) -{ - if (!string_len) - return (int)buf->datalen; - check(); - - while (string_len) { - size_t copy; - if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) - buf_add_chunk_with_capacity(buf, string_len, 1); - - copy = CHUNK_REMAINING_CAPACITY(buf->tail); - if (copy > string_len) - copy = string_len; - memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); - string_len -= copy; - string += copy; - buf->datalen += copy; - buf->tail->datalen += copy; - } - - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> - * onto <b>string</b>. - */ -static inline void -peek_from_buf(char *string, size_t string_len, const buf_t *buf) -{ - chunk_t *chunk; - - tor_assert(string); - /* make sure we don't ask for too much */ - tor_assert(string_len <= buf->datalen); - /* assert_buf_ok(buf); */ - - chunk = buf->head; - while (string_len) { - size_t copy = string_len; - tor_assert(chunk); - if (chunk->datalen < copy) - copy = chunk->datalen; - memcpy(string, chunk->data, copy); - string_len -= copy; - string += copy; - chunk = chunk->next; - } -} - -/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store - * them into <b>string</b>. Return the new buffer size. <b>string_len</b> - * must be \<= the number of bytes on the buffer. - */ -int -fetch_from_buf(char *string, size_t string_len, buf_t *buf) -{ - /* There must be string_len bytes in buf; write them onto string, - * then memmove buf back (that is, remove them from buf). - * - * Return the number of bytes still on the buffer. */ - - check(); - peek_from_buf(string, string_len, buf); - buf_remove_from_front(buf, string_len); - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** True iff the cell command <b>command</b> is one that implies a - * variable-length cell in Tor link protocol <b>linkproto</b>. */ -static inline int -cell_command_is_var_length(uint8_t command, int linkproto) -{ - /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells - * work as implemented here. If it's 1, there are no variable-length cells. - * Tor does not support other versions right now, and so can't negotiate - * them. - */ - switch (linkproto) { - case 1: - /* Link protocol version 1 has no variable-length cells. */ - return 0; - case 2: - /* In link protocol version 2, VERSIONS is the only variable-length cell */ - return command == CELL_VERSIONS; - case 0: - case 3: - default: - /* In link protocol version 3 and later, and in version "unknown", - * commands 128 and higher indicate variable-length. VERSIONS is - * grandfathered in. */ - return command == CELL_VERSIONS || command >= 128; - } -} - -/** Check <b>buf</b> for a variable-length cell according to the rules of link - * protocol version <b>linkproto</b>. If one is found, pull it off the buffer - * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. - * Return 0 if whatever is on the start of buf_t is not a variable-length - * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start - * of a variable-length cell on <b>buf</b>, but the whole thing isn't there - * yet. */ -int -fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) -{ - char hdr[VAR_CELL_MAX_HEADER_SIZE]; - var_cell_t *result; - uint8_t command; - uint16_t length; - const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; - const int circ_id_len = get_circ_id_size(wide_circ_ids); - const unsigned header_len = get_var_cell_header_size(wide_circ_ids); - check(); - *out = NULL; - if (buf->datalen < header_len) - return 0; - peek_from_buf(hdr, header_len, buf); - - command = get_uint8(hdr + circ_id_len); - if (!(cell_command_is_var_length(command, linkproto))) - return 0; - - length = ntohs(get_uint16(hdr + circ_id_len + 1)); - if (buf->datalen < (size_t)(header_len+length)) - return 1; - result = var_cell_new(length); - result->command = command; - if (wide_circ_ids) - result->circ_id = ntohl(get_uint32(hdr)); - else - result->circ_id = ntohs(get_uint16(hdr)); - - buf_remove_from_front(buf, header_len); - peek_from_buf((char*) result->payload, length, buf); - buf_remove_from_front(buf, length); - check(); - - *out = result; - return 1; -} - -/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to - * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. - * Return the number of bytes actually copied. - */ -int -move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) -{ - /* We can do way better here, but this doesn't turn up in any profiles. */ - char b[4096]; - size_t cp, len; - len = *buf_flushlen; - if (len > buf_in->datalen) - len = buf_in->datalen; - - cp = len; /* Remember the number of bytes we intend to copy. */ - tor_assert(cp < INT_MAX); - while (len) { - /* This isn't the most efficient implementation one could imagine, since - * it does two copies instead of 1, but I kinda doubt that this will be - * critical path. */ - size_t n = len > sizeof(b) ? sizeof(b) : len; - fetch_from_buf(b, n, buf_in); - write_to_buf(b, n, buf_out); - len -= n; - } - *buf_flushlen -= cp; - return (int)cp; -} - -/** Internal structure: represents a position in a buffer. */ -typedef struct buf_pos_t { - const chunk_t *chunk; /**< Which chunk are we pointing to? */ - int pos;/**< Which character inside the chunk's data are we pointing to? */ - size_t chunk_pos; /**< Total length of all previous chunks. */ -} buf_pos_t; - -/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ -static void -buf_pos_init(const buf_t *buf, buf_pos_t *out) -{ - out->chunk = buf->head; - out->pos = 0; - out->chunk_pos = 0; -} - -/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current - * position of <b>out</b>, or later. Return -1 if no instances are found; - * otherwise returns the absolute position of the character. */ -static off_t -buf_find_pos_of_char(char ch, buf_pos_t *out) -{ - const chunk_t *chunk; - int pos; - tor_assert(out); - if (out->chunk) { - if (out->chunk->datalen) { - tor_assert(out->pos < (off_t)out->chunk->datalen); - } else { - tor_assert(out->pos == 0); - } - } - pos = out->pos; - for (chunk = out->chunk; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); - if (cp) { - out->chunk = chunk; - tor_assert(cp - chunk->data < INT_MAX); - out->pos = (int)(cp - chunk->data); - return out->chunk_pos + out->pos; - } else { - out->chunk_pos += chunk->datalen; - pos = 0; - } - } - return -1; -} - -/** Advance <b>pos</b> by a single character, if there are any more characters - * in the buffer. Returns 0 on success, -1 on failure. */ -static inline int -buf_pos_inc(buf_pos_t *pos) -{ - ++pos->pos; - if (pos->pos == (off_t)pos->chunk->datalen) { - if (!pos->chunk->next) - return -1; - pos->chunk_pos += pos->chunk->datalen; - pos->chunk = pos->chunk->next; - pos->pos = 0; - } - return 0; -} - -/** Return true iff the <b>n</b>-character string in <b>s</b> appears - * (verbatim) at <b>pos</b>. */ -static int -buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) -{ - buf_pos_t p; - if (!n) - return 1; - - memcpy(&p, pos, sizeof(p)); - - while (1) { - char ch = p.chunk->data[p.pos]; - if (ch != *s) - return 0; - ++s; - /* If we're out of characters that don't match, we match. Check this - * _before_ we test incrementing pos, in case we're at the end of the - * string. */ - if (--n == 0) - return 1; - if (buf_pos_inc(&p)<0) - return 0; - } -} - -/** Return the first position in <b>buf</b> at which the <b>n</b>-character - * string <b>s</b> occurs, or -1 if it does not occur. */ -STATIC int -buf_find_string_offset(const buf_t *buf, const char *s, size_t n) -{ - buf_pos_t pos; - buf_pos_init(buf, &pos); - while (buf_find_pos_of_char(*s, &pos) >= 0) { - if (buf_matches_at_pos(&pos, s, n)) { - tor_assert(pos.chunk_pos + pos.pos < INT_MAX); - return (int)(pos.chunk_pos + pos.pos); - } else { - if (buf_pos_inc(&pos)<0) - return -1; - } - } - return -1; -} - -/** There is a (possibly incomplete) http statement on <b>buf</b>, of the - * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) - * If a) the headers include a Content-Length field and all bytes in - * the body are present, or b) there's no Content-Length field and - * all headers are present, then: - * - * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. - * - memdup body into <b>*body_out</b>, and NUL-terminate it. - * - Then remove them from <b>buf</b>, and return 1. - * - * - If headers or body is NULL, discard that part of the buf. - * - If a headers or body doesn't fit in the arg, return -1. - * (We ensure that the headers or body don't exceed max len, - * _even if_ we're planning to discard them.) - * - If force_complete is true, then succeed even if not all of the - * content has arrived. - * - * Else, change nothing and return 0. - */ -int -fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete) -{ - char *headers, *p; - size_t headerlen, bodylen, contentlen; - int crlf_offset; - - check(); - if (!buf->head) - return 0; - - crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); - if (crlf_offset > (int)max_headerlen || - (crlf_offset < 0 && buf->datalen > max_headerlen)) { - log_debug(LD_HTTP,"headers too long."); - return -1; - } else if (crlf_offset < 0) { - log_debug(LD_HTTP,"headers not all here yet."); - return 0; - } - /* Okay, we have a full header. Make sure it all appears in the first - * chunk. */ - if ((int)buf->head->datalen < crlf_offset + 4) - buf_pullup(buf, crlf_offset+4); - headerlen = crlf_offset + 4; - - headers = buf->head->data; - bodylen = buf->datalen - headerlen; - log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); - - if (max_headerlen <= headerlen) { - log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", - (int)headerlen, (int)max_headerlen-1); - return -1; - } - if (max_bodylen <= bodylen) { - log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", - (int)bodylen, (int)max_bodylen-1); - return -1; - } - -#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; - /* 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); - } - } - /* all happy. copy into the appropriate places, and return 1 */ - if (headers_out) { - *headers_out = tor_malloc(headerlen+1); - fetch_from_buf(*headers_out, headerlen, buf); - (*headers_out)[headerlen] = 0; /* NUL terminate it */ - } - if (body_out) { - tor_assert(body_used); - *body_used = bodylen; - *body_out = tor_malloc(bodylen+1); - fetch_from_buf(*body_out, bodylen, buf); - (*body_out)[bodylen] = 0; /* NUL terminate it */ - } - check(); - return 1; -} - -/** - * Wait this many seconds before warning the user about using SOCKS unsafely - * again (requires that WarnUnsafeSocks is turned on). */ -#define SOCKS_WARN_INTERVAL 5 - -/** Warn that the user application has made an unsafe socks request using - * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than - * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ -static void -log_unsafe_socks_warning(int socks_protocol, const char *address, - uint16_t port, int safe_socks) -{ - static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - - 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 " - "Tor only an IP address. Applications that do DNS resolves " - "themselves may leak information. Consider using Socks4A " - "(e.g. via privoxy or socat) instead. For more information, " - "please see https://wiki.torproject.org/TheOnionRouter/" - "TorFAQ#SOCKSAndDNS.%s", - socks_protocol, - (int)port, - safe_socks ? " Rejecting." : ""); - } - control_event_client_status(LOG_WARN, - "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", - socks_protocol, address, (int)port); -} - -/** Do not attempt to parse socks messages longer than this. This value is - * actually significantly higher than the longest possible socks message. */ -#define MAX_SOCKS_MESSAGE_LEN 512 - -/** Return a new socks_request_t. */ -socks_request_t * -socks_request_new(void) -{ - return tor_malloc_zero(sizeof(socks_request_t)); -} - -/** Free all storage held in the socks_request_t <b>req</b>. */ -void -socks_request_free(socks_request_t *req) -{ - if (!req) - return; - if (req->username) { - memwipe(req->username, 0x10, req->usernamelen); - tor_free(req->username); - } - if (req->password) { - memwipe(req->password, 0x04, req->passwordlen); - tor_free(req->password); - } - memwipe(req, 0xCC, sizeof(socks_request_t)); - tor_free(req); -} - -/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one - * of the forms - * - socks4: "socksheader username\\0" - * - socks4a: "socksheader username\\0 destaddr\\0" - * - socks5 phase one: "version #methods methods" - * - socks5 phase two: "version command 0 addresstype..." - * If it's a complete and valid handshake, and destaddr fits in - * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, - * assign to <b>req</b>, and return 1. - * - * If it's invalid or too big, return -1. - * - * Else it's not all there yet, leave buf alone and return 0. - * - * If you want to specify the socks reply, write it into <b>req->reply</b> - * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. - * - * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether - * the connection is possibly leaking DNS requests locally or not. - * - * If <b>safe_socks</b> is true, then reject unsafe socks protocols. - * - * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are - * undefined. - */ -int -fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks) -{ - int res; - ssize_t n_drain; - size_t want_length = 128; - - if (buf->datalen < 2) /* version and another byte */ - return 0; - - do { - n_drain = 0; - buf_pullup(buf, want_length); - tor_assert(buf->head && buf->head->datalen >= 2); - want_length = 0; - - res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, - safe_socks, &n_drain, &want_length); - - if (n_drain < 0) - buf_clear(buf); - else if (n_drain > 0) - buf_remove_from_front(buf, n_drain); - - } while (res == 0 && buf->head && want_length < buf->datalen && - buf->datalen >= 2); - - return res; -} - -/** The size of the header of an Extended ORPort message: 2 bytes for - * COMMAND, 2 bytes for BODYLEN */ -#define EXT_OR_CMD_HEADER_SIZE 4 - -/** Read <b>buf</b>, which should contain an Extended ORPort message - * from a transport proxy. If well-formed, create and populate - * <b>out</b> with the Extended ORport message. Return 0 if the - * buffer was incomplete, 1 if it was well-formed and -1 if we - * encountered an error while parsing it. */ -int -fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) -{ - char hdr[EXT_OR_CMD_HEADER_SIZE]; - uint16_t len; - - check(); - if (buf->datalen < EXT_OR_CMD_HEADER_SIZE) - return 0; - peek_from_buf(hdr, sizeof(hdr), buf); - len = ntohs(get_uint16(hdr+2)); - if (buf->datalen < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) - return 0; - *out = ext_or_cmd_new(len); - (*out)->cmd = ntohs(get_uint16(hdr)); - (*out)->len = len; - buf_remove_from_front(buf, EXT_OR_CMD_HEADER_SIZE); - fetch_from_buf((*out)->body, len, buf); - return 1; -} - -/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and - * have Tor send it as error response to <b>req</b>. - */ -static void -socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason) -{ - req->replylen = 10; - memset(req->reply,0,10); - - req->reply[0] = 0x05; // VER field. - req->reply[1] = reason; // REP field. - req->reply[3] = 0x01; // ATYP field. -} - -/** Implementation helper to implement fetch_from_*_socks. Instead of looking - * at a buffer's contents, we look at the <b>datalen</b> bytes of data in - * <b>data</b>. Instead of removing data from the buffer, we set - * <b>drain_out</b> to the amount of data that should be removed (or -1 if the - * buffer should be cleared). Instead of pulling more data into the first - * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes - * we'd like to see in the input buffer, if they're available. */ -static int -parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out) -{ - unsigned int len; - char tmpbuf[TOR_ADDR_BUF_LEN+1]; - tor_addr_t destaddr; - uint32_t destip; - uint8_t socksver; - char *next, *startaddr; - unsigned char usernamelen, passlen; - struct in_addr in; - - if (datalen < 2) { - /* We always need at least 2 bytes. */ - *want_length_out = 2; - return 0; - } - - if (req->socks_version == 5 && !req->got_auth) { - /* See if we have received authentication. Strictly speaking, we should - also check whether we actually negotiated username/password - authentication. But some broken clients will send us authentication - even if we negotiated SOCKS_NO_AUTH. */ - if (*data == 1) { /* username/pass version 1 */ - /* Format is: authversion [1 byte] == 1 - usernamelen [1 byte] - username [usernamelen bytes] - passlen [1 byte] - password [passlen bytes] */ - usernamelen = (unsigned char)*(data + 1); - if (datalen < 2u + usernamelen + 1u) { - *want_length_out = 2u + usernamelen + 1u; - return 0; - } - passlen = (unsigned char)*(data + 2u + usernamelen); - if (datalen < 2u + usernamelen + 1u + passlen) { - *want_length_out = 2u + usernamelen + 1u + passlen; - return 0; - } - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 1; /* authversion == 1 */ - req->reply[1] = 0; /* authentication successful */ - log_debug(LD_APP, - "socks5: Accepted username/password without checking."); - if (usernamelen) { - req->username = tor_memdup(data+2u, usernamelen); - req->usernamelen = usernamelen; - } - if (passlen) { - req->password = tor_memdup(data+3u+usernamelen, passlen); - req->passwordlen = passlen; - } - *drain_out = 2u + usernamelen + 1u + passlen; - req->got_auth = 1; - *want_length_out = 7; /* Minimal socks5 command. */ - return 0; - } else if (req->auth_type == SOCKS_USER_PASS) { - /* unknown version byte */ - log_warn(LD_APP, "Socks5 username/password version %d not recognized; " - "rejecting.", (int)*data); - return -1; - } - } - - socksver = *data; - - switch (socksver) { /* which version of socks? */ - case 5: /* socks5 */ - - if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(data+1); - int have_user_pass, have_no_auth; - int r=0; - tor_assert(!req->socks_version); - if (datalen < 2u+nummethods) { - *want_length_out = 2u+nummethods; - return 0; - } - if (!nummethods) - return -1; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); - have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); - if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { - req->auth_type = SOCKS_USER_PASS; - req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" - auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); - r=0; - } else if (have_no_auth) { - req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth - method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); - r=0; - } else { - log_warn(LD_APP, - "socks5: offered methods don't include 'no auth' or " - "username/password. Rejecting."); - req->reply[1] = '\xFF'; /* reject all methods */ - r=-1; - } - /* Remove packet from buf. Some SOCKS clients will have sent extra - * junk at this point; let's hope it's an authentication message. */ - *drain_out = 2u + nummethods; - - return r; - } - if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { - log_warn(LD_APP, - "socks5: negotiated authentication, but none provided"); - return -1; - } - /* we know the method; read in the request */ - log_debug(LD_APP,"socks5: checking request"); - if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ - *want_length_out = 7; - return 0; /* not yet */ - } - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE && - req->command != SOCKS_COMMAND_RESOLVE_PTR) { - /* not a connect or resolve or a resolve_ptr? we don't support it. */ - socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); - - log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", - req->command); - return -1; - } - switch (*(data+3)) { /* address type */ - case 1: /* IPv4 address */ - case 4: /* IPv6 address */ { - const int is_v6 = *(data+3) == 4; - const unsigned addrlen = is_v6 ? 16 : 4; - log_debug(LD_APP,"socks5: ipv4 address type"); - if (datalen < 6+addrlen) {/* ip/port there? */ - *want_length_out = 6+addrlen; - return 0; /* not yet */ - } - - if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, data+4); - else - tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); - - tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); - - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 IP takes %d bytes, which doesn't fit in %d. " - "Rejecting.", - (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); - return -1; - } - strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(data+4+addrlen)); - *drain_out = 6+addrlen; - if (req->command != SOCKS_COMMAND_RESOLVE_PTR && - !addressmap_have_mapping(req->address,0)) { - log_unsafe_socks_warning(5, req->address, req->port, safe_socks); - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } - return 1; - } - case 3: /* fqdn */ - log_debug(LD_APP,"socks5: fqdn address type"); - if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " - "hostname type. Rejecting."); - return -1; - } - len = (unsigned char)*(data+4); - if (datalen < 7+len) { /* addr/port there? */ - *want_length_out = 7+len; - return 0; /* not yet */ - } - if (len+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 hostname is %d bytes, which doesn't fit in " - "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); - return -1; - } - memcpy(req->address,data+5,len); - req->address[len] = 0; - req->port = ntohs(get_uint16(data+5+len)); - *drain_out = 5+len+2; - - if (string_is_valid_ipv4_address(req->address) || - string_is_valid_ipv6_address(req->address)) { - log_unsafe_socks_warning(5,req->address,req->port,safe_socks); - - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } else if (!string_is_valid_hostname(req->address)) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - - log_warn(LD_PROTOCOL, - "Your application (using socks5 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks5 to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - return 1; - default: /* unsupported */ - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", - (int) *(data+3)); - return -1; - } - tor_assert(0); - break; - case 4: { /* socks4 */ - enum {socks4, socks4a} socks4_prot = socks4a; - const char *authstart, *authend; - /* http://ss5.sourceforge.net/socks4.protocol.txt */ - /* http://ss5.sourceforge.net/socks4A.protocol.txt */ - - req->socks_version = 4; - if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ - *want_length_out = SOCKS4_NETWORK_LEN; - return 0; /* not yet */ - } - // buf_pullup(buf, 1280); - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. (No resolve_ptr with - * socks4.) */ - log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", - req->command); - return -1; - } - - req->port = ntohs(get_uint16(data+2)); - destip = ntohl(get_uint32(data+4)); - if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { - log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); - return -1; - } - if (destip >> 8) { - log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); - in.s_addr = htonl(destip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", - (int)strlen(tmpbuf)); - return -1; - } - log_debug(LD_APP, - "socks4: successfully read destip (%s)", - safe_str_client(tmpbuf)); - socks4_prot = socks4; - } - - authstart = data + SOCKS4_NETWORK_LEN; - next = memchr(authstart, 0, - datalen-SOCKS4_NETWORK_LEN); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP, "Socks4 user name too long; rejecting."); - return -1; - } - log_debug(LD_APP,"socks4: Username not here yet."); - *want_length_out = datalen+1024; /* More than we need, but safe */ - return 0; - } - authend = next; - tor_assert(next < data+datalen); - - startaddr = NULL; - if (socks4_prot != socks4a && - !addressmap_have_mapping(tmpbuf,0)) { - log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); - - if (safe_socks) - return -1; - } - if (socks4_prot == socks4a) { - if (next+1 == data+datalen) { - log_debug(LD_APP,"socks4: No part of destaddr here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - startaddr = next+1; - next = memchr(startaddr, 0, data + datalen - startaddr); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP,"socks4: Destaddr too long."); - return -1; - } - log_debug(LD_APP,"socks4: Destaddr not all here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { - log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); - return -1; - } - // tor_assert(next < buf->cur+buf->datalen); - - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks4a to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - } - log_debug(LD_APP,"socks4: Everything is here. Success."); - strlcpy(req->address, startaddr ? startaddr : tmpbuf, - sizeof(req->address)); - if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { - log_warn(LD_PROTOCOL, - "Your application (using socks4 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (authend != authstart) { - req->got_auth = 1; - req->usernamelen = authend - authstart; - req->username = tor_memdup(authstart, authend - authstart); - } - /* next points to the final \0 on inbuf */ - *drain_out = next - data + 1; - return 1; - } - case 'G': /* get */ - case 'H': /* head */ - case 'P': /* put/post */ - case 'C': /* connect */ - strlcpy((char*)req->reply, -"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" -"Content-Type: text/html; charset=iso-8859-1\r\n\r\n" -"<html>\n" -"<head>\n" -"<title>Tor is not an HTTP Proxy</title>\n" -"</head>\n" -"<body>\n" -"<h1>Tor is not an HTTP Proxy</h1>\n" -"<p>\n" -"It appears you have configured your web browser to use Tor as an HTTP proxy." -"\n" -"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" -"Please configure your client accordingly.\n" -"</p>\n" -"<p>\n" -"See <a href=\"https://www.torproject.org/documentation.html\">" - "https://www.torproject.org/documentation.html</a> for more " - "information.\n" -"<!-- Plus this comment, to make the body response more than 512 bytes, so " -" IE will be willing to display it. Comment comment comment comment " -" comment comment comment comment comment comment comment comment.-->\n" -"</p>\n" -"</body>\n" -"</html>\n" - , MAX_SOCKS_REPLY_LEN); - req->replylen = strlen((char*)req->reply)+1; - /* fall through */ - default: /* version is not socks4 or socks5 */ - log_warn(LD_APP, - "Socks version %d not recognized. (Tor is not an http proxy.)", - *(data)); - { - /* Tell the controller the first 8 bytes. */ - char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); - control_event_client_status(LOG_WARN, - "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", - escaped(tmp)); - tor_free(tmp); - } - return -1; - } -} - -/** Inspect a reply from SOCKS server stored in <b>buf</b> according - * to <b>state</b>, removing the protocol data upon success. Return 0 on - * incomplete response, 1 on success and -1 on error, in which case - * <b>reason</b> is set to a descriptive message (free() when finished - * with it). - * - * As a special case, 2 is returned when user/pass is required - * during SOCKS5 handshake and user/pass is configured. - */ -int -fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) -{ - ssize_t drain = 0; - int r; - if (buf->datalen < 2) - return 0; - - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN); - tor_assert(buf->head && buf->head->datalen >= 2); - - r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, - state, reason, &drain); - if (drain > 0) - buf_remove_from_front(buf, drain); - else if (drain < 0) - buf_clear(buf); - - return r; -} - -/** Implementation logic for fetch_from_*_socks_client. */ -static int -parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out) -{ - unsigned int addrlen; - *drain_out = 0; - if (datalen < 2) - return 0; - - switch (state) { - case PROXY_SOCKS4_WANT_CONNECT_OK: - /* Wait for the complete response */ - if (datalen < 8) - return 0; - - if (data[1] != 0x5a) { - *reason = tor_strdup(socks4_response_code_to_string(data[1])); - return -1; - } - - /* Success */ - *drain_out = 8; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - /* we don't have any credentials */ - if (data[1] != 0x00) { - *reason = tor_strdup("server doesn't support any of our " - "available authentication methods"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - /* we have a username and password. return 1 if we can proceed without - * providing authentication, or 2 otherwise. */ - switch (data[1]) { - case 0x00: - log_info(LD_NET, "SOCKS 5 client: we have auth details but server " - "doesn't require authentication."); - *drain_out = -1; - return 1; - case 0x02: - log_info(LD_NET, "SOCKS 5 client: need authentication."); - *drain_out = -1; - return 2; - /* fall through */ - } - - *reason = tor_strdup("server doesn't support any of our available " - "authentication methods"); - return -1; - - case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - /* handle server reply to rfc1929 authentication */ - if (data[1] != 0x00) { - *reason = tor_strdup("authentication failed"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: authentication successful."); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_CONNECT_OK: - /* response is variable length. BND.ADDR, etc, isn't needed - * (don't bother with buf_pullup()), but make sure to eat all - * the data used */ - - /* wait for address type field to arrive */ - if (datalen < 4) - return 0; - - switch (data[3]) { - case 0x01: /* ip4 */ - addrlen = 4; - break; - case 0x04: /* ip6 */ - addrlen = 16; - break; - case 0x03: /* fqdn (can this happen here?) */ - if (datalen < 5) - return 0; - addrlen = 1 + data[4]; - break; - default: - *reason = tor_strdup("invalid response to connect request"); - return -1; - } - - /* wait for address and port */ - if (datalen < 6 + addrlen) - return 0; - - if (data[1] != 0x00) { - *reason = tor_strdup(socks5_response_code_to_string(data[1])); - return -1; - } - - *drain_out = 6 + addrlen; - return 1; - } - - /* shouldn't get here... */ - tor_assert(0); - - return -1; -} - -/** Return 1 iff buf looks more like it has an (obsolete) v0 controller - * command on it than any valid v1 controller command. */ -int -peek_buf_has_control0_command(buf_t *buf) -{ - if (buf->datalen >= 4) { - char header[4]; - uint16_t cmd; - peek_from_buf(header, sizeof(header), buf); - cmd = ntohs(get_uint16(header+2)); - if (cmd <= 0x14) - return 1; /* This is definitely not a v1 control command. */ - } - return 0; -} - -/** Return the index within <b>buf</b> at which <b>ch</b> first appears, - * or -1 if <b>ch</b> does not appear on buf. */ -static off_t -buf_find_offset_of_char(buf_t *buf, char ch) -{ - chunk_t *chunk; - off_t offset = 0; - for (chunk = buf->head; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data, ch, chunk->datalen); - if (cp) - return offset + (cp - chunk->data); - else - offset += chunk->datalen; - } - return -1; -} - -/** Try to read a single LF-terminated line from <b>buf</b>, and write it - * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer - * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the - * line, not counting the terminating NUL. Return 1 if we read a whole line, - * return 0 if we don't have a whole line yet, and return -1 if the line - * length exceeds *<b>data_len</b>. - */ -int -fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) -{ - size_t sz; - off_t offset; - - if (!buf->head) - return 0; - - offset = buf_find_offset_of_char(buf, '\n'); - if (offset < 0) - return 0; - sz = (size_t) offset; - if (sz+2 > *data_len) { - *data_len = sz + 2; - return -1; - } - fetch_from_buf(data_out, sz+1, buf); - data_out[sz+1] = '\0'; - *data_len = sz+1; - return 1; -} - -/** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the - * zlib 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) -{ - char *next; - size_t old_avail, avail; - int over = 0; - - do { - int need_new_chunk = 0; - if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { - size_t cap = data_len / 4; - buf_add_chunk_with_capacity(buf, cap, 1); - } - next = CHUNK_WRITE_PTR(buf->tail); - avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); - switch (tor_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. */ - need_new_chunk = 1; - } - break; - } - buf->datalen += old_avail - avail; - buf->tail->datalen += old_avail - avail; - if (need_new_chunk) { - buf_add_chunk_with_capacity(buf, data_len/4, 1); - } - - } while (!over); - check(); - return 0; -} - -/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ -int -buf_set_to_copy(buf_t **output, - const buf_t *input) -{ - if (*output) - buf_free(*output); - *output = buf_copy(input); - return 0; -} - -/** Log an error and exit if <b>buf</b> is corrupted. - */ -void -assert_buf_ok(buf_t *buf) -{ - tor_assert(buf); - tor_assert(buf->magic == BUFFER_MAGIC); - - if (! buf->head) { - tor_assert(!buf->tail); - tor_assert(buf->datalen == 0); - } else { - chunk_t *ch; - size_t total = 0; - tor_assert(buf->tail); - for (ch = buf->head; ch; ch = ch->next) { - total += ch->datalen; - tor_assert(ch->datalen <= ch->memlen); - tor_assert(ch->data >= &ch->mem[0]); - tor_assert(ch->data <= &ch->mem[0]+ch->memlen); - if (ch->data == &ch->mem[0]+ch->memlen) { - static int warned = 0; - if (! warned) { - log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); - warned = 1; - } - } - tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); - if (!ch->next) - tor_assert(ch == buf->tail); - } - tor_assert(buf->datalen == total); - } -} - diff --git a/src/or/buffers.h b/src/or/buffers.h deleted file mode 100644 index 52b21d5885..0000000000 --- a/src/or/buffers.h +++ /dev/null @@ -1,101 +0,0 @@ -/* 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. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.h - * \brief Header file for buffers.c. - **/ - -#ifndef TOR_BUFFERS_H -#define TOR_BUFFERS_H - -#include "testsupport.h" - -buf_t *buf_new(void); -buf_t *buf_new_with_capacity(size_t size); -size_t buf_get_default_chunk_size(const buf_t *buf); -void buf_free(buf_t *buf); -void buf_clear(buf_t *buf); -buf_t *buf_copy(const buf_t *buf); - -MOCK_DECL(size_t, buf_datalen, (const buf_t *buf)); -size_t buf_allocation(const buf_t *buf); -size_t buf_slack(const buf_t *buf); - -uint32_t buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now); -size_t buf_get_total_allocation(void); - -int read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error); -int read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf); - -int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen); -int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen); - -int write_to_buf(const char *string, size_t string_len, buf_t *buf); -int write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, - const char *data, size_t data_len, int done); -int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); -int fetch_from_buf(char *string, size_t string_len, buf_t *buf); -int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto); -int fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete); -socks_request_t *socks_request_new(void); -void socks_request_free(socks_request_t *req); -int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks); -int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); -int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); - -int peek_buf_has_control0_command(buf_t *buf); - -int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); - -int buf_set_to_copy(buf_t **output, - const buf_t *input); - -void assert_buf_ok(buf_t *buf); - -#ifdef BUFFERS_PRIVATE -STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); -STATIC void buf_pullup(buf_t *buf, size_t bytes); -void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz); -STATIC size_t preferred_chunk_size(size_t target); - -#define DEBUG_CHUNK_ALLOC -/** A single chunk on a buffer. */ -typedef struct chunk_t { - struct chunk_t *next; /**< The next chunk on the buffer. */ - size_t datalen; /**< The number of bytes stored in this chunk */ - size_t memlen; /**< The number of usable bytes of storage in <b>mem</b>. */ -#ifdef DEBUG_CHUNK_ALLOC - size_t DBG_alloc; -#endif - char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ - uint32_t inserted_time; /**< Timestamp in truncated ms since epoch - * when this chunk was inserted. */ - char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in - * this chunk. */ -} chunk_t; - -/** Magic value for buf_t.magic, to catch pointer errors. */ -#define BUFFER_MAGIC 0xB0FFF312u -/** A resizeable buffer, optimized for reading and writing. */ -struct buf_t { - uint32_t magic; /**< Magic cookie for debugging: Must be set to - * BUFFER_MAGIC. */ - size_t datalen; /**< How many bytes is this buffer holding right now? */ - size_t default_chunk_size; /**< Don't allocate any chunks smaller than - * this for this buffer. */ - chunk_t *head; /**< First chunk in the list, or NULL for none. */ - chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ -}; -#endif - -#endif - diff --git a/src/or/channel.c b/src/or/channel.c index 54e10666d2..3c0025aff6 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 */ /** @@ -49,6 +49,7 @@ #include "or.h" #include "channel.h" #include "channeltls.h" +#include "channelpadding.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" @@ -63,6 +64,9 @@ #include "router.h" #include "routerlist.h" #include "scheduler.h" +#include "compat_time.h" +#include "networkstatus.h" +#include "rendservice.h" /* Global lists of channels */ @@ -84,6 +88,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; /* @@ -429,6 +455,7 @@ void channel_register(channel_t *chan) { tor_assert(chan); + tor_assert(chan->global_identifier); /* No-op if already registered */ if (chan->registered) return; @@ -443,6 +470,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)) { @@ -498,7 +527,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; @@ -533,7 +564,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); @@ -578,7 +609,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 */ @@ -670,7 +701,7 @@ channel_remove_from_digest_map(channel_t *chan) return; } -#endif +#endif /* 0 */ /* Pull it out of its list, wherever that list is */ TOR_LIST_REMOVE(chan, next_with_same_id); @@ -719,41 +750,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 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. + * 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. + * + * 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; } @@ -766,7 +830,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); @@ -774,6 +838,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 @@ -787,7 +928,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); @@ -810,6 +951,9 @@ channel_init(channel_t *chan) /* Scheduler state is idle */ chan->scheduler_state = SCHED_CHAN_IDLE; + + /* Channel is not in the scheduler heap. */ + chan->sched_heap_idx = -1; } /** @@ -826,7 +970,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); @@ -863,6 +1007,11 @@ 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_fn) chan->free_fn(chan); @@ -941,6 +1090,11 @@ 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_fn) chan->free_fn(chan); @@ -1433,10 +1587,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; @@ -1475,6 +1629,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) @@ -1676,7 +1835,7 @@ cell_queue_entry_is_padding(cell_queue_entry_t *q) return 0; } -#endif +#endif /* 0 */ /** * Allocate a new cell queue entry for a fixed-size cell @@ -1738,7 +1897,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; @@ -1838,45 +1997,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 @@ -1888,30 +2060,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); } /** @@ -1927,30 +2078,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); } /** @@ -1961,8 +2091,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; @@ -2081,18 +2211,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)); @@ -2100,6 +2220,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 @@ -2307,121 +2456,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 = 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 = 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_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; } } } @@ -2458,8 +2606,8 @@ channel_flush_cells(channel_t *chan) * available. */ -int -channel_more_to_flush(channel_t *chan) +MOCK_IMPL(int, +channel_more_to_flush, (channel_t *chan)) { tor_assert(chan); @@ -2567,20 +2715,10 @@ 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 */ - if (!router_get_by_id_digest(chan->identity_digest)) { + if (!connection_or_digest_is_known_relay(chan->identity_digest)) { if (channel_get_addr_if_possible(chan, &remote_addr)) { char *transport_name = NULL; channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); @@ -2600,6 +2738,32 @@ 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) { + /* Disable if torrc disabled */ + channelpadding_disable_padding_on_channel(chan); + } else if (get_options()->Tor2webMode && + !networkstatus_get_param(NULL, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1)) { + /* Disable if we're using tor2web and the consensus disabled padding + * for tor2web */ + channelpadding_disable_padding_on_channel(chan); + } else if (rend_service_allow_non_anonymous_connection(get_options()) && + !networkstatus_get_param(NULL, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 0, 1)) { + /* Disable if we're using RSOS and the consensus disabled padding + * for RSOS*/ + channelpadding_disable_padding_on_channel(chan); + } else if (get_options()->ReducedConnectionPadding) { + /* Padding can be forced and/or reduced by clients, regardless of if + * the channel supports it */ + channelpadding_reduce_padding_on_channel(chan); + } + } + circuit_n_chan_done(chan, 1, close_origin_circuits); } @@ -3237,6 +3401,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"); } @@ -3254,9 +3423,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); } /** @@ -3271,22 +3441,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); @@ -3294,26 +3462,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; } @@ -3329,7 +3502,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) @@ -3337,19 +3511,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; @@ -3360,6 +3533,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 @@ -3402,7 +3580,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; } @@ -3920,7 +4098,7 @@ channel_mark_bad_for_new_circs(channel_t *chan) */ int -channel_is_client(channel_t *chan) +channel_is_client(const channel_t *chan) { tor_assert(chan); @@ -3942,6 +4120,20 @@ channel_mark_client(channel_t *chan) } /** + * Clear the client flag + * + * Mark a channel as being _not_ from a client + */ + +void +channel_clear_client(channel_t *chan) +{ + tor_assert(chan); + + chan->is_client = 0; +} + +/** * Get the canonical flag for a channel * * This returns the is_canonical for a channel; this flag is determined by @@ -4184,8 +4376,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; } /** @@ -4268,11 +4464,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; } /** @@ -4285,11 +4484,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; } /*************************************************************** @@ -4531,6 +4734,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 @@ -4585,8 +4863,6 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); - /* Tell the scheduler we're increasing the queue size */ - scheduler_adjust_queue_size(chan, 1, adj); } } else if (queued < chan->bytes_queued_for_xmit) { adj = chan->bytes_queued_for_xmit - queued; @@ -4609,8 +4885,6 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); - /* Tell the scheduler we're decreasing the queue size */ - scheduler_adjust_queue_size(chan, -1, adj); } } } diff --git a/src/or/channel.h b/src/or/channel.h index bcd345e8d2..074cc86fe7 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,6 +161,18 @@ 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 */ @@ -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) */ @@ -382,7 +484,10 @@ 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); -#endif + +void channel_write_cell_generic_(channel_t *chan, const char *cell_type, + void *cell, cell_queue_entry_t *q); +#endif /* defined(CHANNEL_PRIVATE_) */ /* 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); @@ -461,7 +568,7 @@ MOCK_DECL(ssize_t, channel_flush_some_cells, (channel_t *chan, ssize_t num_cells)); /* Query if data available on this channel */ -int channel_more_to_flush(channel_t *chan); +MOCK_DECL(int, channel_more_to_flush, (channel_t *chan)); /* Notify flushed outgoing for dirreq handling */ void channel_notify_flushed(channel_t *chan); @@ -473,7 +580,7 @@ void channel_do_open_actions(channel_t *chan); extern uint64_t estimated_total_queue_size; #endif -#endif +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ /* Helper functions to perform operations on channels */ @@ -486,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. @@ -558,11 +667,12 @@ int channel_is_bad_for_new_circs(channel_t *chan); void channel_mark_bad_for_new_circs(channel_t *chan); int channel_is_canonical(channel_t *chan); int channel_is_canonical_is_reliable(channel_t *chan); -int channel_is_client(channel_t *chan); +int channel_is_client(const channel_t *chan); int channel_is_local(channel_t *chan); int channel_is_incoming(channel_t *chan); int channel_is_outgoing(channel_t *chan); void channel_mark_client(channel_t *chan); +void channel_clear_client(channel_t *chan); int channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info); int channel_matches_target_addr_for_extend(channel_t *chan, const tor_addr_t *target); @@ -578,6 +688,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); @@ -605,5 +718,8 @@ int packed_cell_is_destroy(channel_t *chan, const packed_cell_t *packed_cell, circid_t *circid_out); -#endif +/* Declare the handle helpers */ +HANDLE_DECL(channel, channel_s,) + +#endif /* !defined(TOR_CHANNEL_H) */ diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c new file mode 100644 index 0000000000..435436c45c --- /dev/null +++ b/src/or/channelpadding.c @@ -0,0 +1,793 @@ +/* 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> +#include "rendservice.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; +/** Should we pad tor2web connections? */ +static int consensus_nf_pad_tor2web; +/** Should we pad rosos connections? */ +static int consensus_nf_pad_single_onion; + +#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)) || channel_is_client(chan) || \ + !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); + + consensus_nf_pad_tor2web = + networkstatus_get_param(ns, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1); + + consensus_nf_pad_single_onion = + networkstatus_get_param(ns, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 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 (options->Tor2webMode && !consensus_nf_pad_tor2web) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + return CHANNELPADDING_WONTPAD; + } + + if (rend_service_allow_non_anonymous_connection(options) && + !consensus_nf_pad_single_onion) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + 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..58bf741d5c --- /dev/null +++ b/src/or/channelpadding.h @@ -0,0 +1,45 @@ +/* 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" + +#define CHANNELPADDING_TOR2WEB_PARAM "nf_pad_tor2web" +#define CHANNELPADDING_TOR2WEB_DEFAULT 1 +#define CHANNELPADDING_SOS_PARAM "nf_pad_single_onion" +#define CHANNELPADDING_SOS_DEFAULT 1 + +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 /* !defined(TOR_CHANNELPADDING_H) */ + diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 3a352d47fe..0244871548 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 */ /** @@ -49,12 +49,17 @@ #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; @@ -120,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() @@ -170,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_); @@ -198,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; @@ -598,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)); @@ -667,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 */ @@ -731,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); } @@ -830,7 +847,7 @@ channel_tls_write_packed_cell_method(channel_t *chan, tor_assert(packed_cell); if (tlschan->conn) { - connection_write_to_buf(packed_cell->body, cell_network_size, + connection_buf_add(packed_cell->body, cell_network_size, TO_CONN(tlschan->conn)); /* This is where the cell is finished; used to be done from relay.c */ @@ -976,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); @@ -1027,7 +1044,7 @@ channel_tls_time_process_cell(cell_t *cell, channel_tls_t *chan, int *time, *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** * Handle an incoming cell on a channel_tls_t @@ -1055,9 +1072,9 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) channel_tls_time_process_cell(cl, cn, & tp ## time , \ channel_tls_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) channel_tls_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(cell); tor_assert(conn); @@ -1092,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; @@ -1104,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: @@ -1172,7 +1204,7 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(var_cell); tor_assert(conn); @@ -1270,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) { @@ -1543,7 +1579,7 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) connection_or_close_normally(chan->conn, 1); return; } -#endif +#endif /* defined(DISABLE_V3_LINKPROTO_SERVERSIDE) */ if (send_versions) { if (connection_or_send_versions(chan->conn, 1) < 0) { @@ -1555,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) { @@ -1584,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 @@ -1600,9 +1676,12 @@ 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; + int started_here = 0; + const char *identity_digest = NULL; tor_assert(cell); tor_assert(chan); @@ -1622,10 +1701,12 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) } tor_assert(chan->conn->handshake_state && chan->conn->handshake_state->received_versions); + started_here = connection_or_nonopen_was_started_here(chan->conn); + identity_digest = chan->conn->identity_digest; if (chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { tor_assert(chan->conn->link_proto >= 3); - if (chan->conn->handshake_state->started_here) { + if (started_here) { if (!(chan->conn->handshake_state->authenticated)) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got a NETINFO cell from server, " @@ -1639,7 +1720,10 @@ 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) */ @@ -1650,8 +1734,10 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) 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); } } @@ -1677,8 +1763,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++; @@ -1694,6 +1792,13 @@ 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 sound a bit + * concerning... but that's what "canonical" means: that the + * address is one that the relay itself has claimed. The relay + * might be doing something funny, but nobody else is doing a MITM + * on the relay's TCP. + */ if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { connection_or_set_canonical(chan->conn, 1); break; @@ -1702,11 +1807,27 @@ 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(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 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - router_get_by_id_digest(chan->conn->identity_digest)) { + (started_here || + connection_or_digest_is_known_relay(chan->conn->identity_digest))) { int trusted = router_digest_is_trusted_dir(chan->conn->identity_digest); clock_skew_warning(TO_CONN(chan->conn), apparent_skew, trusted, LD_GENERAL, "NETINFO cell", "OR"); @@ -1740,14 +1861,48 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) safe_str_client(chan->conn->base_.address), chan->conn->base_.port, (int)(chan->conn->link_proto), - hex_str(TLS_CHAN_TO_BASE(chan)->identity_digest, - DIGEST_LEN), + hex_str(identity_digest, DIGEST_LEN), tor_addr_is_null(&my_apparent_addr) ? "<none>" : fmt_and_decorate_addr(&my_apparent_addr)); } 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. * @@ -1763,18 +1918,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; + int send_netinfo = 0, started_here = 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); @@ -1789,6 +1950,11 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) goto err; \ } while (0) + /* Can't use connection_or_nonopen_was_started_here(); its conn->tls + * check looks like it breaks + * test_link_handshake_recv_certs_ok_server(). */ + started_here = chan->conn->handshake_state->started_here; + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) ERR("We're not doing a v3 handshake!"); if (chan->conn->link_proto < 3) @@ -1818,77 +1984,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 (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"); + if (started_here) { + /* 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 @@ -1897,25 +2135,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; @@ -1929,9 +2156,13 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) } err: - for (unsigned u = 0; u < ARRAY_LENGTH(certs); ++u) { - tor_x509_cert_free(certs[u]); + 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 } @@ -1988,8 +2219,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; @@ -2004,9 +2239,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, @@ -2047,9 +2283,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); @@ -2062,6 +2300,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) @@ -2079,9 +2318,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"); @@ -2093,8 +2330,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; @@ -2103,25 +2341,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); @@ -2132,7 +2400,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); @@ -2145,41 +2413,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 8b5863a461..d9c4239c3a 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 */ /** @@ -26,10 +26,11 @@ struct channel_tls_s { or_connection_t *conn; }; -#endif /* TOR_CHANNEL_INTERNAL_ */ +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest); + 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); @@ -68,7 +69,7 @@ STATIC void channel_tls_process_auth_challenge_cell(var_cell_t *cell, STATIC void channel_tls_common_init(channel_tls_t *tlschan); STATIC void channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *tlschan); -#endif +#endif /* defined(CHANNELTLS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CHANNELTLS_H) */ diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 9f93e737f7..f4bd5ea2fa 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; } @@ -282,7 +292,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) return circ->cpath && circ->cpath->next != circ->cpath && circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else +#else /* !(defined(N2N_TAGGING_IS_POSSIBLE)) */ /* If tagging attacks are no longer possible, we probably want to * count bias from the first hop. However, one could argue that * timing-based tagging is still more useful than per-hop failure. @@ -290,7 +300,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) */ return circ->cpath && circ->cpath->state == CPATH_STATE_AWAITING_KEYS; -#endif +#endif /* defined(N2N_TAGGING_IS_POSSIBLE) */ } /** @@ -505,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()))) { @@ -527,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. @@ -574,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; } @@ -588,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; @@ -702,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)); } } @@ -1018,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 @@ -1057,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 @@ -1090,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 @@ -1133,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(); } } @@ -1165,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", @@ -1189,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); @@ -1205,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); @@ -1223,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. " @@ -1242,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. " @@ -1268,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. " @@ -1292,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)); } } @@ -1329,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. " @@ -1348,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. " @@ -1374,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 " @@ -1399,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)); } } @@ -1450,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); @@ -1460,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)); } } } @@ -1509,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..c9e572d2ae 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 */ /** @@ -25,5 +25,5 @@ void pathbias_mark_use_success(origin_circuit_t *circ); void pathbias_mark_use_rollback(origin_circuit_t *circ); const char *pathbias_state_to_string(path_state_t state); -#endif +#endif /* !defined(TOR_CIRCPATHBIAS_H) */ diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index cb9c146fb7..8c52c58e6d 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 @@ -31,6 +46,7 @@ #include "crypto.h" #include "directory.h" #include "entrynodes.h" +#include "hs_ntor.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -49,16 +65,21 @@ #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 int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit, + int is_hs_v3_rp_circuit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); -static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); +static int circuit_send_first_onion_skin(origin_circuit_t *circ); +static int circuit_build_no_more_hops(origin_circuit_t *circ); +static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -66,11 +87,12 @@ static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); */ 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; @@ -268,14 +290,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - node = node_get_by_id(id); - if (node && node_is_named(node)) { - elt = tor_strdup(node_get_nickname(node)); - } else { - elt = tor_malloc(HEX_DIGEST_LEN+2); - elt[0] = '$'; - base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); - } + elt = tor_malloc(HEX_DIGEST_LEN+2); + elt[0] = '$'; + base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } tor_assert(elt); if (verbose) { @@ -484,10 +501,15 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) { origin_circuit_t *circ; int err_reason = 0; + int is_hs_v3_rp_circuit = 0; + + if (flags & CIRCLAUNCH_IS_V3_RP) { + is_hs_v3_rp_circuit = 1; + } circ = origin_circuit_init(purpose, flags); - if (onion_pick_cpath_exit(circ, exit_ei) < 0 || + if (onion_pick_cpath_exit(circ, exit_ei, is_hs_v3_rp_circuit) < 0 || onion_populate_cpath(circ) < 0) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH); return NULL; @@ -502,6 +524,13 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, 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. @@ -540,6 +569,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); @@ -557,7 +587,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; @@ -791,12 +822,7 @@ should_use_create_fast_for_circuit(origin_circuit_t *circ) * 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 @@ -866,196 +892,301 @@ circuit_pick_extend_handshake(uint8_t *cell_type_out, } } +/** + * 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); - } else { - /* We are not an OR, and we're building the first hop of a circuit to a - * new OR: we can be speedy and use CREATE_FAST to save an RSA operation - * and a DH operation. */ - cc.cell_type = CELL_CREATE_FAST; - cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; - } + tor_assert(circ->cpath->state == CPATH_STATE_OPEN); + tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); + crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath); - len = onion_skin_create(cc.handshake_type, - circ->cpath->extend_info, - &circ->cpath->handshake_state, - cc.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); - return - END_CIRC_REASON_INTERNAL; - } - cc.handshake_len = len; + if (hop) { + /* Case two: we're on a hop after the first. */ + return circuit_send_intermediate_onion_skin(circ, hop); + } - if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) - return - END_CIRC_REASON_RESOURCELIMIT; + /* Case three: the circuit is finished. Do housekeeping tasks on it. */ + return circuit_build_no_more_hops(circ); +} - circ->cpath->state = CPATH_STATE_AWAITING_KEYS; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", - fast ? "CREATE_FAST" : "CREATE", - node ? node_describe(node) : "<unnamed>"); +/** + * Called from circuit_send_next_onion_skin() when we find ourselves connected + * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST + * cell to that hop. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_send_first_onion_skin(origin_circuit_t *circ) +{ + int fast; + int len; + const node_t *node; + create_cell_t cc; + memset(&cc, 0, sizeof(cc)); + + log_debug(LD_CIRC,"First skin; sending create cell."); + + if (circ->build_state->onehop_tunnel) { + control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); } else { - extend_cell_t ec; - int len; - tor_assert(circ->cpath->state == CPATH_STATE_OPEN); - tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); - log_debug(LD_CIRC,"starting to send subsequent skin."); - hop = onion_next_hop_in_cpath(circ->cpath); - memset(&ec, 0, sizeof(ec)); - if (!hop) { - /* done building the circuit. whew. */ - 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(options)) { - 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; } - - if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { - log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); - return - END_CIRC_REASON_INTERNAL; + } + log_info(LD_CIRC,"circuit built!"); + circuit_reset_failure_count(0); + + if (circ->build_state->onehop_tunnel || circ->has_opened) { + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); + } + + pathbias_count_build_success(circ); + circuit_rep_hist_note_result(circ); + if (is_usable_for_streams) + circuit_has_opened(circ); /* do other actions as necessary */ + + if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { + const or_options_t *options = get_options(); + note_that_we_completed_a_circuit(); + /* FFFF Log a count of known routers here */ + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); + if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); } - - circuit_pick_extend_handshake(&ec.cell_type, - &ec.create_cell.cell_type, - &ec.create_cell.handshake_type, - hop->extend_info); - - tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); - ec.orport_ipv4.port = hop->extend_info->port; - tor_addr_make_unspec(&ec.orport_ipv6.addr); - memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); - - len = onion_skin_create(ec.create_cell.handshake_type, - hop->extend_info, - &hop->handshake_state, - ec.create_cell.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create failed."); - return - END_CIRC_REASON_INTERNAL; + control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); + clear_broken_connection_map(1); + if (server_mode(options) && !check_whether_orport_reachable(options)) { + inform_testing_reachability(); + consider_testing_reachability(1, 1); } - ec.create_cell.handshake_len = len; + } - log_info(LD_CIRC,"Sending extend relay cell."); - { - uint8_t command = 0; - uint16_t payload_len=0; - uint8_t payload[RELAY_PAYLOAD_SIZE]; - if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { - log_warn(LD_CIRC,"Couldn't format extend cell"); - return -END_CIRC_REASON_INTERNAL; - } + /* We're done with measurement circuits here. Just close them */ + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + } + return 0; +} + +/** + * 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; } @@ -1143,7 +1274,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. */ @@ -1153,6 +1284,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. */ @@ -1164,7 +1307,18 @@ 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)."); + return -1; + } + n_chan = channel_get_for_extend((const char*)ec.node_id, + &ec.ed_pubkey, &ec.orport_ipv4.addr, &msg, &should_launch); @@ -1176,8 +1330,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); @@ -1190,7 +1345,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); @@ -1217,40 +1373,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; } @@ -1316,7 +1509,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; } @@ -1374,7 +1567,7 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) log_info(LD_CIRC, "finished"); return 0; -#endif +#endif /* 0 */ } /** Given a response payload and keys, initialize, then send a created @@ -1383,12 +1576,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); @@ -1404,7 +1599,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; @@ -1418,12 +1613,12 @@ onionskin_answer(or_circuit_t *circ, memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); - circ->is_first_hop = (created_cell->cell_type == CELL_CREATED_FAST); + int used_create_fast = (created_cell->cell_type == CELL_CREATED_FAST); append_cell_to_circuit_queue(TO_CIRCUIT(circ), circ->p_chan, &cell, CELL_DIRECTION_IN, 0); log_debug(LD_CIRC,"Finished sending '%s' cell.", - circ->is_first_hop ? "created_fast" : "created"); + used_create_fast ? "created_fast" : "created"); /* Ignore the local bit when ExtendAllowPrivateAddresses is set: * it violates the assumption that private addresses are local. @@ -1441,13 +1636,98 @@ 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 +STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes) { int num_acceptable_routers; @@ -1455,11 +1735,7 @@ new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes) tor_assert(nodes); - routelen = DEFAULT_ROUTE_LEN; - if (exit_ei && - 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); @@ -1492,9 +1768,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; @@ -1643,15 +1919,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; } @@ -1766,9 +2043,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) } if (options->ExitNodes) { log_warn(LD_CIRC, - "No specified %sexit routers seem to be running: " + "No exits in ExitNodes%s seem to be running: " "can't choose an exit.", - options->ExcludeExitNodesUnion_ ? "non-excluded " : ""); + options->ExcludeExitNodesUnion_ ? + ", except possibly those excluded by your configuration, " : ""); } return NULL; } @@ -1783,7 +2061,6 @@ 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; @@ -1795,7 +2072,6 @@ 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, pref_addr, @@ -1829,7 +2105,7 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, return rp_node; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ /* Pick a Rendezvous Point for our HS circuits according to <b>flags</b>. */ static const node_t * @@ -1837,9 +2113,6 @@ 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; @@ -1868,7 +2141,7 @@ pick_rendezvous_node(router_crn_flags_t flags) "Unable to find a random rendezvous point that is reachable via " "a direct connection, falling back to a 3-hop path."); } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); } @@ -1885,7 +2158,8 @@ pick_rendezvous_node(router_crn_flags_t flags) */ static const node_t * choose_good_exit_server(uint8_t purpose, - int need_uptime, int need_capacity, int is_internal) + int need_uptime, int need_capacity, int is_internal, + int need_hs_v3) { const or_options_t *options = get_options(); router_crn_flags_t flags = CRN_NEED_DESC; @@ -1893,11 +2167,11 @@ choose_good_exit_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (need_capacity) flags |= CRN_NEED_CAPACITY; + if (need_hs_v3) + flags |= CRN_RENDEZVOUS_V3; switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: - 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 @@ -1994,9 +2268,15 @@ warn_if_last_router_excluded(origin_circuit_t *circ, /** Decide a suitable length for circ's cpath, and pick an exit * router (or use <b>exit</b> if provided). Store these in the - * cpath. Return 0 if ok, -1 if circuit should be closed. */ + * cpath. + * + * If <b>is_hs_v3_rp_circuit</b> is set, then this exit should be suitable to + * be used as an HS v3 rendezvous point. + * + * Return 0 if ok, -1 if circuit should be closed. */ static int -onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) +onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, + int is_hs_v3_rp_circuit) { cpath_build_state_t *state = circ->build_state; @@ -2020,13 +2300,15 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) } else { /* we have to decide one */ const node_t *node = choose_good_exit_server(circ->base_.purpose, state->need_uptime, - state->need_capacity, state->is_internal); + state->need_capacity, state->is_internal, + is_hs_v3_rp_circuit); if (!node) { log_warn(LD_CIRC,"Failed to choose an exit server"); return -1; } exit_ei = extend_info_from_node(node, 0); - tor_assert(exit_ei); + if (BUG(exit_ei == NULL)) + return -1; } state->chosen_exit = exit_ei; return 0; @@ -2082,8 +2364,8 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit_ei) /** 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; @@ -2094,10 +2376,6 @@ count_acceptable_nodes(smartlist_t *nodes) if (! node->is_running) // log_debug(LD_CIRC,"Nope, the directory says %d is not running.",i); continue; - /* XXX This clause makes us count incorrectly: if AllowInvalidRouters - * allows this node in some places, then we're getting an inaccurate - * count. For now, be conservative and don't count it. But later we - * should try to be smarter. */ if (! node->is_valid) // log_debug(LD_CIRC,"Nope, the directory says %d is not valid.",i); continue; @@ -2131,6 +2409,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) } } +#ifdef TOR_UNIT_TESTS + +/** Unittest helper function: Count number of hops in cpath linked list. */ +unsigned int +cpath_get_n_hops(crypt_path_t **head_ptr) +{ + unsigned int n_hops = 0; + crypt_path_t *tmp; + + if (!*head_ptr) { + return 0; + } + + tmp = *head_ptr; + do { + n_hops++; + tmp = tmp->next; + } while (tmp != *head_ptr); + + return n_hops; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + /** A helper function used by onion_extend_cpath(). Use <b>purpose</b> * and <b>state</b> and the cpath <b>head</b> (currently populated only * to length <b>cur_len</b> to decide a suitable middle hop for a @@ -2152,8 +2454,8 @@ choose_good_middle_server(uint8_t purpose, tor_assert(CIRCUIT_PURPOSE_MIN_ <= purpose && purpose <= CIRCUIT_PURPOSE_MAX_); - log_debug(LD_CIRC, "Contemplating intermediate hop %d: random choice.", - cur_len); + log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.", + cur_len+1); excluded = smartlist_new(); if ((r = build_state_get_exit_node(state))) { nodelist_add_node_and_family(excluded, r); @@ -2168,8 +2470,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; @@ -2180,11 +2480,13 @@ choose_good_middle_server(uint8_t purpose, * router (if we're an OR), and respect firewall settings; if we're * configured to use entry guards, return one. * - * If <b>state</b> is NULL, we're choosing a router to serve as an entry - * guard, not for any particular circuit. + * Set *<b>guard_state_out</b> to information about the guard that + * we're selecting, which we'll use later to remember whether the + * guard worked or not. */ 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; @@ -2195,11 +2497,17 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) CRN_DIRECT_CONN); const node_t *node; + /* Once we used this function to select a node to be a guard. We had + * 'state == NULL' be the signal for that. But we don't do that any more. + */ + tor_assert_nonfatal(state); + if (state && options->UseEntryGuards && (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { /* This request is for an entry server to use for a regular circuit, * 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(); @@ -2209,25 +2517,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) */ - /*XXXX++ use the using_as_guard flag to accomplish this.*/ - if (options->UseEntryGuards - && (!options->TestingTorNetwork || - smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards()) - )) { - 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) @@ -2235,8 +2524,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); @@ -2283,7 +2570,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 @@ -2291,24 +2579,24 @@ 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); } } if (!info) { - log_warn(LD_CIRC,"Failed to find node for hop %d of our path. Discarding " - "this circuit.", cur_len); + log_warn(LD_CIRC,"Failed to find node for hop #%d of our path. Discarding " + "this circuit.", cur_len+1); return -1; } - log_debug(LD_CIRC,"Chose router %s for hop %d (exit is %s)", + log_debug(LD_CIRC,"Chose router %s for hop #%d (exit is %s)", extend_info_describe(info), cur_len+1, build_state_get_exit_nickname(state)); @@ -2341,19 +2629,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; @@ -2403,20 +2695,35 @@ extend_info_from_node(const node_t *node, int for_direct_connect) 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; } @@ -2447,8 +2754,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 * @@ -2459,6 +2766,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. @@ -2551,3 +2869,26 @@ extend_info_has_preferred_onion_key(const extend_info_t* 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 1244601f71..b8a651e055 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,17 +41,20 @@ 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); @@ -59,20 +65,30 @@ 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); -#endif +unsigned int cpath_get_n_hops(crypt_path_t **head_ptr); + +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ -#endif +#endif /* defined(CIRCUITBUILD_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITBUILD_H) */ diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 977afca18d..d442887c9e 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,11 @@ #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_ident.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -34,6 +79,7 @@ #include "rephist.h" #include "routerlist.h" #include "routerset.h" +#include "channelpadding.h" #include "ht.h" @@ -42,18 +88,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); @@ -309,8 +360,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 +437,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 +449,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 +511,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 +566,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 +578,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 +587,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 +605,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 +816,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 +842,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; } @@ -773,19 +925,32 @@ circuit_clear_testing_cell_stats(circuit_t *circ) STATIC void circuit_free(circuit_t *circ) { + circid_t n_circ_id = 0; void *mem; size_t memlen; int should_free = 1; if (!circ) return; + /* We keep a copy of this so we can log its value before it gets unset. */ + n_circ_id = circ->n_circ_id; + circuit_clear_testing_cell_stats(circ); + /* Cleanup circuit from anything HS v3 related. We also do this when the + * circuit is closed. This is to avoid any code path that free registered + * circuits without closing them before. This needs to be done before the + * hs identifier is freed. */ + hs_circ_cleanup(circ); + if (CIRCUIT_IS_ORIGIN(circ)) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); mem = ocirc; 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,11 +958,22 @@ 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(ô->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); + /* Finally, free the identifier of the circuit and nullify it so multiple + * cleanup will work. */ + hs_ident_circuit_free(ocirc->hs_ident); + ocirc->hs_ident = NULL; + tor_free(ocirc->dest_address); if (ocirc->socks_username) { memwipe(ocirc->socks_username, 0x12, ocirc->socks_username_len); @@ -824,8 +1000,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); @@ -861,6 +1035,11 @@ circuit_free(circuit_t *circ) * "active" checks will be violated. */ cell_queue_clear(&circ->n_chan_cells); + log_info(LD_CIRC, "Circuit %u (id: %" PRIu32 ") has been freed.", + n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0); + if (should_free) { memwipe(mem, 0xAA, memlen); /* poison memory */ tor_free(mem); @@ -925,12 +1104,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); @@ -1262,7 +1447,7 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) smartlist_free(detached_2); } } -#endif +#endif /* defined(DEBUG_CIRCUIT_UNLINK_ALL) */ SMARTLIST_FOREACH_BEGIN(detached, circuit_t *, circ) { int mark = 0; @@ -1311,9 +1496,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 +1511,139 @@ 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) +/** 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 *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; + int idx = 0; + smartlist_t *lst = circuit_get_global_list(); - 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 (start) { + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; } - 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; - } + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); - return circ; + /* Ignore a marked for close circuit or purpose not matching a service + * intro point or if the state is not open. */ + if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN || + (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND && + circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) { + continue; + } + /* The purposes we are looking for are only for origin circuits so the + * following is valid. */ + return TO_ORIGIN_CIRCUIT(circ); + } + /* Not found. */ + return NULL; } -/** 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 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. + */ +origin_circuit_t * +circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, + const uint8_t *digest, uint8_t purpose) { - 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; + 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; - if (!map) { - log_warn(LD_BUG, "Tried to clear rend token on circuit, but found no map"); - return; - } + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); + origin_circuit_t *ocirc; - 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 (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; + } } - - tor_free(circ->rendinfo); /* Sets it to NULL too */ + return NULL; } -/** 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) +/** We might cannibalize this circuit: Return true if its last hop can be used + * as a v3 rendezvous point. */ +static int +circuit_can_be_cannibalized_for_v3_rp(const origin_circuit_t *circ) { - 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 (!circ->build_state) { + return 0; + } - if (token == NULL) { - /* We were only trying to remove this token, not set a new one. */ - return; + extend_info_t *chosen_exit = circ->build_state->chosen_exit; + if (BUG(!chosen_exit)) { + return 0; } - 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 */ - } + const node_t *rp_node = node_get_by_id(chosen_exit->identity_digest); + if (rp_node) { + if (node_supports_v3_rendezvous_point(rp_node)) { + return 1; } } - /* 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); -} - -/** Return the circuit waiting for intro cells of the given digest. - * Return NULL if no such circuit is found. - */ -or_circuit_t * -circuit_get_intro_point(const uint8_t *digest) -{ - return circuit_get_by_rend_token_and_purpose( - CIRCUIT_PURPOSE_INTRO_POINT, 0, - (const char *)digest); -} - -/** Set the rendezvous cookie of <b>circ</b> to <b>cookie</b>. If another - * circuit previously had that cookie, mark it. */ -void -circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie) -{ - circuit_set_rend_token(circ, 1, cookie); -} - -/** Set the intro point key digest of <b>circ</b> to <b>cookie</b>. If another - * circuit previously had that intro point digest, mark it. */ -void -circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest) -{ - circuit_set_rend_token(circ, 0, digest); + return 0; } /** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL, @@ -1539,6 +1656,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, @@ -1603,6 +1728,14 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, hop = hop->next; } while (hop != circ->cpath); } + + if ((flags & CIRCLAUNCH_IS_V3_RP) && + !circuit_can_be_cannibalized_for_v3_rp(circ)) { + log_debug(LD_GENERAL, "Skipping uncannibalizable circuit for v3 " + "rendezvous point."); + goto next; + } + if (!best || (best->build_state->need_uptime && !need_uptime)) best = circ; next: ; @@ -1613,6 +1746,37 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, return best; } +/** + * 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 @@ -1757,10 +1921,20 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, } } + /* Notify the HS subsystem that this circuit is closing. */ + hs_circ_cleanup(circ); + if (circuits_pending_close == NULL) circuits_pending_close = smartlist_new(); smartlist_add(circuits_pending_close, circ); + + log_info(LD_GENERAL, "Circuit %u (id: %" PRIu32 ") marked for close at " + "%s:%d (orig reason: %d, new reason: %d)", + circ->n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0, + file, line, orig_reason, reason); } /** Called immediately before freeing a marked circuit <b>circ</b> from @@ -1807,7 +1981,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 */ @@ -1818,9 +1993,14 @@ 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); } @@ -1829,11 +2009,11 @@ circuit_about_to_free(circuit_t *circ) int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); - tor_assert(ocirc->rend_data); - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { /* treat this like getting a nack from it */ log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", - safe_str_client(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, @@ -1846,11 +2026,12 @@ circuit_about_to_free(circuit_t *circ) reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", - 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, @@ -1945,10 +2126,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; @@ -2300,8 +2481,8 @@ assert_cpath_ok(const crypt_path_t *cp) /** Verify that circuit <b>c</b> has all of its invariants * correct. Trigger an assert if anything is invalid. */ -void -assert_circuit_ok(const circuit_t *c) +MOCK_IMPL(void, +assert_circuit_ok,(const circuit_t *c)) { edge_connection_t *conn; const or_circuit_t *or_circ = NULL; @@ -2343,7 +2524,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..4190c1f82e 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 */ /** @@ -13,8 +13,10 @@ #define TOR_CIRCUITLIST_H #include "testsupport.h" +#include "hs_ident.h" MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); +smartlist_t *circuit_get_global_origin_circuit_list(void); 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 +47,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); @@ -67,7 +68,7 @@ int circuit_count_pending_on_channel(channel_t *chan); circuit_mark_for_close_((c), (reason), __LINE__, SHORT_FILE__) void assert_cpath_layer_ok(const crypt_path_t *cp); -void assert_circuit_ok(const circuit_t *c); +MOCK_DECL(void, assert_circuit_ok,(const circuit_t *c)); void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); @@ -77,13 +78,15 @@ 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); STATIC uint32_t circuit_max_queued_data_age(const circuit_t *c, uint32_t now); STATIC uint32_t circuit_max_queued_cell_age(const circuit_t *c, uint32_t now); STATIC uint32_t circuit_max_queued_item_age(const circuit_t *c, uint32_t now); -#endif +#endif /* defined(CIRCUITLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITLIST_H) */ diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 036fb9570d..2cc0e8fc44 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.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 */ /** @@ -185,7 +185,7 @@ struct chanid_circid_muxinfo_t { circuitmux_assert_okay(cmux) #else #define circuitmux_assert_okay_paranoid(cmux) -#endif +#endif /* defined(CMUX_PARANOIA) */ /* * Static function declarations @@ -261,13 +261,11 @@ circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, if (circ->n_mux == cmux) { next_p = &(circ->next_active_on_n_chan); prev_p = &(circ->prev_active_on_n_chan); - direction = CELL_DIRECTION_OUT; } else { or_circ = TO_OR_CIRCUIT(circ); tor_assert(or_circ->p_mux == cmux); next_p = &(or_circ->next_active_on_p_chan); prev_p = &(or_circ->prev_active_on_p_chan); - direction = CELL_DIRECTION_IN; } } tor_assert(next_p); @@ -1646,7 +1644,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) circid_t circ_id; circuit_t *circ; or_circuit_t *or_circ; - unsigned int circ_is_active; circuit_t **next_p, **prev_p; unsigned int n_circuits, n_active_circuits, n_cells; @@ -1670,8 +1667,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) tor_assert(chan); circ = circuit_get_by_circid_channel_even_if_marked(circ_id, chan); tor_assert(circ); - /* Clear the circ_is_active bit to start */ - circ_is_active = 0; /* Assert that we know which direction this is going */ tor_assert((*i)->muxinfo.direction == CELL_DIRECTION_OUT || @@ -1698,7 +1693,7 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) * Should this circuit be active? I.e., does the mux know about > 0 * cells on it? */ - circ_is_active = ((*i)->muxinfo.cell_count > 0); + const int circ_is_active = ((*i)->muxinfo.cell_count > 0); /* It should be in the linked list iff it's active */ if (circ_is_active) { @@ -1750,7 +1745,6 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) circuit_t **next_p, **prev_p; channel_t *chan; unsigned int n_active_circuits = 0; - cell_direction_t direction; chanid_circid_muxinfo_t search, *hashent = NULL; tor_assert(cmux); @@ -1769,7 +1763,7 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) curr_or_circ = NULL; next_circ = NULL; next_p = prev_p = NULL; - direction = 0; + cell_direction_t direction; /* Figure out if this is n_mux or p_mux */ if (cmux == curr_circ->n_mux) { diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index f53abdd8c8..a95edb99d8 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 */ /** @@ -156,5 +156,5 @@ void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, MOCK_DECL(int, circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); -#endif /* TOR_CIRCUITMUX_H */ +#endif /* !defined(TOR_CIRCUITMUX_H) */ diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 5c2ebde73b..fde2d22a89 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.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 */ /** @@ -500,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 */ @@ -731,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_add(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -746,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_remove(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -760,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol) return smartlist_pqueue_pop(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index)); + offsetof(cell_ewma_t, heap_index)); } diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h index a7b8961ac6..8f4e57865e 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 */ /** @@ -20,5 +20,5 @@ unsigned int cell_ewma_get_tick(void); void cell_ewma_set_scale_factor(const or_options_t *options, const networkstatus_t *consensus); -#endif /* TOR_CIRCUITMUX_EWMA_H */ +#endif /* !defined(TOR_CIRCUITMUX_EWMA_H) */ diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 418acc0024..b8421a3c7e 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 */ /** @@ -60,7 +60,7 @@ static circuit_build_times_t circ_times; static int unit_tests = 0; #else #define unit_tests 0 -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Return a pointer to the data structure describing our current circuit * build time history and computations. */ @@ -105,13 +105,21 @@ get_circuit_build_timeout_ms(void) * 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 { - const or_options_t *options = get_options(); - 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 = !options->LearnCircuitBuildTimeout; int dirauth_disabled = options->AuthoritativeDir; @@ -140,7 +148,7 @@ circuit_build_times_disabled(void) "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 1; } else { #if 0 @@ -149,7 +157,7 @@ circuit_build_times_disabled(void) "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 0; } } @@ -417,15 +425,20 @@ 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) { if (num != cbt->liveness.num_recent_circs) { int8_t *recent_circs; - log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " - "circuits we must track to detect network failures from %d " - "to %d.", cbt->liveness.num_recent_circs, num); + if (cbt->liveness.num_recent_circs > 0) { + log_notice(LD_CIRC, "The Tor Directory Consensus has changed how " + "many circuits we must track to detect network failures " + "from %d to %d.", cbt->liveness.num_recent_circs, num); + } else { + log_notice(LD_CIRC, "Upon receiving a consensus directory, " + "re-enabling circuit-based network failure detection."); + } tor_assert(cbt->liveness.timeouts_after_firsthop || cbt->liveness.num_recent_circs == 0); @@ -493,14 +506,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 (!circuit_build_times_disabled() && + 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); @@ -542,7 +556,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 = @@ -599,7 +613,7 @@ circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n) "Rewound history by %d places. Current index: %d. " "Total: %d", n, cbt->build_times_idx, cbt->total_build_times); } -#endif +#endif /* 0 */ /** * Add a new build time value <b>time</b> to the set of build times. Time @@ -667,7 +681,7 @@ circuit_build_times_min(circuit_build_times_t *cbt) } return min_build_time; } -#endif +#endif /* 0 */ /** * Calculate and return a histogram for the set of build times. @@ -901,12 +915,12 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, int tot_values = 0; uint32_t loaded_cnt = 0, N = 0; config_line_t *line; - unsigned int i; + int i; build_time_t *loaded_times; int err = 0; circuit_build_times_init(cbt); - if (circuit_build_times_disabled()) { + if (circuit_build_times_disabled(get_options())) { return 0; } @@ -930,7 +944,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, uint32_t count, k; build_time_t ms; int ok; - ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, + ms = (build_time_t)tor_parse_ulong(ms_str, 10, 0, CBT_BUILD_TIME_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -940,7 +954,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, smartlist_free(args); break; } - count = (uint32_t)tor_parse_ulong(count_str, 0, 0, + count = (uint32_t)tor_parse_ulong(count_str, 10, 0, UINT32_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -951,8 +965,8 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, break; } - if (loaded_cnt+count+state->CircuitBuildAbandonedCount - > state->TotalBuildTimes) { + if (loaded_cnt+count+ (unsigned)state->CircuitBuildAbandonedCount + > (unsigned) state->TotalBuildTimes) { log_warn(LD_CIRC, "Too many build times in state file. " "Stopping short before %d", @@ -977,7 +991,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED; } - if (loaded_cnt != state->TotalBuildTimes) { + if (loaded_cnt != (unsigned)state->TotalBuildTimes) { log_warn(LD_CIRC, "Corrupt state file? Build times count mismatch. " "Read %d times, but file says %d", loaded_cnt, @@ -1156,7 +1170,7 @@ circuit_build_times_cdf(circuit_build_times_t *cbt, double x) tor_assert(0 <= ret && ret <= 1.0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1191,7 +1205,7 @@ circuit_build_times_generate_sample(circuit_build_times_t *cbt, tor_assert(ret > 0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1214,7 +1228,7 @@ circuit_build_times_initial_alpha(circuit_build_times_t *cbt, (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); tor_assert(cbt->alpha > 0); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** * Returns true if we need circuits to be built @@ -1431,7 +1445,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. " @@ -1507,7 +1521,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; @@ -1538,7 +1552,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; @@ -1612,7 +1626,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)) @@ -1673,7 +1687,7 @@ circuitbuild_running_unit_tests(void) { unit_tests = 1; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ void circuit_build_times_update_last_circ(circuit_build_times_t *cbt) diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index 72b160983f..92dc6405ba 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); @@ -51,7 +54,7 @@ STATIC void circuit_build_times_reset(circuit_build_times_t *cbt); /* Network liveness functions */ STATIC int circuit_build_times_network_check_changed( circuit_build_times_t *cbt); -#endif +#endif /* defined(CIRCUITSTATS_PRIVATE) */ #ifdef TOR_UNIT_TESTS build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, @@ -60,7 +63,7 @@ double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, double quantile, double time_ms); void circuitbuild_running_unit_tests(void); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /* Network liveness functions */ void circuit_build_times_network_is_live(circuit_build_times_t *cbt); @@ -92,7 +95,7 @@ struct circuit_build_times_s { /** How long we wait before actually closing the circuit. */ double close_ms; }; -#endif +#endif /* defined(CIRCUITSTATS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITSTATS_H) */ diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 84574cd5b9..7baa035696 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; } } @@ -289,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn, /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && origin_circ->hs_circ_has_timed_out) { + !must_be_open && origin_circ->hs_circ_has_timed_out && + !circ->marked_for_close) { intro_going_on_but_too_old = 1; continue; } @@ -361,7 +410,7 @@ circuit_conforms_to_options(const origin_circuit_t *circ, return 1; } -#endif +#endif /* 0 */ /** Close all circuits that start at us, aren't open, and were born * at least CircuitBuildTimeout seconds ago. @@ -494,8 +543,7 @@ circuit_expire_building(void) cutoff = begindir_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; - else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || - victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; @@ -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); } } @@ -588,7 +633,7 @@ circuit_expire_building(void) victim->n_circ_id, (int)(now - victim->timestamp_dirty)); } -#endif +#endif /* 0 */ /* if circ is !open, or if it's open but purpose is a non-finished * intro or rend, then mark it for close */ @@ -605,6 +650,7 @@ circuit_expire_building(void) * because that's set when they switch purposes */ if (TO_ORIGIN_CIRCUIT(victim)->rend_data || + TO_ORIGIN_CIRCUIT(victim)->hs_ident || victim->timestamp_dirty > cutoff.tv_sec) continue; break; @@ -614,12 +660,13 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: - /* We keep old introducing circuits around for - * a while in parallel, and they can end up "opened". - * We decide below if we're going to mark them timed - * out and eventually close them. - */ - break; + /* That purpose means that the intro point circuit has been opened + * succesfully but the INTRODUCE1 cell hasn't been sent yet because + * the client is waiting for the rendezvous point circuit to open. + * Keep this circuit open while waiting for the rendezvous circuit. + * We let the circuit idle timeout take care of cleaning this + * circuit if it never used. */ + continue; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: @@ -688,18 +735,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 @@ -713,8 +757,6 @@ circuit_expire_building(void) NULL) break; /* fallthrough! */ - case CIRCUIT_PURPOSE_C_INTRODUCING: - /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ @@ -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; @@ -941,7 +1001,7 @@ circuit_remove_handled_ports(smartlist_t *needed_ports) tor_assert(*port); if (circuit_stream_is_being_handled(NULL, *port, MIN_CIRCUITS_HANDLING_STREAM)) { -// log_debug(LD_CIRC,"Port %d is already being handled; removing.", port); + log_debug(LD_CIRC,"Port %d is already being handled; removing.", *port); smartlist_del(needed_ports, i--); tor_free(port); } else { @@ -978,6 +1038,10 @@ circuit_stream_is_being_handled(entry_connection_t *conn, continue; if (origin_circ->unusable_for_new_conns) continue; + if (origin_circ->isolation_values_set && + (conn == NULL || + !connection_edge_compatible_with_circuit(conn, origin_circ))) + continue; exitnode = build_state_get_exit_node(build_state); if (exitnode && (!need_uptime || build_state->need_uptime)) { @@ -1003,8 +1067,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 @@ -1016,25 +1210,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) @@ -1044,19 +1227,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); @@ -1064,12 +1242,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.", @@ -1078,18 +1254,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.", @@ -1098,34 +1272,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. */ @@ -1141,11 +1306,6 @@ circuit_build_needed_circs(time_t now) if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) connection_ap_rescan_and_attach_pending(); - /* make sure any hidden services have enough intro points - * HS intro point streams only require an internal circuit */ - if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) - rend_consider_services_intro_points(); - circuit_expire_old_circs_as_needed(now); if (!options->DisablePredictedCircuits) @@ -1181,7 +1341,7 @@ circuit_expire_old_circs_as_needed(time_t now) log_fn(LOG_INFO,"Creating a new testing circuit."); circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } -#endif +#endif /* 0 */ } } @@ -1227,8 +1387,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) * number of streams on the circuit associated with the rend service. */ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ->rend_data); - origin_circ->rend_data->nr_streams--; + hs_dec_rdv_stream_counter(origin_circ); } return; } @@ -1267,11 +1426,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. */ @@ -1281,21 +1435,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. */ @@ -1321,8 +1469,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) { @@ -1366,7 +1516,7 @@ circuit_expire_old_circuits_clientside(void) #define IDLE_ONE_HOP_CIRC_TIMEOUT 60 /** Find each non-origin circuit that has been unused for too long, - * has no streams on it, used a create_fast, and ends here: mark it + * has no streams on it, came from a client, and ends here: mark it * for close. */ void @@ -1382,9 +1532,9 @@ circuit_expire_old_circuits_serverside(time_t now) /* If the circuit has been idle for too long, and there are no streams * on it, and it ends here, and it used a create_fast, mark it for close. */ - if (or_circ->is_first_hop && !circ->n_chan && + if (or_circ->p_chan && channel_is_client(or_circ->p_chan) && + !circ->n_chan && !or_circ->n_streams && !or_circ->resolving_streams && - or_circ->p_chan && channel_when_last_xmit(or_circ->p_chan) <= cutoff) { log_info(LD_CIRC, "Closing circ_id %u (empty %d secs ago)", (unsigned)or_circ->p_circ_id, @@ -1490,7 +1640,7 @@ circuit_has_opened(origin_circuit_t *circ) switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - rend_client_rendcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); /* Start building an intro circ if we don't have one yet. */ connection_ap_attach_pending(1); /* This isn't a call to circuit_try_attaching_streams because a @@ -1502,7 +1652,7 @@ circuit_has_opened(origin_circuit_t *circ) * state. */ break; case CIRCUIT_PURPOSE_C_INTRODUCING: - rend_client_introcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_C_GENERAL: /* Tell any AP connections that have been waiting for a new @@ -1511,11 +1661,11 @@ circuit_has_opened(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at the service, waiting for introductions */ - rend_service_intro_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_S_CONNECT_REND: /* at the service, connecting to rend point */ - rend_service_rendezvous_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_TESTING: circuit_testing_opened(circ); @@ -1604,16 +1754,22 @@ circuit_build_failed(origin_circuit_t *circ) already_marked = 1; } log_info(LD_OR, - "Our circuit failed to get a response from the first hop " - "(%s). I'm going to try to rotate to a better connection.", + "Our circuit %u (id: %" PRIu32 ") failed to get a response " + "from the first hop (%s). I'm going to try to rotate to a " + "better connection.", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, channel_get_canonical_remote_descr(n_chan)); n_chan->is_bad_for_new_circs = 1; } else { log_info(LD_OR, - "Our circuit died before the first hop with no connection"); + "Our circuit %u (id: %" PRIu32 ") died before the first hop " + "with no connection", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier); } if (n_chan_id && !already_marked) { - 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); @@ -1663,7 +1819,7 @@ circuit_build_failed(origin_circuit_t *circ) "(%s hop failed).", escaped(build_state_get_exit_nickname(circ->build_state)), failed_at_last_hop?"last":"non-last"); - rend_service_relaunch_rendezvous(circ); + hs_circ_retry_service_rendezvous_point(circ); break; /* default: * This won't happen in normal operation, but might happen if the @@ -1750,8 +1906,9 @@ circuit_launch_by_extend_info(uint8_t purpose, uint8_t old_purpose = circ->base_.purpose; struct timeval old_timestamp_began = circ->base_.timestamp_began; - log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)", - build_state_get_exit_nickname(circ->build_state), purpose, + log_info(LD_CIRC, "Cannibalizing circ %u (id: %" PRIu32 ") for " + "purpose %d (%s)", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, purpose, circuit_purpose_to_string(purpose)); if ((purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || @@ -1874,16 +2031,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) @@ -1891,23 +2054,37 @@ 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. */ - if (entry_list_is_constrained(options) && - entries_known_but_down(options)) { + /* Retry some stuff that might help the connection work. */ + /* If we are configured with EntryNodes or UseBridges */ + if (entry_list_is_constrained(options)) { + /* Retry all our guards / bridges. + * guards_retry_optimistic() always returns true here. */ + int rv = guards_retry_optimistic(options); + tor_assert_nonfatal_once(rv); log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying known %s again.", @@ -1915,8 +2092,12 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "used client functionality lately" : "received a consensus with exits", options->UseBridges ? "bridges" : "entrynodes"); - entries_retry_all(options); - } else if (!options->UseBridges || any_bridge_descriptors_known()) { + } else { + /* Getting directory documents doesn't help much if we have a limited + * number of guards */ + tor_assert_nonfatal(!options->UseBridges); + tor_assert_nonfatal(!options->EntryNodes); + /* Retry our directory fetches, so we have a fresh set of guard info */ log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying directory fetches again.", @@ -1926,14 +2107,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; @@ -1954,7 +2137,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } else { /* XXXX Duplicates checks in connection_ap_handshake_attach_circuit: * refactor into a single function. */ - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, @@ -1974,16 +2157,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; @@ -1997,23 +2189,28 @@ 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) { + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); /* need to pick an intro point */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; - tor_assert(rend_data); - extend_info = rend_client_get_random_intro(rend_data); + extend_info = hs_client_get_random_intro_from_edge(edge_conn); if (!extend_info) { - log_info(LD_REND, - "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(rend_data->onion_address)); - rend_client_refetch_v2_renddesc(rend_data); + log_info(LD_REND, "No intro points: re-fetching service descriptor."); + if (edge_conn->rend_data) { + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + } else { + hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + } connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(rend_data->onion_address)); + (edge_conn->rend_data) ? + safe_str_client(rend_data_get_address(edge_conn->rend_data)) : + "service"); } /* If we have specified a particular exit node for our @@ -2023,7 +2220,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (conn->chosen_exit_name) { const node_t *r; int opt = conn->chosen_exit_optional; - r = node_get_by_nickname(conn->chosen_exit_name, 1); + r = node_get_by_nickname(conn->chosen_exit_name, 0); if (r && node_has_descriptor(r)) { /* We might want to connect to an IPv6 bridge for loading descriptors so we use the preferred address rather than @@ -2034,12 +2231,16 @@ 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; @@ -2054,10 +2255,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, @@ -2075,8 +2279,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) @@ -2085,24 +2291,39 @@ 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)) { want_onehop = 1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ + /* Determine what kind of a circuit to launch, and actually launch it. */ { int flags = CIRCLAUNCH_NEED_CAPACITY; if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL; if (need_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (need_internal) flags |= CIRCLAUNCH_IS_INTERNAL; + + /* If we are about to pick a v3 RP right now, make sure we pick a + * rendezvous point that supports the v3 protocol! */ + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED && + new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && + ENTRY_TO_EDGE_CONN(conn)->hs_ident) { + flags |= CIRCLAUNCH_IS_V3_RP; + log_info(LD_GENERAL, "Getting rendezvous circuit to v3 service!"); + } + circ = circuit_launch_by_extend_info(new_circ_purpose, extend_info, flags); } 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 @@ -2118,14 +2339,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* help predict this next time */ rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { - /* write the service_id into circ */ - circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + if (edge_conn->rend_data) { + /* write the service_id into circ */ + circ->rend_data = rend_data_dup(edge_conn->rend_data); + } else if (edge_conn->hs_ident) { + circ->hs_ident = + hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk, + HS_IDENT_CIRCUIT_INTRO); + } if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->base_.state == CIRCUIT_STATE_OPEN) circuit_has_opened(circ); } } } /* 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 @@ -2197,8 +2429,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, /* We are attaching a stream to a rendezvous circuit. That means * that an attempt to connect to a hidden service just * succeeded. Tell rendclient.c. */ - rend_client_note_connection_attempt_ended( - ENTRY_TO_EDGE_CONN(apconn)->rend_data); + hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn)); } if (cpath) { /* we were given one; use it */ @@ -2325,7 +2556,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) @@ -2340,12 +2573,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. */ @@ -2364,6 +2596,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; @@ -2374,12 +2607,14 @@ 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 this conn might no longer be needed. */ + * 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) { @@ -2397,8 +2632,11 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } + /* 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); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a @@ -2410,6 +2648,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; @@ -2422,6 +2661,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; @@ -2430,20 +2670,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) // XXXX++ 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 */ @@ -2461,9 +2706,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) tor_assert(rendcirc); /* one is already established, attach */ log_info(LD_REND, - "rend joined circ %u already here. attaching. " - "(stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "rend joined circ %u (id: %" PRIu32 ") already here. " + "Attaching. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); /* Mark rendezvous circuits as 'newly dirty' every time you use * them, since the process of rebuilding a rendezvous circ is so * expensive. There is a tradeoff between linkability and @@ -2496,9 +2742,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && (rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) { log_info(LD_REND, - "pending-join circ %u already here, with intro ack. " - "Stalling. (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "pending-join circ %u (id: %" PRIu32 ") already here, with " + "intro ack. Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); return 0; } @@ -2510,10 +2757,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ tor_assert(introcirc); - log_info(LD_REND, "Intro circ %u present and awaiting ack (rend %u). " - "Stalling. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, + log_info(LD_REND, "Intro circ %u (id: %" PRIu32 ") present and " + "awaiting ACK. Rend circuit %u (id: %" PRIu32 "). " + "Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } @@ -2523,19 +2773,26 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && introcirc && rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY) { log_info(LD_REND, - "ready rend circ %u already here (no intro-ack yet on " - "intro %u). (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, - (unsigned)introcirc->base_.n_circ_id, conn_age); + "ready rend circ %u (id: %" PRIu32 ") already here. No" + "intro-ack yet on intro %u (id: %" PRIu32 "). " + "(stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, conn_age); tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { - log_info(LD_REND,"found open intro circ %u (rend %u); sending " - "introduction. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - (unsigned)rendcirc->base_.n_circ_id, - conn_age); - switch (rend_client_send_introduction(introcirc, rendcirc)) { + int ret; + log_info(LD_REND, "Found open intro circ %u (id: %" PRIu32 "). " + "Rend circuit %u (id: %" PRIu32 "); Sending " + "introduction. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); + ret = hs_client_send_introduce1(introcirc, rendcirc); + switch (ret) { case 0: /* success */ rendcirc->base_.timestamp_dirty = time(NULL); introcirc->base_.timestamp_dirty = time(NULL); @@ -2557,10 +2814,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } - log_info(LD_REND, "Intro (%u) and rend (%u) circs are not both ready. " - "Stalling conn. (%d sec old)", - introcirc ? (unsigned)introcirc->base_.n_circ_id : 0, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, conn_age); + log_info(LD_REND, "Intro %u (id: %" PRIu32 ") and rend circuit %u " + "(id: %" PRIu32 ") circuits are not both ready. " + "Stalling conn. (%d sec old)", + introcirc ? (unsigned) TO_CIRCUIT(introcirc)->n_circ_id : 0, + introcirc ? introcirc->global_identifier : 0, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } } diff --git a/src/or/circuituse.h b/src/or/circuituse.h index 5973978c45..2b0f983f1a 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); @@ -43,6 +44,9 @@ void circuit_build_failed(origin_circuit_t *circ); /** Flag to set when the last hop of a circuit doesn't need to be an * exit node. */ #define CIRCLAUNCH_IS_INTERNAL (1<<3) +/** Flag to set when we are trying to launch a v3 rendezvous circuit. We need + * to apply some additional filters on the node picked. */ +#define CIRCLAUNCH_IS_V3_RP (1<<4) origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); @@ -59,5 +63,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); -#endif +#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 /* defined(TOR_UNIT_TESTS) */ + +#endif /* !defined(TOR_CIRCUITUSE_H) */ diff --git a/src/or/command.c b/src/or/command.c index 3f5386c5a3..185596a65a 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -129,7 +129,7 @@ command_time_process_cell(cell_t *cell, channel_t *chan, int *time, } *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** Process a <b>cell</b> that was just received on <b>chan</b>. Keep internal * statistics about how many of each cell we've processed so far @@ -166,7 +166,7 @@ command_process_cell(channel_t *chan, cell_t *cell) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ #ifdef KEEP_TIMING_STATS #define PROCESS_CELL(tp, cl, cn) STMT_BEGIN { \ @@ -174,9 +174,9 @@ command_process_cell(channel_t *chan, cell_t *cell) command_time_process_cell(cl, cn, & tp ## time , \ command_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) command_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ switch (cell->command) { case CELL_CREATE: @@ -339,10 +339,13 @@ 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); + } + 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); @@ -375,7 +378,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..c0d1996cbb 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 */ /** @@ -27,5 +27,5 @@ extern uint64_t stats_n_created_cells_processed; extern uint64_t stats_n_relay_cells_processed; extern uint64_t stats_n_destroy_cells_processed; -#endif +#endif /* !defined(TOR_COMMAND_H) */ diff --git a/src/or/config.c b/src/or/config.c index 810f1e9a7a..8f4b5ab5a1 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" @@ -19,10 +69,12 @@ #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" @@ -40,6 +92,7 @@ #include "relay.h" #include "rendclient.h" #include "rendservice.h" +#include "hs_config.h" #include "rephist.h" #include "router.h" #include "sandbox.h" @@ -50,7 +103,6 @@ #include "statefile.h" #include "transports.h" #include "ext_orport.h" -#include "torgzip.h" #ifdef _WIN32 #include <shlobj.h> #endif @@ -63,9 +115,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ /* Prefix used to indicate a Unix socket in a FooPort configuration. */ static const char unix_socket_prefix[] = "unix:"; @@ -121,21 +173,38 @@ static config_abbrev_t option_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_options_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_options_t); + /** An entry for config_vars: "The option <b>name</b> has type * CONFIG_TYPE_<b>conftype</b>, and corresponds to * or_options_t.<b>member</b>" */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \ + initvalue CONF_TEST_MEMBERS(or_options_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) /** An entry for config_vars: "The option <b>name</b> is obsolete." */ +#ifdef TOR_UNIT_TESTS +#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL, {.INT=NULL} } +#else #define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL } +#endif -#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,11 +215,11 @@ static config_var_t option_vars_[] = { VAR("AccountingRule", STRING, AccountingRule_option, "max"), V(AccountingStart, STRING, NULL), V(Address, STRING, NULL), - V(AllowDotExit, BOOL, "0"), - V(AllowInvalidNodes, CSV, "middle,rendezvous"), + OBSOLETE("AllowDotExit"), + 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"), @@ -163,14 +232,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,10 +252,13 @@ static config_var_t option_vars_[] = { V(BridgePassword, STRING, NULL), V(BridgeRecordUsageByCountry, BOOL, "1"), V(BridgeRelay, BOOL, "0"), + V(BridgeDistribution, STRING, NULL), V(CellStatistics, BOOL, "0"), + V(PaddingStatistics, BOOL, "1"), V(LearnCircuitBuildTimeout, BOOL, "1"), 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"), @@ -203,8 +275,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), @@ -220,9 +292,9 @@ static config_var_t option_vars_[] = { 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), @@ -240,8 +312,8 @@ static config_var_t option_vars_[] = { OBSOLETE("DisableIOCP"), OBSOLETE("DisableV2DirectoryInfo_"), OBSOLETE("DynamicDHGroups"), - VPORT(DNSPort, LINELIST, NULL), - V(DNSListenAddress, LINELIST, NULL), + VPORT(DNSPort), + OBSOLETE("DNSListenAddress"), /* DoS circuit creation options. */ V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"), V(DoSCircuitCreationMinConnections, UINT, "0"), @@ -265,7 +337,7 @@ 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"), @@ -273,17 +345,19 @@ static config_var_t option_vars_[] = { 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"), @@ -299,7 +373,7 @@ static config_var_t option_vars_[] = { SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"), V(GeoIPv6File, FILENAME, SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"), -#endif +#endif /* defined(_WIN32) */ OBSOLETE("Group"), V(GuardLifetime, INTERVAL, "0 minutes"), V(HardwareAccel, BOOL, "0"), @@ -318,16 +392,17 @@ 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), V(HTTPSProxyAuthenticator, STRING, NULL), + VPORT(HTTPTunnelPort), V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), @@ -350,27 +425,31 @@ 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(NoExec, BOOL, "0"), 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"), @@ -416,6 +495,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"), @@ -435,13 +516,16 @@ static config_var_t option_vars_[] = { V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSTestAddresses, CSV, "www.google.com,www.mit.edu,www.yahoo.com,www.slashdot.org"), - V(SchedulerLowWaterMark__, MEMUNIT, "100 MB"), - V(SchedulerHighWaterMark__, MEMUNIT, "101 MB"), - V(SchedulerMaxFlushCells__, UINT, "1000"), + OBSOLETE("SchedulerLowWaterMark__"), + OBSOLETE("SchedulerHighWaterMark__"), + OBSOLETE("SchedulerMaxFlushCells__"), + V(KISTSchedRunInterval, MSEC_INTERVAL, "0 msec"), + V(KISTSockBufSizeFactor, DOUBLE, "1.0"), + V(Schedulers, CSV, "KIST,KISTLite,Vanilla"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), - 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"), @@ -452,23 +536,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"), VAR("UseEntryGuards", BOOL, UseEntryGuards_option, "1"), - V(UseEntryGuardsAsDirGuards, BOOL, "1"), + OBSOLETE("UseEntryGuardsAsDirGuards"), V(UseGuardFraction, AUTOBOOL, "auto"), V(UseMicrodescriptors, AUTOBOOL, "auto"), OBSOLETE("UseNTorHandshake"), V(User, STRING, NULL), OBSOLETE("UserspaceIOCPBuffers"), V(AuthDirSharedRandomness, BOOL, "1"), + V(AuthDirTestEd25519LinkKeys, BOOL, "1"), OBSOLETE("V1AuthoritativeDirectory"), OBSOLETE("V2AuthoritativeDirectory"), VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"), @@ -510,11 +595,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. @@ -533,7 +620,16 @@ static config_var_t option_vars_[] = { * blackholed. Clients will try 3 directories simultaneously. * (Relays never use simultaneous connections.) */ V(ClientBootstrapConsensusMaxInProgressTries, UINT, "3"), - V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "1200, 900, 900, 3600"), + /* When a client has any running bridges, check each bridge occasionally, + * whether or not that bridge is actually up. */ + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, + "10800, 25200, 54000, 111600, 262800"), + /* When a client is just starting, or has no running bridges, check each + * bridge a few times quickly, and then try again later. These schedules + * are much longer than the other schedules, because we try each and every + * configured bridge with this schedule. */ + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, + "0, 30, 90, 600, 3600, 10800, 25200, 54000, 111600, 262800"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), V(TestingConsensusMaxDownloadTries, UINT, "8"), @@ -553,18 +649,16 @@ static config_var_t option_vars_[] = { V(TestingDirAuthVoteHSDirIsStrict, BOOL, "0"), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /** Override default values with these if the user sets the TestingTorNetwork * option. */ static const config_var_t testing_tor_network_defaults[] = { - V(ServerDNSAllowBrokenConfig, BOOL, "1"), V(DirAllowPrivateAddresses, BOOL, "1"), V(EnforceDistinctSubnets, BOOL, "0"), V(AssumeReachable, BOOL, "1"), 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, @@ -573,7 +667,7 @@ static const config_var_t testing_tor_network_defaults[] = { "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), V(ClientBootstrapConsensusMaxDownloadTries, UINT, "80"), V(ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "80"), - V(ClientDNSRejectInternalAddresses, BOOL,"0"), // deprecated in 0.2.9.2-alpha + V(ClientDNSRejectInternalAddresses, BOOL,"0"), V(ClientRejectInternalAddresses, BOOL, "0"), V(CountPrivateBandwidth, BOOL, "1"), V(ExitPolicyRejectPrivate, BOOL, "0"), @@ -584,7 +678,6 @@ static const config_var_t testing_tor_network_defaults[] = { V(TestingV3AuthInitialVotingInterval, INTERVAL, "150 seconds"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "20 seconds"), V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"), - V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), @@ -596,7 +689,9 @@ static const config_var_t testing_tor_network_defaults[] = { "15, 20, 30, 60"), V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " "15, 20, 30, 60"), - V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "10, 30, 60"), + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), V(TestingConsensusMaxDownloadTries, UINT, "80"), @@ -609,7 +704,7 @@ static const config_var_t testing_tor_network_defaults[] = { VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), V(RendPostPeriod, INTERVAL, "2 minutes"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; #undef VAR @@ -617,39 +712,19 @@ static const config_var_t testing_tor_network_defaults[] = { #undef OBSOLETE static const config_deprecation_t option_deprecation_notes_[] = { - /* Deprecated since 0.2.9.2-alpha... */ - { "AllowDotExit", "Unrestricted use of the .exit notation can be used for " - "a wide variety of application-level attacks." }, - { "AllowInvalidNodes", "There is no reason to enable this option; at best " - "it will make you easier to track." }, - { "AllowSingleHopCircuits", "Almost no relays actually allow single-hop " - "exits, making this option pointless." }, - { "AllowSingleHopExits", "Turning this on will make your relay easier " - "to abuse." }, - { "ClientDNSRejectInternalAddresses", "Turning this on makes your client " - "easier to fingerprint, and may open you to esoteric attacks." }, - { "ExcludeSingleHopRelays", "Turning it on makes your client easier to " - "fingerprint." }, - { "FastFirstHopPK", "Changing this option does not make your client more " - "secure, but does make it easier to fingerprint." }, - { "CloseHSClientCircuitsImmediatelyOnTimeout", "This option makes your " - "client easier to fingerprint." }, - { "CloseHSServiceRendCircuitsImmediatelyOnTimeout", "This option makes " - "your hidden services easier to fingerprint." }, - { "WarnUnsafeSocks", "Changing this option makes it easier for you " - "to accidentally lose your anonymity by leaking DNS information" }, - { "TLSECGroup", "The default is a nice secure choice; the other option " - "is less secure." }, - { "ControlListenAddress", "Use ControlPort instead." }, - { "DirListenAddress", "Use DirPort instead, possibly with the " - "NoAdvertise sub-option" }, - { "DNSListenAddress", "Use DNSPort instead." }, - { "SocksListenAddress", "Use SocksPort instead." }, - { "TransListenAddress", "Use TransPort instead." }, - { "NATDListenAddress", "Use NATDPort instead." }, - { "ORListenAddress", "Use ORPort instead, possibly with the " - "NoAdvertise sub-option" }, - /* End of options deprecated since 0.2.9.2-alpha. */ + /* Deprecated since 0.3.2.0-alpha. */ + { "HTTPProxy", "It only applies to direct unencrypted HTTP connections " + "to your directory server, which your Tor probably wasn't using." }, + { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy " + "which should be used with HTTPSProxyAuthenticator." }, + /* End of options deprecated since 0.3.2.1-alpha */ + + /* Options deprecated since 0.3.2.2-alpha */ + { "ReachableDirAddresses", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + { "ClientPreferIPv6DirPort", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + /* End of options deprecated since 0.3.2.2-alpha. */ { NULL, NULL } }; @@ -665,7 +740,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, @@ -674,7 +751,6 @@ static int parse_ports(or_options_t *options, int validate_only, static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); - static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); @@ -700,7 +776,7 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val, STATIC config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, - STRUCT_OFFSET(or_options_t, magic_), + offsetof(or_options_t, magic_), option_abbrevs_, option_deprecation_notes_, option_vars_, @@ -731,6 +807,9 @@ static int have_parsed_cmdline = 0; static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ static smartlist_t *configured_ports = NULL; +/** True iff we're currently validating options, and any calls to + * get_options() are likely to be bugs. */ +static int in_option_validation = 0; /** Return the contents of our frontpage string, or NULL if not configured. */ MOCK_IMPL(const char*, @@ -744,6 +823,7 @@ MOCK_IMPL(or_options_t *, get_options_mutable, (void)) { tor_assert(global_options); + tor_assert_nonfatal(! in_option_validation); return global_options; } @@ -802,7 +882,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); } } @@ -870,9 +950,14 @@ or_options_free(or_options_t *options) rs, routerset_free(rs)); smartlist_free(options->NodeFamilySets); } + if (options->SchedulerTypes_) { + SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i)); + smartlist_free(options->SchedulerTypes_); + } tor_free(options->BridgePassword_AuthDigest_); tor_free(options->command_arg); tor_free(options->master_key_fname); + config_free_lines(options->MyFamily); config_free(&options_format, options); } @@ -964,6 +1049,23 @@ escaped_safe_str(const char *address) return escaped(address); } +/** + * The severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. + * + * We keep this outside the options, in case somebody needs to use + * LOG_PROTOCOL_WARN while an option transition is happening. + */ +static int protocol_warning_severity_level = LOG_WARN; + +/** Return the severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. */ +int +get_protocol_warning_severity_level(void) +{ + return protocol_warning_severity_level; +} + /** List of default directory authorities */ static const char *default_authorities[] = { @@ -1170,13 +1272,13 @@ options_act_reversible(const or_options_t *old_options, char **msg) "on this OS/with this build."); goto rollback; } -#else +#else /* !(!defined(HAVE_SYS_UN_H)) */ if (options->ControlSocketsGroupWritable && !options->ControlSocket) { *msg = tor_strdup("Setting ControlSocketGroupWritable without setting" "a ControlSocket makes no sense."); goto rollback; } -#endif +#endif /* !defined(HAVE_SYS_UN_H) */ if (running_tor) { int n_ports=0; @@ -1253,7 +1355,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) goto rollback; } } -#endif +#endif /* defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) */ /* Attempt to lock all current and future memory with mlockall() only once */ if (options->DisableAllSwap) { @@ -1305,7 +1407,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) options->DataDirectory, strerror(errno)); } } -#endif +#endif /* !defined(_WIN32) */ /* Bail out at this point if we're not going to be a client or server: * we don't run Tor itself. */ @@ -1482,21 +1584,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 @@ -1518,6 +1631,12 @@ 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); + + if (options->NoExec || options->Sandbox) { + tor_disable_spawning_background_processes(); + } /* disable ptrace and later, other basic debugging techniques */ { @@ -1553,6 +1672,11 @@ options_act(const or_options_t *old_options) return -1; } + if (options->ProtocolWarnings) + protocol_warning_severity_level = LOG_WARN; + else + protocol_warning_severity_level = LOG_INFO; + if (consider_adding_dir_servers(options, old_options) < 0) return -1; @@ -1570,7 +1694,7 @@ options_act(const or_options_t *old_options) return -1; } /* LCOV_EXCL_STOP */ -#else +#else /* !(defined(ENABLE_TOR2WEB_MODE)) */ if (options->Tor2webMode) { log_err(LD_CONFIG, "This copy of Tor was not compiled to run in " "'tor2web mode'. It cannot be run with the Tor2webMode torrc " @@ -1578,7 +1702,7 @@ options_act(const or_options_t *old_options) "--enable-tor2web-mode option."); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* If we are a bridge with a pluggable transport proxy but no Extended ORPort, inform the user that they are missing out. */ @@ -1607,7 +1731,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; @@ -1687,19 +1811,16 @@ 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 - * will log a warning */ + * will log a warning and exit. */ if (options->PidFile && !sandbox_is_active()) { - write_pidfile(options->PidFile); + if (write_pidfile(options->PidFile) < 0) { + log_err(LD_CONFIG, "Unable to write PIDFile %s", + escaped(options->PidFile)); + return -1; + } } /* Register addressmap directives */ @@ -1714,6 +1835,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; @@ -1722,16 +1852,14 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); /* reload keys as needed for rendezvous services. */ - if (rend_service_load_all_keys(NULL)<0) { + if (hs_service_load_all_keys() < 0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } - /* Set up scheduler thresholds */ - scheduler_set_watermarks((uint32_t)options->SchedulerLowWaterMark__, - (uint32_t)options->SchedulerHighWaterMark__, - (options->SchedulerMaxFlushCells__ > 0) ? - options->SchedulerMaxFlushCells__ : 1000); + /* Inform the scheduler subsystem that a configuration changed happened. It + * might be a change of scheduler or parameter. */ + scheduler_conf_changed(); /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { @@ -1794,6 +1922,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 && @@ -1810,6 +1939,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; @@ -1840,7 +1979,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. */ @@ -1868,9 +2007,16 @@ options_act(const or_options_t *old_options) if (transition_affects_workers) { log_info(LD_GENERAL, "Worker-related options changed. Rotating workers."); + const int server_mode_turned_on = + server_mode(options) && !server_mode(old_options); + const int dir_server_mode_turned_on = + dir_server_mode(options) && !dir_server_mode(old_options); - if (server_mode(options) && !server_mode(old_options)) { + if (server_mode_turned_on || dir_server_mode_turned_on) { cpu_init(); + } + + if (server_mode_turned_on) { ip_address_changed(0); if (have_completed_a_circuit() || !any_predicted_circuits(time(NULL))) inform_testing_reachability(); @@ -1891,6 +2037,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 || @@ -1905,7 +2053,6 @@ options_act(const or_options_t *old_options) options->CellStatistics = 0; options->EntryStatistics = 0; options->ConnDirectionStatistics = 0; - options->HiddenServiceStatistics = 0; options->ExitPortStatistics = 0; } @@ -1991,13 +2138,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. */ @@ -2065,6 +2205,7 @@ static const struct { { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT }, + { "--key-expiration", ARGUMENT_OPTIONAL }, { "--newpass", TAKES_NO_ARGUMENT }, { "--no-passphrase", TAKES_NO_ARGUMENT }, { "--passphrase-fd", ARGUMENT_NECESSARY }, @@ -2225,24 +2366,36 @@ options_trial_assign(config_line_t *list, unsigned flags, char **msg) return r; } - if (options_validate(get_options_mutable(), trial_options, + setopt_err_t rv; + or_options_t *cur_options = get_options_mutable(); + + in_option_validation = 1; + + if (options_validate(cur_options, trial_options, global_default_options, 1, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + goto done; } - if (options_transition_allowed(get_options(), trial_options, msg) < 0) { + if (options_transition_allowed(cur_options, trial_options, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_TRANSITION; + rv = SETOPT_ERR_TRANSITION; + goto done; } + in_option_validation = 0; if (set_options(trial_options, msg)<0) { or_options_free(trial_options); - return SETOPT_ERR_SETTING; + rv = SETOPT_ERR_SETTING; + goto done; } /* we liked it. put it in place. */ - return SETOPT_OK; + rv = SETOPT_OK; + done: + in_option_validation = 0; + return rv; } /** Print a usage message for tor. */ @@ -2252,7 +2405,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"); @@ -2333,8 +2486,8 @@ using_default_dir_authorities(const or_options_t *options) * 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. @@ -2714,13 +2867,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. */ @@ -2734,10 +2887,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 @@ -2749,8 +2898,11 @@ static int options_validate_cb(void *old_options, void *options, void *default_options, int from_setconf, char **msg) { - return options_validate(old_options, options, default_options, + in_option_validation = 1; + int rv = options_validate(old_options, options, default_options, from_setconf, msg); + in_option_validation = 0; + return rv; } #define REJECT(arg) \ @@ -2761,15 +2913,17 @@ options_validate_cb(void *old_options, void *options, void *default_options, #else #define COMPLAIN(args, ...) \ STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END -#endif +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ /** Log a warning message iff <b>filepath</b> is not absolute. * Warning message must contain option name <b>option</b> and * an absolute path that <b>filepath</b> will resolve to. * * In case <b>filepath</b> is absolute, do nothing. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_if_option_path_is_relative(const char *option, char *filepath) { @@ -2778,39 +2932,100 @@ warn_if_option_path_is_relative(const char *option, COMPLAIN("Path for %s (%s) is relative and will resolve to %s." " Is this what you wanted?", option, filepath, abs_path); tor_free(abs_path); + return 1; } + return 0; } /** Scan <b>options</b> for occurances of relative file/directory * path and log a warning whenever it is found. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_about_relative_paths(or_options_t *options) { tor_assert(options); + int n = 0; - warn_if_option_path_is_relative("CookieAuthFile", - options->CookieAuthFile); - warn_if_option_path_is_relative("ExtORPortCookieAuthFile", - options->ExtORPortCookieAuthFile); - warn_if_option_path_is_relative("DirPortFrontPage", - options->DirPortFrontPage); - warn_if_option_path_is_relative("V3BandwidthsFile", - options->V3BandwidthsFile); - warn_if_option_path_is_relative("ControlPortWriteToFile", - options->ControlPortWriteToFile); - warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); - warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); - warn_if_option_path_is_relative("Log",options->DebugLogFile); - warn_if_option_path_is_relative("AccelDir",options->AccelDir); - warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); - warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("CookieAuthFile", + options->CookieAuthFile); + n += warn_if_option_path_is_relative("ExtORPortCookieAuthFile", + options->ExtORPortCookieAuthFile); + n += warn_if_option_path_is_relative("DirPortFrontPage", + options->DirPortFrontPage); + n += warn_if_option_path_is_relative("V3BandwidthsFile", + options->V3BandwidthsFile); + n += warn_if_option_path_is_relative("ControlPortWriteToFile", + options->ControlPortWriteToFile); + n += warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); + n += warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); + n += warn_if_option_path_is_relative("Log",options->DebugLogFile); + n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); + n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); + n += warn_if_option_path_is_relative("PidFile",options->PidFile); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { if (!strcasecmp(hs_line->key, "HiddenServiceDir")) - warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + n += warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + } + return n != 0; +} + +/* Validate options related to the scheduler. From the Schedulers list, the + * SchedulerTypes_ list is created with int values so once we select the + * scheduler, which can happen anytime at runtime, we don't have to parse + * strings and thus be quick. + * + * Return 0 on success else -1 and msg is set with an error message. */ +static int +options_validate_scheduler(or_options_t *options, char **msg) +{ + tor_assert(options); + tor_assert(msg); + + if (!options->Schedulers || smartlist_len(options->Schedulers) == 0) { + REJECT("Empty Schedulers list. Either remove the option so the defaults " + "can be used or set at least one value."); + } + /* Ok, we do have scheduler types, validate them. */ + options->SchedulerTypes_ = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(options->Schedulers, const char *, type) { + int *sched_type; + if (!strcasecmp("KISTLite", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST_LITE; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("KIST", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("Vanilla", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_VANILLA; + smartlist_add(options->SchedulerTypes_, sched_type); + } else { + tor_asprintf(msg, "Unknown type %s in option Schedulers. " + "Possible values are KIST, KISTLite and Vanilla.", + escaped(type)); + return -1; + } + } SMARTLIST_FOREACH_END(type); + + if (options->KISTSockBufSizeFactor < 0) { + REJECT("KISTSockBufSizeFactor must be at least 0"); } + + /* Don't need to validate that the Interval is less than anything because + * zero is valid and all negative values are valid. */ + if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) { + tor_asprintf(msg, "KISTSchedRunInterval must not be more than %d (ms)", + KIST_SCHED_RUN_INTERVAL_MAX); + return -1; + } + + return 0; } /* Validate options related to single onion services. @@ -2841,13 +3056,13 @@ options_validate_single_onion(or_options_t *options, char **msg) const int client_port_set = (options->SocksPort_set || options->TransPort_set || options->NATDPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); if (rend_service_non_anonymous_mode_enabled(options) && client_port_set && !options->Tor2webMode) { REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as " "an anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or " - "HiddenServiceNonAnonymousMode to 0, or use the non-anonymous " - "Tor2webMode."); + "revert HiddenServiceNonAnonymousMode to 0."); } /* If you run a hidden service in non-anonymous mode, the hidden service @@ -2857,12 +3072,12 @@ options_validate_single_onion(or_options_t *options, char **msg) 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."); + "HiddenServiceNonAnonymousMode."); } if (rend_service_allow_non_anonymous_connection(options) && options->UseEntryGuards) { - /* Single Onion services only use entry guards when uploading descriptors, + /* 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 @@ -2904,13 +3119,21 @@ 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 whenn it's incompatible with other options, + * 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 (warn_about_relative_paths(options) && options->RunAsDaemon) { + REJECT("You have specified at least one relative path (see above) " + "with the RunAsDaemon option. RunAsDaemon is not compatible " + "with relative paths."); + } if (server_mode(options) && (!strcmpstart(uname, "Windows 95") || @@ -2922,10 +3145,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; @@ -2991,13 +3210,13 @@ 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."); #else options->TransProxyType_parsed = TPT_PF_DIVERT; -#endif +#endif /* !defined(OpenBSD) && !defined( DARWIN ) */ } else if (!strcasecmp(options->TransProxyType, "tproxy")) { #if !defined(__linux__) REJECT("TPROXY is a Linux-specific feature."); @@ -3011,22 +3230,20 @@ options_validate(or_options_t *old_options, or_options_t *options, "and OS X/Darwin-specific feature."); #else options->TransProxyType_parsed = TPT_IPFW; -#endif +#endif /* !defined(KERNEL_MAY_SUPPORT_IPFW) */ } else { REJECT("Unrecognized value for TransProxyType"); } 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 +#else /* !(defined(USE_TRANSPARENT)) */ if (options->TransPort_set) - REJECT("TransPort and TransListenAddress are disabled " - "in this build."); -#endif + REJECT("TransPort is disabled in this build."); +#endif /* defined(USE_TRANSPARENT) */ if (options->TokenBucketRefillInterval <= 0 || options->TokenBucketRefillInterval > 1000) { @@ -3039,17 +3256,6 @@ options_validate(or_options_t *old_options, or_options_t *options, routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes); } - if (options->SchedulerLowWaterMark__ == 0 || - options->SchedulerLowWaterMark__ > UINT32_MAX) { - log_warn(LD_GENERAL, "Bad SchedulerLowWaterMark__ option"); - return -1; - } else if (options->SchedulerHighWaterMark__ <= - options->SchedulerLowWaterMark__ || - options->SchedulerHighWaterMark__ > UINT32_MAX) { - log_warn(LD_GENERAL, "Bad SchedulerHighWaterMark option"); - return -1; - } - if (options->NodeFamilies) { options->NodeFamilySets = smartlist_new(); for (cl = options->NodeFamilies; cl; cl = cl->next) { @@ -3062,15 +3268,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 " @@ -3111,7 +3308,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; @@ -3265,23 +3462,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 " @@ -3293,33 +3473,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 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; @@ -3355,6 +3518,23 @@ 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->BridgeDistribution) { + if (!options->BridgeRelay) { + REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!"); + } + if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) { + REJECT("Invalid BridgeDistribution value."); + } + } + if (options->MinUptimeHidServDirectoryV2 < 0) { log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at " "least 0 seconds. Changing to 0."); @@ -3367,7 +3547,7 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->RendPostPeriod < min_rendpostperiod) { log_warn(LD_CONFIG, "RendPostPeriod option is too short; " "raising to %d seconds.", min_rendpostperiod); - options->RendPostPeriod = min_rendpostperiod;; + options->RendPostPeriod = min_rendpostperiod; } if (options->RendPostPeriod > MAX_DIR_PERIOD) { @@ -3376,17 +3556,17 @@ 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; + 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 @@ -3401,7 +3581,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "Tor2WebMode is enabled; disabling UseEntryGuards."); options->UseEntryGuards = 0; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ if (options->Tor2webRendezvousPoints && !options->Tor2webMode) { REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode."); @@ -3436,6 +3616,20 @@ 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, @@ -3461,7 +3655,7 @@ options_validate(or_options_t *old_options, or_options_t *options, int severity = LOG_NOTICE; /* Be a little quieter if we've deliberately disabled * LearnCircuitBuildTimeout. */ - if (circuit_build_times_disabled()) { + if (circuit_build_times_disabled_(options, 1)) { severity = LOG_INFO; } log_fn(severity, LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but " @@ -3533,6 +3727,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("PortForwarding is not compatible with Sandbox; at most one can " "be set"); } + if (options->PortForwarding && options->NoExec) { + COMPLAIN("Both PortForwarding and NoExec are set; PortForwarding will " + "be ignored."); + } if (ensure_bandwidth_cap(&options->BandwidthRate, "BandwidthRate", msg) < 0) @@ -3791,13 +3989,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(); @@ -3970,7 +4169,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. */ @@ -3994,13 +4193,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 && \ @@ -4021,6 +4213,7 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); CHECK_DEFAULT(TestingBridgeDownloadSchedule); + CHECK_DEFAULT(TestingBridgeBootstrapDownloadSchedule); CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); CHECK_DEFAULT(TestingDirConnectionMaxStall); CHECK_DEFAULT(TestingConsensusMaxDownloadTries); @@ -4034,6 +4227,10 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingLinkKeySlop); #undef CHECK_DEFAULT + if (!options->ClientDNSRejectInternalAddresses && + !(options->DirAuthorities || + (options->AlternateDirAuthority && options->AlternateBridgeAuthority))) + REJECT("ClientDNSRejectInternalAddresses used for default network."); if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2) REJECT("SigningKeyLifetime is too short."); if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2) @@ -4197,6 +4394,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid " "combination."); + if (options_validate_scheduler(options, msg) < 0) { + return -1; + } + return 0; } @@ -4231,7 +4432,7 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess) #else /* (presumably) 32-bit system. Let's hope for 1 GB. */ result = ONE_GIGABYTE; -#endif +#endif /* SIZEOF_VOID_P >= 8 */ } else { /* We detected it, so let's pick 3/4 of the total RAM as our limit. */ const uint64_t avail = (ram / 4) * 3; @@ -4266,8 +4467,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 @@ -4287,21 +4488,21 @@ 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 = tor_strdup("Running a Bridge with less than " - STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is " - "not recommended."); + STRINGIFY(DIRCACHE_MIN_MEM_MB) " MB of memory is not " + "recommended."); } else { *msg = tor_strdup("Being a directory cache (default) with less than " - STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is " - "not recommended and may consume most of the available " + 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) { + 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."); @@ -4416,6 +4617,12 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->NoExec && !new_val->NoExec) { + *msg = tor_strdup("While Tor is running, disabling " + "NoExec is not allowed."); + return -1; + } + if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ @@ -4426,7 +4633,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); @@ -4466,6 +4672,7 @@ options_transition_affects_workers(const or_options_t *old_options, old_options->SafeLogging_ != new_options->SafeLogging_ || old_options->ClientOnly != new_options->ClientOnly || server_mode(old_options) != server_mode(new_options) || + dir_server_mode(old_options) != dir_server_mode(new_options) || public_server_mode(old_options) != public_server_mode(new_options) || !config_lines_eq(old_options->Logs, new_options->Logs) || old_options->LogMessageDomains != new_options->LogMessageDomains) @@ -4507,7 +4714,9 @@ 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) || + !opt_streq(old_options->BridgeDistribution, + new_options->BridgeDistribution) || + !config_lines_eq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || old_options->AccountingRule != new_options->AccountingRule || @@ -4560,7 +4769,7 @@ get_windows_conf_root(void) path[sizeof(path)-1] = '\0'; #else strlcpy(path,tpath,sizeof(path)); -#endif +#endif /* defined(UNICODE) */ /* Now we need to free the memory that the path-idl was stored in. In * typical Windows fashion, we can't just call 'free()' on it. */ @@ -4576,7 +4785,7 @@ get_windows_conf_root(void) is_set = 1; return path; } -#endif +#endif /* defined(_WIN32) */ /** Return the default location for our torrc file (if <b>defaults_file</b> is * false), or for the torrc-defaults file (if <b>defaults_file</b> is true). */ @@ -4600,30 +4809,39 @@ get_default_conf_file(int defaults_file) } #else return defaults_file ? CONFDIR "/torrc-defaults" : CONFDIR "/torrc"; -#endif +#endif /* defined(DISABLE_SYSTEM_TORRC) || ... */ } -/** Verify whether lst is a 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] != '$') { @@ -4632,36 +4850,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. @@ -4733,9 +4960,9 @@ find_torrc_filename(config_line_t *cmd_arg, } else { fname = dflt ? tor_strdup(dflt) : NULL; } -#else +#else /* !(!defined(_WIN32)) */ fname = dflt ? tor_strdup(dflt) : NULL; -#endif +#endif /* !defined(_WIN32) */ } } return fname; @@ -4863,9 +5090,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); } @@ -4874,6 +5113,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")) { @@ -5005,6 +5247,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, config_line_t *cl; 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 @@ -5021,7 +5264,8 @@ options_init_from_string(const char *cf_defaults, const char *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; @@ -5049,6 +5293,8 @@ options_init_from_string(const char *cf_defaults, const char *cf, 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. */ @@ -5092,7 +5338,8 @@ options_init_from_string(const char *cf_defaults, const char *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; @@ -5115,6 +5362,9 @@ options_init_from_string(const char *cf_defaults, const char *cf, } } + newoptions->IncludeUsed = cf_has_include; + in_option_validation = 1; + /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, 0, msg) < 0) { @@ -5126,17 +5376,20 @@ options_init_from_string(const char *cf_defaults, const char *cf, err = SETOPT_ERR_TRANSITION; goto err; } + in_option_validation = 0; if (set_options(newoptions, msg)) { err = SETOPT_ERR_SETTING; goto err; /* frees and replaces old options */ } + or_options_free(global_default_options); global_default_options = newdefaultoptions; return SETOPT_OK; err: + in_option_validation = 0; or_options_free(newoptions); or_options_free(newdefaultoptions); if (*msg) { @@ -5218,35 +5471,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; } @@ -5313,7 +5566,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") || @@ -5340,7 +5593,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options, } #else log_warn(LD_CONFIG, "Syslog is not supported on this system. Sorry."); -#endif +#endif /* defined(HAVE_SYSLOG_H) */ goto cleanup; } @@ -5657,6 +5910,15 @@ parse_transport_line(const or_options_t *options, goto err; } + if (is_managed && options->NoExec) { + log_warn(LD_CONFIG, + "Managed proxies are not compatible with NoExec mode; ignoring." + "(%sTransportPlugin line was %s)", + server ? "Server" : "Client", escaped(line)); + r = 0; + goto done; + } + if (is_managed) { /* managed */ @@ -5848,7 +6110,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); @@ -5927,6 +6189,8 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, dirinfo_type_t type = 0; double weight = 1.0; + memset(v3_digest, 0, sizeof(v3_digest)); + items = smartlist_new(); smartlist_split_string(items, line, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); @@ -6173,9 +6437,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; } @@ -6190,8 +6454,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) @@ -6348,16 +6613,60 @@ warn_client_dns_cache(const char *option, int disabling) } /** + * Validate the configured bridge distribution method from a BridgeDistribution + * config line. + * + * The input <b>bd</b>, is a string taken from the BridgeDistribution config + * line (if present). If the option wasn't set, return 0 immediately. The + * BridgeDistribution option is then validated. Currently valid, recognised + * options are: + * + * - "none" + * - "any" + * - "https" + * - "email" + * - "moat" + * - "hyphae" + * + * If the option string is unrecognised, a warning will be logged and 0 is + * returned. If the option string contains an invalid character, -1 is + * returned. + **/ +STATIC int +check_bridge_distribution_setting(const char *bd) +{ + if (bd == NULL) + return 0; + + const char *RECOGNIZED[] = { + "none", "any", "https", "email", "moat", "hyphae" + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) { + if (!strcmp(bd, RECOGNIZED[i])) + return 0; + } + + const char *cp = bd; + // Method = (KeywordChar | "_") + + while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_') + ++cp; + + if (*cp == 0) { + log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll " + "assume you know what you are doing...", escaped(bd)); + return 0; // we reached the end of the string; all is well + } else { + return -1; // we found a bad character in the string. + } +} + +/** * Parse port configuration for a single port type. * - * Read entries of the "FooPort" type from the list <b>ports</b>, 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>. * @@ -6371,9 +6680,6 @@ warn_client_dns_cache(const char *option, int disabling) * 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. @@ -6388,7 +6694,6 @@ warn_client_dns_cache(const char *option, int disabling) 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, @@ -6405,90 +6710,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 defaults are already set by port_cfg_new */ - 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); @@ -6526,9 +6753,9 @@ parse_port_config(smartlist_t *out, /* 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, dns_request = 1, + ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1, onion_traffic = 1, - cache_ipv4 = 1, use_cached_ipv4 = 0, + cache_ipv4 = 0, use_cached_ipv4 = 0, cache_ipv6 = 0, use_cached_ipv6 = 0, prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0, relax_dirmode_check = 0, @@ -6627,7 +6854,7 @@ parse_port_config(smartlist_t *out, } else if (!strcasecmp(elt, "AllAddrs")) { all_addrs = 1; -#endif +#endif /* 0 */ } else if (!strcasecmp(elt, "IPv4Only")) { bind_ipv4_only = 1; } else if (!strcasecmp(elt, "IPv6Only")) { @@ -6889,6 +7116,7 @@ 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) { @@ -6959,36 +7187,45 @@ 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; + } + if (parse_port_config(ports, + options->HTTPTunnelPort_lines, + "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER, + "127.0.0.1", 0, + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { + *msg = tor_strdup("Invalid HTTPTunnelPort configuration"); goto err; } { @@ -7004,16 +7241,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) { @@ -7023,15 +7258,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) { @@ -7039,11 +7274,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; } } @@ -7070,6 +7305,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1); options->NATDPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1); + options->HTTPTunnelPort_set = + !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1); /* Use options->ControlSocket to test if a control socket is set */ options->ControlPort_set = !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0); @@ -7400,7 +7637,7 @@ normalize_data_directory(or_options_t *options) strlcpy(p,get_windows_conf_root(),MAX_PATH); options->DataDirectory = p; return 0; -#else +#else /* !(defined(_WIN32)) */ const char *d = options->DataDirectory; if (!d) d = "~/.tor"; @@ -7426,7 +7663,7 @@ normalize_data_directory(or_options_t *options) options->DataDirectory = fn; } return 0; -#endif +#endif /* defined(_WIN32) */ } /** Check and normalize the value of options->DataDirectory; return 0 if it @@ -7764,7 +8001,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: @@ -7846,60 +8083,98 @@ 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)); + } + + if (parse_outbound_address_lines(options->OutboundBindAddress, + OUTBOUND_ADDR_EXIT_AND_OR, options, + validate_only, msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressOR, + OUTBOUND_ADDR_OR, options, validate_only, + msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressExit, + OUTBOUND_ADDR_EXIT, options, validate_only, + msg) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + /** Load one of the geoip files, <a>family</a> determining which * one. <a>default_fname</a> is used if on Windows and * <a>fname</a> equals "<default>". */ @@ -7919,10 +8194,10 @@ config_load_geoip_file_(sa_family_t family, } geoip_load_file(family, fname); tor_free(free_fname); -#else +#else /* !(defined(_WIN32)) */ (void)default_fname; geoip_load_file(family, fname); -#endif +#endif /* defined(_WIN32) */ } /** Load geoip files for IPv4 and IPv6 if <a>options</a> and @@ -7997,9 +8272,9 @@ init_cookie_authentication(const char *fname, const char *header, log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); } } -#else +#else /* !(!defined(_WIN32)) */ (void) group_readable; -#endif +#endif /* !defined(_WIN32) */ /* Success! */ log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname)); diff --git a/src/or/config.h b/src/or/config.h index 6645532514..efdd8c59b0 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)); @@ -27,6 +31,7 @@ const char *safe_str_client(const char *address); const char *safe_str(const char *address); const char *escaped_safe_str_client(const char *address); const char *escaped_safe_str(const char *address); +int get_protocol_warning_severity_level(void); const char *get_version(void); const char *get_short_version(void); setopt_err_t options_trial_assign(config_line_t *list, unsigned flags, @@ -157,7 +162,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) @@ -193,13 +198,14 @@ 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, int defaultport, const unsigned flags); -#endif -#endif +STATIC int check_bridge_distribution_setting(const char *bd); +#endif /* defined(CONFIG_PRIVATE) */ + +#endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/or/confparse.c b/src/or/confparse.c index efcf4f981e..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,120 +78,6 @@ 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) -{ - const config_line_t *cl; - for (cl = lines; cl; cl = cl->next) { - if (!strcmp(cl->key, key)) - return cl; - } - return NULL; -} - -/** Helper: parse the config string and strdup into key/value - * strings. Set *result to the list, or NULL if parsing the string - * failed. Return 0 on success, -1 on failure. Warn and ignore any - * 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); - } - } 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); - } -} - /** 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. */ @@ -620,23 +517,6 @@ 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 @@ -753,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); @@ -1002,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. */ @@ -1148,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); @@ -1213,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 }, @@ -1333,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; @@ -1352,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 8d915d266b..6f0b3b325c 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 @@ -40,6 +40,36 @@ typedef enum config_type_t { CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */ } config_type_t; +#ifdef TOR_UNIT_TESTS +/** + * Union used when building in test mode typechecking the members of a type + * used with confparse.c. See CONF_CHECK_VAR_TYPE for a description of how + * it is used. */ +typedef union { + char **STRING; + char **FILENAME; + int *UINT; /* yes, really: Even though the confparse type is called + * "UINT", it still uses the C int type -- it just enforces that + * the values are in range [0,INT_MAX]. + */ + int *INT; + int *PORT; + int *INTERVAL; + int *MSEC_INTERVAL; + uint64_t *MEMUNIT; + double *DOUBLE; + int *BOOL; + int *AUTOBOOL; + time_t *ISOTIME; + smartlist_t **CSV; + smartlist_t **CSV_INTERVAL; + config_line_t **LINELIST; + config_line_t **LINELIST_S; + config_line_t **LINELIST_V; + routerset_t **ROUTERSET; +} confparse_dummy_values_t; +#endif + /** An abbreviation for a configuration option allowed on the command line. */ typedef struct config_abbrev_t { const char *abbreviated; @@ -64,8 +94,52 @@ typedef struct config_var_t { * value. */ off_t var_offset; /**< Offset of the corresponding member of or_options_t. */ const char *initvalue; /**< String (or null) describing initial value. */ + +#ifdef TOR_UNIT_TESTS + /** Used for compiler-magic to typecheck the corresponding field in the + * corresponding struct. Only used in unit test mode, at compile-time. */ + confparse_dummy_values_t var_ptr_dummy; +#endif } config_var_t; +/* Macros to define extra members inside config_var_t fields, and at the + * end of a list of them. + */ +#ifdef TOR_UNIT_TESTS +/* This is a somewhat magic type-checking macro for users of confparse.c. + * It initializes a union member "confparse_dummy_values_t.conftype" with + * the address of a static member "tp_dummy.member". This + * will give a compiler warning unless the member field is of the correct + * type. + * + * (This warning is mandatory, because a type mismatch here violates the type + * compatibility constraint for simple assignment, and requires a diagnostic, + * according to the C spec.) + * + * For example, suppose you say: + * "CONF_CHECK_VAR_TYPE(or_options_t, STRING, Address)". + * Then this macro will evaluate to: + * { .STRING = &or_options_t_dummy.Address } + * And since confparse_dummy_values_t.STRING has type "char **", that + * expression will create a warning unless or_options_t.Address also + * has type "char *". + */ +#define CONF_CHECK_VAR_TYPE(tp, conftype, member) \ + { . conftype = &tp ## _dummy . member } +#define CONF_TEST_MEMBERS(tp, conftype, member) \ + , CONF_CHECK_VAR_TYPE(tp, conftype, member) +#define END_OF_CONFIG_VARS \ + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL, { .INT=NULL } } +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + static tp tp ## _dummy +#else +#define CONF_TEST_MEMBERS(tp, conftype, member) +#define END_OF_CONFIG_VARS { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } +/* Repeatedly declarable incomplete struct to absorb redundant semicolons */ +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + struct tor_semicolon_eater +#endif + /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation * of arguments. */ @@ -103,14 +177,7 @@ typedef struct config_format_t { #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); @@ -131,13 +198,10 @@ 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 +#endif /* !defined(TOR_CONFPARSE_H) */ diff --git a/src/or/connection.c b/src/or/connection.c index 791fd95c27..ed8de05d78 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -34,10 +34,10 @@ * they become able to read or write register the fact with the event main * loop by calling connection_watch_events(), connection_start_reading(), or * connection_start_writing(). When they no longer want to read or write, - * they call connection_stop_reading() or connection_start_writing(). + * they call connection_stop_reading() or connection_stop_writing(). * * To queue data to be written on a connection, call - * connection_write_to_buf(). When data arrives, the + * connection_buf_add(). When data arrives, the * connection_process_inbuf() callback is invoked, which dispatches to a * type-specific function (such as connection_edge_process_inbuf() for * example). Connection types that need notice of when data has been written @@ -56,7 +56,9 @@ #define CONNECTION_PRIVATE #include "or.h" +#include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" /* * Define this so we get channel internal functions, since we're implementing * part of a subclass (channel_tls_t). @@ -83,7 +85,11 @@ #include "ext_orport.h" #include "geoip.h" #include "main.h" +#include "hs_common.h" +#include "hs_ident.h" #include "nodelist.h" +#include "proto_http.h" +#include "proto_socks.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -122,8 +128,9 @@ static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); static int connection_reached_eof(connection_t *conn); -static int connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, - int *socket_error); +static int connection_buf_read_from_socket(connection_t *conn, + ssize_t *max_to_read, + int *socket_error); static int connection_process_inbuf(connection_t *conn, int package_partial); static void client_check_address_changed(tor_socket_t sock); static void set_constrained_socket_buffers(tor_socket_t sock, int size); @@ -133,6 +140,8 @@ 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. @@ -154,7 +163,8 @@ static smartlist_t *outgoing_addrs = NULL; case CONN_TYPE_CONTROL_LISTENER: \ case CONN_TYPE_AP_TRANS_LISTENER: \ case CONN_TYPE_AP_NATD_LISTENER: \ - case CONN_TYPE_AP_DNS_LISTENER + case CONN_TYPE_AP_DNS_LISTENER: \ + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER /**************************************************************/ @@ -181,6 +191,7 @@ conn_type_to_string(int type) case CONN_TYPE_CONTROL: return "Control"; case CONN_TYPE_EXT_OR: return "Extended OR"; case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -602,6 +613,7 @@ connection_free_(connection_t *conn) } if (CONN_IS_EDGE(conn)) { rend_data_free(TO_EDGE_CONN(conn)->rend_data); + hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident); } if (conn->type == CONN_TYPE_CONTROL) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); @@ -625,14 +637,20 @@ connection_free_(connection_t *conn) 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)) { @@ -644,7 +662,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)); @@ -675,7 +693,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) { @@ -687,7 +705,7 @@ connection_free,(connection_t *conn)) connection_ap_warn_and_unmark_if_pending_circ(TO_ENTRY_CONN(conn), "connection_free"); } -#endif +#endif /* 1 */ /* Notify the circuit creation DoS mitigation subsystem that an OR client * connection has been closed. And only do that if we track it. */ @@ -806,7 +824,7 @@ connection_mark_for_close_(connection_t *conn, int line, const char *file) * CONN_TYPE_OR checks; this should be called when you either are sure that * if this is an or_connection_t the controlling channel has been notified * (e.g. with connection_or_notify_error()), or you actually are the - * connection_or_close_for_error() or connection_or_close_normally function. + * connection_or_close_for_error() or connection_or_close_normally() function. * For all other cases, use connection_mark_and_flush() instead, which * checks for or_connection_t properly, instead. See below. */ @@ -922,7 +940,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, *len_out = sizeof(struct sockaddr_un); return sockaddr; } -#else +#else /* !(defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN)) */ static struct sockaddr * create_unix_sockaddr(const char *listenaddress, char **readable_address, socklen_t *len_out) @@ -935,7 +953,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, tor_fragile_assert(); return NULL; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN) */ /** Warn that an accept or a connect has failed because we're running out of * TCP sockets we can use on current system. Rate-limit these warnings so @@ -1050,7 +1068,7 @@ check_location_for_unix_socket(const or_options_t *options, const char *path, tor_free(p); return r; } -#endif +#endif /* defined(HAVE_SYS_UN_H) */ /** Tell the TCP stack that it shouldn't wait for a long time after * <b>sock</b> has closed before reusing its port. Return 0 on success, @@ -1073,7 +1091,7 @@ make_socket_reuseable(tor_socket_t sock) return -1; } return 0; -#endif +#endif /* defined(_WIN32) */ } #ifdef _WIN32 @@ -1094,12 +1112,12 @@ make_win32_socket_exclusive(tor_socket_t sock) return -1; } return 0; -#else +#else /* !(defined(SO_EXCLUSIVEADDRUSE)) */ (void) sock; return 0; -#endif +#endif /* defined(SO_EXCLUSIVEADDRUSE) */ } -#endif +#endif /* defined(_WIN32) */ /** Max backlog to pass to listen. We start at */ static int listen_limit = INT_MAX; @@ -1189,7 +1207,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, conn_type_to_string(type), tor_socket_strerror(errno)); } -#endif +#endif /* defined(_WIN32) */ #if defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) if (options->TransProxyType_parsed == TPT_TPROXY && @@ -1206,7 +1224,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(e), extra); } } -#endif +#endif /* defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) */ #ifdef IPV6_V6ONLY if (listensockaddr->sa_family == AF_INET6) { @@ -1221,7 +1239,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, /* Keep going; probably not harmful. */ } } -#endif +#endif /* defined(IPV6_V6ONLY) */ if (bind(s,listensockaddr,socklen) < 0) { const char *helpfulhint = ""; @@ -1324,7 +1342,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, goto err; } } -#endif +#endif /* defined(HAVE_PWD_H) */ { unsigned mode; @@ -1355,7 +1373,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(tor_socket_errno(s))); goto err; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) */ } else { log_err(LD_BUG, "Got unexpected address family %d.", listensockaddr->sa_family); @@ -1710,6 +1728,8 @@ connection_init_accepted_conn(connection_t *conn, TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_NATD_WAIT; break; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: + conn->state = AP_CONN_STATE_HTTP_CONNECT_WAIT; } break; case CONN_TYPE_DIR: @@ -1784,7 +1804,7 @@ connection_connect_sockaddr,(connection_t *conn, /* * We've got the socket open; give the OOS handler a chance to check - * against configuured maximum socket number, but tell it no exhaustion + * against configured maximum socket number, but tell it no exhaustion * failure. */ connection_check_oos(get_n_open_sockets(), 0); @@ -1903,6 +1923,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>. @@ -1924,26 +1993,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, @@ -2110,7 +2168,7 @@ connection_proxy_connect(connection_t *conn, int type) fmt_addrport(&conn->addr, conn->port)); } - connection_write_to_buf(buf, strlen(buf), conn); + connection_buf_add(buf, strlen(buf), conn); conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; break; } @@ -2176,7 +2234,7 @@ connection_proxy_connect(connection_t *conn, int type) buf[8] = 0; /* no userid */ } - connection_write_to_buf((char *)buf, buf_size, conn); + connection_buf_add((char *)buf, buf_size, conn); tor_free(buf); conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; @@ -2207,7 +2265,7 @@ connection_proxy_connect(connection_t *conn, int type) conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; } - connection_write_to_buf((char *)buf, 2 + buf[1], conn); + connection_buf_add((char *)buf, 2 + buf[1], conn); break; } @@ -2313,7 +2371,7 @@ connection_send_socks5_connect(connection_t *conn) memcpy(buf + 20, &port, 2); } - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } @@ -2439,7 +2497,7 @@ connection_read_proxy_handshake(connection_t *conn) if (socks_args_string) tor_free(socks_args_string); - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; ret = 0; @@ -2588,7 +2646,7 @@ retry_listener_ports(smartlist_t *old_conns, if (port->is_unix_addr && !geteuid() && (options->User) && strcmp(options->User, "root")) continue; -#endif +#endif /* !defined(_WIN32) */ if (port->is_unix_addr) { listensockaddr = (struct sockaddr *) @@ -3024,9 +3082,11 @@ connection_buckets_decrement(connection_t *conn, time_t now, (unsigned long)num_read, (unsigned long)num_written, conn_type_to_string(conn->type), conn_state_to_string(conn->type, conn->state)); - if (num_written >= INT_MAX) num_written = 1; - if (num_read >= INT_MAX) num_read = 1; - tor_fragile_assert(); + tor_assert_nonfatal_unreached(); + if (num_written >= INT_MAX) + num_written = 1; + if (num_read >= INT_MAX) + num_read = 1; } record_num_bytes_transferred_impl(conn, now, num_read, num_written); @@ -3338,7 +3398,7 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) /** Read bytes from conn-\>s and process them. * - * It calls connection_read_to_buf() to bring in any new bytes, + * It calls connection_buf_read_from_socket() to bring in any new bytes, * and then calls connection_process_inbuf() to process them. * * Mark the connection and return -1 if you want to close it, else @@ -3364,6 +3424,7 @@ connection_handle_read_impl(connection_t *conn) case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -3380,7 +3441,7 @@ connection_handle_read_impl(connection_t *conn) tor_assert(!conn->marked_for_close); before = buf_datalen(conn->inbuf); - if (connection_read_to_buf(conn, &max_to_read, &socket_error) < 0) { + if (connection_buf_read_from_socket(conn, &max_to_read, &socket_error) < 0) { /* There's a read error; kill the connection.*/ if (conn->type == CONN_TYPE_OR) { connection_or_notify_error(TO_OR_CONN(conn), @@ -3477,7 +3538,7 @@ connection_handle_read(connection_t *conn) * Return -1 if we want to break conn, else return 0. */ static int -connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, +connection_buf_read_from_socket(connection_t *conn, ssize_t *max_to_read, int *socket_error) { int result; @@ -3518,7 +3579,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, initial_size = buf_datalen(conn->inbuf); /* else open, or closing */ - result = read_to_buf_tls(or_conn->tls, at_most, conn->inbuf); + result = buf_read_from_tls(conn->inbuf, or_conn->tls, at_most); if (TOR_TLS_IS_ERROR(result) || result == TOR_TLS_CLOSE) or_conn->tls_error = result; else @@ -3554,10 +3615,8 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, connection_start_reading(conn); } /* we're already reading, one hopes */ - result = 0; break; case TOR_TLS_DONE: /* no data read, so nothing to process */ - result = 0; break; /* so we call bucket_decrement below */ default: break; @@ -3567,7 +3626,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* If we have any pending bytes, we read them now. This *can* * take us over our read allotment, but really we shouldn't be * believing that SSL bytes are the same as TCP bytes anyway. */ - int r2 = read_to_buf_tls(or_conn->tls, pending, conn->inbuf); + int r2 = buf_read_from_tls(conn->inbuf, or_conn->tls, pending); if (BUG(r2<0)) { log_warn(LD_BUG, "apparently, reading pending bytes can fail."); return -1; @@ -3579,7 +3638,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, result, (long)n_read, (long)n_written); } else if (conn->linked) { if (conn->linked_conn) { - result = move_buf_to_buf(conn->inbuf, conn->linked_conn->outbuf, + result = buf_move_to_buf(conn->inbuf, conn->linked_conn->outbuf, &conn->linked_conn->outbuf_flushlen); } else { result = 0; @@ -3597,8 +3656,10 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* !connection_speaks_cells, !conn->linked_conn. */ int reached_eof = 0; CONN_LOG_PROTECT(conn, - result = read_to_buf(conn->s, at_most, conn->inbuf, &reached_eof, - socket_error)); + result = buf_read_from_socket(conn->inbuf, conn->s, + at_most, + &reached_eof, + socket_error)); if (reached_eof) conn->inbuf_reached_eof = 1; @@ -3667,17 +3728,17 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /** A pass-through to fetch_from_buf. */ int -connection_fetch_from_buf(char *string, size_t len, connection_t *conn) +connection_buf_get_bytes(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + return buf_get_bytes(conn->inbuf, string, len); } -/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +/** As buf_get_line(), but read from a connection's input buffer. */ int -connection_fetch_from_buf_line(connection_t *conn, char *data, +connection_buf_get_line(connection_t *conn, char *data, size_t *data_len) { - return fetch_from_buf_line(conn->inbuf, data, data_len); + return buf_get_line(conn->inbuf, data, data_len); } /** As fetch_from_buf_http, but fetches from a connection's input buffer_t as @@ -3714,7 +3775,7 @@ connection_outbuf_too_full(connection_t *conn) * * This function gets called either from conn_write_callback() in main.c * when libevent tells us that conn wants to write, or below - * from connection_write_to_buf() when an entire TLS record is ready. + * from connection_buf_add() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf * or flush_buf_tls appropriately. If it succeeds and there are no more @@ -3825,7 +3886,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* else open, or closing */ initial_size = buf_datalen(conn->outbuf); - result = flush_buf_tls(or_conn->tls, conn->outbuf, + result = buf_flush_to_tls(conn->outbuf, or_conn->tls, max_to_write, &conn->outbuf_flushlen); /* If we just flushed the last bytes, tell the channel on the @@ -3888,8 +3949,8 @@ connection_handle_write_impl(connection_t *conn, int force) result = (int)(initial_size-buf_datalen(conn->outbuf)); } else { CONN_LOG_PROTECT(conn, - result = flush_buf(conn->s, conn->outbuf, - max_to_write, &conn->outbuf_flushlen)); + result = buf_flush_to_socket(conn->outbuf, conn->s, + max_to_write, &conn->outbuf_flushlen)); if (result < 0) { if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); @@ -4011,10 +4072,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, @@ -4033,11 +4090,11 @@ connection_write_to_buf_impl_,(const char *string, size_t len, if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; - CONN_LOG_PROTECT(conn, r = write_to_buf_zlib(conn->outbuf, - dir_conn->zlib_state, - string, len, done)); + CONN_LOG_PROTECT(conn, r = buf_add_compress(conn->outbuf, + dir_conn->compress_state, + string, len, done)); } else { - CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf)); + CONN_LOG_PROTECT(conn, r = buf_add(conn->outbuf, string, len)); } if (r < 0) { if (CONN_IS_EDGE(conn)) { @@ -4076,6 +4133,38 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +#define CONN_GET_ALL_TEMPLATE(var, test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *ret_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \ + if (var && (test) && !var->marked_for_close) \ + smartlist_add(ret_conns, var); \ + } SMARTLIST_FOREACH_END(var); \ + return ret_conns; \ + STMT_END + +/* Return a list of connections that aren't close and matches the given type + * and state. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_state(int type, int state) +{ + CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state)); +} + +/* Return a list of connections that aren't close and matches the given type + * and purpose. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_purpose(int type, int purpose) +{ + CONN_GET_ALL_TEMPLATE(conn, + (conn->type == type && conn->purpose == purpose)); +} + /** Return a connection_t * from get_connection_array() that satisfies test on * var, and that is not marked for close. */ #define CONN_GET_TEMPLATE(var, test) \ @@ -4149,12 +4238,12 @@ 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))) )); } @@ -4260,6 +4349,7 @@ connection_is_listener(connection_t *conn) conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || conn->type == CONN_TYPE_AP_NATD_LISTENER || + conn->type == CONN_TYPE_AP_HTTP_CONNECT_LISTENER || conn->type == CONN_TYPE_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -4927,9 +5017,9 @@ assert_connection_ok(connection_t *conn, time_t now) /* buffers */ if (conn->inbuf) - assert_buf_ok(conn->inbuf); + buf_assert_ok(conn->inbuf); if (conn->outbuf) - assert_buf_ok(conn->outbuf); + buf_assert_ok(conn->outbuf); if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); @@ -5151,7 +5241,7 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, const char *source) { char dbuf[64]; - char *ext_source = NULL; + char *ext_source = NULL, *warn = NULL; format_time_interval(dbuf, sizeof(dbuf), apparent_skew); if (conn) tor_asprintf(&ext_source, "%s:%s:%d", source, conn->address, conn->port); @@ -5165,9 +5255,14 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, apparent_skew > 0 ? "ahead" : "behind", dbuf, apparent_skew > 0 ? "behind" : "ahead", (!conn || trusted) ? "" : ", or they are sending us the wrong time"); - if (trusted) + if (trusted) { control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=%s", apparent_skew, ext_source); + tor_asprintf(&warn, "Clock skew %ld in %s from %s", apparent_skew, + received, source); + control_event_bootstrap_problem(warn, "CLOCK_SKEW", conn, 1); + } + tor_free(warn); tor_free(ext_source); } diff --git a/src/or/connection.h b/src/or/connection.h index d25e002fa4..4a5bd6971b 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 */ /** @@ -123,8 +123,8 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); -int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); -int connection_fetch_from_buf_line(connection_t *conn, char *data, +int connection_buf_get_bytes(char *string, size_t len, connection_t *conn); +int connection_buf_get_line(connection_t *conn, char *data, size_t *data_len); int connection_fetch_from_buf_http(connection_t *conn, char **headers_out, size_t max_headerlen, @@ -139,19 +139,19 @@ int connection_flush(connection_t *conn); MOCK_DECL(void, connection_write_to_buf_impl_, (const char *string, size_t len, connection_t *conn, int zlib)); /* DOCDOC connection_write_to_buf */ -static void connection_write_to_buf(const char *string, size_t len, +static void connection_buf_add(const char *string, size_t len, connection_t *conn); -/* DOCDOC connection_write_to_buf_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_buf_add_compress(const char *string, size_t len, + dir_connection_t *conn, int done); static inline void -connection_write_to_buf(const char *string, size_t len, connection_t *conn) +connection_buf_add(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } static inline void -connection_write_to_buf_zlib(const char *string, size_t len, - dir_connection_t *conn, int done) +connection_buf_add_compress(const char *string, size_t len, + dir_connection_t *conn, int done) { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); } @@ -182,6 +182,8 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +smartlist_t *connection_list_by_type_state(int type, int state); +smartlist_t *connection_list_by_type_purpose(int type, int purpose); smartlist_t *connection_dir_list_by_purpose_and_resource( int purpose, const char *resource); @@ -284,7 +286,7 @@ MOCK_DECL(STATIC int,connection_connect_sockaddr, MOCK_DECL(STATIC void, kill_conn_list_for_oos, (smartlist_t *conns)); MOCK_DECL(STATIC smartlist_t *, pick_oos_victims, (int n)); -#endif +#endif /* defined(CONNECTION_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONNECTION_H) */ diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 7a97c632d1..07c6ac88b4 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -29,7 +29,7 @@ * <li>DNS lookup streams, created on the exit side in response to * a RELAY_RESOLVE cell from a client. * <li>Tunneled directory streams, created on the directory cache side - * in response to a RELAY_BEGINDIR cell. These streams attach directly + * in response to a RELAY_BEGIN_DIR cell. These streams attach directly * to a dir_connection_t object without ever using TCP. * </ul> * @@ -75,9 +75,16 @@ #include "directory.h" #include "dirserv.h" #include "hibernate.h" +#include "hs_common.h" +#include "hs_cache.h" +#include "hs_client.h" +#include "hs_circuit.h" #include "main.h" +#include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_http.h" +#include "proto_socks.h" #include "reasons.h" #include "relay.h" #include "rendclient.h" @@ -108,7 +115,7 @@ #define TRANS_NETFILTER #define TRANS_NETFILTER_IPV6 #endif -#endif +#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */ #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) #include <net/if.h> @@ -151,7 +158,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason, * but we should fix it someday anyway. */ if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) && connection_edge_is_rendezvous_stream(edge_conn)) { - rend_client_note_connection_attempt_ended(edge_conn->rend_data); + if (edge_conn->rend_data) { + rend_client_note_connection_attempt_ended(edge_conn->rend_data); + } } if (base_conn->marked_for_close) { @@ -235,6 +244,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_HTTP_CONNECT_WAIT: + if (connection_ap_process_http_connect(EDGE_TO_ENTRY_CONN(conn)) < 0) { + return -1; + } + return 0; case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) { @@ -329,6 +343,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>. @@ -386,6 +427,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").", @@ -454,6 +498,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: case AP_CONN_STATE_RESOLVE_WAIT: + case AP_CONN_STATE_HTTP_CONNECT_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state); @@ -616,7 +661,7 @@ connection_ap_about_to_close(entry_connection_t *entry_conn) connection_ap_warn_and_unmark_if_pending_circ(entry_conn, "about_to_close"); } -#endif +#endif /* 1 */ control_event_stream_bandwidth(edge_conn); control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, @@ -826,12 +871,13 @@ connection_ap_rescan_and_attach_pending(void) entry_conn->marked_pending_circ_line = 0; \ entry_conn->marked_pending_circ_file = 0; \ } while (0) -#else +#else /* !(defined(DEBUGGING_17659)) */ #define UNMARK() do { } while (0) -#endif +#endif /* defined(DEBUGGING_17659) */ /** Tell any AP streams that are listed as waiting for a new circuit to try - * again, 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. @@ -846,8 +892,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(); @@ -866,9 +913,7 @@ connection_ap_attach_pending(int retry) continue; } if (conn->state != AP_CONN_STATE_CIRCUIT_WAIT) { - // XXXX 030 -- this is downgraded in 0.2.9, since we apparently - // XXXX are running into it in practice. It's harmless. - log_info(LD_BUG, "%p is no longer in circuit_wait. Its current state " + log_warn(LD_BUG, "%p is no longer in circuit_wait. Its current state " "is %s. Why is it on pending_entry_connections?", entry_conn, conn_state_to_string(conn->type, conn->state)); @@ -876,6 +921,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, @@ -885,12 +931,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); @@ -928,7 +979,7 @@ connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, log_warn(LD_BUG, "(Previously called from %s:%d.)\n", f2 ? f2 : "<NULL>", entry_conn->marked_pending_circ_line); -#endif +#endif /* defined(DEBUGGING_17659) */ log_backtrace(LOG_WARN, LD_BUG, "To debug, this may help"); return; } @@ -1036,7 +1087,8 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) if (!entry_conn->chosen_exit_optional && !entry_conn->chosen_exit_retries) continue; - r1 = node_get_by_nickname(entry_conn->chosen_exit_name, 0); + r1 = node_get_by_nickname(entry_conn->chosen_exit_name, + NNF_NO_WARN_UNNAMED); r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; @@ -1139,10 +1191,10 @@ consider_plaintext_ports(entry_connection_t *conn, uint16_t port) * See connection_ap_handshake_rewrite_and_attach()'s * documentation for arguments and return value. */ -int -connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +MOCK_IMPL(int, +connection_ap_rewrite_and_attach_if_allowed,(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)) { const or_options_t *options = get_options(); @@ -1185,10 +1237,9 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, /* Check for whether this is a .exit address. By default, those are * disallowed when they're coming straight from the client, but you're * allowed to have them in MapAddress commands and so forth. */ - if (!strcmpend(socks->address, ".exit") && !options->AllowDotExit) { + if (!strcmpend(socks->address, ".exit")) { log_warn(LD_APP, "The \".exit\" notation is disabled in Tor due to " - "security risks. Set AllowDotExit in your torrc to enable " - "it (at your own risk)."); + "security risks."); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); out->end_reason = END_STREAM_REASON_TORPROTOCOL; @@ -1198,6 +1249,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); @@ -1205,7 +1258,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) { @@ -1217,9 +1270,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 && @@ -1259,7 +1315,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) @@ -1288,7 +1345,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, * 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( @@ -1304,11 +1361,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) { @@ -1347,6 +1405,199 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } +/** We just received a SOCKS request in <b>conn</b> to an onion address of type + * <b>addresstype</b>. Start connecting to the onion service. */ +static int +connection_ap_handle_onion(entry_connection_t *conn, + socks_request_t *socks, + origin_circuit_t *circ, + hostname_type_t addresstype) +{ + time_t now = approx_time(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + + /* If .onion address requests are disabled, refuse the request */ + if (!conn->entry_cfg.onion_traffic) { + log_warn(LD_APP, "Onion address %s requested from a port with .onion " + "disabled", safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + + /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those + * for hidden service addresses. */ + if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { + /* if it's a resolve request, fail it right now, rather than + * building all the circuits and then realizing it won't work. */ + log_warn(LD_APP, + "Resolve requests to hidden services not allowed. Failing."); + connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, + 0,NULL,-1,TIME_MAX); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + return -1; + } + + /* If we were passed a circuit, then we need to fail. .onion addresses + * only work when we launch our own circuits for now. */ + if (circ) { + log_warn(LD_CONTROL, "Attachstream to a circuit is not " + "supported for .onion addresses currently. Failing."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + + /* Interface: Regardless of HS version after the block below we should have + set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */ + const char *onion_address = NULL; + int rend_cache_lookup_result = -ENOENT; + int descriptor_is_usable = 0; + + if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */ + rend_cache_entry_t *entry = NULL; + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization(socks->address); + + const uint8_t *cookie = NULL; + rend_auth_type_t auth_type = REND_NO_AUTH; + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + auth_type = client_auth->auth_type; + cookie = client_auth->descriptor_cookie; + } + + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = + rend_data_client_create(socks->address, NULL, (char *) cookie, + auth_type); + if (rend_data == NULL) { + return -1; + } + onion_address = rend_data_get_address(rend_data); + log_info(LD_REND,"Got a hidden service request for ID '%s'", + safe_str_client(onion_address)); + + rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1, + &entry); + if (!rend_cache_lookup_result && entry) { + descriptor_is_usable = rend_client_any_intro_points_usable(entry); + } + } else { /* it's a v3 hidden service */ + tor_assert(addresstype == ONION_V3_HOSTNAME); + const hs_descriptor_t *cached_desc = NULL; + int retval; + /* Create HS conn identifier with HS pubkey */ + hs_ident_edge_conn_t *hs_conn_ident = + tor_malloc_zero(sizeof(hs_ident_edge_conn_t)); + + retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk, + NULL, NULL); + if (retval < 0) { + log_warn(LD_GENERAL, "failed to parse hs address"); + tor_free(hs_conn_ident); + return -1; + } + ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident; + + onion_address = socks->address; + + /* Check the v3 desc cache */ + cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk); + if (cached_desc) { + rend_cache_lookup_result = 0; + descriptor_is_usable = + hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk, + cached_desc); + log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.", + (descriptor_is_usable) ? "usable" : "unusable", + safe_str_client(onion_address), + (descriptor_is_usable) ? "Not fetching." : "Refecting."); + } else { + rend_cache_lookup_result = -ENOENT; + } + } + + /* Lookup the given onion address. If invalid, stop right now. + * Otherwise, we might have it in the cache or not. */ + unsigned int refetch_desc = 0; + if (rend_cache_lookup_result < 0) { + switch (-rend_cache_lookup_result) { + case EINVAL: + /* We should already have rejected this address! */ + log_warn(LD_BUG,"Invalid service name '%s'", + safe_str_client(onion_address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + case ENOENT: + /* We didn't have this; we should look it up. */ + log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.", + safe_str_client(onion_address)); + refetch_desc = 1; + break; + default: + log_warn(LD_BUG, "Unknown cache lookup error %d", + rend_cache_lookup_result); + return -1; + } + } + + /* Help predict that we'll want to do hidden service circuits in the + * future. We're not sure if it will need a stable circuit yet, but + * we know we'll need *something*. */ + rep_hist_note_used_internal(now, 0, 1); + + /* Now we have a descriptor but is it usable or not? If not, refetch. + * Also, a fetch could have been requested if the onion address was not + * found in the cache previously. */ + if (refetch_desc || !descriptor_is_usable) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + connection_ap_mark_as_non_pending_circuit(conn); + base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; + if (addresstype == ONION_V2_HOSTNAME) { + tor_assert(edge_conn->rend_data); + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + /* Whatever the result of the refetch, we don't go further. */ + return 0; + } else { + tor_assert(addresstype == ONION_V3_HOSTNAME); + tor_assert(edge_conn->hs_ident); + /* Attempt to fetch the hsv3 descriptor. Check the retval to see how it + * went and act accordingly. */ + int ret = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + switch (ret) { + case HS_CLIENT_FETCH_MISSING_INFO: + /* Keeping the connection in descriptor wait state is fine because + * once we get enough dirinfo or a new live consensus, the HS client + * subsystem is notified and every connection in that state will + * trigger a fetch for the service key. */ + case HS_CLIENT_FETCH_LAUNCHED: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_HAVE_DESC: + return 0; + case HS_CLIENT_FETCH_ERROR: + case HS_CLIENT_FETCH_NO_HSDIRS: + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Can't proceed further and better close the SOCKS request. */ + return -1; + } + } + } + + /* We have the descriptor! So launch a connection to the HS. */ + log_info(LD_REND, "Descriptor is here. Great."); + + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ + connection_ap_mark_as_pending_circuit(conn); + return 0; +} + /** Connection <b>conn</b> just finished its socks handshake, or the * controller asked us to take care of it. If <b>circ</b> is defined, * then that's where we'll want to attach it. Otherwise we have to @@ -1373,11 +1624,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); @@ -1391,8 +1645,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); @@ -1406,8 +1660,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. */ @@ -1419,23 +1673,23 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, const node_t *node = NULL; /* If this .exit was added by an AUTOMAP, then it came straight from - * a user. Make sure that options->AllowDotExit permits that. */ - if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { - /* Whoops; this one is stale. It must have gotten added earlier, - * when AllowDotExit was on. */ - log_warn(LD_APP,"Stale automapped address for '%s.exit', with " - "AllowDotExit disabled. Refusing.", + * a user. That's not safe. */ + if (exit_source == ADDRMAPSRC_AUTOMAP) { + /* Whoops; this one is stale. It must have gotten added earlier? + * (Probably this is not possible, since AllowDotExit no longer + * exists.) */ + log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.", safe_str_client(socks->address)); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + tor_assert_nonfatal_unreached(); return -1; } /* Double-check to make sure there are no .exits coming from * impossible/weird sources. */ - if (exit_source == ADDRMAPSRC_DNS || - (exit_source == ADDRMAPSRC_NONE && !options->AllowDotExit)) { + if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) { /* It shouldn't be possible to get a .exit address from any of these * sources. */ log_warn(LD_BUG,"Address '%s.exit', with impossible source for the " @@ -1448,14 +1702,19 @@ 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 */ if (s[1] != '\0') { /* Looks like a real .exit one. */ conn->chosen_exit_name = tor_strdup(s+1); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (exit_source == ADDRMAPSRC_TRACKEXIT) { /* We 5 tries before it expires the addressmap */ @@ -1476,7 +1735,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * form that means (foo's address).foo.exit. */ conn->chosen_exit_name = tor_strdup(socks->address); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (node) { *socks->address = 0; node_get_address_string(node, socks->address, sizeof(socks->address)); @@ -1504,10 +1763,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, implies no. */ } - /* Now, handle everything that isn't a .onion address. */ - if (addresstype != ONION_HOSTNAME) { + /* Now, we handle everything that isn't a .onion address. */ + if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, - * possibly with a .exit that we stripped off. */ + * 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)) { @@ -1530,7 +1791,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* socks->address is a non-onion hostname or IP address. * If we can't do any non-onion requests, refuse the connection. @@ -1554,30 +1815,37 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Then check if we have a hostname or IP address, and whether DNS or - * the IP address family are permitted */ + * 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 && !conn->entry_cfg.dns_request) { - log_warn(LD_APP, "Refusing to connect to hostname %s " - "because Port has NoDNSRequest set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } else if (socks_family == AF_INET && !conn->entry_cfg.ipv4_traffic) { - log_warn(LD_APP, "Refusing to connect to IPv4 address %s because " - "Port has NoIPv4Traffic set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } else if (socks_family == AF_INET6 && !conn->entry_cfg.ipv6_traffic) { - log_warn(LD_APP, "Refusing to connect to IPv6 address %s because " - "Port has NoIPv6Traffic set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; + 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(); } - /* No else, we've covered all possible returned value. */ /* See if this is a hostname lookup that we can answer immediately. * (For example, an attempt to look up the IP address for an IP address.) @@ -1598,7 +1866,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) { @@ -1607,7 +1876,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 && @@ -1651,7 +1920,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. */ @@ -1694,11 +1965,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 */ @@ -1722,7 +1997,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); @@ -1731,7 +2007,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(); } @@ -1747,6 +2023,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; } @@ -1764,110 +2042,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return 0; } else { /* If we get here, it's a request for a .onion address! */ + tor_assert(addresstype == ONION_V2_HOSTNAME || + addresstype == ONION_V3_HOSTNAME); tor_assert(!automap); - - /* If .onion address requests are disabled, refuse the request */ - if (!conn->entry_cfg.onion_traffic) { - log_warn(LD_APP, "Onion address %s requested from a port with .onion " - "disabled", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } - - /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those - * for hidden service addresses. */ - if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { - /* if it's a resolve request, fail it right now, rather than - * building all the circuits and then realizing it won't work. */ - log_warn(LD_APP, - "Resolve requests to hidden services not allowed. Failing."); - connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, - 0,NULL,-1,TIME_MAX); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_SOCKSPROTOCOL | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return -1; - } - - /* If we were passed a circuit, then we need to fail. .onion addresses - * only work when we launch our own circuits for now. */ - if (circ) { - log_warn(LD_CONTROL, "Attachstream to a circuit is not " - "supported for .onion addresses currently. Failing."); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - } - - /* Look up if we have client authorization configured for this hidden - * service. If we do, associate it with the rend_data. */ - rend_service_authorization_t *client_auth = - rend_client_lookup_service_authorization(socks->address); - - const uint8_t *cookie = NULL; - rend_auth_type_t auth_type = REND_NO_AUTH; - if (client_auth) { - log_info(LD_REND, "Using previously configured client authorization " - "for hidden service request."); - auth_type = client_auth->auth_type; - cookie = client_auth->descriptor_cookie; - } - - /* Fill in the rend_data field so we can start doing a connection to - * a hidden service. */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = - rend_data_client_create(socks->address, NULL, (char *) cookie, - auth_type); - if (rend_data == NULL) { - return -1; - } - log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(rend_data->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. */ - 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); - 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)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - case ENOENT: - refetch_desc = 1; - break; - default: - log_warn(LD_BUG, "Unknown cache lookup error %d", - rend_cache_lookup_result); - return -1; - } - } - - /* Help predict this next time. We're not sure if it will need - * a stable circuit yet, but we know we'll need *something*. */ - rep_hist_note_used_internal(now, 0, 1); - - /* Now we have a descriptor but is it usable or not? If not, refetch. - * Also, a fetch could have been requested if the onion address was not - * found in the cache previously. */ - if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { - connection_ap_mark_as_non_pending_circuit(conn); - base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; - log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(rend_data->onion_address)); - rend_client_refetch_v2_renddesc(rend_data); - return 0; - } - - /* We have the descriptor so launch a connection to the HS. */ - base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - log_info(LD_REND, "Descriptor is here. Great."); - connection_ap_mark_as_pending_circuit(conn); - return 0; + return connection_ap_handle_onion(conn, socks, circ, addresstype); } return 0; /* unreached but keeps the compiler happy */ @@ -1883,13 +2061,13 @@ 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 /* works on NetBSD and FreeBSD */ pf = tor_open_cloexec("/dev/pf", O_RDWR, 0); -#endif +#endif /* defined(OpenBSD) */ if (pf < 0) { log_warn(LD_NET, "open(\"/dev/pf\") failed: %s", strerror(errno)); @@ -1899,9 +2077,10 @@ get_pf_socket(void) pf_socket = pf; return pf_socket; } -#endif +#endif /* defined(TRANS_PF) */ -#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || defined(TRANS_TPROXY) +#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || \ + defined(TRANS_TPROXY) /** Try fill in the address of <b>req</b> from the socket configured * with <b>conn</b>. */ static int @@ -1921,7 +2100,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) } goto done; } -#endif +#endif /* defined(TRANS_TPROXY) */ #ifdef TRANS_NETFILTER int rv = -1; @@ -1931,13 +2110,13 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len); break; -#endif +#endif /* defined(TRANS_NETFILTER_IPV4) */ #ifdef TRANS_NETFILTER_IPV6 case AF_INET6: rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len); break; -#endif +#endif /* defined(TRANS_NETFILTER_IPV6) */ default: log_warn(LD_BUG, "Received transparent data from an unsuported socket family %d", @@ -1963,7 +2142,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) (void)req; log_warn(LD_BUG, "Unable to determine destination from socket."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ done: tor_addr_from_sockaddr(&addr, (struct sockaddr*)&orig_dst, &req->port); @@ -1971,7 +2150,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_NETFILTER) || defined(TRANS_PF) || ... */ #ifdef TRANS_PF static int @@ -2005,7 +2184,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(__FreeBSD__) */ memset(&pnl, 0, sizeof(pnl)); pnl.proto = IPPROTO_TCP; @@ -2054,7 +2233,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_PF) */ /** Fetch the original destination address and port from a * system-specific interface and put them into a @@ -2090,7 +2269,7 @@ connection_ap_get_original_destination(entry_connection_t *conn, log_warn(LD_BUG, "Called connection_ap_get_original_destination, but no " "transparent proxy method was configured."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ } /** connection_edge_process_inbuf() found a conn in state @@ -2125,7 +2304,7 @@ connection_ap_handshake_process_socks(entry_connection_t *conn) if (socks->replylen) { had_reply = 1; - connection_write_to_buf((const char*)socks->reply, socks->replylen, + connection_buf_add((const char*)socks->reply, socks->replylen, base_conn); socks->replylen = 0; if (sockshere == -1) { @@ -2222,7 +2401,7 @@ connection_ap_process_natd(entry_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = connection_fetch_from_buf_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); + err = connection_buf_get_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2271,6 +2450,108 @@ connection_ap_process_natd(entry_connection_t *conn) return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } +/** Called on an HTTP CONNECT entry connection when some bytes have arrived, + * but we have not yet received a full HTTP CONNECT request. Try to parse an + * HTTP CONNECT request from the connection's inbuf. On success, set up the + * connection's socks_request field and try to attach the connection. On + * failure, send an HTTP reply, and mark the connection. + */ +STATIC int +connection_ap_process_http_connect(entry_connection_t *conn) +{ + if (BUG(ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_HTTP_CONNECT_WAIT)) + return -1; + + char *headers = NULL, *body = NULL; + char *command = NULL, *addrport = NULL; + char *addr = NULL; + size_t bodylen = 0; + + const char *errmsg = NULL; + int rv = 0; + + const int http_status = + fetch_from_buf_http(ENTRY_TO_CONN(conn)->inbuf, &headers, 8192, + &body, &bodylen, 1024, 0); + if (http_status < 0) { + /* Bad http status */ + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (http_status == 0) { + /* no HTTP request yet. */ + goto done; + } + + const int cmd_status = parse_http_command(headers, &command, &addrport); + if (cmd_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + tor_assert(command); + tor_assert(addrport); + if (strcasecmp(command, "connect")) { + errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + goto err; + } + + tor_assert(conn->socks_request); + socks_request_t *socks = conn->socks_request; + uint16_t port; + if (tor_addr_port_split(LOG_WARN, addrport, &addr, &port) < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + if (strlen(addr) >= MAX_SOCKS_ADDR_LEN) { + errmsg = "HTTP/1.0 414 Request-URI Too Long\r\n\r\n"; + goto err; + } + + /* Abuse the 'username' and 'password' fields here. They are already an + * abuse. */ + { + char *authorization = http_get_header(headers, "Proxy-Authorization: "); + if (authorization) { + socks->username = authorization; // steal reference + socks->usernamelen = strlen(authorization); + } + char *isolation = http_get_header(headers, "X-Tor-Stream-Isolation: "); + if (isolation) { + socks->password = isolation; // steal reference + socks->passwordlen = strlen(isolation); + } + } + + socks->command = SOCKS_COMMAND_CONNECT; + socks->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + strlcpy(socks->address, addr, sizeof(socks->address)); + socks->port = port; + + control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + + rv = connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + + // XXXX send a "100 Continue" message? + + goto done; + + err: + if (BUG(errmsg == NULL)) + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + log_warn(LD_EDGE, "Saying %s", escaped(errmsg)); + connection_buf_add(errmsg, strlen(errmsg), ENTRY_TO_CONN(conn)); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_HTTPPROTOCOL| + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + + done: + tor_free(headers); + tor_free(body); + tor_free(command); + tor_free(addrport); + tor_free(addr); + return rv; +} + /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ @@ -2378,8 +2659,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) * * If ap_conn is broken, mark it for close and return -1. Else return 0. */ -int -connection_ap_handshake_send_begin(entry_connection_t *ap_conn) +MOCK_IMPL(int, +connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)) { char payload[CELL_PAYLOAD_SIZE]; int payload_len; @@ -2451,8 +2732,10 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) /* Sensitive directory connections must have an anonymous path length. * Otherwise, directory connections are typically one-hop. * This matches the earlier check for directory connection path anonymity - * in directory_initiate_command_rend(). */ - if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) { + * 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 { @@ -2888,15 +3171,22 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } if (replylen) { /* we already have a reply in mind */ - connection_write_to_buf(reply, replylen, ENTRY_TO_CONN(conn)); + connection_buf_add(reply, replylen, ENTRY_TO_CONN(conn)); conn->socks_request->has_finished = 1; return; } - if (conn->socks_request->socks_version == 4) { + if (conn->socks_request->listener_type == + CONN_TYPE_AP_HTTP_CONNECT_LISTENER) { + const char *response = end_reason_to_http_connect_response_line(endreason); + if (!response) { + response = "HTTP/1.0 400 Bad Request\r\n\r\n"; + } + connection_buf_add(response, strlen(response), ENTRY_TO_CONN(conn)); + } else if (conn->socks_request->socks_version == 4) { memset(buf,0,SOCKS4_NETWORK_LEN); buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT); /* leave version, destport, destip zero */ - connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); + connection_buf_add(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { size_t buf_len; memset(buf,0,sizeof(buf)); @@ -2915,7 +3205,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, /* 4 bytes for the header, 2 bytes for the port, 16 for the address. */ buf_len = 22; } - connection_write_to_buf(buf,buf_len,ENTRY_TO_CONN(conn)); + connection_buf_add(buf,buf_len,ENTRY_TO_CONN(conn)); } /* If socks_version isn't 4 or 5, don't send anything. * This can happen in the case of AP bridges. */ @@ -2923,12 +3213,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 @@ -2987,6 +3277,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } +/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ +static int +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) +{ + int ret; + origin_circuit_t *origin_circ; + + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); + + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert_nonfatal_unreached(); + return -1; + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); + /* Send back reason DONE because we want to make hidden service port + * scanning harder thus instead of returning that the exit policy + * didn't match, which makes it obvious that the port is closed, + * return DONE and kill the circuit. That way, a user (malicious or + * not) needs one circuit per bad port unless it matches the policy of + * the hidden service. */ + relay_send_end_cell_from_edge(conn->stream_id, circ, + END_STREAM_REASON_DONE, + origin_circ->cpath->prev); + connection_free(TO_CONN(conn)); + + /* Drop the circuit here since it might be someone deliberately + * scanning the hidden service ports. Note that this mitigates port + * scanning by adding more work on the attacker side to successfully + * scan but does not fully solve it. */ + if (ret < -1) { + return END_CIRC_AT_ORIGIN; + } else { + return 0; + } + } + + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; + assert_circuit_ok(circ); + + hs_inc_rdv_stream_counter(origin_circ); + + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); + + /* For path bias: This circuit was used successfully */ + pathbias_mark_use_success(origin_circ); + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3013,14 +3385,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 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) @@ -3045,7 +3424,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) return -END_CIRC_REASON_TORPROTOCOL; } 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; } @@ -3055,22 +3434,21 @@ 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( + const int client_chan = channel_is_client(or_circ->p_chan); + if ((client_chan || + (!connection_or_digest_is_known_relay( or_circ->p_chan->identity_digest) && should_refuse_unknown_exits(options)))) { - /* 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)), - or_circ->is_first_hop ? "on first hop of circuit" : - "from unknown relay"); + client_chan ? "on first hop of circuit" : + "from unknown relay"); relay_send_end_cell_from_edge(rh.stream_id, circ, - or_circ->is_first_hop ? + client_chan ? END_STREAM_REASON_TORPROTOCOL : END_STREAM_REASON_MISC, NULL); @@ -3082,7 +3460,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 @@ -3099,7 +3477,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; } @@ -3110,7 +3488,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; } } @@ -3133,58 +3511,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->deliver_window = STREAMWINDOW_START; if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - 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; @@ -3479,11 +3809,15 @@ 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; } @@ -3507,7 +3841,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, */ if (conn->chosen_exit_name) { const node_t *chosen_exit = - node_get_by_nickname(conn->chosen_exit_name, 1); + node_get_by_nickname(conn->chosen_exit_name, 0); if (!chosen_exit || tor_memneq(chosen_exit->identity, exit_node->identity, DIGEST_LEN)) { /* doesn't match */ @@ -3556,10 +3890,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn, } /** If address is of the form "y.onion" with a well-formed handle y: - * Put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or + * ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "x.y.onion" with a well-formed handle x: - * Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Drop "x.", put a NUL after y, lower-case it, and return + * ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "y.onion" with a badly-formed handle y: * Return BAD_HOSTNAME and log a message. @@ -3575,7 +3911,7 @@ parse_extended_hostname(char *address) { char *s; char *q; - char query[REND_SERVICE_ID_LEN_BASE32+1]; + char query[HS_SERVICE_ADDR_LEN_BASE32+1]; s = strrchr(address,'.'); if (!s) @@ -3595,14 +3931,17 @@ parse_extended_hostname(char *address) goto failed; /* reject sub-domain, as DNS does */ } q = (NULL == q) ? address : q + 1; - if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >= - REND_SERVICE_ID_LEN_BASE32+1) + if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >= + HS_SERVICE_ADDR_LEN_BASE32+1) goto failed; if (q != address) { memmove(address, q, strlen(q) + 1 /* also get \0 */); } - if (rend_valid_service_id(query)) { - return ONION_HOSTNAME; /* success */ + if (rend_valid_v2_service_id(query)) { + return ONION_V2_HOSTNAME; /* success */ + } + if (hs_address_is_valid(query)) { + return ONION_V3_HOSTNAME; } failed: /* otherwise, return to previous state and return 0 */ diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 5dfc8af901..c6583d3845 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); @@ -88,16 +89,18 @@ int connection_ap_process_transparent(entry_connection_t *conn); int address_is_invalid_destination(const char *address, int client); -int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath); +MOCK_DECL(int, connection_ap_rewrite_and_attach_if_allowed, + (entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)); int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); /** Possible return values for parse_extended_hostname. */ typedef enum hostname_type_t { - NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME + NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME, + EXIT_HOSTNAME, BAD_HOSTNAME } hostname_type_t; hostname_type_t parse_extended_hostname(char *address); @@ -186,7 +189,9 @@ typedef struct { STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, rewrite_result_t *out); -#endif -#endif +STATIC int connection_ap_process_http_connect(entry_connection_t *conn); +#endif /* defined(CONNECTION_EDGE_PRIVATE) */ + +#endif /* !defined(TOR_CONNECTION_EDGE_H) */ diff --git a/src/or/connection_or.c b/src/or/connection_or.c index a01d086279..fe2cea4f46 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -21,6 +21,7 @@ * 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 @@ -45,14 +46,18 @@ #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" +#include "proto_cell.h" #include "reasons.h" #include "relay.h" #include "rendcommon.h" #include "rephist.h" #include "router.h" +#include "routerkeys.h" #include "routerlist.h" #include "ext_orport.h" #include "scheduler.h" +#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); @@ -74,56 +79,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); -/**************************************************************/ +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) { @@ -131,57 +105,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 @@ -485,7 +474,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; @@ -504,7 +493,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; @@ -732,14 +721,14 @@ 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, reason); if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( orconn_end_reason_to_control_string(reason), reason, or_conn); } @@ -830,24 +819,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. */ @@ -855,9 +826,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 @@ -866,7 +834,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 @@ -878,15 +853,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); @@ -908,10 +915,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, @@ -957,7 +966,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(). * @@ -974,16 +983,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; @@ -1007,11 +1019,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. */ @@ -1032,13 +1044,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; @@ -1057,17 +1067,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: " @@ -1091,24 +1099,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 @@ -1123,7 +1114,7 @@ connection_or_connect_failed(or_connection_t *conn, { control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(get_options())) - control_event_bootstrap_problem(msg, reason, conn); + control_event_bootstrap_prob_or(msg, reason, conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or @@ -1174,7 +1165,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(); @@ -1194,6 +1187,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)); @@ -1206,7 +1204,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); @@ -1239,7 +1237,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port), transport_name, transport_name); - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Can't connect to bridge", END_OR_CONN_REASON_PT_MISSING, conn); @@ -1373,7 +1371,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving)) connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT, conn->base_.s); - note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); if (connection_tls_continue_handshake(conn) < 0) return -1; @@ -1553,7 +1550,10 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, } if (identity_rcvd) { - crypto_pk_get_digest(identity_rcvd, digest_rcvd_out); + if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) { + crypto_pk_free(identity_rcvd); + return -1; + } } else { memset(digest_rcvd_out, 0, DIGEST_LEN); } @@ -1563,18 +1563,25 @@ 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 did not know the ID before, record the one we got. @@ -1595,12 +1602,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(); - - 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] = '$'; @@ -1612,16 +1638,39 @@ 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()); @@ -1659,30 +1708,48 @@ connection_or_client_learned_peer_id(or_connection_t *conn, } log_fn(severity, LD_HANDSHAKE, - "Tried connecting to router at %s:%d, but identity key was not " - "as expected: wanted %s but got %s.%s", - conn->base_.address, conn->base_.port, expected, seen, extra_log); - entry_guard_register_connect_status(conn->identity_digest, 0, 1, - time(NULL)); + "Tried connecting to router at %s:%d, but RSA + ed25519 identity " + "keys were 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)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Unexpected identity in router certificate", END_OR_CONN_REASON_OR_IDENTITY, 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; } -/** Return when a client used this, for connection.c, since client_used - * is now one of the timestamps of channel_t */ +/** Return when we last used this channel for client activity (origin + * circuits). This is called from connection.c, since client_used is now one + * of the timestamps in channel_t */ time_t connection_or_client_used(or_connection_t *conn) @@ -1696,7 +1763,7 @@ connection_or_client_used(or_connection_t *conn) /** The v1/v2 TLS handshake is finished. * - * Make sure we are happy with the person we just handshaked with. + * Make sure we are happy with the peer we just handshaked with. * * If they initiated the connection, make sure they're not already connected, * then initialize conn from the information in router. @@ -1731,7 +1798,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); @@ -1740,7 +1808,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); } } @@ -1779,6 +1848,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; } @@ -1790,8 +1864,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); } @@ -1912,12 +1986,23 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) cell_pack(&networkcell, cell, conn->wide_circ_ids); - connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn)); + rep_hist_padding_count_write(PADDING_TYPE_TOTAL); + if (cell->command == CELL_PADDING) + rep_hist_padding_count_write(PADDING_TYPE_CELL); + + connection_buf_add(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); } @@ -1935,8 +2020,8 @@ connection_or_write_var_cell_to_buf,(const var_cell_t *cell, tor_assert(cell); tor_assert(conn); n = var_cell_pack_header(cell, hdr, conn->wide_circ_ids); - connection_write_to_buf(hdr, n, TO_CONN(conn)); - connection_write_to_buf((char*)cell->payload, + connection_buf_add(hdr, n, TO_CONN(conn)); + connection_buf_add((char*)cell->payload, cell->payload_len, TO_CONN(conn)); if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_var_cell(conn, conn->handshake_state, cell, 0); @@ -2011,7 +2096,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); circuit_build_times_network_is_live(get_circuit_build_times_mutable()); - connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); + connection_buf_get_bytes(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the * network-order string) */ @@ -2023,7 +2108,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) ); @@ -2144,66 +2229,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 *global_link_cert = NULL, *id_cert = NULL, - *using_link_cert = NULL; + const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL; tor_x509_cert_t *own_link_cert = NULL; - const uint8_t *link_encoded = NULL, *id_encoded = NULL; - size_t link_len, id_len; var_cell_t *cell; - size_t cell_len; - ssize_t pos; + + certs_cell_t *certs_cell = NULL; tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3); if (! conn->handshake_state) return -1; + 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; + if (conn_in_server_mode) { - using_link_cert = own_link_cert = tor_tls_get_own_cert(conn->tls); + own_link_cert = tor_tls_get_own_cert(conn->tls); + } + tor_assert(id_cert); + + 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 { - using_link_cert = global_link_cert; + tor_assert(global_link_cert); + add_x509_cert(certs_cell, + OR_CERT_TYPE_AUTH_1024, global_link_cert); } - tor_x509_cert_get_der(using_link_cert, &link_encoded, &link_len); - tor_x509_cert_get_der(id_cert, &id_encoded, &id_len); - cell_len = 1 /* 1 byte: num certs in cell */ + - 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; + /* Next the RSA->RSA ID cert */ + add_x509_cert(certs_cell, + OR_CERT_TYPE_ID_1024, id_cert); - if (conn_in_server_mode) - cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */ - else - cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */ - set_uint16(&cell->payload[pos+1], htons(link_len)); - memcpy(&cell->payload[pos+3], link_encoded, link_len); - pos += 3 + link_len; + /* 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()); + } + + /* 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); + } + } - 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; + /* We've added all the certs; make the cell. */ + certs_cell->n_certs = certs_cell_getlen_certs(certs_cell); - tor_assert(pos == (int)cell_len); /* Otherwise we just smashed the heap */ + 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 @@ -2218,17 +2424,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); @@ -2242,8 +2457,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 @@ -2259,24 +2474,44 @@ 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; @@ -2286,7 +2521,7 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, 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]; @@ -2302,6 +2537,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) { @@ -2328,7 +2579,8 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, 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; } @@ -2339,36 +2591,79 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, } /* 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]; @@ -2383,18 +2678,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); @@ -2408,44 +2709,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 2e8c6066cc..ee66b7c528 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,10 +60,12 @@ 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); MOCK_DECL(int, connection_or_get_num_circuits, (or_connection_t *conn)); void or_handshake_state_free(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 /* !defined(TOR_CONNECTION_OR_H) */ + diff --git a/src/or/conscache.c b/src/or/conscache.c new file mode 100644 index 0000000000..1a15126f97 --- /dev/null +++ b/src/or/conscache.c @@ -0,0 +1,626 @@ +/* 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 + +#ifdef _WIN32 +/* On Windows, unlink won't work on a file if the file is actively mmap()ed. + * That forces us to be less aggressive about unlinking files, and causes other + * changes throughout our logic. + */ +#define MUST_UNMAP_TO_UNLINK +#endif /* defined(_WIN32) */ + +/** + * 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; + + /** The maximum number of entries that we'd like to allow in this cache. + * This is the same as the storagedir limit when MUST_UNMAP_TO_UNLINK is + * not defined. */ + unsigned max_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) +{ + int storagedir_max_entries; + consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t)); + char *directory = get_datadir_fname(subdir); + cache->max_entries = max_entries; + +#ifdef MUST_UNMAP_TO_UNLINK + /* If we can't unlink the files that we're still using, then we need to + * tell the storagedir backend to allow far more files than this consensus + * cache actually wants, so that it can hold files which, from this cache's + * perspective, have become useless. + */ +#define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) + storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; +#else /* !(defined(MUST_UNMAP_TO_UNLINK)) */ + /* Otherwise, we can just tell the storagedir to use the same limits + * as this cache. */ + storagedir_max_entries = max_entries; +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ + + cache->dir = storage_dir_new(directory, storagedir_max_entries); + tor_free(directory); + if (!cache->dir) { + tor_free(cache); + return NULL; + } + + consensus_cache_rescan(cache); + return cache; +} + +/** Return true if it's okay to put more entries in this cache than + * its official file limit. + * + * (We need this method on Windows, where we can't unlink files that are still + * in use, and therefore might need to temporarily exceed the file limit until + * the no-longer-wanted files are deletable.) + */ +int +consensus_cache_may_overallocate(consensus_cache_t *cache) +{ + (void) cache; +#ifdef MUST_UNMAP_TO_UNLINK + return 1; +#else + return 0; +#endif +} + +/** + * 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) +{ +#ifdef MUST_UNMAP_TO_UNLINK + /* Our Linux sandbox doesn't support huge file lists like the one that would + * be generated by using VERY_LARGE_STORAGEDIR_LIMIT above in + * consensus_cache_open(). Since the Linux sandbox is the only one we have + * right now, we just assert that we never reach this point when we've had + * to use VERY_LARGE_STORAGEDIR_LIMIT. + * + * If at some point in the future we have a different sandbox mechanism that + * can handle huge file lists, we can remove this assertion or make it + * conditional. + */ + tor_assert_nonfatal_unreached(); +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ + 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 = cache->max_entries; + int used = smartlist_len(storage_dir_list(cache->dir)); +#ifdef MUST_UNMAP_TO_UNLINK + if (used > max) + return 0; +#else + tor_assert_nonfatal(max >= used); +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ + 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 in use by something other than 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); + int force_ent = force; +#ifdef MUST_UNMAP_TO_UNLINK + /* We cannot delete anything with an active mmap on win32, so no + * force-deletion. */ + if (ent->map) { + force_ent = 0; + } +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ + if (! force_ent) { + if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { + /* Somebody is using this entry right now */ + 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) { + /* The ERANGE error might come from tor_mmap_file() -- it means the file + * was empty. EINVAL might come from ..map_labeled() -- it means the + * file was misformatted. In both cases, we should just delete it. + */ + if (errno == ERANGE || errno == EINVAL) { + log_warn(LD_FS, "Found %s file %s in consensus cache; removing it.", + errno == ERANGE ? "empty" : "misformatted", + escaped(fname)); + storage_dir_remove_file(cache->dir, fname); + } else { + /* 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 /* defined(TOR_UNIT_TESTS) */ + diff --git a/src/or/conscache.h b/src/or/conscache.h new file mode 100644 index 0000000000..3c89dedf43 --- /dev/null +++ b/src/or/conscache.h @@ -0,0 +1,62 @@ +/* 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_may_overallocate(consensus_cache_t *cache); +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 /* !defined(TOR_CONSCACHE_H) */ + diff --git a/src/or/consdiff.c b/src/or/consdiff.c new file mode 100644 index 0000000000..deaf465fe7 --- /dev/null +++ b/src/or/consdiff.c @@ -0,0 +1,1415 @@ +/* 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) +{ + const char *end_of_str = s + strlen(s); + tor_assert(*end_of_str == '\0'); + + while (*s) { + const char *eol = memchr(s, '\n', end_of_str - s); + 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..eb772c0b2b --- /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 /* defined(CONSDIFF_PRIVATE) */ + +#endif /* !defined(TOR_CONSDIFF_H) */ + diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c new file mode 100644 index 0000000000..8349f257de --- /dev/null +++ b/src/or/consdiffmgr.c @@ -0,0 +1,1900 @@ +/* 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) +{ + if (handle == NULL) { + tor_assert_nonfatal(status != CDM_DIFF_PRESENT); + } + + 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 /* 0 */ +} + +/** + * 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 that we can. + 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 (consensus_cache_may_overallocate(cache)) { + /* If we're allowed to throw extra files into the cache, let's do so + * rather getting upset. + */ + return 0; + } + + 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 (ent == NULL) { + static ratelim_t cant_store_ratelim = RATELIM_INIT(5*60); + log_fn_ratelim(&cant_store_ratelim, LOG_WARN, LD_FS, + "Unable to store object %s compressed with %s.", + description, methodname); + 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) { + consensus_cache_entry_handle_t *h = handles[u]; + int this_status = status; + if (h == NULL) { + this_status = CDM_DIFF_ERROR; + } + tor_assert_nonfatal(h != NULL || this_status == CDM_DIFF_ERROR); + cdm_diff_ht_set_status(flav, from_sha3, to_sha3, method, this_status, h); + } 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..df569c8e23 --- /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 /* defined(CONSDIFFMGR_PRIVATE) */ + +#endif /* !defined(TOR_CONSDIFFMGR_H) */ + diff --git a/src/or/control.c b/src/or/control.c index 03d9fcee2a..3609210b95 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -36,6 +36,7 @@ #include "or.h" #include "addressmap.h" +#include "bridges.h" #include "buffers.h" #include "channel.h" #include "channeltls.h" @@ -57,10 +58,14 @@ #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" +#include "proto_control0.h" +#include "proto_http.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" @@ -69,6 +74,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "shared_random.h" #ifndef _WIN32 #include <pwd.h> @@ -351,7 +357,7 @@ static inline void connection_write_str_to_buf(const char *s, control_connection_t *conn) { size_t len = strlen(s); - connection_write_to_buf(s, len, TO_CONN(conn)); + connection_buf_add(s, len, TO_CONN(conn)); } /** Given a <b>len</b>-character string in <b>data</b>, made of lines @@ -364,16 +370,23 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn) STATIC size_t write_escaped_data(const char *data, size_t len, char **out) { - size_t sz_out = len+8; + tor_assert(len < SIZE_MAX - 9); + size_t sz_out = len+8+1; char *outp; const char *start = data, *end; - int i; + size_t i; int start_of_line; - for (i=0; i<(int)len; ++i) { - if (data[i]== '\n') + for (i=0; i < len; ++i) { + if (data[i] == '\n') { sz_out += 2; /* Maybe add a CR; maybe add a dot. */ + if (sz_out >= SIZE_T_CEILING) { + log_warn(LD_BUG, "Input to write_escaped_data was too long"); + *out = tor_strdup(".\r\n"); + return 3; + } + } } - *out = outp = tor_malloc(sz_out+1); + *out = outp = tor_malloc(sz_out); end = data+len; start_of_line = 1; while (data < end) { @@ -399,7 +412,8 @@ write_escaped_data(const char *data, size_t len, char **out) *outp++ = '\r'; *outp++ = '\n'; *outp = '\0'; /* NUL-terminate just in case. */ - tor_assert((outp - *out) <= (int)sz_out); + tor_assert(outp >= *out); + tor_assert((size_t)(outp - *out) <= sz_out); return outp - *out; } @@ -553,7 +567,7 @@ connection_printf_to_buf(control_connection_t *conn, const char *format, ...) tor_assert(0); } - connection_write_to_buf(buf, (size_t)len, TO_CONN(conn)); + connection_buf_add(buf, (size_t)len, TO_CONN(conn)); tor_free(buf); } @@ -579,7 +593,7 @@ control_ports_write_to_file(void) smartlist_add_asprintf(lines, "UNIX_PORT=%s\n", conn->address); continue; } -#endif +#endif /* defined(AF_UNIX) */ smartlist_add_asprintf(lines, "PORT=%s:%d\n", conn->address, conn->port); } SMARTLIST_FOREACH_END(conn); @@ -596,7 +610,7 @@ control_ports_write_to_file(void) options->ControlPortWriteToFile); } } -#endif +#endif /* !defined(_WIN32) */ tor_free(joined); SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); smartlist_free(lines); @@ -778,7 +792,7 @@ queued_events_flush_all(int force) SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, control_conn) { if (control_conn->event_mask & bit) { - connection_write_to_buf(ev->msg, msg_len, TO_CONN(control_conn)); + connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); } } SMARTLIST_FOREACH_END(control_conn); @@ -799,7 +813,7 @@ queued_events_flush_all(int force) } /** Libevent callback: Flushes pending events to controllers that are - * interested in them */ + * interested in them. */ static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg) { @@ -942,7 +956,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); @@ -1060,7 +1074,7 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, tor_assert(strlen(tmp)>4); tmp[3] = ' '; msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_write_to_buf(msg, msg_len, TO_CONN(conn)); + connection_buf_add(msg, msg_len, TO_CONN(conn)); } else { connection_write_str_to_buf("250 OK\r\n", conn); } @@ -1142,7 +1156,6 @@ static const struct control_event_t control_event_table[] = { { EVENT_ERR_MSG, "ERR" }, { EVENT_NEW_DESC, "NEWDESC" }, { EVENT_ADDRMAP, "ADDRMAP" }, - { EVENT_AUTHDIR_NEWDESCS, "AUTHDIR_NEWDESCS" }, { EVENT_DESCCHANGED, "DESCCHANGED" }, { EVENT_NS, "NS" }, { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, @@ -1182,7 +1195,10 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(events, const char *, ev) { - if (!strcasecmp(ev, "EXTENDED")) { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); continue; } else { int i; @@ -1459,8 +1475,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 { @@ -1640,12 +1658,12 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, if (smartlist_len(reply)) { ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_write_to_buf(r, sz, TO_CONN(conn)); + connection_buf_add(r, sz, TO_CONN(conn)); tor_free(r); } else { const char *response = "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_write_to_buf(response, strlen(response), TO_CONN(conn)); + connection_buf_add(response, strlen(response), TO_CONN(conn)); } SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); @@ -1674,6 +1692,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")) { @@ -1729,7 +1749,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, #else int myUid = geteuid(); tor_asprintf(answer, "%d", myUid); - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/user")) { #ifdef _WIN32 *answer = tor_strdup(""); @@ -1742,7 +1762,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, } else { *answer = tor_strdup(""); } - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/descriptor-limit")) { int max_fds = get_max_sockets(); tor_asprintf(answer, "%d", max_fds); @@ -1828,6 +1848,8 @@ getinfo_helper_listeners(control_connection_t *control_conn, if (!strcmp(question, "net/listeners/or")) type = CONN_TYPE_OR_LISTENER; + else if (!strcmp(question, "net/listeners/extor")) + type = CONN_TYPE_EXT_OR_LISTENER; else if (!strcmp(question, "net/listeners/dir")) type = CONN_TYPE_DIR_LISTENER; else if (!strcmp(question, "net/listeners/socks")) @@ -1836,6 +1858,8 @@ getinfo_helper_listeners(control_connection_t *control_conn, type = CONN_TYPE_AP_TRANS_LISTENER; else if (!strcmp(question, "net/listeners/natd")) type = CONN_TYPE_AP_NATD_LISTENER; + else if (!strcmp(question, "net/listeners/httptunnel")) + type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; else if (!strcmp(question, "net/listeners/dns")) type = CONN_TYPE_AP_DNS_LISTENER; else if (!strcmp(question, "net/listeners/control")) @@ -1870,7 +1894,7 @@ 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) @@ -1878,27 +1902,42 @@ getinfo_helper_dir(control_connection_t *control_conn, (void) control_conn; if (!strcmpstart(question, "desc/id/")) { const routerinfo_t *ri = NULL; - const node_t *node = node_get_by_hex_id(question+strlen("desc/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/id/* " + "instead of desc/id/*."; + return 0; } } else if (!strcmpstart(question, "desc/name/")) { const routerinfo_t *ri = NULL; /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ const node_t *node = - node_get_by_nickname(question+strlen("desc/name/"), 1); + node_get_by_nickname(question+strlen("desc/name/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/name/* " + "instead of desc/name/*."; + return 0; } + } else if (!strcmp(question, "desc/download-enabled")) { + int r = we_fetch_router_descriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmp(question, "desc/all-recent")) { routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_new(); @@ -1968,7 +2007,7 @@ getinfo_helper_dir(control_connection_t *control_conn, return -1; } } else if (!strcmpstart(question, "md/id/")) { - const node_t *node = node_get_by_hex_id(question+strlen("md/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { @@ -1977,17 +2016,20 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "md/name/")) { /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ - const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 1); + const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); /* XXXX duplicated code */ const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { *answer = tor_strndup(md->body, md->bodylen); } + } else if (!strcmp(question, "md/download-enabled")) { + int r = we_fetch_microdescriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmpstart(question, "desc-annotations/id/")) { const routerinfo_t *ri = NULL; const node_t *node = - node_get_by_hex_id(question+strlen("desc-annotations/id/")); + node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); if (node) ri = node->ri; if (ri) { @@ -2028,7 +2070,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); @@ -2044,6 +2086,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) { @@ -2114,7 +2162,7 @@ download_status_to_string(const download_status_t *dl) if (dl) { /* Get some substrings of the eventual output ready */ - format_iso_time(tbuf, dl->next_attempt_at); + format_iso_time(tbuf, download_status_get_next_attempt_at(dl)); switch (dl->schedule) { case DL_SCHED_GENERIC: @@ -2539,7 +2587,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)); } { @@ -2594,6 +2642,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 @@ -2819,12 +2869,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; @@ -2834,13 +2885,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; @@ -2866,12 +2917,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, @@ -2899,6 +2971,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, @@ -2974,9 +3048,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."), @@ -3058,6 +3136,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 } }; @@ -3122,7 +3202,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. */ @@ -3137,20 +3217,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; } @@ -3166,7 +3252,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, size_t esc_len; esc_len = write_escaped_data(v, strlen(v), &esc); connection_printf_to_buf(conn, "250+%s=\r\n", k); - connection_write_to_buf(esc, esc_len, TO_CONN(conn)); + connection_buf_add(esc, esc_len, TO_CONN(conn)); tor_free(esc); } } @@ -3177,8 +3263,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; } @@ -3324,7 +3410,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, nodes = smartlist_new(); SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 1); + const node_t *node = node_get_by_nickname(n, 0); if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; @@ -3350,7 +3436,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; " @@ -3358,10 +3445,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); @@ -3377,7 +3460,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) { @@ -3519,24 +3603,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) { @@ -4039,6 +4108,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 { @@ -4074,7 +4151,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* Extract the first argument (either HSAddress or DescID). */ arg1 = smartlist_get(args, 0); /* Test if it's an HS address without the .onion part. */ - if (rend_valid_service_id(arg1)) { + if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; } else if (strcmpstart(arg1, v2_str) == 0 && rend_valid_descriptor_id(arg1 + v2_str_len) && @@ -4084,7 +4161,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; } @@ -4100,7 +4177,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *server; server = arg + strlen(opt_server); - node = node_get_by_hex_id(server); + node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", server); @@ -4186,7 +4263,7 @@ handle_control_hspost(control_connection_t *conn, SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { if (!strcasecmpstart(arg, opt_server)) { const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server); + const node_t *node = node_get_by_hex_id(server, 0); if (!node || !node->rs) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", @@ -4718,7 +4795,7 @@ handle_control_del_onion(control_connection_t *conn, return 0; const char *service_id = smartlist_get(args, 0); - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } @@ -4859,6 +4936,38 @@ peek_connection_has_control0_command(connection_t *conn) return peek_buf_has_control0_command(conn->inbuf); } +static int +peek_connection_has_http_command(connection_t *conn) +{ + return peek_buf_has_http_command(conn->inbuf); +} + +static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" + "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor's ControlPort is not an HTTP proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor's control port" + " as an HTTP proxy.\n" + "This is not correct: Tor's default SOCKS proxy port is 9050.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -4892,12 +5001,21 @@ connection_control_process_inbuf(control_connection_t *conn) sizeof(buf)-6); body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); - connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); return 0; } + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); + return 0; + } + again: while (1) { size_t last_idx; @@ -4905,7 +5023,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = connection_fetch_from_buf_line(TO_CONN(conn), + r = connection_buf_get_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -5473,15 +5591,20 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) { circuit_t *circ; origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); circ = circuit_get_by_edge_conn(edge_conn); if (circ && CIRCUIT_IS_ORIGIN(circ)) { @@ -5503,6 +5626,8 @@ control_event_stream_bandwidth_used(void) if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { smartlist_t *conns = get_connection_array(); edge_connection_t *edge_conn; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -5512,11 +5637,14 @@ control_event_stream_bandwidth_used(void) if (!edge_conn->n_read && !edge_conn->n_written) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); edge_conn->n_written = edge_conn->n_read = 0; } @@ -5532,6 +5660,8 @@ int control_event_circ_bandwidth_used(void) { origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) return 0; @@ -5541,11 +5671,15 @@ control_event_circ_bandwidth_used(void) ocirc = TO_ORIGIN_CIRCUIT(circ); if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_CIRC_BANDWIDTH_USED, - "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu " + "TIME=%s\r\n", ocirc->global_identifier, (unsigned long)ocirc->n_read_circ_bw, - (unsigned long)ocirc->n_written_circ_bw); + (unsigned long)ocirc->n_written_circ_bw, + tbuf); ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; } SMARTLIST_FOREACH_END(circ); @@ -5716,7 +5850,7 @@ control_event_circuit_cell_stats(void) if (!get_options()->TestingEnableCellStatsEvent || !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) return 0; - cell_stats = tor_malloc(sizeof(cell_stats_t));; + cell_stats = tor_malloc(sizeof(cell_stats_t)); SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->testing_cell_stats) continue; @@ -5951,47 +6085,6 @@ control_event_address_mapped(const char *from, const char *to, time_t expires, return 0; } -/** The authoritative dirserver has received a new descriptor that - * has passed basic syntax checks and is properly self-signed. - * - * Notify any interested party of the new descriptor and what has - * been done with it, and also optionally give an explanation/reason. */ -int -control_event_or_authdir_new_descriptor(const char *action, - const char *desc, size_t desclen, - const char *msg) -{ - char firstline[1024]; - char *buf; - size_t totallen; - char *esc = NULL; - size_t esclen; - - if (!EVENT_IS_INTERESTING(EVENT_AUTHDIR_NEWDESCS)) - return 0; - - tor_snprintf(firstline, sizeof(firstline), - "650+AUTHDIR_NEWDESC=\r\n%s\r\n%s\r\n", - action, - msg ? msg : ""); - - /* Escape the server descriptor properly */ - esclen = write_escaped_data(desc, desclen, &esc); - - totallen = strlen(firstline) + esclen + 1; - buf = tor_malloc(totallen); - strlcpy(buf, firstline, totallen); - strlcpy(buf+strlen(firstline), esc, totallen); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - buf); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - "650 OK\r\n"); - tor_free(esc); - tor_free(buf); - - return 0; -} - /** Cached liveness for network liveness events and GETINFO */ @@ -6053,9 +6146,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); @@ -6483,7 +6576,7 @@ monitor_owning_controller_process(const char *process_spec) msg); owning_controller_process_spec = NULL; tor_cleanup(); - exit(0); + exit(1); } } @@ -6681,80 +6774,112 @@ control_event_bootstrap(bootstrap_status_t status, int progress) } /** Called when Tor has failed to make bootstrapping progress in a way - * that indicates a problem. <b>warn</b> gives a hint as to why, and - * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> - * is the connection that caused this problem. + * that indicates a problem. <b>warn</b> gives a human-readable hint + * as to why, and <b>reason</b> provides a controller-facing short + * tag. <b>conn</b> is the connection that caused this problem and + * can be NULL if a connection cannot be easily identified. */ -MOCK_IMPL(void, - control_event_bootstrap_problem, (const char *warn, int reason, - or_connection_t *or_conn)) +void +control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn) { int status = bootstrap_percent; const char *tag = "", *summary = ""; char buf[BOOTSTRAP_MSG_LEN]; const char *recommendation = "ignore"; int severity; + char *or_id = NULL, *hostaddr = NULL; + or_connection_t *or_conn = NULL; /* bootstrap_percent must not be in "undefined" state here. */ tor_assert(status >= 0); - if (or_conn->have_noted_bootstrap_problem) - return; - - or_conn->have_noted_bootstrap_problem = 1; - if (bootstrap_percent == 100) return; /* already bootstrapped; nothing to be done here. */ bootstrap_problems++; if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) - recommendation = "warn"; - - if (reason == END_OR_CONN_REASON_NO_ROUTE) - recommendation = "warn"; - - /* If we are using bridges and all our OR connections are now - closed, it means that we totally failed to connect to our - bridges. Throw a warning. */ - if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) - recommendation = "warn"; + dowarn = 1; if (we_are_hibernating()) - recommendation = "ignore"; + dowarn = 0; while (status>=0 && bootstrap_status_to_string(status, &tag, &summary) < 0) status--; /* find a recognized status string based on current progress */ status = bootstrap_percent; /* set status back to the actual number */ - severity = !strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO; + severity = dowarn ? LOG_WARN : LOG_INFO; + + if (dowarn) + recommendation = "warn"; + + if (conn && conn->type == CONN_TYPE_OR) { + /* XXX TO_OR_CONN can't deal with const */ + or_conn = TO_OR_CONN((connection_t *)conn); + or_id = tor_strdup(hex_str(or_conn->identity_digest, DIGEST_LEN)); + } else { + or_id = tor_strdup("?"); + } + + if (conn) + tor_asprintf(&hostaddr, "%s:%d", conn->address, (int)conn->port); + else + hostaddr = tor_strdup("?"); log_fn(severity, LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " - "count %d; recommendation %s; host %s at %s:%d)", - status, summary, warn, - orconn_end_reason_to_control_string(reason), + "count %d; recommendation %s; host %s at %s)", + status, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - or_conn->base_.port); + or_id, hostaddr); connection_or_report_broken_states(severity, LD_HANDSHAKE); tor_snprintf(buf, sizeof(buf), "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " - "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s:%d\"", - bootstrap_percent, tag, summary, warn, - orconn_end_reason_to_control_string(reason), bootstrap_problems, + "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s\"", + bootstrap_percent, tag, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - (int)or_conn->base_.port); + or_id, hostaddr); tor_snprintf(last_sent_bootstrap_message, sizeof(last_sent_bootstrap_message), "WARN %s", buf); control_event_client_status(LOG_WARN, "%s", buf); + + tor_free(hostaddr); + tor_free(or_id); +} + +/** Called when Tor has failed to make bootstrapping progress in a way + * that indicates a problem. <b>warn</b> gives a hint as to why, and + * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> + * is the connection that caused this problem. + */ +MOCK_IMPL(void, +control_event_bootstrap_prob_or, (const char *warn, int reason, + or_connection_t *or_conn)) +{ + int dowarn = 0; + + if (or_conn->have_noted_bootstrap_problem) + return; + + or_conn->have_noted_bootstrap_problem = 1; + + if (reason == END_OR_CONN_REASON_NO_ROUTE) + dowarn = 1; + + /* If we are using bridges and all our OR connections are now + closed, it means that we totally failed to connect to our + bridges. Throw a warning. */ + if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) + dowarn = 1; + + control_event_bootstrap_problem(warn, + orconn_end_reason_to_control_string(reason), + TO_CONN(or_conn), dowarn); } /** We just generated a new summary of which countries we've seen clients @@ -6864,8 +6989,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); } @@ -6881,19 +7008,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) { @@ -6978,10 +7111,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; } @@ -7002,8 +7134,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 : ""); @@ -7083,28 +7217,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, @@ -7114,9 +7250,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; } @@ -7131,7 +7267,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); } @@ -7150,7 +7288,7 @@ control_event_hs_descriptor_upload_failed(const char *id_digest, id_digest); return; } - control_event_hs_descriptor_upload_end("UPLOAD_FAILED", onion_address, + control_event_hs_descriptor_upload_end("FAILED", onion_address, id_digest, reason); } @@ -7183,5 +7321,4 @@ control_testing_set_global_event_mask(uint64_t mask) { global_event_mask = mask; } -#endif - +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/control.h b/src/or/control.h index 6330c85571..e957b593a6 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 */ /** @@ -33,7 +33,6 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -#define EVENT_AUTHDIR_NEWDESCS 0x000D #define EVENT_NS 0x000F int control_event_is_interesting(int event); @@ -64,10 +63,6 @@ int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, time_t expires, const char *error, const int cached); -int control_event_or_authdir_new_descriptor(const char *action, - const char *desc, - size_t desclen, - const char *msg); int control_event_my_descriptor_changed(void); int control_event_network_liveness_update(int liveness); int control_event_networkstatus_changed(smartlist_t *statuses); @@ -104,9 +99,11 @@ void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); int control_event_bootstrap(bootstrap_status_t status, int progress); -MOCK_DECL(void, control_event_bootstrap_problem,(const char *warn, +MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, int reason, or_connection_t *or_conn)); +void control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn); void control_event_clients_seen(const char *controller_str); void control_event_transport_launched(const char *mode, @@ -169,8 +166,8 @@ void control_free_all(void); #define EVENT_WARN_MSG 0x000A #define EVENT_ERR_MSG 0x000B #define EVENT_ADDRMAP 0x000C -/* Exposed above */ -// #define EVENT_AUTHDIR_NEWDESCS 0x000D +/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We + can reclaim 0x000D. */ #define EVENT_DESCCHANGED 0x000E /* Exposed above */ // #define EVENT_NS 0x000F @@ -228,7 +225,7 @@ MOCK_DECL(STATIC void, queue_control_event_string,(uint16_t event, char *msg)); void control_testing_set_global_event_mask(uint64_t mask); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Helper structure: temporarily stores cell statistics for a circuit. */ typedef struct cell_stats_t { @@ -262,6 +259,11 @@ STATIC crypto_pk_t *add_onion_helper_keyarg(const char *arg, int discard_pk, 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, @@ -285,8 +287,12 @@ 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 /* defined(CONTROL_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONTROL_H) */ diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index fd6de6ea7c..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 */ /** @@ -11,8 +11,11 @@ * The multithreading backend for this module is in workqueue.c; this module * specializes workqueue.c. * - * Right now, we only use this for processing onionskins, and invoke it mostly - * from onion.c. + * 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" @@ -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, @@ -372,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); @@ -474,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>. * @@ -531,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..d39851325f 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, @@ -25,5 +33,5 @@ void cpuworker_log_onionskin_overhead(int severity, int onionskin_type, const char *onionskin_type_name); void cpuworker_cancel_circ_handshake(or_circuit_t *circ); -#endif +#endif /* !defined(TOR_CPUWORKER_H) */ diff --git a/src/or/dircollate.c b/src/or/dircollate.c index 033a7afe0f..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 */ /** @@ -53,7 +53,7 @@ ddmap_entry_free(ddmap_entry_t *e) static ddmap_entry_t * ddmap_entry_new(int n_votes) { - return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) + + return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) + sizeof(vote_routerstatus_t *) * n_votes); } diff --git a/src/or/dircollate.h b/src/or/dircollate.h index 358c730cbb..7932e18998 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 */ /** @@ -62,7 +62,7 @@ struct dircollator_s { * identity digests .*/ smartlist_t *all_rsa_sha1_lst; }; -#endif +#endif /* defined(DIRCOLLATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRCOLLATE_H) */ diff --git a/src/or/directory.c b/src/or/directory.c index f285e4c6ed..7f47d71ad6 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1,21 +1,31 @@ /* 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 "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -33,16 +43,45 @@ #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: @@ -62,12 +101,9 @@ * 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); static void connection_dir_download_routerdesc_failed(dir_connection_t *conn); static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn); @@ -80,21 +116,10 @@ static void dir_routerdesc_download_failed(smartlist_t *failed, int was_extrainfo, int was_descriptor_digests); static void dir_microdesc_download_failed(smartlist_t *failed, - int status_code); -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); + int status_code, + const char *dir_id); +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); @@ -106,6 +131,7 @@ static void connection_dir_close_consensus_fetches( #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? */ @@ -120,29 +146,58 @@ static void connection_dir_close_consensus_fetches( /********* 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. */ +/** 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) +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_HSDESC: + case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: + case DIR_PURPOSE_UPLOAD_RENDDESC_V2: + case DIR_PURPOSE_FETCH_RENDDESC_V2: + case DIR_PURPOSE_FETCH_HSDESC: + case DIR_PURPOSE_UPLOAD_HSDESC: + return 1; + case DIR_PURPOSE_SERVER: + default: + 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 @@ -193,6 +248,10 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_HSDESC: + return "hidden-service descriptor fetch"; + case DIR_PURPOSE_UPLOAD_HSDESC: + return "hidden-service descriptor upload"; case DIR_PURPOSE_FETCH_MICRODESC: return "microdescriptor fetch"; } @@ -347,7 +406,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, @@ -359,10 +418,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); @@ -380,10 +443,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. */ @@ -398,7 +460,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(); @@ -407,7 +470,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(dir_purpose, guard_state_out); if (node) rs = node->rs; } else { @@ -423,13 +486,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, @@ -441,52 +585,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. @@ -495,25 +604,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. */ - const node_t *node = choose_random_dirguard(type); + const node_t *node = guards_choose_dirguard(dir_purpose, &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)) { @@ -544,9 +662,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 */ } @@ -565,17 +683,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)); } @@ -595,15 +719,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); } @@ -703,106 +829,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); - - /* 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; - } - - 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 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, 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. */ @@ -828,6 +854,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. */ } @@ -983,6 +1014,7 @@ directory_must_use_begindir(const or_options_t *options) /** 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 @@ -993,15 +1025,24 @@ directory_must_use_begindir(const or_options_t *options) */ 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; + 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"; @@ -1014,7 +1055,7 @@ directory_command_should_use_begindir(const or_options_t *options, } if (indirection == DIRIND_ONEHOP) { /* We're firewalled and want a direct OR connection */ - if (!fascist_firewall_allows_address_addr(addr, or_port, + if (!fascist_firewall_allows_address_addr(or_addr, or_port, FIREWALL_OR_CONNECTION, 0, 0)) { *reason = "ORPort not reachable"; return 0; @@ -1033,79 +1074,320 @@ directory_command_should_use_begindir(const or_options_t *options, 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); + tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC); + + directory_request_t *result = tor_malloc_zero(sizeof(*result)); + tor_addr_make_null(&result->or_addr_port.addr, AF_INET); + 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_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_free(directory_request_t *req) { - tor_addr_port_t or_ap, dir_ap; + 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_request_set_dir_addr_port(directory_request_t *req, + const tor_addr_port_t *p) +{ + 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 an object containing HS connection identifier to be associated with + * this fetch request. Note that only an alias to <b>ident</b> is stored, so + * the <b>ident</b> object must outlive the request. + */ +void +directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC); } + req->hs_ident = ident; +} +/** Set a static circuit_guard_state_t object to affliate with the request in + * <b>req</b>. This object will receive notification when the attempt to + * connect to the guard either succeeds or fails. */ +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.) */ -int -is_sensitive_dir_purpose(uint8_t dir_purpose) +/** + * 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) { - return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) || - (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) || - (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)); + 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 +directory_request_set_dir_from_routerstatus(directory_request_t *req) -/** 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) { - tor_assert(or_addr_port); - tor_assert(dir_addr_port); + 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; +} + +/** + * 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(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); @@ -1115,10 +1397,9 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, 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, - &begindir_reason); + 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); @@ -1137,7 +1418,7 @@ 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)); - if (is_sensitive_dir_purpose(dir_purpose)) { + if (purpose_needs_anonymity(dir_purpose, router_purpose, resource)) { tor_assert(anonymized_connection || rend_non_anonymous_mode_enabled(options)); } @@ -1163,9 +1444,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; @@ -1192,8 +1473,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 */ @@ -1203,6 +1492,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: @@ -1214,9 +1508,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, /* fall through */ case 0: /* 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. */ @@ -1239,6 +1531,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 @@ -1262,9 +1562,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, } 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); connection_start_reading(ENTRY_TO_CONN(linked_conn)); @@ -1274,7 +1572,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, /** 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 @@ -1367,15 +1665,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. @@ -1383,7 +1689,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); @@ -1437,6 +1746,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,9 +1810,10 @@ directory_send_command(dir_connection_t *conn, tor_assert(payload); httpcommand = "POST"; url = tor_strdup("/tor/"); - if (why) { - smartlist_add_asprintf(headers, "X-Desc-Gen-Reason: %s\r\n", why); + if (!why) { + why = "for no reason at all"; } + smartlist_add_asprintf(headers, "X-Desc-Gen-Reason: %s\r\n", why); break; } case DIR_PURPOSE_UPLOAD_VOTE: @@ -1509,12 +1835,25 @@ directory_send_command(dir_connection_t *conn, httpcommand = "GET"; tor_asprintf(&url, "/tor/rendezvous2/%s", resource); break; + case DIR_PURPOSE_FETCH_HSDESC: + tor_assert(resource); + tor_assert(strlen(resource) <= ED25519_BASE64_LEN); + tor_assert(!payload); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/hs/3/%s", resource); + break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: tor_assert(!resource); tor_assert(payload); httpcommand = "POST"; url = tor_strdup("/tor/rendezvous2/publish"); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + tor_assert(resource); + tor_assert(payload); + httpcommand = "POST"; + tor_asprintf(&url, "/tor/hs/%s/publish", resource); + break; default: tor_assert(0); return; @@ -1529,8 +1868,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_buf_add(request, request_len, TO_CONN(conn)); + + url_len = strlen(url); + total_request_len += url_len; + connection_buf_add(url, url_len, TO_CONN(conn)); tor_free(url); if (!strcmp(httpcommand, "POST") || payload) { @@ -1545,15 +1890,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_buf_add(request, request_len, TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ - connection_write_to_buf(payload, payload_len, TO_CONN(conn)); + connection_buf_add(payload, payload_len, TO_CONN(conn)); + total_request_len += payload_len; } 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 @@ -1568,15 +1925,41 @@ directory_send_command(dir_connection_t *conn, STATIC int parse_http_url(const char *headers, char **url) { + char *command = NULL; + if (parse_http_command(headers, &command, url) < 0) { + return -1; + } + if (strcmpstart(*url, "/tor/")) { + char *new_url = NULL; + tor_asprintf(&new_url, "/tor%s%s", + *url[0] == '/' ? "" : "/", + *url); + tor_free(*url); + *url = new_url; + } + tor_free(command); + return 0; +} + +/** Parse an HTTP request line at the start of a headers string. On failure, + * return -1. On success, set *<b>command_out</b> to a copy of the HTTP + * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and + * return 0. */ +int +parse_http_command(const char *headers, char **command_out, char **url_out) +{ + const char *command, *end_of_command; char *s, *start, *tmp; s = (char *)eat_whitespace_no_nl(headers); if (!*s) return -1; + command = s; s = (char *)find_whitespace(s); /* get past GET/POST */ if (!*s) return -1; + end_of_command = s; s = (char *)eat_whitespace_no_nl(s); if (!*s) return -1; - start = s; /* this is it, assuming it's valid */ + start = s; /* this is the URL, assuming it's valid */ s = (char *)find_whitespace(start); if (!*s) return -1; @@ -1607,13 +1990,8 @@ parse_http_url(const char *headers, char **url) return -1; } - if (s-start < 5 || strcmpstart(start,"/tor/")) { /* need to rewrite it */ - *url = tor_malloc(s - start + 5); - strlcpy(*url,"/tor", s-start+5); - strlcat((*url)+4, start, s-start+1); - } else { - *url = tor_strndup(start, s-start); - } + *url_out = tor_memdup_nulterm(start, s-start); + *command_out = tor_memdup_nulterm(command, end_of_command - command); return 0; } @@ -1621,7 +1999,7 @@ parse_http_url(const char *headers, char **url) * <b>which</b>. The key should be given with a terminating colon and space; * this function copies everything after, up to but not including the * following \\r\\n. */ -static char * +char * http_get_header(const char *headers, const char *which) { const char *cp = headers; @@ -1738,16 +2116,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)); @@ -1770,15 +2147,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; } @@ -1820,6 +2197,146 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } +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_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. * @@ -1832,21 +2349,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; 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); - 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, @@ -1868,17 +2390,39 @@ connection_dir_client_reached_eof(dir_connection_t *conn) &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) { @@ -1916,540 +2460,861 @@ 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; - } + 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; + case DIR_PURPOSE_FETCH_HSDESC: + rv = handle_response_fetch_hsdesc_v3(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, - conn->identity_digest))<0) { - log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, - "Unable to load %s consensus directory downloaded from " - "server '%s:%d'. I'll try again soon.", - 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; } - /* If we launched other fetches for this consensus, cancel them. */ - connection_dir_close_consensus_fetches(conn, flavname); - - /* 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."); - } - - 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"; + } - /* - * 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 ((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; + } - 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 { - 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); + /* If we launched other fetches for this consensus, cancel them. */ + connection_dir_close_consensus_fetches(conn, flavname); + + /* update the list of routers and directory guards */ + routers_update_all_from_networkstatus(now, 3); + update_microdescs_from_networkstatus(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()); + //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, 1); } - 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 */ - /* 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); - - 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/")); + tor_assert_nonfatal(!tor_mem_is_zero(conn->identity_digest, DIGEST_LEN)); + 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, conn->identity_digest); + 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, conn->identity_digest); } - /* return 0 in all cases, since we don't want to mark any - * dirservers down just because they don't like us. */ + if (mds && smartlist_len(mds)) { + control_event_bootstrap(BOOTSTRAP_STATUS_LOADING_DESCRIPTORS, + count_loading_descriptors_progress()); + directory_info_has_arrived(now, 0, 1); + } + 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; + 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 v3 hidden service + * descriptor. + **/ +STATIC int +handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + tor_assert(conn->hs_ident); + + log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))", + (int)body_len, status_code, escaped(reason)); + + switch (status_code) { + case 200: + /* We got something: Try storing it in the cache. */ + if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { + log_warn(LD_REND, "Failed to store hidden service descriptor"); + } else { + log_info(LD_REND, "Stored hidden service descriptor successfully."); + TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; + hs_client_desc_has_arrived(conn->hs_ident); } - /* 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; + case 404: + /* Not there. We'll retry when connection_about_to_close_connection() + * tries to clean this conn up. */ + log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " + "Retrying at another directory."); + /* TODO: Inform the control port */ + break; + case 400: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "query? Retrying at another directory.", + escaped(reason)); + break; + default: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status %d (%s) response unexpected from HSDir server " + "'%s:%d'. Retrying at another directory.", + status_code, escaped(reason), TO_CONN(conn)->address, + TO_CONN(conn)->port); + break; + } + + return 0; +} + +/** + * Handler function: processes a response to a request for a v2 hidden service + * descriptor. + **/ +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_fn(LOG_PROTOCOL_WARN, LD_REND, + "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + /* XXX: Trigger control event. */ + break; + default: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + /* XXX: Trigger control event. */ + break; } - tor_free(body); tor_free(headers); tor_free(reason); + return 0; } @@ -2524,6 +3389,33 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } +/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection + * that tried to fetch an HS descriptor, check if it successfuly fetched it, + * or if we need to try again. */ +static void +refetch_hsdesc_if_needed(dir_connection_t *dir_conn) +{ + connection_t *conn = TO_CONN(dir_conn); + + /* If we were trying to fetch a v2 rend desc and did not succeed, retry as + * needed. (If a fetch is successful, the connection state is changed to + * DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to + * mark that refetching is unnecessary.) */ + if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && + dir_conn->rend_data && + rend_valid_v2_service_id( + rend_data_get_address(dir_conn->rend_data))) { + rend_client_refetch_v2_renddesc(dir_conn->rend_data); + } + + /* Check for v3 rend desc fetch */ + if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC && + dir_conn->hs_ident && + !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) { + hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk); + } +} + /** Called when we're about to finally unlink and free a directory connection: * perform necessary accounting and cleanup */ void @@ -2536,31 +3428,39 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) * failed: forget about this router, and maybe try again. */ connection_dir_request_failed(dir_conn); } - /* If we were trying to fetch a v2 rend desc and did not succeed, - * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that - * refetching is unnecessary.) */ - if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && - dir_conn->rend_data && - strlen(dir_conn->rend_data->onion_address) == REND_SERVICE_ID_LEN_BASE32) - rend_client_refetch_v2_renddesc(dir_conn->rend_data); + + refetch_hsdesc_if_needed(dir_conn); } /** Create an http response for the client <b>conn</b> out of * <b>status</b> and <b>reason_phrase</b>. Write it to <b>conn</b>. */ static void -write_http_status_line(dir_connection_t *conn, int status, +write_short_http_response(dir_connection_t *conn, int status, const char *reason_phrase) { - char buf[256]; - if (tor_snprintf(buf, sizeof(buf), "HTTP/1.0 %d %s\r\n\r\n", - status, reason_phrase ? reason_phrase : "OK") < 0) { - log_warn(LD_BUG,"status line too long."); - return; + char *buf = NULL; + char *datestring = NULL; + + IF_BUG_ONCE(!reason_phrase) { /* bullet-proofing */ + reason_phrase = "unspecified"; + } + + if (server_mode(get_options())) { + /* include the Date: header, but only if we're a relay or bridge */ + char datebuf[RFC1123_TIME_LEN+1]; + format_rfc1123_time(datebuf, time(NULL)); + tor_asprintf(&datestring, "Date: %s\r\n", datebuf); } + + tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n%s\r\n", + status, reason_phrase, datestring?datestring:""); + log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); - connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); + connection_buf_add(buf, strlen(buf), TO_CONN(conn)); + + tor_free(datestring); + tor_free(buf); } /** Write the header for an HTTP/1.0 response onto <b>conn</b>-\>outbuf, @@ -2633,20 +3533,120 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, memcpy(cp, "\r\n", 3); else tor_assert(0); - connection_write_to_buf(tmp, strlen(tmp), TO_CONN(conn)); + connection_buf_add(tmp, strlen(tmp), TO_CONN(conn)); } /** As write_http_response_header_impl, but sets encoding and content-typed * 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); +} + +/** As write_http_response_headers, but assumes extra_headers is NULL */ +static void +write_http_response_header(dir_connection_t *conn, ssize_t length, + compress_method_t method, + long cache_lifetime) +{ + write_http_response_headers(conn, length, method, NULL, cache_lifetime); +} + +/** 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 << x is set in the bitfield. */ +STATIC unsigned +parse_accept_encoding_header(const char *h) +{ + 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; +} + +/** 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) +{ + smartlist_t *methods = smartlist_new(); + char *header = NULL; + compress_method_t method; + unsigned i; + + 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)); + } + + header = smartlist_join_strings(methods, ", ", 0, NULL); + smartlist_free(methods); + + return header; } /** Decide whether a client would accept the consensus we have. @@ -2664,48 +3664,46 @@ write_http_response_header(dir_connection_t *conn, ssize_t length, * 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) { + smartlist_free(voters); + 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) - != (int) want_len) { - 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()) { @@ -2723,8 +3721,9 @@ choose_compression_level(ssize_t n_bytes) /** Information passed to handle a GET request. */ typedef struct get_handler_args_t { - /** True if the client asked for compressed data. */ - int compressed; + /** 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; @@ -2762,8 +3761,8 @@ static int handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args); static int handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args); -static int handle_get_rendezvous2(dir_connection_t *conn, - const get_handler_args_t *args); +static int handle_get_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, @@ -2779,7 +3778,8 @@ static const url_table_ent_t url_table[] = { { "/tor/server/", 1, handle_get_descriptor }, { "/tor/extra/", 1, handle_get_descriptor }, { "/tor/keys/", 1, handle_get_keys }, - { "/tor/rendezvous2/", 1, handle_get_rendezvous2 }, + { "/tor/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 }, @@ -2791,14 +3791,14 @@ static const url_table_ent_t url_table[] = { * conn-\>outbuf. If the request is unrecognized, send a 404. * Return 0 if we handled this successfully, or -1 if we need to close * the connection. */ -STATIC int -directory_handle_command_get(dir_connection_t *conn, const char *headers, - const char *req_body, size_t req_body_len) +MOCK_IMPL(STATIC int, +directory_handle_command_get,(dir_connection_t *conn, const char *headers, + const char *req_body, size_t req_body_len)) { char *url, *url_mem, *header; time_t if_modified_since = 0; - int compressed; - size_t url_len; + int zlib_compressed_in_url; + unsigned compression_methods_supported; /* We ignore the body of a GET request. */ (void)req_body; @@ -2809,7 +3809,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, conn->base_.state = DIR_CONN_STATE_SERVER_WRITING; if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } if ((header = http_get_header(headers, "If-Modified-Since: "))) { @@ -2828,18 +3828,33 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); url_mem = url; - url_len = strlen(url); - compressed = url_len > 2 && !strcmp(url+url_len-2, ".z"); - if (compressed) { - url[url_len-2] = '\0'; - url_len -= 2; + { + size_t url_len = strlen(url); + + zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); + if (zlib_compressed_in_url) { + url[url_len-2] = '\0'; + } + } + + 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); + } + + /* Remove all methods that we don't both support. */ + compression_methods_supported &= tor_compress_get_supported_method_bitmask(); get_handler_args_t args; args.url = url; args.headers = headers; args.if_modified_since = if_modified_since; - args.compressed = compressed; + args.compression_supported = compression_methods_supported; int i, result = -1; for (i = 0; url_table[i].string; ++i) { @@ -2856,7 +3871,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); result = 0; done: @@ -2882,141 +3897,530 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) * this page no matter what.] */ write_http_response_header_impl(conn, dlen, "text/html", "identity", NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME); - connection_write_to_buf(frontpage, dlen, TO_CONN(conn)); + connection_buf_add(frontpage, dlen, TO_CONN(conn)); } else { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); } return 0; } -/** Helper function for GET /tor/status-vote/current/consensus +/** 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 int -handle_get_current_consensus(dir_connection_t *conn, - const get_handler_args_t *args) +static void +warn_consensus_is_too_old(const struct consensus_cache_entry_t *consensus, + const char *flavor, time_t now) { - const char *url = args->url; - const int compressed = args->compressed; - const time_t if_modified_since = args->if_modified_since; +#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; - { - /* v3 network status fetch. */ - smartlist_t *dir_fps = smartlist_new(); - 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); - } + if (consensus_cache_entry_get_valid_until(consensus, &valid_until)) + return; - v = networkstatus_get_latest_consensus_by_flavor(flav); + 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); + } +} - 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; - } +/** + * 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; +} + +/** 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 - { - char *fp = tor_malloc_zero(DIGEST_LEN); - if (flavor) - strlcpy(fp, flavor, DIGEST_LEN); - tor_free(flavor); - smartlist_add(dir_fps, fp); +/** + * 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; } - lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0; } + } 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 (!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; + 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; } + } - 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; + if (consdiffmgr_find_consensus(&result, flav, + FALLBACK_COMPRESS_METHOD) == CONSDIFF_AVAILABLE) { + tor_assert_nonfatal(result); + *compression_used_out = FALLBACK_COMPRESS_METHOD; + return result; + } + + 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); + } - size_t dlen = dirserv_estimate_data_size(dir_fps, 0, compressed); - if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) { - log_debug(LD_DIRSERV, - "Client asked for network status lists, but we've been " - "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); + /* 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; + } - geoip_note_ns_response(GEOIP_REJECT_BUSY); - goto done; + 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_short_http_response(conn, 404, "Couldn't parse request"); + goto done; + } + + if (digest_list_contains_best_consensus(req.flav, + req.diff_from_digests)) { + write_short_http_response(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_short_http_response(conn, 404, "No such diff available"); + // XXXX warn_consensus_is_too_old(v, req.flavor, now); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } + + 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_short_http_response(conn, 404, "Consensus is too old"); + warn_consensus_is_too_old(cached_consensus, req.flavor, now); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } + + if (cached_consensus && req.want_fps && + !client_likes_consensus(cached_consensus, req.want_fps)) { + write_short_http_response(conn, 404, "Consensus not signed by sufficient " + "number of requested authorities"); + geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS); + goto done; + } + + 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; - 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); + 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); + + if (!smartlist_len(conn->spool) && !n_expired) { + write_short_http_response(conn, 404, "Not found"); + geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); + goto done; + } else if (!smartlist_len(conn->spool)) { + write_short_http_response(conn, 304, "Not modified"); + geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); + goto done; + } - /* Prime the connection with some data. */ - conn->dir_spool_src = DIR_SPOOL_NETWORKSTATUS; - connection_dirserv_flushed_some(conn); + 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_short_http_response(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; } @@ -3026,12 +4430,14 @@ static int handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; { int current; ssize_t body_len = 0; ssize_t estimated_len = 0; + /* 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; /* XXXX?? should actually use vote intervals. */ url += strlen("/tor/status-vote/"); @@ -3079,15 +4485,36 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) smartlist_free(fps); } if (!smartlist_len(dir_items) && !smartlist_len(items)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto vote_done; } + + /* 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; @@ -3095,27 +4522,30 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) }); if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } - write_http_response_header(conn, body_len ? body_len : -1, 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_buf_add_compress(c, strlen(c), conn, 0)); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf(c, strlen(c), TO_CONN(conn))); + connection_buf_add(c, strlen(c), TO_CONN(conn))); } } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - connection_write_to_buf(compressed ? d->dir_z : d->dir, - compressed ? d->dir_z_len : d->dir_len, + connection_buf_add(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: @@ -3133,44 +4563,51 @@ static int handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const compress_method_t compress_method = + find_best_compression_method(args->compression_supported, 1); + int clear_spool = 1; { - smartlist_t *fps = smartlist_new(); + 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)) { - write_http_status_line(conn, 404, "Not found"); - SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp)); - smartlist_free(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_short_http_response(conn, 404, "Not found"); goto done; } - size_t 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); + write_short_http_response(conn, 503, "Directory busy, try again later"); 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; } @@ -3180,71 +4617,93 @@ static int handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const 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/"))) { - size_t dlen; int res; - const char *msg; + 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/")) { - if (smartlist_len(conn->fingerprint_stack) == 1) - cache_lifetime = ROUTERDESC_CACHE_LIFETIME; - } else if (!strcmpstart(url, "authority")) { - cache_lifetime = ROUTERDESC_CACHE_LIFETIME; - } else if (!strcmpstart(url, "all")) { - cache_lifetime = FULL_DIR_CACHE_LIFETIME; - } else if (!strcmpstart(url, "d/")) { - if (smartlist_len(conn->fingerprint_stack) == 1) - cache_lifetime = ROUTERDESC_BY_DIGEST_CACHE_LIFETIME; - } - 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; + } + + 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 (!dirserv_have_any_serverdesc(conn->fingerprint_stack, - conn->dir_spool_src)) { - res = -1; - msg = "Not found"; + 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) - 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)) { + if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) { + if (msg == NULL) + msg = "Not found"; + write_short_http_response(conn, 404, msg); + } else { + if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); - conn->dir_spool_src = DIR_SPOOL_NONE; + write_short_http_response(conn, 503, + "Directory busy, try again later"); + 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: - return 0; + if (clear_spool) + dir_conn_clear_spool(conn); + return 0; } /** Helper function for GET /tor/keys/... @@ -3253,7 +4712,8 @@ static int handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) { const char *url = args->url; - const int compressed = args->compressed; + const 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(); @@ -3298,41 +4758,47 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) }); smartlist_free(fp_sks); } else { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); goto keys_done; } if (!smartlist_len(certs)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto keys_done; } SMARTLIST_FOREACH(certs, authority_cert_t *, c, if (c->cache_info.published_on < if_modified_since) SMARTLIST_DEL_CURRENT(certs, c)); if (!smartlist_len(certs)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); goto keys_done; } len = 0; 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)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + if (global_write_bucket_low(TO_CONN(conn), + compress_method != NO_METHOD ? len/2 : len, + 2)) { + write_short_http_response(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_buf_add_compress( + c->cache_info.signed_descriptor_body, + c->cache_info.signed_descriptor_len, + conn, 0)); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf(c->cache_info.signed_descriptor_body, + connection_buf_add(c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, TO_CONN(conn))); } @@ -3347,7 +4813,8 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) /** Helper function for GET /tor/rendezvous2/ */ static int -handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args) +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)) { @@ -3359,24 +4826,61 @@ handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args) 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); - connection_write_to_buf(descp, strlen(descp), TO_CONN(conn)); + write_http_response_header(conn, strlen(descp), NO_METHOD, 0); + connection_buf_add(descp, strlen(descp), TO_CONN(conn)); break; case 0: /* well-formed but not present */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); break; case -1: /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); break; } } else { /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); } goto done; } else { /* Not encrypted! */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); + } + done: + return 0; +} + +/** 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_short_http_response(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_short_http_response(conn, 404, "Not found"); + goto done; } + + /* Found requested descriptor! Pass it to this nice client. */ + write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); + connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn)); + done: return 0; } @@ -3404,7 +4908,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, if (!header || tor_memneq(digest, options->BridgePassword_AuthDigest_, DIGEST256_LEN)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); tor_free(header); goto done; } @@ -3413,8 +4917,8 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, /* all happy now. send an answer. */ status = networkstatus_getinfo_by_purpose("bridge", time(NULL)); size_t dlen = strlen(status); - write_http_response_header(conn, dlen, 0, 0); - connection_write_to_buf(status, dlen, TO_CONN(conn)); + write_http_response_header(conn, dlen, NO_METHOD, 0); + connection_buf_add(status, dlen, TO_CONN(conn)); tor_free(status); goto done; } @@ -3430,20 +4934,104 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) { const char robots[] = "User-agent: *\r\nDisallow: /\r\n"; size_t len = strlen(robots); - write_http_response_header(conn, len, 0, ROBOTS_CACHE_LIFETIME); - connection_write_to_buf(robots, len, TO_CONN(conn)); + write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME); + connection_buf_add(robots, len, TO_CONN(conn)); } return 0; } +/* 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; + } + + /* 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; + } + + 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 * request. Look for an uploaded server descriptor or rendezvous * 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(); @@ -3455,12 +5043,12 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, if (!public_server_mode(options)) { log_info(LD_DIR, "Rejected dir post request from %s " "since we're not a public relay.", conn->base_.address); - write_http_status_line(conn, 503, "Not acting as a public relay"); + write_short_http_response(conn, 503, "Not acting as a public relay"); goto done; } if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); @@ -3469,26 +5057,41 @@ 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, + write_short_http_response(conn, 400, "Invalid v2 service descriptor rejected"); } else { - write_http_status_line(conn, 200, "Service descriptor (v2) stored"); + write_short_http_response(conn, 200, "Service descriptor (v2) stored"); log_info(LD_REND, "Handled v2 rendezvous descriptor post: accepted"); } goto done; } + /* 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_short_http_response(conn, code, msg); + goto done; + } + if (!authdir_mode(options)) { /* we just provide cached directories; we don't want to * receive anything. */ - write_http_status_line(conn, 400, "Nonauthoritative directory does not " + write_short_http_response(conn, 400, "Nonauthoritative directory does not " "accept posted server descriptors"); goto done; } - if (authdir_mode_handles_descs(options, -1) && + if (authdir_mode(options) && !strcmp(url,"/tor/")) { /* server descriptor post */ const char *msg = "[None]"; uint8_t purpose = authdir_mode_bridge(options) ? @@ -3497,15 +5100,8 @@ 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) { - write_http_status_line(conn, 200, msg); + if (r == ROUTER_ADDED_SUCCESSFULLY) { + write_short_http_response(conn, 200, msg); } else if (WRA_WAS_OUTDATED(r)) { write_http_response_header_impl(conn, -1, NULL, NULL, "X-Descriptor-Not-New: Yes\r\n", -1); @@ -3514,7 +5110,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, "Rejected router descriptor or extra-info from %s " "(\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, 400, msg); + write_short_http_response(conn, 400, msg); } goto done; } @@ -3524,12 +5120,12 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, const char *msg = "OK"; int status; if (dirvote_add_vote(body, &msg, &status)) { - write_http_status_line(conn, status, "Vote stored"); + write_short_http_response(conn, status, "Vote stored"); } else { tor_assert(msg); log_warn(LD_DIRSERV, "Rejected vote from %s (\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, status, msg); + write_short_http_response(conn, status, msg); } goto done; } @@ -3538,17 +5134,18 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, !strcmp(url,"/tor/post/consensus-signature")) { /* sigs on consensus. */ const char *msg = NULL; if (dirvote_add_signatures(body, conn->base_.address, &msg)>=0) { - write_http_status_line(conn, 200, msg?msg:"Signatures stored"); + write_short_http_response(conn, 200, msg?msg:"Signatures stored"); } else { log_warn(LD_DIR, "Unable to store signatures posted by %s: %s", conn->base_.address, msg?msg:"???"); - write_http_status_line(conn, 400, msg?msg:"Unable to store signatures"); + write_short_http_response(conn, 400, + msg?msg:"Unable to store signatures"); } goto done; } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); done: tor_free(url); @@ -3560,7 +5157,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; @@ -3631,7 +5228,7 @@ 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) { + if (conn->spool) { log_warn(LD_BUG, "Emptied a dirserv buffer, but it's still spooling!"); connection_mark_for_close(TO_CONN(conn)); } else { @@ -3701,7 +5298,7 @@ connection_dir_finished_connecting(dir_connection_t *conn) * Helper function for download_status_increment_failure(), * download_status_reset(), and download_status_increment_attempt(). */ STATIC const smartlist_t * -find_dl_schedule(download_status_t *dls, const or_options_t *options) +find_dl_schedule(const download_status_t *dls, const or_options_t *options) { switch (dls->schedule) { case DL_SCHED_GENERIC: @@ -3742,7 +5339,15 @@ find_dl_schedule(download_status_t *dls, const or_options_t *options) } } case DL_SCHED_BRIDGE: - return options->TestingBridgeDownloadSchedule; + if (options->UseBridges && num_bridges_usable(0) > 0) { + /* A bridge client that is sure that one or more of its bridges are + * running can afford to wait longer to update bridge descriptors. */ + return options->TestingBridgeDownloadSchedule; + } else { + /* A bridge client which might have no running bridges, must try to + * get bridge descriptors straight away. */ + return options->TestingBridgeBootstrapDownloadSchedule; + } default: tor_assert(0); } @@ -3770,59 +5375,76 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, const smartlist_t *schedule = find_dl_schedule(dls, options); tor_assert(schedule != NULL && smartlist_len(schedule) >= 2); *min = *((int *)(smartlist_get(schedule, 0))); + /* Increment on failure schedules always use exponential backoff, but they + * have a smaller limit when they're deterministic */ if (dls->backoff == DL_SCHED_DETERMINISTIC) *max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1)))); else *max = INT_MAX; } -/** Advance one delay step. The algorithm is to use the previous delay to - * compute an increment, we construct a value uniformly at random between - * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger - * than max_delay, and return it. +/** As next_random_exponential_delay() below, but does not compute a random + * value. Instead, compute the range of values that + * next_random_exponential_delay() should use when computing its random value. + * Store the low bound into *<b>low_bound_out</b>, and the high bound into + * *<b>high_bound_out</b>. Guarantees that the low bound is strictly less + * than the high bound. */ +STATIC void +next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay) +{ + // This is the "decorrelated jitter" approach, from + // https://www.awsarchitectureblog.com/2015/03/backoff.html + // The formula is + // sleep = min(cap, random_between(base, sleep * 3)) + + const int delay_times_3 = delay < INT_MAX/3 ? delay * 3 : INT_MAX; + *low_bound_out = base_delay; + if (delay_times_3 > base_delay) { + *high_bound_out = delay_times_3; + } else { + *high_bound_out = base_delay+1; + } +} + +/** Advance one delay step. The algorithm will generate a random delay, + * such that each failure is possibly (random) longer than the ones before. + * + * We then clamp that value to be no larger than max_delay, and return it. + * + * The <b>base_delay</b> parameter is lowest possible delay time (can't be + * zero); the <b>backoff_position</b> parameter is the number of times we've + * generated a delay; and the <b>delay</b> argument is the most recently used + * delay. * * Requires that delay is less than INT_MAX, and delay is in [0,max_delay]. */ STATIC int -next_random_exponential_delay(int delay, int max_delay) +next_random_exponential_delay(int delay, + int base_delay, + int max_delay) { /* Check preconditions */ if (BUG(max_delay < 0)) max_delay = 0; if (BUG(delay > max_delay)) delay = max_delay; - if (delay == INT_MAX) - return INT_MAX; /* prevent overflow */ if (BUG(delay < 0)) delay = 0; - /* How much are we willing to add to the delay? */ - int max_increment; - int multiplier = 3; /* no more than quadruple the previous delay */ - if (get_options()->TestingTorNetwork) { - /* Decrease the multiplier in testing networks. This reduces the variance, - * so that bootstrap is more reliable. */ - multiplier = 2; /* no more than triple the previous delay */ - } + if (base_delay < 1) + base_delay = 1; - if (delay && delay < (INT_MAX-1) / multiplier) { - max_increment = delay * multiplier; - } else if (delay) { - max_increment = INT_MAX-1; - } else { - max_increment = 1; - } + int low_bound=0, high_bound=max_delay; - if (BUG(max_increment < 1)) - max_increment = 1; + next_random_exponential_delay_range(&low_bound, &high_bound, + delay, base_delay); - /* the + 1 here is so that we always wait longer than last time. */ - int increment = crypto_rand_int(max_increment)+1; + int rand_delay = crypto_rand_int_range(low_bound, high_bound); - if (increment < max_delay - delay) - return delay + increment; - else - return max_delay; + return MIN(rand_delay, max_delay); } /** Find the current delay for dls based on schedule or min_delay/ @@ -3861,7 +5483,7 @@ download_status_schedule_get_delay(download_status_t *dls, delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); } else if (dls->backoff == DL_SCHED_RANDOM_EXPONENTIAL) { /* Check if we missed a reset somehow */ - if (dls->last_backoff_position > dls_schedule_position) { + IF_BUG_ONCE(dls->last_backoff_position > dls_schedule_position) { dls->last_backoff_position = 0; dls->last_delay_used = 0; } @@ -3871,7 +5493,7 @@ download_status_schedule_get_delay(download_status_t *dls, while (dls->last_backoff_position < dls_schedule_position) { /* Do one increment step */ - delay = next_random_exponential_delay(delay, max_delay); + delay = next_random_exponential_delay(delay, min_delay, max_delay); /* Update our position */ ++(dls->last_backoff_position); } @@ -3954,6 +5576,11 @@ download_status_increment_failure(download_status_t *dls, int status_code, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + /* count the failure */ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) { ++dls->n_download_failures; @@ -3978,14 +5605,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, download_status_log_helper(item, !dls->increment_on, "failed", "concurrently", dls->n_download_failures, - increment, dls->next_attempt_at, now); + increment, + download_status_get_next_attempt_at(dls), + now); if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { /* stop this schedule retrying on failure, it will launch concurrent * connections instead */ return TIME_MAX; } else { - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); } } @@ -4006,6 +5635,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { /* this schedule should retry on failure, and not launch any concurrent attempts */ @@ -4024,9 +5658,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item, download_status_log_helper(item, dls->increment_on, "attempted", "on failure", dls->n_download_attempts, - delay, dls->next_attempt_at, now); + delay, download_status_get_next_attempt_at(dls), + now); - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); +} + +static time_t +download_status_get_initial_delay_from_now(const download_status_t *dls) +{ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + /* We use constant initial delays, even in exponential backoff + * schedules. */ + return time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Reset <b>dls</b> so that it will be considered downloadable @@ -4038,8 +5682,8 @@ download_status_increment_attempt(download_status_t *dls, const char *item, * (We find the zeroth element of the download schedule, and set * next_attempt_at to be the appropriate offset from 'now'. In most * cases this means setting it to 'now', so the item will be immediately - * downloadable; in the case of bridge descriptors, the zeroth element - * is an hour from now.) */ + * downloadable; when using authorities with fallbacks, there is a few seconds' + * delay.) */ void download_status_reset(download_status_t *dls) { @@ -4047,11 +5691,9 @@ download_status_reset(download_status_t *dls) || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule(dls, get_options()); - dls->n_download_failures = 0; dls->n_download_attempts = 0; - dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + dls->next_attempt_at = download_status_get_initial_delay_from_now(dls); dls->last_backoff_position = 0; dls->last_delay_used = 0; /* Don't reset dls->want_authority or dls->increment_on */ @@ -4078,6 +5720,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; } @@ -4125,13 +5773,14 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ } -/** Called when a connection to download microdescriptors has failed in whole - * or in part. <b>failed</b> is a list of every microdesc digest we didn't - * get. <b>status_code</b> is the http status code we received. Reschedule the - * microdesc downloads as appropriate. */ +/** Called when a connection to download microdescriptors from relay with + * <b>dir_id</b> has failed in whole or in part. <b>failed</b> is a list + * of every microdesc digest we didn't get. <b>status_code</b> is the http + * status code we received. Reschedule the microdesc downloads as + * appropriate. */ static void dir_microdesc_download_failed(smartlist_t *failed, - int status_code) + int status_code, const char *dir_id) { networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); @@ -4142,17 +5791,26 @@ dir_microdesc_download_failed(smartlist_t *failed, if (! consensus) return; + + /* We failed to fetch a microdescriptor from 'dir_id', note it down + * so that we don't try the same relay next time... */ + microdesc_note_outdated_dirserver(dir_id); + SMARTLIST_FOREACH_BEGIN(failed, const char *, d) { rs = router_get_mutable_consensus_status_by_descriptor_digest(consensus,d); if (!rs) continue; dls = &rs->dl_status; if (dls->n_download_failures >= - get_options()->TestingMicrodescMaxDownloadTries) + get_options()->TestingMicrodescMaxDownloadTries) { continue; - { + } + + { /* Increment the failure count for this md fetch */ char buf[BASE64_DIGEST256_LEN+1]; digest256_to_base64(buf, d); + log_info(LD_DIR, "Failed to download md %s from %s", + buf, hex_str(dir_id, DIGEST_LEN)); download_status_increment_failure(dls, status_code, buf, server, now); } @@ -4314,3 +5972,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 629b3ead90..5e6a91d3e7 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, @@ -41,43 +43,60 @@ typedef enum { int directory_must_use_begindir(const or_options_t *options); -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); +/** + * 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_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); + +void directory_request_set_routerstatus(directory_request_t *req, + const routerstatus_t *rs); +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 parse_http_command(const char *headers, + char **command_out, char **url_out); +char *http_get_header(const char *headers, const char *which); -int connection_dir_is_encrypted(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); #define DSR_HEX (1<<0) #define DSR_BASE64 (1<<1) @@ -86,7 +105,12 @@ void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, 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); @@ -108,12 +132,19 @@ time_t download_status_increment_attempt(download_status_t *dls, void download_status_reset(download_status_t *dls); static int download_status_is_ready(download_status_t *dls, time_t now, int max_failures); +time_t download_status_get_next_attempt_at(const download_status_t *dls); + /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is * ready to get its download reattempted. */ static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->backoff == DL_SCHED_DETERMINISTIC) { /* Deterministic schedules can hit an endpoint; exponential backoff * schedules just wait longer and longer. */ @@ -122,7 +153,7 @@ download_status_is_ready(download_status_t *dls, time_t now, if (!under_failure_limit) return 0; } - return dls->next_attempt_at <= now; + return download_status_get_next_attempt_at(dls) <= now; } static void download_status_mark_impossible(download_status_t *dl); @@ -136,40 +167,134 @@ 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); -/* Yes, these two functions are confusingly similar. - * Let's sort that out in #20077. */ -int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose); -int is_sensitive_dir_purpose(uint8_t dir_purpose); +int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, + const char *resource); + +#ifdef DIRECTORY_PRIVATE + +/** A structure to hold arguments passed into each directory response + * handler */ +typedef struct response_handler_args_t { + int status_code; + const char *reason; + const char *body; + size_t body_len; + const char *headers; +} response_handler_args_t; + +struct directory_request_t { + /** + * These fields specify which directory we're contacting. Routerstatus, + * if present, overrides the other fields. + * + * @{ */ + tor_addr_port_t or_addr_port; + tor_addr_port_t dir_addr_port; + char digest[DIGEST_LEN]; + + const routerstatus_t *routerstatus; + /** @} */ + /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what + * kind of operation we'll be doing (upload/download), and of what kind + * of document. */ + uint8_t dir_purpose; + /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo + * and extrainfo docs. */ + uint8_t router_purpose; + /** Enum: determines whether to anonymize, and whether to use dirport or + * orport. */ + dir_indirection_t indirection; + /** Alias to the variable part of the URL for this request */ + const char *resource; + /** Alias to the payload to upload (if any) */ + const char *payload; + /** Number of bytes to upload from payload</b> */ + size_t payload_len; + /** Value to send in an if-modified-since header, or 0 for none. */ + time_t if_modified_since; + /** Hidden-service-specific information v2. */ + const rend_data_t *rend_query; + /** Extra headers to append to the request */ + config_line_t *additional_headers; + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; + /** Used internally to directory.c: gets informed when the attempt to + * connect to the directory succeeds or fails, if that attempt bears on the + * directory's usability as a directory guard. */ + struct circuit_guard_state_t *guard_state; +}; + +struct get_handler_args_t; +STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, + const struct get_handler_args_t *args); +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); + +STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args); +STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, + const response_handler_args_t *args); + +#endif /* defined(DIRECTORY_PRIVATE) */ #ifdef TOR_UNIT_TESTS -/* Used only by directory.c and test_dir.c */ +/* Used only by test_dir.c and test_hs_cache.c */ STATIC int parse_http_url(const char *headers, char **url); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, 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); +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); -#endif +STATIC int next_random_exponential_delay(int delay, + int base_delay, + int max_delay); + +STATIC void next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay); + +STATIC int parse_hs_version_from_post(const char *url, const char *prefix, + const char **end_pos); + +STATIC unsigned parse_accept_encoding_header(const char *h); +#endif /* defined(TOR_UNIT_TESTS) */ + +#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) +/* Used only by directory.c and test_dir.c */ + +/* no more than quadruple the previous delay (multiplier + 1) */ +#define DIR_DEFAULT_RANDOM_MULTIPLIER (3) +/* no more than triple the previous delay */ +#define DIR_TEST_NET_RANDOM_MULTIPLIER (2) + +#endif /* defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRECTORY_H) */ diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 177009208d..a1ccf03e91 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,6 +13,8 @@ #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" @@ -80,14 +82,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 */ @@ -273,6 +284,13 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, return FP_REJECT; } + /* 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 @@ -319,13 +337,11 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, } return FP_REJECT; } -#endif +#endif /* defined(DISABLE_DISABLING_ED25519) */ } } - 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 @@ -577,6 +593,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; } @@ -660,9 +678,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) ri->nickname, source, (int)ri->cache_info.signed_descriptor_len, MAX_DESCRIPTOR_UPLOAD_SIZE); *msg = "Router descriptor was too large."; - control_event_or_authdir_new_descriptor("REJECTED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -681,9 +696,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) router_describe(ri), source); *msg = "Not replacing router descriptor; no information has changed since " "the last one with this identity."; - control_event_or_authdir_new_descriptor("DROPPED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_IS_ALREADY_KNOWN; goto fail; } @@ -691,10 +703,19 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) /* Do keypinning again ... this time, to add the pin if appropriate */ int keypin_status; if (ri->cache_info.signing_key_cert) { + ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key; + /* First let's validate this pubkey before pinning it */ + if (ed25519_validate_pubkey(pkey) < 0) { + log_warn(LD_DIRSERV, "Received bad key from %s (source %s)", + router_describe(ri), source); + routerinfo_free(ri); + return ROUTER_AUTHDIR_REJECTS; + } + + /* Now pin it! */ keypin_status = keypin_check_and_add( (const uint8_t*)ri->cache_info.identity_digest, - ri->cache_info.signing_key_cert->signing_key.pubkey, - ! key_pinning); + pkey->pubkey, ! key_pinning); } else { keypin_status = keypin_check_lone_rsa( (const uint8_t*)ri->cache_info.identity_digest); @@ -707,7 +728,12 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because " "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."; + *msg = "Looks like your keypair has changed? This authority previously " + "recorded a different RSA identity for this Ed25519 identity (or vice " + "versa.) Did you replace or copy some of your key files, but not " + "the others? You should either restore the expected keypair, or " + "delete your keys and restart Tor to start your relay with a new " + "identity."; r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -724,14 +750,11 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) r = router_add_to_routerlist(ri, msg, 0, 0); if (!WRA_WAS_ADDED(r)) { /* unless the routerinfo was fine, just out-of-date */ - if (WRA_WAS_REJECTED(r)) - control_event_or_authdir_new_descriptor("REJECTED", desc, desclen, *msg); log_info(LD_DIRSERV, "Did not add descriptor from '%s' (source: %s): %s.", nickname, source, *msg ? *msg : "(no message)"); } else { smartlist_t *changed; - control_event_or_authdir_new_descriptor("ACCEPTED", desc, desclen, *msg); changed = smartlist_new(); smartlist_add(changed, ri); @@ -867,6 +890,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) @@ -979,6 +1005,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, @@ -1011,7 +1040,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)); @@ -1163,8 +1192,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) @@ -1172,11 +1203,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. +/** 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) @@ -1191,7 +1225,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 @@ -1240,8 +1274,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; @@ -1252,7 +1286,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)); } @@ -1275,6 +1309,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; @@ -1284,6 +1319,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) @@ -2043,7 +2080,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); @@ -2053,7 +2090,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); @@ -2145,12 +2182,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_); @@ -2163,9 +2196,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); @@ -2325,8 +2356,8 @@ 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 @@ -2698,7 +2729,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) } cp+=strlen("bw="); - out->bw_kb = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr); + out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr); if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) { log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s", escaped(orig_line)); @@ -3051,16 +3082,16 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, /* 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"); + "Link=4 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"); + "Link=4 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"); + "Link=4 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"); + "Link=3-4 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)); @@ -3077,7 +3108,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, 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); } } @@ -3086,9 +3117,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) { @@ -3133,58 +3164,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; @@ -3280,7 +3314,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; @@ -3292,8 +3327,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)) { @@ -3301,7 +3354,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), @@ -3349,21 +3402,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. */ @@ -3375,7 +3438,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); } } @@ -3418,410 +3482,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 XXXX */; + 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_buf_add_compress((const char*)body, bodylen, conn, 0); + } else { + connection_buf_add((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_buf_add_compress( + ptr + spooled->cached_dir_offset, + bytes, conn, 0); + } else { + connection_buf_add(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_buf_add_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 624cd7e0b7..5f195442ce 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); @@ -63,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); @@ -88,13 +145,6 @@ 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 *version, @@ -132,7 +182,7 @@ STATIC int dirserv_has_measured_bw(const char *node_id); STATIC int dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, smartlist_t *vote_routerstatuses); -#endif +#endif /* defined(DIRSERV_PRIVATE) */ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses); @@ -140,4 +190,18 @@ int dirserv_read_measured_bandwidths(const char *from_file, int dirserv_read_guardfraction_file(const char *fname, smartlist_t *vote_routerstatuses); -#endif +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 /* !defined(TOR_DIRSERV_H) */ diff --git a/src/or/dirvote.c b/src/or/dirvote.c index e6a745612e..fbd112d488 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 @@ -26,6 +26,39 @@ /** * \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 @@ -250,11 +283,11 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, 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.) */ @@ -273,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); @@ -509,8 +541,8 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, if (cur_n > most_n || (cur && cur_n == most_n && cur->status.published_on > most_published)) { most = cur; - most_n = cur_n; - most_published = cur->status.published_on; + // most_n = cur_n; // unused after this point. + // most_published = cur->status.published_on; // unused after this point. } tor_assert(most); @@ -712,12 +744,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list, } } SMARTLIST_FOREACH_END(k_v_pair); - if (n_found == 1) + if (n_found == 1) { return value; - else if (BUG(n_found > 1)) - return default_val; - else + } else { + tor_assert_nonfatal(n_found == 0); return default_val; + } } /** Minimum number of directory authorities voting for a parameter to @@ -902,7 +934,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) @@ -984,7 +1016,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; @@ -997,7 +1029,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); @@ -1066,7 +1098,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; @@ -1078,7 +1110,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; @@ -1112,7 +1144,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. @@ -1295,7 +1327,17 @@ compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes) * 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, @@ -1314,7 +1356,7 @@ 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; @@ -1346,6 +1388,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. */ { @@ -1385,7 +1437,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); @@ -1418,7 +1470,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); @@ -1482,9 +1534,9 @@ networkstatus_compute_consensus(smartlist_t *votes, total_authorities); if (smartlist_len(param_list)) { params = smartlist_join_strings(param_list, " ", 0, NULL); - smartlist_add(chunks, tor_strdup("params ")); + 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) { @@ -2071,10 +2123,10 @@ 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(chunks, tor_strdup("\n")); + 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); @@ -2127,7 +2179,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; @@ -2178,7 +2230,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); @@ -2205,7 +2257,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, @@ -2518,7 +2570,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); @@ -2743,48 +2795,10 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset) return next; } -/* Using the time <b>now</b>, return the next voting valid-after time. */ -time_t -get_next_valid_after_time(time_t now) -{ - time_t next_valid_after_time; - const or_options_t *options = get_options(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - - next_valid_after_time = new_voting_schedule->interval_starts; - voting_schedule_free(new_voting_schedule); - - return next_valid_after_time; -} - -static voting_schedule_t voting_schedule; - -/** Set voting_schedule to hold the timing for the next vote we should be - * doing. */ -void -dirvote_recalculate_timing(const or_options_t *options, time_t now) -{ - voting_schedule_t *new_voting_schedule; - - if (!authdir_mode_v3(options)) { - return; - } - - /* get the new voting schedule */ - new_voting_schedule = get_voting_schedule(options, now, LOG_NOTICE); - tor_assert(new_voting_schedule); - - /* Fill in the global static struct now */ - memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); - voting_schedule_free(new_voting_schedule); -} - /* Populate and return a new voting_schedule_t that can be used to schedule * voting. The object is allocated on the heap and it's the responsibility of * the caller to free it. Can't fail. */ -voting_schedule_t * +static voting_schedule_t * get_voting_schedule(const or_options_t *options, time_t now, int severity) { int interval, vote_delay, dist_delay; @@ -2839,7 +2853,7 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity) /** Frees a voting_schedule_t. This should be used instead of the generic * tor_free. */ -void +static void voting_schedule_free(voting_schedule_t *voting_schedule_to_free) { if (!voting_schedule_to_free) @@ -2847,13 +2861,53 @@ voting_schedule_free(voting_schedule_t *voting_schedule_to_free) tor_free(voting_schedule_to_free); } +static voting_schedule_t voting_schedule; + +/* Using the time <b>now</b>, return the next voting valid-after time. */ +time_t +dirvote_get_next_valid_after_time(void) +{ + /* This is a safe guard in order to make sure that the voting schedule + * static object is at least initialized. Using this function with a zeroed + * voting schedule can lead to bugs. */ + if (tor_mem_is_zero((const char *) &voting_schedule, + sizeof(voting_schedule))) { + dirvote_recalculate_timing(get_options(), time(NULL)); + voting_schedule.created_on_demand = 1; + } + return voting_schedule.interval_starts; +} + +/** Set voting_schedule to hold the timing for the next vote we should be + * doing. All type of tor do that because HS subsystem needs the timing as + * well to function properly. */ +void +dirvote_recalculate_timing(const or_options_t *options, time_t now) +{ + voting_schedule_t *new_voting_schedule; + + /* get the new voting schedule */ + new_voting_schedule = get_voting_schedule(options, now, LOG_INFO); + tor_assert(new_voting_schedule); + + /* Fill in the global static struct now */ + memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); + voting_schedule_free(new_voting_schedule); +} + /** Entry point: Take whatever voting actions are pending as of <b>now</b>. */ void dirvote_act(const or_options_t *options, time_t now) { if (!authdir_mode_v3(options)) return; - if (!voting_schedule.voting_starts) { + tor_assert_nonfatal(voting_schedule.voting_starts); + /* If we haven't initialized this object through this codeflow, we need to + * recalculate the timings to match our vote. The reason to do that is if we + * have a voting schedule initialized 1 minute ago, the voting timings might + * not be aligned to what we should expect with "now". This is especially + * true for TestingTorNetwork using smaller timings. */ + if (voting_schedule.created_on_demand) { char *keys = list_v3_auth_ids(); authority_cert_t *c = get_my_v3_authority_cert(); log_notice(LD_DIR, "Scheduling voting. Known authority IDs are %s. " @@ -3628,8 +3682,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; } @@ -3949,14 +4003,15 @@ dirvote_format_all_microdesc_vote_lines(const routerinfo_t *ri, time_t now, while ((ep = entries)) { char buf[128]; vote_microdesc_hash_t *h; - dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, - ep->low, ep->high); - h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); - h->microdesc_hash_line = tor_strdup(buf); - h->next = result; - result = h; - ep->md->last_listed = now; - smartlist_add(microdescriptors_out, ep->md); + if (dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, + ep->low, ep->high) >= 0) { + h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); + h->microdesc_hash_line = tor_strdup(buf); + h->next = result; + result = h; + ep->md->last_listed = now; + smartlist_add(microdescriptors_out, ep->md); + } entries = ep->next; tor_free(ep); } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index efd233ef5f..72a35fea6d 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 25 +#define MAX_SUPPORTED_CONSENSUS_METHOD 26 /** Lowest consensus method where microdesc consensuses omit any entry * with no microdesc. */ @@ -111,6 +111,10 @@ * 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.) */ @@ -164,12 +168,14 @@ typedef struct { int have_fetched_missing_signatures; /* True iff we have published our consensus. */ int have_published_consensus; -} voting_schedule_t; -voting_schedule_t *get_voting_schedule(const or_options_t *options, - time_t now, int severity); - -void voting_schedule_free(voting_schedule_t *voting_schedule_to_free); + /* True iff this voting schedule was set on demand meaning not through the + * normal vote operation of a dirauth or when a consensus is set. This only + * applies to a directory authority that needs to recalculate the voting + * timings only for the first vote even though this object was initilized + * prior to voting. */ + int created_on_demand; +} voting_schedule_t; void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); time_t dirvote_get_start_of_next_interval(time_t now, @@ -177,7 +183,7 @@ time_t dirvote_get_start_of_next_interval(time_t now, int offset); void dirvote_recalculate_timing(const or_options_t *options, time_t now); void dirvote_act(const or_options_t *options, time_t now); -time_t get_next_valid_after_time(time_t now); +time_t dirvote_get_next_valid_after_time(void); /* invoked on timers and by outside triggers. */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, @@ -234,7 +240,11 @@ 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); -#endif +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 /* defined(DIRVOTE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRVOTE_H) */ diff --git a/src/or/dns.c b/src/or/dns.c index c1e3c3256e..7dc3575f53 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 */ /** @@ -102,7 +102,7 @@ static void assert_cache_ok_(void); #define assert_cache_ok() assert_cache_ok_() #else #define assert_cache_ok() STMT_NIL -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ static void assert_resolve_ok(cached_resolve_t *resolve); /** Hash table of cached_resolve objects. */ @@ -160,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. */ @@ -181,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); } @@ -365,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); } @@ -412,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, @@ -948,14 +961,14 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn) for (pend = resolve->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } -#else +#else /* !(1) */ cached_resolve_t **resolve; HT_FOREACH(resolve, cache_map, &cache_root) { for (pend = (*resolve)->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } } -#endif +#endif /* 1 */ } /** Log an error and abort if any connection waiting for a DNS resolve is @@ -1383,7 +1396,7 @@ configure_nameservers(int force) evdns_base_load_hosts(the_evdns_base, sandbox_intern_string("/etc/hosts")); } -#endif +#endif /* defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) */ log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, sandbox_intern_string(conf_fname)))) { @@ -1421,7 +1434,7 @@ configure_nameservers(int force) tor_free(resolv_conf_fname); resolv_conf_mtime = 0; } -#endif +#endif /* defined(_WIN32) */ #define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v)) @@ -1565,10 +1578,11 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, escaped_safe_str(hostname)); tor_free(escaped_address); } else if (count) { - log_warn(LD_EXIT, "eventdns returned only non-IPv4 answers for %s.", + log_info(LD_EXIT, "eventdns returned only unrecognized answer types " + " for %s.", escaped_safe_str(string_address)); } else { - log_warn(LD_BUG, "eventdns returned no addresses or error for %s!", + log_info(LD_EXIT, "eventdns returned no addresses or error for %s.", escaped_safe_str(string_address)); } } @@ -1759,7 +1773,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"); @@ -1783,7 +1797,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, @@ -1944,7 +1958,7 @@ dns_launch_wildcard_checks(void) launch_wildcard_check(8, 16, ipv6, ".com"); launch_wildcard_check(8, 16, ipv6, ".org"); launch_wildcard_check(8, 16, ipv6, ".net"); - } + } } } @@ -2037,7 +2051,7 @@ assert_resolve_ok(cached_resolve_t *resolve) tor_assert(!resolve->hostname); else tor_assert(!resolve->result_ipv4.addr_ipv4); -#endif +#endif /* 0 */ /*XXXXX ADD MORE */ } } @@ -2087,7 +2101,7 @@ assert_cache_ok_(void) smartlist_pqueue_assert_ok(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx)); + offsetof(cached_resolve_t, minheap_idx)); SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, { @@ -2101,10 +2115,10 @@ assert_cache_ok_(void) }); } -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ -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 951a2a3467..28d9f947b4 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 */ /** @@ -64,7 +64,7 @@ set_exitconn_info_from_resolve,(edge_connection_t *exitconn, MOCK_DECL(STATIC int, launch_resolve,(cached_resolve_t *resolve)); -#endif +#endif /* defined(DNS_PRIVATE) */ -#endif +#endif /* !defined(TOR_DNS_H) */ diff --git a/src/or/dns_structs.h b/src/or/dns_structs.h index bc6067213d..e22f23ac15 100644 --- a/src/or/dns_structs.h +++ b/src/or/dns_structs.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 */ /** @@ -98,5 +98,5 @@ typedef struct cached_resolve_t { int minheap_idx; } cached_resolve_t; -#endif +#endif /* !defined(TOR_DNS_STRUCTS_H) */ diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index f5a4f2ac0f..d254717a54 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" @@ -214,10 +226,10 @@ dnsserv_launch_request(const char *name, int reverse, TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); } -#else +#else /* !(defined(AF_UNIX)) */ TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); -#endif +#endif /* defined(AF_UNIX) */ if (reverse) entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; @@ -272,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, diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index ad0e248c83..2af366eee5 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 */ /** @@ -23,5 +23,5 @@ void dnsserv_reject_request(entry_connection_t *conn); int dnsserv_launch_request(const char *name, int is_reverse, control_connection_t *control_conn); -#endif +#endif /* !defined(TOR_DNSSERV_H) */ diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 265b6dcda1..67b0259243 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,2509 +137,3501 @@ #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); - -/* Default number of entry guards in the case where the NumEntryGuards - * consensus parameter is not set */ -#define DEFAULT_N_GUARDS 1 -/* Minimum and maximum number of entry guards (in case the NumEntryGuards - * consensus parameter is set). */ -#define MIN_N_GUARDS 1 -#define MAX_N_GUARDS 10 - -/** Return the list of entry guards, creating it if necessary. */ -const smartlist_t * -get_entry_guards(void) -{ - if (! entry_guards) - entry_guards = smartlist_new(); - return entry_guards; -} - -/** 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. - */ -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 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) { - char buf[HEX_DIGEST_LEN+1]; - int changed = 0; + /* 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); + } - *reason = NULL; + return options->UseGuardFraction; +} - /* Do we want to mark this guard as bad? */ +/** Return true iff we know a descriptor for <b>guard</b> */ +static int +guard_has_descriptor(const entry_guard_t *guard) +{ + const node_t *node = node_get_by_id(guard->identity); 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; + return 0; + return node_has_descriptor(node); +} + +/** + * Try to determine the correct type for a selection named "name", + * if <b>type</b> is GS_TYPE_INFER. + */ +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; +} - 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; - } +/** + * 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) +{ + guard_selection_t *gs; + + type = guard_selection_infer_type(type, name); + + 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 (! 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 changed; + return new_selection; } -/** 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) +/** + * Allocate the first guard context that we're planning to use, + * and make it the current context. + */ +static void +create_initial_guard_context(void) { - struct guard_retry_period_s { - time_t period_duration; - time_t interval_during_period; - }; + 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(); + } - 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 curr_guard_context; +} - time_t ith_deadline_for_retry; - time_t unreachable_for; - unsigned i; +/** Return a statically allocated human-readable description of <b>guard</b> + */ +const char * +entry_guard_describe(const entry_guard_t *guard) +{ + 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; +} - if (e->last_attempted < e->unreachable_since) - return 1; +/** 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; +} - unreachable_for = now - e->unreachable_since; +/** Return the pathbias state associated with <b>guard</b>. */ +guard_pathbias_t * +entry_guard_get_pathbias_state(entry_guard_t *guard) +{ + return &guard->pb; +} - 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; +HANDLE_IMPL(entry_guard, entry_guard_t, ATTR_UNUSED STATIC) - return (now > ith_deadline_for_retry); - } - } - return 0; +/** 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); + + time_t earliest = now - max_backdate; + time_t latest = now; + if (earliest <= 0) + earliest = 1; + if (latest <= earliest) + latest = earliest + 1; + + 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. + */ +/**@{*/ +/** + * We never let our sampled guard set grow larger than this fraction + * of the guards on the network. */ -STATIC const node_t * -entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, - const char **msg) +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) && + !router_digest_is_me(node->identity)); +} + +/** + * 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; } -/** Largest amount that we'll backdate chosen_on_date */ -#define CHOSEN_ON_DATE_SLOP (30*86400) +/** + * 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 -/** 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) + return entry_guard_add_to_sample_impl(gs, + (const uint8_t*)node->identity, + node_get_nickname(node), + NULL); +} + +/** + * 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 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(const 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", DEFAULT_N_GUARDS, - MIN_N_GUARDS, MAX_N_GUARDS); +/** 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); - if (! entry_guards) + return node && node_is_possible_guard(node); + } +} + +/** + * 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. - */ -/* XXX We could change succeeded and mark_relay_status into 'int flags'. - * Too many boolean arguments is a recipe for confusion. - */ -int -entry_guard_register_connect_status(const char *digest, int succeeded, - 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; +} + +/** + * 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; } + + return node_passes_guard_filter(options, node); } +} - /* 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 in the same family as <b>node</b>. + */ +static int +guard_in_node_family(const entry_guard_t *guard, const node_t *node) +{ + 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; } +} - if (changed) - entry_guards_changed(); - return refuse_conn ? -1 : 0; +/* Allocate and return a new exit guard restriction (where <b>exit_id</b> is of + * size DIGEST_LEN) */ +STATIC entry_guard_restriction_t * +guard_create_exit_restriction(const uint8_t *exit_id) +{ + entry_guard_restriction_t *rst = NULL; + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + rst->type = RST_EXIT_NODE; + memcpy(rst->exclude_id, exit_id, DIGEST_LEN); + return rst; } -/** When we try to choose an entry guard, should we parse and add - * config's EntryNodes first? */ -static int should_add_entry_nodes = 0; +/** If we have fewer than this many possible usable guards, don't set + * MD-availability-based restrictions: we might blacklist all of them. */ +#define MIN_GUARDS_FOR_MD_RESTRICTION 10 -/** Called when the value of EntryNodes changes in our configuration. */ -void -entry_nodes_should_be_added(void) +/** Return true if we should set md dirserver restrictions. We might not want + * to set those if our guard options are too restricted, since we don't want + * to blacklist all of them. */ +static int +should_set_md_dirserver_restriction(void) { - 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 guard_selection_t *gs = get_guard_selection_info(); + int num_usable_guards = num_reachable_filtered_guards(gs, NULL); + + /* Don't set restriction if too few reachable filtered guards. */ + if (num_usable_guards < MIN_GUARDS_FOR_MD_RESTRICTION) { + log_info(LD_GUARD, "Not setting md restriction: only %d" + " usable guards.", num_usable_guards); + return 0; + } + + /* We have enough usable guards: set MD restriction */ + return 1; } -/** 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) +/** Allocate and return an outdated md guard restriction. Return NULL if no + * such restriction is needed. */ +STATIC entry_guard_restriction_t * +guard_create_dirserver_md_restriction(void) { - 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); + entry_guard_restriction_t *rst = NULL; + + if (!should_set_md_dirserver_restriction()) { + log_debug(LD_GUARD, "Not setting md restriction: too few " + "filtered guards."); + return NULL; + } + + rst = tor_malloc_zero(sizeof(entry_guard_restriction_t)); + rst->type = RST_OUTDATED_MD_DIRSERVER; + + return rst; } -/** 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) +/* Return True if <b>guard</b> obeys the exit restriction <b>rst</b>. */ +static int +guard_obeys_exit_restriction(const entry_guard_t *guard, + const entry_guard_restriction_t *rst) { - 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); + tor_assert(rst->type == RST_EXIT_NODE); - should_add_entry_nodes = 0; + // Exclude the 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; - 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; + return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN); +} + +/** Return True if <b>guard</b> should be used as a dirserver for fetching + * microdescriptors. */ +static int +guard_obeys_md_dirserver_restriction(const entry_guard_t *guard) +{ + /* If this guard is an outdated dirserver, don't use it. */ + if (microdesc_relay_is_outdated_dirserver(guard->identity)) { + log_info(LD_GENERAL, "Skipping %s dirserver: outdated", + hex_str(guard->identity, DIGEST_LEN)); + return 0; } - { - char *string = routerset_to_string(options->EntryNodes); - log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string); - tor_free(string); + log_debug(LD_GENERAL, "%s dirserver obeys md restrictions", + hex_str(guard->identity, DIGEST_LEN)); + + return 1; +} + +/** + * 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) +{ + tor_assert(guard); + if (! rst) + return 1; // No restriction? No problem. + + if (rst->type == RST_EXIT_NODE) { + return guard_obeys_exit_restriction(guard, rst); + } else if (rst->type == RST_OUTDATED_MD_DIRSERVER) { + return guard_obeys_md_dirserver_restriction(guard); } - 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(); + tor_assert_nonfatal_unreached(); + return 0; +} - /* Split entry guards into those on the list and those not. */ +/** + * 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) +{ + unsigned was_filtered = guard->is_filtered_guard; + guard->is_filtered_guard = 0; + guard->is_usable_filtered_guard = 0; - 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)); + if (entry_guard_passes_filter(options, gs, guard)) { + guard->is_filtered_guard = 1; - 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 (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); + + if (!bool_eq(was_filtered, guard->is_filtered_guard)) { + /* This guard might now be primary or nonprimary. */ + gs->primary_guards_up_to_date = 0; + } +} + +/** + * 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(); - /* 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); + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + entry_guard_set_filtered_flags(options, gs, guard); + } SMARTLIST_FOREACH_END(guard); +} + +/** + * 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); + } + + if (exclude_primary && !gs->primary_guards_up_to_date && !no_update_primary) + entry_guards_update_primary(gs); + + /* 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(); + + smartlist_t *new_primary_guards = smartlist_new(); + smartlist_t *old_primary_guards = smartlist_new(); + smartlist_add_all(old_primary_guards, gs->primary_entry_guards); - tor_assert(all_entry_guards); + /* 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); - if (chosen_exit) { - nodelist_add_node_and_family(exit_family, chosen_exit); + /* 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 /* 1 */ + + 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))) != DIGEST_LEN) { - *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) != sizeof(d) || - 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) { - 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; - /* XXX hand new_entry_guards to this func, and move it up a - * few lines, so we don't have to re-dirty it */ - if (remove_obsolete_entry_guards(now)) - entry_guards_dirty = 1; + 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. + */ + + smartlist_t *result = smartlist_new(); + char tbuf[ISO_TIME_LEN+1]; + + tor_assert(guard); - 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_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->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; - 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; +} + +/** + * 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); - smartlist_add(bridge_list, b); + 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. If use_maybe_reachable is + * true, include bridges that might be reachable in the count. + * Otherwise, if it is false, only include bridges that have recently been + * found running in the count. + * + * We use this function to decide if we're ready to start building + * circuits through our bridges, or if we need to wait until the + * directory "server/authority" requests finish. */ +MOCK_IMPL(int, +num_bridges_usable,(int use_maybe_reachable)) { - bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, - port, - NULL); - return bridge ? bridge->socks_args : NULL; + int n_options = 0; + + if (BUG(!get_options()->UseBridges)) { + return 0; + } + guard_selection_t *gs = get_guard_selection_info(); + if (BUG(gs->type != GS_TYPE_BRIDGE)) { + return 0; + } + + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + /* Definitely not usable */ + if (guard->is_reachable == GUARD_REACHABLE_NO) + continue; + /* If we want to be really sure the bridges will work, skip maybes */ + if (!use_maybe_reachable && guard->is_reachable == GUARD_REACHABLE_MAYBE) + continue; + if (tor_digest_is_zero(guard->identity)) + continue; + const node_t *node = node_get_by_id(guard->identity); + 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 a smartlist containing all bridge identity digests */ -MOCK_IMPL(smartlist_t *, -list_bridge_identities, (void)) +/** 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) { - smartlist_t *result = NULL; - char *digest_tmp; + int mark_circuits = 0; + if (update_guard_selection_choice(get_options())) + mark_circuits = 1; - if (get_options()->UseBridges && bridge_list) { - result = smartlist_new(); + tor_assert(curr_guard_context); - 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); - } + if (entry_guards_update_all(curr_guard_context)) + mark_circuits = 1; - return result; + return mark_circuits; } -/** 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)) +/** 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 = guard_create_exit_restriction(exit_id); + tor_assert(rst); + } + 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) { - download_status_t *dl = NULL; + // 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; - 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); + 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; } - return dl; + 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); } -/** 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) +/** 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 +remove_all_entry_guards(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; + remove_all_entry_guards_for_guard_selection(get_guard_selection_info()); } -/** Do we know any descriptors for our bridges / entrynodes, and are - * all the ones we have descriptors for down? */ +/** Helper: pick a directory guard, with whatever algorithm is used. */ +const node_t * +guards_choose_dirguard(uint8_t dir_purpose, + circuit_guard_state_t **guard_state_out) +{ + const node_t *r = NULL; + entry_guard_restriction_t *rst = NULL; + + /* If we are fetching microdescs, don't query outdated dirservers. */ + if (dir_purpose == DIR_PURPOSE_FETCH_MICRODESC) { + rst = guard_create_dirserver_md_restriction(); + } + + if (entry_guard_pick_for_circuit(get_guard_selection_info(), + GUARD_USAGE_DIRGUARD, + rst, + &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 -entries_known_but_down(const or_options_t *options) +guards_retry_optimistic(const or_options_t *options) { - tor_assert(entry_list_is_constrained(options)); - return entries_retry_helper(options, 0); + if (! entry_list_is_constrained(options)) + return 0; + + mark_primary_guards_maybe_reachable(get_guard_selection_info()); + + return 1; } -/** Mark all down known bridges / entrynodes up. */ -void -entries_retry_all(const or_options_t *options) +/** + * Check if we are missing any crucial dirinfo for the guard subsystem to + * work. Return NULL if everything went well, otherwise return a newly + * allocated string with an informative error message. In the latter case, use + * the genreal descriptor information <b>using_mds</b>, <b>num_present</b> and + * <b>num_usable</b> to improve the error message. */ +char * +guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable) +{ + if (!gs->primary_guards_up_to_date) + entry_guards_update_primary(gs); + + char *ret_str = NULL; + int n_missing_descriptors = 0; + int n_considered = 0; + int num_primary_to_check; + + /* 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); + + /* If we are not missing any descriptors, return NULL. */ + if (!n_missing_descriptors) { + return NULL; + } + + /* otherwise return a helpful error string */ + tor_asprintf(&ret_str, "We're missing descriptors for %d/%d of our " + "primary entry guards (total %sdescriptors: %d/%d).", + n_missing_descriptors, num_primary_to_check, + using_mds?"micro":"", num_present, num_usable); + + return ret_str; +} + +/** As guard_selection_have_enough_dir_info_to_build_circuits, but uses + * the default guard selection. */ +char * +entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable) { - tor_assert(entry_list_is_constrained(options)); - entries_retry_helper(options, 1); + return guard_selection_get_err_str_if_dir_info_missing( + get_guard_selection_info(), + using_mds, + num_present, num_usable); } -/** 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. */ -int -any_bridge_supports_microdescriptors(void) +/** Free one guard selection context */ +STATIC void +guard_selection_free(guard_selection_t *gs) { - const node_t *node; - if (!get_options()->UseBridges || !entry_guards) - 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; + 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 @@ -2547,15 +3639,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 1021e67d43..d909115b1f 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,539 @@ 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; + +/** Types of restrictions we impose when picking guard nodes */ +typedef enum guard_restriction_type_t { + /* Don't pick the same guard node as our exit node (or its family) */ + RST_EXIT_NODE = 0, + /* Don't pick dirguards that have previously shown to be outdated */ + RST_OUTDATED_MD_DIRSERVER = 1 +} guard_restriction_type_t; + +/** + * A restriction to remember which entry guards are off-limits for a given + * circuit. + * + * 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 { + /* What type of restriction are we imposing? */ + guard_restriction_type_t type; + /* In case of restriction type RST_EXIT_NODE, 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 /* defined(ENTRYNODES_PRIVATE) */ + +/* 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(uint8_t dir_purpose, + 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 /* 1 */ -#endif +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); -#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. */ +/** Enum to specify how we're going to use a given guard, when we're picking + * one for immediate use. */ 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_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 { + 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); + +MOCK_DECL(int,num_bridges_usable,(int use_maybe_reachable)); + +#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 const node_t *entry_is_live(const entry_guard_t *e, - entry_is_live_flags_t flags, - const char **msg); +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 int entry_is_time_to_retry(const entry_guard_t *e, time_t now); +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); -#endif +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(const 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); + +STATIC entry_guard_restriction_t *guard_create_exit_restriction( + const uint8_t *exit_id); + +STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void); + +STATIC void entry_guard_restriction_free(entry_guard_restriction_t *rst); + +#endif /* defined(ENTRYNODES_PRIVATE) */ + +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); +char *entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable); +char *guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable); void entry_guards_free_all(void); -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); @@ -179,9 +613,5 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, uint32_t guardfraction_percentage); -MOCK_DECL(smartlist_t *, list_bridge_identities, (void)); -MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id, - (const char *digest)); - -#endif +#endif /* !defined(TOR_ENTRYNODES_H) */ diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index 676adfd8bf..28377a3f36 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.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 */ /** @@ -23,15 +23,16 @@ #include "ext_orport.h" #include "control.h" #include "config.h" -#include "util.h" #include "main.h" +#include "proto_ext_or.h" +#include "util.h" /** Allocate and return a structure capable of holding an Extended * ORPort message of body length <b>len</b>. */ ext_or_cmd_t * ext_or_cmd_new(uint16_t len) { - size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; + size_t size = offsetof(ext_or_cmd_t, body) + len; ext_or_cmd_t *cmd = tor_malloc(size); cmd->len = len; return cmd; @@ -69,10 +70,10 @@ connection_write_ext_or_command(connection_t *conn, return -1; set_uint16(header, htons(command)); set_uint16(header+2, htons(bodylen)); - connection_write_to_buf(header, 4, conn); + connection_buf_add(header, 4, conn); if (bodylen) { tor_assert(body); - connection_write_to_buf(body, bodylen, conn); + connection_buf_add(body, bodylen, conn); } return 0; } @@ -170,7 +171,7 @@ connection_ext_or_auth_neg_auth_type(connection_t *conn) if (connection_get_inbuf_len(conn) < 1) return 0; - if (connection_fetch_from_buf(authtype, 1, conn) < 0) + if (connection_buf_get_bytes(authtype, 1, conn) < 0) return -1; log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); @@ -310,7 +311,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) return 0; - if (connection_fetch_from_buf(client_nonce, + if (connection_buf_get_bytes(client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) return -1; @@ -325,7 +326,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) &reply, &reply_len) < 0) return -1; - connection_write_to_buf(reply, reply_len, conn); + connection_buf_add(reply, reply_len, conn); memwipe(reply, 0, reply_len); tor_free(reply); @@ -347,9 +348,9 @@ static void connection_ext_or_auth_send_result(connection_t *conn, int success) { if (success) - connection_write_to_buf("\x01", 1, conn); + connection_buf_add("\x01", 1, conn); else - connection_write_to_buf("\x00", 1, conn); + connection_buf_add("\x00", 1, conn); } /** Receive the client's hash from <b>conn</b>, validate that it's @@ -367,7 +368,7 @@ connection_ext_or_auth_handle_client_hash(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) return 0; - if (connection_fetch_from_buf(provided_client_hash, + if (connection_buf_get_bytes(provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) return -1; @@ -459,6 +460,11 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn, tor_free(addr_str); if (res<0) return -1; + if (port == 0) { + log_warn(LD_GENERAL, "Server transport proxy gave us an empty port " + "in ExtORPort UserAddr command."); + // return -1; // enable this if nothing breaks after a while. + } res = tor_addr_parse(&addr, address_part); tor_free(address_part); @@ -637,7 +643,7 @@ connection_ext_or_start_auth(or_connection_t *or_conn) log_debug(LD_GENERAL, "ExtORPort authentication: Sending supported authentication types"); - connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + connection_buf_add((const char *)authtypes, sizeof(authtypes), conn); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; return 0; diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h index 33d954e8d0..af2b97e77c 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 @@ -36,7 +36,7 @@ STATIC int handle_client_auth_nonce(const char *client_nonce, extern uint8_t *ext_or_auth_cookie; extern int ext_or_auth_cookie_is_set; #endif -#endif +#endif /* defined(EXT_ORPORT_PRIVATE) */ -#endif +#endif /* !defined(EXT_ORPORT_H) */ diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c index eeeb0f1de3..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 */ /** diff --git a/src/or/fp_pair.h b/src/or/fp_pair.h index b1466581d2..f7c060b459 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 */ /** @@ -41,5 +41,5 @@ void fp_pair_map_assert_ok(const fp_pair_map_t *map); #undef DECLARE_MAP_FNS -#endif +#endif /* !defined(_TOR_FP_PAIR_H) */ diff --git a/src/or/geoip.c b/src/or/geoip.c index a39366ed13..14e0b1b6fa 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 */ /** @@ -1017,7 +1017,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.", @@ -1802,6 +1802,15 @@ getinfo_helper_geoip(control_connection_t *control_conn, sa_family_t family; tor_addr_t addr; question += strlen("ip-to-country/"); + + if (!strcmp(question, "ipv4-available") || + !strcmp(question, "ipv6-available")) { + family = !strcmp(question, "ipv4-available") ? AF_INET : AF_INET6; + const int available = geoip_is_loaded(family); + tor_asprintf(answer, "%d", !! available); + return 0; + } + family = tor_addr_parse(&addr, question); if (family != AF_INET && family != AF_INET6) { *errmsg = "Invalid address family"; diff --git a/src/or/geoip.h b/src/or/geoip.h index c8ea9f85ea..753bdbf82a 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 */ /** @@ -20,7 +20,7 @@ STATIC int geoip_parse_entry(const char *line, sa_family_t family); STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); STATIC void clear_geoip_db(void); -#endif +#endif /* defined(GEOIP_PRIVATE) */ /** Entry in a map from IP address to the last time we've seen an incoming * connection from that IP address. Used by bridges only to track which @@ -96,5 +96,5 @@ const char *geoip_get_bridge_stats_extrainfo(time_t); char *geoip_get_bridge_stats_controller(time_t); char *format_client_stats_heartbeat(time_t now); -#endif +#endif /* !defined(TOR_GEOIP_H) */ diff --git a/src/or/hibernate.c b/src/or/hibernate.c index e3c80b5f14..74ab766468 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 */ /** @@ -424,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); @@ -587,7 +587,10 @@ accounting_set_wakeup_time(void) char buf[ISO_TIME_LEN+1]; format_iso_time(buf, interval_start_time); - crypto_pk_get_digest(get_server_identity_key(), digest); + if (crypto_pk_get_digest(get_server_identity_key(), digest) < 0) { + log_err(LD_BUG, "Error getting our key's digest."); + tor_assert(0); + } d_env = crypto_digest_new(); crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN); @@ -896,7 +899,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. */ @@ -1121,5 +1124,5 @@ hibernate_set_state_for_testing_(hibernate_state_t newstate) { hibernate_state = newstate; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/hibernate.h b/src/or/hibernate.h index fa9da6de39..85fb42864b 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 */ /** @@ -53,7 +53,7 @@ typedef enum { #ifdef TOR_UNIT_TESTS void hibernate_set_state_for_testing_(hibernate_state_t newstate); #endif -#endif +#endif /* defined(HIBERNATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_HIBERNATE_H) */ diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c new file mode 100644 index 0000000000..6a5a3895b0 --- /dev/null +++ b/src/or/hs_cache.c @@ -0,0 +1,946 @@ +/* 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 "or.h" +#include "config.h" +#include "hs_ident.h" +#include "hs_common.h" +#include "hs_client.h" +#include "hs_descriptor.h" +#include "networkstatus.h" +#include "rendcache.h" + +#include "hs_cache.h" + +static int cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc); + +/********************** Directory HS cache ******************/ + +/* Directory descriptor cache. Map indexed by blinded key. */ +static digest256map_t *hs_cache_v3_dir; + +/* 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_dir_entry_size(const hs_cache_dir_descriptor_t *entry) +{ + return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data) + + strlen(entry->encoded_desc)); +} + +/* 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_dir_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_dir_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_dir_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]; + digest256_to_base64(key_b64, (const char *) key); + 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); +} + +/********************** Client-side HS cache ******************/ + +/* Client-side HS descriptor cache. Map indexed by service identity key. */ +static digest256map_t *hs_cache_v3_client; + +/* Client-side introduction point state cache. Map indexed by service public + * identity key (onion address). It contains hs_cache_client_intro_state_t + * objects all related to a specific service. */ +static digest256map_t *hs_cache_client_intro_state; + +/* Return the size of a client cache entry in bytes. */ +static size_t +cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) +{ + return sizeof(*entry) + + strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); +} + +/* Remove a given descriptor from our cache. */ +static void +remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_remove(hs_cache_v3_client, desc->key.pubkey); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_decrement_allocation(cache_get_client_entry_size(desc)); +} + +/* Store a given descriptor in our cache. */ +static void +store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_increment_allocation(cache_get_client_entry_size(desc)); +} + +/* Query our cache and return the entry or NULL if not found or if expired. */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key) +{ + time_t now = approx_time(); + hs_cache_client_descriptor_t *cached_desc; + + tor_assert(key); + + /* Do the lookup */ + cached_desc = digest256map_get(hs_cache_v3_client, key); + if (!cached_desc) { + return NULL; + } + + /* Don't return expired entries */ + if (cached_client_descriptor_has_expired(now, cached_desc)) { + return NULL; + } + + return cached_desc; +} + +/* Parse the encoded descriptor in <b>desc_str</b> using + * <b>service_identity_pk<b> to decrypt it first. + * + * If everything goes well, allocate and return a new + * hs_cache_client_descriptor_t object. In case of error, return NULL. */ +static hs_cache_client_descriptor_t * +cache_client_desc_new(const char *desc_str, + const ed25519_public_key_t *service_identity_pk) +{ + hs_descriptor_t *desc = NULL; + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + + /* Decode the descriptor we just fetched. */ + if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) { + goto end; + } + tor_assert(desc); + + /* All is good: make a cache object for this descriptor */ + client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t)); + ed25519_pubkey_copy(&client_desc->key, service_identity_pk); + /* Set expiration time for this cached descriptor to be the start of the next + * time period since that's when clients need to start using the next blinded + * pk of the service (and hence will need its next descriptor). */ + client_desc->expiration_ts = hs_get_start_time_of_next_time_period(0); + client_desc->desc = desc; + client_desc->encoded_desc = tor_strdup(desc_str); + + end: + return client_desc; +} + +/** Free memory allocated by <b>desc</b>. */ +static void +cache_client_desc_free(hs_cache_client_descriptor_t *desc) +{ + if (desc == NULL) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->key, 0, sizeof(desc->key)); + memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc)); + tor_free(desc->encoded_desc); + tor_free(desc); +} + +/** Helper function: Use by the free all function to clear the client cache */ +static void +cache_client_desc_free_(void *ptr) +{ + hs_cache_client_descriptor_t *desc = ptr; + cache_client_desc_free(desc); +} + +/* Return a newly allocated and initialized hs_cache_intro_state_t object. */ +static hs_cache_intro_state_t * +cache_intro_state_new(void) +{ + hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state)); + state->created_ts = approx_time(); + return state; +} + +/* Free an hs_cache_intro_state_t object. */ +static void +cache_intro_state_free(hs_cache_intro_state_t *state) +{ + tor_free(state); +} + +/* Helper function: use by the free all function. */ +static void +cache_intro_state_free_(void *state) +{ + cache_intro_state_free(state); +} + +/* Return a newly allocated and initialized hs_cache_client_intro_state_t + * object. */ +static hs_cache_client_intro_state_t * +cache_client_intro_state_new(void) +{ + hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache)); + cache->intro_points = digest256map_new(); + return cache; +} + +/* Free a cache client intro state object. */ +static void +cache_client_intro_state_free(hs_cache_client_intro_state_t *cache) +{ + if (cache == NULL) { + return; + } + digest256map_free(cache->intro_points, cache_intro_state_free_); + tor_free(cache); +} + +/* Helper function: use by the free all function. */ +static void +cache_client_intro_state_free_(void *entry) +{ + cache_client_intro_state_free(entry); +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, lookup the intro state object. Return 1 if + * found and put it in entry if not NULL. Return 0 if not found and entry is + * untouched. */ +static int +cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **entry) +{ + hs_cache_intro_state_t *state; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the intro state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + goto not_found; + } + + /* From the cache we just found for the service, lookup in the introduction + * points map for the given authentication key. */ + state = digest256map_get(cache->intro_points, auth_key->pubkey); + if (state == NULL) { + goto not_found; + } + if (entry) { + *entry = state; + } + return 1; + not_found: + return 0; +} + +/* Note the given failure in state. */ +static void +cache_client_intro_state_note(hs_cache_intro_state_t *state, + rend_intro_point_failure_t failure) +{ + tor_assert(state); + switch (failure) { + case INTRO_POINT_FAILURE_GENERIC: + state->error = 1; + break; + case INTRO_POINT_FAILURE_TIMEOUT: + state->timed_out = 1; + break; + case INTRO_POINT_FAILURE_UNREACHABLE: + state->unreachable_count++; + break; + default: + tor_assert_nonfatal_unreached(); + return; + } +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, add an entry in the client intro state cache + * If no entry exists for the service, it will create one. If state is non + * NULL, it will point to the new intro state entry. */ +static void +cache_client_intro_state_add(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **state) +{ + hs_cache_intro_state_t *entry, *old_entry; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + cache = cache_client_intro_state_new(); + digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache); + } + + entry = cache_intro_state_new(); + old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry); + /* This should never happened because the code flow is to lookup the entry + * before adding it. But, just in case, non fatal assert and free it. */ + tor_assert_nonfatal(old_entry == NULL); + tor_free(old_entry); + + if (state) { + *state = entry; + } +} + +/* Remove every intro point state entry from cache that has been created + * before or at the cutoff. */ +static void +cache_client_intro_state_clean(time_t cutoff, + hs_cache_client_intro_state_t *cache) +{ + tor_assert(cache); + + DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key, + hs_cache_intro_state_t *, entry) { + if (entry->created_ts <= cutoff) { + cache_intro_state_free(entry); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Return true iff no intro points are in this cache. */ +static int +cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache) +{ + return digest256map_isempty(cache->intro_points); +} + +/** Check whether <b>client_desc</b> is useful for us, and store it in the + * client-side HS cache if so. The client_desc is freed if we already have a + * fresher (higher revision counter count) in the cache. */ +static int +cache_store_as_client(hs_cache_client_descriptor_t *client_desc) +{ + hs_cache_client_descriptor_t *cache_entry; + + /* TODO: Heavy code duplication with cache_store_as_dir(). Consider + * refactoring and uniting! */ + + tor_assert(client_desc); + + /* Check if we already have a descriptor from this HS in cache. If we do, + * check if this descriptor is newer than the cached one */ + cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); + if (cache_entry != NULL) { + /* If we have an entry in our cache that has a revision counter greater + * than the one we just fetched, discard the one we fetched. */ + if (cache_entry->desc->plaintext_data.revision_counter > + client_desc->desc->plaintext_data.revision_counter) { + cache_client_desc_free(client_desc); + goto done; + } + /* Remove old entry. Make space for the new one! */ + remove_v3_desc_as_client(cache_entry); + cache_client_desc_free(cache_entry); + } + + /* Store descriptor in cache */ + store_v3_desc_as_client(client_desc); + + done: + return 0; +} + +/* Return true iff the cached client descriptor at <b>cached_desc</b has + * expired. */ +static int +cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc) +{ + /* We use the current consensus time to see if we should expire this + * descriptor since we use consensus time for all other parts of the protocol + * as well (e.g. to build the blinded key and compute time periods). */ + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + /* If we don't have a recent consensus, consider this entry expired since we + * will want to fetch a new HS desc when we get a live consensus. */ + if (!ns) { + return 1; + } + + if (cached_desc->expiration_ts <= ns->valid_after) { + return 1; + } + + return 0; +} + +/* clean the client cache using now as the current time. Return the total size + * of removed bytes from the cache. */ +static size_t +cache_clean_v3_as_client(time_t now) +{ + size_t bytes_removed = 0; + + if (!hs_cache_v3_client) { /* No cache to clean. Just return. */ + return 0; + } + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size; + + /* If the entry has not expired, continue to the next cached entry */ + if (!cached_client_descriptor_has_expired(now, entry)) { + continue; + } + /* Here, our entry has expired, remove and free. */ + MAP_DEL_CURRENT(key); + entry_size = cache_get_client_entry_size(entry); + bytes_removed += entry_size; + /* Entry is not in the cache anymore, destroy it. */ + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + digest256_to_base64(key_b64, (const char *) key); + log_info(LD_REND, "Removing hidden service v3 descriptor '%s' " + "from client cache", + safe_str_client(key_b64)); + } + } DIGEST256MAP_FOREACH_END; + + return bytes_removed; +} + +/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return + * its HS descriptor if it's stored in our cache, or NULL if not. */ +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (cached_desc) { + tor_assert(cached_desc->desc); + return cached_desc->desc; + } + + return NULL; +} + +/** Public API: Given an encoded descriptor, store it in the client HS + * cache. Return -1 on error, 0 on success .*/ +int +hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk) +{ + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(identity_pk); + + /* Create client cache descriptor object */ + client_desc = cache_client_desc_new(desc_str, identity_pk); + if (!client_desc) { + log_warn(LD_GENERAL, "Failed to parse received descriptor %s.", + escaped(desc_str)); + goto err; + } + + /* Push it to the cache */ + if (cache_store_as_client(client_desc) < 0) { + goto err; + } + + return 0; + + err: + cache_client_desc_free(client_desc); + return -1; +} + +/* Clean all client caches using the current time now. */ +void +hs_cache_clean_as_client(time_t now) +{ + /* Start with v2 cache cleaning. */ + rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); + /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function + * to compute the cutoff by itself using the lifetime value. */ + cache_clean_v3_as_client(now); +} + +/* Purge the client descriptor cache. */ +void +hs_cache_purge_as_client(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size = cache_get_client_entry_size(entry); + MAP_DEL_CURRENT(key); + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client descriptor cache purged."); +} + +/* For a given service identity public key and an introduction authentication + * key, note the given failure in the client intro state cache. */ +void +hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure) +{ + int found; + hs_cache_intro_state_t *entry; + + tor_assert(service_pk); + tor_assert(auth_key); + + found = cache_client_intro_state_lookup(service_pk, auth_key, &entry); + if (!found) { + /* Create a new entry and add it to the cache. */ + cache_client_intro_state_add(service_pk, auth_key, &entry); + } + /* Note down the entry. */ + cache_client_intro_state_note(entry, failure); +} + +/* For a given service identity public key and an introduction authentication + * key, return true iff it is present in the failure cache. */ +const hs_cache_intro_state_t * +hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key) +{ + hs_cache_intro_state_t *state = NULL; + cache_client_intro_state_lookup(service_pk, auth_key, &state); + return state; +} + +/* Cleanup the client introduction state cache. */ +void +hs_cache_client_intro_state_clean(time_t now) +{ + time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE; + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + /* Cleanup intro points failure. */ + cache_client_intro_state_clean(cutoff, cache); + + /* Is this cache empty for this service key? If yes, remove it from the + * cache. Else keep it. */ + if (cache_client_intro_state_is_empty(cache)) { + cache_client_intro_state_free(cache); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Purge the client introduction state cache. */ +void +hs_cache_client_intro_state_purge(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + MAP_DEL_CURRENT(key); + cache_client_intro_state_free(cache); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client introduction point state " + "cache purged."); +} + +/**************** Generics *********************************/ + +/* Do a round of OOM cleanup on all directory caches. Return the amount of + * removed bytes. It is possible that the returned value is lower than + * min_remove_bytes if the caches get emptied out so the caller should be + * 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 a v3 HS descriptor. */ +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(); + + tor_assert(!hs_cache_v3_client); + hs_cache_v3_client = digest256map_new(); + + tor_assert(!hs_cache_client_intro_state); + hs_cache_client_intro_state = digest256map_new(); +} + +/* Cleanup the hidden service cache subsystem. */ +void +hs_cache_free_all(void) +{ + digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_); + hs_cache_v3_dir = NULL; + + digest256map_free(hs_cache_v3_client, cache_client_desc_free_); + hs_cache_v3_client = NULL; + + digest256map_free(hs_cache_client_intro_state, + cache_client_intro_state_free_); + hs_cache_client_intro_state = NULL; +} + diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h new file mode 100644 index 0000000000..2dcc518a71 --- /dev/null +++ b/src/or/hs_cache.h @@ -0,0 +1,127 @@ +/* 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 "rendcommon.h" +#include "torcert.h" + +/* This is the maximum time an introduction point state object can stay in the + * client cache in seconds (2 mins or 120 seconds). */ +#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60) + +/* Introduction point state. */ +typedef struct hs_cache_intro_state_t { + /* When this entry was created and put in the cache. */ + time_t created_ts; + + /* Did it suffered a generic error? */ + unsigned int error : 1; + + /* Did it timed out? */ + unsigned int timed_out : 1; + + /* How many times we tried to reached it and it was unreachable. */ + uint32_t unreachable_count; +} hs_cache_intro_state_t; + +typedef struct hs_cache_client_intro_state_t { + /* Contains hs_cache_intro_state_t object indexed by introduction point + * authentication key. */ + digest256map_t *intro_points; +} hs_cache_client_intro_state_t; + +/* Descriptor representation on the directory side which is a subset of + * information that the HSDir can decode and serve it. */ +typedef struct hs_cache_dir_descriptor_t { + /* 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); + +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key); +int hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk); +void hs_cache_clean_as_client(time_t now); +void hs_cache_purge_as_client(void); + +/* Client failure cache. */ +void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure); +const hs_cache_intro_state_t *hs_cache_client_intro_state_find( + const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key); +void hs_cache_client_intro_state_clean(time_t now); +void hs_cache_client_intro_state_purge(void); + +#ifdef HS_CACHE_PRIVATE + +/** Represents a locally cached HS descriptor on a hidden service client. */ +typedef struct hs_cache_client_descriptor_t { + /* This object is indexed using the service identity public key */ + ed25519_public_key_t key; + + /* When will this entry expire? We expire cached client descriptors in the + * start of the next time period, since that's when clients need to start + * using the next blinded key of the service. */ + time_t expiration_ts; + + /* The cached descriptor, this object is the owner. It can't be NULL. A + * cache object without a valid descriptor is not possible. */ + hs_descriptor_t *desc; + + /* Encoded descriptor in string form. Can't be NULL. */ + char *encoded_desc; +} hs_cache_client_descriptor_t; + +STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff); + +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key); + +#endif /* defined(HS_CACHE_PRIVATE) */ + +#endif /* !defined(TOR_HS_CACHE_H) */ + diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c new file mode 100644 index 0000000000..5244cfa3dd --- /dev/null +++ b/src/or/hs_cell.c @@ -0,0 +1,948 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.c + * \brief Hidden service API for cell creation and handling. + **/ + +#include "or.h" +#include "config.h" +#include "rendservice.h" +#include "replaycache.h" +#include "util.h" + +#include "hs_cell.h" +#include "hs_ntor.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" +#include "hs/cell_introduce1.h" +#include "hs/cell_rendezvous.h" + +/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is + * the cell content up to the ENCRYPTED section of length encoded_cell_len. + * The encrypted param is the start of the ENCRYPTED section of length + * encrypted_len. The mac_key is the key needed for the computation of the MAC + * derived from the ntor handshake of length mac_key_len. + * + * The length mac_out_len must be at least DIGEST256_LEN. */ +static void +compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, + const uint8_t *encrypted, size_t encrypted_len, + const uint8_t *mac_key, size_t mac_key_len, + uint8_t *mac_out, size_t mac_out_len) +{ + size_t offset = 0; + size_t mac_msg_len; + uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(encoded_cell); + tor_assert(encrypted); + tor_assert(mac_key); + tor_assert(mac_out); + tor_assert(mac_out_len >= DIGEST256_LEN); + + /* Compute the size of the message which is basically the entire cell until + * the MAC field of course. */ + mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN); + tor_assert(mac_msg_len <= sizeof(mac_msg)); + + /* First, put the encoded cell in the msg. */ + memcpy(mac_msg, encoded_cell, encoded_cell_len); + offset += encoded_cell_len; + /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which + * is junk at this point). */ + memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN)); + offset += (encrypted_len - DIGEST256_LEN); + tor_assert(offset == mac_msg_len); + + crypto_mac_sha3_256(mac_out, mac_out_len, + mac_key, mac_key_len, + mac_msg, mac_msg_len); + memwipe(mac_msg, 0, sizeof(mac_msg)); +} + +/* From a set of keys, subcredential and the ENCRYPTED section of an + * INTRODUCE2 cell, return a newly allocated intro cell keys structure. + * Finally, the client public key is copied in client_pk. On error, return + * NULL. */ +static hs_ntor_intro_cell_keys_t * +get_introduce2_key_material(const ed25519_public_key_t *auth_key, + const curve25519_keypair_t *enc_key, + const uint8_t *subcredential, + const uint8_t *encrypted_section, + curve25519_public_key_t *client_pk) +{ + hs_ntor_intro_cell_keys_t *keys; + + tor_assert(auth_key); + tor_assert(enc_key); + tor_assert(subcredential); + tor_assert(encrypted_section); + tor_assert(client_pk); + + keys = tor_malloc_zero(sizeof(*keys)); + + /* First bytes of the ENCRYPTED section are the client public key. */ + memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); + + if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, + subcredential, keys) < 0) { + /* Don't rely on the caller to wipe this on error. */ + memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); + tor_free(keys); + keys = NULL; + } + return keys; +} + +/* Using the given encryption key, decrypt the encrypted_section of length + * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated + * buffer containing the decrypted data. On decryption failure, NULL is + * returned. */ +static uint8_t * +decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + uint8_t *decrypted = NULL; + crypto_cipher_t *cipher = NULL; + + tor_assert(enc_key); + tor_assert(encrypted_section); + + /* Decrypt ENCRYPTED section. */ + cipher = crypto_cipher_new_with_bits((char *) enc_key, + CURVE25519_PUBKEY_LEN * 8); + tor_assert(cipher); + + /* This is symmetric encryption so can't be bigger than the encrypted + * section length. */ + decrypted = tor_malloc_zero(encrypted_section_len); + if (crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted_section, + encrypted_section_len) < 0) { + tor_free(decrypted); + decrypted = NULL; + goto done; + } + + done: + crypto_cipher_free(cipher); + return decrypted; +} + +/* Given a pointer to the decrypted data of the ENCRYPTED section of an + * INTRODUCE2 cell of length decrypted_len, parse and validate the cell + * content. Return a newly allocated cell structure or NULL on error. The + * circuit and service object are only used for logging purposes. */ +static trn_cell_introduce_encrypted_t * +parse_introduce2_encrypted(const uint8_t *decrypted_data, + size_t decrypted_len, const origin_circuit_t *circ, + const hs_service_t *service) +{ + trn_cell_introduce_encrypted_t *enc_cell = NULL; + + tor_assert(decrypted_data); + tor_assert(circ); + tor_assert(service); + + if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data, + decrypted_len) < 0) { + log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of " + "the INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) != + HS_CELL_ONION_KEY_TYPE_NTOR) { + log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but " + "expected %u on circuit %u for service %s", + trn_cell_introduce_encrypted_get_onion_key_type(enc_cell), + HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != + CURVE25519_PUBKEY_LEN) { + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but " + "expected %d on circuit %u for service %s", + (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* XXX: Validate NSPEC field as well. */ + + return enc_cell; + err: + trn_cell_introduce_encrypted_free(enc_cell); + return NULL; +} + +/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA + * encryption key. The encoded cell is put in cell_out that MUST at least be + * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on + * success else a negative value and cell_out is untouched. */ +static ssize_t +build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, + uint8_t *cell_out) +{ + ssize_t cell_len; + + tor_assert(circ_nonce); + tor_assert(enc_key); + tor_assert(cell_out); + + memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE); + + cell_len = rend_service_encode_establish_intro_cell((char*)cell_out, + RELAY_PAYLOAD_SIZE, + enc_key, circ_nonce); + return cell_len; +} + +/* Parse an INTRODUCE2 cell from payload of size payload_len for the given + * service and circuit which are used only for logging purposes. The resulting + * parsed cell is put in cell_ptr_out. + * + * This function only parses prop224 INTRODUCE2 cells even when the intro point + * is a legacy intro point. That's because intro points don't actually care + * about the contents of the introduce cell. Legacy INTRODUCE cells are only + * used by the legacy system now. + * + * Return 0 on success else a negative value and cell_ptr_out is untouched. */ +static int +parse_introduce2_cell(const hs_service_t *service, + const origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len, + trn_cell_introduce1_t **cell_ptr_out) +{ + trn_cell_introduce1_t *cell = NULL; + + tor_assert(service); + tor_assert(circ); + tor_assert(payload); + tor_assert(cell_ptr_out); + + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Success. */ + *cell_ptr_out = cell; + return 0; + err: + return -1; +} + +/* Set the onion public key onion_pk in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, + const uint8_t *onion_pk) +{ + tor_assert(cell); + tor_assert(onion_pk); + /* There is only one possible key type for a non legacy cell. */ + trn_cell_introduce_encrypted_set_onion_key_type(cell, + HS_CELL_ONION_KEY_TYPE_NTOR); + trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN); + trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN); + memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk, + trn_cell_introduce_encrypted_getlen_onion_key(cell)); +} + +/* Set the link specifiers in lspecs in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, + const smartlist_t *lspecs) +{ + tor_assert(cell); + tor_assert(lspecs); + tor_assert(smartlist_len(lspecs) > 0); + tor_assert(smartlist_len(lspecs) <= UINT8_MAX); + + uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs); + trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num); + /* We aren't duplicating the link specifiers object here which means that + * the ownership goes to the trn_cell_introduce_encrypted_t cell and those + * object will be freed when the cell is. */ + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, + trn_cell_introduce_encrypted_add_nspecs(cell, ls)); +} + +/* Set padding in the enc_cell only if needed that is the total length of both + * sections are below the mininum required for an INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, + trn_cell_introduce_encrypted_t *enc_cell) +{ + tor_assert(cell); + tor_assert(enc_cell); + /* This is the length we expect to have once encoded of the whole cell. */ + ssize_t full_len = trn_cell_introduce1_encoded_len(cell) + + trn_cell_introduce_encrypted_encoded_len(enc_cell); + tor_assert(full_len > 0); + if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) { + size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len; + trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding); + memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0, + trn_cell_introduce_encrypted_getlen_pad(enc_cell)); + } +} + +/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell + * and the INTRODUCE1 data. + * + * This can't fail but it is very important that the caller sets every field + * in data so the computation of the INTRODUCE1 keys doesn't fail. */ +static void +introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell, + const trn_cell_introduce_encrypted_t *enc_cell, + const hs_cell_introduce1_data_t *data) +{ + size_t offset = 0; + ssize_t encrypted_len; + ssize_t encoded_cell_len, encoded_enc_cell_len; + uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t *encrypted = NULL; + uint8_t mac[DIGEST256_LEN]; + crypto_cipher_t *cipher = NULL; + hs_ntor_intro_cell_keys_t keys; + + tor_assert(cell); + tor_assert(enc_cell); + tor_assert(data); + + /* Encode the cells up to now of what we have to we can perform the MAC + * computation on it. */ + encoded_cell_len = trn_cell_introduce1_encode(encoded_cell, + sizeof(encoded_cell), cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_cell_len > 0); + + encoded_enc_cell_len = + trn_cell_introduce_encrypted_encode(encoded_enc_cell, + sizeof(encoded_enc_cell), enc_cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_enc_cell_len > 0); + + /* Get the key material for the encryption. */ + if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk, + data->client_kp, + data->subcredential, &keys) < 0) { + tor_assert_unreached(); + } + + /* Prepare cipher with the encryption key just computed. */ + cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key, + sizeof(keys.enc_key) * 8); + tor_assert(cipher); + + /* Compute the length of the ENCRYPTED section which is the CLIENT_PK, + * ENCRYPTED_DATA and MAC length. */ + encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len + + sizeof(mac); + tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE); + encrypted = tor_malloc_zero(encrypted_len); + + /* Put the CLIENT_PK first. */ + memcpy(encrypted, data->client_kp->pubkey.public_key, + sizeof(data->client_kp->pubkey.public_key)); + offset += sizeof(data->client_kp->pubkey.public_key); + /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) encrypted + offset, + (const char *) encoded_enc_cell, encoded_enc_cell_len); + crypto_cipher_free(cipher); + offset += encoded_enc_cell_len; + /* Compute MAC from the above and put it in the buffer. This function will + * make the adjustment to the encryptled_len to ommit the MAC length. */ + compute_introduce_mac(encoded_cell, encoded_cell_len, + encrypted, encrypted_len, + keys.mac_key, sizeof(keys.mac_key), + mac, sizeof(mac)); + memcpy(encrypted + offset, mac, sizeof(mac)); + offset += sizeof(mac); + tor_assert(offset == (size_t) encrypted_len); + + /* Set the ENCRYPTED section in the cell. */ + trn_cell_introduce1_setlen_encrypted(cell, encrypted_len); + memcpy(trn_cell_introduce1_getarray_encrypted(cell), + encrypted, encrypted_len); + + /* Cleanup. */ + memwipe(&keys, 0, sizeof(keys)); + memwipe(mac, 0, sizeof(mac)); + memwipe(encrypted, 0, sizeof(encrypted_len)); + memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell)); + tor_free(encrypted); +} + +/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means + * set it, encrypt it and encode it. */ +static void +introduce1_set_encrypted(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + trn_cell_introduce_encrypted_t *enc_cell; + trn_cell_extension_t *ext; + + tor_assert(cell); + tor_assert(data); + + enc_cell = trn_cell_introduce_encrypted_new(); + tor_assert(enc_cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce_encrypted_set_extensions(enc_cell, ext); + + /* Set the rendezvous cookie. */ + memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell), + data->rendezvous_cookie, REND_COOKIE_LEN); + + /* Set the onion public key. */ + introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key); + + /* Set the link specifiers. */ + introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers); + + /* Set padding. */ + introduce1_set_encrypted_padding(cell, enc_cell); + + /* Encrypt and encode it in the cell. */ + introduce1_encrypt_and_encode(cell, enc_cell, data); + + /* Cleanup. */ + trn_cell_introduce_encrypted_free(enc_cell); +} + +/* Set the authentication key in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_auth_key(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + /* There is only one possible type for a non legacy cell. */ + trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519); + trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN); + trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN); + memcpy(trn_cell_introduce1_getarray_auth_key(cell), + data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell)); +} + +/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_legacy_id(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + + if (data->is_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) { + return; + } + memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell), + digest, trn_cell_introduce1_getlen_legacy_key_id(cell)); + } else { + /* We have to zeroed the LEGACY_KEY_ID field. */ + memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0, + trn_cell_introduce1_getlen_legacy_key_id(cell)); + } +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point + * object. The encoded cell is put in cell_out that MUST at least be of the + * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else + * a negative value and cell_out is untouched. This function also supports + * legacy cell creation. */ +ssize_t +hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out) +{ + ssize_t cell_len = -1; + uint16_t sig_len = ED25519_SIG_LEN; + trn_cell_extension_t *ext; + trn_cell_establish_intro_t *cell = NULL; + + tor_assert(circ_nonce); + tor_assert(ip); + + /* Quickly handle the legacy IP. */ + if (ip->base.is_only_legacy) { + tor_assert(ip->legacy_key); + cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key, + cell_out); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + /* Success or not we are done here. */ + goto done; + } + + /* Set extension data. None used here. */ + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + cell = trn_cell_establish_intro_new(); + trn_cell_establish_intro_set_extensions(cell, ext); + /* Set signature size. Array is then allocated in the cell. We need to do + * this early so we can use trunnel API to get the signature length. */ + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); + + /* Set AUTH_KEY_TYPE: 2 means ed25519 */ + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); + + /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of + * AUTH_KEY to match */ + { + uint16_t auth_key_len = ED25519_PUBKEY_LEN; + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + /* We do this call _after_ setting the length because it's reallocated at + * that point only. */ + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); + memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len); + } + + /* Calculate HANDSHAKE_AUTH field (MAC). */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_mac_offset = + sig_len + sizeof(cell->sig_len) + + trn_cell_establish_intro_getlen_handshake_mac(cell); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr; + + /* We first encode the current fields we have in the cell so we can + * compute the MAC using the raw bytes. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + /* Sanity check. */ + tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset); + + /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */ + crypto_mac_sha3_256(mac, sizeof(mac), + (uint8_t *) circ_nonce, DIGEST_LEN, + tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); + handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); + memcpy(handshake_ptr, mac, sizeof(mac)); + + memwipe(mac, 0, sizeof(mac)); + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Calculate the cell signature SIG. */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len)); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr; + ed25519_signature_t sig; + + /* We first encode the current fields we have in the cell so we can + * compute the signature from the raw bytes of the cell. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + + if (ed25519_sign_prefixed(&sig, tmp_cell_enc, + tmp_cell_enc_len - tmp_cell_sig_offset, + ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) { + log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell."); + goto done; + } + /* Copy the signature into the cell. */ + sig_ptr = trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, sig_len); + + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Encode the cell. Can't be bigger than a standard cell. */ + cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + done: + trn_cell_establish_intro_free(cell); + return cell_len; +} + +/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we + * are successful at parsing it, return the length of the parsed cell else a + * negative value on error. */ +ssize_t +hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) +{ + ssize_t ret; + trn_cell_intro_established_t *cell = NULL; + + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. */ + ret = trn_cell_intro_established_parse(&cell, payload, payload_len); + if (ret >= 0) { + /* On success, we do not keep the cell, we just notify the caller that it + * was successfully parsed. */ + trn_cell_intro_established_free(cell); + } + return ret; +} + +/* Parsse the INTRODUCE2 cell using data which contains everything we need to + * do so and contains the destination buffers of information we extract and + * compute from the cell. Return 0 on success else a negative value. The + * service and circ are only used for logging purposes. */ +ssize_t +hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service) +{ + int ret = -1; + time_t elapsed; + uint8_t *decrypted = NULL; + size_t encrypted_section_len; + const uint8_t *encrypted_section; + trn_cell_introduce1_t *cell = NULL; + trn_cell_introduce_encrypted_t *enc_cell = NULL; + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + + tor_assert(data); + tor_assert(circ); + tor_assert(service); + + /* Parse the cell into a decoded data structure pointed by cell_ptr. */ + if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, + &cell) < 0) { + goto done; + } + + log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " + "for service %s. Decoding encrypted section...", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + + /* Encrypted section must at least contain the CLIENT_PK and MAC which is + * defined in section 3.3.2 of the specification. */ + if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length " + "for service %s. Dropping cell.", + safe_str_client(service->onion_address)); + goto done; + } + + /* Check our replay cache for this introduction point. */ + if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, + encrypted_section_len, &elapsed)) { + log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the" + "same ENCRYPTED section was seen %ld seconds ago. " + "Dropping cell.", (long int) elapsed); + goto done; + } + + /* Build the key material out of the key material found in the cell. */ + intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, + data->subcredential, + encrypted_section, + &data->client_pk); + if (intro_keys == NULL) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " + "compute key material on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Validate MAC from the cell and our computed key material. The MAC field + * in the cell is at the end of the encrypted section. */ + { + uint8_t mac[DIGEST256_LEN]; + /* The MAC field is at the very end of the ENCRYPTED section. */ + size_t mac_offset = encrypted_section_len - sizeof(mac); + /* Compute the MAC. Use the entire encoded payload with a length up to the + * ENCRYPTED section. */ + compute_introduce_mac(data->payload, + data->payload_len - encrypted_section_len, + encrypted_section, encrypted_section_len, + intro_keys->mac_key, sizeof(intro_keys->mac_key), + mac, sizeof(mac)); + if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) { + log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + } + + { + /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ + const uint8_t *encrypted_data = + encrypted_section + sizeof(data->client_pk); + /* It's symmetric encryption so it's correct to use the ENCRYPTED length + * for decryption. Computes the length of ENCRYPTED_DATA meaning removing + * the CLIENT_PK and MAC length. */ + size_t encrypted_data_len = + encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN); + + /* This decrypts the ENCRYPTED_DATA section of the cell. */ + decrypted = decrypt_introduce2(intro_keys->enc_key, + encrypted_data, encrypted_data_len); + if (decrypted == NULL) { + log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an " + "INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Parse this blob into an encrypted cell structure so we can then extract + * the data we need out of it. */ + enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len, + circ, service); + memwipe(decrypted, 0, encrypted_data_len); + if (enc_cell == NULL) { + goto done; + } + } + + /* XXX: Implement client authorization checks. */ + + /* Extract onion key and rendezvous cookie from the cell used for the + * rendezvous point circuit e2e encryption. */ + memcpy(data->onion_pk.public_key, + trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN); + memcpy(data->rendezvous_cookie, + trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell), + sizeof(data->rendezvous_cookie)); + + /* Extract rendezvous link specifiers. */ + for (size_t idx = 0; + idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { + link_specifier_t *lspec = + trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); + smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + } + + /* Success. */ + ret = 0; + log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); + + done: + if (intro_keys) { + memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); + tor_free(intro_keys); + } + tor_free(decrypted); + trn_cell_introduce_encrypted_free(enc_cell); + trn_cell_introduce1_free(cell); + return ret; +} + +/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake + * info. The encoded cell is put in cell_out and the length of the data is + * returned. This can't fail. */ +ssize_t +hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_rendezvous1_t *cell; + + tor_assert(rendezvous_cookie); + tor_assert(rendezvous_handshake_info); + tor_assert(cell_out); + + cell = trn_cell_rendezvous1_new(); + /* Set the RENDEZVOUS_COOKIE. */ + memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell), + rendezvous_cookie, rendezvous_cookie_len); + /* Set the HANDSHAKE_INFO. */ + trn_cell_rendezvous1_setlen_handshake_info(cell, + rendezvous_handshake_info_len); + memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell), + rendezvous_handshake_info, rendezvous_handshake_info_len); + /* Encoding. */ + cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + tor_assert(cell_len > 0); + + trn_cell_rendezvous1_free(cell); + return cell_len; +} + +/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in + * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the + * encoded length is returned else a negative value and the content of + * cell_out should be ignored. */ +ssize_t +hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_introduce1_t *cell; + trn_cell_extension_t *ext; + + tor_assert(data); + tor_assert(cell_out); + + cell = trn_cell_introduce1_new(); + tor_assert(cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce1_set_extensions(cell, ext); + + /* Set the legacy ID field. */ + introduce1_set_legacy_id(cell, data); + + /* Set the authentication key. */ + introduce1_set_auth_key(cell, data); + + /* Set the encrypted section. This will set, encrypt and encode the + * ENCRYPTED section in the cell. After this, we'll be ready to encode. */ + introduce1_set_encrypted(cell, data); + + /* Final encoding. */ + cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + + trn_cell_introduce1_free(cell); + return cell_len; +} + +/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The + * encoded cell is put in cell_out which must be of at least + * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the + * caller should clear up the content of the cell. + * + * This function can't fail. */ +ssize_t +hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out) +{ + tor_assert(rendezvous_cookie); + tor_assert(cell_out); + + memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN); + return HS_REND_COOKIE_LEN; +} + +/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. + * Return the status code on success else a negative value if the cell as not + * decodable. */ +int +hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + trn_cell_introduce_ack_t *cell = NULL; + + tor_assert(payload); + + /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a + * NACK is 1 byte. We can't use the legacy function for this so we have to + * do a special case. */ + if (payload_len <= 1) { + if (payload_len == 0) { + ret = HS_CELL_INTRO_ACK_SUCCESS; + } else { + ret = HS_CELL_INTRO_ACK_FAILURE; + } + goto end; + } + + if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it."); + goto end; + } + + ret = trn_cell_introduce_ack_get_status(cell); + + end: + trn_cell_introduce_ack_free(cell); + return ret; +} + +/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On + * success, handshake_info contains the data in the HANDSHAKE_INFO field, and + * 0 is returned. On error, a negative value is returned. */ +int +hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, size_t handshake_info_len) +{ + int ret = -1; + trn_cell_rendezvous2_t *cell = NULL; + + tor_assert(payload); + tor_assert(handshake_info); + + if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it."); + goto end; + } + + /* Static size, we should never have an issue with this else we messed up + * our code flow. */ + tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) == + handshake_info_len); + memcpy(handshake_info, + trn_cell_rendezvous2_getconstarray_handshake_info(cell), + handshake_info_len); + ret = 0; + + end: + trn_cell_rendezvous2_free(cell); + return ret; +} + +/* Clear the given INTRODUCE1 data structure data. */ +void +hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) +{ + if (data == NULL) { + return; + } + /* Object in this list have been moved to the cell object when building it + * so they've been freed earlier. We do that in order to avoid duplicating + * them leading to more memory and CPU time being used for nothing. */ + smartlist_free(data->link_specifiers); + /* The data object has no ownership of any members. */ + memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h new file mode 100644 index 0000000000..958dde4ffc --- /dev/null +++ b/src/or/hs_cell.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.h + * \brief Header file containing cell data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CELL_H +#define TOR_HS_CELL_H + +#include "or.h" +#include "hs_service.h" + +/* An INTRODUCE1 cell requires at least this amount of bytes (see section + * 3.2.2 of the specification). Below this value, the cell must be padded. */ +#define HS_CELL_INTRODUCE1_MIN_SIZE 246 + +/* Status code of an INTRODUCE_ACK cell. */ +typedef enum { + HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */ + HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */ + HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */ + HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */ +} hs_cell_introd_ack_status_t; + +/* Onion key type found in the INTRODUCE1 cell. */ +typedef enum { + HS_CELL_ONION_KEY_TYPE_NTOR = 1, +} hs_cell_onion_key_type_t; + +/* This data structure contains data that we need to build an INTRODUCE1 cell + * used by the INTRODUCE1 build function. */ +typedef struct hs_cell_introduce1_data_t { + /* Is this a legacy introduction point? */ + unsigned int is_legacy : 1; + /* (Legacy only) The encryption key for a legacy intro point. Only set if + * is_legacy is true. */ + const crypto_pk_t *legacy_key; + /* Introduction point authentication public key. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption public key. */ + const curve25519_public_key_t *enc_pk; + /* Subcredentials of the service. */ + const uint8_t *subcredential; + /* Onion public key for the ntor handshake. */ + const curve25519_public_key_t *onion_pk; + /* Rendezvous cookie. */ + const uint8_t *rendezvous_cookie; + /* Public key put before the encrypted data (CLIENT_PK). */ + const curve25519_keypair_t *client_kp; + /* Rendezvous point link specifiers. */ + smartlist_t *link_specifiers; +} hs_cell_introduce1_data_t; + +/* This data structure contains data that we need to parse an INTRODUCE2 cell + * which is used by the INTRODUCE2 cell parsing function. On a successful + * parsing, the onion_pk and rendezvous_cookie will be populated with the + * computed key material from the cell data. This structure is only used during + * INTRO2 parsing and discarded after that. */ +typedef struct hs_cell_introduce2_data_t { + /*** Immutable Section: Set on structure init. ***/ + + /* Introduction point authentication public key. Pointer owned by the + introduction point object through which we received the INTRO2 cell. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption keypair for the ntor handshake. Pointer + owned by the introduction point object through which we received the + INTRO2 cell*/ + const curve25519_keypair_t *enc_kp; + /* Subcredentials of the service. Pointer owned by the descriptor that owns + the introduction point through which we received the INTRO2 cell. */ + const uint8_t *subcredential; + /* Payload of the received encoded cell. */ + const uint8_t *payload; + /* Size of the payload of the received encoded cell. */ + size_t payload_len; + + /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ + + /* Onion public key computed using the INTRODUCE2 encrypted section. */ + curve25519_public_key_t onion_pk; + /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + uint8_t rendezvous_cookie[REND_COOKIE_LEN]; + /* Client public key from the INTRODUCE2 encrypted section. */ + curve25519_public_key_t client_pk; + /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + smartlist_t *link_specifiers; + /* Replay cache of the introduction point. */ + replaycache_t *replay_cache; +} hs_cell_introduce2_data_t; + +/* Build cell API. */ +ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out); +ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out); +ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out); +ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out); + +/* Parse cell API. */ +ssize_t hs_cell_parse_intro_established(const uint8_t *payload, + size_t payload_len); +ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service); +int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len); +int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, + size_t handshake_info_len); + +/* Util API. */ +void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); + +#endif /* !defined(TOR_HS_CELL_H) */ + diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c new file mode 100644 index 0000000000..66c59e0dc7 --- /dev/null +++ b/src/or/hs_circuit.c @@ -0,0 +1,1213 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.c + **/ + +#define HS_CIRCUIT_PRIVATE + +#include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "policies.h" +#include "relay.h" +#include "rendservice.h" +#include "rephist.h" +#include "router.h" + +#include "hs_cell.h" +#include "hs_ident.h" +#include "hs_ntor.h" +#include "hs_service.h" +#include "hs_circuit.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* A circuit is about to become an e2e rendezvous circuit. Check + * <b>circ_purpose</b> and ensure that it's properly set. Return true iff + * circuit purpose is properly set, otherwise return false. */ +static int +circuit_purpose_is_correct_for_rend(unsigned int circ_purpose, + int is_service_side) +{ + if (is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) { + log_warn(LD_BUG, + "HS e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + if (!is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY && + circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_BUG, + "Client e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + return 1; +} + +/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous + * circuit. Initialize the crypt path crypto using the output material from the + * ntor key exchange at <b>ntor_key_seed</b>. + * + * If <b>is_service_side</b> is set, we are the hidden service and the final + * hop of the rendezvous circuit is the client on the other side. */ +static crypt_path_t * +create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN]; + crypt_path_t *cpath = NULL; + + /* Do the key expansion */ + if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len, + keys, sizeof(keys)) < 0) { + goto err; + } + + /* Setup the cpath */ + cpath = tor_malloc_zero(sizeof(crypt_path_t)); + cpath->magic = CRYPT_PATH_MAGIC; + + if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys), + is_service_side, 1) < 0) { + tor_free(cpath); + goto err; + } + + err: + memwipe(keys, 0, sizeof(keys)); + return cpath; +} + +/* We are a v2 legacy HS client: Create and return a crypt path for the hidden + * service on the other side of the rendezvous circuit <b>circ</b>. Initialize + * the crypt path crypto using the body of the RENDEZVOUS1 cell at + * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes). + */ +static crypt_path_t * +create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) +{ + crypt_path_t *hop = NULL; + char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; + + /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh + * handshake...*/ + tor_assert(circ->build_state); + tor_assert(circ->build_state->pending_final_cpath); + hop = circ->build_state->pending_final_cpath; + + tor_assert(hop->rend_dh_handshake_state); + if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state, + (char*)rend_cell_body, DH_KEY_LEN, + keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { + log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + goto err; + } + /* ... and set up cpath. */ + if (circuit_init_cpath_crypto(hop, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 0, 0) < 0) + goto err; + + /* Check whether the digest is right... */ + if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) { + log_warn(LD_PROTOCOL, "Incorrect digest of key material."); + goto err; + } + + /* clean up the crypto stuff we just made */ + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; + + goto done; + + err: + hop = NULL; + + done: + memwipe(keys, 0, sizeof(keys)); + return hop; +} + +/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark + * <b>circ</b> ready for use to transfer HS relay cells. */ +static void +finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, + int is_service_side) +{ + tor_assert(circ); + tor_assert(hop); + + /* Notify the circuit state machine that we are splicing this circuit */ + int new_circ_purpose = is_service_side ? + CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED; + circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose); + + /* All is well. Extend the circuit. */ + hop->state = CPATH_STATE_OPEN; + /* Set the windows to default. */ + hop->package_window = circuit_initial_package_window(); + hop->deliver_window = CIRCWINDOW_START; + + /* Now that this circuit has finished connecting to its destination, + * make sure circuit_get_open_circ_or_launch is willing to return it + * so we can actually use it. */ + circ->hs_circ_has_timed_out = 0; + + /* Append the hop to the cpath of this circuit */ + onion_append_to_cpath(&circ->cpath, hop); + + /* In legacy code, 'pending_final_cpath' points to the final hop we just + * appended to the cpath. We set the original pointer to NULL so that we + * don't double free it. */ + if (circ->build_state) { + circ->build_state->pending_final_cpath = NULL; + } + + /* Finally, mark circuit as ready to be used for client streams */ + if (!is_service_side) { + circuit_try_attaching_streams(circ); + } +} + +/* For a given circuit and a service introduction point object, register the + * intro circuit to the circuitmap. This supports legacy intro point. */ +static void +register_intro_circ(const hs_service_intro_point_t *ip, + origin_circuit_t *circ) +{ + tor_assert(ip); + tor_assert(circ); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + return; + } + hs_circuitmap_register_intro_circ_v2_service_side(circ, digest); + } else { + hs_circuitmap_register_intro_circ_v3_service_side(circ, + &ip->auth_key_kp.pubkey); + } +} + +/* Return the number of opened introduction circuit for the given circuit that + * is matching its identity key. */ +static unsigned int +count_opened_desc_intro_point_circuits(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(service); + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + const circuit_t *circ; + const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc == NULL) { + continue; + } + circ = TO_CIRCUIT(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO); + /* Having a circuit not for the requested service is really bad. */ + tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk, + ô->hs_ident->identity_pk)); + /* Only count opened circuit and skip circuit that will be closed. */ + if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) { + count++; + } + } DIGEST256MAP_FOREACH_END; + return count; +} + +/* From a given service, rendezvous cookie and handshake info, create a + * rendezvous point circuit identifier. This can't fail. */ +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys) +{ + hs_ident_circuit_t *ident; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + tor_assert(service); + tor_assert(rendezvous_cookie); + tor_assert(server_pk); + tor_assert(keys); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ + memcpy(ident->rendezvous_cookie, rendezvous_cookie, + sizeof(ident->rendezvous_cookie)); + /* Build the HANDSHAKE_INFO which looks like this: + * SERVER_PK [32 bytes] + * AUTH_INPUT_MAC [32 bytes] + */ + memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN); + memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac, + DIGEST256_LEN); + tor_assert(sizeof(ident->rendezvous_handshake_info) == + sizeof(handshake_info)); + memcpy(ident->rendezvous_handshake_info, handshake_info, + sizeof(ident->rendezvous_handshake_info)); + /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */ + tor_assert(sizeof(ident->rendezvous_ntor_key_seed) == + sizeof(keys->ntor_key_seed)); + memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed, + sizeof(ident->rendezvous_ntor_key_seed)); + return ident; +} + +/* From a given service and service intro point, create an introduction point + * circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_intro_circuit_identifier(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_ident_circuit_t *ident; + + tor_assert(service); + tor_assert(ip); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); + + return ident; +} + +/* For a given introduction point and an introduction circuit, send the + * ESTABLISH_INTRO cell. The service object is used for logging. This can fail + * and if so, the circuit is closed and the intro point object is flagged + * that the circuit is not established anymore which is important for the + * retry mechanism. */ +static void +send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, origin_circuit_t *circ) +{ + ssize_t cell_len; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + + /* Encode establish intro cell. */ + cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, + ip, payload); + if (cell_len < 0) { + log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " + "on circuit %u. Closing circuit.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* Send the cell on the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_INTRO, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " + "on circuit %u.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + /* On error, the circuit has been closed. */ + goto done; + } + + /* Record the attempt to use this circuit. */ + pathbias_count_use_attempt(circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Return a string constant describing the anonymity of service. */ +static const char * +get_service_anonymity_string(const hs_service_t *service) +{ + if (service->config.is_single_onion) { + return "single onion"; + } else { + return "hidden"; + } +} + +/* For a given service, the ntor onion key and a rendezvous cookie, launch a + * circuit to the rendezvous point specified by the link specifiers. On + * success, a circuit identifier is attached to the circuit with the needed + * data. This function will try to open a circuit for a maximum value of + * MAX_REND_FAILURES then it will give up. */ +static void +launch_rendezvous_point_circuit(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data) +{ + int circ_needs_uptime; + time_t now = time(NULL); + extend_info_t *info = NULL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); + + /* Get the extend info data structure for the chosen rendezvous point + * specified by the given link specifiers. */ + info = hs_get_extend_info_from_lspecs(data->link_specifiers, + &data->onion_pk, + service->config.is_single_onion); + if (info == NULL) { + /* We are done here, we can't extend to the rendezvous point. + * If you're running an IPv6-only v3 single onion service on 0.3.2 or with + * 0.3.2 clients, and somehow disable the option check, it will fail here. + */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Not enough info to open a circuit to a rendezvous point for " + "%s service %s.", + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + + for (int i = 0; i < MAX_REND_FAILURES; i++) { + int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; + if (circ_needs_uptime) { + circ_flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* Firewall and policies are checked when getting the extend info. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, + circ_flags); + if (circ != NULL) { + /* Stop retrying, we have a circuit! */ + break; + } + } + if (circ == NULL) { + log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(hex_str((const char *) data->rendezvous_cookie, + REND_COOKIE_LEN)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + tor_assert(circ->build_state); + /* Rendezvous circuit have a specific timeout for the time spent on trying + * to connect to the rendezvous point. */ + circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; + + /* Create circuit identifier and key material. */ + { + hs_ntor_rend_cell_keys_t keys; + curve25519_keypair_t ephemeral_kp; + /* No need for extra strong, this is only for this circuit life time. This + * key will be used for the RENDEZVOUS1 cell that will be sent on the + * circuit once opened. */ + curve25519_keypair_generate(&ephemeral_kp, 0); + if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, + &ip->enc_key_kp, + &ephemeral_kp, &data->client_pk, + &keys) < 0) { + /* This should not really happened but just in case, don't make tor + * freak out, close the circuit and move on. */ + log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " + "service %s", + safe_str_client(service->onion_address)); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + circ->hs_ident = create_rp_circuit_identifier(service, + data->rendezvous_cookie, + &ephemeral_kp.pubkey, &keys); + memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); + memwipe(&keys, 0, sizeof(keys)); + tor_assert(circ->hs_ident); + } + + end: + extend_info_free(info); +} + +/* Return true iff the given service rendezvous circuit circ is allowed for a + * relaunch to the rendezvous point. */ +static int +can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* XXX: Retrying under certain condition. This is related to #22455. */ + + /* Avoid to relaunch twice a circuit to the same rendezvous point at the + * same time. */ + if (circ->hs_service_side_rend_circ_has_been_relaunched) { + log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " + "Skipping retry.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + goto disallow; + } + + /* We check failure_count >= hs_get_service_max_rend_failures()-1 below, and + * the -1 is because we increment the failure count for our current failure + * *after* this clause. */ + int max_rend_failures = hs_get_service_max_rend_failures() - 1; + + /* A failure count that has reached maximum allowed or circuit that expired, + * we skip relaunching. */ + if (circ->build_state->failure_count > max_rend_failures || + circ->build_state->expiry_time <= time(NULL)) { + log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " + "failed with %d attempts and expiry time %ld. " + "Giving up building.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit)), + circ->build_state->failure_count, + (long int) circ->build_state->expiry_time); + goto disallow; + } + + /* Allowed to relaunch. */ + return 1; + disallow: + return 0; +} + +/* Retry the rendezvous point of circ by launching a new circuit to it. */ +static void +retry_service_rendezvous_point(const origin_circuit_t *circ) +{ + int flags = 0; + origin_circuit_t *new_circ; + cpath_build_state_t *bstate; + + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Ease our life. */ + bstate = circ->build_state; + + log_info(LD_REND, "Retrying rendezvous point circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + + /* Get the current build state flags for the next circuit. */ + flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; + flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; + flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; + + /* We do NOT add the onehop tunnel flag even though it might be a single + * onion service. The reason is that if we failed once to connect to the RP + * with a direct connection, we consider that chances are that we will fail + * again so try a 3-hop circuit and hope for the best. Because the service + * has no anonymity (single onion), this change of behavior won't affect + * security directly. */ + + new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, + bstate->chosen_exit, flags); + if (new_circ == NULL) { + log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + goto done; + } + + /* Transfer build state information to the new circuit state in part to + * catch any other failures. */ + new_circ->build_state->failure_count = bstate->failure_count+1; + new_circ->build_state->expiry_time = bstate->expiry_time; + new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); + + done: + return; +} + +/* Using an extend info object ei, set all possible link specifiers in lspecs. + * legacy ID is mandatory thus MUST be present in ei. If IPv4 is not present, + * logs a BUG() warning, and returns an empty smartlist. Clients never make + * direct connections to rendezvous points, so they should always have an + * IPv4 address in ei. */ +static void +get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) +{ + link_specifier_t *ls; + + tor_assert(ei); + tor_assert(lspecs); + + /* We require IPv4, we will add IPv6 support in a later tor version */ + if (BUG(!tor_addr_is_v4(&ei->addr))) { + return; + } + + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr)); + link_specifier_set_un_ipv4_port(ls, ei->port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) + + sizeof(ei->port)); + smartlist_add(lspecs, ls); + + /* Legacy ID is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest, + link_specifier_getlen_un_legacy_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); + smartlist_add(lspecs, ls); + + /* ed25519 ID is only included if the extend_info has it. */ + if (!ed25519_public_key_is_zero(&ei->ed_identity)) { + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + } +} + +/* Using the given descriptor intro point ip, the extend information of the + * rendezvous point rp_ei and the service's subcredential, populate the + * already allocated intro1_data object with the needed key material and link + * specifiers. + * + * This can't fail but the ip MUST be a valid object containing the needed + * keys and authentication method. */ +static void +setup_introduce1_data(const hs_desc_intro_point_t *ip, + const extend_info_t *rp_ei, + const uint8_t *subcredential, + hs_cell_introduce1_data_t *intro1_data) +{ + smartlist_t *rp_lspecs; + + tor_assert(ip); + tor_assert(rp_ei); + tor_assert(subcredential); + tor_assert(intro1_data); + + /* Build the link specifiers from the extend information of the rendezvous + * circuit that we've picked previously. */ + rp_lspecs = smartlist_new(); + get_lspecs_from_extend_info(rp_ei, rp_lspecs); + + /* Populate the introduce1 data object. */ + memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t)); + if (ip->legacy.key != NULL) { + intro1_data->is_legacy = 1; + intro1_data->legacy_key = ip->legacy.key; + } + intro1_data->auth_pk = &ip->auth_key_cert->signed_key; + intro1_data->enc_pk = &ip->enc_key; + intro1_data->subcredential = subcredential; + intro1_data->onion_pk = &rp_ei->curve25519_onion_key; + intro1_data->link_specifiers = rp_lspecs; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return an introduction point circuit matching the given intro point object. + * NULL is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) +{ + origin_circuit_t *circ = NULL; + + tor_assert(ip); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + goto end; + } + circ = hs_circuitmap_get_intro_circ_v2_service_side(digest); + } else { + circ = hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + } + end: + return circ; +} + +/* Called when we fail building a rendezvous circuit at some point other than + * the last hop: launches a new circuit to the same rendezvous point. This + * supports legacy service. + * + * We currently relaunch connections to rendezvous points if: + * - A rendezvous circuit timed out before connecting to RP. + * - The redenzvous circuit failed to connect to the RP. + * + * We avoid relaunching a connection to this rendezvous point if: + * - We have already tried MAX_REND_FAILURES times to connect to this RP. + * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT + * seconds + * - We've already retried this specific rendezvous circuit. + */ +void +hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Check if we are allowed to relaunch to the rendezvous point of circ. */ + if (!can_relaunch_service_rendezvous_point(circ)) { + goto done; + } + + /* Flag the circuit that we are relaunching so to avoid to relaunch twice a + * circuit to the same rendezvous point at the same time. */ + circ->hs_service_side_rend_circ_has_been_relaunched = 1; + + /* Legacy service don't have an hidden service ident. */ + if (circ->hs_ident) { + retry_service_rendezvous_point(circ); + } else { + rend_service_relaunch_rendezvous(circ); + } + + done: + return; +} + +/* For a given service and a service intro point, launch a circuit to the + * extend info ei. If the service is a single onion, a one-hop circuit will be + * requested. Return 0 if the circuit was successfully launched and tagged + * with the correct identifier. On error, a negative value is returned. */ +int +hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei) +{ + /* Standard flags for introduction circuit. */ + int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(ei); + + /* Update circuit flags in case of a single onion service that requires a + * direct connection. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + log_info(LD_REND, "Launching a circuit to intro point %s for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + + /* Note down the launch for the retry period. Even if the circuit fails to + * be launched, we still want to respect the retry period to avoid stress on + * the circuit subsystem. */ + service->state.num_intro_circ_launched++; + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + ei, circ_flags); + if (circ == NULL) { + goto end; + } + + /* Setup the circuit identifier and attach it to it. */ + circ->hs_ident = create_intro_circuit_identifier(service, ip); + tor_assert(circ->hs_ident); + /* Register circuit in the global circuitmap. */ + register_intro_circ(ip, circ); + + /* Success. */ + ret = 0; + end: + return ret; +} + +/* Called when a service introduction point circuit is done building. Given + * the service and intro point object, this function will send the + * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the + * circuit has been repurposed to General because we already have too many + * opened. */ +int +hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ) +{ + int ret = 0; + unsigned int num_intro_circ, num_needed_circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(desc); + tor_assert(circ); + + /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already + * established introduction circuits */ + num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); + num_needed_circ = service->config.num_intro_points; + if (num_intro_circ > num_needed_circ) { + /* There are too many opened valid intro circuit for what the service + * needs so repurpose this one. */ + + /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just + * closes the circuit. I have NO idea why it does that so it hasn't been + * added here. I can only assume in case our ExcludeNodes list changes but + * in that case, all circuit are flagged unusable (config.c). --dgoulet */ + + log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " + "have enough for service %s. Repurposing " + "it to general and leaving internal.", + safe_str_client(service->onion_address)); + tor_assert(circ->build_state->is_internal); + /* Remove it from the circuitmap. */ + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + /* Cleaning up the hidden service identifier and repurpose. */ + hs_ident_circuit_free(circ->hs_ident); + circ->hs_ident = NULL; + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); + /* Inform that this circuit just opened for this new purpose. */ + circuit_has_opened(circ); + /* This return value indicate to the caller that the IP object should be + * removed from the service because it's corresponding circuit has just + * been repurposed. */ + ret = 1; + goto done; + } + + log_info(LD_REND, "Introduction circuit %u established for service %s.", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call + * makes sure the circuit gets closed. */ + send_establish_intro(service, ip, circ); + + done: + return ret; +} + +/* Called when a service rendezvous point circuit is done building. Given the + * service and the circuit, this function will send a RENDEZVOUS1 cell on the + * circuit using the information in the circuit identifier. If the cell can't + * be sent, the circuit is closed. */ +void +hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ) +{ + size_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(service); + tor_assert(circ); + tor_assert(circ->hs_ident); + + /* Some useful logging. */ + log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN), + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* This can't fail. */ + payload_len = hs_cell_build_rendezvous1( + circ->hs_ident->rendezvous_cookie, + sizeof(circ->hs_ident->rendezvous_cookie), + circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info), + payload); + + /* Pad the payload with random bytes so it matches the size of a legacy cell + * which is normally always bigger. Also, the size of a legacy cell is + * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */ + if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) { + crypto_rand((char *) payload + payload_len, + HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len); + payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_RENDEZVOUS1, + (const char *) payload, payload_len, + circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Setup end-to-end rendezvous circuit between the client and us. */ + if (hs_circuit_setup_e2e_rend_circ(circ, + circ->hs_ident->rendezvous_ntor_key_seed, + sizeof(circ->hs_ident->rendezvous_ntor_key_seed), + 1) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); + goto done; + } + + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle + * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the + * given introduction circuit circ. The service is only used for logging + * purposes. Return 0 on success else a negative value. */ +int +hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + tor_assert(payload); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + goto done; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. For a legacy node, it's an empty payload so as long as we + * have the cell, we are good. */ + if (!ip->base.is_only_legacy && + hs_cell_parse_intro_established(payload, payload_len) < 0) { + log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Switch the purpose to a fully working intro point. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the + * circuit so update our pathbias subsystem. */ + pathbias_mark_use_success(circ); + /* Success. */ + ret = 0; + + done: + return ret; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the INTRODUCE2 payload of size payload_len for the given + * circuit and service. This cell is associated with the intro point object ip + * and the subcredential. Return 0 on success else a negative value. */ +int +hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + time_t elapsed; + hs_cell_introduce2_data_t data; + + tor_assert(service); + tor_assert(circ); + tor_assert(ip); + tor_assert(subcredential); + tor_assert(payload); + + /* Populate the data structure with everything we need for the cell to be + * parsed, decrypted and key material computed correctly. */ + data.auth_pk = &ip->auth_key_kp.pubkey; + data.enc_kp = &ip->enc_key_kp; + data.subcredential = subcredential; + data.payload = payload; + data.payload_len = payload_len; + data.link_specifiers = smartlist_new(); + data.replay_cache = ip->replay_cache; + + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { + goto done; + } + + /* Check whether we've seen this REND_COOKIE before to detect repeats. */ + if (replaycache_add_test_and_elapsed( + service->state.replay_cache_rend_cookie, + data.rendezvous_cookie, sizeof(data.rendezvous_cookie), + &elapsed)) { + /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE + * as its previous one if its intro circ times out while in state + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first + * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2 + * cell), we are already trying to connect to that rend point (and may + * have already succeeded); drop this cell. */ + log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE " + "field %ld seconds ago. Dropping cell.", + (long int) elapsed); + goto done; + } + + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid + * so increment our counter that we've seen one on this intro point. */ + ip->introduce2_count++; + + /* Launch rendezvous circuit with the onion key and rend cookie. */ + launch_rendezvous_point_circuit(service, ip, &data); + /* Success. */ + ret = 0; + + done: + SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(data.link_specifiers); + memwipe(&data, 0, sizeof(data)); + return ret; +} + +/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key + * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to + * serve as a rendezvous end-to-end circuit between the client and the + * service. If <b>is_service_side</b> is set, then we are the hidden service + * and the other side is the client. + * + * Return 0 if the operation went well; in case of error return -1. */ +int +hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose, + is_service_side))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len, + is_service_side); + if (!hop) { + log_warn(LD_REND, "Couldn't get v3 %s cpath!", + is_service_side ? "service-side" : "client-side"); + return -1; + } + + finalize_rend_circuit(circ, hop, is_service_side); + + return 0; +} + +/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell + * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then + * extend the crypt path of <b>circ</b> so that the hidden service is on the + * other side. */ +int +hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body) +{ + + if (BUG(!circuit_purpose_is_correct_for_rend( + TO_CIRCUIT(circ)->purpose, 0))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body); + if (!hop) { + log_warn(LD_GENERAL, "Couldn't get v2 cpath."); + return -1; + } + + finalize_rend_circuit(circ, hop, 0); + + return 0; +} + +/* Given the introduction circuit intro_circ, the rendezvous circuit + * rend_circ, a descriptor intro point object ip and the service's + * subcredential, send an INTRODUCE1 cell on intro_circ. + * + * This will also setup the circuit identifier on rend_circ containing the key + * material for the handshake and e2e encryption. Return 0 on success else + * negative value. Because relay_send_command_from_edge() closes the circuit + * on error, it is possible that intro_circ is closed on error. */ +int +hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential) +{ + int ret = -1; + ssize_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + hs_cell_introduce1_data_t intro1_data; + + tor_assert(intro_circ); + tor_assert(rend_circ); + tor_assert(ip); + tor_assert(subcredential); + + /* This takes various objects in order to populate the introduce1 data + * object which is used to build the content of the cell. */ + setup_introduce1_data(ip, rend_circ->build_state->chosen_exit, + subcredential, &intro1_data); + /* If we didn't get any link specifiers, it's because our extend info was + * bad. */ + if (BUG(!intro1_data.link_specifiers) || + !smartlist_len(intro1_data.link_specifiers)) { + log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on " + "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Final step before we encode a cell, we setup the circuit identifier which + * will generate both the rendezvous cookie and client keypair for this + * connection. Those are put in the ident. */ + intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie; + intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp; + + memcpy(intro_circ->hs_ident->rendezvous_cookie, + rend_circ->hs_ident->rendezvous_cookie, + sizeof(intro_circ->hs_ident->rendezvous_cookie)); + + /* From the introduce1 data object, this will encode the INTRODUCE1 cell + * into payload which is then ready to be sent as is. */ + payload_len = hs_cell_build_introduce1(&intro1_data, payload); + if (BUG(payload_len < 0)) { + goto done; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ), + RELAY_COMMAND_INTRODUCE1, + (const char *) payload, payload_len, + intro_circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.", + TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Success. */ + ret = 0; + goto done; + + done: + hs_cell_introduce1_data_clear(&intro1_data); + memwipe(payload, 0, sizeof(payload)); + return ret; +} + +/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On + * success, 0 is returned else -1 and the circuit is marked for close. */ +int +hs_circ_send_establish_rendezvous(origin_circuit_t *circ) +{ + ssize_t cell_len = 0; + uint8_t cell[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u", + TO_CIRCUIT(circ)->n_circ_id); + + /* Set timestamp_dirty, because circuit_expire_building expects it, + * and the rend cookie also means we've used the circ. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* We've attempted to use this circuit. Probe it if we fail */ + pathbias_count_use_attempt(circ); + + /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can + * complete the handshake when receiving the acknowledgement. */ + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN); + /* Generate the client keypair. No need to be extra strong, not long term */ + curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0); + + cell_len = + hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie, + cell); + if (BUG(cell_len < 0)) { + goto err; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_RENDEZVOUS, + (const char *) cell, cell_len, + circ->cpath->prev) < 0) { + /* Circuit has been marked for close */ + log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on " + "circuit %u", TO_CIRCUIT(circ)->n_circ_id); + memwipe(cell, 0, cell_len); + goto err; + } + + memwipe(cell, 0, cell_len); + return 0; + err: + return -1; +} + +/* We are about to close or free this <b>circ</b>. Clean it up from any + * related HS data structures. This function can be called multiple times + * safely for the same circuit. */ +void +hs_circ_cleanup(circuit_t *circ) +{ + tor_assert(circ); + + /* If it's a service-side intro circ, notify the HS subsystem for the intro + * point circuit closing so it can be dealt with cleanly. */ + if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { + hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + } + + /* Clear HS circuitmap token for this circ (if any). Very important to be + * done after the HS subsystem has been notified of the close else the + * circuit will not be found. + * + * We do this at the close if possible because from that point on, the + * circuit is good as dead. We can't rely on removing it in the circuit + * free() function because we open a race window between the close and free + * where we can't register a new circuit for the same intro point. */ + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); + } +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h new file mode 100644 index 0000000000..63ff5e463c --- /dev/null +++ b/src/or/hs_circuit.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.h + * \brief Header file containing circuit data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CIRCUIT_H +#define TOR_HS_CIRCUIT_H + +#include "or.h" +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_service.h" + +/* Cleanup function when the circuit is closed or/and freed. */ +void hs_circ_cleanup(circuit_t *circ); + +/* Circuit API. */ +int hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ); +void hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ); +int hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei); +int hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie); +void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); + +origin_circuit_t *hs_circ_service_get_intro_circ( + const hs_service_intro_point_t *ip); + +/* Cell API. */ +int hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len); +int hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential); +int hs_circ_send_establish_rendezvous(origin_circuit_t *circ); + +/* e2e circuit API. */ + +int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, + size_t seed_len, + int is_service_side); +int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body); + +#ifdef HS_CIRCUIT_PRIVATE + +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys); + +#endif + +#endif /* !defined(TOR_HS_CIRCUIT_H) */ + diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c new file mode 100644 index 0000000000..97d6053e9b --- /dev/null +++ b/src/or/hs_circuitmap.c @@ -0,0 +1,556 @@ +/* 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: + * (a) by relays acting as intro points and rendezvous points + * (b) by hidden services to find intro and rend circuits and + * (c) by HS clients to find rendezvous circuits. + **/ + +#define HS_CIRCUITMAP_PRIVATE + +#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 /* defined(TOR_UNIT_TESTS) */ + +/****************** 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 function: Return client-side rendezvous circuit with rendezvous + * <b>cookie</b>. It will look for circuits with the following purposes: + + * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received + * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for + * INTRODUCE_ACK from intro point. + * + * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and + * introduce circuit acked. Waiting for RENDEZVOUS2 from service. + * + * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received + * RENDEZVOUS2 from service. + * + * d) CIRCUIT_PURPOSE_C_ESTABLISH_REND: Rend circuit open but not yet + * established. + * + * Return NULL if no such circuit is found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) +{ + origin_circuit_t *circ = NULL; + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_JOINED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_ESTABLISH_REND); + return circ; +} + +/**** Public servide-side setters: */ + +/* Public function: Register v2 intro circuit with key <b>digest</b> to the + * 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); +} + +/* Public function: Register rendezvous circuit with key <b>cookie</b> to the + * client-side circuitmap. */ +void +hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, + const uint8_t *cookie) +{ + circuit_t *circ = TO_CIRCUIT(or_circ); + { /* Basic circ purpose sanity checking */ + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + } + + hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie); +} + +/**** Misc public functions: */ + +/** Public function: Remove this circuit from the HS circuitmap. Clear its HS + * 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..43b2947c17 --- /dev/null +++ b/src/or/hs_circuitmap.h @@ -0,0 +1,111 @@ +/* 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); +struct origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie); + +void hs_circuitmap_register_intro_circ_v2_service_side( + struct origin_circuit_t *circ, + 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_register_rend_circ_client_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, + + /** A rendezvous cookie on the client side (128bit) */ + HS_TOKEN_REND_CLIENT_SIDE, +} hs_token_type_t; + +/** Represents a token used in the HS protocol. Each such token maps to a + * 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 /* defined(HS_CIRCUITMAP_PRIVATE) */ + +#ifdef TOR_UNIT_TESTS + +hs_circuitmap_ht *get_hs_circuitmap(void); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* !defined(TOR_HS_CIRCUITMAP_H) */ + diff --git a/src/or/hs_client.c b/src/or/hs_client.c new file mode 100644 index 0000000000..4e2824c13d --- /dev/null +++ b/src/or/hs_client.c @@ -0,0 +1,1611 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.c + * \brief Implement next generation hidden service client functionality + **/ + +#define HS_CLIENT_PRIVATE + +#include "or.h" +#include "hs_circuit.h" +#include "hs_ident.h" +#include "connection_edge.h" +#include "container.h" +#include "rendclient.h" +#include "hs_descriptor.h" +#include "hs_cache.h" +#include "hs_cell.h" +#include "hs_ident.h" +#include "config.h" +#include "directory.h" +#include "hs_client.h" +#include "router.h" +#include "routerset.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "connection.h" +#include "nodelist.h" +#include "circpathbias.h" +#include "connection.h" +#include "hs_ntor.h" +#include "circuitbuild.h" +#include "networkstatus.h" +#include "reasons.h" + +/* Return a human-readable string for the client fetch status code. */ +static const char * +fetch_status_to_string(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_ERROR: + return "Internal error"; + case HS_CLIENT_FETCH_LAUNCHED: + return "Descriptor fetch launched"; + case HS_CLIENT_FETCH_HAVE_DESC: + return "Already have descriptor"; + case HS_CLIENT_FETCH_NO_HSDIRS: + return "No more HSDir available to query"; + case HS_CLIENT_FETCH_NOT_ALLOWED: + return "Fetching descriptors is not allowed"; + case HS_CLIENT_FETCH_MISSING_INFO: + return "Missing directory information"; + case HS_CLIENT_FETCH_PENDING: + return "Pending descriptor fetch"; + default: + return "(Unknown client fetch status code)"; + } +} + +/* Return true iff tor should close the SOCKS request(s) for the descriptor + * fetch that ended up with this given status code. */ +static int +fetch_status_should_close_socks(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_NO_HSDIRS: + /* No more HSDir to query, we can't complete the SOCKS request(s). */ + case HS_CLIENT_FETCH_ERROR: + /* The fetch triggered an internal error. */ + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Client is not allowed to fetch (FetchHidServDescriptors 0). */ + goto close; + case HS_CLIENT_FETCH_MISSING_INFO: + case HS_CLIENT_FETCH_HAVE_DESC: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_LAUNCHED: + /* The rest doesn't require tor to close the SOCKS request(s). */ + goto no_close; + } + + no_close: + return 0; + close: + return 1; +} + +/* Cancel all descriptor fetches currently in progress. */ +static void +cancel_descriptor_fetches(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + log_debug(LD_REND, "Marking for close a directory connection fetching " + "a hidden service descriptor for service %s.", + safe_str_client(ed25519_fmt(&ident->identity_pk))); + connection_mark_for_close(conn); + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + log_info(LD_REND, "Hidden service client descriptor fetches cancelled."); +} + +/* Get all connections that are waiting on a circuit and flag them back to + * waiting for a hidden service descriptor for the given service key + * service_identity_pk. */ +static void +flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) +{ + tor_assert(service_identity_pk); + + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + edge_connection_t *edge_conn; + if (BUG(!CONN_IS_EDGE(conn))) { + continue; + } + edge_conn = TO_EDGE_CONN(conn); + if (edge_conn->hs_ident && + ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + service_identity_pk)) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); + conn->state = AP_CONN_STATE_RENDDESC_WAIT; + } + } SMARTLIST_FOREACH_END(conn); + + smartlist_free(conns); +} + +/* Remove tracked HSDir requests from our history for this hidden service + * identity public key. */ +static void +purge_hid_serv_request(const ed25519_public_key_t *identity_pk) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + ed25519_public_key_t blinded_pk; + + tor_assert(identity_pk); + + /* Get blinded pubkey of hidden service. It is possible that we just moved + * to a new time period meaning that we won't be able to purge the request + * from the previous time period. That is fine because they will expire at + * some point and we don't care about those anymore. */ + hs_build_blinded_pubkey(identity_pk, NULL, 0, + hs_get_time_period_num(0), &blinded_pk); + if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { + return; + } + /* Purge last hidden service request from cache for this blinded key. */ + hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); +} + +/* Return true iff there is at least one pending directory descriptor request + * for the service identity_pk. */ +static int +directory_request_is_pending(const ed25519_public_key_t *identity_pk) +{ + int ret = 0; + smartlist_t *conns = + connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + if (!ed25519_pubkey_eq(identity_pk, &ident->identity_pk)) { + continue; + } + ret = 1; + break; + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + return ret; +} + +/* We failed to fetch a descriptor for the service with <b>identity_pk</b> + * because of <b>status</b>. Find all pending SOCKS connections for this + * service that are waiting on the descriptor and close them with + * <b>reason</b>. */ +static void +close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t status, + int reason) +{ + unsigned int count = 0; + time_t now = approx_time(); + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we tried to get the descriptor */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + /* Unattach the entry connection which will close for the reason. */ + connection_mark_unattached_ap(entry_conn, reason); + count++; + } SMARTLIST_FOREACH_END(base_conn); + + if (count > 0) { + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + hs_build_address(identity_pk, HS_VERSION_THREE, onion_address); + log_notice(LD_REND, "Closed %u streams for service %s.onion " + "for reason %s. Fetch status: %s.", + count, safe_str_client(onion_address), + stream_end_reason_to_string(reason), + fetch_status_to_string(status)); + } + + /* No ownership of the object(s) in this list. */ + smartlist_free(conns); +} + +/* Find all pending SOCKS connection waiting for a descriptor and retry them + * all. This is called when the directory information changed. */ +static void +retry_all_socks_conn_waiting_for_desc(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + hs_client_fetch_status_t status; + const edge_connection_t *edge_conn = + ENTRY_TO_EDGE_CONN(TO_ENTRY_CONN(base_conn)); + + /* Ignore non HS or non v3 connection. */ + if (edge_conn->hs_ident == NULL) { + continue; + } + /* In this loop, we will possibly try to fetch a descriptor for the + * pending connections because we just got more directory information. + * However, the refetch process can cleanup all SOCKS request so the same + * service if an internal error happens. Thus, we can end up with closed + * connections in our list. */ + if (base_conn->marked_for_close) { + continue; + } + + /* XXX: There is an optimization we could do which is that for a service + * key, we could check if we can fetch and remember that decision. */ + + /* Order a refetch in case it works this time. */ + status = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + if (BUG(status == HS_CLIENT_FETCH_HAVE_DESC)) { + /* This case is unique because it can NOT happen in theory. Once we get + * a new descriptor, the HS client subsystem is notified immediately and + * the connections waiting for it are handled which means the state will + * change from renddesc wait state. Log this and continue to next + * connection. */ + continue; + } + /* In the case of an error, either all SOCKS connections have been + * closed or we are still missing directory information. Leave the + * connection in renddesc wait state so when we get more info, we'll be + * able to try it again. */ + } SMARTLIST_FOREACH_END(base_conn); + + /* We don't have ownership of those objects. */ + smartlist_free(conns); +} + +/* A v3 HS circuit successfully connected to the hidden service. Update the + * stream state at <b>hs_conn_ident</b> appropriately. */ +static void +note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) +{ + tor_assert(hs_conn_ident); + + /* Remove from the hid serv cache all requests for that service so we can + * query the HSDir again later on for various reasons. */ + purge_hid_serv_request(&hs_conn_ident->identity_pk); + + /* The v2 subsystem cleans up the intro point time out flag at this stage. + * We don't try to do it here because we still need to keep intact the intro + * point state for future connections. Even though we are able to connect to + * the service, doesn't mean we should reset the timed out intro points. + * + * It is not possible to have successfully connected to an intro point + * present in our cache that was on error or timed out. Every entry in that + * cache have a 2 minutes lifetime so ultimately the intro point(s) state + * will be reset and thus possible to be retried. */ +} + +/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its + * descriptor by launching a dir connection to <b>hsdir</b>. Return a + * hs_client_fetch_status_t status code depending on how it went. */ +static hs_client_fetch_status_t +directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, + const routerstatus_t *hsdir) +{ + uint64_t current_time_period = hs_get_time_period_num(0); + ed25519_public_key_t blinded_pubkey; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + hs_ident_dir_conn_t hs_conn_dir_ident; + int retval; + + tor_assert(hsdir); + tor_assert(onion_identity_pk); + + /* Get blinded pubkey */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return HS_CLIENT_FETCH_ERROR; + } + + /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ + hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey, + &hs_conn_dir_ident); + + /* Setup directory request */ + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_HSDESC); + directory_request_set_routerstatus(req, hsdir); + directory_request_set_indirection(req, DIRIND_ANONYMOUS); + directory_request_set_resource(req, base64_blinded_pubkey); + directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident); + directory_initiate_request(req); + directory_request_free(req); + + log_info(LD_REND, "Descriptor fetch request for service %s with blinded " + "key %s to directory %s", + safe_str_client(ed25519_fmt(onion_identity_pk)), + safe_str_client(base64_blinded_pubkey), + safe_str_client(routerstatus_describe(hsdir))); + + /* Cleanup memory. */ + memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey)); + memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey)); + memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident)); + + return HS_CLIENT_FETCH_LAUNCHED; +} + +/** Return the HSDir we should use to fetch the descriptor of the hidden + * service with identity key <b>onion_identity_pk</b>. */ +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) +{ + int retval; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + uint64_t current_time_period = hs_get_time_period_num(0); + smartlist_t *responsible_hsdirs = NULL; + ed25519_public_key_t blinded_pubkey; + routerstatus_t *hsdir_rs = NULL; + + tor_assert(onion_identity_pk); + + /* Get blinded pubkey of hidden service */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return NULL; + } + + /* Get responsible hsdirs of service for this time period */ + responsible_hsdirs = smartlist_new(); + + hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, + 0, 1, responsible_hsdirs); + + log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.", + smartlist_len(responsible_hsdirs)); + + /* Pick an HSDir from the responsible ones. The ownership of + * responsible_hsdirs is given to this function so no need to free it. */ + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + + return hsdir_rs; +} + +/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>. + * + * On success, HS_CLIENT_FETCH_LAUNCHED is returned. Otherwise, an error from + * hs_client_fetch_status_t is returned. */ +MOCK_IMPL(STATIC hs_client_fetch_status_t, +fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)) +{ + routerstatus_t *hsdir_rs =NULL; + + tor_assert(onion_identity_pk); + + hsdir_rs = pick_hsdir_v3(onion_identity_pk); + if (!hsdir_rs) { + log_info(LD_REND, "Couldn't pick a v3 hsdir."); + return HS_CLIENT_FETCH_NO_HSDIRS; + } + + return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); +} + +/* Make sure that the given v3 origin circuit circ is a valid correct + * introduction circuit. This will BUG() on any problems and hard assert if + * the anonymity of the circuit is not ok. Return 0 on success else -1 where + * the circuit should be mark for closed immediately. */ +static int +intro_circ_is_ok(const origin_circuit_t *circ) +{ + int ret = 0; + + tor_assert(circ); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + ret = -1; + } + if (BUG(circ->hs_ident == NULL)) { + ret = -1; + } + if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) { + ret = -1; + } + + /* This can stop the tor daemon but we want that since if we don't have + * anonymity on this circuit, something went really wrong. */ + assert_circ_anonymity_ok(circ, get_options()); + return ret; +} + +/* Find a descriptor intro point object that matches the given ident in the + * given descriptor desc. Return NULL if not found. */ +static const hs_desc_intro_point_t * +find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, + const hs_descriptor_t *desc) +{ + const hs_desc_intro_point_t *intro_point = NULL; + + tor_assert(ident); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (ed25519_pubkey_eq(&ident->intro_auth_pk, + &ip->auth_key_cert->signed_key)) { + intro_point = ip; + break; + } + } SMARTLIST_FOREACH_END(ip); + + return intro_point; +} + +/* Find a descriptor intro point object from the descriptor object desc that + * matches the given legacy identity digest in legacy_id. Return NULL if not + * found. */ +static hs_desc_intro_point_t * +find_desc_intro_point_by_legacy_id(const char *legacy_id, + const hs_descriptor_t *desc) +{ + hs_desc_intro_point_t *ret_ip = NULL; + + tor_assert(legacy_id); + tor_assert(desc); + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + /* Not all tor node have an ed25519 identity key so we still rely on the + * legacy identity digest. */ + if (lspec->type != LS_LEGACY_ID) { + continue; + } + if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) { + break; + } + /* Found it. */ + ret_ip = ip; + goto end; + } SMARTLIST_FOREACH_END(lspec); + } SMARTLIST_FOREACH_END(ip); + + end: + return ret_ip; +} + +/* Send an INTRODUCE1 cell along the intro circuit and populate the rend + * circuit identifier with the needed key material for the e2e encryption. + * Return 0 on success, -1 if there is a transient error such that an action + * has been taken to recover and -2 if there is a permanent error indicating + * that both circuits were closed. */ +static int +send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + int status; + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const ed25519_public_key_t *service_identity_pk = NULL; + const hs_desc_intro_point_t *ip; + + tor_assert(rend_circ); + if (intro_circ_is_ok(intro_circ) < 0) { + goto perm_err; + } + + service_identity_pk = &intro_circ->hs_ident->identity_pk; + /* For logging purposes. There will be a time where the hs_ident will have a + * version number but for now there is none because it's all v3. */ + hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address); + + log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u", + safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id); + + /* 1) Get descriptor from our cache. */ + const hs_descriptor_t *desc = + hs_cache_lookup_as_client(service_identity_pk); + if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk, + desc)) { + log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.", + safe_str_client(onion_address), + (desc) ? "didn't have usable intro points" : + "didn't have a descriptor"); + hs_client_refetch_hsdesc(service_identity_pk); + /* We just triggered a refetch, make sure every connections are back + * waiting for that descriptor. */ + flag_all_conn_wait_desc(service_identity_pk); + /* We just asked for a refetch so this is a transient error. */ + goto tran_err; + } + + /* We need to find which intro point in the descriptor we are connected to + * on intro_circ. */ + ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); + if (BUG(ip == NULL)) { + /* If we can find a descriptor from this introduction circuit ident, we + * must have a valid intro point object. Permanent error. */ + goto perm_err; + } + + /* Send the INTRODUCE1 cell. */ + if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, + desc->subcredential) < 0) { + /* Unable to send the cell, the intro circuit has been marked for close so + * this is a permanent error. */ + tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close); + goto perm_err; + } + + /* Cell has been sent successfully. Copy the introduction point + * authentication and encryption key in the rendezvous circuit identifier so + * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */ + memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key, + sizeof(rend_circ->hs_ident->intro_enc_pk)); + ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk, + &intro_circ->hs_ident->intro_auth_pk); + + /* Now, we wait for an ACK or NAK on this circuit. */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */ + TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(intro_circ); + + /* Success. */ + status = 0; + goto end; + + perm_err: + /* Permanent error: it is possible that the intro circuit was closed prior + * because we weren't able to send the cell. Make sure we don't double close + * it which would result in a warning. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL); + } + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL); + status = -2; + goto end; + + tran_err: + status = -1; + + end: + memwipe(onion_address, 0, sizeof(onion_address)); + return status; +} + +/* Using the introduction circuit circ, setup the authentication key of the + * intro point this circuit has extended to. */ +static void +setup_intro_circ_auth_key(origin_circuit_t *circ) +{ + const hs_descriptor_t *desc; + const hs_desc_intro_point_t *ip; + + tor_assert(circ); + + desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* Opening intro circuit without the descriptor is no good... */ + goto end; + } + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + ip = find_desc_intro_point_by_legacy_id( + circ->build_state->chosen_exit->identity_digest, desc); + if (ip) { + /* We got it, copy its authentication key to the identifier. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_cert->signed_key); + goto end; + } + + /* Reaching this point means we didn't find any intro point for this circuit + * which is not suppose to happen. */ + tor_assert_nonfatal_unreached(); + + end: + return; +} + +/* Called when an introduction circuit has opened. */ +static void +client_intro_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + + /* This is an introduction circuit so we'll attach the correct + * authentication key to the circuit identifier so it can be identified + * properly later on. */ + setup_intro_circ_auth_key(circ); + + connection_ap_attach_pending(1); +} + +/* Called when a rendezvous circuit has opened. */ +static void +client_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + const extend_info_t *rp_ei = circ->build_state->chosen_exit; + + /* Check that we didn't accidentally choose a node that does not understand + * the v3 rendezvous protocol */ + if (rp_ei) { + const node_t *rp_node = node_get_by_id(rp_ei->identity_digest); + if (rp_node) { + if (BUG(!node_supports_v3_rendezvous_point(rp_node))) { + return; + } + } + } + + log_info(LD_REND, "Rendezvous circuit has opened to %s.", + safe_str_client(extend_info_describe(rp_ei))); + + /* Ignore returned value, nothing we can really do. On failure, the circuit + * will be marked for close. */ + hs_circ_send_establish_rendezvous(circ); + + /* Register rend circuit in circuitmap if it's still alive. */ + if (!TO_CIRCUIT(circ)->marked_for_close) { + hs_circuitmap_register_rend_circ_client_side(circ, + circ->hs_ident->rendezvous_cookie); + } +} + +/* This is an helper function that convert a descriptor intro point object ip + * to a newly allocated extend_info_t object fully initialized. Return NULL if + * we can't convert it for which chances are that we are missing or malformed + * link specifiers. */ +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) +{ + extend_info_t *ei; + smartlist_t *lspecs = smartlist_new(); + + tor_assert(ip); + + /* We first encode the descriptor link specifiers into the binary + * representation which is a trunnel object. */ + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, desc_lspec) { + link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec); + smartlist_add(lspecs, lspec); + } SMARTLIST_FOREACH_END(desc_lspec); + + /* Explicitely put the direct connection option to 0 because this is client + * side and there is no such thing as a non anonymous client. */ + ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0); + + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls)); + smartlist_free(lspecs); + return ei; +} + +/* Return true iff the intro point ip for the service service_pk is usable. + * This function checks if the intro point is in the client intro state cache + * and checks at the failures. It is considered usable if: + * - No error happened (INTRO_POINT_FAILURE_GENERIC) + * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT) + * - The unreachable count is lower than + * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE) + */ +static int +intro_point_is_usable(const ed25519_public_key_t *service_pk, + const hs_desc_intro_point_t *ip) +{ + const hs_cache_intro_state_t *state; + + tor_assert(service_pk); + tor_assert(ip); + + state = hs_cache_client_intro_state_find(service_pk, + &ip->auth_key_cert->signed_key); + if (state == NULL) { + /* This means we've never encountered any problem thus usable. */ + goto usable; + } + if (state->error) { + log_info(LD_REND, "Intro point with auth key %s had an error. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->timed_out) { + log_info(LD_REND, "Intro point with auth key %s timed out. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) { + log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + + usable: + return 1; + not_usable: + return 0; +} + +/* Using a descriptor desc, return a newly allocated extend_info_t object of a + * randomly picked introduction point from its list. Return NULL if none are + * usable. */ +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk) +{ + extend_info_t *ei = NULL, *ei_excluded = NULL; + smartlist_t *usable_ips = NULL; + const hs_descriptor_t *desc; + const hs_desc_encrypted_data_t *enc_data; + const or_options_t *options = get_options(); + /* Calculate the onion address for logging purposes */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + tor_assert(service_pk); + + desc = hs_cache_lookup_as_client(service_pk); + /* Assume the service is v3 if the descriptor is missing. This is ok, + * because we only use the address in log messages */ + hs_build_address(service_pk, + desc ? desc->plaintext_data.version : HS_VERSION_THREE, + onion_address); + if (desc == NULL || !hs_client_any_intro_points_usable(service_pk, + desc)) { + log_info(LD_REND, "Unable to randomly select an introduction point " + "for service %s because descriptor %s. We can't connect.", + safe_str_client(onion_address), + (desc) ? "doesn't have any usable intro points" + : "is missing (assuming v3 onion address)"); + goto end; + } + + enc_data = &desc->encrypted_data; + usable_ips = smartlist_new(); + smartlist_add_all(usable_ips, enc_data->intro_points); + while (smartlist_len(usable_ips) != 0) { + int idx; + const hs_desc_intro_point_t *ip; + + /* Pick a random intro point and immediately remove it from the usable + * list so we don't pick it again if we have to iterate more. */ + idx = crypto_rand_int(smartlist_len(usable_ips)); + ip = smartlist_get(usable_ips, idx); + smartlist_del(usable_ips, idx); + + /* We need to make sure we have a usable intro points which is in a good + * state in our cache. */ + if (!intro_point_is_usable(service_pk, ip)) { + continue; + } + + /* Generate an extend info object from the intro point object. */ + ei = desc_intro_point_to_extend_info(ip); + if (ei == NULL) { + /* We can get here for instance if the intro point is a private address + * and we aren't allowed to extend to those. */ + log_info(LD_REND, "Unable to select introduction point with auth key %s " + "for service %s, because we could not extend to it.", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)), + safe_str_client(onion_address)); + continue; + } + + /* Test the pick against ExcludeNodes. */ + if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) { + /* If this pick is in the ExcludeNodes list, we keep its reference so if + * we ever end up not being able to pick anything else and StrictNodes is + * unset, we'll use it. */ + if (ei_excluded) { + /* If something was already here free it. After the loop is gone we + * will examine the last excluded intro point, and that's fine since + * that's random anyway */ + extend_info_free(ei_excluded); + } + ei_excluded = ei; + continue; + } + + /* Good pick! Let's go with this. */ + goto end; + } + + /* Reaching this point means a couple of things. Either we can't use any of + * the intro point listed because the IP address can't be extended to or it + * is listed in the ExcludeNodes list. In the later case, if StrictNodes is + * set, we are forced to not use anything. */ + ei = ei_excluded; + if (options->StrictNodes) { + log_warn(LD_REND, "Every introduction point for service %s is in the " + "ExcludeNodes set and StrictNodes is set. We can't connect.", + safe_str_client(onion_address)); + extend_info_free(ei); + ei = NULL; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service " + "%s is unusable or we can't extend to it. We can't connect.", + safe_str_client(onion_address)); + } + + end: + smartlist_free(usable_ips); + memwipe(onion_address, 0, sizeof(onion_address)); + return ei; +} + +/* For this introduction circuit, we'll look at if we have any usable + * introduction point left for this service. If so, we'll use the circuit to + * re-extend to a new intro point. Else, we'll close the circuit and its + * corresponding rendezvous circuit. Return 0 if we are re-extending else -1 + * if we are closing the circuits. + * + * This is called when getting an INTRODUCE_ACK cell with a NACK. */ +static int +close_or_reextend_intro_circ(origin_circuit_t *intro_circ) +{ + int ret = -1; + const hs_descriptor_t *desc; + origin_circuit_t *rend_circ; + + tor_assert(intro_circ); + + desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* We can't continue without a descriptor. */ + goto close; + } + /* We still have the descriptor, great! Let's try to see if we can + * re-extend by looking up if there are any usable intro points. */ + if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk, + desc)) { + goto close; + } + /* Try to re-extend now. */ + if (hs_client_reextend_intro_circuit(intro_circ) < 0) { + goto close; + } + /* Success on re-extending. Don't return an error. */ + ret = 0; + goto end; + + close: + /* Change the intro circuit purpose before so we don't report an intro point + * failure again triggering an extra descriptor fetch. The circuit can + * already be closed on failure to re-extend. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + } + /* Close the related rendezvous circuit. */ + rend_circ = hs_circuitmap_get_rend_circ_client_side( + intro_circ->hs_ident->rendezvous_cookie); + /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was + * inflight so we can't expect one every time. */ + if (rend_circ) { + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED); + } + + end: + return ret; +} + +/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate + * actions for the rendezvous point and finally close intro_circ. */ +static void +handle_introduce_ack_success(origin_circuit_t *intro_circ) +{ + origin_circuit_t *rend_circ = NULL; + + tor_assert(intro_circ); + + log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous"); + + /* Get the rendezvous circuit for this rendezvous cookie. */ + uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie; + rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie); + if (rend_circ == NULL) { + log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping"); + goto end; + } + + assert_circ_anonymity_ok(rend_circ, get_options()); + + /* It is possible to get a RENDEZVOUS2 cell before the INTRODUCE_ACK which + * means that the circuit will be joined and already transmitting data. In + * that case, simply skip the purpose change and close the intro circuit + * like it should be. */ + if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { + goto end; + } + circuit_change_purpose(TO_CIRCUIT(rend_circ), + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the + * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */ + TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL); + + end: + /* We don't need the intro circuit anymore. It did what it had to do! */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + + /* XXX: Close pending intro circuits we might have in parallel. */ + return; +} + +/* Called when we get an INTRODUCE_ACK failure status code. Depending on our + * failure cache status, either close the circuit or re-extend to a new + * introduction point. */ +static void +handle_introduce_ack_bad(origin_circuit_t *circ, int status) +{ + tor_assert(circ); + + log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u", + safe_str_client(extend_info_describe(circ->build_state->chosen_exit)), + status); + + /* It's a NAK. The introduction point didn't relay our request. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); + + /* Note down this failure in the intro point failure cache. Depending on how + * many times we've tried this intro point, close it or reextend. */ + hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk, + &circ->hs_ident->intro_auth_pk, + INTRO_POINT_FAILURE_GENERIC); +} + +/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded + * cell is in payload of length payload_len. Return 0 on success else a + * negative value. The circuit is either close or reuse to re-extend to a new + * introduction point. */ +static int +handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int status, ret = -1; + + tor_assert(circ); + tor_assert(circ->build_state); + tor_assert(circ->build_state->chosen_exit); + assert_circ_anonymity_ok(circ, get_options()); + tor_assert(payload); + + status = hs_cell_parse_introduce_ack(payload, payload_len); + switch (status) { + case HS_CELL_INTRO_ACK_SUCCESS: + ret = 0; + handle_introduce_ack_success(circ); + goto end; + case HS_CELL_INTRO_ACK_FAILURE: + case HS_CELL_INTRO_ACK_BADFMT: + case HS_CELL_INTRO_ACK_NORELAY: + handle_introduce_ack_bad(circ, status); + /* We are going to see if we have to close the circuits (IP and RP) or we + * can re-extend to a new intro point. */ + ret = close_or_reextend_intro_circ(circ); + break; + default: + log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s", + status, + safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); + break; + } + + end: + return ret; +} + +/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The + * encoded cell is in payload of length payload_len. Return 0 on success or a + * negative value on error. On error, the circuit is marked for close. */ +STATIC int +handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + curve25519_public_key_t server_pk; + uint8_t auth_mac[DIGEST256_LEN] = {0}; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0}; + hs_ntor_rend_cell_keys_t keys; + const hs_ident_circuit_t *ident; + + tor_assert(circ); + tor_assert(payload); + + /* Make things easier. */ + ident = circ->hs_ident; + tor_assert(ident); + + if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info, + sizeof(handshake_info)) < 0) { + goto err; + } + /* Get from the handshake info the SERVER_PK and AUTH_MAC. */ + memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN); + memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac)); + + /* Generate the handshake info. */ + if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk, + &ident->rendezvous_client_kp, + &ident->intro_enc_pk, &server_pk, + &keys) < 0) { + log_info(LD_REND, "Unable to compute the rendezvous keys."); + goto err; + } + + /* Critical check, make sure that the MAC matches what we got with what we + * computed just above. */ + if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) { + log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell."); + goto err; + } + + /* Setup the e2e encryption on the circuit and finalize its state. */ + if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed, + sizeof(keys.ntor_key_seed), 0) < 0) { + log_info(LD_REND, "Unable to setup the e2e encryption."); + goto err; + } + /* Success. Hidden service connection finalized! */ + ret = 0; + goto end; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + end: + memwipe(&keys, 0, sizeof(keys)); + return ret; +} + +/* Return true iff the client can fetch a descriptor for this service public + * identity key and status_out if not NULL is untouched. If the client can + * _not_ fetch the descriptor and if status_out is not NULL, it is set with + * the fetch status code. */ +static unsigned int +can_client_refetch_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t *status_out) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + /* Are we configured to fetch descriptors? */ + if (!get_options()->FetchHidServDescriptors) { + log_warn(LD_REND, "We received an onion address for a hidden service " + "descriptor but we are configured to not fetch."); + status = HS_CLIENT_FETCH_NOT_ALLOWED; + goto cannot; + } + + /* Without a live consensus we can't do any client actions. It is needed to + * compute the hashring for a service. */ + if (!networkstatus_get_live_consensus(approx_time())) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "are missing a live consensus. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + if (!router_have_minimum_dir_info()) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "dont have enough descriptors. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + /* Check if fetching a desc for this HS is useful to us right now */ + { + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(identity_pk); + if (cached_desc && hs_client_any_intro_points_usable(identity_pk, + cached_desc)) { + log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor " + "but we already have a usable descriptor."); + status = HS_CLIENT_FETCH_HAVE_DESC; + goto cannot; + } + } + + /* Don't try to refetch while we have a pending request for it. */ + if (directory_request_is_pending(identity_pk)) { + log_info(LD_REND, "Already a pending directory request. Waiting on it."); + status = HS_CLIENT_FETCH_PENDING; + goto cannot; + } + + /* Yes, client can fetch! */ + return 1; + cannot: + if (status_out) { + *status_out = status; + } + return 0; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/** A circuit just finished connecting to a hidden service that the stream + * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */ +void +hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) +{ + tor_assert(connection_edge_is_rendezvous_stream(conn)); + + if (BUG(conn->rend_data && conn->hs_ident)) { + log_warn(LD_BUG, "Stream had both rend_data and hs_ident..." + "Prioritizing hs_ident"); + } + + if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */ + note_connection_attempt_succeeded(conn->hs_ident); + return; + } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */ + rend_client_note_connection_attempt_ended(conn->rend_data); + return; + } +} + +/* With the given encoded descriptor in desc_str and the service key in + * service_identity_pk, decode the descriptor and set the desc pointer with a + * newly allocated descriptor object. + * + * Return 0 on success else a negative value and desc is set to NULL. */ +int +hs_client_decode_descriptor(const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc) +{ + int ret; + uint8_t subcredential[DIGEST256_LEN]; + ed25519_public_key_t blinded_pubkey; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + tor_assert(desc); + + /* Create subcredential for this HS so that we can decrypt */ + { + uint64_t current_time_period = hs_get_time_period_num(0); + hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, + &blinded_pubkey); + hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); + } + + /* Parse descriptor */ + ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); + memwipe(subcredential, 0, sizeof(subcredential)); + if (ret < 0) { + log_warn(LD_GENERAL, "Could not parse received descriptor as client."); + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_NONE) { + log_warn(LD_GENERAL, "%s", escaped(desc_str)); + } + goto err; + } + + /* Make sure the descriptor signing key cross certifies with the computed + * blinded key. Without this validation, anyone knowing the subcredential + * and onion address can forge a descriptor. */ + tor_cert_t *cert = (*desc)->plaintext_data.signing_key_cert; + if (tor_cert_checksig(cert, + &blinded_pubkey, approx_time()) < 0) { + log_warn(LD_GENERAL, "Descriptor signing key certificate signature " + "doesn't validate with computed blinded key: %s", + tor_cert_describe_signature_status(cert)); + goto err; + } + + return 0; + err: + return -1; +} + +/* Return true iff there are at least one usable intro point in the service + * descriptor desc. */ +int +hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc) +{ + tor_assert(service_pk); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (intro_point_is_usable(service_pk, ip)) { + goto usable; + } + } SMARTLIST_FOREACH_END(ip); + + return 0; + usable: + return 1; +} + +/** Launch a connection to a hidden service directory to fetch a hidden + * service descriptor using <b>identity_pk</b> to get the necessary keys. + * + * A hs_client_fetch_status_t code is returned. */ +int +hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + if (!can_client_refetch_desc(identity_pk, &status)) { + return status; + } + + /* Try to fetch the desc and if we encounter an unrecoverable error, mark + * the desc as unavailable for now. */ + status = fetch_v3_desc(identity_pk); + if (fetch_status_should_close_socks(status)) { + close_all_socks_conns_waiting_for_desc(identity_pk, status, + END_STREAM_REASON_RESOLVEFAILED); + /* Remove HSDir fetch attempts so that we can retry later if the user + * wants us to regardless of if we closed any connections. */ + purge_hid_serv_request(identity_pk); + } + return status; +} + +/* This is called when we are trying to attach an AP connection to these + * hidden service circuits from connection_ap_handshake_attach_circuit(). + * Return 0 on success, -1 for a transient error that is actions were + * triggered to recover or -2 for a permenent error where both circuits will + * marked for close. + * + * The following supports every hidden service version. */ +int +hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) : + rend_client_send_introduction(intro_circ, + rend_circ); +} + +/* Called when the client circuit circ has been established. It can be either + * an introduction or rendezvous circuit. This function handles all hidden + * service versions. */ +void +hs_client_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_C_INTRODUCING: + if (circ->hs_ident) { + client_intro_circ_has_opened(circ); + } else { + rend_client_introcirc_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: + if (circ->hs_ident) { + client_rendezvous_circ_has_opened(circ); + } else { + rend_client_rendcirc_has_opened(circ); + } + break; + default: + tor_assert_nonfatal_unreached(); + } +} + +/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of + * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a + * negative value and the circuit marked for close. */ +int +hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + tor_assert(circ); + tor_assert(payload); + + (void) payload_len; + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not " + "expecting one. Closing circuit."); + goto err; + } + + log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is " + "now ready for rendezvous."); + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); + + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_REND_READY state. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* From a path bias point of view, this circuit is now successfully used. + * Waiting any longer opens us up to attacks from malicious hidden services. + * They could induce the client to attempt to connect to their hidden + * service and never reply to the client's rend requests */ + pathbias_mark_use_success(circ); + + /* If we already have the introduction circuit built, make sure we send + * the INTRODUCE cell _now_ */ + connection_ap_attach_pending(1); + + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* This is called when a descriptor has arrived following a fetch request and + * has been stored in the client cache. Every entry connection that matches + * the service identity key in the ident will get attached to the hidden + * service circuit. */ +void +hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) +{ + time_t now = time(NULL); + smartlist_t *conns = NULL; + + tor_assert(ident); + + conns = connection_list_by_type_state(CONN_TYPE_AP, + AP_CONN_STATE_RENDDESC_WAIT); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const hs_descriptor_t *desc; + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we just fetched its descriptor. */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(&ident->identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + + /* We were just called because we stored the descriptor for this service + * so not finding a descriptor means we have a bigger problem. */ + desc = hs_cache_lookup_as_client(&ident->identity_pk); + if (BUG(desc == NULL)) { + goto end; + } + + if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) { + log_info(LD_REND, "Hidden service descriptor is unusable. " + "Closing streams."); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_RESOLVEFAILED); + /* We are unable to use the descriptor so remove the directory request + * from the cache so the next connection can try again. */ + note_connection_attempt_succeeded(edge_conn->hs_ident); + goto end; + } + + log_info(LD_REND, "Descriptor has arrived. Launching circuits."); + + /* Because the connection can now proceed to opening circuit and + * ultimately connect to the service, reset those timestamp so the + * connection is considered "fresh" and can continue without being closed + * too early. */ + base_conn->timestamp_created = now; + base_conn->timestamp_lastread = now; + base_conn->timestamp_lastwritten = now; + /* Change connection's state into waiting for a circuit. */ + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + + connection_ap_mark_as_pending_circuit(entry_conn); + } SMARTLIST_FOREACH_END(base_conn); + + end: + /* We don't have ownership of the objects in this list. */ + smartlist_free(conns); +} + +/* Return a newly allocated extend_info_t for a randomly chosen introduction + * point for the given edge connection identifier ident. Return NULL if we + * can't pick any usable introduction points. */ +extend_info_t * +hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) +{ + tor_assert(edge_conn); + + return (edge_conn->hs_ident) ? + client_get_random_intro(&edge_conn->hs_ident->identity_pk) : + rend_client_get_random_intro(edge_conn->rend_data); +} +/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ. + * Return 0 on success else a negative value is returned. The circuit will be + * closed or reuse to extend again to another intro point. */ +int +hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) : + rend_client_introduction_acked(circ, payload, + payload_len); + /* For path bias: This circuit was used successfully. NACK or ACK counts. */ + pathbias_mark_use_success(circ); + + end: + return ret; +} + +/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return + * 0 on success else a negative value is returned. The circuit will be closed + * on error. */ +int +hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Circuit can possibly be in both state because we could receive a + * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. " + "Closing circuit.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.", + TO_CIRCUIT(circ)->n_circ_id); + + ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) : + rend_client_receive_rendezvous(circ, payload, + payload_len); + end: + return ret; +} + +/* Extend the introduction circuit circ to another valid introduction point + * for the hidden service it is trying to connect to, or mark it and launch a + * new circuit if we can't extend it. Return 0 on success or possible + * success. Return -1 and mark the introduction circuit for close on permanent + * failure. + * + * On failure, the caller is responsible for marking the associated rendezvous + * circuit for close. */ +int +hs_client_reextend_intro_circuit(origin_circuit_t *circ) +{ + int ret = -1; + extend_info_t *ei; + + tor_assert(circ); + + ei = (circ->hs_ident) ? + client_get_random_intro(&circ->hs_ident->identity_pk) : + rend_client_get_random_intro(circ->rend_data); + if (ei == NULL) { + log_warn(LD_REND, "No usable introduction points left. Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + + if (circ->remaining_relay_early_cells) { + log_info(LD_REND, "Re-extending circ %u, this time to %s.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(extend_info_describe(ei))); + ret = circuit_extend_to_new_exit(circ, ei); + if (ret == 0) { + /* We were able to extend so update the timestamp so we avoid expiring + * this circuit too early. The intro circuit is short live so the + * linkability issue is minimized, we just need the circuit to hold a + * bit longer so we can introduce. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + } + } else { + log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ + ret = 0; + } + + end: + extend_info_free(ei); + return ret; +} + +/* Release all the storage held by the client subsystem. */ +void +hs_client_free_all(void) +{ + /* Purge the hidden service request cache. */ + hs_purge_last_hid_serv_requests(); +} + +/* Purge all potentially remotely-detectable state held in the hidden + * service client code. Called on SIGNAL NEWNYM. */ +void +hs_client_purge_state(void) +{ + /* v2 subsystem. */ + rend_client_purge_state(); + + /* Cancel all descriptor fetches. Do this first so once done we are sure + * that our descriptor cache won't modified. */ + cancel_descriptor_fetches(); + /* Purge the introduction point state cache. */ + hs_cache_client_intro_state_purge(); + /* Purge the descriptor cache. */ + hs_cache_purge_as_client(); + /* Purge the last hidden service request cache. */ + hs_purge_last_hid_serv_requests(); + + log_info(LD_REND, "Hidden service client state has been purged."); +} + +/* Called when our directory information has changed. */ +void +hs_client_dir_info_changed(void) +{ + /* We have possibly reached the minimum directory information or new + * consensus so retry all pending SOCKS connection in + * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */ + retry_all_socks_conn_waiting_for_desc(); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h new file mode 100644 index 0000000000..2523568ad1 --- /dev/null +++ b/src/or/hs_client.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.h + * \brief Header file containing client data for the HS subsytem. + **/ + +#ifndef TOR_HS_CLIENT_H +#define TOR_HS_CLIENT_H + +#include "crypto_ed25519.h" +#include "hs_descriptor.h" +#include "hs_ident.h" + +/* Status code of a descriptor fetch request. */ +typedef enum { + /* Something internally went wrong. */ + HS_CLIENT_FETCH_ERROR = -1, + /* The fetch request has been launched successfully. */ + HS_CLIENT_FETCH_LAUNCHED = 0, + /* We already have a usable descriptor. No fetch. */ + HS_CLIENT_FETCH_HAVE_DESC = 1, + /* No more HSDir available to query. */ + HS_CLIENT_FETCH_NO_HSDIRS = 2, + /* The fetch request is not allowed. */ + HS_CLIENT_FETCH_NOT_ALLOWED = 3, + /* We are missing information to be able to launch a request. */ + HS_CLIENT_FETCH_MISSING_INFO = 4, + /* There is a pending fetch for the requested service. */ + HS_CLIENT_FETCH_PENDING = 5, +} hs_client_fetch_status_t; + +void hs_client_note_connection_attempt_succeeded( + const edge_connection_t *conn); + +int hs_client_decode_descriptor( + const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc); +int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc); +int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); +void hs_client_dir_info_changed(void); + +int hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ); + +void hs_client_circuit_has_opened(origin_circuit_t *circ); + +int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); + +extend_info_t *hs_client_get_random_intro_from_edge( + const edge_connection_t *edge_conn); + +int hs_client_reextend_intro_circuit(origin_circuit_t *circ); + +void hs_client_purge_state(void); + +void hs_client_free_all(void); + +#ifdef HS_CLIENT_PRIVATE + +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); + +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk); + +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip); + +STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len); + +MOCK_DECL(STATIC hs_client_fetch_status_t, + fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)); + +#endif /* defined(HS_CLIENT_PRIVATE) */ + +#endif /* !defined(TOR_HS_CLIENT_H) */ + diff --git a/src/or/hs_common.c b/src/or/hs_common.c new file mode 100644 index 0000000000..e9d7323316 --- /dev/null +++ b/src/or/hs_common.c @@ -0,0 +1,1810 @@ +/* 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 "circuitbuild.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "hs_cache.h" +#include "hs_common.h" +#include "hs_client.h" +#include "hs_ident.h" +#include "hs_service.h" +#include "hs_circuitmap.h" +#include "policies.h" +#include "rendcommon.h" +#include "rendservice.h" +#include "routerset.h" +#include "router.h" +#include "routerset.h" +#include "shared_random.h" +#include "shared_random_state.h" + +/* Trunnel */ +#include "ed25519_cert.h" + +/* Ed25519 Basepoint value. Taken from section 5 of + * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ +static const char *str_ed25519_basepoint = + "(15112221349535400772501151409588531511" + "454012693041857206046113283949847762202, " + "463168356949264781694283940034751631413" + "07993866256225615783033603165251855960)"; + +#ifdef HAVE_SYS_UN_H + +/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, + * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* !(defined(HAVE_SYS_UN_H)) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* defined(HAVE_SYS_UN_H) */ + +/* Helper function: The key is a digest that we compare to a node_t object + * current hsdir_index. */ +static int +compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->fetch, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_first_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_first, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_second_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_second, DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects current hsdir_index. */ +static int +compare_node_fetch_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->fetch, + node2->hsdir_index->fetch, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_first_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_first, + node2->hsdir_index->store_first, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_second_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_second, + node2->hsdir_index->store_second, + DIGEST256_LEN); +} + +/* Allocate and return a string containing the path to filename in directory. + * This function will never return NULL. The caller must free this path. */ +char * +hs_path_from_filename(const char *directory, const char *filename) +{ + char *file_path = NULL; + + tor_assert(directory); + tor_assert(filename); + + tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename); + return file_path; +} + +/* Make sure that the directory for <b>service</b> is private, using the config + * <b>username</b>. + * 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; +} + +/* Default, minimum and maximum values for the maximum rendezvous failures + * consensus parameter. */ +#define MAX_REND_FAILURES_DEFAULT 2 +#define MAX_REND_FAILURES_MIN 1 +#define MAX_REND_FAILURES_MAX 10 + +/** How many times will a hidden service operator attempt to connect to + * a requested rendezvous point before giving up? */ +int +hs_get_service_max_rend_failures(void) +{ + return networkstatus_get_param(NULL, "hs_service_max_rdv_failures", + MAX_REND_FAILURES_DEFAULT, + MAX_REND_FAILURES_MIN, + MAX_REND_FAILURES_MAX); +} + +/** 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>. If <b>now</b> is not set, + * we try to get the time ourselves from a live consensus. */ +uint64_t +hs_get_time_period_num(time_t now) +{ + uint64_t time_period_num; + time_t current_time; + + /* If no time is specified, set current time based on consensus time, and + * only fall back to system time if that fails. */ + if (now != 0) { + current_time = now; + } else { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + current_time = ns ? ns->valid_after : approx_time(); + } + + /* Start by calculating minutes since the epoch */ + uint64_t time_period_length = get_time_period_length(); + uint64_t minutes_since_epoch = current_time / 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>. If <b>now</b> is not set, we try to get the time from a + * live consensus. */ +uint64_t +hs_get_next_time_period_num(time_t now) +{ + return hs_get_time_period_num(now) + 1; +} + +/* Get the number of the _previous_ HS time period, given that the current time + * is <b>now</b>. If <b>now</b> is not set, we try to get the time from a live + * consensus. */ +uint64_t +hs_get_previous_time_period_num(time_t now) +{ + return hs_get_time_period_num(now) - 1; +} + +/* Return the start time of the upcoming time period based on <b>now</b>. If + <b>now</b> is not set, we try to get the time ourselves from a live + consensus. */ +time_t +hs_get_start_time_of_next_time_period(time_t now) +{ + uint64_t time_period_length = get_time_period_length(); + + /* Get start time of next time period */ + uint64_t next_time_period_num = hs_get_next_time_period_num(now); + uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length; + + /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset); +} + +/* Create a new rend_data_t for a specific given <b>version</b>. + * 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. + */ +static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; +static uint64_t cached_time_period_nums[2] = {0}; + +/** Compute the disaster SRV value for this <b>time_period_num</b> and put it + * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches + * to see if we have already computed it. */ +STATIC void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + if (time_period_num == cached_time_period_nums[0]) { + memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN); + return; + } else if (time_period_num == cached_time_period_nums[1]) { + memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN); + return; + } else { + int replace_idx; + // Replace the lower period number. + if (cached_time_period_nums[0] <= cached_time_period_nums[1]) { + replace_idx = 0; + } else { + replace_idx = 1; + } + cached_time_period_nums[replace_idx] = time_period_num; + compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]); + memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN); + return; + } +} + +#ifdef TOR_UNIT_TESTS + +/** Get the first cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_first_cached_disaster_srv(void) +{ + return cached_disaster_srv[0]; +} + +/** Get the second cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_second_cached_disaster_srv(void) +{ + return cached_disaster_srv[1]; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/* When creating a blinded key, we need a parameter which construction is as + * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). + * + * The nonce has a pre-defined format which uses the time period number + * period_num and the start of the period in second start_time_period. + * + * The secret of size secret_len is optional meaning that it can be NULL and + * thus will be ignored for the param construction. + * + * The result is put in param_out. */ +static void +build_blinded_key_param(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t period_num, uint64_t period_length, + uint8_t *param_out) +{ + size_t offset = 0; + const char blind_str[] = "Derive temporary signing key"; + uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; + crypto_digest_t *digest; + + tor_assert(pubkey); + tor_assert(param_out); + + /* Create the nonce N. The construction is as follow: + * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */ + memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); + offset += HS_KEYBLIND_NONCE_PREFIX_LEN; + set_uint64(nonce + offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(nonce + offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == HS_KEYBLIND_NONCE_LEN); + + /* Generate the parameter h and the construction is as follow: + * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str)); + crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); + /* Optional secret. */ + if (secret) { + crypto_digest_add_bytes(digest, (char *) secret, secret_len); + } + crypto_digest_add_bytes(digest, str_ed25519_basepoint, + strlen(str_ed25519_basepoint)); + crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce)); + + /* Extract digest and put it in the param. */ + crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(nonce, 0, sizeof(nonce)); +} + +/* Using an ed25519 public key and version to build the checksum of an + * address. Put in checksum_out. Format is: + * SHA3-256(".onion checksum" || PUBKEY || VERSION) + * + * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */ +static void +build_hs_checksum(const ed25519_public_key_t *key, uint8_t version, + uint8_t *checksum_out) +{ + size_t offset = 0; + char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN]; + + /* Build checksum data. */ + memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX, + HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN); + offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN; + memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + set_uint8(data + offset, version); + offset += sizeof(version); + tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN); + + /* Hash the data payload to create the checksum. */ + crypto_digest256((char *) checksum_out, data, sizeof(data), + DIGEST_SHA3_256); +} + +/* Using an ed25519 public key, checksum and version to build the binary + * representation of a service address. Put in addr_out. Format is: + * addr_out = PUBKEY || CHECKSUM || VERSION + * + * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */ +static void +build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum, + uint8_t version, char *addr_out) +{ + size_t offset = 0; + + tor_assert(key); + tor_assert(checksum); + + memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + set_uint8(addr_out + offset, version); + offset += sizeof(uint8_t); + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Helper for hs_parse_address(): Using a binary representation of a service + * address, parse its content into the key_out, checksum_out and version_out. + * Any out variable can be NULL in case the caller would want only one field. + * checksum_out MUST at least be 2 bytes long. address must be at least + * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */ +static void +hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + size_t offset = 0; + + tor_assert(address); + + if (key_out) { + /* First is the key. */ + memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN); + } + offset += ED25519_PUBKEY_LEN; + if (checksum_out) { + /* Followed by a 2 bytes checksum. */ + memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + } + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + if (version_out) { + /* Finally, version value is 1 byte. */ + *version_out = get_uint8(address + offset); + } + offset += sizeof(uint8_t); + /* Extra safety. */ + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Using the given identity public key and a blinded public key, compute the + * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * This can't fail. */ +void +hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out) +{ + uint8_t credential[DIGEST256_LEN]; + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(subcred_out); + + /* First, build the credential. Construction is as follow: + * credential = H("credential" | public-identity-key) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX, + HS_CREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN); + crypto_digest_free(digest); + + /* Now, compute the subcredential. Construction is as follow: + * subcredential = H("subcredential" | credential | blinded-public-key). */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX, + HS_SUBCREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) credential, + sizeof(credential)); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(credential, 0, sizeof(credential)); +} + +/* From the given list of hidden service ports, find the ones that much the + * given edge connection conn, pick one at random and use it to set the + * connection address. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { + smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } + } + return (chosen_port) ? 0 : -1; +} + +/* Using a base32 representation of a service address, parse its content into + * the key_out, checksum_out and version_out. Any out variable can be NULL in + * case the caller would want only one field. checksum_out MUST at least be 2 + * bytes long. + * + * Return 0 if parsing went well; return -1 in case of error. */ +int +hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + char decoded[HS_SERVICE_ADDR_LEN]; + + tor_assert(address); + + /* Obvious length check. */ + if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { + log_warn(LD_REND, "Service address %s has an invalid length. " + "Expected %lu but got %lu.", + escaped_safe_str(address), + (unsigned long) HS_SERVICE_ADDR_LEN_BASE32, + (unsigned long) strlen(address)); + goto invalid; + } + + /* Decode address so we can extract needed fields. */ + if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) { + log_warn(LD_REND, "Service address %s can't be decoded.", + escaped_safe_str(address)); + goto invalid; + } + + /* Parse the decoded address into the fields we need. */ + hs_parse_address_impl(decoded, key_out, checksum_out, version_out); + + return 0; + invalid: + return -1; +} + +/* Validate a given onion address. The length, the base32 decoding and + * checksum are validated. Return 1 if valid else 0. */ +int +hs_address_is_valid(const char *address) +{ + uint8_t version; + uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED]; + uint8_t target_checksum[DIGEST256_LEN]; + ed25519_public_key_t service_pubkey; + + /* Parse the decoded address into the fields we need. */ + if (hs_parse_address(address, &service_pubkey, checksum, &version) < 0) { + goto invalid; + } + + /* Get the checksum it's suppose to be and compare it with what we have + * encoded in the address. */ + build_hs_checksum(&service_pubkey, version, target_checksum); + if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) { + log_warn(LD_REND, "Service address %s invalid checksum.", + escaped_safe_str(address)); + goto invalid; + } + + /* Validate that this pubkey does not have a torsion component. We need to do + * this on the prop224 client-side so that attackers can't give equivalent + * forms of an onion address to users. */ + if (ed25519_validate_pubkey(&service_pubkey) < 0) { + log_warn(LD_REND, "Service address %s has bad pubkey .", + escaped_safe_str(address)); + goto invalid; + } + + /* Valid address. */ + return 1; + invalid: + return 0; +} + +/* Build a service address using an ed25519 public key and a given version. + * The returned address is base32 encoded and put in addr_out. The caller MUST + * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long. + * + * Format is as follow: + * base32(PUBKEY || CHECKSUM || VERSION) + * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION) + * */ +void +hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out) +{ + uint8_t checksum[DIGEST256_LEN]; + char address[HS_SERVICE_ADDR_LEN]; + + tor_assert(key); + tor_assert(addr_out); + + /* Get the checksum of the address. */ + build_hs_checksum(key, version, checksum); + /* Get the binary address representation. */ + build_hs_address(key, checksum, version, address); + + /* Encode the address. addr_out will be NUL terminated after this. */ + base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address, + sizeof(address)); + /* Validate what we just built. */ + tor_assert(hs_address_is_valid(addr_out)); +} + +/* Return a newly allocated copy of lspec. */ +link_specifier_t * +hs_link_specifier_dup(const link_specifier_t *lspec) +{ + link_specifier_t *result = link_specifier_new(); + memcpy(result, lspec, sizeof(*result)); + /* The unrecognized field is a dynamic array so make sure to copy its + * content and not the pointer. */ + link_specifier_setlen_un_unrecognized( + result, link_specifier_getlen_un_unrecognized(lspec)); + if (link_specifier_getlen_un_unrecognized(result)) { + memcpy(link_specifier_getarray_un_unrecognized(result), + link_specifier_getconstarray_un_unrecognized(lspec), + link_specifier_getlen_un_unrecognized(result)); + } + return result; +} + +/* From a given ed25519 public key pk and an optional secret, compute a + * blinded public key and put it in blinded_pk_out. This is only useful to + * the client side because the client only has access to the identity public + * key of the service. */ +void +hs_build_blinded_pubkey(const ed25519_public_key_t *pk, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *blinded_pk_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(pk); + tor_assert(blinded_pk_out); + tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + + build_blinded_key_param(pk, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_public_blind(blinded_pk_out, pk, param); + + memwipe(param, 0, sizeof(param)); +} + +/* From a given ed25519 keypair kp and an optional secret, compute a blinded + * keypair for the current time period and put it in blinded_kp_out. This is + * only useful by the service side because the client doesn't have access to + * the identity secret key. */ +void +hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *blinded_kp_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(kp); + tor_assert(blinded_kp_out); + /* Extra safety. A zeroed key is bad. */ + tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + + build_blinded_key_param(&kp->pubkey, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_keypair_blind(blinded_kp_out, kp, param); + + memwipe(param, 0, sizeof(param)); +} + +/* Return true if we are currently in the time segment between a new time + * period and a new SRV (in the real network that happens between 12:00 and + * 00:00 UTC). Here is a diagram showing exactly when this returns true: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | + * | | + * +------------------------------------------------------------------+ + */ +MOCK_IMPL(int, +hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) +{ + time_t valid_after; + time_t srv_start_time, tp_start_time; + + if (!consensus) { + consensus = networkstatus_get_live_consensus(now); + if (!consensus) { + return 0; + } + } + + /* Get start time of next TP and of current SRV protocol run, and check if we + * are between them. */ + valid_after = consensus->valid_after; + srv_start_time = + sr_state_get_start_time_of_current_protocol_run(valid_after); + tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); + + if (valid_after >= srv_start_time && valid_after < tp_start_time) { + return 0; + } + + return 1; +} + +/* Return 1 if any virtual port in ports needs a circuit with good uptime. + * Else return 0. */ +int +hs_service_requires_uptime_circ(const smartlist_t *ports) +{ + tor_assert(ports); + + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, + p->virtual_port)) { + return 1; + } + } SMARTLIST_FOREACH_END(p); + return 0; +} + +/* Build hs_index which is used to find the responsible hsdirs. This index + * value is used to select the responsible HSDir where their hsdir_index is + * closest to this value. + * SHA3-256("store-at-idx" | blinded_public_key | + * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) ) + * + * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out) +{ + crypto_digest_t *digest; + + tor_assert(blinded_pk); + tor_assert(hs_index_out); + + /* Build hs_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + + /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */ + { + uint64_t period_length = get_time_period_length(); + char buf[sizeof(uint64_t)*3]; + size_t offset = 0; + set_uint64(buf, tor_htonll(replica)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(buf)); + + crypto_digest_add_bytes(digest, buf, sizeof(buf)); + } + + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Build hsdir_index which is used to find the responsible hsdirs. This is the + * index value that is compare to the hs_index when selecting an HSDir. + * SHA3-256("node-idx" | node_identity | + * shared_random_value | INT_8(period_length) | INT_8(period_num) ) + * + * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv_value, uint64_t period_num, + uint8_t *hsdir_index_out) +{ + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(srv_value); + tor_assert(hsdir_index_out); + + /* Build hsdir_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); + + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Return a newly allocated buffer containing the current shared random value + * or if not present, a disaster value is computed using the given time period + * number. If a consensus is provided in <b>ns</b>, use it to get the SRV + * value. This function can't fail. */ +uint8_t * +hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *current_srv = sr_get_current(ns); + + if (current_srv) { + memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return a newly allocated buffer containing the previous shared random + * value or if not present, a disaster value is computed using the given time + * period number. This function can't fail. */ +uint8_t * +hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *previous_srv = sr_get_previous(ns); + + if (previous_srv) { + memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return the number of replicas defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_n_replicas(void) +{ + /* The [1,16] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_n_replicas", + HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); +} + +/* Return the spread fetch value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_fetch(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_fetch", + HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); +} + +/* Return the spread store value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_store(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_store", + HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); +} + +/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index. + * Return 0 if everything is as expected, else return -1. */ +static int +node_has_hsdir_index(const node_t *node) +{ + tor_assert(node_supports_v3_hsdir(node)); + + /* A node can't have an HSDir index without a descriptor since we need desc + * to get its ed25519 key */ + if (!node_has_descriptor(node)) { + return 0; + } + + /* At this point, since the node has a desc, this node must also have an + * hsdir index. If not, something went wrong, so BUG out. */ + if (BUG(node->hsdir_index == NULL)) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->fetch, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_first, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_second, + DIGEST256_LEN))) { + return 0; + } + + return 1; +} + +/* For a given blinded key and time period number, get the responsible HSDir + * and put their routerstatus_t object in the responsible_dirs list. If + * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t + * is used. If 'for_fetching' is true, the spread fetch consensus parameter is + * used else the spread store is used which is only for upload. This function + * can't fail but it is possible that the responsible_dirs list contains fewer + * nodes than expected. + * + * This function goes over the latest consensus routerstatus list and sorts it + * by their node_t hsdir_index then does a binary search to find the closest + * node. All of this makes it a bit CPU intensive so use it wisely. */ +void +hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs) +{ + smartlist_t *sorted_nodes; + /* The compare function used for the smartlist bsearch. We have two + * different depending on is_next_period. */ + int (*cmp_fct)(const void *, const void **); + + tor_assert(blinded_pk); + tor_assert(responsible_dirs); + + sorted_nodes = smartlist_new(); + + /* Add every node_t that support HSDir v3 for which we do have a valid + * hsdir_index already computed for them for this consensus. */ + { + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (!c || smartlist_len(c->routerstatus_list) == 0) { + log_warn(LD_REND, "No valid consensus so we can't get the responsible " + "hidden service directories."); + goto done; + } + SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) { + /* Even though this node_t object won't be modified and should be const, + * we can't add const object in a smartlist_t. */ + node_t *n = node_get_mutable_by_id(rs->identity_digest); + tor_assert(n); + if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { + if (!node_has_hsdir_index(n)) { + log_info(LD_GENERAL, "Node %s was found without hsdir index.", + node_describe(n)); + continue; + } + smartlist_add(sorted_nodes, n); + } + } SMARTLIST_FOREACH_END(rs); + } + if (smartlist_len(sorted_nodes) == 0) { + log_warn(LD_REND, "No nodes found to be HSDir or supporting v3."); + goto done; + } + + /* First thing we have to do is sort all node_t by hsdir_index. The + * is_next_period tells us if we want the current or the next one. Set the + * bsearch compare function also while we are at it. */ + if (for_fetching) { + smartlist_sort(sorted_nodes, compare_node_fetch_hsdir_index); + cmp_fct = compare_digest_to_fetch_hsdir_index; + } else if (use_second_hsdir_index) { + smartlist_sort(sorted_nodes, compare_node_store_second_hsdir_index); + cmp_fct = compare_digest_to_store_second_hsdir_index; + } else { + smartlist_sort(sorted_nodes, compare_node_store_first_hsdir_index); + cmp_fct = compare_digest_to_store_first_hsdir_index; + } + + /* For all replicas, we'll select a set of HSDirs using the consensus + * parameters and the sorted list. The replica starting at value 1 is + * defined by the specification. */ + for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) { + int idx, start, found, n_added = 0; + uint8_t hs_index[DIGEST256_LEN] = {0}; + /* Number of node to add to the responsible dirs list depends on if we are + * trying to fetch or store. A client always fetches. */ + int n_to_add = (for_fetching) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); + + /* Get the index that we should use to select the node. */ + hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); + /* The compare function pointer has been set correctly earlier. */ + start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct, + &found); + /* Getting the length of the list if no member is greater than the key we + * are looking for so start at the first element. */ + if (idx == smartlist_len(sorted_nodes)) { + start = idx = 0; + } + while (n_added < n_to_add) { + const node_t *node = smartlist_get(sorted_nodes, idx); + /* If the node has already been selected which is possible between + * replicas, the specification says to skip over. */ + if (!smartlist_contains(responsible_dirs, node->rs)) { + smartlist_add(responsible_dirs, node->rs); + ++n_added; + } + if (++idx == smartlist_len(sorted_nodes)) { + /* Wrap if we've reached the end of the list. */ + idx = 0; + } + if (idx == start) { + /* We've gone over the whole list, stop and avoid infinite loop. */ + break; + } + } + } + + done: + smartlist_free(sorted_nodes); +} + +/*********************** HSDir request tracking ***************************/ + +/** Return the period for which a hidden service directory cannot be queried + * for the same descriptor ID again, taking TestingTorNetwork into account. */ +time_t +hs_hsdir_requery_period(const or_options_t *options) +{ + tor_assert(options); + + if (options->TestingTorNetwork) { + return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; + } else { + return REND_HID_SERV_DIR_REQUERY_PERIOD; + } +} + +/** Tracks requests for fetching hidden service descriptors. It's used by + * hidden service clients, to avoid querying HSDirs that have already failed + * giving back a descriptor. The same data structure is used to track both v2 + * and v3 HS descriptor requests. + * + * The string map is a key/value store that contains the last request times to + * hidden service directories for certain queries. Specifically: + * + * key = base32(hsdir_identity) + base32(hs_identity) + * value = time_t of last request for that hs_identity to that HSDir + * + * where 'hsdir_identity' is the identity digest of the HSDir node, and + * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519 + * blinded public key of the HS in the v3 case. */ +static strmap_t *last_hid_serv_requests_ = NULL; + +/** Returns last_hid_serv_requests_, initializing it to a new strmap if + * necessary. */ +STATIC strmap_t * +get_last_hid_serv_requests(void) +{ + if (!last_hid_serv_requests_) + last_hid_serv_requests_ = strmap_new(); + return last_hid_serv_requests_; +} + +/** Look up the last request time to hidden service directory <b>hs_dir</b> + * for descriptor request key <b>req_key_str</b> which is the descriptor ID + * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero, + * assign the current time <b>now</b> and return that. Otherwise, return the + * most recent request time, or 0 if no such request has been sent before. */ +time_t +hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *req_key_str, + time_t now, int set) +{ + char hsdir_id_base32[BASE32_DIGEST_LEN + 1]; + char *hsdir_desc_comb_id = NULL; + time_t *last_request_ptr; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + /* Create the key */ + base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), + hs_dir->identity_digest, DIGEST_LEN); + tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str); + + if (set) { + time_t *oldptr; + last_request_ptr = tor_malloc_zero(sizeof(time_t)); + *last_request_ptr = now; + oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, + last_request_ptr); + tor_free(oldptr); + } else { + last_request_ptr = strmap_get(last_hid_serv_requests, + hsdir_desc_comb_id); + } + + tor_free(hsdir_desc_comb_id); + return (last_request_ptr) ? *last_request_ptr : 0; +} + +/** Clean the history of request times to hidden service directories, so that + * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD + * seconds any more. */ +void +hs_clean_last_hid_serv_requests(time_t now) +{ + strmap_iter_t *iter; + time_t cutoff = now - hs_hsdir_requery_period(get_options()); + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + time_t *ent; + strmap_iter_get(iter, &key, &val); + ent = (time_t *) val; + if (*ent < cutoff) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(ent); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Remove all requests related to the descriptor request key string + * <b>req_key_str</b> from the history of times of requests to hidden service + * directories. + * + * This is called from rend_client_note_connection_attempt_ended(), which + * must be idempotent, so any future changes to this function must leave it + * idempotent too. */ +void +hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str) +{ + strmap_iter_t *iter; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + strmap_iter_get(iter, &key, &val); + + /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of + * semantic, see #23305. */ + + /* This strmap contains variable-sized elements so this is a basic length + * check on the strings we are about to compare. The key is variable sized + * since it's composed as follows: + * key = base32(hsdir_identity) + base32(req_key_str) + * where 'req_key_str' is the descriptor ID of the HS in the v2 case, or + * the ed25519 blinded public key of the HS in the v3 case. */ + if (strlen(key) < REND_DESC_ID_V2_LEN_BASE32 + strlen(req_key_str)) { + iter = strmap_iter_next(last_hid_serv_requests, iter); + continue; + } + + /* Check if the tracked request matches our request key */ + if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str, + strlen(req_key_str))) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(val); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Purge the history of request times to hidden service directories, + * so that future lookups of an HS descriptor will not fail because we + * accessed all of the HSDir relays responsible for the descriptor + * recently. */ +void +hs_purge_last_hid_serv_requests(void) +{ + /* Don't create the table if it doesn't exist yet (and it may very + * well not exist if the user hasn't accessed any HSes)... */ + strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; + /* ... and let get_last_hid_serv_requests re-create it for us if + * necessary. */ + last_hid_serv_requests_ = NULL; + + if (old_last_hid_serv_requests != NULL) { + log_info(LD_REND, "Purging client last-HS-desc-request-time table"); + strmap_free(old_last_hid_serv_requests, tor_free_); + } +} + +/***********************************************************************/ + +/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the + * one that we should use to fetch a descriptor right now. Take into account + * previous failed attempts at fetching this descriptor from HSDirs using the + * string identifier <b>req_key_str</b>. + * + * Steals ownership of <b>responsible_dirs</b>. + * + * Return the routerstatus of the chosen HSDir if successful, otherwise return + * NULL if no HSDirs are worth trying right now. */ +routerstatus_t * +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) +{ + smartlist_t *usable_responsible_dirs = smartlist_new(); + const or_options_t *options = get_options(); + routerstatus_t *hs_dir; + time_t now = time(NULL); + int excluded_some; + + tor_assert(req_key_str); + + /* Clean outdated request history first. */ + hs_clean_last_hid_serv_requests(now); + + /* Only select those hidden service directories to which we did not send a + * request recently and for which we have a router descriptor here. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { + time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0); + const node_t *node = node_get_by_id(dir->identity_digest); + if (last + hs_hsdir_requery_period(options) >= now || + !node || !node_has_descriptor(node)) { + SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + continue; + } + if (!routerset_contains_node(options->ExcludeNodes, node)) { + smartlist_add(usable_responsible_dirs, dir); + } + } SMARTLIST_FOREACH_END(dir); + + excluded_some = + smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); + + hs_dir = smartlist_choose(usable_responsible_dirs); + if (!hs_dir && !options->StrictNodes) { + hs_dir = smartlist_choose(responsible_dirs); + } + + smartlist_free(responsible_dirs); + smartlist_free(usable_responsible_dirs); + if (!hs_dir) { + log_info(LD_REND, "Could not pick one of the responsible hidden " + "service directories, because we requested them all " + "recently without success."); + if (options->StrictNodes && excluded_some) { + log_warn(LD_REND, "Could not pick a hidden service directory for the " + "requested hidden service: they are all either down or " + "excluded, and StrictNodes is set."); + } + } else { + /* Remember that we are requesting a descriptor from this hidden service + * directory now. */ + hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); + } + + return hs_dir; +} + +/* From a list of link specifier, an onion key and if we are requesting a + * direct connection (ex: single onion service), return a newly allocated + * extend_info_t object. This function always returns an extend info with + * an IPv4 address, or NULL. + * + * It performs the following checks: + * if either IPv4 or legacy ID is missing, return NULL. + * if direct_conn, and we can't reach the IPv4 address, return NULL. + */ +extend_info_t * +hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn) +{ + int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0; + char legacy_id[DIGEST_LEN] = {0}; + uint16_t port_v4 = 0; + tor_addr_t addr_v4; + ed25519_public_key_t ed25519_pk; + extend_info_t *info = NULL; + + tor_assert(lspecs); + + SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) { + switch (link_specifier_get_ls_type(ls)) { + case LS_IPV4: + /* Skip if we already seen a v4. */ + if (have_v4) continue; + tor_addr_from_ipv4h(&addr_v4, + link_specifier_get_un_ipv4_addr(ls)); + port_v4 = link_specifier_get_un_ipv4_port(ls); + have_v4 = 1; + break; + case LS_LEGACY_ID: + /* Make sure we do have enough bytes for the legacy ID. */ + if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { + break; + } + memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls), + sizeof(legacy_id)); + have_legacy_id = 1; + break; + case LS_ED25519_ID: + memcpy(ed25519_pk.pubkey, + link_specifier_getconstarray_un_ed25519_id(ls), + ED25519_PUBKEY_LEN); + have_ed25519_id = 1; + break; + default: + /* Ignore unknown. */ + break; + } + } SMARTLIST_FOREACH_END(ls); + + /* Legacy ID is mandatory, and we require IPv4. */ + if (!have_v4 || !have_legacy_id) { + goto done; + } + + /* We know we have IPv4, because we just checked. */ + if (!direct_conn) { + /* All clients can extend to any IPv4 via a 3-hop path. */ + goto validate; + } else if (direct_conn && + fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, + 0, 0)) { + /* Direct connection and we can reach it in IPv4 so go for it. */ + goto validate; + + /* We will add support for falling back to a 3-hop path in a later + * release. */ + } else { + /* If we can't reach IPv4, return NULL. */ + goto done; + } + + /* We will add support for IPv6 in a later release. */ + + validate: + /* We'll validate now that the address we've picked isn't a private one. If + * it is, are we allowing to extend to private address? */ + if (!extend_info_addr_is_allowed(&addr_v4)) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Requested address is private and we are not allowed to extend to " + "it: %s:%u", fmt_addr(&addr_v4), port_v4); + goto done; + } + + /* We do have everything for which we think we can connect successfully. */ + info = extend_info_new(NULL, legacy_id, + (have_ed25519_id) ? &ed25519_pk : NULL, NULL, + onion_key, &addr_v4, port_v4); + done: + return info; +} + +/***********************************************************************/ + +/* Initialize the entire HS subsytem. This is called in tor_init() before any + * torrc options are loaded. Only for >= v3. */ +void +hs_init(void) +{ + hs_circuitmap_init(); + hs_service_init(); + hs_cache_init(); +} + +/* Release and cleanup all memory of the HS subsystem (all version). This is + * called by tor_free_all(). */ +void +hs_free_all(void) +{ + hs_circuitmap_free_all(); + hs_service_free_all(); + hs_cache_free_all(); + hs_client_free_all(); +} + +/* For the given origin circuit circ, decrement the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_dec_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams--; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams--; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + +/* For the given origin circuit circ, increment the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_inc_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams++; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams++; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h new file mode 100644 index 0000000000..7c5ea4792c --- /dev/null +++ b/src/or/hs_common.h @@ -0,0 +1,284 @@ +/* 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 4 +/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 + +/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is + * bigger than the 84 bytes needed for version 3 so we need to pad up to that + * length so it is indistinguishable between versions. */ +#define HS_LEGACY_RENDEZVOUS_CELL_SIZE \ + (REND_COOKIE_LEN + DH_KEY_LEN + DIGEST_LEN) + +/* Type of authentication key used by an introduction point. */ +typedef enum { + HS_AUTH_KEY_TYPE_LEGACY = 1, + HS_AUTH_KEY_TYPE_ED25519 = 2, +} hs_auth_key_type_t; + +/* Represents the mapping from a virtual port of a rendezvous service to a + * real port on some IP. */ +typedef struct rend_service_port_config_t { + /* The incoming HS virtual port we're mapping */ + uint16_t virtual_port; + /* Is this an AF_UNIX port? */ + unsigned int is_unix_addr:1; + /* The outgoing TCP port to use, if !is_unix_addr */ + uint16_t real_port; + /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ + tor_addr_t real_addr; + /* The socket path to connect to, if is_unix_addr */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; +} rend_service_port_config_t; + +/* Hidden service directory index used in a node_t which is set once we set + * the consensus. */ +typedef struct hsdir_index_t { + /* HSDir index to use when fetching a descriptor. */ + uint8_t fetch[DIGEST256_LEN]; + + /* HSDir index used by services to store their first and second + * descriptor. The first descriptor is chronologically older than the second + * one and uses older TP and SRV values. */ + uint8_t store_first[DIGEST256_LEN]; + uint8_t store_second[DIGEST256_LEN]; +} hsdir_index_t; + +void hs_init(void); +void hs_free_all(void); + +void hs_cleanup_circ(circuit_t *circ); + +int hs_check_service_private_dir(const char *username, const char *path, + unsigned int dir_group_readable, + unsigned int create); +int hs_get_service_max_rend_failures(void); + +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); + +routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); + +void hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out); + +uint64_t hs_get_previous_time_period_num(time_t now); +uint64_t hs_get_time_period_num(time_t now); +uint64_t hs_get_next_time_period_num(time_t now); +time_t hs_get_start_time_of_next_time_period(time_t now); + +link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); + +MOCK_DECL(int, hs_in_period_between_tp_and_srv, + (const networkstatus_t *consensus, time_t now)); + +uint8_t *hs_get_current_srv(uint64_t time_period_num, + const networkstatus_t *ns); +uint8_t *hs_get_previous_srv(uint64_t time_period_num, + const networkstatus_t *ns); + +void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv, uint64_t period_num, + uint8_t *hsdir_index_out); +void hs_build_hs_index(uint64_t replica, + const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out); + +int32_t hs_get_hsdir_n_replicas(void); +int32_t hs_get_hsdir_spread_fetch(void); +int32_t hs_get_hsdir_spread_store(void); + +void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, + int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs); +routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, + const char *req_key_str); + +time_t hs_hsdir_requery_period(const or_options_t *options); +time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *desc_id_base32, + time_t now, int set); +void hs_clean_last_hid_serv_requests(time_t now); +void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); +void hs_purge_last_hid_serv_requests(void); + +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + +void hs_inc_rdv_stream_counter(origin_circuit_t *circ); +void hs_dec_rdv_stream_counter(origin_circuit_t *circ); + +extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn); + +#ifdef HS_COMMON_PRIVATE + +STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); + +/** The period for which a hidden service directory cannot be queried for + * the same descriptor ID again. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) +/** Test networks generate a new consensus every 5 or 10 seconds. + * So allow them to requery HSDirs much faster. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) + +#ifdef TOR_UNIT_TESTS + +STATIC strmap_t *get_last_hid_serv_requests(void); +STATIC uint64_t get_time_period_length(void); + +STATIC uint8_t *get_first_cached_disaster_srv(void); +STATIC uint8_t *get_second_cached_disaster_srv(void); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(HS_COMMON_PRIVATE) */ + +#endif /* !defined(TOR_HS_COMMON_H) */ + diff --git a/src/or/hs_config.c b/src/or/hs_config.c new file mode 100644 index 0000000000..fa5c1ab176 --- /dev/null +++ b/src/or/hs_config.c @@ -0,0 +1,590 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.c + * \brief Implement hidden service configuration subsystem. + * + * \details + * + * This file has basically one main entry point: hs_config_service_all(). It + * takes the torrc options and configure hidden service from it. In validate + * mode, nothing is added to the global service list or keys are not generated + * nor loaded. + * + * A service is configured in two steps. It is first created using the tor + * options and then put in a staging list. It will stay there until + * hs_service_load_all_keys() is called. That function is responsible to + * load/generate the keys for the service in the staging list and if + * successful, transfert the service to the main global service list where + * at that point it is ready to be used. + * + * Configuration functions are per-version and there is a main generic one for + * every option that is common to all version (config_generic_service). + **/ + +#define HS_CONFIG_PRIVATE + +#include "hs_common.h" +#include "hs_config.h" +#include "hs_service.h" +#include "rendservice.h" + +/* Using the given list of services, stage them into our global state. Every + * service version are handled. This function can remove entries in the given + * service_list. + * + * Staging a service means that we take all services in service_list and we + * put them in the staging list (global) which acts as a temporary list that + * is used by the service loading key process. In other words, staging a + * service puts it in a list to be considered when loading the keys and then + * moved to the main global list. */ +static void +stage_services(smartlist_t *service_list) +{ + tor_assert(service_list); + + /* This is v2 specific. Trigger service pruning which will make sure the + * just configured services end up in the main global list. It should only + * be done in non validation mode because v2 subsystem handles service + * object differently. */ + rend_service_prune_list(); + + /* Cleanup v2 service from the list, we don't need those object anymore + * because we validated them all against the others and we want to stage + * only >= v3 service. And remember, v2 has a different object type which is + * shadow copied from an hs_service_t type. */ + SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) { + if (s->config.version == HS_VERSION_TWO) { + SMARTLIST_DEL_CURRENT(service_list, s); + hs_service_free(s); + } + } SMARTLIST_FOREACH_END(s); + + /* This is >= v3 specific. Using the newly configured service list, stage + * them into our global state. Every object ownership is lost after. */ + hs_service_stage_services(service_list); +} + +/* Validate the given service against all service in the given list. If the + * service is ephemeral, this function ignores it. Services with the same + * directory path aren't allowed and will return an error. If a duplicate is + * found, 1 is returned else 0 if none found. */ +static int +service_is_duplicate_in_list(const smartlist_t *service_list, + const hs_service_t *service) +{ + int ret = 0; + + tor_assert(service_list); + tor_assert(service); + + /* Ephemeral service don't have a directory configured so no need to check + * for a service in the list having the same path. */ + if (service->config.is_ephemeral) { + goto end; + } + + /* XXX: Validate if we have any service that has the given service dir path. + * This has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. */ + SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) { + if (!strcmp(s->config.directory_path, service->config.directory_path)) { + log_warn(LD_REND, "Another hidden service is already configured " + "for directory %s", + escaped(service->config.directory_path)); + ret = 1; + goto end; + } + } SMARTLIST_FOREACH_END(s); + + end: + return ret; +} + +/* Helper function: Given an configuration option name, its value, a minimum + * min and a maxium max, parse the value as a uint64_t. On success, ok is set + * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be + * ignored. This function logs both on error and success. */ +static uint64_t +helper_parse_uint64(const char *opt, const char *value, uint64_t min, + uint64_t max, int *ok) +{ + uint64_t ret = 0; + + tor_assert(opt); + tor_assert(value); + tor_assert(ok); + + *ok = 0; + ret = tor_parse_uint64(value, 10, min, max, ok, NULL); + if (!*ok) { + log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64 + ", not %s.", + opt, min, max, value); + goto err; + } + log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret); + err: + return ret; +} + +/* Return true iff the given options starting at line_ for a hidden service + * contains at least one invalid option. Each hidden service option don't + * apply to all versions so this function can find out. The line_ MUST start + * right after the HiddenServiceDir line of this service. + * + * This is mainly for usability so we can inform the user of any invalid + * option for the hidden service version instead of silently ignoring. */ +static int +config_has_invalid_options(const config_line_t *line_, + const hs_service_t *service) +{ + int ret = 0; + const char **optlist; + const config_line_t *line; + + tor_assert(service); + tor_assert(service->config.version <= HS_VERSION_MAX); + + /* List of options that a v3 service doesn't support thus must exclude from + * its configuration. */ + const char *opts_exclude_v3[] = { + "HiddenServiceAuthorizeClient", + NULL /* End marker. */ + }; + + /* Defining the size explicitly allows us to take advantage of the compiler + * which warns us if we ever bump the max version but forget to grow this + * array. The plus one is because we have a version 0 :). */ + struct { + const char **list; + } exclude_lists[HS_VERSION_MAX + 1] = { + { NULL }, /* v0. */ + { NULL }, /* v1. */ + { NULL }, /* v2 */ + { opts_exclude_v3 }, /* v3. */ + }; + + optlist = exclude_lists[service->config.version].list; + if (optlist == NULL) { + /* No exclude options to look at for this version. */ + goto end; + } + for (int i = 0; optlist[i]; i++) { + const char *opt = optlist[i]; + for (line = line_; line; line = line->next) { + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + goto end; + } + if (!strcasecmp(line->key, opt)) { + log_warn(LD_CONFIG, "Hidden service option %s is incompatible with " + "version %" PRIu32 " of service in %s", + opt, service->config.version, + service->config.directory_path); + ret = 1; + /* Continue the loop so we can find all possible options. */ + continue; + } + } + } + end: + return ret; +} + +/* Validate service configuration. This is used when loading the configuration + * and once we've setup a service object, it's config object is passed to this + * function for further validation. This does not validate service key + * material. Return 0 if valid else -1 if invalid. */ +static int +config_validate_service(const hs_service_config_t *config) +{ + tor_assert(config); + + /* Amount of ports validation. */ + if (!config->ports || smartlist_len(config->ports) == 0) { + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", + escaped(config->directory_path)); + goto invalid; + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/* Configuration funcion for a version 3 service. The line_ must be pointing + * to the directive directly after a HiddenServiceDir. That way, when hitting + * the next HiddenServiceDir line or reaching the end of the list of lines, we + * know that we have to stop looking for more options. The given service + * object must be already allocated and passed through + * config_generic_service() prior to calling this function. + * + * Return 0 on success else a negative value. */ +static int +config_service_v3(const config_line_t *line_, + hs_service_config_t *config) +{ + int have_num_ip = 0; + const char *dup_opt_seen = NULL; + const config_line_t *line; + + tor_assert(config); + + for (line = line_; line; line = line->next) { + int ok = 0; + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + break; + } + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + config->num_intro_points = + (unsigned int) helper_parse_uint64(line->key, line->value, + NUM_INTRO_POINTS_DEFAULT, + HS_CONFIG_V3_MAX_INTRO_POINTS, + &ok); + if (!ok || have_num_ip) { + if (have_num_ip) + dup_opt_seen = line->key; + goto err; + } + have_num_ip = 1; + continue; + } + } + + /* We do not load the key material for the service at this stage. This is + * done later once tor can confirm that it is in a running state. */ + + /* We are about to return a fully configured service so do one last pass of + * validation at it. */ + if (config_validate_service(config) < 0) { + goto err; + } + + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given options in line_ and options. This is + * called for any service regardless of its version which means that all + * directives in this function are generic to any service version. This + * function will also check the validity of the service directory path. + * + * The line_ must be pointing to the directive directly after a + * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or + * reaching the end of the list of lines, we know that we have to stop looking + * for more options. + * + * Return 0 on success else -1. */ +static int +config_generic_service(const config_line_t *line_, + const or_options_t *options, + hs_service_t *service) +{ + int dir_seen = 0; + const config_line_t *line; + hs_service_config_t *config; + /* If this is set, we've seen a duplicate of this option. Keep the string + * so we can log the directive. */ + const char *dup_opt_seen = NULL; + /* These variables will tell us if we ever have duplicate. */ + int have_version = 0, have_allow_unknown_ports = 0; + int have_dir_group_read = 0, have_max_streams = 0; + int have_max_streams_close = 0; + + tor_assert(line_); + tor_assert(options); + tor_assert(service); + + /* Makes thing easier. */ + config = &service->config; + + /* The first line starts with HiddenServiceDir so we consider what's next is + * the configuration of the service. */ + for (line = line_; line ; line = line->next) { + int ok = 0; + + /* This indicate that we have a new service to configure. */ + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* This function only configures one service at a time so if we've + * already seen one, stop right now. */ + if (dir_seen) { + break; + } + /* Ok, we've seen one and we are about to configure it. */ + dir_seen = 1; + config->directory_path = tor_strdup(line->value); + log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...", + escaped(config->directory_path)); + continue; + } + if (BUG(!dir_seen)) { + goto err; + } + /* Version of the service. */ + if (!strcasecmp(line->key, "HiddenServiceVersion")) { + service->config.version = + (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN, + HS_VERSION_MAX, &ok); + if (!ok || have_version) { + if (have_version) + dup_opt_seen = line->key; + goto err; + } + have_version = 1; + continue; + } + /* Virtual port. */ + if (!strcasecmp(line->key, "HiddenServicePort")) { + char *err_msg = NULL; + /* XXX: Can we rename this? */ + rend_service_port_config_t *portcfg = + rend_service_parse_port_config(line->value, " ", &err_msg); + if (!portcfg) { + if (err_msg) { + log_warn(LD_CONFIG, "%s", err_msg); + } + tor_free(err_msg); + goto err; + } + tor_assert(!err_msg); + smartlist_add(config->ports, portcfg); + log_info(LD_CONFIG, "HiddenServicePort=%s for %s", + line->value, escaped(config->directory_path)); + continue; + } + /* Do we allow unknown ports. */ + if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + config->allow_unknown_ports = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_allow_unknown_ports) { + if (have_allow_unknown_ports) + dup_opt_seen = line->key; + goto err; + } + have_allow_unknown_ports = 1; + continue; + } + /* Directory group readable. */ + if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) { + config->dir_group_readable = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_dir_group_read) { + if (have_dir_group_read) + dup_opt_seen = line->key; + goto err; + } + have_dir_group_read = 1; + continue; + } + /* Maximum streams per circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { + config->max_streams_per_rdv_circuit = + helper_parse_uint64(line->key, line->value, 0, + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok); + if (!ok || have_max_streams) { + if (have_max_streams) + dup_opt_seen = line->key; + goto err; + } + have_max_streams = 1; + continue; + } + /* Maximum amount of streams before we close the circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { + config->max_streams_close_circuit = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_max_streams_close) { + if (have_max_streams_close) + dup_opt_seen = line->key; + goto err; + } + have_max_streams_close = 1; + continue; + } + } + + /* Check if we are configured in non anonymous mode meaning every service + * becomes a single onion service. */ + if (rend_service_non_anonymous_mode_enabled(options)) { + config->is_single_onion = 1; + /* We will add support for IPv6-only v3 single onion services in a future + * Tor version. This won't catch "ReachableAddresses reject *4", but that + * option doesn't work anyway. */ + if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) { + log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not " + "supported. Set HiddenServiceSingleHopMode 0 and " + "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1."); + goto err; + } + } + + /* Success */ + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given line and options. This function will + * call the corresponding configuration function for a specific service + * version and validate the service against the other ones. On success, add + * the service to the given list and return 0. On error, nothing is added to + * the list and a negative value is returned. */ +static int +config_service(const config_line_t *line, const or_options_t *options, + smartlist_t *service_list) +{ + int ret; + hs_service_t *service = NULL; + + tor_assert(line); + tor_assert(options); + tor_assert(service_list); + + /* We have a new hidden service. */ + service = hs_service_new(options); + /* We'll configure that service as a generic one and then pass it to a + * specific function according to the configured version number. */ + if (config_generic_service(line, options, service) < 0) { + goto err; + } + tor_assert(service->config.version <= HS_VERSION_MAX); + /* Before we configure the service on a per-version basis, we'll make + * sure that this set of options for a service are valid that is for + * instance an option only for v2 is not used for v3. */ + if (config_has_invalid_options(line->next, service)) { + goto err; + } + /* Check permission on service directory that was just parsed. And this must + * be done regardless of the service version. Do not ask for the directory + * to be created, this is done when the keys are loaded because we could be + * in validation mode right now. */ + if (hs_check_service_private_dir(options->User, + service->config.directory_path, + service->config.dir_group_readable, + 0) < 0) { + goto err; + } + /* Different functions are in charge of specific options for a version. We + * start just after the service directory line so once we hit another + * directory line, the function knows that it has to stop parsing. */ + switch (service->config.version) { + case HS_VERSION_TWO: + ret = rend_config_service(line->next, options, &service->config); + break; + case HS_VERSION_THREE: + ret = config_service_v3(line->next, &service->config); + break; + default: + /* We do validate before if we support the parsed version. */ + tor_assert_nonfatal_unreached(); + goto err; + } + if (ret < 0) { + goto err; + } + /* We'll check if this service can be kept depending on the others + * configured previously. */ + if (service_is_duplicate_in_list(service_list, service)) { + goto err; + } + /* Passes, add it to the given list. */ + smartlist_add(service_list, service); + return 0; + + err: + hs_service_free(service); + return -1; +} + +/* From a set of <b>options</b>, setup every hidden service found. Return 0 on + * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and + * return as normal, but don't actually change the configured services. */ +int +hs_config_service_all(const or_options_t *options, int validate_only) +{ + int dir_option_seen = 0, ret = -1; + const config_line_t *line; + smartlist_t *new_service_list = NULL; + + tor_assert(options); + + /* Newly configured service are put in that list which is then used for + * validation and staging for >= v3. */ + new_service_list = smartlist_new(); + + for (line = options->RendConfigLines; line; line = line->next) { + /* Ignore all directives that aren't the start of a service. */ + if (strcasecmp(line->key, "HiddenServiceDir")) { + if (!dir_option_seen) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + goto err; + } + continue; + } + /* Flag that we've seen a directory directive and we'll use it to make + * sure that the torrc options ordering is actually valid. */ + dir_option_seen = 1; + + /* Try to configure this service now. On success, it will be added to the + * list and validated against the service in that same list. */ + if (config_service(line, options, new_service_list) < 0) { + goto err; + } + } + + /* In non validation mode, we'll stage those services we just successfully + * configured. Service ownership is transfered from the list to the global + * state. If any service is invalid, it will be removed from the list and + * freed. All versions are handled in that function. */ + if (!validate_only) { + stage_services(new_service_list); + } else { + /* We've just validated that we were able to build a clean working list of + * services. We don't need those objects anymore. */ + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, + hs_service_free(s)); + /* For the v2 subsystem, the configuration function adds the service + * object to the staging list and it is transferred in the main list + * through the prunning process. In validation mode, we thus have to purge + * the staging list so it's not kept in memory as valid service. */ + rend_service_free_staging_list(); + } + + /* Success. Note that the service list has no ownership of its content. */ + ret = 0; + goto end; + + err: + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s)); + + end: + smartlist_free(new_service_list); + /* Tor main should call the free all function on error. */ + return ret; +} + diff --git a/src/or/hs_config.h b/src/or/hs_config.h new file mode 100644 index 0000000000..6cd7aed460 --- /dev/null +++ b/src/or/hs_config.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.h + * \brief Header file containing configuration ABI/API for the HS subsytem. + **/ + +#ifndef TOR_HS_CONFIG_H +#define TOR_HS_CONFIG_H + +#include "or.h" + +/* Max value for HiddenServiceMaxStreams */ +#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 +/* Maximum number of intro points per version 3 services. */ +#define HS_CONFIG_V3_MAX_INTRO_POINTS 20 + +/* API */ + +int hs_config_service_all(const or_options_t *options, int validate_only); + +#endif /* !defined(TOR_HS_CONFIG_H) */ + diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c new file mode 100644 index 0000000000..582ac9cb7c --- /dev/null +++ b/src/or/hs_descriptor.c @@ -0,0 +1,2606 @@ +/* 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 "or.h" +#include "ed25519_cert.h" /* Trunnel interface. */ +#include "hs_descriptor.h" +#include "circuitbuild.h" +#include "parsecommon.h" +#include "rendcache.h" +#include "hs_cache.h" +#include "hs_config.h" +#include "torcert.h" /* tor_cert_encode_ed22519() */ + +/* Constant string value used for the descriptor format. */ +#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 = hs_desc_lspec_to_trunnel(spec); + if (ls) { + 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: + tor_free(hs_spec); + 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, approx_time()) < 0) { + log_warn(LD_REND, "Invalid signature for %s: %s", log_obj_type, + tor_cert_describe_signature_status(cert)); + 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. + * + * On any error case, including an empty output, return 0 and set + * *<b>decrypted_out</b> to NULL. + */ +MOCK_IMPL(STATIC size_t, +decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)) +{ + uint8_t *decrypted = NULL; + uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN]; + 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; + } + } + + if (result_len == 0) { + /* Treat this as an error, so that somebody will free the output. */ + goto err; + } + + /* Make sure to NUL terminate the string. */ + decrypted[encrypted_len] = '\0'; + *decrypted_out = (char *) decrypted; + 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: %s", + tor_cert_describe_signature_status(ip->auth_key_cert)); + 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: %s", + tor_cert_describe_signature_status(ip->enc_key_cert)); + 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 (!sig_start) { + log_warn(LD_GENERAL, "Malformed signature line. Rejecting."); + 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. */ +MOCK_IMPL(int, +hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)) +{ + int ret = -1; + uint32_t version; + + 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 the size in bytes of the given encrypted data object. Used by OOM + * subsystem. */ +static size_t +hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) +{ + tor_assert(data); + size_t intro_size = 0; + if (data->intro_auth_types) { + intro_size += + smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types); + } + if (data->intro_points) { + /* XXX could follow pointers here and get more accurate size */ + intro_size += + smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t); + } + + return sizeof(*data) + intro_size; +} + +/* Return the size in bytes of the given descriptor object. Used by OOM + * subsystem. */ + size_t +hs_desc_obj_size(const hs_descriptor_t *data) +{ + tor_assert(data); + return (hs_desc_plaintext_obj_size(&data->plaintext_data) + + hs_desc_encrypted_obj_size(&data->encrypted_data) + + sizeof(data->subcredential)); +} + +/* Return a newly allocated descriptor intro point. */ +hs_desc_intro_point_t * +hs_desc_intro_point_new(void) +{ + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + return ip; +} + +/* Free a descriptor intro point object. */ +void +hs_desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (ip == NULL) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + tor_cert_free(ip->enc_key_cert); + crypto_pk_free(ip->legacy.key); + tor_free(ip->legacy.cert.encoded); + tor_free(ip); +} + +/* Free the given descriptor link specifier. */ +void +hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls) +{ + if (ls == NULL) { + return; + } + tor_free(ls); +} + +/* Return a newly allocated descriptor link specifier using the given extend + * info and requested type. Return NULL on error. */ +hs_desc_link_specifier_t * +hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) +{ + hs_desc_link_specifier_t *ls = NULL; + + tor_assert(info); + + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = type; + switch (ls->type) { + case LS_IPV4: + if (info->addr.family != AF_INET) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_IPV6: + if (info->addr.family != AF_INET6) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_LEGACY_ID: + /* Bug out if the identity digest is not set */ + if (BUG(tor_mem_is_zero(info->identity_digest, + sizeof(info->identity_digest)))) { + goto err; + } + memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); + break; + case LS_ED25519_ID: + /* ed25519 keys are optional for intro points */ + if (ed25519_public_key_is_zero(&info->ed_identity)) { + goto err; + } + memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, + sizeof(ls->u.ed25519_id)); + break; + default: + /* Unknown type is code flow error. */ + tor_assert(0); + } + + return ls; + err: + tor_free(ls); + return NULL; +} + +/* From the given descriptor, remove and free every introduction point. */ +void +hs_descriptor_clear_intro_points(hs_descriptor_t *desc) +{ + smartlist_t *ips; + + tor_assert(desc); + + ips = desc->encrypted_data.intro_points; + if (ips) { + SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *, + ip, hs_desc_intro_point_free(ip)); + smartlist_clear(ips); + } +} + +/* From a descriptor link specifier object spec, returned a newly allocated + * link specifier object that is the encoded representation of spec. Return + * NULL on error. */ +link_specifier_t * +hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec) +{ + tor_assert(spec); + + link_specifier_t *ls = link_specifier_new(); + link_specifier_set_ls_type(ls, spec->type); + + switch (spec->type) { + case LS_IPV4: + link_specifier_set_un_ipv4_addr(ls, + tor_addr_to_ipv4h(&spec->u.ap.addr)); + link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + + sizeof(spec->u.ap.port)); + break; + case LS_IPV6: + { + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); + break; + } + case LS_LEGACY_ID: + { + size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); + uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); + memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); + link_specifier_set_ls_len(ls, legacy_id_len); + break; + } + case LS_ED25519_ID: + { + size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); + uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); + memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); + link_specifier_set_ls_len(ls, ed25519_id_len); + break; + } + default: + tor_assert_nonfatal_unreached(); + link_specifier_free(ls); + ls = NULL; + } + + return ls; +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h new file mode 100644 index 0000000000..52bec8e244 --- /dev/null +++ b/src/or/hs_descriptor.h @@ -0,0 +1,273 @@ +/* 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" + +/* Trunnel */ +struct link_specifier_t; + +/* The earliest descriptor format version we support. */ +#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 +/* The latest descriptor format version we support. */ +#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 + +/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours + * which is 180 minutes or 10800 seconds. */ +#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) +/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours + * which is 720 minutes or 43200 seconds. */ +#define HS_DESC_MAX_LIFETIME (12 * 60 * 60) +/* Lifetime of certificate in the descriptor. This defines the lifetime of the + * descriptor signing key and the cross certification cert of that key. It is + * set to 54 hours because a descriptor can be around for 48 hours and because + * consensuses are used after the hour, add an extra 6 hours to give some time + * for the service to stop using it. */ +#define HS_DESC_CERT_LIFETIME (54 * 60 * 60) +/* Length of the salt needed for the encrypted section of a descriptor. */ +#define HS_DESC_ENCRYPTED_SALT_LEN 16 +/* Length of the secret input needed for the KDF construction which derives + * 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); + +MOCK_DECL(int, + hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)); + +int hs_desc_decode_descriptor(const char *encoded, + const uint8_t *subcredential, + 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_obj_size(const hs_descriptor_t *data); +size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); + +hs_desc_intro_point_t *hs_desc_intro_point_new(void); +void hs_desc_intro_point_free(hs_desc_intro_point_t *ip); + +link_specifier_t *hs_desc_lspec_to_trunnel( + const hs_desc_link_specifier_t *spec); + +#ifdef HS_DESCRIPTOR_PRIVATE + +/* Encoding. */ +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); +STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); + +MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)); + +#endif /* defined(HS_DESCRIPTOR_PRIVATE) */ + +#endif /* !defined(TOR_HS_DESCRIPTOR_H) */ + diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c new file mode 100644 index 0000000000..b0e4e36a9b --- /dev/null +++ b/src/or/hs_ident.c @@ -0,0 +1,126 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.c + * \brief Contains circuit and connection identifier code for the whole HS + * subsytem. + **/ + +#include "hs_ident.h" + +/* Return a newly allocated circuit identifier. The given public key is copied + * identity_pk into the identifier. */ +hs_ident_circuit_t * +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type) +{ + tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || + circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ident->circuit_type = circuit_type; + return ident; +} + +/* Free the given circuit identifier. */ +void +hs_ident_circuit_free(hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_circuit_t)); + tor_free(ident); +} + +/* For a given circuit identifier src, return a newly allocated copy of it. + * This can't fail. */ +hs_ident_circuit_t * +hs_ident_circuit_dup(const hs_ident_circuit_t *src) +{ + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* For a given directory connection identifier src, return a newly allocated + * copy of it. This can't fail. */ +hs_ident_dir_conn_t * +hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) +{ + hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* Free the given directory connection identifier. */ +void +hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_dir_conn_t)); + tor_free(ident); +} + +/* Initialized the allocated ident object with identity_pk and blinded_pk. + * None of them can be NULL since a valid directory connection identifier must + * have all fields set. */ +void +hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident) +{ + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(ident); + + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk); +} + +/* Return a newly allocated edge connection identifier. The given public key + * identity_pk is copied into the identifier. */ +hs_ident_edge_conn_t * +hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) +{ + hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + return ident; +} + +/* Free the given edge connection identifier. */ +void +hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_edge_conn_t)); + tor_free(ident); +} + +/* Return true if the given ident is valid for an introduction circuit. */ +int +hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->identity_pk)) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) { + goto invalid; + } + + /* Valid. */ + return 1; + invalid: + return 0; +} + diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h new file mode 100644 index 0000000000..03150d25ea --- /dev/null +++ b/src/or/hs_ident.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.h + * \brief Header file containing circuit and connection identifier data for + * the whole HS subsytem. + * + * \details + * This interface is used to uniquely identify a hidden service on a circuit + * or connection using the service identity public key. Once the circuit or + * connection subsystem calls in the hidden service one, we use those + * identifiers to lookup the corresponding objects like service, intro point + * and descriptor. + * + * Furthermore, the circuit identifier holds cryptographic material needed for + * the e2e encryption on the rendezvous circuit which is set once the + * rendezvous circuit has opened and ready to be used. + **/ + +#ifndef TOR_HS_IDENT_H +#define TOR_HS_IDENT_H + +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_common.h" + +/* Length of the rendezvous cookie that is used to connect circuits at the + * rendezvous point. */ +#define HS_REND_COOKIE_LEN DIGEST_LEN + +/* Type of circuit an hs_ident_t object is associated with. */ +typedef enum { + HS_IDENT_CIRCUIT_INTRO = 1, + HS_IDENT_CIRCUIT_RENDEZVOUS = 2, +} hs_ident_circuit_type_t; + +/* Client and service side circuit identifier that is used for hidden service + * circuit establishment. Not all fields contain data, it depends on the + * circuit purpose. This is attached to an origin_circuit_t. All fields are + * used by both client and service. */ +typedef struct hs_ident_circuit_t { + /* (All circuit) The public key used to uniquely identify the service. It is + * the one found in the onion address. */ + ed25519_public_key_t identity_pk; + + /* (All circuit) The type of circuit this identifier is attached to. + * Accessors of the fields in this object assert non fatal on this circuit + * type. In other words, if a rendezvous field is being accessed, the + * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is + * set when an object is initialized in its constructor. */ + hs_ident_circuit_type_t circuit_type; + + /* (All circuit) Introduction point authentication key. It's also needed on + * the rendezvous circuit for the ntor handshake. It's used as the unique key + * of the introduction point so it should not be shared between multiple + * intro points. */ + ed25519_public_key_t intro_auth_pk; + + /* (Only client rendezvous circuit) Introduction point encryption public + * key. We keep it in the rendezvous identifier for the ntor handshake. */ + curve25519_public_key_t intro_enc_pk; + + /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + * service with an INTRODUCE1 cell and used by the service in an + * RENDEZVOUS1 cell. */ + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + + /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the + * RENDEZVOUS1 cell of the service. The construction is as follows: + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] + */ + uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + /* (Only client rendezvous circuit) Client ephemeral keypair needed for the + * e2e encryption with the service. */ + curve25519_keypair_t rendezvous_client_kp; + + /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for + * the e2e encryption with the client on the circuit. */ + uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; + + /* (Only rendezvous circuit) Number of streams associated with this + * rendezvous circuit. We track this because there is a check on a maximum + * value. */ + uint64_t num_rdv_streams; +} hs_ident_circuit_t; + +/* Client and service side directory connection identifier used for a + * directory connection to identify which service is being queried. This is + * attached to a dir_connection_t. */ +typedef struct hs_ident_dir_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* The blinded public key used to uniquely identify the descriptor that this + * directory connection identifier is for. Only used by the service-side code + * to fine control descriptor uploads. */ + ed25519_public_key_t blinded_pk; + + /* XXX: Client authorization. */ +} hs_ident_dir_conn_t; + +/* Client and service side edge connection identifier used for an edge + * connection to identify which service is being queried. This is attached to + * a edge_connection_t. */ +typedef struct hs_ident_edge_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* XXX: Client authorization. */ +} hs_ident_edge_conn_t; + +/* Circuit identifier API. */ +hs_ident_circuit_t *hs_ident_circuit_new( + const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type); +void hs_ident_circuit_free(hs_ident_circuit_t *ident); +hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src); + +/* Directory connection identifier API. */ +hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); +void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident); +void hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident); + +/* Edge connection identifier API. */ +hs_ident_edge_conn_t *hs_ident_edge_conn_new( + const ed25519_public_key_t *identity_pk); +void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident); + +/* Validators */ +int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident); + +#endif /* !defined(TOR_HS_IDENT_H) */ + diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c new file mode 100644 index 0000000000..4a5202f614 --- /dev/null +++ b/src/or/hs_intropoint.c @@ -0,0 +1,613 @@ +/* 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: + /* When sending the intro establish ack, on error the circuit can be marked + * as closed so avoid a double close. */ + if (!TO_CIRCUIT(circ)->marked_for_close) { + 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..749d1530e1 --- /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 /* defined(HS_INTROPOINT_PRIVATE) */ + +#endif /* !defined(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..77e544a130 --- /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 /* !defined(TOR_HS_NTOR_H) */ + diff --git a/src/or/hs_service.c b/src/or/hs_service.c new file mode 100644 index 0000000000..408625c3ac --- /dev/null +++ b/src/or/hs_service.c @@ -0,0 +1,3378 @@ +/* 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 "connection.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 "shared_random_state.h" +#include "statefile.h" + +#include "hs_circuit.h" +#include "hs_common.h" +#include "hs_config.h" +#include "hs_circuit.h" +#include "hs_descriptor.h" +#include "hs_ident.h" +#include "hs_intropoint.h" +#include "hs_service.h" + +/* Trunnel */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* Helper macro. Iterate over every service in the global map. The var is the + * name of the service pointer. */ +#define FOR_EACH_SERVICE_BEGIN(var) \ + STMT_BEGIN \ + hs_service_t **var##_iter, *var; \ + HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \ + var = *var##_iter; +#define FOR_EACH_SERVICE_END } STMT_END ; + +/* Helper macro. Iterate over both current and previous descriptor of a + * service. The var is the name of the descriptor pointer. This macro skips + * any descriptor object of the service that is NULL. */ +#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \ + STMT_BEGIN \ + hs_service_descriptor_t *var; \ + for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \ + ++var ## _loop_idx) { \ + (var ## _loop_idx == 0) ? (var = service->desc_current) : \ + (var = service->desc_next); \ + if (var == NULL) continue; +#define FOR_EACH_DESCRIPTOR_END } STMT_END ; + +/* Onion service directory file names. */ +static const char fname_keyfile_prefix[] = "hs_ed25519"; +static const char fname_hostname[] = "hostname"; +static const char address_tld[] = "onion"; + +/* Staging list of service object. When configuring service, we add them to + * this list considered a staging area and they will get added to our global + * map once the keys have been loaded. These two steps are seperated because + * loading keys requires that we are an actual running tor process. */ +static smartlist_t *hs_service_staging_list; + +/** True if the list of available router descriptors might have changed which + * might result in an altered hash ring. Check if the hash ring changed and + * reupload if needed */ +static int consider_republishing_hs_descriptors = 0; + +static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); +static void move_descriptors(hs_service_t *src, hs_service_t *dst); + +/* 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)); +} + +/* Helper function to return a human readable description of the given intro + * point object. + * + * This function is not thread-safe. Each call to this invalidates the + * previous values returned by it. */ +static const char * +describe_intro_point(const hs_service_intro_point_t *ip) +{ + /* Hex identity digest of the IP prefixed by the $ sign and ends with NUL + * byte hence the plus two. */ + static char buf[HEX_DIGEST_LEN + 2]; + const char *legacy_id = NULL; + + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + if (lspec->type == LS_LEGACY_ID) { + legacy_id = (const char *) lspec->u.legacy_id; + break; + } + } SMARTLIST_FOREACH_END(lspec); + + /* For now, we only print the identity digest but we could improve this with + * much more information such as the ed25519 identity has well. */ + buf[0] = '$'; + if (legacy_id) { + base16_encode(buf + 1, HEX_DIGEST_LEN + 1, legacy_id, DIGEST_LEN); + } + + return buf; +} + +/* 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. + * + * If ei is NULL, returns a hs_service_intro_point_t with an empty link + * specifier list and no onion key. (This is used for testing.) + * + * ei must be an extend_info_t containing an IPv4 address. (We will add supoort + * for IPv6 in a later release.) When calling extend_info_from_node(), pass + * 0 in for_direct_connection to make sure ei always has an IPv4 address. */ +STATIC hs_service_intro_point_t * +service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip; + + ip = tor_malloc_zero(sizeof(*ip)); + /* We'll create the key material. No need for extra strong, those are short + * term keys. */ + ed25519_keypair_generate(&ip->auth_key_kp, 0); + + { /* Set introduce2 max cells limit */ + int32_t min_introduce2_cells = get_intro_point_min_introduce2(); + int32_t max_introduce2_cells = get_intro_point_max_introduce2(); + if (BUG(max_introduce2_cells < min_introduce2_cells)) { + goto err; + } + ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells, + max_introduce2_cells); + } + { /* Set intro point lifetime */ + int32_t intro_point_min_lifetime = get_intro_point_min_lifetime(); + int32_t intro_point_max_lifetime = get_intro_point_max_lifetime(); + if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { + goto err; + } + ip->time_to_expire = time(NULL) + + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); + } + + ip->replay_cache = replaycache_new(0, 0); + + /* Initialize the base object. We don't need the certificate object. */ + ip->base.link_specifiers = smartlist_new(); + + /* Generate the encryption key for this intro point. */ + curve25519_keypair_generate(&ip->enc_key_kp, 0); + /* Figure out if this chosen node supports v3 or is legacy only. */ + if (is_legacy) { + ip->base.is_only_legacy = 1; + /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */ + ip->legacy_key = crypto_pk_new(); + if (crypto_pk_generate_key(ip->legacy_key) < 0) { + goto err; + } + } + + if (ei == NULL) { + goto done; + } + + /* We'll try to add all link specifiers. Legacy is mandatory. + * IPv4 or IPv6 is required, and we always send IPv4. */ + ls = hs_desc_link_specifier_new(ei, LS_IPV4); + /* It is impossible to have an extend info object without a v4. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID); + /* It is impossible to have an extend info object without an identity + * digest. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + /* ed25519 identity key is optional for intro points */ + ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + + /* IPv6 is not supported in this release. */ + + /* Finally, copy onion key from the extend_info_t object. */ + memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); + + done: + return ip; + err: + service_intro_point_free(ip); + return NULL; +} + +/* Add the given intro point object to the given intro point map. The intro + * point MUST have its RSA encryption key set if this is a legacy type or the + * authentication key set otherwise. */ +STATIC void +service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) +{ + hs_service_intro_point_t *old_ip_entry; + + tor_assert(map); + tor_assert(ip); + + old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + /* Make sure we didn't just try to double-add an intro point */ + tor_assert_nonfatal(!old_ip_entry); +} + +/* For a given service, remove the intro point from that service's descriptors + * (check both current and next descriptor) */ +STATIC void +service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + tor_assert(service); + tor_assert(ip); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We'll try to remove the descriptor on both descriptors which is not + * very expensive to do instead of doing loopup + remove. */ + digest256map_remove(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey); + } FOR_EACH_DESCRIPTOR_END; +} + +/* For a given service and authentication key, return the intro point or NULL + * if not found. This will check both descriptors in the service. */ +STATIC hs_service_intro_point_t * +service_intro_point_find(const hs_service_t *service, + const ed25519_public_key_t *auth_key) +{ + hs_service_intro_point_t *ip = NULL; + + tor_assert(service); + tor_assert(auth_key); + + /* Trying all descriptors to find the right intro point. + * + * Even if we use the same node as intro point in both descriptors, the node + * will have a different intro auth key for each descriptor since we generate + * a new one everytime we pick an intro point. + * + * After #22893 gets implemented, intro points will be moved to be + * per-service instead of per-descriptor so this function will need to + * change. + */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if ((ip = digest256map_get(desc->intro_points.map, + auth_key->pubkey)) != NULL) { + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return ip; +} + +/* For a given service and intro point, return the descriptor for which the + * intro point is assigned to. NULL is returned if not found. */ +STATIC hs_service_descriptor_t * +service_desc_find_by_intro(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_service_descriptor_t *descp = NULL; + + tor_assert(service); + tor_assert(ip); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (digest256map_get(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey)) { + descp = desc; + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return descp; +} + +/* From a circuit identifier, get all the possible objects associated with the + * ident. If not NULL, service, ip or desc are set if the object can be found. + * They are untouched if they can't be found. + * + * This is an helper function because we do those lookups often so it's more + * convenient to simply call this functions to get all the things at once. */ +STATIC void +get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc) +{ + hs_service_t *s; + + tor_assert(ident); + + /* Get service object from the circuit identifier. */ + s = find_service(hs_service_map, &ident->identity_pk); + if (s && service) { + *service = s; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + if (s && ip) { + *ip = service_intro_point_find(s, &ident->intro_auth_pk); + } + + /* Get the descriptor for this introduction point and service. */ + if (s && ip && *ip && desc) { + *desc = service_desc_find_by_intro(s, *ip); + } +} + +/* From a given intro point, return the first link specifier of type + * encountered in the link specifier list. Return NULL if it can't be found. + * + * The caller does NOT have ownership of the object, the intro point does. */ +static hs_desc_link_specifier_t * +get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) +{ + hs_desc_link_specifier_t *lnk_spec = NULL; + + tor_assert(ip); + + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == type) { + lnk_spec = ls; + goto end; + } + } SMARTLIST_FOREACH_END(ls); + + end: + return lnk_spec; +} + +/* Given a service intro point, return the node_t associated to it. This can + * return NULL if the given intro point has no legacy ID or if the node can't + * be found in the consensus. */ +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip) +{ + const hs_desc_link_specifier_t *ls; + + tor_assert(ip); + + ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + if (BUG(!ls)) { + return NULL; + } + /* XXX In the future, we want to only use the ed25519 ID (#22173). */ + return node_get_by_id((const char *) ls->u.legacy_id); +} + +/* Given a service intro point, return the extend_info_t for it. This can + * return NULL if the node can't be found for the intro point or the extend + * info can't be created for the found node. If direct_conn is set, the extend + * info is validated on if we can connect directly. */ +static extend_info_t * +get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, + unsigned int direct_conn) +{ + extend_info_t *info = NULL; + const node_t *node; + + tor_assert(ip); + + node = get_node_from_intro_point(ip); + if (node == NULL) { + /* This can happen if the relay serving as intro point has been removed + * from the consensus. In that case, the intro point will be removed from + * the descriptor during the scheduled events. */ + goto end; + } + + /* In the case of a direct connection (single onion service), it is possible + * our firewall policy won't allow it so this can return a NULL value. */ + info = extend_info_from_node(node, direct_conn); + + end: + return info; +} + +/* Return the number of introduction points that are established for the + * given descriptor. */ +static unsigned int +count_desc_circuit_established(const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + count += ip->circuit_established; + } DIGEST256MAP_FOREACH_END; + + return count; +} + +/* For a given service and descriptor of that service, close all active + * directory connections. */ +static void +close_directory_connections(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + smartlist_t *dir_conns; + + tor_assert(service); + tor_assert(desc); + + /* Close pending HS desc upload connections for the blinded key of 'desc'. */ + dir_conns = connection_list_by_type_purpose(CONN_TYPE_DIR, + DIR_PURPOSE_UPLOAD_HSDESC); + SMARTLIST_FOREACH_BEGIN(dir_conns, connection_t *, conn) { + dir_connection_t *dir_conn = TO_DIR_CONN(conn); + if (ed25519_pubkey_eq(&dir_conn->hs_ident->identity_pk, + &service->keys.identity_pk) && + ed25519_pubkey_eq(&dir_conn->hs_ident->blinded_pk, + &desc->blinded_kp.pubkey)) { + connection_mark_for_close(conn); + count++; + continue; + } + } SMARTLIST_FOREACH_END(conn); + + log_info(LD_REND, "Closed %u active service directory connections for " + "descriptor %s of service %s", + count, safe_str_client(ed25519_fmt(&desc->blinded_kp.pubkey)), + safe_str_client(service->onion_address)); + /* We don't have ownership of the objects in this list. */ + smartlist_free(dir_conns); +} + +/* Close all rendezvous circuits for the given service. */ +static void +close_service_rp_circuits(hs_service_t *service) +{ + origin_circuit_t *ocirc = NULL; + + tor_assert(service); + + /* The reason we go over all circuit instead of using the circuitmap API is + * because most hidden service circuits are rendezvous circuits so there is + * no real improvement at getting all rendezvous circuits from the + * circuitmap and then going over them all to find the right ones. + * Furthermore, another option would have been to keep a list of RP cookies + * for a service but it creates an engineering complexity since we don't + * have a "RP circuit closed" event to clean it up properly so we avoid a + * memory DoS possibility. */ + + while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) { + /* Only close circuits that are v3 and for this service. */ + if (ocirc->hs_ident != NULL && + ed25519_pubkey_eq(ô->hs_ident->identity_pk, + &service->keys.identity_pk)) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, it is removed from the + * hs circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } +} + +/* Close the circuit(s) for the given map of introduction points. */ +static void +close_intro_circuits(hs_service_intropoints_t *intro_points) +{ + tor_assert(intro_points); + + DIGEST256MAP_FOREACH(intro_points->map, key, + const hs_service_intro_point_t *, ip) { + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, the circuit is removed + * from the HS circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Close all introduction circuits for the given service. */ +static void +close_service_intro_circuits(hs_service_t *service) +{ + tor_assert(service); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + close_intro_circuits(&desc->intro_points); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Close any circuits related to the given service. */ +static void +close_service_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* Only support for version >= 3. */ + if (BUG(service->config.version < HS_VERSION_THREE)) { + return; + } + /* Close intro points. */ + close_service_intro_circuits(service); + /* Close rendezvous points. */ + close_service_rp_circuits(service); +} + +/* Move every ephemeral services from the src service map to the dst service + * map. It is possible that a service can't be register to the dst map which + * won't stop the process of moving them all but will trigger a log warn. */ +static void +move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst) +{ + hs_service_t **iter, **next; + + tor_assert(src); + tor_assert(dst); + + /* Iterate over the map to find ephemeral service and move them to the other + * map. We loop using this method to have a safe removal process. */ + for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) { + hs_service_t *s = *iter; + if (!s->config.is_ephemeral) { + /* Yeah, we are in a very manual loop :). */ + next = HT_NEXT(hs_service_ht, src, iter); + continue; + } + /* Remove service from map and then register to it to the other map. + * Reminder that "*iter" and "s" are the same thing. */ + next = HT_NEXT_RMV(hs_service_ht, src, iter); + if (register_service(dst, s) < 0) { + log_warn(LD_BUG, "Ephemeral service key is already being used. " + "Skipping."); + } + } +} + +/* Return a const string of the directory path escaped. If this is an + * ephemeral service, it returns "[EPHEMERAL]". This can only be called from + * the main thread because escaped() uses a static variable. */ +static const char * +service_escaped_dir(const hs_service_t *s) +{ + return (s->config.is_ephemeral) ? "[EPHEMERAL]" : + escaped(s->config.directory_path); +} + +/** Move the hidden service state from <b>src</b> to <b>dst</b>. We do this + * when we receive a SIGHUP: <b>dst</b> is the post-HUP service */ +static void +move_hs_state(hs_service_t *src_service, hs_service_t *dst_service) +{ + tor_assert(src_service); + tor_assert(dst_service); + + hs_service_state_t *src = &src_service->state; + hs_service_state_t *dst = &dst_service->state; + + /* Let's do a shallow copy */ + dst->intro_circ_retry_started_time = src->intro_circ_retry_started_time; + dst->num_intro_circ_launched = src->num_intro_circ_launched; + /* Freeing a NULL replaycache triggers an info LD_BUG. */ + if (dst->replay_cache_rend_cookie != NULL) { + replaycache_free(dst->replay_cache_rend_cookie); + } + dst->replay_cache_rend_cookie = src->replay_cache_rend_cookie; + dst->next_rotation_time = src->next_rotation_time; + + src->replay_cache_rend_cookie = NULL; /* steal pointer reference */ +} + +/* Register services that are in the staging list. Once this function returns, + * the global service map will be set with the right content and all non + * surviving services will be cleaned up. */ +static void +register_all_services(void) +{ + struct hs_service_ht *new_service_map; + + tor_assert(hs_service_staging_list); + + /* Allocate a new map that will replace the current one. */ + new_service_map = tor_malloc_zero(sizeof(*new_service_map)); + HT_INIT(hs_service_ht, new_service_map); + + /* First step is to transfer all ephemeral services from the current global + * map to the new one we are constructing. We do not prune ephemeral + * services as the only way to kill them is by deleting it from the control + * port or stopping the tor daemon. */ + move_ephemeral_services(hs_service_map, new_service_map); + + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) { + hs_service_t *s; + + /* Check if that service is already in our global map and if so, we'll + * transfer the intro points to it. */ + s = find_service(hs_service_map, &snew->keys.identity_pk); + if (s) { + /* Pass ownership of the descriptors from s (the current service) to + * snew (the newly configured one). */ + move_descriptors(s, snew); + move_hs_state(s, snew); + /* Remove the service from the global map because after this, we need to + * go over the remaining service in that map that aren't surviving the + * reload to close their circuits. */ + remove_service(hs_service_map, s); + hs_service_free(s); + } + /* Great, this service is now ready to be added to our new map. */ + if (BUG(register_service(new_service_map, snew) < 0)) { + /* This should never happen because prior to registration, we validate + * every service against the entire set. Not being able to register a + * service means we failed to validate correctly. In that case, don't + * break tor and ignore the service but tell user. */ + log_warn(LD_BUG, "Unable to register service with directory %s", + service_escaped_dir(snew)); + SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew); + hs_service_free(snew); + } + } SMARTLIST_FOREACH_END(snew); + + /* Close any circuits associated with the non surviving services. Every + * service in the current global map are roaming. */ + FOR_EACH_SERVICE_BEGIN(service) { + close_service_circuits(service); + } FOR_EACH_SERVICE_END; + + /* Time to make the switch. We'll clear the staging list because its content + * has now changed ownership to the map. */ + smartlist_clear(hs_service_staging_list); + service_free_all(); + hs_service_map = new_service_map; +} + +/* Write the onion address of a given service to the given filename fname_ in + * the service directory. Return 0 on success else -1 on error. */ +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_) +{ + int ret = -1; + char *fname = NULL; + char *addr_buf = NULL; + + tor_assert(service); + tor_assert(fname_); + + /* Construct the full address with the onion tld and write the hostname file + * to disk. */ + tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld); + /* Notice here that we use the given "fname_". */ + fname = hs_path_from_filename(service->config.directory_path, fname_); + if (write_str_to_file(fname, addr_buf, 0) < 0) { + log_warn(LD_REND, "Could not write onion address to hostname file %s", + escaped(fname)); + goto end; + } + +#ifndef _WIN32 + if (service->config.dir_group_readable) { + /* Mode to 0640. */ + if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { + log_warn(LD_FS, "Unable to make onion service hostname file %s " + "group-readable.", escaped(fname)); + } + } +#endif /* !defined(_WIN32) */ + + /* Success. */ + ret = 0; + end: + tor_free(fname); + tor_free(addr_buf); + return ret; +} + +/* Load and/or generate private keys for the given service. On success, the + * hostname file will be written to disk along with the master private key iff + * the service is not configured for offline keys. Return 0 on success else -1 + * on failure. */ +static int +load_service_keys(hs_service_t *service) +{ + int ret = -1; + char *fname = NULL; + ed25519_keypair_t *kp; + const hs_service_config_t *config; + + tor_assert(service); + + config = &service->config; + + /* Create and fix permission on service directory. We are about to write + * files to that directory so make sure it exists and has the right + * permissions. We do this here because at this stage we know that Tor is + * actually running and the service we have has been validated. */ + if (hs_check_service_private_dir(get_options()->User, + config->directory_path, + config->dir_group_readable, 1) < 0) { + goto end; + } + + /* Try to load the keys from file or generate it if not found. */ + fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix); + /* Don't ask for key creation, we want to know if we were able to load it or + * we had to generate it. Better logging! */ + kp = ed_key_init_from_file(fname, INIT_ED_KEY_SPLIT, LOG_INFO, NULL, 0, 0, + 0, NULL); + if (!kp) { + log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname); + /* We'll now try to generate the keys and for it we want the strongest + * randomness for it. The keypair will be written in different files. */ + uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG | + INIT_ED_KEY_SPLIT; + kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0, + NULL); + if (!kp) { + log_warn(LD_REND, "Unable to generate keys and save in %s.", fname); + goto end; + } + } + + /* Copy loaded or generated keys to service object. */ + ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey); + memcpy(&service->keys.identity_sk, &kp->seckey, + sizeof(service->keys.identity_sk)); + /* This does a proper memory wipe. */ + ed25519_keypair_free(kp); + + /* Build onion address from the newly loaded keys. */ + tor_assert(service->config.version <= UINT8_MAX); + hs_build_address(&service->keys.identity_pk, + (uint8_t) service->config.version, + service->onion_address); + + /* Write onion address to hostname file. */ + if (write_address_to_file(service, fname_hostname) < 0) { + goto end; + } + + /* Succes. */ + ret = 0; + end: + tor_free(fname); + return ret; +} + +/* Free a given service descriptor object and all key material is wiped. */ +STATIC void +service_descriptor_free(hs_service_descriptor_t *desc) +{ + if (!desc) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); + memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); + /* Cleanup all intro points. */ + digest256map_free(desc->intro_points.map, service_intro_point_free_); + digestmap_free(desc->intro_points.failed_id, tor_free_); + if (desc->previous_hsdirs) { + SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s)); + smartlist_free(desc->previous_hsdirs); + } + tor_free(desc); +} + +/* Return a newly allocated service descriptor object. */ +STATIC hs_service_descriptor_t * +service_descriptor_new(void) +{ + hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc)); + sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); + /* Initialize the intro points map. */ + sdesc->intro_points.map = digest256map_new(); + sdesc->intro_points.failed_id = digestmap_new(); + sdesc->previous_hsdirs = smartlist_new(); + return sdesc; +} + +/* Move descriptor(s) from the src service to the dst service. We do this + * during SIGHUP when we re-create our hidden services. */ +static void +move_descriptors(hs_service_t *src, hs_service_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + if (src->desc_current) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_current)) { + service_descriptor_free(dst->desc_current); + } + dst->desc_current = src->desc_current; + src->desc_current = NULL; + } + + if (src->desc_next) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_next)) { + service_descriptor_free(dst->desc_next); + } + dst->desc_next = src->desc_next; + src->desc_next = NULL; + } +} + +/* From the given service, remove all expired failing intro points for each + * descriptor. */ +static void +remove_expired_failing_intro(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the failing intro points list. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) { + time_t failure_time = *t; + if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) { + MAP_DEL_CURRENT(key); + tor_free(t); + } + } DIGESTMAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* For the given descriptor desc, put all node_t object found from its failing + * intro point list and put them in the given node_list. */ +static void +setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, + smartlist_t *node_list) +{ + tor_assert(desc); + tor_assert(node_list); + + DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) { + (void) t; /* Make gcc happy. */ + const node_t *node = node_get_by_id(key); + if (node) { + smartlist_add(node_list, (void *) node); + } + } DIGESTMAP_FOREACH_END; +} + +/* For the given failing intro point ip, we add its time of failure to the + * failed map and index it by identity digest (legacy ID) in the descriptor + * desc failed id map. */ +static void +remember_failing_intro_point(const hs_service_intro_point_t *ip, + hs_service_descriptor_t *desc, time_t now) +{ + time_t *time_of_failure, *prev_ptr; + const hs_desc_link_specifier_t *legacy_ls; + + tor_assert(ip); + tor_assert(desc); + + time_of_failure = tor_malloc_zero(sizeof(time_t)); + *time_of_failure = now; + legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + tor_assert(legacy_ls); + prev_ptr = digestmap_set(desc->intro_points.failed_id, + (const char *) legacy_ls->u.legacy_id, + time_of_failure); + tor_free(prev_ptr); +} + +/* Copy the descriptor link specifier object from src to dst. */ +static void +link_specifier_copy(hs_desc_link_specifier_t *dst, + const hs_desc_link_specifier_t *src) +{ + tor_assert(dst); + tor_assert(src); + memcpy(dst, src, sizeof(hs_desc_link_specifier_t)); +} + +/* Using a given descriptor signing keypair signing_kp, a service intro point + * object ip and the time now, setup the content of an already allocated + * descriptor intro desc_ip. + * + * Return 0 on success else a negative value. */ +static int +setup_desc_intro_point(const ed25519_keypair_t *signing_kp, + const hs_service_intro_point_t *ip, + time_t now, hs_desc_intro_point_t *desc_ip) +{ + int ret = -1; + time_t nearest_hour = now - (now % 3600); + + tor_assert(signing_kp); + tor_assert(ip); + tor_assert(desc_ip); + + /* Copy the onion key. */ + memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key)); + + /* Key and certificate material. */ + desc_ip->auth_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_AUTH_HS_IP_KEY, + &ip->auth_key_kp.pubkey, + nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->auth_key_cert == NULL) { + log_warn(LD_REND, "Unable to create intro point auth-key certificate"); + goto done; + } + + /* Copy link specifier(s). */ + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, ls) { + hs_desc_link_specifier_t *copy = tor_malloc_zero(sizeof(*copy)); + link_specifier_copy(copy, ls); + smartlist_add(desc_ip->link_specifiers, copy); + } 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 both descriptors for the given service that has just booted up. + * Because it's a special case, it deserves its special function ;). */ +static void +build_descriptors_for_new_service(hs_service_t *service, time_t now) +{ + uint64_t current_desc_tp, next_desc_tp; + + tor_assert(service); + /* These are the conditions for a new service. */ + tor_assert(!service->desc_current); + tor_assert(!service->desc_next); + + /* + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | A B | + * +------------------------------------------------------------------+ + * + * Case A: The service boots up before a new time period, the current time + * period is thus TP#1 and the next is TP#2 which for both we have access to + * their SRVs. + * + * Case B: The service boots up inside TP#2, we can't use the TP#3 for the + * next descriptor because we don't have the SRV#3 so the current should be + * TP#1 and next TP#2. + */ + + if (hs_in_period_between_tp_and_srv(NULL, now)) { + /* Case B from the above, inside of the new time period. */ + current_desc_tp = hs_get_previous_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_time_period_num(0); /* TP#2 */ + } else { + /* Case A from the above, outside of the new time period. */ + current_desc_tp = hs_get_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_next_time_period_num(0); /* TP#2 */ + } + + /* Build descriptors. */ + build_service_descriptor(service, now, current_desc_tp, + &service->desc_current); + build_service_descriptor(service, now, next_desc_tp, + &service->desc_next); + log_info(LD_REND, "Hidden service %s has just started. Both descriptors " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); +} + +/* Build descriptors for each service if needed. There are conditions to build + * a descriptor which are details in the function. */ +STATIC void +build_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + + /* A service booting up will have both descriptors to NULL. No other cases + * makes both descriptor non existent. */ + if (service->desc_current == NULL && service->desc_next == NULL) { + build_descriptors_for_new_service(service, now); + continue; + } + + /* Reaching this point means we are pass bootup so at runtime. We should + * *never* have an empty current descriptor. If the next descriptor is + * empty, we'll try to build it for the next time period. This only + * happens when we rotate meaning that we are guaranteed to have a new SRV + * at that point for the next time period. */ + tor_assert(service->desc_current); + + if (service->desc_next == NULL) { + build_service_descriptor(service, now, hs_get_next_time_period_num(0), + &service->desc_next); + log_info(LD_REND, "Hidden service %s next descriptor successfully " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); + } + } FOR_EACH_DESCRIPTOR_END; +} + +/* Randomly pick a node to become an introduction point but not present in the + * given exclude_nodes list. The chosen node is put in the exclude list + * regardless of success or not because in case of failure, the node is simply + * unsusable from that point on. + * + * If direct_conn is set, try to pick a node that our local firewall/policy + * allows us to connect to directly. If we can't find any, return NULL. + * This function supports selecting dual-stack nodes for direct single onion + * service IPv6 connections. But it does not send IPv6 addresses in link + * specifiers. (Current clients don't use IPv6 addresses to extend, and + * direct client connections to intro points are not supported.) + * + * Return a newly allocated service intro point ready to be used for encoding. + * Return NULL on error. */ +static hs_service_intro_point_t * +pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) +{ + const node_t *node; + extend_info_t *info = NULL; + hs_service_intro_point_t *ip = NULL; + /* Normal 3-hop introduction point flags. */ + router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC; + /* Single onion flags. */ + router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN; + + node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes, + direct_conn ? direct_flags : flags); + /* Unable to find a node. When looking for a node for a direct connection, + * we could try a 3-hop path instead. We'll add support for this in a later + * release. */ + if (!node) { + goto err; + } + + /* We have a suitable node, add it to the exclude list. We do this *before* + * we can validate the extend information because even in case of failure, + * we don't want to use that node anymore. */ + smartlist_add(exclude_nodes, (void *) node); + + /* We do this to ease our life but also this call makes appropriate checks + * of the node object such as validating ntor support for instance. + * + * We must provide an extend_info for clients to connect over a 3-hop path, + * so we don't pass direct_conn here. */ + info = extend_info_from_node(node, 0); + if (BUG(info == NULL)) { + goto err; + } + + /* Let's do a basic sanity check here so that we don't end up advertising the + * ed25519 identity key of relays that don't actually support the link + * protocol */ + if (!node_supports_ed25519_link_authentication(node)) { + tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); + } else { + /* Make sure we *do* have an ed key if we support the link authentication. + * Sending an empty key would result in a failure to extend. */ + tor_assert_nonfatal(!ed25519_public_key_is_zero(&info->ed_identity)); + } + + /* Create our objects and populate them with the node information. */ + ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node)); + if (ip == NULL) { + goto err; + } + + log_info(LD_REND, "Picked intro point: %s", extend_info_describe(info)); + extend_info_free(info); + return ip; + err: + service_intro_point_free(ip); + extend_info_free(info); + return NULL; +} + +/* For a given descriptor from the given service, pick any needed intro points + * and update the current map with those newly picked intro points. Return the + * number node that might have been added to the descriptor current map. */ +static unsigned int +pick_needed_intro_points(hs_service_t *service, + hs_service_descriptor_t *desc) +{ + int i = 0, num_needed_ip; + smartlist_t *exclude_nodes = smartlist_new(); + + tor_assert(service); + tor_assert(desc); + + /* Compute how many intro points we actually need to open. */ + num_needed_ip = service->config.num_intro_points - + digest256map_size(desc->intro_points.map); + if (BUG(num_needed_ip < 0)) { + /* Let's not make tor freak out here and just skip this. */ + goto done; + } + + /* We want to end up with config.num_intro_points intro points, but if we + * have no intro points at all (chances are they all cycled or we are + * starting up), we launch get_intro_point_num_extra() extra circuits and + * use the first config.num_intro_points that complete. See proposal #155, + * section 4 for the rationale of this which is purely for performance. + * + * The ones after the first config.num_intro_points will be converted to + * 'General' internal circuits and then we'll drop them from the list of + * intro points. */ + if (digest256map_size(desc->intro_points.map) == 0) { + num_needed_ip += get_intro_point_num_extra(); + } + + /* Build an exclude list of nodes of our intro point(s). The expiring intro + * points are OK to pick again because this is afterall a concept of round + * robin so they are considered valid nodes to pick again. */ + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *intro_node = get_node_from_intro_point(ip); + if (intro_node) { + smartlist_add(exclude_nodes, (void*)intro_node); + } + } DIGEST256MAP_FOREACH_END; + /* Also, add the failing intro points that our descriptor encounteered in + * the exclude node list. */ + setup_intro_point_exclude_list(desc, exclude_nodes); + + for (i = 0; i < num_needed_ip; i++) { + hs_service_intro_point_t *ip; + + /* This function will add the picked intro point node to the exclude nodes + * list so we don't pick the same one at the next iteration. */ + ip = pick_intro_point(service->config.is_single_onion, exclude_nodes); + if (ip == NULL) { + /* If we end up unable to pick an introduction point it is because we + * can't find suitable node and calling this again is highly unlikely to + * give us a valid node all of the sudden. */ + log_info(LD_REND, "Unable to find a suitable node to be an " + "introduction point for service %s.", + safe_str_client(service->onion_address)); + goto done; + } + /* Valid intro point object, add it to the descriptor current map. */ + service_intro_point_add(desc->intro_points.map, ip); + } + /* We've successfully picked all our needed intro points thus none are + * missing which will tell our upload process to expect the number of + * circuits to be the number of configured intro points circuits and not the + * number of intro points object that we have. */ + desc->missing_intro_points = 0; + + /* Success. */ + done: + /* We don't have ownership of the node_t object in this list. */ + smartlist_free(exclude_nodes); + return i; +} + +/** Clear previous cached HSDirs in <b>desc</b>. */ +static void +service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc) +{ + if (BUG(!desc->previous_hsdirs)) { + return; + } + + SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s)); + smartlist_clear(desc->previous_hsdirs); +} + +/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */ +static void +service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir->identity); + + if (BUG(!desc->previous_hsdirs)) { + return; + } + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + smartlist_add_strdup(desc->previous_hsdirs, b64_digest); + } +} + +/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it + * means that this descriptor is dirty. */ +STATIC void +service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed) + +{ + desc->next_upload_time = now; + + /* If the descriptor changed, clean up the old HSDirs list. We want to + * re-upload no matter what. */ + if (descriptor_changed) { + service_desc_clear_previous_hsdirs(desc); + } +} + +/* Update the given descriptor from the given service. The possible update + * actions includes: + * - Picking missing intro points if needed. + * - Incrementing the revision counter if needed. + */ +static void +update_service_descriptor(hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + tor_assert(desc->desc); + + num_intro_points = digest256map_size(desc->intro_points.map); + + /* Pick any missing introduction point(s). */ + if (num_intro_points < service->config.num_intro_points) { + unsigned int num_new_intro_points = pick_needed_intro_points(service, + desc); + if (num_new_intro_points != 0) { + log_info(LD_REND, "Service %s just picked %u intro points and wanted " + "%u for %s descriptor. It currently has %d intro " + "points. Launching ESTABLISH_INTRO circuit shortly.", + safe_str_client(service->onion_address), + num_new_intro_points, + service->config.num_intro_points - num_intro_points, + (desc == service->desc_current) ? "current" : "next", + num_intro_points); + /* We'll build those introduction point into the descriptor once we have + * confirmation that the circuits are opened and ready. However, + * indicate that this descriptor should be uploaded from now on. */ + service_desc_schedule_upload(desc, now, 1); + } + /* Were we able to pick all the intro points we needed? If not, we'll + * flag the descriptor that it's missing intro points because it + * couldn't pick enough which will trigger a descriptor upload. */ + if ((num_new_intro_points + num_intro_points) < + service->config.num_intro_points) { + desc->missing_intro_points = 1; + } + } +} + +/* Update descriptors for each service if needed. */ +STATIC void +update_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + /* We'll try to update each descriptor that is if certain conditions apply + * in order for the descriptor to be updated. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + update_service_descriptor(service, desc, now); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Return true iff the given intro point has expired that is it has been used + * for too long or we've reached our max seen INTRODUCE2 cell. */ +STATIC int +intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now) +{ + tor_assert(ip); + + if (ip->introduce2_count >= ip->introduce2_max) { + goto expired; + } + + if (ip->time_to_expire <= now) { + goto expired; + } + + /* Not expiring. */ + return 0; + expired: + return 1; +} + +/* Go over the given set of intro points for each service and remove any + * invalid ones. The conditions for removal are: + * + * - The node doesn't exists anymore (not in consensus) + * OR + * - The intro point maximum circuit retry count has been reached and no + * circuit can be found associated with it. + * OR + * - The intro point has expired and we should pick a new one. + * + * If an intro point is removed, the circuit (if any) is immediately close. + * If a circuit can't be found, the intro point is kept if it hasn't reached + * its maximum circuit retry value and thus should be retried. */ +static void +cleanup_intro_points(hs_service_t *service, time_t now) +{ + /* List of intro points to close. We can't mark the intro circuits for close + * in the modify loop because doing so calls + * hs_service_intro_circ_has_closed() which does a digest256map_get() on the + * intro points map (that we are iterating over). This can't be done in a + * single iteration after a MAP_DEL_CURRENT, the object will still be + * returned leading to a use-after-free. So, we close the circuits and free + * the intro points after the loop if any. */ + smartlist_t *ips_to_free = smartlist_new(); + + tor_assert(service); + + /* For both descriptors, cleanup the intro points. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Go over the current intro points we have, make sure they are still + * valid and remove any of them that aren't. */ + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *node = get_node_from_intro_point(ip); + int has_expired = intro_point_should_expire(ip, now); + + /* We cleanup an intro point if it has expired or if we do not know the + * node_t anymore (removed from our latest consensus) or if we've + * reached the maximum number of retry with a non existing circuit. */ + if (has_expired || node == NULL || + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + log_info(LD_REND, "Intro point %s%s (retried: %u times). " + "Removing it.", + describe_intro_point(ip), + has_expired ? " has expired" : + (node == NULL) ? " fell off the consensus" : "", + ip->circuit_retries); + + /* We've retried too many times, remember it as a failed intro point + * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */ + if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, approx_time()); + } + + /* Remove intro point from descriptor map and add it to the list of + * ips to free for which we'll also try to close the intro circuit. */ + MAP_DEL_CURRENT(key); + smartlist_add(ips_to_free, ip); + } + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; + + /* Go over the intro points to free and close their circuit if any. */ + SMARTLIST_FOREACH_BEGIN(ips_to_free, hs_service_intro_point_t *, ip) { + /* See if we need to close the intro point circuit as well */ + + /* XXX: Legacy code does NOT close circuits like this: it keeps the circuit + * open until a new descriptor is uploaded and then closed all expiring + * intro point circuit. Here, we close immediately and because we just + * discarded the intro point, a new one will be selected, a new descriptor + * created and uploaded. There is no difference to an attacker between the + * timing of a new consensus and intro point rotation (possibly?). */ + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + + /* Cleanup the intro point */ + service_intro_point_free(ip); + } SMARTLIST_FOREACH_END(ip); + + smartlist_free(ips_to_free); +} + +/* Set the next rotation time of the descriptors for the given service for the + * time now. */ +static void +set_rotation_time(hs_service_t *service, time_t now) +{ + time_t valid_after; + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + if (ns) { + valid_after = ns->valid_after; + } else { + valid_after = now; + } + + tor_assert(service); + service->state.next_rotation_time = + sr_state_get_start_time_of_current_protocol_run(valid_after) + + sr_state_get_protocol_run_duration(); + + { + char fmt_time[ISO_TIME_LEN + 1]; + format_local_iso_time(fmt_time, service->state.next_rotation_time); + log_info(LD_REND, "Next descriptor rotation time set to %s for %s", + fmt_time, safe_str_client(service->onion_address)); + } +} + +/* Return true iff the service should rotate its descriptor. The time now is + * only used to fetch the live consensus and if none can be found, this + * returns false. */ +static unsigned int +should_rotate_descriptors(hs_service_t *service, time_t now) +{ + const networkstatus_t *ns; + + tor_assert(service); + + ns = networkstatus_get_live_consensus(now); + if (ns == NULL) { + goto no_rotation; + } + + if (ns->valid_after >= service->state.next_rotation_time) { + goto rotation; + } + + no_rotation: + return 0; + rotation: + return 1; +} + +/* Rotate the service descriptors of the given service. The current descriptor + * will be freed, the next one put in as the current and finally the next + * descriptor pointer is NULLified. */ +static void +rotate_service_descriptors(hs_service_t *service, time_t now) +{ + if (service->desc_current) { + /* Close all IP circuits for the descriptor. */ + close_intro_circuits(&service->desc_current->intro_points); + /* We don't need this one anymore, we won't serve any clients coming with + * this service descriptor. */ + service_descriptor_free(service->desc_current); + } + /* The next one become the current one and emptying the next will trigger + * a descriptor creation for it. */ + service->desc_current = service->desc_next; + service->desc_next = NULL; + + /* We've just rotated, set the next time for the rotation. */ + set_rotation_time(service, now); +} + +/* Rotate descriptors for each service if needed. A non existing current + * descriptor will trigger a descriptor build for the next time period. */ +STATIC void +rotate_all_descriptors(time_t now) +{ + /* XXX We rotate all our service descriptors at once. In the future it might + * be wise, to rotate service descriptors independently to hide that all + * those descriptors are on the same tor instance */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* Note for a service booting up: Both descriptors are NULL in that case + * so this function might return true if we are in the timeframe for a + * rotation leading to basically swapping two NULL pointers which is + * harmless. However, the side effect is that triggering a rotation will + * update the service state and avoid doing anymore rotations after the + * two descriptors have been built. */ + if (!should_rotate_descriptors(service, now)) { + continue; + } + + tor_assert(service->desc_current); + tor_assert(service->desc_next); + + log_info(LD_REND, "Time to rotate our descriptors (%p / %p) for %s", + service->desc_current, service->desc_next, + safe_str_client(service->onion_address)); + + rotate_service_descriptors(service, now); + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all our services are up + * to date and ready for the other scheduled events. This includes looking at + * the introduction points status and descriptor rotation time. */ +STATIC void +run_housekeeping_event(time_t now) +{ + /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are + * simply moving things around or removing unneeded elements. */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* If the service is starting off, set the rotation time. We can't do that + * at configure time because the get_options() needs to be set for setting + * that time that uses the voting interval. */ + if (service->state.next_rotation_time == 0) { + /* Set the next rotation time of the descriptors. If it's Oct 25th + * 23:47:00, the next rotation time is when the next SRV is computed + * which is at Oct 26th 00:00:00 that is in 13 minutes. */ + set_rotation_time(service, now); + } + + /* Cleanup invalid intro points from the service descriptor. */ + cleanup_intro_points(service, now); + + /* Remove expired failing intro point from the descriptor failed list. We + * reset them at each INTRO_CIRC_RETRY_PERIOD. */ + remove_expired_failing_intro(service, now); + + /* At this point, the service is now ready to go through the scheduled + * events guaranteeing a valid state. Intro points might be missing from + * the descriptors after the cleanup but the update/build process will + * make sure we pick those missing ones. */ + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all descriptors are up to + * date. Once this returns, each service descriptor needs to be considered for + * new introduction circuits and then for upload. */ +static void +run_build_descriptor_event(time_t now) +{ + /* For v2 services, this step happens in the upload event. */ + + /* Run v3+ events. */ + /* We start by rotating the descriptors only if needed. */ + rotate_all_descriptors(now); + + /* Then, we'll try to build new descriptors that we might need. The + * condition is that the next descriptor is non existing because it has + * been rotated or we just started up. */ + build_all_descriptors(now); + + /* Finally, we'll check if we should update the descriptors. Missing + * introduction points will be picked in this function which is useful for + * newly built descriptors. */ + update_all_descriptors(now); +} + +/* For the given service, launch any intro point circuits that could be + * needed. This considers every descriptor of the service. */ +static void +launch_intro_point_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* For both descriptors, try to launch any missing introduction point + * circuits using the current map. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Keep a ref on if we need a direct connection. We use this often. */ + unsigned int direct_conn = service->config.is_single_onion; + + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + extend_info_t *ei; + + /* Skip the intro point that already has an existing circuit + * (established or not). */ + if (hs_circ_service_get_intro_circ(ip)) { + continue; + } + + ei = get_extend_info_from_intro_point(ip, direct_conn); + if (ei == NULL) { + /* This is possible if we can get a node_t but not the extend info out + * of it. In this case, we remove the intro point and a new one will + * be picked at the next main loop callback. */ + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + continue; + } + + /* Launch a circuit to the intro point. */ + ip->circuit_retries++; + if (hs_circ_launch_intro_point(service, ip, ei) < 0) { + log_info(LD_REND, "Unable to launch intro circuit to node %s " + "for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + /* Intro point will be retried if possible after this. */ + } + extend_info_free(ei); + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* Don't try to build more than this many circuits before giving up for a + * while. Dynamically calculated based on the configured number of intro + * points for the given service and how many descriptor exists. The default + * use case of 3 introduction points and two descriptors will allow 28 + * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */ +static unsigned int +get_max_intro_circ_per_period(const hs_service_t *service) +{ + unsigned int count = 0; + unsigned int multiplier = 0; + unsigned int num_wanted_ip; + + tor_assert(service); + tor_assert(service->config.num_intro_points <= + HS_CONFIG_V3_MAX_INTRO_POINTS); + +/* For a testing network, allow to do it for the maximum amount so circuit + * creation and rotation and so on can actually be tested without limit. */ +#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING; + } + + num_wanted_ip = service->config.num_intro_points; + + /* The calculation is as follow. We have a number of intro points that we + * want configured as a torrc option (num_intro_points). We then add an + * extra value so we can launch multiple circuits at once and pick the + * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll + * pick 5 intros and launch 5 circuits. */ + count += (num_wanted_ip + get_intro_point_num_extra()); + + /* Then we add the number of retries that is possible to do for each intro + * point. If we want 3 intros, we'll allow 3 times the number of possible + * retry. */ + count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES); + + /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we + * have none. */ + multiplier += (service->desc_current) ? 1 : 0; + multiplier += (service->desc_next) ? 1 : 0; + + return (count * multiplier); +} + +/* For the given service, return 1 if the service is allowed to launch more + * introduction circuits else 0 if the maximum has been reached for the retry + * period of INTRO_CIRC_RETRY_PERIOD. */ +STATIC int +can_service_launch_intro_circuit(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* Consider the intro circuit retry period of the service. */ + if (now > (service->state.intro_circ_retry_started_time + + INTRO_CIRC_RETRY_PERIOD)) { + service->state.intro_circ_retry_started_time = now; + service->state.num_intro_circ_launched = 0; + goto allow; + } + /* Check if we can still launch more circuits in this period. */ + if (service->state.num_intro_circ_launched <= + get_max_intro_circ_per_period(service)) { + goto allow; + } + + /* Rate limit log that we've reached our circuit creation limit. */ + { + char *msg; + time_t elapsed_time = now - service->state.intro_circ_retry_started_time; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, now))) { + log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit " + "of %u per %d seconds. It launched %u circuits in " + "the last %ld seconds. Will retry in %ld seconds.", + safe_str_client(service->onion_address), + get_max_intro_circ_per_period(service), + INTRO_CIRC_RETRY_PERIOD, + service->state.num_intro_circ_launched, + (long int) elapsed_time, + (long int) (INTRO_CIRC_RETRY_PERIOD - elapsed_time)); + tor_free(msg); + } + } + + /* Not allow. */ + return 0; + allow: + return 1; +} + +/* Scheduled event run from the main loop. Make sure we have all the circuits + * we need for each service. */ +static void +run_build_circuit_event(time_t now) +{ + /* Make sure we can actually have enough information or able to build + * internal circuits as required by services. */ + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN || + !have_completed_a_circuit()) { + return; + } + + /* Run v2 check. */ + if (rend_num_services() > 0) { + rend_consider_services_intro_points(now); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* For introduction circuit, we need to make sure we don't stress too much + * circuit creation so make sure this service is respecting that limit. */ + if (can_service_launch_intro_circuit(service, now)) { + /* Launch intro point circuits if needed. */ + launch_intro_point_circuits(service); + /* Once the circuits have opened, we'll make sure to update the + * descriptor intro point list and cleanup any extraneous. */ + } + } FOR_EACH_SERVICE_END; +} + +/* Encode and sign the service descriptor desc and upload it to the given + * hidden service directory. This does nothing if PublishHidServDescriptors + * is false. */ +static void +upload_descriptor_to_hsdir(const hs_service_t *service, + hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char version_str[4] = {0}, *encoded_desc = NULL; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(service); + tor_assert(desc); + tor_assert(hsdir); + + memset(&ident, 0, sizeof(ident)); + + /* Let's avoid doing that if tor is configured to not publish. */ + if (!get_options()->PublishHidServDescriptors) { + log_info(LD_REND, "Service %s not publishing descriptor. " + "PublishHidServDescriptors is set to 1.", + safe_str_client(service->onion_address)); + goto end; + } + + /* First of all, we'll encode the descriptor. This should NEVER fail but + * just in case, let's make sure we have an actual usable descriptor. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto end; + } + + /* Setup the connection identifier. */ + hs_ident_dir_conn_init(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + &ident); + + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs/<version>/publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", + service->config.version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir->rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_upload_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); + + /* Add this node to previous_hsdirs list */ + service_desc_note_upload(desc, hsdir); + + /* Logging so we know where it was sent. */ + { + int is_next_desc = (service->desc_next == desc); + const uint8_t *idx = (is_next_desc) ? hsdir->hsdir_index->store_second: + hsdir->hsdir_index->store_first; + log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 + " initiated upload request to %s with index %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", + desc->desc->plaintext_data.revision_counter, + safe_str_client(node_describe(hsdir)), + safe_str_client(hex_str((const char *) idx, 32))); + } + + /* XXX: Inform control port of the upload event (#20699). */ + end: + tor_free(encoded_desc); + return; +} + +/** Return a newly-allocated string for our state file which contains revision + * counter information for <b>desc</b>. The format is: + * + * HidServRevCounter <blinded_pubkey> <rev_counter> + */ +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) +{ + char *state_str = NULL; + char blinded_pubkey_b64[ED25519_BASE64_LEN+1]; + uint64_t rev_counter = desc->desc->plaintext_data.revision_counter; + const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey; + + /* Turn the blinded key into b64 so that we save it on state */ + tor_assert(blinded_pubkey); + if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) { + goto done; + } + + /* Format is: <blinded key> <rev counter> */ + tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter); + + log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!", + rev_counter, blinded_pubkey_b64); + + done: + return state_str; +} + +/** Update HS descriptor revision counters in our state by removing the old + * ones and writing down the ones that are currently active. */ +static void +update_revision_counters_in_state(void) +{ + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + or_state_t *state = get_or_state(); + + /* Prepare our state structure with the rev counters */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We don't want to save zero counters */ + if (desc->desc->plaintext_data.revision_counter == 0) { + continue; + } + + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("HidServRevCounter"); + (*nextline)->value = encode_desc_rev_counter_for_state(desc); + nextline = &(*nextline)->next; + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* Remove the old rev counters, and replace them with the new ones */ + config_free_lines(state->HidServRevCounter); + state->HidServRevCounter = lines; + + /* Set the state as dirty since we just edited it */ + if (!get_options()->AvoidDiskWrites) { + or_state_mark_dirty(state, 0); + } +} + +/** Scan the string <b>state_line</b> for the revision counter of the service + * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the + * line is relevant to this service, and return the cached revision + * counter. Else set <b>service_found_out</b> to False. */ +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out) +{ + smartlist_t *items = NULL; + int ok; + ed25519_public_key_t pubkey_in_state; + uint64_t rev_counter = 0; + + tor_assert(service_found_out); + tor_assert(state_line); + tor_assert(blinded_pubkey); + + /* Assume that the line is not for this service */ + *service_found_out = 0; + + /* Start parsing the state line */ + items = smartlist_new(); + smartlist_split_string(items, state_line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 2) { + log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring."); + goto done; + } + + char *b64_key_str = smartlist_get(items, 0); + char *saved_rev_counter_str = smartlist_get(items, 1); + + /* Parse blinded key to check if it's for this hidden service */ + if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) { + log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring."); + goto done; + } + /* State line not for this hidden service */ + if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) { + goto done; + } + + rev_counter = tor_parse_uint64(saved_rev_counter_str, + 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring."); + goto done; + } + + /* Since we got this far, the line was for this service */ + *service_found_out = 1; + + log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64, + b64_key_str, rev_counter); + + done: + tor_assert(items); + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return rev_counter; +} + +/** Dig into our state file and find the current revision counter for the + * service with blinded key <b>blinded_pubkey</b>. If no revision counter is + * found, return 0. */ +static uint64_t +get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey) +{ + or_state_t *state = get_or_state(); + config_line_t *line; + + /* Set default value for rev counters (if not found) to 0 */ + uint64_t final_rev_counter = 0; + + for (line = state->HidServRevCounter ; line ; line = line->next) { + int service_found = 0; + uint64_t rev_counter = 0; + + tor_assert(!strcmp(line->key, "HidServRevCounter")); + + /* Scan all the HidServRevCounter lines till we find the line for this + service: */ + rev_counter = check_state_line_for_service_rev_counter(line->value, + blinded_pubkey, + &service_found); + if (service_found) { + final_rev_counter = rev_counter; + goto done; + } + } + + done: + return final_rev_counter; +} + +/** Update the value of the revision counter for <b>hs_desc</b> and save it on + our state file. */ +static void +increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + /* Increment the revision counter of <b>hs_desc</b> so the next update (which + * will trigger an upload) will have the right value. We do this at this + * stage to only do it once because a descriptor can have many updates before + * being uploaded. By doing it at upload, we are sure to only increment by 1 + * and thus avoid leaking how many operations we made on the descriptor from + * the previous one before uploading. */ + rev_counter++; + hs_desc->plaintext_data.revision_counter = rev_counter; + + update_revision_counters_in_state(); +} + +/** Set the revision counter in <b>hs_desc</b>, using the state file to find + * the current counter value if it exists. */ +static void +set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + hs_desc->plaintext_data.revision_counter = rev_counter; +} + +/* Encode and sign the service descriptor desc and upload it to the + * responsible hidden service directories. If for_next_period is true, the set + * of directories are selected using the next hsdir_index. This does nothing + * if PublishHidServDescriptors is false. */ +STATIC void +upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + smartlist_t *responsible_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + /* We'll first cancel any directory request that are ongoing for this + * descriptor. It is possible that we can trigger multiple uploads in a + * short time frame which can lead to a race where the second upload arrives + * before the first one leading to a 400 malformed descriptor response from + * the directory. Closing all pending requests avoids that. */ + close_directory_connections(service, desc); + + /* Get our list of responsible HSDir. */ + responsible_dirs = smartlist_new(); + /* The parameter 0 means that we aren't a client so tell the function to use + * the spread store consensus paremeter. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /** Clear list of previous hsdirs since we are about to upload to a new + * list. Let's keep it up to date. */ + service_desc_clear_previous_hsdirs(desc); + + /* For each responsible HSDir we have, initiate an upload command. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, + hsdir_rs) { + const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + /* Getting responsible hsdir implies that the node_t object exists for the + * routerstatus_t found in the consensus else we have a problem. */ + tor_assert(hsdir_node); + /* Upload this descriptor to the chosen directory. */ + upload_descriptor_to_hsdir(service, desc, hsdir_node); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Set the next upload time for this descriptor. Even if we are configured + * to not upload, we still want to follow the right cycle of life for this + * descriptor. */ + desc->next_upload_time = + (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN, + HS_SERVICE_NEXT_UPLOAD_TIME_MAX)); + { + char fmt_next_time[ISO_TIME_LEN+1]; + format_local_iso_time(fmt_next_time, desc->next_upload_time); + log_debug(LD_REND, "Service %s set to upload a descriptor at %s", + safe_str_client(service->onion_address), fmt_next_time); + } + + /* Update the revision counter of this descriptor */ + increment_descriptor_revision_counter(desc->desc); + + smartlist_free(responsible_dirs); + return; +} + +/** The set of HSDirs have changed: check if the change affects our descriptor + * HSDir placement, and if it does, reupload the desc. */ +STATIC int +service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + int should_reupload = 0; + smartlist_t *responsible_dirs = smartlist_new(); + + /* No desc upload has happened yet: it will happen eventually */ + if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { + goto done; + } + + /* Get list of responsible hsdirs */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /* Check if any new hsdirs have been added to the responsible hsdirs set: + * Iterate over the list of new hsdirs, and reupload if any of them is not + * present in the list of previous hsdirs. + */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir_rs->identity_digest); + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + should_reupload = 1; + break; + } + } SMARTLIST_FOREACH_END(hsdir_rs); + + done: + smartlist_free(responsible_dirs); + + return should_reupload; +} + +/* Return 1 if the given descriptor from the given service can be uploaded + * else return 0 if it can not. */ +static int +should_service_upload_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + + /* If this descriptors has missing intro points that is that it couldn't get + * them all when it was time to pick them, it means that we should upload + * instead of waiting an arbitrary amount of time breaking the service. + * Else, if we have no missing intro points, we use the value taken from the + * service configuration. */ + if (desc->missing_intro_points) { + num_intro_points = digest256map_size(desc->intro_points.map); + } else { + num_intro_points = service->config.num_intro_points; + } + + /* This means we tried to pick intro points but couldn't get any so do not + * upload descriptor in this case. We need at least one for the service to + * be reachable. */ + if (desc->missing_intro_points && num_intro_points == 0) { + goto cannot; + } + + /* Check if all our introduction circuit have been established for all the + * intro points we have selected. */ + if (count_desc_circuit_established(desc) != num_intro_points) { + goto cannot; + } + + /* Is it the right time to upload? */ + if (desc->next_upload_time > now) { + goto cannot; + } + + /* Don't upload desc if we don't have a live consensus */ + if (!networkstatus_get_live_consensus(now)) { + goto cannot; + } + + /* Do we know enough router descriptors to have adequate vision of the HSDir + hash ring? */ + if (!router_have_minimum_dir_info()) { + goto cannot; + } + + /* Can upload! */ + return 1; + cannot: + return 0; +} + +/* Scheduled event run from the main loop. Try to upload the descriptor for + * each service. */ +STATIC void +run_upload_descriptor_event(time_t now) +{ + /* v2 services use the same function for descriptor creation and upload so + * we do everything here because the intro circuits were checked before. */ + if (rend_num_services() > 0) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* If we were asked to re-examine the hash ring, and it changed, then + schedule an upload */ + if (consider_republishing_hs_descriptors && + service_desc_hsdirs_changed(service, desc)) { + service_desc_schedule_upload(desc, now, 0); + } + + /* Can this descriptor be uploaded? */ + if (!should_service_upload_descriptor(service, desc, now)) { + continue; + } + + log_info(LD_REND, "Initiating upload for hidden service %s descriptor " + "for service %s with %u/%u introduction points%s.", + (desc == service->desc_current) ? "current" : "next", + safe_str_client(service->onion_address), + digest256map_size(desc->intro_points.map), + service->config.num_intro_points, + (desc->missing_intro_points) ? " (couldn't pick more)" : ""); + + /* At this point, we have to upload the descriptor so start by building + * the intro points descriptor section which we are now sure to be + * accurate because all circuits have been established. */ + build_desc_intro_points(service, desc, now); + + upload_descriptor_to_all(service, desc); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* We are done considering whether to republish rend descriptors */ + consider_republishing_hs_descriptors = 0; +} + +/* Called when the introduction point circuit is done building and ready to be + * used. */ +static void +service_intro_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + /* Let's do some basic sanity checking of the circ state */ + if (BUG(!circ->cpath)) { + return; + } + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + return; + } + if (BUG(!circ->hs_ident)) { + return; + } + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + log_warn(LD_REND, "Unknown introduction point auth key on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* We can't have an IP object without a descriptor. */ + tor_assert(desc); + + if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { + /* Getting here means that the circuit has been re-purposed because we + * have enough intro circuit opened. Remove the IP from the service. */ + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + goto done; + + err: + /* Close circuit, we can't use it. */ + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* Called when a rendezvous circuit is done building and ready to be used. */ +static void +service_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(circ->cpath); + /* Getting here means this is a v3 rendezvous circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the rendezvous " + "circuit %u with cookie %s. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN)); + goto err; + } + + /* If the cell can't be sent, the circuit will be closed within this + * function. */ + hs_circ_service_rp_has_opened(service, circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just + * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given + * introduction circuit. Return 0 on success else a negative value. */ +static int +service_handle_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* We need the service and intro point for this cell. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, NULL); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_REND, "Introduction circuit established without an intro " + "point object on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. On success, the ip object and circuit purpose is updated to + * reflect the fact that the introduction circuit is established. */ + if (hs_circ_handle_intro_established(service, ip, circ, payload, + payload_len) < 0) { + goto err; + } + + /* Flag that we have an established circuit for this intro point. This value + * is what indicates the upload scheduled event if we are ready to build the + * intro point into the descriptor and upload. */ + ip->circuit_established = 1; + + log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " + "on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + return 0; + + err: + return -1; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the cell and return 0 on success else a negative value. */ +static int +service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); + + /* We'll need every object associated with this circuit. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_BUG, "Unknown service identity key %s when handling " + "an INTRODUCE2 cell on circuit %u", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_BUG, "Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* If we have an IP object, we MUST have a descriptor object. */ + tor_assert(desc); + + /* The following will parse, decode and launch the rendezvous point circuit. + * Both current and legacy cells are handled. */ + if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + payload, payload_len) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + +/* Add to list every filename used by service. This is used by the sandbox + * subsystem. */ +static void +service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) +{ + const char *s_dir; + char fname[128] = {0}; + + tor_assert(service); + tor_assert(list); + + /* Ease our life. */ + s_dir = service->config.directory_path; + /* The hostname file. */ + smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname)); + /* The key files splitted in two. */ + tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); + tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return the number of service we have configured and usable. */ +unsigned int +hs_service_get_num_services(void) +{ + if (hs_service_map == NULL) { + return 0; + } + return HT_SIZE(hs_service_map); +} + +/* Called once an introduction circuit is closed. If the circuit doesn't have + * a v3 identifier, it is ignored. */ +void +hs_service_intro_circ_has_closed(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + if (circ->hs_ident == NULL) { + /* This is not a v3 circuit, ignore. */ + goto end; + } + + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on intro circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + goto end; + } + if (ip == NULL) { + /* The introduction point object has already been removed probably by our + * cleanup process so ignore. */ + goto end; + } + /* Can't have an intro point object without a descriptor. */ + tor_assert(desc); + + /* Circuit disappeared so make sure the intro point is updated. By + * keeping the object in the descriptor, we'll be able to retry. */ + ip->circuit_established = 0; + + end: + return; +} + +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + +/* Add to file_list every filename used by a configured hidden service, and to + * dir_list every directory path used by a configured hidden service. This is + * used by the sandbox subsystem to whitelist those. */ +void +hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list) +{ + tor_assert(file_list); + tor_assert(dir_list); + + /* Add files and dirs for legacy services. */ + rend_services_add_filenames_to_lists(file_list, dir_list); + + /* Add files and dirs for v3+. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* Skip ephemeral service, they don't touch the disk. */ + if (service->config.is_ephemeral) { + continue; + } + service_add_fnames_to_list(service, file_list); + smartlist_add_strdup(dir_list, service->config.directory_path); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Called when our internal view of the directory has changed. We might have + * received a new batch of descriptors which might affect the shape of the + * HSDir hash ring. Signal that we should reexamine the hash ring and + * re-upload our HS descriptors if needed. */ +void +hs_service_dir_info_changed(void) +{ + if (hs_service_get_num_services() > 0) { + /* New directory information usually goes every consensus so rate limit + * every 30 minutes to not be too conservative. */ + static struct ratelim_t dir_info_changed_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&dir_info_changed_ratelim, LOG_INFO, LD_REND, + "New dirinfo arrived: consider reuploading descriptor"); + consider_republishing_hs_descriptors = 1; + } +} + +/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and + * launch a circuit to the rendezvous point. */ +int +hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Do some initial validation and logging before we parse the cell */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto done; + } + + if (circ->hs_ident) { + ret = service_handle_introduce2(circ, payload, payload_len); + } else { + ret = rend_service_receive_introduction(circ, payload, payload_len); + } + + done: + return ret; +} + +/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an + * established introduction point. Return 0 on success else a negative value + * and the circuit is closed. */ +int +hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto err; + } + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + if (circ->hs_ident) { + ret = service_handle_intro_established(circ, payload, payload_len); + } else { + ret = rend_service_intro_established(circ, payload, payload_len); + } + + if (ret < 0) { + goto err; + } + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* Called when any kind of hidden service circuit is done building thus + * opened. This is the entry point from the circuit subsystem. */ +void +hs_service_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + if (circ->hs_ident) { + service_intro_circ_has_opened(circ); + } else { + rend_service_intro_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + if (circ->hs_ident) { + service_rendezvous_circ_has_opened(circ); + } else { + rend_service_rendezvous_has_opened(circ); + } + break; + default: + tor_assert(0); + } +} + +/* Load and/or generate keys for all onion services including the client + * authorization if any. Return 0 on success, -1 on failure. */ +int +hs_service_load_all_keys(void) +{ + /* Load v2 service keys if we have v2. */ + if (rend_num_services() != 0) { + if (rend_service_load_all_keys(NULL) < 0) { + goto err; + } + } + + /* Load or/and generate them for v3+. */ + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) { + /* Ignore ephemeral service, they already have their keys set. */ + if (service->config.is_ephemeral) { + continue; + } + log_info(LD_REND, "Loading v3 onion service keys from %s", + service_escaped_dir(service)); + if (load_service_keys(service) < 0) { + goto err; + } + /* XXX: Load/Generate client authorization keys. (#20700) */ + } SMARTLIST_FOREACH_END(service); + + /* Final step, the staging list contains service in a quiescent state that + * is ready to be used. Register them to the global map. Once this is over, + * the staging list will be cleaned up. */ + register_all_services(); + + /* All keys have been loaded successfully. */ + return 0; + err: + return -1; +} + +/* Put all service object in the given service list. After this, the caller + * looses ownership of every elements in the list and responsible to free the + * list pointer. */ +void +hs_service_stage_services(const smartlist_t *service_list) +{ + tor_assert(service_list); + /* This list is freed at registration time but this function can be called + * multiple time. */ + if (hs_service_staging_list == NULL) { + hs_service_staging_list = smartlist_new(); + } + /* Add all service object to our staging list. Caller is responsible for + * freeing the service_list. */ + smartlist_add_all(hs_service_staging_list, service_list); +} + +/* Allocate and initilize a service object. The service configuration will + * contain the default values. Return the newly allocated object pointer. This + * function can't fail. */ +hs_service_t * +hs_service_new(const or_options_t *options) +{ + hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t)); + /* Set default configuration value. */ + set_service_default_config(&service->config, options); + /* Set the default service version. */ + service->config.version = HS_SERVICE_DEFAULT_VERSION; + /* Allocate the CLIENT_PK replay cache in service state. */ + service->state.replay_cache_rend_cookie = + replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); + + return service; +} + +/* Free the given <b>service</b> object and all its content. This function + * also takes care of wiping service keys from memory. It is safe to pass a + * NULL pointer. */ +void +hs_service_free(hs_service_t *service) +{ + if (service == NULL) { + return; + } + + /* Free descriptors. Go over both descriptor with this loop. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + service_descriptor_free(desc); + } FOR_EACH_DESCRIPTOR_END; + + /* Free service configuration. */ + service_clear_config(&service->config); + + /* Free replay cache from state. */ + if (service->state.replay_cache_rend_cookie) { + replaycache_free(service->state.replay_cache_rend_cookie); + } + + /* Wipe service keys. */ + memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); + + tor_free(service); +} + +/* Periodic callback. Entry point from the main loop to the HS service + * subsystem. This is call every second. This is skipped if tor can't build a + * circuit or the network is disabled. */ +void +hs_service_run_scheduled_events(time_t now) +{ + /* First thing we'll do here is to make sure our services are in a + * quiescent state for the scheduled events. */ + run_housekeeping_event(now); + + /* Order matters here. We first make sure the descriptor object for each + * service contains the latest data. Once done, we check if we need to open + * new introduction circuit. Finally, we try to upload the descriptor for + * each service. */ + + /* Make sure descriptors are up to date. */ + run_build_descriptor_event(now); + /* Make sure services have enough circuits. */ + run_build_circuit_event(now); + /* Upload the descriptors if needed/possible. */ + run_upload_descriptor_event(now); +} + +/* Initialize the service HS subsystem. */ +void +hs_service_init(void) +{ + /* Should never be called twice. */ + tor_assert(!hs_service_map); + tor_assert(!hs_service_staging_list); + + /* v2 specific. */ + rend_service_init(); + + hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht)); + HT_INIT(hs_service_ht, hs_service_map); + + hs_service_staging_list = smartlist_new(); +} + +/* Release all global storage of the hidden service subsystem. */ +void +hs_service_free_all(void) +{ + rend_service_free_all(); + service_free_all(); +} + +#ifdef TOR_UNIT_TESTS + +/* Return the global service map size. Only used by unit test. */ +STATIC unsigned int +get_hs_service_map_size(void) +{ + return HT_SIZE(hs_service_map); +} + +/* Return the staging list size. Only used by unit test. */ +STATIC int +get_hs_service_staging_list_size(void) +{ + return smartlist_len(hs_service_staging_list); +} + +STATIC hs_service_ht * +get_hs_service_map(void) +{ + return hs_service_map; +} + +STATIC hs_service_t * +get_first_service(void) +{ + hs_service_t **obj = HT_START(hs_service_ht, hs_service_map); + if (obj == NULL) { + return NULL; + } + return *obj; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + diff --git a/src/or/hs_service.h b/src/or/hs_service.h new file mode 100644 index 0000000000..ed1053d850 --- /dev/null +++ b/src/or/hs_service.h @@ -0,0 +1,354 @@ +/* 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 the responsible HSDirs (their b64ed identity digest) last time we + * uploaded this descriptor. If the set of responsible HSDirs is different + * from this list, this means we received new dirinfo and we need to + * reupload our descriptor. */ + smartlist_t *previous_hsdirs; +} hs_service_descriptor_t; + +/* Service key material. */ +typedef struct hs_service_keys_t { + /* Master identify public key. */ + ed25519_public_key_t identity_pk; + /* Master identity private key. */ + ed25519_secret_key_t identity_sk; + /* True iff the key is kept offline which means the identity_sk MUST not be + * used in that case. */ + unsigned int is_identify_key_offline : 1; +} hs_service_keys_t; + +/* Service configuration. The following are set from the torrc options either + * set by the configuration file or by the control port. Nothing else should + * change those values. */ +typedef struct hs_service_config_t { + /* Protocol version of the service. Specified by HiddenServiceVersion + * option. */ + uint32_t version; + + /* List of rend_service_port_config_t */ + smartlist_t *ports; + + /* Path on the filesystem where the service persistent data is stored. NULL + * if the service is ephemeral. Specified by HiddenServiceDir option. */ + char *directory_path; + + /* The maximum number of simultaneous streams per rendezvous circuit that + * are allowed to be created. No limit if 0. Specified by + * HiddenServiceMaxStreams option. */ + uint64_t max_streams_per_rdv_circuit; + + /* If true, we close circuits that exceed the max_streams_per_rdv_circuit + * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */ + unsigned int max_streams_close_circuit : 1; + + /* How many introduction points this service has. Specified by + * HiddenServiceNumIntroductionPoints option. */ + unsigned int num_intro_points; + + /* True iff we allow request made on unknown ports. Specified by + * HiddenServiceAllowUnknownPorts option. */ + unsigned int allow_unknown_ports : 1; + + /* If true, this service is a Single Onion Service. Specified by + * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */ + unsigned int is_single_onion : 1; + + /* If true, allow group read permissions on the directory_path. Specified by + * HiddenServiceDirGroupReadable option. */ + unsigned int dir_group_readable : 1; + + /* Is this service ephemeral? */ + unsigned int is_ephemeral : 1; +} hs_service_config_t; + +/* Service state. */ +typedef struct hs_service_state_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t intro_circ_retry_started_time; + + /* Number of circuit we've launched during a single retry period. This + * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ + unsigned int num_intro_circ_launched; + + /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point + * through two or more different introduction points; when they do, this + * keeps us from launching multiple simultaneous attempts to connect to the + * same rend point. */ + replaycache_t *replay_cache_rend_cookie; + + /* When is the next time we should rotate our descriptors. This is has to be + * done at the start time of the next SRV protocol run. */ + time_t next_rotation_time; +} hs_service_state_t; + +/* Representation of a service running on this tor instance. */ +typedef struct hs_service_t { + /* Onion address base32 encoded and NUL terminated. We keep it for logging + * purposes so we don't have to build it everytime. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + /* Hashtable node: use to look up the service by its master public identity + * key in the service global map. */ + HT_ENTRY(hs_service_t) hs_service_node; + + /* Service state which contains various flags and counters. */ + hs_service_state_t state; + + /* Key material of the service. */ + hs_service_keys_t keys; + + /* Configuration of the service. */ + hs_service_config_t config; + + /* Current descriptor. */ + hs_service_descriptor_t *desc_current; + /* Next descriptor. */ + hs_service_descriptor_t *desc_next; + + /* XXX: Credential (client auth.) #20700. */ + +} hs_service_t; + +/* For the service global hash map, we define a specific type for it which + * will make it safe to use and specific to some controlled parameters such as + * the hashing function and how to compare services. */ +typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht; + +/* API */ + +/* Global initializer and cleanup function. */ +void hs_service_init(void); +void hs_service_free_all(void); + +/* Service new/free functions. */ +hs_service_t *hs_service_new(const or_options_t *options); +void hs_service_free(hs_service_t *service); + +unsigned int hs_service_get_num_services(void); +void hs_service_stage_services(const smartlist_t *service_list); +int hs_service_load_all_keys(void); +void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); + +void hs_service_dir_info_changed(void); +void hs_service_run_scheduled_events(time_t now); +void hs_service_circuit_has_opened(origin_circuit_t *circ); +int hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_service_receive_introduce2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_service_intro_circ_has_closed(origin_circuit_t *circ); + +#ifdef HS_SERVICE_PRIVATE + +#ifdef TOR_UNIT_TESTS + +/* Useful getters for unit tests. */ +STATIC unsigned int get_hs_service_map_size(void); +STATIC int get_hs_service_staging_list_size(void); +STATIC hs_service_ht *get_hs_service_map(void); +STATIC hs_service_t *get_first_service(void); + +/* Service accessors. */ +STATIC hs_service_t *find_service(hs_service_ht *map, + const ed25519_public_key_t *pk); +STATIC void remove_service(hs_service_ht *map, hs_service_t *service); +STATIC int register_service(hs_service_ht *map, hs_service_t *service); +/* Service introduction point functions. */ +STATIC hs_service_intro_point_t *service_intro_point_new( + const extend_info_t *ei, + unsigned int is_legacy); +STATIC void service_intro_point_free(hs_service_intro_point_t *ip); +STATIC void service_intro_point_add(digest256map_t *map, + hs_service_intro_point_t *ip); +STATIC void service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip); +STATIC hs_service_intro_point_t *service_intro_point_find( + const hs_service_t *service, + const ed25519_public_key_t *auth_key); +STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( + const hs_service_t *service, + const hs_ident_circuit_t *ident); +/* Service descriptor functions. */ +STATIC hs_service_descriptor_t *service_descriptor_new(void); +STATIC hs_service_descriptor_t *service_desc_find_by_intro( + const hs_service_t *service, + const hs_service_intro_point_t *ip); +/* Helper functions. */ +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, + hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc); +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip); +STATIC int can_service_launch_intro_circuit(hs_service_t *service, + time_t now); +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now); +STATIC void run_housekeeping_event(time_t now); +STATIC void rotate_all_descriptors(time_t now); +STATIC void build_all_descriptors(time_t now); +STATIC void update_all_descriptors(time_t now); +STATIC void run_upload_descriptor_event(time_t now); + +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc); + +STATIC void service_descriptor_free(hs_service_descriptor_t *desc); + +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out); + +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_); + +STATIC void upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc); + +STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed); + +STATIC int service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(HS_SERVICE_PRIVATE) */ + +#endif /* !defined(TOR_HS_SERVICE_H) */ + diff --git a/src/or/include.am b/src/or/include.am index 19cf00264b..4938ae8e73 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -19,8 +19,9 @@ EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake LIBTOR_A_SOURCES = \ src/or/addressmap.c \ - src/or/buffers.c \ + src/or/bridges.c \ src/or/channel.c \ + src/or/channelpadding.c \ src/or/channeltls.c \ src/or/circpathbias.c \ src/or/circuitbuild.c \ @@ -35,6 +36,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 \ @@ -49,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 \ @@ -60,8 +76,14 @@ LIBTOR_A_SOURCES = \ 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/proto_cell.c \ + src/or/proto_control0.c \ + src/or/proto_ext_or.c \ + src/or/proto_http.c \ + src/or/proto_socks.c \ src/or/policies.c \ src/or/reasons.c \ src/or/relay.c \ @@ -78,6 +100,8 @@ LIBTOR_A_SOURCES = \ src/or/routerparse.c \ src/or/routerset.c \ src/or/scheduler.c \ + src/or/scheduler_kist.c \ + src/or/scheduler_vanilla.c \ src/or/statefile.c \ src/or/status.c \ src/or/torcert.c \ @@ -109,8 +133,12 @@ src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libev src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ + src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ if COVERAGE_ENABLED src_or_tor_cov_SOURCES = src/or/tor_main.c @@ -122,14 +150,16 @@ src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-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/auth_dirs.inc \ - src/or/buffers.h \ + src/or/bridges.h \ src/or/channel.h \ + src/or/channelpadding.h \ src/or/channeltls.h \ src/or/circpathbias.h \ src/or/circuitbuild.h \ @@ -144,6 +174,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 \ @@ -160,6 +193,18 @@ ORHEADERS = \ 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 \ @@ -174,9 +219,15 @@ ORHEADERS = \ 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/proto_cell.h \ + src/or/proto_control0.h \ + src/or/proto_ext_or.h \ + src/or/proto_http.h \ + src/or/proto_socks.h \ src/or/reasons.h \ src/or/relay.h \ src/or/rendcache.h \ diff --git a/src/or/keypin.c b/src/or/keypin.c index 2d4c4e92d2..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 */ /** diff --git a/src/or/keypin.h b/src/or/keypin.h index 673f24d9e3..fbb77e5c35 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 @@ -41,7 +41,7 @@ STATIC keypin_ent_t * keypin_parse_journal_line(const char *cp); STATIC int keypin_load_journal_impl(const char *data, size_t size); MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent)); -#endif +#endif /* defined(KEYPIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_KEYPIN_H) */ diff --git a/src/or/main.c b/src/or/main.c index 55bcb18c4b..6c32ee5d87 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1,31 +1,73 @@ /* 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 "buffers_tls.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" @@ -38,6 +80,9 @@ #include "entrynodes.h" #include "geoip.h" #include "hibernate.h" +#include "hs_cache.h" +#include "hs_circuitmap.h" +#include "hs_client.h" #include "keypin.h" #include "main.h" #include "microdesc.h" @@ -66,7 +111,6 @@ #include "ext_orport.h" #ifdef USE_DMALLOC #include <dmalloc.h> -#include <openssl/crypto.h> #endif #include "memarea.h" #include "sandbox.h" @@ -79,9 +123,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ void evdns_shutdown(int); @@ -138,7 +182,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; @@ -326,7 +370,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 @@ -448,7 +492,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; @@ -699,7 +743,7 @@ conn_read_callback(evutil_socket_t fd, short event, void *_conn) "(fd %d); removing", conn_type_to_string(conn->type), (int)conn->s); tor_fragile_assert(); -#endif +#endif /* !defined(_WIN32) */ if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); connection_mark_for_close(conn); @@ -794,7 +838,7 @@ conn_close_if_marked(int i) (int)conn->outbuf_flushlen, conn->marked_for_close_file, conn->marked_for_close); if (conn->linked_conn) { - retval = move_buf_to_buf(conn->linked_conn->inbuf, conn->outbuf, + retval = buf_move_to_buf(conn->linked_conn->inbuf, conn->outbuf, &conn->outbuf_flushlen); if (retval >= 0) { /* The linked conn will notice that it has data when it notices that @@ -808,12 +852,13 @@ conn_close_if_marked(int i) connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { - retval = flush_buf_tls(TO_OR_CONN(conn)->tls, conn->outbuf, sz, + retval = buf_flush_to_tls(conn->outbuf, TO_OR_CONN(conn)->tls, sz, &conn->outbuf_flushlen); } else retval = -1; /* never flush non-open broken tls connections */ } else { - retval = flush_buf(conn->s, conn->outbuf, sz, &conn->outbuf_flushlen); + retval = buf_flush_to_socket(conn->outbuf, conn->s, sz, + &conn->outbuf_flushlen); } if (retval >= 0 && /* Technically, we could survive things like TLS_WANT_WRITE here. But don't bother for now. */ @@ -927,6 +972,15 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) { const or_options_t *options = get_options(); + /* if we have enough dir info, then update our guard status with + * whatever we just learned. */ + int invalidate_circs = guards_update_all(); + + if (invalidate_circs) { + circuit_mark_all_unused_circs(); + circuit_mark_all_dirty_circs_as_unusable(); + } + if (!router_have_minimum_dir_info()) { int quiet = suppress_logs || from_cache || directory_too_idle_to_fetch_descriptors(options, now); @@ -940,9 +994,6 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs) update_all_descriptor_downloads(now); } - /* if we have enough dir info, then update our guard status with - * whatever we just learned. */ - entry_guards_compute_status(options, now); /* Don't even bother trying to get extrainfo until the rest of our * directory info is up-to-date */ if (options->DownloadExtraInfo) @@ -1051,8 +1102,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, @@ -1075,6 +1127,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); } } @@ -1092,7 +1146,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); - rend_client_purge_state(); + hs_client_purge_state(); time_of_last_signewnym = now; signewnym_is_pending = 0; @@ -1117,6 +1171,7 @@ 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(rotate_x509_certificate); @@ -1140,6 +1195,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 @@ -1148,6 +1207,7 @@ 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(rotate_x509_certificate), @@ -1171,6 +1231,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 @@ -1339,6 +1403,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); } @@ -1359,6 +1426,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. @@ -1390,7 +1458,7 @@ run_scheduled_events(time_t now) } /* 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); @@ -1399,12 +1467,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. @@ -1422,19 +1484,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 (dir_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."); @@ -1445,21 +1514,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; @@ -1467,6 +1564,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) { @@ -1481,6 +1583,10 @@ launch_descriptor_fetches_callback(time_t now, const or_options_t *options) return GREEDY_DESCRIPTOR_RETRY_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) { @@ -1499,6 +1605,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 @@ -1506,6 +1617,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) { @@ -1522,6 +1637,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) { @@ -1533,6 +1652,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) { @@ -1544,6 +1667,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) { @@ -1556,6 +1683,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) { @@ -1568,18 +1699,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(); } @@ -1587,6 +1721,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) { @@ -1634,6 +1771,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) { @@ -1661,19 +1823,26 @@ 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) { /* Remove old information from rephist and the rend cache. */ rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); - rend_cache_clean_v2_descs_as_dir(now, 0); + hs_cache_clean_as_client(now); + 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) { @@ -1682,23 +1851,25 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options) * clean it as soon as we can since we want to make sure the client waits * as little as possible for reachability reasons. */ rend_cache_failure_clean(now); + hs_cache_client_intro_state_clean(now); return 30; } +/** + * 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) { @@ -1725,6 +1896,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) { @@ -1761,13 +1937,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( @@ -1789,12 +1965,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; @@ -1802,6 +1979,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) { @@ -1811,6 +1991,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) { @@ -1833,6 +2017,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) { @@ -1845,12 +2033,16 @@ 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) { if (net_is_disabled() || ! server_mode(options) || - ! options->PortForwarding) { + ! options->PortForwarding || + options->NoExec) { return PERIODIC_EVENT_NO_UPDATE; } /* 11. check the port forwarding app */ @@ -1868,7 +2060,12 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) return PORT_FORWARDING_CHECK_INTERVAL; } -/** Callback to write heartbeat message in the logs. */ +/** + * 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) { @@ -1879,14 +2076,54 @@ heartbeat_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } - /* Write the heartbeat message */ + /* Skip the first one. */ if (first) { - first = 0; /* Skip the first one. */ + 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; } +} - 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() || + networkstatus_get_live_consensus(now) == NULL) { + goto end; + } + + hs_service_run_scheduled_events(now); + + end: + /* Every 1 second. */ + return 1; } /** Timer: used to invoke second_elapsed_callback() once per second. */ @@ -1991,7 +2228,7 @@ systemd_watchdog_callback(periodic_timer_t *timer, void *arg) (void)arg; sd_notify(0, "WATCHDOG=1"); } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ /** Timer: used to invoke refill_callback(). */ static periodic_timer_t *refill_timer = NULL; @@ -2055,7 +2292,7 @@ got_libevent_error(void) } return 0; } -#endif +#endif /* !defined(_WIN32) */ #define UPTIME_CUTOFF_FOR_NEW_BANDWIDTH_TEST (6*60*60) @@ -2152,7 +2389,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 */ @@ -2179,8 +2416,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."); } @@ -2217,6 +2455,8 @@ do_main_loop(void) } handle_signals(1); + monotime_init(); + timers_initialize(); /* load the private keys, if we're supposed to have them, and set up the * TLS context. */ @@ -2280,13 +2520,14 @@ do_main_loop(void) now = time(NULL); directory_info_has_arrived(now, 1, 0); - if (server_mode(get_options())) { + if (server_mode(get_options()) || dir_server_mode(get_options())) { /* 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_publishes_statuses(get_options())) { + if (authdir_mode_v3(get_options())) { if (sr_init(1) < 0) { return -1; } @@ -2325,7 +2566,7 @@ do_main_loop(void) tor_assert(systemd_watchdog_timer); } } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ if (!refill_timer) { struct timeval refill_interval; @@ -2353,7 +2594,7 @@ do_main_loop(void) log_info(LD_GENERAL, "Systemd NOTIFY_SOCKET not present."); } } -#endif +#endif /* defined(HAVE_SYSTEMD) */ return run_main_loop_until_done(); } @@ -2374,19 +2615,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 */ @@ -2399,7 +2647,7 @@ run_main_loop_once(void) log_warn(LD_NET, "EINVAL from libevent: should you upgrade libevent?"); if (got_libevent_error()) return -1; -#endif +#endif /* !defined(_WIN32) */ } else { tor_assert_nonfatal_once(! ERRNO_IS_EINPROGRESS(e)); log_debug(LD_NET,"libevent call interrupted."); @@ -2409,9 +2657,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; @@ -2652,7 +2908,6 @@ dumpstats(int severity) rep_hist_dump_stats(now,severity); rend_service_dump_stats(severity); - dump_pk_ops(severity); dump_distinct_digest_count(severity); } @@ -2748,7 +3003,7 @@ handle_signals(int is_parent) #ifdef SIGXFSZ sigaction(SIGXFSZ, &action, NULL); #endif -#endif +#endif /* !defined(_WIN32) */ } } @@ -2791,6 +3046,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 @@ -2835,11 +3092,16 @@ tor_init(int argc, char *argv[]) const char *version = get_version(); log_notice(LD_GENERAL, "Tor %s running on %s with Libevent %s, " - "OpenSSL %s and Zlib %s.", version, + "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 " @@ -2850,6 +3112,15 @@ tor_init(int argc, char *argv[]) "Expect more bugs than usual."); } + { + 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."); return -1; @@ -2864,6 +3135,13 @@ tor_init(int argc, char *argv[]) /* 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, " @@ -2921,7 +3199,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; } @@ -2969,7 +3247,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(); @@ -2990,6 +3267,9 @@ tor_free_all(int postfork) control_free_all(); sandbox_free_getaddrinfo_cache(); protover_free_all(); + bridges_free_all(); + consdiffmgr_free_all(); + hs_free_all(); dos_free_all(); if (!postfork) { config_free_all(); @@ -3020,6 +3300,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. */ } } @@ -3057,6 +3338,9 @@ tor_cleanup(void) rep_hist_record_mtbf_data(now, 0); keypin_close_journal(); } + + timers_shutdown(); + #ifdef USE_DMALLOC dmalloc_log_stats(); #endif @@ -3224,7 +3508,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) @@ -3296,7 +3580,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); @@ -3412,6 +3696,8 @@ sandbox_init_filter(void) OPEN_DATADIR("stats"); STAT_DATADIR("stats"); STAT_DATADIR2("stats", "dirreq-stats"); + + consdiffmgr_register_with_sandbox(&cfg); } init_addrinfo(); @@ -3454,28 +3740,30 @@ tor_main(int argc, char *argv[]) } } #endif /* !defined(_WIN64) */ -#endif +#endif /* defined(_WIN32) */ configure_backtrace_handler(get_version()); 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 +#endif /* defined(USE_DMALLOC) */ #ifdef NT_SERVICE { int done = 0; result = nt_service_parse_options(argc, argv, &done); if (done) return result; } -#endif +#endif /* defined(NT_SERVICE) */ if (tor_init(argc, argv)<0) return -1; @@ -3494,8 +3782,6 @@ tor_main(int argc, char *argv[]) #endif } - monotime_init(); - switch (get_options()->command) { case CMD_RUN_TOR: #ifdef NT_SERVICE @@ -3504,7 +3790,11 @@ 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(); break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); diff --git a/src/or/main.h b/src/or/main.h index 07b22598b1..132ab12bbb 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 */ /** @@ -92,7 +92,10 @@ 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 /* defined(MAIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_MAIN_H) */ diff --git a/src/or/microdesc.c b/src/or/microdesc.c index a81dc54628..fe327c6c82 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 */ /** @@ -74,6 +74,102 @@ HT_GENERATE2(microdesc_map, microdesc_t, node, microdesc_hash_, microdesc_eq_, 0.6, tor_reallocarray_, tor_free_) +/************************* md fetch fail cache *****************************/ + +/* If we end up with too many outdated dirservers, something probably went + * wrong so clean up the list. */ +#define TOO_MANY_OUTDATED_DIRSERVERS 30 + +/** List of dirservers with outdated microdesc information. The smartlist is + * filled with the hex digests of outdated dirservers. */ +static smartlist_t *outdated_dirserver_list = NULL; + +/** Note that we failed to fetch a microdescriptor from the relay with + * <b>relay_digest</b> (of size DIGEST_LEN). */ +void +microdesc_note_outdated_dirserver(const char *relay_digest) +{ + char relay_hexdigest[HEX_DIGEST_LEN+1]; + + /* Don't register outdated dirservers if we don't have a live consensus, + * since we might be trying to fetch microdescriptors that are not even + * currently active. */ + if (!networkstatus_get_live_consensus(approx_time())) { + return; + } + + if (!outdated_dirserver_list) { + outdated_dirserver_list = smartlist_new(); + } + + tor_assert(outdated_dirserver_list); + + /* If the list grows too big, clean it up */ + if (BUG(smartlist_len(outdated_dirserver_list) > + TOO_MANY_OUTDATED_DIRSERVERS)) { + microdesc_reset_outdated_dirservers_list(); + } + + /* Turn the binary relay digest to a hex since smartlists have better support + * for strings than digests. */ + base16_encode(relay_hexdigest,sizeof(relay_hexdigest), + relay_digest, DIGEST_LEN); + + /* Make sure we don't add a dirauth as an outdated dirserver */ + if (router_get_trusteddirserver_by_digest(relay_digest)) { + log_info(LD_GENERAL, "Auth %s gave us outdated dirinfo.", relay_hexdigest); + return; + } + + /* Don't double-add outdated dirservers */ + if (smartlist_contains_string(outdated_dirserver_list, relay_hexdigest)) { + return; + } + + /* Add it to the list of outdated dirservers */ + smartlist_add_strdup(outdated_dirserver_list, relay_hexdigest); + + log_info(LD_GENERAL, "Noted %s as outdated md dirserver", relay_hexdigest); +} + +/** Return True if the relay with <b>relay_digest</b> (size DIGEST_LEN) is an + * outdated dirserver */ +int +microdesc_relay_is_outdated_dirserver(const char *relay_digest) +{ + char relay_hexdigest[HEX_DIGEST_LEN+1]; + + if (!outdated_dirserver_list) { + return 0; + } + + /* Convert identity digest to hex digest */ + base16_encode(relay_hexdigest, sizeof(relay_hexdigest), + relay_digest, DIGEST_LEN); + + /* Last time we tried to fetch microdescs, was this directory mirror missing + * any mds we asked for? */ + if (smartlist_contains_string(outdated_dirserver_list, relay_hexdigest)) { + return 1; + } + + return 0; +} + +/** Reset the list of outdated dirservers. */ +void +microdesc_reset_outdated_dirservers_list(void) +{ + if (!outdated_dirserver_list) { + return; + } + + SMARTLIST_FOREACH(outdated_dirserver_list, char *, cp, tor_free(cp)); + smartlist_clear(outdated_dirserver_list); +} + +/****************************************************************************/ + /** Write the body of <b>md</b> into <b>f</b>, with appropriate annotations. * On success, return the total number of bytes written, and set * *<b>annotation_len_out</b> to the number of bytes written as @@ -789,6 +885,11 @@ microdesc_free_all(void) tor_free(the_microdesc_cache->journal_fname); tor_free(the_microdesc_cache); } + + if (outdated_dirserver_list) { + SMARTLIST_FOREACH(outdated_dirserver_list, char *, cp, tor_free(cp)); + smartlist_free(outdated_dirserver_list); + } } /** If there is a microdescriptor in <b>cache</b> whose sha256 digest is @@ -804,18 +905,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 @@ -888,7 +977,7 @@ update_microdesc_downloads(time_t now) smartlist_free(missing); } -/** For every microdescriptor listed in the current microdecriptor consensus, +/** For every microdescriptor listed in the current microdescriptor consensus, * update its last_listed field to be at least as recent as the publication * time of the current microdescriptor consensus. */ @@ -917,20 +1006,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. */ - /* XXXX++ what does not being a server have to do with it? also there's - * a partitioning issue here where bridges differ from clients. */ - ret = !server_mode(options) && !options->FetchUselessDescriptors; - } - 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. */ diff --git a/src/or/microdesc.h b/src/or/microdesc.h index 40c83139e9..1be12156a4 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, @@ -52,5 +50,9 @@ 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); -#endif +void microdesc_note_outdated_dirserver(const char *relay_digest); +int microdesc_relay_is_outdated_dirserver(const char *relay_digest); +void microdesc_reset_outdated_dirservers_list(void); + +#endif /* !defined(TOR_MICRODESC_H) */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index d8e2c00273..3d99dd9eec 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" @@ -34,23 +62,19 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" #include "shared_random.h" #include "transports.h" #include "torcert.h" - -/** Map from lowercase nickname to identity digest of named server, if any. */ -static strmap_t *named_server_map = NULL; -/** Map from lowercase nickname to (void*)1 for all names that are listed - * as unnamed for some server in the consensus. */ -static strmap_t *unnamed_server_map = NULL; +#include "channelpadding.h" /** 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. */ @@ -114,7 +138,6 @@ static int have_warned_about_old_version = 0; * listed by the authorities. */ static int have_warned_about_new_version = 0; -static void routerstatus_list_update_named_server_map(void); static void update_consensus_bootstrap_multiple_downloads( time_t now, const or_options_t *options); @@ -152,60 +175,74 @@ 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, NULL) < -1) { - log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); + 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, NULL)) { - log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"", - flavor, filename); - } + log_info(LD_FS, "Couldn't load unverified consensus %s networkstatus " + "from cache", flavor); + } tor_free(s); } - tor_free(filename); - } - - if (!networkstatus_get_latest_consensus()) { - if (!named_server_map) - named_server_map = strmap_new(); - if (!unnamed_server_map) - unnamed_server_map = strmap_new(); } update_certificate_downloads(time(NULL)); @@ -217,7 +254,7 @@ 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; @@ -745,41 +782,6 @@ router_get_consensus_status_by_id(const char *digest) return router_get_mutable_consensus_status_by_id(digest); } -/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding routerstatus_t, or NULL if none exists. Warn the - * user if <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ -const routerstatus_t * -router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed) -{ - const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); - if (node) - return node->rs; - else - return NULL; -} - -/** Return the identity digest that's mapped to officially by - * <b>nickname</b>. */ -const char * -networkstatus_get_router_digest_by_nickname(const char *nickname) -{ - if (!named_server_map) - return NULL; - return strmap_get_lc(named_server_map, nickname); -} - -/** Return true iff <b>nickname</b> is disallowed from being the nickname - * of any server. */ -int -networkstatus_nickname_is_unnamed(const char *nickname) -{ - if (!unnamed_server_map) - return 0; - return strmap_get_lc(unnamed_server_map, nickname) != NULL; -} - /** How frequently do directory authorities re-download fresh networkstatus * documents? */ #define AUTHORITY_NS_CACHE_INTERVAL (10*60) @@ -789,8 +791,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) { @@ -812,6 +817,29 @@ 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) @@ -1182,7 +1210,9 @@ should_delay_dir_fetches(const or_options_t *options, const char **msg_out) } if (options->UseBridges) { - if (!any_bridge_descriptors_known()) { + /* If we know that none of our bridges can possibly work, avoid fetching + * directory documents. But if some of them might work, try again. */ + if (num_bridges_usable(1) == 0) { if (msg_out) { *msg_out = "No running bridges"; } @@ -1318,14 +1348,47 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) MOCK_IMPL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)) { - if (networkstatus_get_latest_consensus() && - networkstatus_get_latest_consensus()->valid_after <= now && - now <= networkstatus_get_latest_consensus()->valid_until) - return networkstatus_get_latest_consensus(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && networkstatus_is_live(ns, now)) + return ns; else return NULL; } +/** Given a consensus in <b>ns</b>, return true iff currently live and + * unexpired. */ +int +networkstatus_is_live(const networkstatus_t *ns, time_t now) +{ + return (ns->valid_after <= now && now <= ns->valid_until); +} + +/** Determine if <b>consensus</b> is valid or expired recently enough that + * we can still use it. + * + * 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. */ @@ -1334,12 +1397,11 @@ 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; @@ -1503,15 +1565,23 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, smartlist_free(changed); } -/* Called when the consensus has changed from old_c to new_c. */ +/* Called before the consensus changes from old_c to new_c. */ static void -notify_networkstatus_changed(const networkstatus_t *old_c, - const networkstatus_t *new_c) +notify_before_networkstatus_changes(const networkstatus_t *old_c, + const networkstatus_t *new_c) { notify_control_networkstatus_changed(old_c, new_c); dos_consensus_has_changed(new_c); } +/* Called after a new consensus has been put in the global state. It is safe + * to use the consensus getters in this function. */ +static void +notify_after_networkstatus_changes(void) +{ + scheduler_notify_networkstatus_changed(); +} + /** Copy all the ancillary information (like router download status and so on) * from <b>old_c</b> to <b>new_c</b>. */ static void @@ -1569,7 +1639,7 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c, } return current_md_consensus ? 0 : -1; } -#endif //TOR_UNIT_TESTS +#endif /* defined(TOR_UNIT_TESTS) */ /** * Return true if any option is set in <b>options</b> to make us behave @@ -1584,7 +1654,8 @@ any_client_port_set(const or_options_t *options) return (options->SocksPort_set || options->TransPort_set || options->NATDPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); } /** @@ -1691,7 +1762,7 @@ networkstatus_set_current_consensus(const char *consensus, if (from_cache && !was_waiting_for_certs) { /* We previously stored this; check _now_ to make sure that version-kills - * really work. This happens even before we check signatures: we did so + * really work. This happens even before we check signatures: we did so * before when we stored this to disk. This does mean an attacker who can * write to the datadir can make us not start: such an attacker could * already harm us by replacing our guards, which would be worse. */ @@ -1711,9 +1782,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; } @@ -1834,8 +1905,11 @@ networkstatus_set_current_consensus(const char *consensus, const int is_usable_flavor = flav == usable_consensus_flavor(); + /* Before we switch to the new consensus, notify that we are about to change + * it using the old consensus and the new one. */ if (is_usable_flavor) { - notify_networkstatus_changed(networkstatus_get_latest_consensus(), c); + notify_before_networkstatus_changes(networkstatus_get_latest_consensus(), + c); } if (flav == FLAV_NS) { if (current_ns_consensus) { @@ -1878,14 +1952,21 @@ networkstatus_set_current_consensus(const char *consensus, } if (is_usable_flavor) { + /* Notify that we just changed the consensus so the current global value + * can be looked at. */ + notify_after_networkstatus_changes(); + + /* The "current" consensus has just been set and it is a usable flavor so + * the first thing we need to do is recalculate the voting schedule static + * object so we can use the timings in there needed by some subsystems + * such as hidden service and shared random. */ + dirvote_recalculate_timing(options, now); + nodelist_set_consensus(c); /* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/ update_consensus_networkstatus_fetch_time(now); - dirvote_recalculate_timing(options, now); - routerstatus_list_update_named_server_map(); - /* Update ewma and adjust policy if needed; first cache the old value */ old_ewma_enabled = cell_ewma_enabled(); /* Change the cell EWMA settings */ @@ -1904,6 +1985,7 @@ networkstatus_set_current_consensus(const char *consensus, circuit_build_times_new_consensus_params( get_circuit_build_times_mutable(), c); + channelpadding_new_consensus_params(c); } /* Reset the failure count only if this consensus is actually valid. */ @@ -1914,11 +1996,15 @@ networkstatus_set_current_consensus(const char *consensus, download_status_failed(&consensus_dl_status[flav], 0); } - if (directory_caches_dir_info(options)) { + 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 (dir_server_mode(get_options())) { + consdiffmgr_add_consensus(consensus, c); + } } if (!from_cache) { @@ -1933,16 +2019,21 @@ networkstatus_set_current_consensus(const char *consensus, char tbuf[ISO_TIME_LEN+1]; char dbuf[64]; long delta = now - c->valid_after; + char *flavormsg = NULL; format_iso_time(tbuf, c->valid_after); format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Our clock is %s behind the time published in the " "consensus network status document (%s UTC). Tor needs an " "accurate clock to work correctly. Please check your time and " "date settings!", dbuf, tbuf); - control_event_general_status(LOG_WARN, - "CLOCK_SKEW MIN_SKEW=%ld SOURCE=CONSENSUS", delta); + tor_asprintf(&flavormsg, "%s flavor consensus", flavor); + clock_skew_warning(NULL, delta, 1, LD_GENERAL, flavormsg, "CONSENSUS"); + tor_free(flavormsg); } + /* We got a new consesus. Reset our md fetch fail cache */ + microdesc_reset_outdated_dirservers_list(); + router_dir_info_changed(); result = 0; @@ -2048,31 +2139,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) } } -/** Update our view of the list of named servers from the most recently - * retrieved networkstatus consensus. */ -static void -routerstatus_list_update_named_server_map(void) -{ - networkstatus_t *ns = networkstatus_get_latest_consensus(); - if (!ns) - return; - - strmap_free(named_server_map, tor_free_); - named_server_map = strmap_new(); - strmap_free(unnamed_server_map, NULL); - unnamed_server_map = strmap_new(); - smartlist_t *rslist = ns->routerstatus_list; - SMARTLIST_FOREACH_BEGIN(rslist, const routerstatus_t *, rs) { - if (rs->is_named) { - strmap_set_lc(named_server_map, rs->nickname, - tor_memdup(rs->identity_digest, DIGEST_LEN)); - } - if (rs->is_unnamed) { - strmap_set_lc(unnamed_server_map, rs->nickname, (void*)1); - } - } SMARTLIST_FOREACH_END(rs); -} - /** Given a list <b>routers</b> of routerinfo_t *, update each status field * according to our current consensus networkstatus. May re-order * <b>routers</b>. */ @@ -2210,13 +2276,23 @@ networkstatus_dump_bridge_status_to_file(time_t now) char *thresholds = NULL; char *published_thresholds_and_status = NULL; 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(); 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); @@ -2224,6 +2300,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 */ @@ -2270,9 +2347,9 @@ get_net_param_from_list(smartlist_t *net_params, const char *param_name, * Make sure the value parsed from the consensus is at least * <b>min_val</b> and at most <b>max_val</b> and raise/cap the parsed value * if necessary. */ -int32_t -networkstatus_get_param(const networkstatus_t *ns, const char *param_name, - int32_t default_val, int32_t min_val, int32_t max_val) +MOCK_IMPL(int32_t, +networkstatus_get_param, (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)) { if (!ns) /* if they pass in null, go find it ourselves */ ns = networkstatus_get_latest_consensus(); @@ -2285,6 +2362,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 @@ -2360,12 +2456,11 @@ 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. */ + * (Fetching and storing depends on by we_want_to_fetch_flavor().) */ return 0; } if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) { @@ -2421,7 +2516,8 @@ getinfo_helper_networkstatus(control_connection_t *conn, } status = router_get_consensus_status_by_id(d); } else if (!strcmpstart(question, "ns/name/")) { - status = router_get_consensus_status_by_nickname(question+8, 0); + const node_t *n = node_get_by_nickname(question+8, 0); + status = n ? n->rs : NULL; } else if (!strcmpstart(question, "ns/purpose/")) { *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL)); return *answer ? 0 : -1; @@ -2528,8 +2624,5 @@ networkstatus_free_all(void) } tor_free(waiting->body); } - - strmap_free(named_server_map, tor_free_); - strmap_free(unnamed_server_map, NULL); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 71f36b69ed..39a0f753d8 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); @@ -61,11 +62,8 @@ const routerstatus_t *router_get_consensus_status_by_descriptor_digest( MOCK_DECL(routerstatus_t *, router_get_mutable_consensus_status_by_descriptor_digest, (networkstatus_t *consensus, const char *digest)); -const routerstatus_t *router_get_consensus_status_by_nickname( - const char *nickname, - int warn_if_unnamed); -const char *networkstatus_get_router_digest_by_nickname(const char *nickname); -int networkstatus_nickname_is_unnamed(const char *nickname); +int we_want_to_fetch_flavor(const or_options_t *options, int flavor); +int we_want_to_fetch_unknown_auth_certs(const or_options_t *options); void networkstatus_consensus_download_failed(int status_code, const char *flavname); void update_consensus_networkstatus_fetch_time(time_t now); @@ -73,12 +71,16 @@ 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); +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)); 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); MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now)); @@ -107,10 +109,14 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); -int32_t networkstatus_get_param(const networkstatus_t *ns, - const char *param_name, - int32_t default_val, int32_t min_val, - int32_t max_val); +MOCK_DECL(int32_t, networkstatus_get_param, + (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)); +int32_t networkstatus_get_overridable_param(const networkstatus_t *ns, + int32_t torrc_value, + const char *param_name, + 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); @@ -123,13 +129,16 @@ 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 -#endif +extern networkstatus_t *current_ns_consensus; +extern networkstatus_t *current_md_consensus; +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(NETWORKSTATUS_PRIVATE) */ -#endif +#endif /* !defined(TOR_NETWORKSTATUS_H) */ diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 26f990b08c..ac94498558 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,24 +10,58 @@ * \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.) */ +#define NODELIST_PRIVATE + #include "or.h" #include "address.h" #include "address_set.h" #include "config.h" #include "control.h" #include "dirserv.h" +#include "entrynodes.h" #include "geoip.h" +#include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" #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> @@ -46,7 +80,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); @@ -64,7 +97,14 @@ typedef struct nodelist_t { smartlist_t *nodes; /* Hash table to map from node ID digest to node. */ HT_HEAD(nodelist_map, node_t) nodes_by_id; - + /* Hash table to map from node Ed25519 ID to node. + * + * Whenever a node's routerinfo or microdescriptor is about to change, + * you should remove it from this map with node_remove_from_ed25519_map(). + * Whenever a node's routerinfo or microdescriptor has just chaned, + * you should add it to this map with node_add_to_ed25519_map(). + */ + HT_HEAD(nodelist_ed_map, node_t) nodes_by_ed_id; /* Set of addresses that belong to nodes we believe in. */ address_set_t *node_addrs; } nodelist_t; @@ -85,6 +125,23 @@ HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq) HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, 0.6, tor_reallocarray_, tor_free_) +static inline unsigned int +node_ed_id_hash(const node_t *node) +{ + return (unsigned) siphash24g(node->ed25519_id.pubkey, ED25519_PUBKEY_LEN); +} + +static inline unsigned int +node_ed_id_eq(const node_t *node1, const node_t *node2) +{ + return ed25519_pubkey_eq(&node1->ed25519_id, &node2->ed25519_id); +} + +HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq) +HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_) + /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -95,6 +152,7 @@ init_nodelist(void) if (PREDICT_UNLIKELY(the_nodelist == NULL)) { the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + HT_INIT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); the_nodelist->nodes = smartlist_new(); } } @@ -112,6 +170,21 @@ node_get_mutable_by_id(const char *identity_digest) return node; } +/** As node_get_by_ed25519_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + if (BUG(ed_id == NULL) || BUG(ed25519_public_key_is_zero(ed_id))) + return NULL; + + memcpy(&search.ed25519_id, ed_id, sizeof(search.ed25519_id)); + node = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, &search); + return node; +} + /** Return the node_t whose identity is <b>identity_digest</b>, or NULL * if no such node exists. */ MOCK_IMPL(const node_t *, @@ -120,6 +193,14 @@ node_get_by_id,(const char *identity_digest)) return node_get_mutable_by_id(identity_digest); } +/** Return the node_t whose ed25519 identity is <b>ed_id</b>, or NULL + * if no such node exists. */ +MOCK_IMPL(const node_t *, +node_get_by_ed25519_id,(const ed25519_public_key_t *ed_id)) +{ + return node_get_mutable_by_ed25519_id(ed_id); +} + /** Internal: return the node_t whose identity_digest is * <b>identity_digest</b>. If none exists, create a new one, add it to the * nodelist, and return it. @@ -140,12 +221,181 @@ node_get_or_create(const char *identity_digest) smartlist_add(the_nodelist->nodes, node); node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); node->country = -1; return node; } +/** Remove <b>node</b> from the ed25519 map (if it present), and + * set its ed25519_id field to zero. */ +static int +node_remove_from_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + int rv = 0; + node_t *search = + HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(search != node)) { + goto clear_and_return; + } + + search = HT_REMOVE(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + tor_assert(search == node); + rv = 1; + + clear_and_return: + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return rv; +} + +/** If <b>node</b> has an ed25519 id, and it is not already in the ed25519 id + * map, set its ed25519_id field, and add it to the ed25519 map. + */ +static int +node_add_to_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (! ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + const ed25519_public_key_t *key = node_get_ed25519_id(node); + if (!key) { + return 0; + } + + node_t *old; + memcpy(&node->ed25519_id, key, sizeof(node->ed25519_id)); + old = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(old)) { + /* XXXX order matters here, and this may mean that authorities aren't + * pinning. */ + if (old != node) + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return 0; + } + + HT_INSERT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + return 1; +} + +/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index + * for the node, both current and next if possible. This can only fails if the + * node_t ed25519 identity key can't be found which would be a bug. */ +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns) +{ + time_t now = approx_time(); + const ed25519_public_key_t *node_identity_pk; + uint8_t *fetch_srv = NULL, *store_first_srv = NULL, *store_second_srv = NULL; + uint64_t next_time_period_num, current_time_period_num; + uint64_t fetch_tp, store_first_tp, store_second_tp; + + tor_assert(node); + tor_assert(ns); + + if (!networkstatus_is_live(ns, now)) { + static struct ratelim_t live_consensus_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&live_consensus_ratelim, LOG_INFO, LD_GENERAL, + "Not setting hsdir index with a non-live consensus."); + goto done; + } + + node_identity_pk = node_get_ed25519_id(node); + if (node_identity_pk == NULL) { + log_debug(LD_GENERAL, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); + goto done; + } + + /* Get the current and next time period number. */ + current_time_period_num = hs_get_time_period_num(0); + next_time_period_num = hs_get_next_time_period_num(0); + + /* We always use the current time period for fetching descs */ + fetch_tp = current_time_period_num; + + /* Now extract the needed SRVs and time periods for building hsdir indices */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + fetch_srv = hs_get_current_srv(fetch_tp, ns); + + store_first_tp = hs_get_previous_time_period_num(0); + store_second_tp = current_time_period_num; + } else { + fetch_srv = hs_get_previous_srv(fetch_tp, ns); + + store_first_tp = current_time_period_num; + store_second_tp = next_time_period_num; + } + + /* We always use the old SRV for storing the first descriptor and the latest + * SRV for storing the second descriptor */ + store_first_srv = hs_get_previous_srv(store_first_tp, ns); + store_second_srv = hs_get_current_srv(store_second_tp, ns); + + /* Build the fetch index. */ + hs_build_hsdir_index(node_identity_pk, fetch_srv, fetch_tp, + node->hsdir_index->fetch); + + /* If we are in the time segment between SRV#N and TP#N, the fetch index is + the same as the first store index */ + if (!hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_first, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_first)); + } else { + hs_build_hsdir_index(node_identity_pk, store_first_srv, store_first_tp, + node->hsdir_index->store_first); + } + + /* If we are in the time segment between TP#N and SRV#N+1, the fetch index is + the same as the second store index */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_second, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_second)); + } else { + hs_build_hsdir_index(node_identity_pk, store_second_srv, store_second_tp, + node->hsdir_index->store_second); + } + + done: + tor_free(fetch_srv); + tor_free(store_first_srv); + tor_free(store_second_srv); + return; +} + +/** Recompute all node hsdir indices. */ +void +nodelist_recompute_all_hsdir_indices(void) +{ + networkstatus_t *consensus; + if (!the_nodelist) { + return; + } + + /* Get a live consensus. Abort if not found */ + consensus = networkstatus_get_live_consensus(approx_time()); + if (!consensus) { + return; + } + + /* Recompute all hsdir indices */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node_set_hsdir_index(node, consensus); + } SMARTLIST_FOREACH_END(node); +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -214,6 +464,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) id_digest = ri->cache_info.identity_digest; node = node_get_or_create(id_digest); + node_remove_from_ed25519_map(node); + if (node->ri) { if (!routers_have_same_or_addrs(node->ri, ri)) { node_addrs_changed(node); @@ -227,6 +479,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) } node->ri = ri; + node_add_to_ed25519_map(node); + if (node->country == -1) node_set_country(node); @@ -236,6 +490,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()); + } + node_add_to_address_set(node); return node; @@ -265,10 +527,20 @@ nodelist_add_microdesc(microdesc_t *md) node = node_get_mutable_by_id(rs->identity_digest); if (node == NULL) return NULL; + + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; + node->md = md; md->held_by_nodes++; + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } + node_add_to_ed25519_map(node); node_add_to_address_set(node); return node; @@ -315,15 +587,20 @@ nodelist_set_consensus(networkstatus_t *ns) if (ns->flavor == FLAV_MICRODESC) { if (node->md == NULL || tor_memneq(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; node->md = microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); if (node->md) node->md->held_by_nodes++; + node_add_to_ed25519_map(node); } } + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } node_set_country(node); /* If we're not an authdir, believe others. */ @@ -387,6 +664,9 @@ nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) if (node && node->md == md) { node->md = NULL; md->held_by_nodes--; + if (! node_get_ed25519_id(node)) { + node_remove_from_ed25519_map(node); + } } } @@ -415,6 +695,7 @@ nodelist_drop_node(node_t *node, int remove_from_ht) tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); tor_assert(tmp == node); } + node_remove_from_ed25519_map(node); idx = node->nodelist_idx; tor_assert(idx >= 0); @@ -456,6 +737,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); } @@ -497,6 +779,7 @@ nodelist_free_all(void) return; HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + HT_CLEAR(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { node->nodelist_idx = -1; node_free(node); @@ -564,9 +847,27 @@ nodelist_assert_ok(void) tor_assert(node_sl_idx == node->nodelist_idx); } SMARTLIST_FOREACH_END(node); + /* Every node listed with an ed25519 identity should be listed by that + * identity. + */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!ed25519_public_key_is_zero(&node->ed25519_id)) { + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + } SMARTLIST_FOREACH_END(node); + + node_t **idx; + HT_FOREACH(idx, nodelist_ed_map, &the_nodelist->nodes_by_ed_id) { + node_t *node = *idx; + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + tor_assert((long)smartlist_len(the_nodelist->nodes) == (long)HT_SIZE(&the_nodelist->nodes_by_id)); + tor_assert((long)smartlist_len(the_nodelist->nodes) >= + (long)HT_SIZE(&the_nodelist->nodes_by_ed_id)); + digestmap_free(dm, NULL); } @@ -583,28 +884,23 @@ nodelist_get_list,(void)) /** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, * or $DIGEST~name, return the node with the matching identity digest and * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> - * is not well-formed. */ + * is not well-formed. DOCDOC flags */ const node_t * -node_get_by_hex_id(const char *hex_id) +node_get_by_hex_id(const char *hex_id, unsigned flags) { char digest_buf[DIGEST_LEN]; char nn_buf[MAX_NICKNAME_LEN+1]; char nn_char='\0'; + (void) flags; // XXXX + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { const node_t *node = node_get_by_id(digest_buf); if (!node) return NULL; - if (nn_char) { - const char *real_name = node_get_nickname(node); - if (!real_name || strcasecmp(real_name, nn_buf)) - return NULL; - if (nn_char == '=') { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nn_buf); - if (!named_id || tor_memneq(named_id, digest_buf, DIGEST_LEN)) - return NULL; - } + if (nn_char == '=') { + /* "=" indicates a Named relay, but there aren't any of those now. */ + return NULL; } return node; } @@ -613,42 +909,27 @@ node_get_by_hex_id(const char *hex_id) } /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding node_t, or NULL if none exists. Warn the user if - * <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ + * the corresponding node_t, or NULL if none exists. Warn the user if they + * have specified a router by nickname, unless the NNF_NO_WARN_UNNAMED bit is + * set in <b>flags</b>. */ MOCK_IMPL(const node_t *, -node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) +node_get_by_nickname,(const char *nickname, unsigned flags)) { + const int warn_if_unnamed = !(flags & NNF_NO_WARN_UNNAMED); + if (!the_nodelist) return NULL; /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ { const node_t *node; - if ((node = node_get_by_hex_id(nickname)) != NULL) + if ((node = node_get_by_hex_id(nickname, flags)) != NULL) return node; } if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) return NULL; - /* Okay, so if we get here, the nickname is just a nickname. Is there - * a binding for it in the consensus? */ - { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nickname); - if (named_id) - return node_get_by_id(named_id); - } - - /* Is it marked as owned-by-someone-else? */ - if (networkstatus_nickname_is_unnamed(nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " - "router that holds it, but not one listed in the current " - "consensus.", escaped(nickname)); - return NULL; - } - /* Okay, so the name is not canonical for anybody. */ { smartlist_t *matches = smartlist_new(); @@ -679,11 +960,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) if (! node->name_lookup_warned) { base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); log_warn(LD_CONFIG, - "You specified a server \"%s\" by name, but the directory " - "authorities do not have any key registered for this " - "nickname -- so it could be used by any server, not just " - "the one you meant. " - "To make sure you get the same server in the future, refer " + "You specified a relay \"%s\" by name, but nicknames can be " + "used by any relay, not just the one you meant. " + "To make sure you get the same relay in the future, refer " "to it by key, as \"$%s\".", nickname, fp); node->name_lookup_warned = 1; } @@ -697,6 +976,161 @@ 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) +{ + const ed25519_public_key_t *ri_pk = NULL; + const ed25519_public_key_t *md_pk = NULL; + if (node->ri) { + if (node->ri->cache_info.signing_key_cert) { + ri_pk = &node->ri->cache_info.signing_key_cert->signing_key; + if (BUG(ed25519_public_key_is_zero(ri_pk))) + ri_pk = NULL; + } + } + + if (node->md) { + if (node->md->ed25519_identity_pkey) { + md_pk = node->md->ed25519_identity_pkey; + } + } + + if (ri_pk && md_pk) { + if (ed25519_pubkey_eq(ri_pk, md_pk)) { + return ri_pk; + } else { + /* This can happen if the relay gets flagged NoEdConsensus which will be + * triggered on all relays of the network. Thus a protocol warning. */ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Inconsistent ed25519 identities in the nodelist"); + return NULL; + } + } else if (ri_pk) { + return ri_pk; + } else { + return md_pk; + } +} + +/** Return true iff this node's Ed25519 identity matches <b>id</b>. + * (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 true iff <b>node</b> supports to be a rendezvous point for hidden + * service version 3 (HSRend=2). */ +int +node_supports_v3_rendezvous_point(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_rendezvous_point; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return the RSA ID key's SHA1 digest for the provided node. */ +const uint8_t * +node_get_rsa_id_digest(const node_t *node) +{ + 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) @@ -710,21 +1144,6 @@ node_get_nickname(const node_t *node) return NULL; } -/** Return true iff the nickname of <b>node</b> is canonical, based on the - * latest consensus. */ -int -node_is_named(const node_t *node) -{ - const char *named_id; - const char *nickname = node_get_nickname(node); - if (!nickname) - return 0; - named_id = networkstatus_get_router_digest_by_nickname(nickname); - if (!named_id) - return 0; - return tor_memeq(named_id, node->identity, DIGEST_LEN); -} - /** Return true iff <b>node</b> appears to be a directory authority or * directory cache */ int @@ -772,13 +1191,12 @@ node_get_verbose_nickname(const node_t *node, char *verbose_name_out) { const char *nickname = node_get_nickname(node); - int is_named = node_is_named(node); verbose_name_out[0] = '$'; base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, DIGEST_LEN); if (!nickname) return; - verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + verbose_name_out[1+HEX_DIGEST_LEN] = '~'; strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); } @@ -992,16 +1410,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) @@ -1146,9 +1554,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)) { @@ -1208,6 +1618,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 */ @@ -1240,8 +1653,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, @@ -1321,7 +1737,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) { @@ -1340,8 +1756,7 @@ node_nickname_matches(const node_t *node, const char *nickname) return 1; return hex_digest_nickname_matches(nickname, node->identity, - n, - node_is_named(node)); + n); } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ @@ -1443,7 +1858,7 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { const node_t *node2; const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, 0))) + if (!(node2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED))) continue; if (!(family2 = node_get_declared_family(node2))) continue; @@ -1595,8 +2010,8 @@ static char dir_info_status[512] = ""; * no exits in the consensus." * To obtain the final weighted bandwidth, we multiply the * weighted bandwidth fraction for each position (guard, middle, exit). */ -int -router_have_minimum_dir_info(void) +MOCK_IMPL(int, +router_have_minimum_dir_info,(void)) { static int logged_delay=0; const char *delay_fetches_msg = NULL; @@ -1628,8 +2043,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; } @@ -1643,6 +2058,8 @@ router_dir_info_changed(void) { need_to_update_have_min_dir_info = 1; rend_hsdir_routers_changed(); + hs_service_dir_info_changed(); + hs_client_dir_info_changed(); } /** Return a string describing what we're missing before we have enough @@ -1668,7 +2085,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) { @@ -1685,7 +2102,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. */ @@ -1718,9 +2135,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, @@ -1739,10 +2156,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, { @@ -1763,7 +2180,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", @@ -1812,7 +2229,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", @@ -1823,7 +2240,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", @@ -1952,6 +2369,7 @@ update_router_have_minimum_dir_info(void) { time_t now = time(NULL); int res; + int num_present=0, num_usable=0; const or_options_t *options = get_options(); const networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); @@ -1973,7 +2391,6 @@ update_router_have_minimum_dir_info(void) /* Check fraction of available paths */ { char *status = NULL; - int num_present=0, num_usable=0; double paths = compute_frac_paths_available(consensus, options, now, &num_present, &num_usable, &status); @@ -1994,6 +2411,18 @@ update_router_have_minimum_dir_info(void) res = 1; } + { /* Check entry guard dirinfo status */ + char *guard_error = entry_guards_get_err_str_if_dir_info_missing(using_md, + num_present, + num_usable); + if (guard_error) { + strlcpy(dir_info_status, guard_error, sizeof(dir_info_status)); + tor_free(guard_error); + res = 0; + goto done; + } + } + done: /* If paths have just become available in this update. */ diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 098f1d1555..8a0c79f86d 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 */ /** @@ -18,7 +18,14 @@ node_t *node_get_mutable_by_id(const char *identity_digest); MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); -const node_t *node_get_by_hex_id(const char *identity_digest); +node_t *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); +MOCK_DECL(const node_t *, node_get_by_ed25519_id, + (const ed25519_public_key_t *ed_id)); + +#define NNF_NO_WARN_UNNAMED (1u<<0) + +const node_t *node_get_by_hex_id(const char *identity_digest, + unsigned flags); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(networkstatus_t *ns); @@ -29,16 +36,17 @@ void nodelist_remove_routerinfo(routerinfo_t *ri); void nodelist_purge(void); smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); +void nodelist_recompute_all_hsdir_indices(void); + void nodelist_free_all(void); void nodelist_assert_ok(void); MOCK_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unnamed)); + (const char *nickname, unsigned flags)); void node_get_verbose_nickname(const node_t *node, char *verbose_name_out); void node_get_verbose_nickname_by_id(const char *id_digest, char *verbose_name_out); -int node_is_named(const node_t *node); int node_is_dir(const node_t *node); int node_has_descriptor(const node_t *node); int node_get_purpose(const node_t *node); @@ -54,8 +62,15 @@ 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); +int node_supports_v3_rendezvous_point(const node_t *node); +const uint8_t *node_get_rsa_id_digest(const node_t *node); int node_has_ipv6_addr(const node_t *node); int node_has_ipv6_orport(const node_t *node); @@ -90,6 +105,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. @@ -98,7 +115,7 @@ void router_set_status(const char *digest, int up); * no exits in the consensus, we wait for enough info to create internal * paths, and should avoid creating exit paths, as they will simply fail. * We make sure we create all available circuit types at the same time. */ -int router_have_minimum_dir_info(void); +MOCK_DECL(int, router_have_minimum_dir_info,(void)); /** Set to CONSENSUS_PATH_EXIT if there is at least one exit node * in the consensus. We update this flag in compute_frac_paths_available if @@ -119,13 +136,25 @@ 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); int count_loading_descriptors_progress(void); +#ifdef NODELIST_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(NODELIST_PRIVATE) */ + MOCK_DECL(int, get_estimated_address_per_node, (void)); -#endif +#endif /* !defined(TOR_NODELIST_H) */ diff --git a/src/or/ntmain.c b/src/or/ntmain.c index 0e6f296d24..508e5844eb 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.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 */ /** @@ -329,9 +329,10 @@ nt_service_main(void) case CMD_VERIFY_CONFIG: case CMD_DUMP_CONFIG: case CMD_KEYGEN: + case CMD_KEY_EXPIRATION: log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, " - "--hash-password, --keygen, --dump-config, or --verify-config) " - "in NT service."); + "--hash-password, --keygen, --dump-config, --verify-config, " + "or --key-expiration) in NT service."); break; case CMD_RUN_UNITTESTS: default: @@ -501,7 +502,7 @@ nt_service_command_line(int *using_default_torrc) tor_exe_ascii[sizeof(tor_exe_ascii)-1] = '\0'; #else strlcpy(tor_exe_ascii, tor_exe, sizeof(tor_exe_ascii)); -#endif +#endif /* defined(UNICODE) */ /* Allocate a string for the NT service command line and */ /* Format the service command */ @@ -777,5 +778,5 @@ nt_service_parse_options(int argc, char **argv, int *should_exit) return 0; } -#endif +#endif /* defined(_WIN32) */ diff --git a/src/or/ntmain.h b/src/or/ntmain.h index 31bf38c62c..81b7159855 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 */ /** @@ -22,7 +22,7 @@ int nt_service_is_stopping(void); void nt_service_set_state(DWORD state); #else #define nt_service_is_stopping() 0 -#endif +#endif /* defined(NT_SERVICE) */ -#endif +#endif /* !defined(TOR_NTMAIN_H) */ diff --git a/src/or/onion.c b/src/or/onion.c index 4b803a785c..7e1e89df1b 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -76,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 { @@ -873,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 @@ -888,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; } @@ -994,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; @@ -1015,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; @@ -1131,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 @@ -1139,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); @@ -1154,7 +1219,7 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out, *command_out = RELAY_COMMAND_EXTEND; *len_out = 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN; set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr)); - set_uint16(p+4, ntohs(cell_in->orport_ipv4.port)); + set_uint16(p+4, htons(cell_in->orport_ipv4.port)); if (cell_in->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_NTOR) { memcpy(p+6, NTOR_CREATE_MAGIC, 16); memcpy(p+22, cell_in->create_cell.onionskin, NTOR_ONIONSKIN_LEN); @@ -1167,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..95544dfac1 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. */ @@ -117,5 +119,5 @@ int extend_cell_format(uint8_t *command_out, uint16_t *len_out, int extended_cell_format(uint8_t *command_out, uint16_t *len_out, uint8_t *payload_out, const extended_cell_t *cell_in); -#endif +#endif /* !defined(TOR_ONION_H) */ diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c index 8dcbfe22d8..146943a273 100644 --- a/src/or/onion_fast.c +++ b/src/or/onion_fast.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h index b9626002c3..3a5aefea3f 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 */ /** @@ -35,5 +35,5 @@ int fast_client_handshake(const fast_handshake_state_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_FAST_H) */ diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c index ded97ee73d..902260b54b 100644 --- a/src/or/onion_ntor.c +++ b/src/or/onion_ntor.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 */ /** diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h index f637b437fd..02dea2dfc1 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 @@ -55,7 +55,7 @@ struct ntor_handshake_state_t { curve25519_public_key_t pubkey_X; /** @} */ }; -#endif +#endif /* defined(ONION_NTOR_PRIVATE) */ -#endif +#endif /* !defined(TOR_ONION_NTOR_H) */ diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 2769300945..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 */ /** @@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key, if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; - note_crypto_pk_op(ENC_ONIONSKIN); - /* set meeting point, meeting cookie, etc here. Leave zero for now. */ - if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, + if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) @@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake( k = i==0?private_key:prev_private_key; if (!k) break; - note_crypto_pk_op(DEC_ONIONSKIN); - len = crypto_pk_private_hybrid_decrypt(k, challenge, + len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge, TAP_ONIONSKIN_CHALLENGE_LEN, onion_skin, TAP_ONIONSKIN_CHALLENGE_LEN, @@ -159,7 +156,7 @@ onion_skin_TAP_server_handshake( * big. That should be impossible. */ log_info(LD_GENERAL, "crypto_dh_get_public failed."); goto err; - /* LCOV_EXCP_STOP */ + /* 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..713c1d7391 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 */ /** @@ -34,5 +34,5 @@ int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_TAP_H) */ diff --git a/src/or/or.h b/src/or/or.h index 024a9cff0f..5128fd2197 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 */ /** @@ -64,22 +64,24 @@ #include <process.h> #include <direct.h> #include <windows.h> -#endif +#endif /* defined(_WIN32) */ #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. */ @@ -114,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 @@ -143,8 +148,27 @@ /** Maximum size of a single extrainfo document, as above. */ #define MAX_EXTRAINFO_UPLOAD_SIZE 50000 -/** 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) @@ -202,8 +226,10 @@ typedef enum { #define CONN_TYPE_EXT_OR 16 /** Type for sockets listening for Extended ORPort connections. */ #define CONN_TYPE_EXT_OR_LISTENER 17 +/** Type for sockets listening for HTTP CONNECT tunnel connections. */ +#define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18 -#define CONN_TYPE_MAX_ 17 +#define CONN_TYPE_MAX_ 19 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ @@ -324,7 +350,9 @@ typedef enum { /** State for a transparent natd connection: waiting for original * destination. */ #define AP_CONN_STATE_NATD_WAIT 12 -#define AP_CONN_STATE_MAX_ 12 +/** State for an HTTP tunnel: waiting for an HTTP CONNECT command. */ +#define AP_CONN_STATE_HTTP_CONNECT_WAIT 13 +#define AP_CONN_STATE_MAX_ 13 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding * edge connection is not attached to any circuit. */ @@ -397,14 +425,23 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define DIR_PURPOSE_MAX_ 19 - -/** 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 +/** A connection to a directory server: set after a hidden service descriptor + * is downloaded. */ +#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22 +#define DIR_PURPOSE_MAX_ 22 + +/** True iff <b>p</b> is a purpose corresponding to uploading + * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES) + (p)==DIR_PURPOSE_UPLOAD_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. */ @@ -423,8 +460,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 @@ -611,6 +652,10 @@ typedef enum { /** The target address is in a private network (like 127.0.0.1 or 10.0.0.1); * you don't want to do that over a randomly chosen exit */ #define END_STREAM_REASON_PRIVATE_ADDR 262 +/** This is an HTTP tunnel connection and the client used or misused HTTP in a + * way we can't handle. + */ +#define END_STREAM_REASON_HTTPPROTOCOL 263 /** Bitwise-and this value with endreason to mask out all flags. */ #define END_STREAM_REASON_MASK 511 @@ -702,15 +747,15 @@ typedef enum { #define REND_NUMBER_OF_CONSECUTIVE_REPLICAS 3 /** Length of v2 descriptor ID (32 base32 chars = 160 bits). */ -#define REND_DESC_ID_V2_LEN_BASE32 32 +#define REND_DESC_ID_V2_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded secret ID part of versioned hidden service * descriptors. */ -#define REND_SECRET_ID_PART_LEN_BASE32 32 +#define REND_SECRET_ID_PART_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded hash of an introduction point's * identity key. */ -#define REND_INTRO_POINT_ID_LEN_BASE32 32 +#define REND_INTRO_POINT_ID_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the descriptor cookie that is used for client authorization * to hidden services. */ @@ -767,6 +812,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]; @@ -788,17 +851,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]; - - /** List of HSDir fingerprints on which this request has been sent to. - * This contains binary identity digest of the directory. */ - smartlist_t *hsdirs_fp; +/* 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); +} - /** 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 @@ -850,6 +919,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 @@ -1132,11 +1202,8 @@ typedef struct { uint16_t length; /**< How long is the payload body? */ } relay_header_t; -typedef struct buf_t buf_t; typedef struct socks_request_t socks_request_t; -#define buf_t buf_t - typedef struct entry_port_cfg_t { /* Client port types (socks, dns, trans, natd) only: */ uint8_t isolation_flags; /**< Zero or more isolation flags */ @@ -1196,6 +1263,8 @@ typedef struct server_port_cfg_t { #define CONTROL_CONNECTION_MAGIC 0x8abc765du #define LISTENER_CONNECTION_MAGIC 0x1a1ac741u +struct buf_t; + /** Description of a connection to another host or process, and associated * data. * @@ -1267,8 +1336,9 @@ typedef struct connection_t { struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ - buf_t *inbuf; /**< Buffer holding data read over this connection. */ - buf_t *outbuf; /**< Buffer holding data to write over this connection. */ + struct buf_t *inbuf; /**< Buffer holding data read over this connection. */ + struct buf_t *outbuf; /**< Buffer holding data to write over this + * connection. */ size_t outbuf_flushlen; /**< How much data should we try to flush from the * outbuf? */ time_t timestamp_lastread; /**< When was the last time libevent said we could @@ -1351,13 +1421,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_SHA256_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 @@ -1368,6 +1455,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. */ @@ -1388,10 +1503,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. * @@ -1405,9 +1528,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. @@ -1420,14 +1546,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. */ @@ -1489,10 +1609,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 */ @@ -1524,8 +1640,6 @@ typedef struct or_connection_t { * bandwidthburst. (OPEN ORs only) */ int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */ - 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; @@ -1561,6 +1675,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 @@ -1603,6 +1722,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 @@ -1628,11 +1749,11 @@ typedef struct entry_connection_t { /** For AP connections only: buffer for data that we have sent * optimistically, which we might need to re-send if we have to * retry this connection. */ - buf_t *pending_optimistic_data; + struct buf_t *pending_optimistic_data; /* For AP connections only: buffer for data that we previously sent * optimistically which we are currently re-sending as we retry this * connection. */ - buf_t *sending_optimistic_data; + struct buf_t *sending_optimistic_data; /** If this is a DNSPort connection, this field holds the pending DNS * request that we're going to try to answer. */ @@ -1682,14 +1803,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 { @@ -1698,33 +1811,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. */ @@ -1732,6 +1846,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 /* defined(MEASUREMENTS_21206) */ } dir_connection_t; /** Subtype of connection_t for an connection to a controller. */ @@ -1768,8 +1890,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_)) @@ -1872,11 +1992,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; @@ -1988,7 +2110,9 @@ typedef struct download_status_t { * or after each failure? */ download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the * deterministic schedule, or random - * exponential backoffs? */ + * exponential backoffs? + * Increment on failure schedules + * always use exponential backoff. */ uint8_t last_backoff_position; /**< number of attempts/failures, depending * on increment_on, when we last recalculated * the delay. Only updated if backoff @@ -2210,6 +2334,25 @@ typedef struct routerstatus_t { * accept EXTEND2 cells */ unsigned int 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; + + /** True iff this router has a protocol list that allows it to be an hidden + * service rendezvous point supporting version 3 as seen in proposal 224. + * This requires HSRend=2. */ + unsigned int supports_v3_rendezvous_point: 1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2335,6 +2478,8 @@ typedef struct node_t { /** Used to look up the node_t by its identity digest. */ HT_ENTRY(node_t) ht_ent; + /** Used to look up the node_t by its ed25519 identity digest. */ + HT_ENTRY(node_t) ed_ht_ent; /** Position of the node within the list of nodes */ int nodelist_idx; @@ -2342,6 +2487,13 @@ typedef struct node_t { * identity may exist at a time. */ char identity[DIGEST_LEN]; + /** The ed25519 identity of this node_t. This field is nonzero iff we + * currently have an ed25519 identity for this node in either md or ri, + * _and_ this node has been inserted to the ed25519-to-node map in the + * nodelist. + */ + ed25519_public_key_t ed25519_id; + microdesc_t *md; routerinfo_t *ri; routerstatus_t *rs; @@ -2372,9 +2524,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. @@ -2392,6 +2541,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 @@ -2566,6 +2719,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 @@ -2654,7 +2810,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. */ @@ -2996,6 +3155,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 @@ -3092,6 +3258,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; @@ -3235,6 +3414,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; @@ -3296,8 +3482,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 */ @@ -3308,9 +3492,6 @@ typedef struct or_circuit_t { /* We have already received an INTRODUCE1 cell on this circuit. */ unsigned int already_received_introduce1 : 1; - /** True iff this circuit was made with a CREATE_FAST cell. */ - unsigned int is_first_hop : 1; - /** If set, this circuit carries HS traffic. Consider it in any HS * statistics. */ unsigned int circuit_carries_hs_traffic_stats : 1; @@ -3331,25 +3512,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_)) @@ -3392,15 +3559,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 */ @@ -3462,33 +3620,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_; @@ -3497,7 +3640,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. */ @@ -3541,10 +3685,6 @@ 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 reserved private * addresses, and our own published addresses? @@ -3555,27 +3695,16 @@ typedef struct { * 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; @@ -3587,7 +3716,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; @@ -3595,8 +3723,8 @@ typedef struct { config_line_t *SocksPort_lines; /** Ports to listen on for transparent pf/netfilter connections. */ config_line_t *TransPort_lines; - const char *TransProxyType; /**< What kind of transparent proxy - * implementation are we using? */ + char *TransProxyType; /**< What kind of transparent proxy + * implementation are we using? */ /** Parsed value of TransProxyType. */ enum { TPT_DEFAULT, @@ -3606,6 +3734,8 @@ typedef struct { } TransProxyType_parsed; config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd * connections. */ + /** Ports to listen on for HTTP Tunnel connections. */ + config_line_t *HTTPTunnelPort_lines; config_line_t *ControlPort_lines; /**< Ports to listen on for control * connections. */ config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on @@ -3632,7 +3762,8 @@ typedef struct { * configured in one of the _lines options above. * For client ports, also true if there is a unix socket configured. * If you are checking for client ports, you may want to use: - * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set + * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set || + * HTTPTunnelPort_set * rather than SocksPort_set. * * @{ @@ -3645,6 +3776,7 @@ typedef struct { unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; unsigned int ExtORPort_set : 1; + unsigned int HTTPTunnelPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -3657,6 +3789,10 @@ typedef struct { int BridgeAuthoritativeDir; /**< Boolean: is this an authoritative directory * that aggregates bridge descriptors? */ + /** If set on a bridge relay, it will include this value on a new + * "bridge-distribution-request" line in its bridge descriptor. */ + char *BridgeDistribution; + /** If set on a bridge authority, it will answer requests on its dirport * for bridge statuses -- but only if the requests use this password. */ char *BridgePassword; @@ -3690,6 +3826,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; @@ -3715,15 +3860,6 @@ 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 @@ -3804,8 +3940,8 @@ typedef struct { 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 @@ -3815,16 +3951,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 @@ -3832,8 +3964,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 @@ -3884,7 +4014,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. */ @@ -3910,9 +4041,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? */ @@ -3974,8 +4102,6 @@ typedef struct { int Sandbox; /**< Boolean: should sandboxing be enabled? */ int SafeSocks; /**< Boolean: should we outright refuse application * connections that use socks4 or socks5-with-local-dns? */ -#define LOG_PROTOCOL_WARN (get_options()->ProtocolWarnings ? \ - LOG_WARN : LOG_INFO) int ProtocolWarnings; /**< Boolean: when other parties screw up the Tor * protocol, is it a warn or an info in our logs? */ int TestSocks; /**< Boolean: when we get a socks connection, do we loudly @@ -3997,8 +4123,6 @@ typedef struct { 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 @@ -4008,8 +4132,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; @@ -4065,27 +4187,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 - * websites and exit relays can use it to manipulate your path - * 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; @@ -4102,11 +4203,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. */ @@ -4243,6 +4351,10 @@ typedef struct { * altered on testing networks. */ smartlist_t *TestingBridgeDownloadSchedule; + /** Schedule for when clients should download bridge descriptors when they + * have no running bridges. Only altered on testing networks. */ + smartlist_t *TestingBridgeBootstrapDownloadSchedule; + /** When directory clients have only a few descriptors to request, they * batch them until they have more, or until this amount of time has * passed. Only altered on testing networks. */ @@ -4305,8 +4417,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; @@ -4438,8 +4549,6 @@ typedef struct { int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */ - char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */ - /** Fraction: */ double PathsNeededToBuildCircuits; @@ -4450,19 +4559,6 @@ typedef struct { /** How long (seconds) do we keep a guard before picking a new one? */ int GuardLifetime; - /** Low-water mark for global scheduler - start sending when estimated - * queued size falls below this threshold. - */ - uint64_t SchedulerLowWaterMark__; - /** High-water mark for global scheduler - stop sending when estimated - * queued size exceeds this threshold. - */ - uint64_t SchedulerHighWaterMark__; - /** Flush size for global scheduler - flush this many cells at a time - * when sending. - */ - int SchedulerMaxFlushCells__; - /** Is this an exit node? This is a tristate, where "1" means "yes, and use * the default exit policy if none is given" and "0" means "no; exit policy * is 'reject *'" and "auto" (-1) means "same as 1, but warn the user." @@ -4470,7 +4566,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; @@ -4515,6 +4611,42 @@ typedef struct { /** 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; + + /** Bool (default: 0). Tells Tor to never try to exec another program. + */ + int NoExec; + + /** Have the KIST scheduler run every X milliseconds. If less than zero, do + * not use the KIST scheduler but use the old vanilla scheduler instead. If + * zero, do what the consensus says and fall back to using KIST as if this is + * set to "10 msec" if the consensus doesn't say anything. */ + int KISTSchedRunInterval; + + /** A multiplier for the KIST per-socket limit calculation. */ + double KISTSockBufSizeFactor; + + /** The list of scheduler type string ordered by priority that is first one + * has to be tried first. Default: KIST,KISTLite,Vanilla */ + smartlist_t *Schedulers; + /* An ordered list of scheduler_types mapped from Schedulers. */ + smartlist_t *SchedulerTypes_; + /** Autobool: Is the circuit creation DoS mitigation subsystem enabled? */ int DoSCircuitCreationEnabled; /** Minimum concurrent connection needed from one single address before any @@ -4545,6 +4677,8 @@ typedef struct { int DoSRefuseSingleHopClientRendezvous; } or_options_t; +#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) + /** Persistent state for an onion router, as saved to disk. */ typedef struct { uint32_t magic_; @@ -4566,11 +4700,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, @@ -4598,8 +4738,8 @@ typedef struct { /** Build time histogram */ config_line_t * BuildtimeHistogram; - unsigned int TotalBuildTimes; - unsigned int CircuitBuildAbandonedCount; + int TotalBuildTimes; + int CircuitBuildAbandonedCount; /** What version of Tor wrote this state file? */ char *TorVersion; @@ -4765,7 +4905,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 @@ -4960,7 +5100,7 @@ typedef struct measured_bw_line_t { long int bw_kb; } measured_bw_line_t; -#endif +#endif /* defined(DIRSERV_PRIVATE) */ /********************************* dirvote.c ************************/ @@ -5254,7 +5394,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! **/ @@ -5294,10 +5435,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 { @@ -5311,7 +5448,6 @@ 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, @@ -5319,15 +5455,16 @@ typedef enum { CRN_PREF_ADDR = 1<<7, /* On clients, only provide nodes that we can connect to directly, based on * our firewall rules */ - CRN_DIRECT_CONN = 1<<8 + CRN_DIRECT_CONN = 1<<8, + /* On clients, only provide nodes with HSRend >= 2 protocol version which + * is required for hidden service version >= 3. */ + CRN_RENDEZVOUS_V3 = 1<<9, } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ 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. */ @@ -5376,5 +5513,5 @@ typedef struct tor_version_t { char git_tag[DIGEST_LEN]; } tor_version_t; -#endif +#endif /* !defined(TOR_OR_H) */ diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c new file mode 100644 index 0000000000..6c3dd3100e --- /dev/null +++ b/src/or/parsecommon.c @@ -0,0 +1,451 @@ +/* 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]; + memset(args, 0, sizeof(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..903d94478b --- /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 /* !defined(TOR_PARSECOMMON_H) */ + diff --git a/src/or/periodic.c b/src/or/periodic.c index d02d4a7bbb..6896b41c86 100644 --- a/src/or/periodic.c +++ b/src/or/periodic.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 */ /** diff --git a/src/or/periodic.h b/src/or/periodic.h index 021bb4ef5c..8baf3994eb 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 @@ -33,5 +33,5 @@ void periodic_event_setup(periodic_event_item_t *event); void periodic_event_destroy(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); -#endif +#endif /* !defined(TOR_PERIODIC_H) */ diff --git a/src/or/policies.c b/src/or/policies.c index f58bf329ad..3bfea3a57c 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" @@ -290,8 +298,8 @@ parse_reachable_addresses(void) } else if (fascist_firewall_use_ipv6(options) && (policy_is_reject_star(reachable_or_addr_policy, AF_INET6, 0) || policy_is_reject_star(reachable_dir_addr_policy, AF_INET6, 0))) { - log_warn(LD_CONFIG, "You have configured tor to use IPv6 " - "(ClientUseIPv6 1 or UseBridges 1), but " + 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."); @@ -309,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 @@ -419,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. */ @@ -426,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 @@ -883,6 +894,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 @@ -899,15 +937,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)); @@ -941,6 +979,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)); @@ -1971,10 +2021,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. */ @@ -1983,10 +2033,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]); + } + } + } } } @@ -2003,10 +2057,10 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list, * - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it * to the list of configured addresses. * If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true: - * - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add - * it to the list of configured addresses. - * - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add - * it to the list of configured addresses. + * - 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. @@ -2134,21 +2188,16 @@ exit_policy_is_general_exit_helper(smartlist_t *policy, int port) } /** Return true iff <b>ri</b> is "useful as an exit node", meaning - * it allows exit to at least one /8 address space for at least - * two of ports 80, 443, and 6667. */ + * it allows exit to at least one /8 address space for each of ports 80 + * and 443. */ int exit_policy_is_general_exit(smartlist_t *policy) { - static const int ports[] = { 80, 443, 6667 }; - int n_allowed = 0; - int i; if (!policy) /*XXXX disallow NULL policies? */ return 0; - for (i = 0; i < 3; ++i) { - n_allowed += exit_policy_is_general_exit_helper(policy, ports[i]); - } - return n_allowed >= 2; + return (exit_policy_is_general_exit_helper(policy, 80) && + exit_policy_is_general_exit_helper(policy, 443)); } /** Return false if <b>policy</b> might permit access to some addr:port; @@ -2528,9 +2577,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; @@ -2679,7 +2728,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); @@ -2710,7 +2759,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)); diff --git a/src/or/policies.h b/src/or/policies.h index f73f850c21..52ff4e2f99 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 */ /** @@ -141,7 +141,7 @@ STATIC const tor_addr_port_t * fascist_firewall_choose_address( firewall_connection_t fw_connection, int pref_only, int pref_ipv6); -#endif +#endif /* defined(POLICIES_PRIVATE) */ -#endif +#endif /* !defined(TOR_POLICIES_H) */ diff --git a/src/or/proto_cell.c b/src/or/proto_cell.c new file mode 100644 index 0000000000..75eb2a7e7f --- /dev/null +++ b/src/or/proto_cell.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_cell.h" + +#include "connection_or.h" + +/** True iff the cell command <b>command</b> is one that implies a + * variable-length cell in Tor link protocol <b>linkproto</b>. */ +static inline int +cell_command_is_var_length(uint8_t command, int linkproto) +{ + /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells + * work as implemented here. If it's 1, there are no variable-length cells. + * Tor does not support other versions right now, and so can't negotiate + * them. + */ + switch (linkproto) { + case 1: + /* Link protocol version 1 has no variable-length cells. */ + return 0; + case 2: + /* In link protocol version 2, VERSIONS is the only variable-length cell */ + return command == CELL_VERSIONS; + case 0: + case 3: + default: + /* In link protocol version 3 and later, and in version "unknown", + * commands 128 and higher indicate variable-length. VERSIONS is + * grandfathered in. */ + return command == CELL_VERSIONS || command >= 128; + } +} + +/** Check <b>buf</b> for a variable-length cell according to the rules of link + * protocol version <b>linkproto</b>. If one is found, pull it off the buffer + * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. + * Return 0 if whatever is on the start of buf_t is not a variable-length + * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start + * of a variable-length cell on <b>buf</b>, but the whole thing isn't there + * yet. */ +int +fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) +{ + char hdr[VAR_CELL_MAX_HEADER_SIZE]; + var_cell_t *result; + uint8_t command; + uint16_t length; + const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; + const int circ_id_len = get_circ_id_size(wide_circ_ids); + const unsigned header_len = get_var_cell_header_size(wide_circ_ids); + *out = NULL; + if (buf_datalen(buf) < header_len) + return 0; + buf_peek(buf, hdr, header_len); + + command = get_uint8(hdr + circ_id_len); + if (!(cell_command_is_var_length(command, linkproto))) + return 0; + + length = ntohs(get_uint16(hdr + circ_id_len + 1)); + if (buf_datalen(buf) < (size_t)(header_len+length)) + return 1; + + result = var_cell_new(length); + result->command = command; + if (wide_circ_ids) + result->circ_id = ntohl(get_uint32(hdr)); + else + result->circ_id = ntohs(get_uint16(hdr)); + + buf_drain(buf, header_len); + buf_peek(buf, (char*) result->payload, length); + buf_drain(buf, length); + + *out = result; + return 1; +} + diff --git a/src/or/proto_cell.h b/src/or/proto_cell.h new file mode 100644 index 0000000000..bbc14b9a02 --- /dev/null +++ b/src/or/proto_cell.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CELL_H +#define TOR_PROTO_CELL_H + +struct buf_t; +struct var_cell_t; + +int fetch_var_cell_from_buf(struct buf_t *buf, struct var_cell_t **out, + int linkproto); + +#endif /* !defined(TOR_PROTO_CELL_H) */ + diff --git a/src/or/proto_control0.c b/src/or/proto_control0.c new file mode 100644 index 0000000000..c17ba34948 --- /dev/null +++ b/src/or/proto_control0.c @@ -0,0 +1,26 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_control0.h" + +/** Return 1 iff buf looks more like it has an (obsolete) v0 controller + * command on it than any valid v1 controller command. */ +int +peek_buf_has_control0_command(buf_t *buf) +{ + if (buf_datalen(buf) >= 4) { + char header[4]; + uint16_t cmd; + buf_peek(buf, header, sizeof(header)); + cmd = ntohs(get_uint16(header+2)); + if (cmd <= 0x14) + return 1; /* This is definitely not a v1 control command. */ + } + return 0; +} + diff --git a/src/or/proto_control0.h b/src/or/proto_control0.h new file mode 100644 index 0000000000..0cc8eacad0 --- /dev/null +++ b/src/or/proto_control0.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CONTROL0_H +#define TOR_PROTO_CONTROL0_H + +struct buf_t; +int peek_buf_has_control0_command(struct buf_t *buf); + +#endif /* !defined(TOR_PROTO_CONTROL0_H) */ + diff --git a/src/or/proto_ext_or.c b/src/or/proto_ext_or.c new file mode 100644 index 0000000000..057cf109ec --- /dev/null +++ b/src/or/proto_ext_or.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "ext_orport.h" +#include "proto_ext_or.h" + +/** The size of the header of an Extended ORPort message: 2 bytes for + * COMMAND, 2 bytes for BODYLEN */ +#define EXT_OR_CMD_HEADER_SIZE 4 + +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + + if (buf_datalen(buf) < EXT_OR_CMD_HEADER_SIZE) + return 0; + buf_peek(buf, hdr, sizeof(hdr)); + len = ntohs(get_uint16(hdr+2)); + if (buf_datalen(buf) < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + buf_drain(buf, EXT_OR_CMD_HEADER_SIZE); + buf_get_bytes(buf, (*out)->body, len); + return 1; +} + diff --git a/src/or/proto_ext_or.h b/src/or/proto_ext_or.h new file mode 100644 index 0000000000..cc504d18e3 --- /dev/null +++ b/src/or/proto_ext_or.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_EXT_OR_H +#define TOR_PROTO_EXT_OR_H + +struct buf_t; +struct ext_or_cmt_t; + +int fetch_ext_or_command_from_buf(struct buf_t *buf, + struct ext_or_cmd_t **out); + +#endif /* !defined(TOR_PROTO_EXT_OR_H) */ + diff --git a/src/or/proto_http.c b/src/or/proto_http.c new file mode 100644 index 0000000000..3762429e1e --- /dev/null +++ b/src/or/proto_http.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROTO_HTTP_PRIVATE +#include "or.h" +#include "buffers.h" +#include "proto_http.h" + +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (buf_peek_startswith(buf, "CONNECT ") || + buf_peek_startswith(buf, "DELETE ") || + buf_peek_startswith(buf, "GET ") || + buf_peek_startswith(buf, "POST ") || + buf_peek_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** There is a (possibly incomplete) http statement on <b>buf</b>, of the + * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * + * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. + * - memdup body into <b>*body_out</b>, and NUL-terminate it. + * - Then remove them from <b>buf</b>, and return 1. + * + * - If headers or body is NULL, discard that part of the buf. + * - If a headers or body doesn't fit in the arg, return -1. + * (We ensure that the headers or body don't exceed max len, + * _even if_ we're planning to discard them.) + * - If force_complete is true, then succeed even if not all of the + * content has arrived. + * + * Else, change nothing and return 0. + */ +int +fetch_from_buf_http(buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + const char *headers; + size_t headerlen, bodylen, contentlen=0; + int crlf_offset; + int r; + + if (buf_datalen(buf) == 0) + return 0; + + crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); + if (crlf_offset > (int)max_headerlen || + (crlf_offset < 0 && buf_datalen(buf) > max_headerlen)) { + log_debug(LD_HTTP,"headers too long."); + return -1; + } else if (crlf_offset < 0) { + log_debug(LD_HTTP,"headers not all here yet."); + return 0; + } + /* Okay, we have a full header. Make sure it all appears in the first + * chunk. */ + headerlen = crlf_offset + 4; + size_t headers_in_chunk = 0; + buf_pullup(buf, headerlen, &headers, &headers_in_chunk); + + bodylen = buf_datalen(buf) - headerlen; + log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); + + if (max_headerlen <= headerlen) { + log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", + (int)headerlen, (int)max_headerlen-1); + return -1; + } + if (max_bodylen <= bodylen) { + log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", + (int)bodylen, (int)max_bodylen-1); + return -1; + } + + r = buf_http_find_content_length(headers, headerlen, &contentlen); + if (r == -1) { + log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " + "someone is trying to crash us."); + return -1; + } else if (r == 1) { + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } else { + tor_assert(r == 0); + /* Leave bodylen alone */ + } + + /* all happy. copy into the appropriate places, and return 1 */ + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + buf_get_bytes(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = 0; /* NUL terminate it */ + } + if (body_out) { + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + buf_get_bytes(buf, *body_out, bodylen); + (*body_out)[bodylen] = 0; /* NUL terminate it */ + } + return 1; +} + +/** + * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at + * <b>headers</b>, looking for a "Content-Length" header. Try to set + * *<b>result_out</b> to the numeric value of that header if possible. + * Return -1 if the header was malformed, 0 if it was missing, and 1 if + * it was present and well-formed. + */ +STATIC int +buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out) +{ + const char *p, *newline; + char *len_str, *eos=NULL; + size_t remaining, result; + int ok; + *result_out = 0; /* The caller shouldn't look at this unless the + * return value is 1, but let's prevent confusion */ + +#define CONTENT_LENGTH "\r\nContent-Length: " + p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); + if (p == NULL) + return 0; + + tor_assert(p >= headers && p < headers+headerlen); + remaining = (headers+headerlen)-p; + p += strlen(CONTENT_LENGTH); + remaining -= strlen(CONTENT_LENGTH); + + newline = memchr(p, '\n', remaining); + if (newline == NULL) + return -1; + + len_str = tor_memdup_nulterm(p, newline-p); + /* We limit the size to INT_MAX because other parts of the buffer.c + * code don't like buffers to be any bigger than that. */ + result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); + if (eos && !tor_strisspace(eos)) { + ok = 0; + } else { + *result_out = result; + } + tor_free(len_str); + + return ok ? 1 : -1; +} + diff --git a/src/or/proto_http.h b/src/or/proto_http.h new file mode 100644 index 0000000000..805686070f --- /dev/null +++ b/src/or/proto_http.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_HTTP_H +#define TOR_PROTO_HTTP_H + +struct buf_t; + +int fetch_from_buf_http(struct buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +int peek_buf_has_http_command(const struct buf_t *buf); + +#ifdef PROTO_HTTP_PRIVATE +STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out); +#endif + +#endif /* !defined(TOR_PROTO_HTTP_H) */ + diff --git a/src/or/proto_socks.c b/src/or/proto_socks.c new file mode 100644 index 0000000000..7649fcc4be --- /dev/null +++ b/src/or/proto_socks.c @@ -0,0 +1,710 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "addressmap.h" +#include "buffers.h" +#include "control.h" +#include "config.h" +#include "ext_orport.h" +#include "proto_socks.h" +#include "reasons.h" + +static void socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason); + +static int parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); +/** + * Wait this many seconds before warning the user about using SOCKS unsafely + * again. */ +#define SOCKS_WARN_INTERVAL 5 + +/** Warn that the user application has made an unsafe socks request using + * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than + * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ +static void +log_unsafe_socks_warning(int socks_protocol, const char *address, + uint16_t port, int safe_socks) +{ + static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); + + if (safe_socks) { + log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, + "Your application (using socks%d to port %d) is giving " + "Tor only an IP address. Applications that do DNS resolves " + "themselves may leak information. Consider using Socks4A " + "(e.g. via privoxy or socat) instead. For more information, " + "please see https://wiki.torproject.org/TheOnionRouter/" + "TorFAQ#SOCKSAndDNS.%s", + socks_protocol, + (int)port, + safe_socks ? " Rejecting." : ""); + } + control_event_client_status(LOG_WARN, + "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", + socks_protocol, address, (int)port); +} + +/** Do not attempt to parse socks messages longer than this. This value is + * actually significantly higher than the longest possible socks message. */ +#define MAX_SOCKS_MESSAGE_LEN 512 + +/** Return a new socks_request_t. */ +socks_request_t * +socks_request_new(void) +{ + return tor_malloc_zero(sizeof(socks_request_t)); +} + +/** Free all storage held in the socks_request_t <b>req</b>. */ +void +socks_request_free(socks_request_t *req) +{ + if (!req) + return; + if (req->username) { + memwipe(req->username, 0x10, req->usernamelen); + tor_free(req->username); + } + if (req->password) { + memwipe(req->password, 0x04, req->passwordlen); + tor_free(req->password); + } + memwipe(req, 0xCC, sizeof(socks_request_t)); + tor_free(req); +} + +/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one + * of the forms + * - socks4: "socksheader username\\0" + * - socks4a: "socksheader username\\0 destaddr\\0" + * - socks5 phase one: "version #methods methods" + * - socks5 phase two: "version command 0 addresstype..." + * If it's a complete and valid handshake, and destaddr fits in + * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, + * assign to <b>req</b>, and return 1. + * + * If it's invalid or too big, return -1. + * + * Else it's not all there yet, leave buf alone and return 0. + * + * If you want to specify the socks reply, write it into <b>req->reply</b> + * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. + * + * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether + * the connection is possibly leaking DNS requests locally or not. + * + * If <b>safe_socks</b> is true, then reject unsafe socks protocols. + * + * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are + * undefined. + */ +int +fetch_from_buf_socks(buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + int res; + ssize_t n_drain; + size_t want_length = 128; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, &head, &datalen); + tor_assert(head && datalen >= 2); + want_length = 0; + + res = parse_socks(head, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_drain(buf, n_drain); + + } while (res == 0 && head && want_length < buf_datalen(buf) && + buf_datalen(buf) >= 2); + + return res; +} + +/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and + * have Tor send it as error response to <b>req</b>. + */ +static void +socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason) +{ + req->replylen = 10; + memset(req->reply,0,10); + + req->reply[0] = 0x05; // VER field. + req->reply[1] = reason; // REP field. + req->reply[3] = 0x01; // ATYP field. +} + +static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>This is a SOCKS Proxy, Not An HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>This is a SOCKs proxy, not an HTTP proxy.</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use this Tor port as\n" + "an HTTP proxy.\n" + "</p><p>\n" + "This is not correct: This port is configured as a SOCKS proxy, not\n" + "an HTTP proxy. If you need an HTTP proxy tunnel, use the HTTPTunnelPort\n" + "configuration option in place of, or in addition to, SOCKSPort.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "</p>\n" + "</body>\n" + "</html>\n"; + +/** Implementation helper to implement fetch_from_*_socks. Instead of looking + * at a buffer's contents, we look at the <b>datalen</b> bytes of data in + * <b>data</b>. Instead of removing data from the buffer, we set + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ + unsigned int len; + char tmpbuf[TOR_ADDR_BUF_LEN+1]; + tor_addr_t destaddr; + uint32_t destip; + uint8_t socksver; + char *next, *startaddr; + unsigned char usernamelen, passlen; + struct in_addr in; + + if (datalen < 2) { + /* We always need at least 2 bytes. */ + *want_length_out = 2; + return 0; + } + + if (req->socks_version == 5 && !req->got_auth) { + /* See if we have received authentication. Strictly speaking, we should + also check whether we actually negotiated username/password + authentication. But some broken clients will send us authentication + even if we negotiated SOCKS_NO_AUTH. */ + if (*data == 1) { /* username/pass version 1 */ + /* Format is: authversion [1 byte] == 1 + usernamelen [1 byte] + username [usernamelen bytes] + passlen [1 byte] + password [passlen bytes] */ + usernamelen = (unsigned char)*(data + 1); + if (datalen < 2u + usernamelen + 1u) { + *want_length_out = 2u + usernamelen + 1u; + return 0; + } + passlen = (unsigned char)*(data + 2u + usernamelen); + if (datalen < 2u + usernamelen + 1u + passlen) { + *want_length_out = 2u + usernamelen + 1u + passlen; + return 0; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 1; /* authversion == 1 */ + req->reply[1] = 0; /* authentication successful */ + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + if (usernamelen) { + req->username = tor_memdup(data+2u, usernamelen); + req->usernamelen = usernamelen; + } + if (passlen) { + req->password = tor_memdup(data+3u+usernamelen, passlen); + req->passwordlen = passlen; + } + *drain_out = 2u + usernamelen + 1u + passlen; + req->got_auth = 1; + *want_length_out = 7; /* Minimal socks5 command. */ + return 0; + } else if (req->auth_type == SOCKS_USER_PASS) { + /* unknown version byte */ + log_warn(LD_APP, "Socks5 username/password version %d not recognized; " + "rejecting.", (int)*data); + return -1; + } + } + + socksver = *data; + + switch (socksver) { /* which version of socks? */ + case 5: /* socks5 */ + + if (req->socks_version != 5) { /* we need to negotiate a method */ + unsigned char nummethods = (unsigned char)*(data+1); + int have_user_pass, have_no_auth; + int r=0; + tor_assert(!req->socks_version); + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; + return 0; + } + if (!nummethods) + return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); + have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); + if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { + req->auth_type = SOCKS_USER_PASS; + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else if (have_no_auth) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + } else { + log_warn(LD_APP, + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); + req->reply[1] = '\xFF'; /* reject all methods */ + r=-1; + } + /* Remove packet from buf. Some SOCKS clients will have sent extra + * junk at this point; let's hope it's an authentication message. */ + *drain_out = 2u + nummethods; + + return r; + } + if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { + log_warn(LD_APP, + "socks5: negotiated authentication, but none provided"); + return -1; + } + /* we know the method; read in the request */ + log_debug(LD_APP,"socks5: checking request"); + if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ + *want_length_out = 7; + return 0; /* not yet */ + } + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE && + req->command != SOCKS_COMMAND_RESOLVE_PTR) { + /* not a connect or resolve or a resolve_ptr? we don't support it. */ + socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); + + log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", + req->command); + return -1; + } + switch (*(data+3)) { /* address type */ + case 1: /* IPv4 address */ + case 4: /* IPv6 address */ { + const int is_v6 = *(data+3) == 4; + const unsigned addrlen = is_v6 ? 16 : 4; + log_debug(LD_APP,"socks5: ipv4 address type"); + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; + return 0; /* not yet */ + } + + if (is_v6) + tor_addr_from_ipv6_bytes(&destaddr, data+4); + else + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); + + tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); + + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of MAX_SOCKS_ADDR_LEN */ + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 IP takes %d bytes, which doesn't fit in %d. " + "Rejecting.", + (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); + return -1; + /* LCOV_EXCL_STOP */ + } + strlcpy(req->address,tmpbuf,sizeof(req->address)); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; + if (req->command != SOCKS_COMMAND_RESOLVE_PTR && + !addressmap_have_mapping(req->address,0)) { + log_unsafe_socks_warning(5, req->address, req->port, safe_socks); + if (safe_socks) { + socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); + return -1; + } + } + return 1; + } + case 3: /* fqdn */ + log_debug(LD_APP,"socks5: fqdn address type"); + if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " + "hostname type. Rejecting."); + return -1; + } + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; + return 0; /* not yet */ + } + if (BUG(len+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- unreachable, since len is at most 255, + * and MAX_SOCKS_ADDR_LEN is 256. */ + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 hostname is %d bytes, which doesn't fit in " + "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); + return -1; + /* LCOV_EXCL_STOP */ + } + memcpy(req->address,data+5,len); + req->address[len] = 0; + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; + + if (!string_is_valid_hostname(req->address)) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + + log_warn(LD_PROTOCOL, + "Your application (using socks5 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks5 to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + return 1; + default: /* unsupported */ + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", + (int) *(data+3)); + return -1; + } + tor_assert(0); + break; + case 4: { /* socks4 */ + enum {socks4, socks4a} socks4_prot = socks4a; + const char *authstart, *authend; + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ + + req->socks_version = 4; + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; + return 0; /* not yet */ + } + // buf_pullup(buf, 1280); + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE) { + /* not a connect or resolve? we don't support it. (No resolve_ptr with + * socks4.) */ + log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", + req->command); + return -1; + } + + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); + if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { + log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); + return -1; + } + if (destip >> 8) { + log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); + in.s_addr = htonl(destip); + tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of MAX_SOCKS_ADDR_LEN */ + log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", + (int)strlen(tmpbuf)); + return -1; + /* LCOV_EXCL_STOP */ + } + log_debug(LD_APP, + "socks4: successfully read destip (%s)", + safe_str_client(tmpbuf)); + socks4_prot = socks4; + } + + authstart = data + SOCKS4_NETWORK_LEN; + next = memchr(authstart, 0, + datalen-SOCKS4_NETWORK_LEN); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP, "Socks4 user name too long; rejecting."); + return -1; + } + log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* More than we need, but safe */ + return 0; + } + authend = next; + tor_assert(next < data+datalen); + + startaddr = NULL; + if (socks4_prot != socks4a && + !addressmap_have_mapping(tmpbuf,0)) { + log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); + + if (safe_socks) + return -1; + } + if (socks4_prot == socks4a) { + if (next+1 == data+datalen) { + log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + startaddr = next+1; + next = memchr(startaddr, 0, data + datalen - startaddr); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP,"socks4: Destaddr too long."); + return -1; + } + log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { + log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); + return -1; + } + // tor_assert(next < buf->cur+buf_datalen(buf)); + + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks4a to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + } + log_debug(LD_APP,"socks4: Everything is here. Success."); + strlcpy(req->address, startaddr ? startaddr : tmpbuf, + sizeof(req->address)); + if (!string_is_valid_hostname(req->address)) { + log_warn(LD_PROTOCOL, + "Your application (using socks4 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (authend != authstart) { + req->got_auth = 1; + req->usernamelen = authend - authstart; + req->username = tor_memdup(authstart, authend - authstart); + } + /* next points to the final \0 on inbuf */ + *drain_out = next - data + 1; + return 1; + } + case 'G': /* get */ + case 'H': /* head */ + case 'P': /* put/post */ + case 'C': /* connect */ + strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, + MAX_SOCKS_REPLY_LEN); + req->replylen = strlen((char*)req->reply)+1; + /* fall through */ + default: /* version is not socks4 or socks5 */ + log_warn(LD_APP, + "Socks version %d not recognized. (This port is not an " + "HTTP proxy; did you want to use HTTPTunnelPort?)", + *(data)); + { + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); + control_event_client_status(LOG_WARN, + "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", + escaped(tmp)); + tor_free(tmp); + } + return -1; + } +} + +/** Inspect a reply from SOCKS server stored in <b>buf</b> according + * to <b>state</b>, removing the protocol data upon success. Return 0 on + * incomplete response, 1 on success and -1 on error, in which case + * <b>reason</b> is set to a descriptive message (free() when finished + * with it). + * + * As a special case, 2 is returned when user/pass is required + * during SOCKS5 handshake and user/pass is configured. + */ +int +fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) +{ + ssize_t drain = 0; + int r; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) + return 0; + + buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, &head, &datalen); + tor_assert(head && datalen >= 2); + + r = parse_socks_client((uint8_t*)head, datalen, + state, reason, &drain); + if (drain > 0) + buf_drain(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; + + switch (state) { + case PROXY_SOCKS4_WANT_CONNECT_OK: + /* Wait for the complete response */ + if (datalen < 8) + return 0; + + if (data[1] != 0x5a) { + *reason = tor_strdup(socks4_response_code_to_string(data[1])); + return -1; + } + + /* Success */ + *drain_out = 8; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: + /* we don't have any credentials */ + if (data[1] != 0x00) { + *reason = tor_strdup("server doesn't support any of our " + "available authentication methods"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: + /* we have a username and password. return 1 if we can proceed without + * providing authentication, or 2 otherwise. */ + switch (data[1]) { + case 0x00: + log_info(LD_NET, "SOCKS 5 client: we have auth details but server " + "doesn't require authentication."); + *drain_out = -1; + return 1; + case 0x02: + log_info(LD_NET, "SOCKS 5 client: need authentication."); + *drain_out = -1; + return 2; + /* fall through */ + } + + *reason = tor_strdup("server doesn't support any of our available " + "authentication methods"); + return -1; + + case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: + /* handle server reply to rfc1929 authentication */ + if (data[1] != 0x00) { + *reason = tor_strdup("authentication failed"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: authentication successful."); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_CONNECT_OK: + /* response is variable length. BND.ADDR, etc, isn't needed + * (don't bother with buf_pullup()), but make sure to eat all + * the data used */ + + /* wait for address type field to arrive */ + if (datalen < 4) + return 0; + + switch (data[3]) { + case 0x01: /* ip4 */ + addrlen = 4; + break; + case 0x04: /* ip6 */ + addrlen = 16; + break; + case 0x03: /* fqdn (can this happen here?) */ + if (datalen < 5) + return 0; + addrlen = 1 + data[4]; + break; + default: + *reason = tor_strdup("invalid response to connect request"); + return -1; + } + + /* wait for address and port */ + if (datalen < 6 + addrlen) + return 0; + + if (data[1] != 0x00) { + *reason = tor_strdup(socks5_response_code_to_string(data[1])); + return -1; + } + + *drain_out = 6 + addrlen; + return 1; + } + + /* LCOV_EXCL_START */ + /* shouldn't get here if the input state is one we know about... */ + tor_assert(0); + + return -1; + /* LCOV_EXCL_STOP */ +} + diff --git a/src/or/proto_socks.h b/src/or/proto_socks.h new file mode 100644 index 0000000000..a714151414 --- /dev/null +++ b/src/or/proto_socks.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_SOCKS_H +#define TOR_PROTO_SOCKS_H + +struct socks_request_t; +struct buf_t; + +struct socks_request_t *socks_request_new(void); +void socks_request_free(struct socks_request_t *req); +int fetch_from_buf_socks(struct buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); + +#endif /* !defined(TOR_PROTO_SOCKS_H) */ + diff --git a/src/or/protover.c b/src/or/protover.c index 2c5d5ab1fc..ebaca07ba3 100644 --- a/src/or/protover.c +++ b/src/or/protover.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -315,12 +315,12 @@ protover_get_supported_protocols(void) return "Cons=1-2 " "Desc=1-2 " - "DirCache=1 " - "HSDir=1 " - "HSIntro=3 " + "DirCache=1-2 " + "HSDir=1-2 " + "HSIntro=3-4 " "HSRend=1-2 " - "Link=1-4 " - "LinkAuth=1 " + "Link=1-5 " + "LinkAuth=1,3 " "Microdesc=1-2 " "Relay=1-2"; } @@ -375,7 +375,7 @@ encode_protocol_list(const smartlist_t *sl) const char *separator = ""; smartlist_t *chunks = smartlist_new(); SMARTLIST_FOREACH_BEGIN(sl, const proto_entry_t *, ent) { - smartlist_add(chunks, tor_strdup(separator)); + smartlist_add_strdup(chunks, separator); proto_entry_encode_into(chunks, ent); @@ -508,7 +508,7 @@ contract_protocol_list(const smartlist_t *proto_strings) smartlist_sort(lst, cmp_single_ent_by_version); if (! first_entry) - smartlist_add(chunks, tor_strdup(" ")); + 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)); diff --git a/src/or/protover.h b/src/or/protover.h index 5c658931ea..657977279e 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -17,6 +17,13 @@ /* This is a guess. */ #define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha" +/** The protover version number that signifies HSDir support for HSv3 */ +#define PROTOVER_HSDIR_V3 2 +/** The protover version number that signifies HSv3 intro point support */ +#define PROTOVER_HS_INTRO_V3 4 +/** The protover version number that signifies HSv3 rendezvous point support */ +#define PROTOVER_HS_RENDEZVOUS_POINT_V3 2 + /** List of recognized subprotocols. */ typedef enum protocol_type_t { PRT_LINK, @@ -68,7 +75,7 @@ STATIC void proto_entry_free(proto_entry_t *entry); STATIC char *encode_protocol_list(const smartlist_t *sl); STATIC const char *protocol_type_to_str(protocol_type_t pr); STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out); -#endif +#endif /* defined(PROTOVER_PRIVATE) */ -#endif +#endif /* !defined(TOR_PROTOVER_H) */ diff --git a/src/or/reasons.c b/src/or/reasons.c index a1566e2299..03d49418da 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -45,6 +45,8 @@ stream_end_reason_to_control_string(int reason) case END_STREAM_REASON_CANT_ATTACH: return "CANT_ATTACH"; case END_STREAM_REASON_NET_UNREACHABLE: return "NET_UNREACHABLE"; case END_STREAM_REASON_SOCKSPROTOCOL: return "SOCKS_PROTOCOL"; + // XXXX Controlspec + case END_STREAM_REASON_HTTPPROTOCOL: return "HTTP_PROTOCOL"; case END_STREAM_REASON_PRIVATE_ADDR: return "PRIVATE_ADDR"; @@ -138,6 +140,11 @@ stream_end_reason_to_socks5_response(int reason) return SOCKS5_NET_UNREACHABLE; case END_STREAM_REASON_SOCKSPROTOCOL: return SOCKS5_GENERAL_ERROR; + case END_STREAM_REASON_HTTPPROTOCOL: + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + return SOCKS5_GENERAL_ERROR; + // LCOV_EXCL_STOP case END_STREAM_REASON_PRIVATE_ADDR: return SOCKS5_GENERAL_ERROR; @@ -160,7 +167,7 @@ stream_end_reason_to_socks5_response(int reason) #else #define E_CASE(s) case s #define S_CASE(s) case s -#endif +#endif /* defined(_WIN32) */ /** Given an errno from a failed exit connection, return a reason code * appropriate for use in a RELAY END cell. */ @@ -442,3 +449,48 @@ bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule) } } +/** Given a RELAY_END reason value, convert it to an HTTP response to be + * send over an HTTP tunnel connection. */ +const char * +end_reason_to_http_connect_response_line(int endreason) +{ + endreason &= END_STREAM_REASON_MASK; + /* XXXX these are probably all wrong. Should they all be 502? */ + switch (endreason) { + case 0: + return "HTTP/1.0 200 OK\r\n\r\n"; + case END_STREAM_REASON_MISC: + return "HTTP/1.0 500 Internal Server Error\r\n\r\n"; + case END_STREAM_REASON_RESOLVEFAILED: + return "HTTP/1.0 404 Not Found (resolve failed)\r\n\r\n"; + case END_STREAM_REASON_NOROUTE: + return "HTTP/1.0 404 Not Found (no route)\r\n\r\n"; + case END_STREAM_REASON_CONNECTREFUSED: + return "HTTP/1.0 403 Forbidden (connection refused)\r\n\r\n"; + case END_STREAM_REASON_EXITPOLICY: + return "HTTP/1.0 403 Forbidden (exit policy)\r\n\r\n"; + case END_STREAM_REASON_DESTROY: + return "HTTP/1.0 502 Bad Gateway (destroy cell received)\r\n\r\n"; + case END_STREAM_REASON_DONE: + return "HTTP/1.0 502 Bad Gateway (unexpected close)\r\n\r\n"; + case END_STREAM_REASON_TIMEOUT: + return "HTTP/1.0 504 Gateway Timeout\r\n\r\n"; + case END_STREAM_REASON_HIBERNATING: + return "HTTP/1.0 502 Bad Gateway (hibernating server)\r\n\r\n"; + case END_STREAM_REASON_INTERNAL: + return "HTTP/1.0 502 Bad Gateway (internal error)\r\n\r\n"; + case END_STREAM_REASON_RESOURCELIMIT: + return "HTTP/1.0 502 Bad Gateway (resource limit)\r\n\r\n"; + case END_STREAM_REASON_CONNRESET: + return "HTTP/1.0 403 Forbidden (connection reset)\r\n\r\n"; + case END_STREAM_REASON_TORPROTOCOL: + return "HTTP/1.0 502 Bad Gateway (tor protocol violation)\r\n\r\n"; + case END_STREAM_REASON_ENTRYPOLICY: + return "HTTP/1.0 403 Forbidden (entry policy violation)\r\n\r\n"; + case END_STREAM_REASON_NOTDIRECTORY: /* Fall Through */ + default: + tor_assert_nonfatal_unreached(); + return "HTTP/1.0 500 Internal Server Error (weird end reason)\r\n\r\n"; + } +} + diff --git a/src/or/reasons.h b/src/or/reasons.h index 2e12c93728..3d6ba8fc83 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 */ /** @@ -26,6 +26,7 @@ const char *socks4_response_code_to_string(uint8_t code); const char *socks5_response_code_to_string(uint8_t code); const char *bandwidth_weight_rule_to_string(enum bandwidth_weight_rule_t rule); +const char *end_reason_to_http_connect_response_line(int endreason); -#endif +#endif /* !defined(TOR_REASONS_H) */ diff --git a/src/or/relay.c b/src/or/relay.c index 1c791e02cc..defbf63b79 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1,30 +1,68 @@ /* 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 #include "or.h" #include "addressmap.h" +#include "backtrace.h" #include "buffers.h" #include "channel.h" #include "circpathbias.h" #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 +76,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 +185,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 +298,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 +333,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; } } @@ -327,8 +444,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, do { /* Remember: cpath is in forward order, that is, first hop first. */ tor_assert(thishop); - if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0) - return -1; + /* decrypt one layer */ + relay_crypt_one_payload(thishop->b_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -345,19 +462,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, log_fn(LOG_PROTOCOL_WARN, LD_OR, "Incoming cell at client not recognized. Closing."); return -1; - } else { /* we're in the middle. Just one crypt. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, - cell->payload, 1) < 0) - return -1; -// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not " -// "the client."); + } else { + /* We're in the middle. Encrypt one layer. */ + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload); } } else /* cell_direction == CELL_DIRECTION_OUT */ { - /* we're in the middle. Just one crypt. */ + /* We're in the middle. Decrypt one layer. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, - cell->payload, 0) < 0) - return -1; + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -393,12 +505,22 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, chan = circ->n_chan; if (!chan) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL." - " Dropping.", filename, lineno); + " Dropping. Circuit is in state %s (%d), and is " + "%smarked for close. (%s:%d, %d)", filename, lineno, + circuit_state_to_string(circ->state), circ->state, + circ->marked_for_close ? "" : "not ", + circ->marked_for_close_file?circ->marked_for_close_file:"", + circ->marked_for_close, circ->marked_for_close_reason); + if (CIRCUIT_IS_ORIGIN(circ)) { + circuit_log_path(LOG_WARN, LD_BUG, TO_ORIGIN_CIRCUIT(circ)); + } + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } if (!CIRCUIT_IS_ORIGIN(circ)) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d on non-origin " "circ. Dropping.", filename, lineno); + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } @@ -408,11 +530,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, /* moving from farthest to nearest hop */ do { tor_assert(thishop); - /* XXXX RD This is a bug, right? */ - log_debug(LD_OR,"crypting a layer of the relay cell."); - if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) { - return -1; - } + log_debug(LD_OR,"encrypting a layer of the relay cell."); + relay_crypt_one_payload(thishop->f_crypto, cell->payload); thishop = thishop->prev; } while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev); @@ -429,8 +548,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, or_circ = TO_OR_CIRCUIT(circ); chan = or_circ->p_chan; relay_set_digest(or_circ->p_digest, cell); - if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0) - return -1; + /* encrypt one layer */ + relay_crypt_one_payload(or_circ->p_crypto, cell->payload); } ++stats_n_relay_cells_relayed; @@ -564,11 +683,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; @@ -580,14 +699,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)); @@ -601,6 +720,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) @@ -707,6 +829,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 /* defined(MEASUREMENTS_21206) */ + return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, payload_len, cpath_layer); @@ -1358,8 +1490,9 @@ connection_edge_process_relay_cell_not_open( circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ tor_assert(entry_conn->socks_request != NULL); - if (!entry_conn->socks_request->has_finished) + if (!entry_conn->socks_request->has_finished) { connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); + } /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -1490,6 +1623,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: @@ -1560,9 +1694,19 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } stats_n_data_bytes_received += rh.length; - connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), + connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); +#ifdef MEASUREMENTS_21206 + /* 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 /* defined(MEASUREMENTS_21206) */ + if (!optimistic_data) { /* Only send a SENDME if we're not getting optimistic data; otherwise * a SENDME could arrive before the CONNECTED. @@ -1918,13 +2062,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, /* XXXX We could be more efficient here by sometimes packing * previously-sent optimistic data in the same cell with data * from the inbuf. */ - fetch_from_buf(payload, length, entry_conn->sending_optimistic_data); + buf_get_bytes(entry_conn->sending_optimistic_data, payload, length); if (!buf_datalen(entry_conn->sending_optimistic_data)) { buf_free(entry_conn->sending_optimistic_data); entry_conn->sending_optimistic_data = NULL; } } else { - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + connection_buf_get_bytes(payload, length, TO_CONN(conn)); } log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).", @@ -1936,7 +2080,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, retry */ if (!entry_conn->pending_optimistic_data) entry_conn->pending_optimistic_data = buf_new(); - write_to_buf(payload, length, entry_conn->pending_optimistic_data); + buf_add(entry_conn->pending_optimistic_data, payload, length); } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, @@ -2259,7 +2403,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) assert_circuit_mux_okay(chan) #else #define assert_cmux_ok_paranoid(chan) -#endif +#endif /* defined(ACTIVE_CIRCUITS_PARANOIA) */ /** The total number of cells we have allocated. */ static size_t total_cells_allocated = 0; @@ -2473,7 +2617,7 @@ cell_queues_check_size(void) time_t now = time(NULL); 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; const size_t geoip_client_cache_total = @@ -2488,9 +2632,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(now, bytes_to_remove); - alloc -= rend_cache_total; - alloc += rend_cache_get_total_allocation(); + alloc -= hs_cache_handle_oom(time(NULL), bytes_to_remove); } if (geoip_client_cache_total > get_options()->MaxMemInQueues / 5) { const size_t bytes_to_remove = @@ -2814,7 +2956,7 @@ get_max_middle_cells(void) { return ORCIRC_MAX_MIDDLE_CELLS; } -#endif +#endif /* 0 */ /** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ @@ -2924,7 +3066,7 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, } } } -#endif +#endif /* 0 */ cell_queue_append_packed_copy(circ, queue, exitward, cell, chan->wide_circ_ids, 1); diff --git a/src/or/relay.h b/src/or/relay.h index c4f98d92ff..4cc1a0fbdb 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), \ @@ -109,7 +112,7 @@ STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue); STATIC destroy_cell_t *destroy_cell_queue_pop(destroy_cell_queue_t *queue); STATIC size_t cell_queues_get_total_allocation(void); STATIC int cell_queues_check_size(void); -#endif +#endif /* defined(RELAY_PRIVATE) */ -#endif +#endif /* !defined(TOR_RELAY_H) */ diff --git a/src/or/rendcache.c b/src/or/rendcache.c index aa69d735fe..b98b2bccfa 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; @@ -303,7 +303,7 @@ void rend_cache_purge(void) { if (rend_cache) { - log_info(LD_REND, "Purging HS descriptor cache"); + log_info(LD_REND, "Purging HS v2 descriptor cache"); strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); @@ -315,7 +315,7 @@ void rend_cache_failure_purge(void) { if (rend_cache_failure) { - log_info(LD_REND, "Purging HS failure cache"); + log_info(LD_REND, "Purging HS v2 failure cache"); strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); } rend_cache_failure = strmap_new(); @@ -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 @@ -521,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) tor_assert(rend_cache); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } @@ -567,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e) tor_assert(rend_cache_local_service); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } @@ -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 " diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 270b614c38..5b13eadfa1 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); @@ -108,8 +115,8 @@ extern strmap_t *rend_cache; extern strmap_t *rend_cache_failure; extern digestmap_t *rend_cache_v2_dir; extern size_t rend_cache_total_allocation; -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(RENDCACHE_PRIVATE) */ -#endif /* TOR_RENDCACHE_H */ +#endif /* !defined(TOR_RENDCACHE_H) */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index f0144b076f..ac2ac5be21 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,9 @@ #include "connection.h" #include "connection_edge.h" #include "directory.h" +#include "hs_common.h" +#include "hs_circuit.h" +#include "hs_client.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -40,7 +43,7 @@ rend_client_purge_state(void) rend_cache_purge(); rend_cache_failure_purge(); rend_client_cancel_descriptor_fetches(); - rend_client_purge_last_hid_serv_requests(); + hs_purge_last_hid_serv_requests(); } /** Called when we've established a circuit to an introduction point: @@ -87,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) return 0; } -/** Extend the introduction circuit <b>circ</b> to another valid - * introduction point for the hidden service it is trying to connect - * to, or mark it and launch a new circuit if we can't extend it. - * Return 0 on success or possible success. Return -1 and mark the - * introduction circuit for close on permanent failure. - * - * On failure, the caller is responsible for marking the associated - * rendezvous circuit for close. */ -static int -rend_client_reextend_intro_circuit(origin_circuit_t *circ) -{ - extend_info_t *extend_info; - int result; - extend_info = rend_client_get_random_intro(circ->rend_data); - if (!extend_info) { - log_warn(LD_REND, - "No usable introduction points left for %s. Closing.", - safe_str_client(circ->rend_data->onion_address)); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); - return -1; - } - // XXX: should we not re-extend if hs_circ_has_timed_out? - if (circ->remaining_relay_early_cells) { - log_info(LD_REND, - "Re-extending circ %u, this time to %s.", - (unsigned)circ->base_.n_circ_id, - safe_str_client(extend_info_describe(extend_info))); - result = circuit_extend_to_new_exit(circ, extend_info); - } else { - log_info(LD_REND, - "Closing intro circ %u (out of RELAY_EARLY cells).", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); - /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ - result = 0; - } - extend_info_free(extend_info); - return result; -} - /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ @@ -144,18 +107,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)); + 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)) { @@ -164,14 +128,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; } @@ -195,12 +158,12 @@ 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)); - if (rend_client_reextend_intro_circuit(introcirc)) { + if (hs_client_reextend_intro_circuit(introcirc)) { status = -2; goto perm_err; } else { @@ -235,11 +198,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; } @@ -263,6 +227,11 @@ rend_client_send_introduction(origin_circuit_t *introcirc, klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+v3_shift+7+DIGEST_LEN+2, sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2)); + if (klen < 0) { + log_warn(LD_BUG,"Internal error: can't encode public key."); + status = -2; + goto perm_err; + } set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen)); memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_data->rend_cookie, REND_COOKIE_LEN); @@ -292,10 +261,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), @@ -368,7 +336,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) { @@ -377,8 +345,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); @@ -399,23 +366,11 @@ rend_client_introduction_acked(origin_circuit_t *circ, origin_circuit_t *rendcirc; (void) request; // XXXX Use this. - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { - log_warn(LD_PROTOCOL, - "Received REND_INTRODUCE_ACK on unexpected circuit %u.", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - tor_assert(circ->build_state); tor_assert(circ->build_state->chosen_exit); assert_circ_anonymity_ok(circ, options); tor_assert(circ->rend_data); - /* For path bias: This circuit was used successfully. Valid - * nacks and acks count. */ - pathbias_mark_use_success(circ); - if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ /* Locate the rend circ which is waiting to hear about this ack, @@ -440,7 +395,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); @@ -449,14 +405,14 @@ 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, INTRO_POINT_FAILURE_GENERIC)>0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ - int result = rend_client_reextend_intro_circuit(circ); + int result = hs_client_reextend_intro_circuit(circ); /* XXXX If that call failed, should we close the rend circuit, * too? */ return result; @@ -472,230 +428,6 @@ rend_client_introduction_acked(origin_circuit_t *circ, return 0; } -/** The period for which a hidden service directory cannot be queried for - * the same descriptor ID again. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) -/** Test networks generate a new consensus every 5 or 10 seconds. - * So allow them to requery HSDirs much faster. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) - -/** Return the period for which a hidden service directory cannot be queried - * for the same descriptor ID again, taking TestingTorNetwork into account. */ -static time_t -hsdir_requery_period(const or_options_t *options) -{ - tor_assert(options); - - if (options->TestingTorNetwork) { - return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; - } else { - return REND_HID_SERV_DIR_REQUERY_PERIOD; - } -} - -/** Contains the last request times to hidden service directories for - * certain queries; each key is a string consisting of the - * concatenation of a base32-encoded HS directory identity digest and - * base32-encoded HS descriptor ID; each value is a pointer to a time_t - * holding the time of the last request for that descriptor ID to that - * HS directory. */ -static strmap_t *last_hid_serv_requests_ = NULL; - -/** Returns last_hid_serv_requests_, initializing it to a new strmap if - * necessary. */ -static strmap_t * -get_last_hid_serv_requests(void) -{ - if (!last_hid_serv_requests_) - last_hid_serv_requests_ = strmap_new(); - return last_hid_serv_requests_; -} - -#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ - REND_DESC_ID_V2_LEN_BASE32) - -/** Look up the last request time to hidden service directory <b>hs_dir</b> - * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero, - * assign the current time <b>now</b> and return that. Otherwise, return the - * most recent request time, or 0 if no such request has been sent before. - */ -static time_t -lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, - time_t now, int set) -{ - char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; - time_t *last_request_ptr; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), - hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, - desc_id_base32); - /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == - LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (set) { - time_t *oldptr; - last_request_ptr = tor_malloc_zero(sizeof(time_t)); - *last_request_ptr = now; - oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, - last_request_ptr); - tor_free(oldptr); - } else - last_request_ptr = strmap_get_lc(last_hid_serv_requests, - hsdir_desc_comb_id); - return (last_request_ptr) ? *last_request_ptr : 0; -} - -/** Clean the history of request times to hidden service directories, so that - * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD - * seconds any more. */ -static void -directory_clean_last_hid_serv_requests(time_t now) -{ - strmap_iter_t *iter; - time_t cutoff = now - hsdir_requery_period(get_options()); - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - time_t *ent; - strmap_iter_get(iter, &key, &val); - ent = (time_t *) val; - if (*ent < cutoff) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(ent); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Remove all requests related to the descriptor ID <b>desc_id</b> from the - * history of times of requests to hidden service directories. - * <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN. - * - * This is called from rend_client_note_connection_attempt_ended(), which - * must be idempotent, so any future changes to this function must leave it - * idempotent too. */ -static void -purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) -{ - strmap_iter_t *iter; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - - /* Key is stored with the base32 encoded desc_id. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - strmap_iter_get(iter, &key, &val); - /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - - REND_DESC_ID_V2_LEN_BASE32, - desc_id_base32, - REND_DESC_ID_V2_LEN_BASE32)) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(val); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Purge the history of request times to hidden service directories, - * so that future lookups of an HS descriptor will not fail because we - * accessed all of the HSDir relays responsible for the descriptor - * recently. */ -void -rend_client_purge_last_hid_serv_requests(void) -{ - /* Don't create the table if it doesn't exist yet (and it may very - * well not exist if the user hasn't accessed any HSes)... */ - strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; - /* ... and let get_last_hid_serv_requests re-create it for us if - * necessary. */ - last_hid_serv_requests_ = NULL; - - if (old_last_hid_serv_requests != NULL) { - log_info(LD_REND, "Purging client last-HS-desc-request-time table"); - strmap_free(old_last_hid_serv_requests, tor_free_); - } -} - -/** This returns a good valid hs dir that should be used for the given - * descriptor id. - * - * Return NULL on error else the hsdir node pointer. */ -static routerstatus_t * -pick_hsdir(const char *desc_id, const char *desc_id_base32) -{ - smartlist_t *responsible_dirs = smartlist_new(); - smartlist_t *usable_responsible_dirs = smartlist_new(); - const or_options_t *options = get_options(); - routerstatus_t *hs_dir; - time_t now = time(NULL); - int excluded_some; - - tor_assert(desc_id); - tor_assert(desc_id_base32); - - /* Determine responsible dirs. Even if we can't get all we want, work with - * the ones we have. If it's empty, we'll notice below. */ - hid_serv_get_responsible_directories(responsible_dirs, desc_id); - - /* Clean request history first. */ - directory_clean_last_hid_serv_requests(now); - - /* Only select those hidden service directories to which we did not send a - * request recently and for which we have a router descriptor here. */ - SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); - const node_t *node = node_get_by_id(dir->identity_digest); - if (last + hsdir_requery_period(options) >= now || - !node || !node_has_descriptor(node)) { - SMARTLIST_DEL_CURRENT(responsible_dirs, dir); - continue; - } - if (!routerset_contains_node(options->ExcludeNodes, node)) { - smartlist_add(usable_responsible_dirs, dir); - } - } SMARTLIST_FOREACH_END(dir); - - excluded_some = - smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); - - hs_dir = smartlist_choose(usable_responsible_dirs); - if (!hs_dir && !options->StrictNodes) { - hs_dir = smartlist_choose(responsible_dirs); - } - - smartlist_free(responsible_dirs); - smartlist_free(usable_responsible_dirs); - if (!hs_dir) { - log_info(LD_REND, "Could not pick one of the responsible hidden " - "service directories, because we requested them all " - "recently without success."); - if (options->StrictNodes && excluded_some) { - log_warn(LD_REND, "Could not pick a hidden service directory for the " - "requested hidden service: they are all either down or " - "excluded, and StrictNodes is set."); - } - } else { - /* Remember that we are requesting a descriptor from this hidden service - * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); - } - - return hs_dir; -} - /** Determine the responsible hidden service directories for <b>desc_id</b> * and fetch the descriptor with that ID from one of them. Only * send a request to a hidden service directory that we have not yet tried @@ -703,30 +435,42 @@ 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; #else const int how_to_fetch = DIRIND_ANONYMOUS; -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ 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); /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { - hs_dir = pick_hsdir(desc_id, desc_id_base32); + /* Determine responsible dirs. Even if we can't get all we want, work with + * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ + smartlist_t *responsible_dirs = smartlist_new(); + hid_serv_get_responsible_directories(responsible_dirs, desc_id); + + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ + control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR"); + control_event_hs_descriptor_content(rend_data_get_address(rend_query), + desc_id_base32, NULL, NULL); return 0; } } @@ -740,12 +484,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. */ @@ -758,20 +506,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, @@ -780,14 +530,28 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, return 1; } +/** Remove tracked HSDir requests from our history for this hidden service + * descriptor <b>desc_id</b> (of size DIGEST_LEN) */ +static void +purge_v2_hidserv_req(const char *desc_id) +{ + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + + /* The hsdir request tracker stores v2 keys using the base32 encoded + desc_id. Do it: */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32); +} + /** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are * given, they will be used instead. * * 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; @@ -820,13 +584,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. */ @@ -840,9 +603,10 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs) int chosen_replica = replicas_left_to_try[rand_val]; replicas_left_to_try[rand_val] = replicas_left_to_try[--tries_left]; - ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address, - query->auth_type == REND_STEALTH_AUTH ? - query->descriptor_cookie : NULL, + 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 @@ -850,18 +614,17 @@ 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])); + purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]); + memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, + sizeof(rend_data->descriptor_id[chosen_replica])); } /* 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. */ @@ -889,16 +652,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; @@ -916,10 +686,11 @@ 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); /* 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."); @@ -932,7 +703,7 @@ rend_client_refetch_v2_renddesc(rend_data_t *rend_query) 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 @@ -968,7 +739,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); } @@ -998,25 +769,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); @@ -1040,7 +812,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; @@ -1058,8 +830,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); } @@ -1073,14 +844,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; } @@ -1089,122 +860,28 @@ 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; } -/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of - * the circuit to C_REND_READY. - */ -int -rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, - size_t request_len) -{ - (void) request; - (void) request_len; - /* we just got an ack for our establish-rendezvous. switch purposes. */ - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { - log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. " - "Closing circ."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for " - "rendezvous."); - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); - /* Set timestamp_dirty, because circuit_expire_building expects it - * to specify when a circuit entered the _C_REND_READY state. */ - circ->base_.timestamp_dirty = time(NULL); - - /* From a path bias point of view, this circuit is now successfully used. - * Waiting any longer opens us up to attacks from malicious hidden services. - * They could induce the client to attempt to connect to their hidden - * service and never reply to the client's rend requests */ - pathbias_mark_use_success(circ); - - /* XXXX++ This is a pretty brute-force approach. It'd be better to - * attach only the connections that are waiting on this circuit, rather - * than trying to attach them all. See comments bug 743. */ - /* If we already have the introduction circuit built, make sure we send - * the INTRODUCE cell _now_ */ - connection_ap_attach_pending(1); - return 0; -} - /** The service sent us a rendezvous cell; join the circuits. */ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { - crypt_path_t *hop; - char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; - - if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && - circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) - || !circ->build_state->pending_final_cpath) { - log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not " - "expecting it. Closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - if (request_len != DH_KEY_LEN+DIGEST_LEN) { log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.", (int)request_len); goto err; } - log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service."); - - /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh - * handshake...*/ - tor_assert(circ->build_state); - tor_assert(circ->build_state->pending_final_cpath); - hop = circ->build_state->pending_final_cpath; - tor_assert(hop->rend_dh_handshake_state); - if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, - hop->rend_dh_handshake_state, (char*)request, - DH_KEY_LEN, - keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { - log_warn(LD_GENERAL, "Couldn't complete DH handshake."); - 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."); + if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); 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; } @@ -1230,10 +907,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 @@ -1268,11 +946,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; } @@ -1286,17 +965,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]; - purge_hid_serv_from_last_hid_serv_requests(desc_id); + const char *desc_id = rend_data_v2->descriptor_id[replica]; + purge_v2_hidserv_req(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_v2_hidserv_req(rend_data_v2->desc_id_fetch); } } @@ -1310,12 +989,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; @@ -1494,7 +1174,7 @@ rend_parse_service_authorization(const or_options_t *options, goto err; } strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1); - if (!rend_valid_service_id(auth->onion_address)) { + if (!rend_valid_v2_service_id(auth->onion_address)) { log_warn(LD_CONFIG, "Onion address has wrong format: '%s'", onion_address); goto err; @@ -1547,7 +1227,7 @@ rend_client_allow_non_anonymous_connection(const or_options_t *options) #else (void)options; return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } /* At compile-time, was non-anonymous mode enabled via @@ -1562,6 +1242,6 @@ rend_client_non_anonymous_mode_enabled(const or_options_t *options) return 1; #else return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } diff --git a/src/or/rendclient.h b/src/or/rendclient.h index b8f8c2f871..e8495ce09c 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 */ /** @@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ, void rend_client_refetch_v2_renddesc(rend_data_t *rend_query); int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs); void rend_client_cancel_descriptor_fetches(void); -void rend_client_purge_last_hid_serv_requests(void); int rend_client_report_intro_point_failure(extend_info_t *failed_intro, - rend_data_t *rend_query, + rend_data_t *rend_data, unsigned int failure_type); -int rend_client_rendezvous_acked(origin_circuit_t *circ, - const uint8_t *request, - size_t request_len); int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -54,5 +50,5 @@ void rend_service_authorization_free_all(void); int rend_client_allow_non_anonymous_connection(const or_options_t *options); int rend_client_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDCLIENT_H) */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index d9d39b1f19..458a90058f 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,18 @@ * 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 "hs_client.h" #include "rendservice.h" #include "rephist.h" #include "router.h" @@ -393,7 +398,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out, /** Attempt to parse the given <b>desc_str</b> and return true if this * succeeds, false otherwise. */ -static int +STATIC int rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc) { rend_service_descriptor_t *test_parsed = NULL; @@ -475,7 +480,10 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, tor_assert(descriptor_cookie); } /* Obtain service_id from public key. */ - crypto_pk_get_digest(service_key, service_id); + if (crypto_pk_get_digest(service_key, service_id) < 0) { + log_warn(LD_BUG, "Couldn't compute service key digest."); + return -1; + } /* Calculate current time-period. */ time_period = get_time_period(now, period, service_id); /* Determine how many seconds the descriptor will be valid. */ @@ -691,7 +699,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out) /** Return true iff <b>query</b> is a syntactically valid service ID (as * generated by rend_get_service_id). */ int -rend_valid_service_id(const char *query) +rend_valid_v2_service_id(const char *query) { if (strlen(query) != REND_SERVICE_ID_LEN_BASE32) return 0; @@ -761,7 +769,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) @@ -769,15 +777,15 @@ 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) - r = rend_client_introduction_acked(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) @@ -785,15 +793,15 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = rend_client_receive_rendezvous(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = rend_service_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = rend_client_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); break; default: tor_fragile_assert(); @@ -804,124 +812,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. @@ -1104,7 +994,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options) * service. */ void -assert_circ_anonymity_ok(origin_circuit_t *circ, +assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options) { tor_assert(options); @@ -1116,3 +1006,32 @@ assert_circ_anonymity_ok(origin_circuit_t *circ, } } +/* 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 090e6f25e0..c35d7272fd 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, @@ -43,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free( rend_encoded_v2_service_descriptor_t *desc); void rend_intro_point_free(rend_intro_point_t *intro); -int rend_valid_service_id(const char *query); +int rend_valid_v2_service_id(const char *query); int rend_valid_descriptor_id(const char *query); int rend_valid_client_name(const char *client_name); int rend_encode_v2_descriptors(smartlist_t *descs_out, @@ -60,15 +47,8 @@ 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); @@ -80,8 +60,15 @@ int rend_auth_decode_cookie(const char *cookie_in, int rend_allow_non_anonymous_connection(const or_options_t* options); int rend_non_anonymous_mode_enabled(const or_options_t *options); -void assert_circ_anonymity_ok(origin_circuit_t *circ, +void assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options); -#endif +#ifdef RENDCOMMON_PRIVATE + +STATIC int +rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc); + +#endif /* defined(RENDCOMMON_PRIVATE) */ + +#endif /* !defined(TOR_RENDCOMMON_H) */ diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 441d5043ce..c4a34ca62c 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 */ /** @@ -12,17 +12,20 @@ #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "crypto.h" #include "dos.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]; @@ -34,15 +37,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. */ @@ -71,7 +73,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), @@ -96,7 +97,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); @@ -104,16 +106,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", @@ -124,8 +124,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; } @@ -134,8 +135,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]; @@ -144,26 +145,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 @@ -182,7 +167,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; " @@ -203,14 +189,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; } @@ -222,8 +209,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; } @@ -270,7 +255,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; @@ -281,12 +266,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); @@ -335,7 +320,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.", @@ -358,7 +343,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. */ @@ -369,7 +355,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..6cc1fc8d95 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,14 +12,14 @@ #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, size_t request_len); -#endif +#endif /* !defined(TOR_RENDMID_H) */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index da200d1381..fed8c97ebb 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,6 +17,8 @@ #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" @@ -75,58 +77,11 @@ static ssize_t rend_service_parse_intro_for_v3( static int rend_service_check_private_dir(const or_options_t *options, const rend_service_t *s, int create); -static int rend_service_check_private_dir_impl(const or_options_t *options, - const rend_service_t *s, - int create); - -/** Represents the mapping from a virtual port of a rendezvous service to - * a real port on some IP. - */ -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 seconds should we spend trying to connect to a requested - * rendezvous point before giving up? */ -#define MAX_REND_TIMEOUT 30 -/* Default, minimum and maximum values for the maximum rendezvous failures - * consensus parameter. */ -#define MAX_REND_FAILURES_DEFAULT 2 -#define MAX_REND_FAILURES_MIN 1 -#define MAX_REND_FAILURES_MAX 10 - -/** How many times will a hidden service operator attempt to connect to - * a requested rendezvous point before giving up? */ -static int -get_max_rend_failures(void) -{ - return networkstatus_get_param(NULL, "hs_service_max_rdv_failures", - MAX_REND_FAILURES_DEFAULT, - MAX_REND_FAILURES_MIN, - MAX_REND_FAILURES_MAX); -} +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() @@ -136,21 +91,64 @@ 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. + */ +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; @@ -218,137 +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_all(void) +rend_service_free_staging_list(void) { - if (!rend_service_list) - return; - - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_list); - rend_service_list = NULL; + 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; + } } -/** Validate <b>service</b> and add it to <b>service_list</b>, or to - * the global rend_service_list if <b>service_list</b> is NULL. - * Return 0 on success. On failure, free <b>service</b> and return -1. - * Takes ownership of <b>service</b>. - */ -static int -rend_add_service(smartlist_t *service_list, rend_service_t *service) +/** Release all the storage held in both rend_service_list and + * rend_service_staging_list. */ +void +rend_service_free_all(void) { - int i; - rend_service_port_config_t *p; + 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_t *s_list; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - /* No global HS list, which is a failure. */ - return -1; - } +/* Initialize the subsystem. */ +void +rend_service_init(void) +{ + tor_assert(!rend_service_list); + tor_assert(!rend_service_staging_list); - s_list = rend_service_list; - } else { - s_list = service_list; - } + rend_service_list = smartlist_new(); + rend_service_staging_list = smartlist_new(); +} - service->intro_nodes = smartlist_new(); - service->expiring_nodes = smartlist_new(); +/* 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_validate_service(const smartlist_t *service_list, + const rend_service_t *service) +{ + 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 && - (!service->clients || - 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 (!service->ports || !smartlist_len(service->ports)) { - log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; " - "ignoring.", + 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(s_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(s_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 @@ -367,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)? * @@ -394,14 +394,12 @@ rend_service_parse_port_config(const char *string, const char *sep, smartlist_split_string(sl, string, sep, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2); if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) { - if (err_msg_out) - err_msg = tor_strdup("Bad syntax in hidden service port configuration."); + 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; @@ -429,10 +427,8 @@ rend_service_parse_port_config(const char *string, const char *sep, } 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; @@ -440,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 */ @@ -463,7 +457,11 @@ rend_service_parse_port_config(const char *string, const char *sep, err: tor_free(addrport); - if (err_msg_out) *err_msg_out = err_msg; + 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); @@ -477,196 +475,260 @@ rend_service_port_config_free(rend_service_port_config_t *p) tor_free(p); } -/* Check the directory for <b>service</b>, and add the service to - * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL. - * Only add the service to the list if <b>validate_only</b> is false. - * If <b>validate_only</b> is true, free the service. - * If <b>service</b> is NULL, ignore it, and return 0. - * Returns 0 on success, and -1 on failure. - * Takes ownership of <b>service</b>, either freeing it, or adding it to the - * global service list. - */ -STATIC int -rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only) +/* Copy relevant data from service src to dst while pruning the service lists. + * This should only be called during the pruning process which takes existing + * services and copy their data to the newly configured services. The src + * service replaycache will be set to NULL after this call. */ +static void +copy_service_on_prunning(rend_service_t *dst, rend_service_t *src) { - if (!service) { - /* It is ok for a service to be NULL, this means there are no services */ - return 0; - } + tor_assert(dst); + tor_assert(src); + + /* Keep the timestamps for when the content changed and the next upload + * time so we can properly upload the descriptor if needed for the new + * service object. */ + dst->desc_is_dirty = src->desc_is_dirty; + dst->next_upload_time = src->next_upload_time; + /* Move the replaycache to the new object. */ + dst->accepted_intro_dh_parts = src->accepted_intro_dh_parts; + src->accepted_intro_dh_parts = NULL; + /* Copy intro point information to destination service. */ + dst->intro_period_started = src->intro_period_started; + dst->n_intro_circuits_launched = src->n_intro_circuits_launched; + dst->n_intro_points_wanted = src->n_intro_points_wanted; +} - if (rend_service_check_private_dir(options, service, !validate_only) - < 0) { - rend_service_free(service); - return -1; +/* 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; } - if (validate_only) { - rend_service_free(service); - return 0; - } else { - /* Use service_list for unit tests */ - smartlist_t *s_list = NULL; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - /* No global HS list, which is a failure, because we plan on adding to - * it */ - return -1; + /* 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; } - s_list = rend_service_list; - } else { - s_list = service_list; + 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); + + /* Copy needed information from old to new. */ + copy_service_on_prunning(new, old); + + /* This regular service will survive the closing IPs step after. */ + smartlist_add(surviving_services, old); + break; + } 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; + if (ocirc->rend_data == NULL) { + /* This is a v3 circuit, ignore it. */ + continue; } - /* s_list can not be NULL here - if both service_list and rend_service_list - * are NULL, and validate_only is false, we exit earlier in the function - */ - if (BUG(!s_list)) { - return -1; + 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; } - /* Ignore service failures until 030 */ - rend_add_service(s_list, service); - return 0; + 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; + + if (!rend_service_staging_list) { + rend_service_staging_list = smartlist_new(); + } + + 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); } } -/** Set up rend_service_list, based on the values of HiddenServiceDir and - * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on - * failure. (If <b>validate_only</b> is set, parse, warn and return as - * normal, but don't actually change the configured services.) - */ +/* Copy all the relevant data that the hs_service object contains over to the + * rend_service_t object. The reason to do so is because when configuring a + * service, we go through a generic handler that creates an hs_service_t + * object which so we have to copy the parsed values to a rend service object + * which is version 2 specific. */ +static void +service_config_shadow_copy(rend_service_t *service, + hs_service_config_t *config) +{ + tor_assert(service); + tor_assert(config); + + service->directory = tor_strdup(config->directory_path); + service->dir_group_readable = config->dir_group_readable; + service->allow_unknown_ports = config->allow_unknown_ports; + /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535) + * if the code flow is right so this cast is safe. But just in case, we'll + * check it. */ + service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit; + if (BUG(config->max_streams_per_rdv_circuit > + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) { + service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT; + } + service->max_streams_close_circuit = config->max_streams_close_circuit; + service->n_intro_points_wanted = config->num_intro_points; + /* Switching ownership of the ports to the rend service object. */ + smartlist_add_all(service->ports, config->ports); + smartlist_free(config->ports); + config->ports = NULL; +} + +/* Parse the hidden service configuration starting at <b>line_</b> using the + * already configured generic service configuration in <b>config</b>. This + * function will translate the config object to a rend_service_t and add it to + * the temporary list if valid. If <b>validate_only</b> is set, parse, warn + * and return as normal but don't actually add the service to the list. */ int -rend_config_services(const or_options_t *options, int validate_only) +rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config) { - config_line_t *line; + const config_line_t *line; rend_service_t *service = NULL; - rend_service_port_config_t *portcfg; - 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")) { - /* register the service we just finished parsing - * this code registers every service except the last one parsed, - * which is registered below the loop */ - if (rend_service_check_dir_and_add(NULL, options, service, - validate_only) < 0) { - return -1; - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - 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. */ smartlist_t *type_names_split, *clients; const char *authname; - int num_clients; if (service->auth_type != REND_NO_AUTH) { log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " "lines for a single service."); - rend_service_free(service); - return -1; + goto err; } type_names_split = smartlist_new(); smartlist_split_string(type_names_split, line->value, " ", 0, 2); @@ -675,8 +737,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")) { @@ -690,8 +751,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) { @@ -708,14 +768,15 @@ rend_config_services(const or_options_t *options, int validate_only) SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); smartlist_free(type_names_split); /* Remove duplicate client names. */ - num_clients = smartlist_len(clients); - smartlist_sort_strings(clients); - smartlist_uniq_strings(clients); - if (smartlist_len(clients) < num_clients) { - log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " - "duplicate client name(s); removing.", - num_clients - smartlist_len(clients)); - num_clients = smartlist_len(clients); + { + int num_clients = smartlist_len(clients); + smartlist_sort_strings(clients); + smartlist_uniq_strings(clients); + if (smartlist_len(clients) < num_clients) { + log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " + "duplicate client name(s); removing.", + num_clients - smartlist_len(clients)); + } } SMARTLIST_FOREACH_BEGIN(clients, const char *, client_name) { @@ -728,8 +789,7 @@ rend_config_services(const or_options_t *options, int validate_only) client_name, REND_CLIENTNAME_MAX_LEN); SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); smartlist_free(clients); - rend_service_free(service); - return -1; + goto err; } client = tor_malloc_zero(sizeof(rend_authorized_client_t)); client->client_name = tor_strdup(client_name); @@ -751,109 +811,29 @@ rend_config_services(const or_options_t *options, int validate_only) smartlist_len(service->clients), service->auth_type == REND_BASIC_AUTH ? 512 : 16, service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - 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; } } - /* register the final service after we have finished parsing all services - * this code only registers the last service, other services are registered - * within the loop. It is ok for this service to be NULL, it is ignored. */ - if (rend_service_check_dir_and_add(NULL, options, service, - validate_only) < 0) { - return -1; + /* 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; } - /* 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); - } - }); - - /* 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, using @@ -942,7 +922,7 @@ int rend_service_del_ephemeral(const char *service_id) { rend_service_t *s; - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal."); return -1; } @@ -951,7 +931,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; } @@ -967,13 +947,14 @@ rend_service_del_ephemeral(const char *service_id) (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); - tor_assert(oc->rend_data); - if (!tor_memeq(s->pk_digest, oc->rend_data->rend_pk_digest, DIGEST_LEN)) + if (oc->rend_data == NULL || + !rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { continue; + } log_debug(LD_REND, "Closing intro point %s for service %s.", 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); @@ -985,6 +966,45 @@ 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); + { + char *msg; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + 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.%s", + 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, msg); + rend_service_dump_stats(severity); + tor_free(msg); + } + } +} + /** Replace the old value of <b>service</b>-\>desc with one that reflects * the other fields in service. */ @@ -992,7 +1012,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); @@ -1013,9 +1032,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; } @@ -1037,6 +1057,26 @@ 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 @@ -1046,15 +1086,8 @@ rend_service_update_descriptor(rend_service_t *service) static char * rend_service_path(const rend_service_t *service, const char *file_name) { - char *file_path = NULL; - tor_assert(service->directory); - - /* Can never fail: asserts rather than leaving file_path NULL. */ - tor_asprintf(&file_path, "%s%s%s", - service->directory, PATH_SEPARATOR, file_name); - - return file_path; + return hs_path_from_filename(service->directory, file_name); } /* Allocate and return a string containing the path to the single onion @@ -1067,7 +1100,7 @@ rend_service_sos_poison_path(const rend_service_t *service) return rend_service_path(service, sos_poison_fname); } -/** Return True if hidden services <b>service> has been poisoned by single +/** 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) @@ -1080,7 +1113,7 @@ service_is_single_onion_poisoned(const rend_service_t *service) return 0; } - if (!service->directory) { + if (rend_service_is_ephemeral(service)) { return 0; } @@ -1132,8 +1165,13 @@ rend_service_verify_single_onion_poison(const rend_service_t* s, } /* Ephemeral services are checked at ADD_ONION time */ - if (!s->directory) { - return 0; + 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 @@ -1176,7 +1214,7 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service, int retval = -1; char *poison_fname = NULL; - if (!service->directory) { + if (rend_service_is_ephemeral(service)) { log_info(LD_REND, "Ephemeral HS started in non-anonymous mode."); return 0; } @@ -1189,7 +1227,8 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service, } /* Make sure the directory was created before calling this function. */ - if (BUG(rend_service_check_private_dir_impl(options, service, 0) < 0)) + 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); @@ -1226,7 +1265,7 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service, return retval; } -/** We just got launched in Single Onion Mode. That's a non-anoymous mode for +/** 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.) @@ -1243,6 +1282,16 @@ rend_service_poison_new_single_onion_dir(const rend_service_t *s, /* 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) { @@ -1262,22 +1311,17 @@ rend_service_poison_new_single_onion_dir(const rend_service_t *s, int rend_service_load_all_keys(const smartlist_t *service_list) { - const smartlist_t *s_list = NULL; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - return -1; - } - s_list = rend_service_list; - } else { - s_list = service_list; + /* 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; @@ -1309,9 +1353,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); } @@ -1334,32 +1378,6 @@ rend_service_derive_key_digests(struct rend_service_t *s) return 0; } -/* Implements the directory check from rend_service_check_private_dir, - * without doing the single onion poison checks. */ -static int -rend_service_check_private_dir_impl(const or_options_t *options, - const rend_service_t *s, - int create) -{ - cpd_check_t check_opts = CPD_NONE; - if (create) { - check_opts |= CPD_CREATE; - } else { - check_opts |= CPD_CHECK_MODE_ONLY; - check_opts |= CPD_CHECK; - } - if (s->dir_group_readable) { - check_opts |= CPD_GROUP_READ; - } - /* Check/create directory */ - if (check_private_dir(s->directory, check_opts, options->User) < 0) { - log_warn(LD_REND, "Checking service directory %s failed.", s->directory); - return -1; - } - - return 0; -} - /** Make sure that the directory for <b>s</b> is private, using the config in * <b>options</b>. * If <b>create</b> is true: @@ -1380,7 +1398,8 @@ rend_service_check_private_dir(const or_options_t *options, } /* Check/create directory */ - if (rend_service_check_private_dir_impl(options, s, create) < 0) { + if (hs_check_service_private_dir(options->User, s->directory, + s->dir_group_readable, create) < 0) { return -1; } @@ -1438,9 +1457,9 @@ rend_service_load_keys(rend_service_t *s) char *fname = NULL; char buf[128]; - /* Make sure the directory was created and single onion poisoning was - * checked before calling this function */ - if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0)) + /* Create the directory if needed which will also poison it in case of + * single onion service. */ + if (rend_service_check_private_dir(get_options(), s, 1) < 0) goto err; /* Load key */ @@ -1469,7 +1488,7 @@ rend_service_load_keys(rend_service_t *s) log_warn(LD_FS,"Unable to make hidden hostname file %s group-readable.", fname); } -#endif +#endif /* !defined(_WIN32) */ /* If client authorization is configured, load or generate keys. */ if (s->auth_type != REND_NO_AUTH) { @@ -1698,24 +1717,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. */ @@ -1806,7 +1807,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, const or_options_t *options = get_options(); char *err_msg = NULL; int err_msg_severity = LOG_WARN; - const char *stage_descr = NULL; + 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]; @@ -1841,14 +1842,15 @@ rend_service_receive_introduction(origin_circuit_t *circuit, 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 " @@ -2034,14 +2036,14 @@ 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); /* Launch a circuit to the client's chosen rendezvous point. */ - int max_rend_failures=get_max_rend_failures(); + int max_rend_failures=hs_get_service_max_rend_failures(); for (i=0;i<max_rend_failures;i++) { int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; @@ -2073,8 +2075,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 = @@ -2088,7 +2089,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); @@ -2151,7 +2154,7 @@ find_rp_for_intro(const rend_intro_cell_t *intro, if (intro->version == 0 || intro->version == 1) { rp_nickname = (const char *)(intro->u.v0_v1.rp); - node = node_get_by_nickname(rp_nickname, 0); + node = node_get_by_nickname(rp_nickname, NNF_NO_WARN_UNNAMED); if (!node) { if (err_msg_out) { tor_asprintf(&err_msg, @@ -2705,7 +2708,14 @@ rend_service_decrypt_intro( /* Check that this cell actually matches this service key */ /* first DIGEST_LEN bytes of request is intro or service pk digest */ - crypto_pk_get_digest(key, (char *)key_digest); + if (crypto_pk_get_digest(key, (char *)key_digest) < 0) { + if (err_msg_out) + *err_msg_out = tor_strdup("Couldn't compute RSA digest."); + log_warn(LD_BUG, "Couldn't compute key digest."); + status = -7; + goto err; + } + if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) { if (err_msg_out) { base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1, @@ -2736,10 +2746,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); @@ -2932,35 +2940,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) cpath_build_state_t *newstate, *oldstate; 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; - - /* We check failure_count >= get_max_rend_failures()-1 below, and the -1 - * is because we increment the failure count for our current failure - * *after* this clause. */ - int max_rend_failures = get_max_rend_failures() - 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); @@ -3113,15 +3092,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. */ @@ -3129,23 +3160,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; + const char *rend_pk_digest; tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); 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); @@ -3153,13 +3184,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. */ @@ -3186,9 +3226,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; @@ -3206,42 +3245,25 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) (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. */ - crypto_pk_t *intro_key = circuit->intro_key; - /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */ - r = crypto_pk_asn1_encode(intro_key, buf+2, - RELAY_PAYLOAD_SIZE-2); - 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 */ @@ -3253,7 +3275,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; @@ -3272,22 +3293,24 @@ 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); goto err; } 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); /* We've just successfully established a intro circuit to one of our * introduction point, account for it. */ intro = find_intro_point(circuit); @@ -3330,6 +3353,7 @@ 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); @@ -3337,18 +3361,24 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) assert_circ_anonymity_ok(circuit, get_options()); tor_assert(circuit->rend_data); - /* Declare the circuit dirty to avoid reuse, and for path-bias */ - if (!circuit->base_.timestamp_dirty) - circuit->base_.timestamp_dirty = time(NULL); + /* 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. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + circuit->base_.timestamp_dirty = time(NULL); /* This may be redundant */ pathbias_count_use_attempt(circuit); 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 " @@ -3377,8 +3407,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."); @@ -3387,7 +3416,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."); @@ -3400,11 +3429,10 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* Send the cell */ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit), RELAY_COMMAND_RENDEZVOUS1, - buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN, + buf, HS_LEGACY_RENDEZVOUS_CELL_SIZE, circuit->cpath->prev)<0) { log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell."); - reason = END_CIRC_REASON_INTERNAL; - goto err; + goto done; } crypto_dh_free(hop->rend_dh_handshake_state); @@ -3451,8 +3479,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) { @@ -3461,8 +3489,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) { @@ -3501,7 +3530,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)) { @@ -3577,13 +3606,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); @@ -3815,6 +3847,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. */ @@ -3890,10 +3935,13 @@ 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) { return; } @@ -3911,6 +3959,23 @@ 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); + + /* For the normal use case, 3 intro points plus 2 extra for performance and + * allow that twice because once every 24h or so, we can do it twice for two + * descriptors that is the current one and the next one. So (3 + 2) * 2 == + * 12 allowed attempts for one period. */ + return ((n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA) * 2); +} + /** 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. @@ -3920,10 +3985,9 @@ 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( @@ -3940,7 +4004,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; @@ -3955,23 +4018,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); @@ -3991,17 +4060,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 @@ -4021,8 +4090,6 @@ 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; @@ -4058,6 +4125,9 @@ rend_consider_services_intro_points(void) * 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); @@ -4193,8 +4263,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); @@ -4211,60 +4281,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 @@ -4277,17 +4293,15 @@ 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"); + 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.", @@ -4313,41 +4327,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; } @@ -4409,3 +4391,19 @@ rend_service_non_anonymous_mode_enabled(const or_options_t *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 /* defined(TOR_UNIT_TESTS) */ + diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 3b185672f6..5946e31861 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,6 +61,8 @@ struct rend_intro_cell_s { uint8_t dh[DH_KEY_LEN]; }; +#ifdef RENDSERVICE_PRIVATE + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ @@ -119,24 +119,32 @@ typedef struct rend_service_t { STATIC void rend_service_free(rend_service_t *service); STATIC char *rend_service_sos_poison_path(const rend_service_t *service); -STATIC int rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only); STATIC int rend_service_verify_single_onion_poison( const rend_service_t *s, const or_options_t *options); STATIC int rend_service_poison_new_single_onion_dir( const rend_service_t *s, const or_options_t* options); -#endif +#ifdef TOR_UNIT_TESTS + +STATIC void set_rend_service_list(smartlist_t *new_list); +STATIC void set_rend_rend_service_staging_list(smartlist_t *new_list); +STATIC void rend_service_prune_list_impl_(void); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(RENDSERVICE_PRIVATE) */ -int num_rend_services(void); -int rend_config_services(const or_options_t *options, int validate_only); +int rend_num_services(void); +int rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config); +void rend_service_prune_list(void); +void rend_service_free_staging_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); -void rend_consider_services_intro_points(void); +void rend_consider_services_intro_points(time_t now); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); void rend_consider_descriptor_republication(void); @@ -159,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); @@ -166,6 +178,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn, origin_circuit_t *circ); void rend_service_dump_stats(int severity); void rend_service_free_all(void); +void rend_service_init(void); rend_service_port_config_t *rend_service_parse_port_config(const char *string, const char *sep, @@ -201,5 +214,5 @@ int rend_service_allow_non_anonymous_connection(const or_options_t *options); int rend_service_reveal_startup_time(const or_options_t *options); int rend_service_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDSERVICE_H) */ diff --git a/src/or/rephist.c b/src/or/rephist.c index f0bac57898..345722d8ce 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -84,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; @@ -165,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; @@ -264,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 @@ -905,9 +947,9 @@ rep_hist_record_mtbf_data(time_t now, int missing_means_down) base16_encode(dbuf, sizeof(dbuf), digest, DIGEST_LEN); if (missing_means_down && hist->start_of_run && - !router_get_by_id_digest(digest)) { + !connection_or_digest_is_known_relay(digest)) { /* We think this relay is running, but it's not listed in our - * routerlist. Somehow it fell out without telling us it went + * consensus. Somehow it fell out without telling us it went * down. Complain and also correct it. */ log_info(LD_HIST, "Relay '%s' is listed as up in rephist, but it's not in " @@ -1758,6 +1800,42 @@ 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; + + /* 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; + } else { + idle_delta = now - last_prediction_add_time; + } + + /* 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 @@ -1767,21 +1845,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 @@ -1812,6 +1909,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); @@ -1828,7 +1931,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) { @@ -1888,6 +1992,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; @@ -1901,7 +2017,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; @@ -1923,7 +2040,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; @@ -1949,105 +2066,6 @@ rep_hist_circbuilding_dormant(time_t now) return 1; } -/** Structure to track how many times we've done each public key operation. */ -static struct { - /** How many directory objects have we signed? */ - unsigned long n_signed_dir_objs; - /** How many routerdescs have we signed? */ - unsigned long n_signed_routerdescs; - /** How many directory objects have we verified? */ - unsigned long n_verified_dir_objs; - /** How many routerdescs have we verified */ - unsigned long n_verified_routerdescs; - /** How many onionskins have we encrypted to build circuits? */ - unsigned long n_onionskins_encrypted; - /** How many onionskins have we decrypted to do circuit build requests? */ - unsigned long n_onionskins_decrypted; - /** How many times have we done the TLS handshake as a client? */ - unsigned long n_tls_client_handshakes; - /** How many times have we done the TLS handshake as a server? */ - unsigned long n_tls_server_handshakes; - /** How many PK operations have we done as a hidden service client? */ - unsigned long n_rend_client_ops; - /** How many PK operations have we done as a hidden service midpoint? */ - unsigned long n_rend_mid_ops; - /** How many PK operations have we done as a hidden service provider? */ - unsigned long n_rend_server_ops; -} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0}; - -/** Increment the count of the number of times we've done <b>operation</b>. */ -void -note_crypto_pk_op(pk_op_t operation) -{ - switch (operation) - { - case SIGN_DIR: - pk_op_counts.n_signed_dir_objs++; - break; - case SIGN_RTR: - pk_op_counts.n_signed_routerdescs++; - break; - case VERIFY_DIR: - pk_op_counts.n_verified_dir_objs++; - break; - case VERIFY_RTR: - pk_op_counts.n_verified_routerdescs++; - break; - case ENC_ONIONSKIN: - pk_op_counts.n_onionskins_encrypted++; - break; - case DEC_ONIONSKIN: - pk_op_counts.n_onionskins_decrypted++; - break; - case TLS_HANDSHAKE_C: - pk_op_counts.n_tls_client_handshakes++; - break; - case TLS_HANDSHAKE_S: - pk_op_counts.n_tls_server_handshakes++; - break; - case REND_CLIENT: - pk_op_counts.n_rend_client_ops++; - break; - case REND_MID: - pk_op_counts.n_rend_mid_ops++; - break; - case REND_SERVER: - pk_op_counts.n_rend_server_ops++; - break; - default: - log_warn(LD_BUG, "Unknown pk operation %d", operation); - } -} - -/** Log the number of times we've done each public/private-key operation. */ -void -dump_pk_ops(int severity) -{ - tor_log(severity, LD_HIST, - "PK operations: %lu directory objects signed, " - "%lu directory objects verified, " - "%lu routerdescs signed, " - "%lu routerdescs verified, " - "%lu onionskins encrypted, " - "%lu onionskins decrypted, " - "%lu client-side TLS handshakes, " - "%lu server-side TLS handshakes, " - "%lu rendezvous client operations, " - "%lu rendezvous middle operations, " - "%lu rendezvous server operations.", - pk_op_counts.n_signed_dir_objs, - pk_op_counts.n_verified_dir_objs, - pk_op_counts.n_signed_routerdescs, - pk_op_counts.n_verified_routerdescs, - pk_op_counts.n_onionskins_encrypted, - pk_op_counts.n_onionskins_decrypted, - pk_op_counts.n_tls_client_handshakes, - pk_op_counts.n_tls_server_handshakes, - pk_op_counts.n_rend_client_ops, - pk_op_counts.n_rend_mid_ops, - pk_op_counts.n_rend_server_ops); -} - /*** Exit port statistics ***/ /* Some constants */ @@ -2536,7 +2554,7 @@ rep_hist_format_buffer_stats(time_t now) processed_cells_string, queued_cells_string, time_in_queue_string, - (number_of_circuits + SHARES - 1) / SHARES); + CEIL_DIV(number_of_circuits, SHARES)); tor_free(processed_cells_string); tor_free(queued_cells_string); tor_free(time_in_queue_string); @@ -3210,8 +3228,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. @@ -3220,7 +3237,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; } @@ -3228,6 +3245,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 ff4810a56d..496e366865 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); @@ -119,5 +118,30 @@ extern int onion_handshakes_requested[MAX_ONION_HANDSHAKE_TYPE+1]; extern int onion_handshakes_assigned[MAX_ONION_HANDSHAKE_TYPE+1]; #endif -#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 /* !defined(TOR_REPHIST_H) */ diff --git a/src/or/replaycache.c b/src/or/replaycache.c index 8290fa6964..3d42deb90a 100644 --- a/src/or/replaycache.c +++ b/src/or/replaycache.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 */ /** diff --git a/src/or/replaycache.h b/src/or/replaycache.h index 64a6caf5f5..1cae3497ae 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 */ /** @@ -29,7 +29,7 @@ struct replaycache_s { digest256map_t *digests_seen; }; -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* replaycache_t free/new */ @@ -51,7 +51,7 @@ STATIC int replaycache_add_and_test_internal( STATIC void replaycache_scrub_if_needed_internal( time_t present, replaycache_t *r); -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* * replaycache_t methods @@ -62,5 +62,5 @@ int replaycache_add_test_and_elapsed( replaycache_t *r, const void *data, size_t len, time_t *elapsed); void replaycache_scrub_if_needed(replaycache_t *r); -#endif +#endif /* !defined(TOR_REPLAYCACHE_H) */ diff --git a/src/or/router.c b/src/or/router.c index 35b6bd203c..854eaa71a9 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 @@ -131,7 +131,8 @@ get_onion_key(void) } /** Store a full copy of the current onion key into *<b>key</b>, and a full - * copy of the most recent onion key into *<b>last</b>. + * copy of the most recent onion key into *<b>last</b>. Store NULL into + * a pointer if the corresponding key does not exist. */ void dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) @@ -139,8 +140,10 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) tor_assert(key); tor_assert(last); tor_mutex_acquire(key_lock); - tor_assert(onionkey); - *key = crypto_pk_copy_full(onionkey); + if (onionkey) + *key = crypto_pk_copy_full(onionkey); + else + *key = NULL; if (lastonionkey) *last = crypto_pk_copy_full(lastonionkey); else @@ -148,6 +151,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 * @@ -162,10 +210,14 @@ construct_ntor_key_map(void) { di_digest256_map_t *m = NULL; - dimap_add_entry(&m, - curve25519_onion_key.pubkey.public_key, - tor_memdup(&curve25519_onion_key, - sizeof(curve25519_keypair_t))); + if (!tor_mem_is_zero((const char*) + curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN)) { + dimap_add_entry(&m, + curve25519_onion_key.pubkey.public_key, + tor_memdup(&curve25519_onion_key, + sizeof(curve25519_keypair_t))); + } if (!tor_mem_is_zero((const char*) last_curve25519_onion_key.pubkey.public_key, CURVE25519_PUBKEY_LEN)) { @@ -212,7 +264,11 @@ set_server_identity_key(crypto_pk_t *k) { crypto_pk_free(server_identitykey); server_identitykey = k; - crypto_pk_get_digest(server_identitykey, server_identitykey_digest); + if (crypto_pk_get_digest(server_identitykey, + server_identitykey_digest) < 0) { + log_err(LD_BUG, "Couldn't compute our own identity key digest."); + tor_assert(0); + } } /** Make sure that we have set up our identity keys to match or not match as @@ -683,6 +739,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 @@ -693,12 +790,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 */ @@ -849,7 +940,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. */ @@ -871,8 +967,12 @@ init_keys(void) } cert = get_my_v3_authority_cert(); if (cert) { - crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, - v3_digest); + if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, + v3_digest) < 0) { + log_err(LD_BUG, "Couldn't compute my v3 authority identity key " + "digest."); + return -1; + } v3_digest_set = 1; } } @@ -901,7 +1001,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. */ @@ -923,7 +1024,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); @@ -971,7 +1072,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; } @@ -979,7 +1080,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. */ @@ -1178,9 +1279,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); @@ -1312,8 +1413,15 @@ extend_info_from_router(const routerinfo_t *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); } @@ -1372,13 +1480,23 @@ consider_testing_reachability(int test_or, int test_dir) !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); } } @@ -1493,32 +1611,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; } @@ -1530,7 +1635,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. @@ -1538,7 +1643,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. @@ -1555,8 +1660,7 @@ MOCK_IMPL(int, server_mode,(const or_options_t *options)) { if (options->ClientOnly) return 0; - /* XXXX 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. @@ -1762,12 +1866,12 @@ static routerinfo_t *desc_routerinfo = NULL; static extrainfo_t *desc_extrainfo = NULL; /** Why did we most recently decide to regenerate our descriptor? Used to * tell the authorities why we're sending it to them. */ -static const char *desc_gen_reason = NULL; +static const char *desc_gen_reason = "uninitialized reason"; /** Since when has our descriptor been "clean"? 0 if we need to regenerate it * 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; @@ -1848,7 +1952,7 @@ router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) desc_routerinfo->ipv6_exit_policy && compare_tor_addr_to_short_policy(addr, port, me->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED; -#endif +#endif /* 0 */ } else { return -1; } @@ -2181,19 +2285,17 @@ 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); + member = node_get_by_nickname(name, 0); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_contains_string(warned_nonexistent_family, name) && @@ -2207,11 +2309,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 */ @@ -2225,15 +2326,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. */ @@ -2358,6 +2455,9 @@ router_rebuild_descriptor(int force) desc_clean_since = time(NULL); desc_needs_upload = 1; desc_gen_reason = desc_dirty_reason; + if (BUG(desc_gen_reason == NULL)) { + desc_gen_reason = "descriptor was marked dirty earlier, for no reason."; + } desc_dirty_reason = NULL; control_event_my_descriptor_changed(); return 0; @@ -2414,6 +2514,9 @@ void mark_my_descriptor_dirty(const char *reason) { const or_options_t *options = get_options(); + if (BUG(reason == NULL)) { + reason = "marked descriptor dirty for unspecified reason"; + } if (server_mode(options) && options->PublishServerDescriptor_) log_info(LD_OR, "Decided to publish new relay descriptor: %s", reason); desc_clean_since = 0; @@ -2751,7 +2854,7 @@ router_dump_router_to_string(routerinfo_t *router, make_ntor_onion_key_crosscert(ntor_keypair, &router->cache_info.signing_key_cert->signing_key, router->cache_info.published_on, - MIN_ONION_KEY_LIFETIME, &sign); + get_onion_key_lifetime(), &sign); if (!cert) { log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!"); goto err; @@ -2837,7 +2940,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, @@ -2860,8 +2963,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; @@ -2872,10 +2974,13 @@ router_dump_router_to_string(routerinfo_t *router, if (options->BridgeRelay) { const char *bd; - if (options->PublishServerDescriptor_ & BRIDGE_DIRINFO) + if (options->BridgeDistribution && strlen(options->BridgeDistribution)) { + bd = options->BridgeDistribution; + } else { bd = "any"; - else - bd = "none"; + } + if (strchr(bd, '\n') || strchr(bd, '\r')) + bd = escaped(bd); smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd); } @@ -2893,7 +2998,7 @@ router_dump_router_to_string(routerinfo_t *router, /* 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); @@ -2915,12 +3020,12 @@ router_dump_router_to_string(routerinfo_t *router, if (decide_to_advertise_begindir(options, router->supports_tunnelled_dir_requests)) { - smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n")); + 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); @@ -2936,11 +3041,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))) { @@ -2951,7 +3055,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); @@ -2971,7 +3075,7 @@ router_dump_router_to_string(routerinfo_t *router, tor_free(s_dup); routerinfo_free(ri_tmp); } -#endif +#endif /* defined(DEBUG_ROUTER_DUMP_ROUTER_TO_STRING) */ goto done; @@ -3199,6 +3303,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(); @@ -3209,13 +3319,13 @@ 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 sha256_digest[DIGEST256_LEN]; - smartlist_add(chunks, tor_strdup("router-sig-ed25519 ")); + smartlist_add_strdup(chunks, "router-sig-ed25519 "); crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, ED_DESC_SIGNATURE_PREFIX, chunks, "", DIGEST_SHA256); @@ -3230,7 +3340,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, 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) { @@ -3265,7 +3375,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); @@ -3415,7 +3525,7 @@ router_get_description(char *buf, const routerinfo_t *ri) return "<null>"; return format_node_description(buf, ri->cache_info.identity_digest, - router_is_named(ri), + 0, ri->nickname, NULL, ri->addr); @@ -3525,7 +3635,7 @@ routerstatus_describe(const routerstatus_t *rs) return routerstatus_get_description(buf, rs); } -/** Return a human-readable description of the extend_info_t <b>ri</b>. +/** Return a human-readable description of the extend_info_t <b>ei</b>. * * This function is not thread-safe. Each call to this function invalidates * previous values returned by this function. @@ -3541,21 +3651,16 @@ extend_info_describe(const extend_info_t *ei) * verbose representation of the identity of <b>router</b>. The format is: * A dollar sign. * The upper-case hexadecimal encoding of the SHA1 hash of router's identity. - * A "=" if the router is named; a "~" if it is not. + * A "=" if the router is named (no longer implemented); a "~" if it is not. * The router's nickname. **/ void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { - const char *good_digest = networkstatus_get_router_digest_by_nickname( - router->nickname); - int is_named = good_digest && tor_memeq(good_digest, - router->cache_info.identity_digest, - DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/router.h b/src/or/router.h index c30a0301b7..3351400911 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); @@ -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); @@ -159,5 +160,5 @@ STATIC void get_platform_str(char *platform, size_t len); STATIC int router_write_fingerprint(int hashed); #endif -#endif +#endif /* !defined(TOR_ROUTER_H) */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index ca32228fc7..f0973044b5 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) @@ -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) @@ -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, @@ -527,7 +536,8 @@ ed_key_init_from_file(const char *fname, uint32_t flags, bad_cert = 1; } else if (signing_key && tor_cert_checksig(cert, &signing_key->pubkey, now) < 0) { - tor_log(severity, LD_OR, "Can't check certificate"); + tor_log(severity, LD_OR, "Can't check certificate: %s", + tor_cert_describe_signature_status(cert)); bad_cert = 1; } else if (cert->cert_expired) { tor_log(severity, LD_OR, "Certificate is expired"); @@ -659,10 +669,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 +689,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 +709,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 +731,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 +770,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 +783,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 +813,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; @@ -836,8 +884,12 @@ load_ed_keys(const or_options_t *options, time_t now) if (! ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey)) FAIL("The signing cert we have was not signed with the master key " "we loaded!"); - if (tor_cert_checksig(sign_cert, &id->pubkey, 0) < 0) - FAIL("The signing cert we loaded was not signed correctly!"); + if (tor_cert_checksig(sign_cert, &id->pubkey, 0) < 0) { + log_warn(LD_OR, "The signing cert we loaded was not signed " + "correctly: %s!", + tor_cert_describe_signature_status(sign_cert)); + goto err; + } } if (want_new_signing_key && sign_signing_key_with_id) { @@ -859,6 +911,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 +932,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 +976,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 +986,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; tor_cert_t *link_cert = 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_); - 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 +1044,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) { @@ -996,6 +1084,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 /* defined(TOR_UNIT_TESTS) */ + +/** + * Print the ISO8601-formated <b>expiration</b> for a certificate with + * some <b>description</b> to stdout. + * + * For example, for a signing certificate, this might print out: + * signing-cert-expiry: 2017-07-25 08:30:15 UTC + */ +static void +print_cert_expiration(const char *expiration, + const char *description) +{ + fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration); +} + +/** + * Log when a certificate, <b>cert</b>, with some <b>description</b> and + * stored in a file named <b>fname</b>, is going to expire. + */ +static void +log_ed_cert_expiration(const tor_cert_t *cert, + const char *description, + const char *fname) { + char expiration[ISO_TIME_LEN+1]; + + if (BUG(!cert)) { /* If the specified key hasn't been loaded */ + log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.", + description); + } else { + format_local_iso_time(expiration, cert->valid_until); + log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.", + description, fname, expiration); + print_cert_expiration(expiration, description); + } +} + +/** + * Log when our master signing key certificate expires. Used when tor is given + * the --key-expiration command-line option. + * + * Returns 0 on success and 1 on failure. + */ +static int +log_master_signing_key_cert_expiration(const or_options_t *options) +{ + const tor_cert_t *signing_key; + char *fn = NULL; + int failed = 0; + time_t now = approx_time(); + + fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert"); + + /* Try to grab our cached copy of the key. */ + signing_key = get_master_signing_key_cert(); + + tor_assert(server_identity_key_is_set()); + + /* Load our keys from disk, if necessary. */ + if (!signing_key) { + failed = load_ed_keys(options, now) < 0; + signing_key = get_master_signing_key_cert(); + } + + /* If we do have a signing key, log the expiration time. */ + if (signing_key) { + log_ed_cert_expiration(signing_key, "signing", fn); + } else { + log_warn(LD_OR, "Could not load signing key certificate from %s, so " \ + "we couldn't learn anything about certificate expiration.", fn); + } + + tor_free(fn); + + return failed; +} + +/** + * Log when a key certificate expires. Used when tor is given the + * --key-expiration command-line option. + * + * If an command argument is given, which should specify the type of + * key to get expiry information about (currently supported arguments + * are "sign"), get info about that type of certificate. Otherwise, + * print info about the supported arguments. + * + * Returns 0 on success and -1 on failure. + */ +int +log_cert_expiration(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + + if (!strcmp(arg, "sign")) { + return log_master_signing_key_cert_expiration(options); + } else { + fprintf(stderr, "No valid argument to --key-expiration found!\n"); + fprintf(stderr, "Currently recognised arguments are: 'sign'\n"); + + return -1; + } +} const ed25519_public_key_t * get_master_identity_key(void) @@ -1005,6 +1251,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 /* defined(TOR_UNIT_TESTS) */ + const ed25519_keypair_t * get_master_signing_keypair(void) { @@ -1079,7 +1343,9 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN]; *len_out = 0; - crypto_pk_get_digest(rsa_id_key, (char*)signed_data); + if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) { + return NULL; + } memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN); int r = crypto_pk_private_sign(onion_key, @@ -1095,12 +1361,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 +1405,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..3e67952ea0 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 /* !defined(TOR_ROUTERKEYS_H) */ + diff --git a/src/or/routerlist.c b/src/or/routerlist.c index f73ec9baa1..95b39d3571 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 */ /** @@ -93,6 +93,7 @@ #define ROUTERLIST_PRIVATE #include "or.h" #include "backtrace.h" +#include "bridges.h" #include "crypto_ed25519.h" #include "circuitstats.h" #include "config.h" @@ -426,8 +427,8 @@ list_sk_digests_for_authority_id, (const char *digest)) * 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_for_authority_id_and_sk,(const char *id_digest, + const char *sk_digest)) { download_status_t *dl = NULL; cert_list_t *cl = NULL; @@ -587,7 +588,7 @@ trusted_dirs_load_certs_from_string(const char *contents, int source, "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", @@ -930,7 +931,8 @@ authority_certs_fetch_resource_impl(const char *resource, const routerstatus_t *rs) { const or_options_t *options = get_options(); - int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0); + 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) { @@ -946,31 +948,34 @@ authority_certs_fetch_resource_impl(const char *resource, 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 && !get_via_tor) { + 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); - directory_initiate_command(&or_ap.addr, or_ap.port, - NULL, 0, /*no dirport*/ - dir_hint, - DIR_PURPOSE_FETCH_CERTIFICATE, - 0, - indirection, - resource, NULL, 0, 0); - return; - } - if (rs) { - /* If we've just downloaded a consensus from a directory, re-use that + 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 */ - directory_initiate_command_routerstatus(rs, - DIR_PURPOSE_FETCH_CERTIFICATE, - 0, indirection, resource, NULL, - 0, 0); + 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; } @@ -1012,7 +1017,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, char *resource = NULL; cert_list_t *cl; const or_options_t *options = get_options(); - const int cache = directory_caches_unknown_auth_certs(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]; @@ -1084,9 +1089,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 @@ -1203,7 +1209,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, 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; @@ -1243,7 +1249,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; @@ -1824,43 +1830,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)); @@ -1886,7 +1873,7 @@ router_picked_poor_directory_log(const routerstatus_t *rs) || !router_have_minimum_dir_info()) { return; } -#endif +#endif /* !LOG_FALSE_POSITIVES_DURING_BOOTSTRAP */ /* We couldn't find a node, or the one we have doesn't fit our preferences. * Sometimes this is normal, sometimes it can be a reachability issue. */ @@ -1952,6 +1939,21 @@ router_picked_poor_directory_log(const routerstatus_t *rs) } \ 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? */ @@ -1998,7 +2000,6 @@ 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; @@ -2020,7 +2021,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, /* Find all the running dirservers we know about. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { - int is_trusted, is_trusted_extrainfo; + int is_trusted; int is_overloaded; const routerstatus_t *status = node->rs; const country_t country = node->country; @@ -2031,20 +2032,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)) { @@ -2052,14 +2042,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. @@ -2191,11 +2184,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 && @@ -2205,8 +2196,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; } @@ -2313,7 +2307,7 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) { /* XXXX MOVE ? */ node_t fake_node; - const node_t *node = node_get_by_id(router->cache_info.identity_digest);; + const node_t *node = node_get_by_id(router->cache_info.identity_digest); if (node == NULL) { memset(&fake_node, 0, sizeof(fake_node)); fake_node.ri = (routerinfo_t *)router; @@ -2327,17 +2321,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, int direct_conn) +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; @@ -2798,8 +2791,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 @@ -2820,11 +2811,11 @@ 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; + const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); @@ -2836,14 +2827,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); - }); - } + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + if (node_allows_single_hop_exits(node)) { + /* Exclude relays that allow single hop exit circuits. This is an + * obsolete option since 0.2.9.2-alpha and done by default in + * 0.3.1.0-alpha. */ + smartlist_add(excludednodes, node); + } else if (rendezvous_v3 && + !node_supports_v3_rendezvous_point(node)) { + /* Exclude relays that do not support to rendezvous for a hidden service + * version 3. */ + smartlist_add(excludednodes, node); + } + } SMARTLIST_FOREACH_END(node); /* If the node_t is not found we won't be to exclude ourself but we * won't be able to pick ourself in router_choose_random_node() so @@ -2851,8 +2847,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, if ((r = router_get_my_routerinfo())) routerlist_add_node_and_family(excludednodes, r); - router_add_running_nodes_to_smartlist(sl, allow_invalid, - need_uptime, need_capacity, + router_add_running_nodes_to_smartlist(sl, need_uptime, need_capacity, need_guard, need_desc, pref_addr, direct_conn); log_debug(LD_CIRC, @@ -2958,7 +2953,7 @@ hex_digest_nickname_decode(const char *hexdigest, * <b>hexdigest</b> is malformed, or it doesn't match. */ int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) + const char *nickname) { char digest[DIGEST_LEN]; char nn_char='\0'; @@ -2967,30 +2962,20 @@ hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - if (nn_char == '=' || nn_char == '~') { - if (!nickname) + if (nn_char == '=') { + return 0; + } + + if (nn_char == '~') { + if (!nickname) // XXX This seems wrong. -NM return 0; if (strcasecmp(nn_buf, nickname)) return 0; - if (nn_char == '=' && !is_named) - return 0; } return tor_memeq(digest, identity_digest, DIGEST_LEN); } -/** Return true iff <b>router</b> is listed as named in the current - * consensus. */ -int -router_is_named(const routerinfo_t *router) -{ - const char *digest = - networkstatus_get_router_digest_by_nickname(router->nickname); - - return (digest && - tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); -} - /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> * is zero (NO_DIRINFO), or ALL_DIRINFO, any authority is okay. */ @@ -3008,20 +2993,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. @@ -3087,8 +3058,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); @@ -3926,7 +3897,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); @@ -4100,7 +4071,7 @@ routerlist_remove_old_cached_routers_with_id(time_t now, signed_descriptor_t *r = smartlist_get(lst, i); tor_assert(tor_memeq(ident, r->identity_digest, DIGEST_LEN)); } -#endif +#endif /* 1 */ /* Check whether we need to do anything at all. */ { int mdpr = directory_caches_dir_info(get_options()) ? 2 : 1; @@ -4513,7 +4484,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. */ @@ -4556,13 +4527,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); @@ -4608,7 +4580,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. */ @@ -4919,9 +4891,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; @@ -4973,10 +4946,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); @@ -5014,8 +4988,9 @@ max_dl_per_request(const or_options_t *options, int purpose) } /** Don't split our requests so finely that we are requesting fewer than - * this number per server. */ -#define MIN_DL_PER_REQUEST 4 + * this number per server. (Grouping more than this at once leads to + * diminishing returns.) */ +#define MIN_DL_PER_REQUEST 32 /** To prevent a single screwy cache from confusing us by selective reply, * try to split our requests into at least this many requests. */ #define MIN_REQUESTS 3 @@ -5081,7 +5056,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 @@ -5103,8 +5078,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"; @@ -5190,8 +5166,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. */ } @@ -5212,7 +5188,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_add(downloadable, rs->descriptor_digest); } SMARTLIST_FOREACH_END(rsp); - if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) + if (!authdir_mode_v3(options) && smartlist_len(no_longer_old)) { routerlist_t *rl = router_get_routerlist(); log_info(LD_DIR, "%d router descriptors listed in consensus are " diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 606e9085ce..8384c7eb8c 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 */ /** @@ -62,10 +62,10 @@ 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, int direct_conn); +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); @@ -80,20 +80,19 @@ const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type); #define router_digest_is_trusted_dir(d) \ 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); @@ -124,7 +123,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: @@ -228,7 +227,7 @@ int hex_digest_nickname_decode(const char *hexdigest, char *nickname_out); int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named); + const char *nickname); #ifdef ROUTERLIST_PRIVATE STATIC int choose_array_element_by_weight(const uint64_t *entries, @@ -252,7 +251,7 @@ MOCK_DECL(STATIC void, initiate_descriptor_downloads, STATIC int router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc, int microdesc); -#endif +#endif /* defined(ROUTERLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_ROUTERLIST_H) */ diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 521e237be2..3eda024f0f 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.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 */ /** @@ -60,6 +60,7 @@ #include "circuitstats.h" #include "dirserv.h" #include "dirvote.h" +#include "parsecommon.h" #include "policies.h" #include "protover.h" #include "rendcommon.h" @@ -81,267 +82,6 @@ /****************************************************************************/ -/** 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_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; - -/** - * @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 -/**@}*/ - /** List of tokens recognized in router descriptors */ static token_rule_t routerdesc_token_table[] = { T0N("reject", K_REJECT, ARGS, NO_OBJ ), @@ -619,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, @@ -628,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, @@ -668,9 +388,9 @@ static int check_signature_token(const char *digest, log_debug(LD_MM, "Area for %s has %lu allocated; using %lu.", \ name, (unsigned long)alloc, (unsigned long)used); \ STMT_END -#else +#else /* !(defined(DEBUG_AREA_ALLOC)) */ #define DUMP_AREA(a,name) STMT_NIL -#endif +#endif /* defined(DEBUG_AREA_ALLOC) */ /* Dump mechanism for unparseable descriptors */ @@ -981,7 +701,7 @@ dump_desc_populate_one_file, (const char *dirname, const char *f)) goto done; /* LCOV_EXCL_STOP */ } -#endif +#endif /* SIZE_MAX > UINT64_MAX */ if (BUG(st.st_size < 0)) { /* LCOV_EXCL_START * Should be impossible, since the OS isn't supposed to be b0rken. */ @@ -995,7 +715,7 @@ dump_desc_populate_one_file, (const char *dirname, const char *f)) * filename. */ if (crypto_digest256((char *)content_digest, desc, (size_t) st.st_size, - DIGEST_SHA256) != 0) { + DIGEST_SHA256) < 0) { /* Weird, but okay */ log_info(LD_DIR, "Unable to hash content of %s from unparseable descriptors " @@ -1144,8 +864,8 @@ dump_desc_populate_fifo_from_directory(const char *dirname) * 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)) { tor_assert(desc); tor_assert(type); @@ -1159,7 +879,7 @@ dump_desc(const char *desc, const char *type) /* Get the hash for logging purposes anyway */ len = strlen(desc); if (crypto_digest256((char *)digest_sha256, desc, len, - DIGEST_SHA256) != 0) { + DIGEST_SHA256) < 0) { log_info(LD_DIR, "Unable to parse descriptor of type %s, and unable to even hash" " it!", type); @@ -1269,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 @@ -1453,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. @@ -1489,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); @@ -1497,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); @@ -1521,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; @@ -1689,9 +1425,9 @@ dump_distinct_digest_count(int severity) verified_digests = digestmap_new(); tor_log(severity, LD_GENERAL, "%d *distinct* router digests verified", digestmap_size(verified_digests)); -#else +#else /* !(defined(COUNT_DISTINCT_DIGESTS)) */ (void)severity; /* suppress "unused parameter" warning */ -#endif +#endif /* defined(COUNT_DISTINCT_DIGESTS) */ } /** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's @@ -2087,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; } @@ -2100,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; } @@ -2135,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; } } @@ -2220,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]); } } @@ -2261,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(); @@ -2332,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); } @@ -2439,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; } @@ -2452,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; } @@ -2492,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; @@ -2846,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) { @@ -2964,6 +2701,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, 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); + rs->supports_v3_rendezvous_point = + protocol_list_supports_protocol(tok->args[0], PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -2975,6 +2722,12 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); rs->protocols_known = 1; } + if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { + /* Bug #22447 forces us to filter on this version. */ + if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { + rs->supports_v3_hsdir = 0; + } + } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); } @@ -3111,7 +2864,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b) int networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) { - int64_t weight_scale; int64_t G=0, M=0, E=0, D=0, T=0; double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed; double Gtotal=0, Mtotal=0, Etotal=0; @@ -3119,7 +2871,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) int valid = 1; (void) consensus_method; - weight_scale = networkstatus_get_weight_scale_param(ns); + const int64_t weight_scale = networkstatus_get_weight_scale_param(ns); + tor_assert(weight_scale >= 1); Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1); Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1); @@ -3181,7 +2934,7 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) } Wgg /= weight_scale; - Wgm /= weight_scale; + Wgm /= weight_scale; (void) Wgm; // unused from here on. Wgd /= weight_scale; Wmg /= weight_scale; @@ -3189,8 +2942,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) Wme /= weight_scale; Wmd /= weight_scale; - Weg /= weight_scale; - Wem /= weight_scale; + Weg /= weight_scale; (void) Weg; // unused from here on. + Wem /= weight_scale; (void) Wem; // unused from here on. Wee /= weight_scale; Wed /= weight_scale; @@ -3615,8 +3368,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) voter_identity = "consensus"; } - /* We extract both and on error, everything is stopped because it means - * the votes is malformed for the shared random value(s). */ + /* We extract both, and on error everything is stopped because it means + * the vote is malformed for the shared random value(s). */ if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", voter_identity); @@ -3639,6 +3392,7 @@ 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; struct in_addr in; @@ -3652,7 +3406,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; } @@ -3669,6 +3424,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); @@ -3723,9 +3479,9 @@ 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); @@ -3807,7 +3563,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); } @@ -3816,7 +3572,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; @@ -3868,7 +3624,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"); @@ -4011,11 +3767,10 @@ 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; @@ -4111,7 +3866,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]); } } @@ -4740,445 +4495,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 < 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 -} - -/** 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>. */ @@ -5204,16 +4520,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; @@ -5221,12 +4539,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; @@ -5250,17 +4570,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; } @@ -5276,7 +4607,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; @@ -5446,11 +4777,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)); @@ -5476,7 +4809,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]); } } @@ -5923,7 +5256,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; } @@ -5962,12 +5296,14 @@ 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; /* Verify that descriptor ID belongs to public key and secret ID part. */ - crypto_pk_get_digest(result->pk, public_key_hash); + if (crypto_pk_get_digest(result->pk, public_key_hash) < 0) { + log_warn(LD_REND, "Unable to compute rend descriptor public key digest"); + goto err; + } rend_get_descriptor_id_bytes(test_desc_id, public_key_hash, secret_id_part); if (tor_memneq(desc_id_out, test_desc_id, DIGEST_LEN)) { diff --git a/src/or/routerparse.h b/src/or/routerparse.h index 01a5de88e8..c4c8635f4b 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, @@ -113,7 +118,6 @@ STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str, MOCK_DECL(STATIC dumped_desc_t *, dump_desc_populate_one_file, (const char *dirname, const char *f)); STATIC void dump_desc_populate_fifo_from_directory(const char *dirname); -STATIC void dump_desc(const char *desc, const char *type); STATIC void dump_desc_fifo_cleanup(void); struct memarea_t; STATIC routerstatus_t *routerstatus_parse_entry_from_string( @@ -123,9 +127,15 @@ STATIC routerstatus_t *routerstatus_parse_entry_from_string( vote_routerstatus_t *vote_rs, int consensus_method, consensus_flavor_t flav); -#endif +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 /* defined(ROUTERPARSE_PRIVATE) */ #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1" -#endif +#endif /* !defined(TOR_ROUTERPARSE_H) */ diff --git a/src/or/routerset.c b/src/or/routerset.c index 58b66ea777..54e26ef943 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 */ /** @@ -28,6 +28,7 @@ #define ROUTERSET_PRIVATE #include "or.h" +#include "bridges.h" #include "geoip.h" #include "nodelist.h" #include "policies.h" @@ -262,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) { @@ -334,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. */ @@ -349,7 +362,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, /* No routers are specified by type; all are given by name or digest. * we can do a lookup in O(len(routerset)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { - const node_t *node = node_get_by_nickname(name, 1); + const node_t *node = node_get_by_nickname(name, 0); if (node) { if (!running_only || node->is_running) if (!routerset_contains_node(excludeset, node)) diff --git a/src/or/routerset.h b/src/or/routerset.h index c2f7205c3e..d8819ef3fd 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); @@ -79,6 +82,6 @@ struct routerset_t { * reloaded. */ bitarray_t *countries; }; -#endif -#endif +#endif /* defined(ROUTERSET_PRIVATE) */ +#endif /* !defined(TOR_ROUTERSET_H) */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 49ac1b939a..1984084feb 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -1,178 +1,379 @@ -/* * 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() */ -#include "channel.h" +#include "config.h" #include "compat_libevent.h" #define SCHEDULER_PRIVATE_ +#define SCHEDULER_KIST_PRIVATE #include "scheduler.h" +#include "main.h" +#include "buffers.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channeltls.h" #include <event2/event.h> -/* - * Scheduler high/low watermarks - */ - -static uint32_t sched_q_low_water = 16384; -static uint32_t sched_q_high_water = 32768; - -/* - * Maximum cells to flush in a single call to channel_flush_some_cells(); - * setting this low means more calls, but too high and we could overshoot - * sched_q_high_water. - */ - -static uint32_t sched_max_flush_cells = 16; - -/* - * 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: +/** + * \file scheduler.c + * \brief Channel scheduling system: decides which channels should send and + * receive when. + * + * This module is the global/common parts of the scheduling system. This system + * is what decides what channels get to send cells on their circuits and when. * - * 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 + * Terms: + * - "Scheduling system": the collection of scheduler*.{h,c} files and their + * aggregate behavior. + * - "Scheduler implementation": a scheduler_t. The scheduling system has one + * active scheduling implementation at a time. + * + * In this file you will find state that any scheduler implementation can have + * access to as well as the functions the rest of Tor uses to interact with the + * scheduling system. + * + * The earliest versions of Tor approximated a kind of round-robin system + * among active connections, but only approximated it. It would only consider + * one connection (roughly equal to a channel in today's terms) at a time, and + * thus could only prioritize circuits against others on the same connection. + * + * Then in response to the KIST paper[0], Tor implemented a global + * circuit scheduler. It was supposed to prioritize circuits across many + * channels, but wasn't effective. It is preserved in scheduler_vanilla.c. + * + * [0]: http://www.robgjansen.com/publications/kist-sec2014.pdf + * + * Then we actually got around to implementing KIST for real. We decided to + * modularize the scheduler so new ones can be implemented. You can find KIST + * in scheduler_kist.c. + * + * Channels have one of four scheduling states based on whether or not they + * have cells to send and whether or not they are able to send. + * + * <ol> + * <li> + * 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- - * writes/has-cells channels and is the only path for those to transition to - * other states. The scheduler_run() function gives us the opportunity to do - * scheduling work, and is called from other scheduler functions whenever a - * state transition occurs, and periodically from the main event loop. + * states by calling scheduler functions. The scheduling system builds up a + * list of channels in the SCHED_CHAN_PENDING state that the scheduler + * implementation should then use when it runs. Scheduling implementations need + * to properly update channel states during their scheduler_t->run() function + * as that is the only opportunity for channels to move from SCHED_CHAN_PENDING + * to any other state. + * + * The remainder of this file is a small amount of state that any scheduler + * implementation should have access to, and the functions the rest of Tor uses + * to interact with the scheduling system. */ -/* Scheduler global data structures */ +/***************************************************************************** + * Scheduling system state + * + * State that can be accessed from any scheduler implementation (but not + * outside the scheduling system) + *****************************************************************************/ -/* +/** DOCDOC */ +STATIC const scheduler_t *the_scheduler; + +/** * We keep a list of channels that are pending - i.e, have cells to write - * and can accept them to send. The enum scheduler_state in channel_t + * and can accept them to send. The enum scheduler_state in channel_t * is reserved for our use. + * + * Priority queue of channels that can write and have cells (pending work) */ - -/* Pqueue of channels that can write and have cells (pending work) */ STATIC smartlist_t *channels_pending = NULL; -/* +/** * This event runs the scheduler from its callback, and is manually * activated whenever a channel enters open for writes/cells to send. */ - STATIC struct event *run_sched_ev = NULL; -/* - * Queue heuristic; this is not the queue size, but an 'effective queuesize' - * that ages out contributions from stalled channels. - */ +static int have_logged_kist_suddenly_disabled = 0; -STATIC uint64_t queue_heuristic = 0; +/***************************************************************************** + * Scheduling system static function definitions + * + * Functions that can only be accessed from this file. + *****************************************************************************/ -/* - * Timestamp for last queue heuristic update +/** Return a human readable string for the given scheduler type. */ +static const char * +get_scheduler_type_string(scheduler_types_t type) +{ + switch (type) { + case SCHEDULER_VANILLA: + return "Vanilla"; + case SCHEDULER_KIST: + return "KIST"; + case SCHEDULER_KIST_LITE: + return "KISTLite"; + case SCHEDULER_NONE: + /* fallthrough */ + default: + tor_assert_unreached(); + return "(N/A)"; + } +} + +/** + * Scheduler event callback; this should get triggered once per event loop + * if any scheduling work was created during the event loop. */ +static void +scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +{ + (void) fd; + (void) events; + (void) arg; -STATIC time_t queue_heuristic_timestamp = 0; + log_debug(LD_SCHED, "Scheduler event callback called"); -/* Scheduler static function declarations */ + /* Run the scheduler. This is a mandatory function. */ -static void scheduler_evt_callback(evutil_socket_t fd, - short events, void *arg); -static int scheduler_more_work(void); -static void scheduler_retrigger(void); -#if 0 -static void scheduler_trigger(void); -#endif + /* We might as well assert on this. If this function doesn't exist, no cells + * are getting scheduled. Things are very broken. scheduler_t says the run() + * function is mandatory. */ + tor_assert(the_scheduler->run); + the_scheduler->run(); -/* Scheduler function implementations */ + /* Schedule itself back in if it has more work. */ -/** Free everything and shut down the scheduling system */ + /* Again, might as well assert on this mandatory scheduler_t function. If it + * doesn't exist, there's no way to tell libevent to run the scheduler again + * in the future. */ + tor_assert(the_scheduler->schedule); + the_scheduler->schedule(); +} -void -scheduler_free_all(void) +/** Using the global options, select the scheduler we should be using. */ +static void +select_scheduler(void) { - log_debug(LD_SCHED, "Shutting down scheduler"); - - if (run_sched_ev) { - if (event_del(run_sched_ev) < 0) { - log_warn(LD_BUG, "Problem deleting run_sched_ev"); + scheduler_t *new_scheduler = NULL; + +#ifdef TOR_UNIT_TESTS + /* This is hella annoying to set in the options for every test that passes + * through the scheduler and there are many so if we don't explicitly have + * a list of types set, just put the vanilla one. */ + if (get_options()->SchedulerTypes_ == NULL) { + the_scheduler = get_vanilla_scheduler(); + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + + /* This list is ordered that is first entry has the first priority. Thus, as + * soon as we find a scheduler type that we can use, we use it and stop. */ + SMARTLIST_FOREACH_BEGIN(get_options()->SchedulerTypes_, int *, type) { + switch (*type) { + case SCHEDULER_VANILLA: + new_scheduler = get_vanilla_scheduler(); + goto end; + case SCHEDULER_KIST: + if (!scheduler_can_use_kist()) { +#ifdef HAVE_KIST_SUPPORT + if (!have_logged_kist_suddenly_disabled) { + /* We should only log this once in most cases. If it was the kernel + * losing support for kist that caused scheduler_can_use_kist() to + * return false, then this flag makes sure we only log this message + * once. If it was the consensus that switched from "yes use kist" + * to "no don't use kist", then we still set the flag so we log + * once, but we unset the flag elsewhere if we ever can_use_kist() + * again. + */ + have_logged_kist_suddenly_disabled = 1; + log_notice(LD_SCHED, "Scheduler type KIST has been disabled by " + "the consensus or no kernel support."); + } +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + log_info(LD_SCHED, "Scheduler type KIST not built in"); +#endif /* defined(HAVE_KIST_SUPPORT) */ + continue; + } + /* This flag will only get set in one of two cases: + * 1 - the kernel lost support for kist. In that case, we don't expect to + * ever end up here + * 2 - the consensus went from "yes use kist" to "no don't use kist". + * We might end up here if the consensus changes back to "yes", in which + * case we might want to warn the user again if it goes back to "no" + * yet again. Thus we unset the flag */ + have_logged_kist_suddenly_disabled = 0; + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_full_mode(); + goto end; + case SCHEDULER_KIST_LITE: + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_lite_mode(); + goto end; + case SCHEDULER_NONE: + /* fallthrough */ + default: + /* Our option validation should have caught this. */ + tor_assert_unreached(); } - tor_event_free(run_sched_ev); - run_sched_ev = NULL; + } SMARTLIST_FOREACH_END(type); + + end: + if (new_scheduler == NULL) { + log_err(LD_SCHED, "Tor was unable to select a scheduler type. Please " + "make sure Schedulers is correctly configured with " + "what Tor does support."); + /* We weren't able to choose a scheduler which means that none of the ones + * set in Schedulers are supported or usable. We will respect the user + * wishes of using what it has been configured and don't do a sneaky + * fallback. Because this can be changed at runtime, we have to stop tor + * right now. */ + exit(1); } - if (channels_pending) { - smartlist_free(channels_pending); - channels_pending = NULL; - } + /* Set the chosen scheduler. */ + the_scheduler = new_scheduler; } /** - * Comparison function to use when sorting pending channels + * Helper function called from a few different places. It changes the + * scheduler implementation, if necessary. And if it did, it then tells the + * old one to free its state and the new one to initialize. */ +static void +set_scheduler(void) +{ + const scheduler_t *old_scheduler = the_scheduler; + scheduler_types_t old_scheduler_type = SCHEDULER_NONE; + + /* We keep track of the type in order to log only if the type switched. We + * can't just use the scheduler pointers because KIST and KISTLite share the + * same object. */ + if (the_scheduler) { + old_scheduler_type = the_scheduler->type; + } + + /* From the options, select the scheduler type to set. */ + select_scheduler(); + tor_assert(the_scheduler); + + /* We look at the pointer difference in case the old sched and new sched + * share the same scheduler object, as is the case with KIST and KISTLite. */ + if (old_scheduler != the_scheduler) { + /* Allow the old scheduler to clean up, if needed. */ + if (old_scheduler && old_scheduler->free_all) { + old_scheduler->free_all(); + } + + /* Initialize the new scheduler. */ + if (the_scheduler->init) { + the_scheduler->init(); + } + } -MOCK_IMPL(STATIC int, + /* Finally we notice log if we switched schedulers. We use the type in case + * two schedulers share a scheduler object. */ + if (old_scheduler_type != the_scheduler->type) { + log_notice(LD_CONFIG, "Scheduler type %s has been enabled.", + get_scheduler_type_string(the_scheduler->type)); + } +} + +/***************************************************************************** + * Scheduling system private function definitions + * + * Functions that can only be accessed from scheduler*.c + *****************************************************************************/ + +/** Return the pending channel list. */ +smartlist_t * +get_channels_pending(void) +{ + return channels_pending; +} + +/** Comparison function to use when sorting pending channels. */ +MOCK_IMPL(int, scheduler_compare_channels, (const void *c1_v, const void *c2_v)) { - channel_t *c1 = NULL, *c2 = NULL; + const channel_t *c1 = NULL, *c2 = NULL; /* These are a workaround for -Wbad-function-cast throwing a fit */ const circuitmux_policy_t *p1, *p2; uintptr_t p1_i, p2_i; @@ -180,11 +381,8 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) tor_assert(c1_v); tor_assert(c2_v); - c1 = (channel_t *)(c1_v); - c2 = (channel_t *)(c2_v); - - tor_assert(c1); - tor_assert(c2); + c1 = (const channel_t *)(c1_v); + c2 = (const channel_t *)(c2_v); if (c1 != c2) { if (circuitmux_get_policy(c1->cmux) == @@ -211,36 +409,83 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) } } -/* - * Scheduler event callback; this should get triggered once per event loop - * if any scheduling work was created during the event loop. - */ +/***************************************************************************** + * Scheduling system global functions + * + * Functions that can be accessed from anywhere in Tor. + *****************************************************************************/ -static void -scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +/** + * This is how the scheduling system is notified of Tor's configuration + * changing. For example: a SIGHUP was issued. + */ +void +scheduler_conf_changed(void) { - (void)fd; - (void)events; - (void)arg; - log_debug(LD_SCHED, "Scheduler event callback called"); + /* Let the scheduler decide what it should do. */ + set_scheduler(); - tor_assert(run_sched_ev); + /* Then tell the (possibly new) scheduler that we have new options. */ + if (the_scheduler->on_new_options) { + the_scheduler->on_new_options(); + } +} - /* Run the scheduler */ - scheduler_run(); +/** + * Whenever we get a new consensus, this function is called. + */ +void +scheduler_notify_networkstatus_changed(void) +{ + /* Maybe the consensus param made us change the scheduler. */ + set_scheduler(); - /* Do we have more work to do? */ - if (scheduler_more_work()) scheduler_retrigger(); + /* Then tell the (possibly new) scheduler that we have a new consensus */ + if (the_scheduler->on_new_consensus) { + the_scheduler->on_new_consensus(); + } } -/** Mark a channel as no longer ready to accept writes */ +/** + * Free everything scheduling-related from main.c. Note this is only called + * when Tor is shutting down, while scheduler_t->free_all() is called both when + * Tor is shutting down and when we are switching schedulers. + */ +void +scheduler_free_all(void) +{ + log_debug(LD_SCHED, "Shutting down scheduler"); + + if (run_sched_ev) { + if (event_del(run_sched_ev) < 0) { + log_warn(LD_BUG, "Problem deleting run_sched_ev"); + } + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } + + if (channels_pending) { + /* We don't have ownership of the objects in this list. */ + smartlist_free(channels_pending); + channels_pending = NULL; + } + + if (the_scheduler && the_scheduler->free_all) { + the_scheduler->free_all(); + } + the_scheduler = NULL; +} +/** Mark a channel as no longer ready to accept writes. */ MOCK_IMPL(void, scheduler_channel_doesnt_want_writes,(channel_t *chan)) { - tor_assert(chan); - - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* If it's already in pending, we can put it in waiting_to_write */ if (chan->scheduler_state == SCHED_CHAN_PENDING) { @@ -251,7 +496,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, @@ -273,17 +518,18 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) } } -/** Mark a channel as having waiting cells */ - +/** Mark a channel as having waiting cells. */ MOCK_IMPL(void, scheduler_channel_has_waiting_cells,(channel_t *chan)) { - int became_pending = 0; - - tor_assert(chan); - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } - /* First, check if this one also writeable */ + /* First, check if it's also writeable */ if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { /* * It's in channels_waiting_for_cells, so it shouldn't be in any of @@ -291,15 +537,19 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) * channels_pending. */ chan->scheduler_state = SCHED_CHAN_PENDING; - smartlist_pqueue_add(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - chan); + if (!SCHED_BUG(chan->sched_heap_idx != -1, chan)) { + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + chan); + } log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_for_cells " "to pending", U64_PRINTF_ARG(chan->global_identifier), chan); - became_pending = 1; + /* If we made a channel pending, we potentially have scheduling work to + * do. */ + the_scheduler->schedule(); } else { /* * It's not in waiting_for_cells, so it can't become pending; it's @@ -314,250 +564,125 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) U64_PRINTF_ARG(chan->global_identifier), chan); } } +} - /* - * If we made a channel pending, we potentially have scheduling work - * to do. - */ - if (became_pending) scheduler_retrigger(); +/** Add the scheduler event to the set of pending events with next_run being + * the longest time libevent should wait before triggering the event. */ +void +scheduler_ev_add(const struct timeval *next_run) +{ + tor_assert(run_sched_ev); + tor_assert(next_run); + if (BUG(event_add(run_sched_ev, next_run) < 0)) { + log_warn(LD_SCHED, "Adding to libevent failed. Next run time was set to: " + "%ld.%06ld", next_run->tv_sec, (long)next_run->tv_usec); + return; + } } -/** Set up the scheduling system */ +/** Make the scheduler event active with the given flags. */ +void +scheduler_ev_active(int flags) +{ + tor_assert(run_sched_ev); + event_active(run_sched_ev, flags, 1); +} +/* + * Initialize everything scheduling-related from config.c. Note this is only + * called when Tor is starting up, while scheduler_t->init() is called both + * when Tor is starting up and when we are switching schedulers. + */ void scheduler_init(void) { log_debug(LD_SCHED, "Initting scheduler"); - tor_assert(!run_sched_ev); + // Two '!' because we really do want to check if the pointer is non-NULL + IF_BUG_ONCE(!!run_sched_ev) { + log_warn(LD_SCHED, "We should not already have a libevent scheduler event." + "I'll clean the old one up, but this is odd."); + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } run_sched_ev = tor_event_new(tor_libevent_get_base(), -1, 0, scheduler_evt_callback, NULL); - channels_pending = smartlist_new(); - queue_heuristic = 0; - queue_heuristic_timestamp = approx_time(); -} - -/** Check if there's more scheduling work */ -static int -scheduler_more_work(void) -{ - tor_assert(channels_pending); - - return ((scheduler_get_queue_heuristic() < sched_q_low_water) && - ((smartlist_len(channels_pending) > 0))) ? 1 : 0; + set_scheduler(); } -/** Retrigger the scheduler in a way safe to use from the callback */ - -static void -scheduler_retrigger(void) -{ - tor_assert(run_sched_ev); - event_active(run_sched_ev, EV_TIMEOUT, 1); -} - -/** Notify the scheduler of a channel being closed */ - +/* + * If a channel is going away, this is how the scheduling system is informed + * so it can do any freeing necessary. This ultimately calls + * scheduler_t->on_channel_free() so the current scheduler can release any + * state specific to this channel. + */ MOCK_IMPL(void, scheduler_release_channel,(channel_t *chan)) { - tor_assert(chan); - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } - if (chan->scheduler_state == SCHED_CHAN_PENDING) { + /* Try to remove the channel from the pending list regardless of its + * scheduler state. We can release a channel in many places in the tor code + * so we can't rely on the channel state (PENDING) to remove it from the + * list. + * + * For instance, the channel can change state from OPEN to CLOSING while + * being handled in the scheduler loop leading to the channel being in + * PENDING state but not in the pending list. Furthermore, we release the + * channel when it changes state to close and a second time when we free it. + * Not ideal at all but for now that is the way it is. */ + if (chan->sched_heap_idx != -1) { 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_IDLE; -} - -/** Run the scheduling algorithm if necessary */ - -MOCK_IMPL(void, -scheduler_run, (void)) -{ - int n_cells, n_chans_before, n_chans_after; - uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; - ssize_t flushed, flushed_this_time; - smartlist_t *to_readd = NULL; - channel_t *chan = NULL; - - log_debug(LD_SCHED, "We have a chance to run the scheduler"); - - if (scheduler_get_queue_heuristic() < sched_q_low_water) { - n_chans_before = smartlist_len(channels_pending); - q_len_before = channel_get_global_queue_estimate(); - q_heur_before = scheduler_get_queue_heuristic(); - - while (scheduler_get_queue_heuristic() <= sched_q_high_water && - smartlist_len(channels_pending) > 0) { - /* Pop off a channel */ - chan = smartlist_pqueue_pop(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx)); - tor_assert(chan); - - /* Figure out how many cells we can write */ - n_cells = channel_num_cells_writeable(chan); - if (n_cells > 0) { - log_debug(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "%d cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); - - flushed = 0; - while (flushed < n_cells && - scheduler_get_queue_heuristic() <= sched_q_high_water) { - flushed_this_time = - channel_flush_some_cells(chan, - MIN(sched_max_flush_cells, - (size_t) n_cells - flushed)); - if (flushed_this_time <= 0) break; - flushed += flushed_this_time; - } - - if (flushed < n_cells) { - /* We ran out of cells to flush */ - chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_for_cells from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* The channel may still have some cells */ - if (channel_more_to_flush(chan)) { - /* The channel goes to either pending or waiting_to_write */ - if (channel_num_cells_writeable(chan) > 0) { - /* Add it back to pending later */ - if (!to_readd) to_readd = smartlist_new(); - smartlist_add(to_readd, chan); - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "is still pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* It's waiting to be able to write more */ - chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_to_write from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } - } else { - /* No cells left; it can go to idle or waiting_for_cells */ - if (channel_num_cells_writeable(chan) > 0) { - /* - * It can still accept writes, so it goes to - * waiting_for_cells - */ - chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_for_cells from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* - * We exactly filled up the output queue with all available - * cells; go to idle. - */ - chan->scheduler_state = SCHED_CHAN_IDLE; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "become idle from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } - } - } - - log_debug(LD_SCHED, - "Scheduler flushed %d cells onto pending channel " - U64_FORMAT " at %p", - (int)flushed, U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - log_info(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "no cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan); - /* Put it back to WAITING_TO_WRITE */ - chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; - } - } - - /* Readd any channels we need to */ - if (to_readd) { - SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { - readd_chan->scheduler_state = SCHED_CHAN_PENDING; - smartlist_pqueue_add(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - readd_chan); - } SMARTLIST_FOREACH_END(readd_chan); - smartlist_free(to_readd); - } - - n_chans_after = smartlist_len(channels_pending); - q_len_after = channel_get_global_queue_estimate(); - q_heur_after = scheduler_get_queue_heuristic(); - log_debug(LD_SCHED, - "Scheduler handled %d of %d pending channels, queue size from " - U64_FORMAT " to " U64_FORMAT ", queue heuristic from " - U64_FORMAT " to " U64_FORMAT, - n_chans_before - n_chans_after, n_chans_before, - U64_PRINTF_ARG(q_len_before), U64_PRINTF_ARG(q_len_after), - U64_PRINTF_ARG(q_heur_before), U64_PRINTF_ARG(q_heur_after)); + if (the_scheduler->on_channel_free) { + the_scheduler->on_channel_free(chan); } + chan->scheduler_state = SCHED_CHAN_IDLE; } -/** Trigger the scheduling event so we run the scheduler later */ - -#if 0 -static void -scheduler_trigger(void) -{ - log_debug(LD_SCHED, "Triggering scheduler event"); - - tor_assert(run_sched_ev); - - event_add(run_sched_ev, EV_TIMEOUT, 1); -} -#endif - /** Mark a channel as ready to accept writes */ void scheduler_channel_wants_writes(channel_t *chan) { - int became_pending = 0; - - tor_assert(chan); - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* If it's already in waiting_to_write, we can put it in pending */ if (chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE) { /* * It can write now, so it goes to channels_pending. */ - smartlist_pqueue_add(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - chan); + log_debug(LD_SCHED, "chan=%" PRIu64 " became pending", + chan->global_identifier); + if (!SCHED_BUG(chan->sched_heap_idx != -1, chan)) { + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + chan); + } chan->scheduler_state = SCHED_CHAN_PENDING; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_to_write " "to pending", U64_PRINTF_ARG(chan->global_identifier), chan); - became_pending = 1; + /* We just made a channel pending, we have scheduling work to do. */ + the_scheduler->schedule(); } else { /* * It's not in SCHED_CHAN_WAITING_TO_WRITE, so it can't become pending; @@ -571,137 +696,70 @@ scheduler_channel_wants_writes(channel_t *chan) U64_PRINTF_ARG(chan->global_identifier), chan); } } +} + +/* Log warn the given channel and extra scheduler context as well. This is + * used by SCHED_BUG() in order to be able to extract as much information as + * we can when we hit a bug. Channel chan can be NULL. */ +void +scheduler_bug_occurred(const channel_t *chan) +{ + char buf[128]; + + if (chan != NULL) { + const size_t outbuf_len = + buf_datalen(TO_CONN(BASE_CHAN_TO_TLS((channel_t *) chan)->conn)->outbuf); + tor_snprintf(buf, sizeof(buf), + "Channel %" PRIu64 " in state %s and scheduler state %d." + " Num cells on cmux: %d. Connection outbuf len: %lu.", + chan->global_identifier, + channel_state_to_string(chan->state), + chan->scheduler_state, circuitmux_num_cells(chan->cmux), + (unsigned long)outbuf_len); + } - /* - * If we made a channel pending, we potentially have scheduling work - * to do. - */ - if (became_pending) scheduler_retrigger(); + { + char *msg; + /* Rate limit every 60 seconds. If we start seeing this every 60 sec, we + * know something is stuck/wrong. It *should* be loud but not too much. */ + static ratelim_t rlimit = RATELIM_INIT(60); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_warn(LD_BUG, "%s Num pending channels: %d. " + "Channel in pending list: %s.%s", + (chan != NULL) ? buf : "No channel in bug context.", + smartlist_len(channels_pending), + (smartlist_pos(channels_pending, chan) == -1) ? "no" : "yes", + msg); + tor_free(msg); + } + } } -/** - * Notify the scheduler that a channel's position in the pqueue may have - * changed - */ +#ifdef TOR_UNIT_TESTS +/* + * Notify scheduler that a channel's queue position may have changed. + */ void scheduler_touch_channel(channel_t *chan) { - tor_assert(chan); + IF_BUG_ONCE(!chan) { + return; + } if (chan->scheduler_state == SCHED_CHAN_PENDING) { /* Remove and re-add it */ smartlist_pqueue_remove(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); } /* else no-op, since it isn't in the queue */ } -/** - * Notify the scheduler of a queue size adjustment, to recalculate the - * queue heuristic. - */ - -void -scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj) -{ - time_t now = approx_time(); - - log_debug(LD_SCHED, - "Queue size adjustment by %s" U64_FORMAT " for channel " - U64_FORMAT, - (dir >= 0) ? "+" : "-", - U64_PRINTF_ARG(adj), - U64_PRINTF_ARG(chan->global_identifier)); - - /* Get the queue heuristic up to date */ - scheduler_update_queue_heuristic(now); - - /* Adjust as appropriate */ - if (dir >= 0) { - /* Increasing it */ - queue_heuristic += adj; - } else { - /* Decreasing it */ - if (queue_heuristic > adj) queue_heuristic -= adj; - else queue_heuristic = 0; - } - - log_debug(LD_SCHED, - "Queue heuristic is now " U64_FORMAT, - U64_PRINTF_ARG(queue_heuristic)); -} - -/** - * Query the current value of the queue heuristic - */ - -STATIC uint64_t -scheduler_get_queue_heuristic(void) -{ - time_t now = approx_time(); - - scheduler_update_queue_heuristic(now); - - return queue_heuristic; -} - -/** - * Adjust the queue heuristic value to the present time - */ - -STATIC void -scheduler_update_queue_heuristic(time_t now) -{ - time_t diff; - - if (queue_heuristic_timestamp == 0) { - /* - * Nothing we can sensibly do; must not have been initted properly. - * Oh well. - */ - queue_heuristic_timestamp = now; - } else if (queue_heuristic_timestamp < now) { - diff = now - queue_heuristic_timestamp; - /* - * This is a simple exponential age-out; the other proposed alternative - * was a linear age-out using the bandwidth history in rephist.c; I'm - * going with this out of concern that if an adversary can jam the - * scheduler long enough, it would cause the bandwidth to drop to - * zero and render the aging mechanism ineffective thereafter. - */ - if (0 <= diff && diff < 64) queue_heuristic >>= diff; - else queue_heuristic = 0; - - queue_heuristic_timestamp = now; - - log_debug(LD_SCHED, - "Queue heuristic is now " U64_FORMAT, - U64_PRINTF_ARG(queue_heuristic)); - } - /* else no update needed, or time went backward */ -} - -/** - * Set scheduler watermarks and flush size - */ - -void -scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush) -{ - /* Sanity assertions - caller should ensure these are true */ - tor_assert(lo > 0); - tor_assert(hi > lo); - tor_assert(max_flush > 0); - - sched_q_low_water = lo; - sched_q_high_water = hi; - sched_max_flush_cells = max_flush; -} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 3dcfd2faca..559f1c8afc 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -1,9 +1,9 @@ -/* * Copyright (c) 2013-2016, The Tor Project, Inc. */ +/* * Copyright (c) 2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file scheduler.h - * \brief Header file for scheduler.c + * \brief Header file for scheduler*.c **/ #ifndef TOR_SCHEDULER_H @@ -13,45 +13,203 @@ #include "channel.h" #include "testsupport.h" -/* Global-visibility scheduler functions */ +/** Scheduler type, we build an ordered list with those values from the + * parsed strings in Schedulers. The reason to do such a thing is so we can + * quickly and without parsing strings select the scheduler at anytime. */ +typedef enum { + SCHEDULER_NONE = -1, + SCHEDULER_VANILLA = 1, + SCHEDULER_KIST = 2, + SCHEDULER_KIST_LITE = 3, +} scheduler_types_t; -/* Set up and shut down the scheduler from main.c */ -void scheduler_free_all(void); -void scheduler_init(void); -MOCK_DECL(void, scheduler_run, (void)); +/** + * A scheduler implementation is a collection of function pointers. If you + * would like to add a new scheduler called foo, create scheduler_foo.c, + * implement at least the mandatory ones, and implement get_foo_scheduler() + * that returns a complete scheduler_t for your foo scheduler. See + * scheduler_kist.c for an example. + * + * These function pointers SHOULD NOT be used anywhere outside of the + * scheduling source files. The rest of Tor should communicate with the + * scheduling system through the functions near the bottom of this file, and + * those functions will call into the current scheduler implementation as + * necessary. + * + * If your scheduler doesn't need to implement something (for example: it + * doesn't create any state for itself, thus it has nothing to free when Tor + * is shutting down), then set that function pointer to NULL. + */ +typedef struct scheduler_s { + /* Scheduler type. This is used for logging when the scheduler is switched + * during runtime. */ + scheduler_types_t type; -/* Mark channels as having cells or wanting/not wanting writes */ -MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); -MOCK_DECL(void,scheduler_channel_has_waiting_cells,(channel_t *chan)); -void scheduler_channel_wants_writes(channel_t *chan); + /* (Optional) To be called when we want to prepare a scheduler for use. + * Perhaps Tor just started and we are the lucky chosen scheduler, or + * perhaps Tor is switching to this scheduler. No matter the case, this is + * where we would prepare any state and initialize parameters. You might + * think of this as the opposite of free_all(). */ + void (*init)(void); -/* Notify the scheduler of a channel being closed */ -MOCK_DECL(void,scheduler_release_channel,(channel_t *chan)); + /* (Optional) To be called when we want to tell the scheduler to delete all + * of its state (if any). Perhaps Tor is shutting down or perhaps we are + * switching schedulers. */ + void (*free_all)(void); -/* Notify scheduler of queue size adjustments */ -void scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj); + /* (Mandatory) Libevent controls the main event loop in Tor, and this is + * where we register with libevent the next execution of run_sched_ev [which + * ultimately calls run()]. */ + void (*schedule)(void); -/* Notify scheduler that a channel's queue position may have changed */ -void scheduler_touch_channel(channel_t *chan); + /* (Mandatory) This is the heart of a scheduler! This is where the + * excitement happens! Here libevent has given us the chance to execute, and + * we should do whatever we need to do in order to move some cells from + * their circuit queues to output buffers in an intelligent manner. We + * should do this quickly. When we are done, we'll try to schedule() ourself + * if more work needs to be done to setup the next scheduling run. */ + void (*run)(void); + + /* + * External event not related to the scheduler but that can influence it. + */ + + /* (Optional) To be called whenever Tor finds out about a new consensus. + * First the scheduling system as a whole will react to the new consensus + * and change the scheduler if needed. After that, the current scheduler + * (which might be new) will call this so it has the chance to react to the + * new consensus too. If there's a consensus parameter that your scheduler + * wants to keep an eye on, this is where you should check for it. */ + void (*on_new_consensus)(void); + + /* (Optional) To be called when a channel is being freed. Sometimes channels + * go away (for example: the relay on the other end is shutting down). If + * the scheduler keeps any channel-specific state and has memory to free + * when channels go away, implement this and free it here. */ + void (*on_channel_free)(const channel_t *); -/* Adjust the watermarks from config file*/ -void scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush); + /* (Optional) To be called whenever Tor is reloading configuration options. + * For example: SIGHUP was issued and Tor is rereading its torrc. A + * scheduler should use this as an opportunity to parse and cache torrc + * options so that it doesn't have to call get_options() all the time. */ + void (*on_new_options)(void); +} scheduler_t; -/* Things only scheduler.c and its test suite should see */ +/***************************************************************************** + * Globally visible scheduler variables/values + * + * These are variables/constants that all of Tor should be able to see. + *****************************************************************************/ +/* Default interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_DEFAULT 10 +/* Minimum interval that KIST runs. This value disables KIST. */ +#define KIST_SCHED_RUN_INTERVAL_MIN 0 +/* Maximum interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_MAX 100 + +/***************************************************************************** + * Globally visible scheduler functions + * + * These functions are how the rest of Tor communicates with the scheduling + * system. + *****************************************************************************/ + +void scheduler_init(void); +void scheduler_free_all(void); +void scheduler_conf_changed(void); +void scheduler_notify_networkstatus_changed(void); +MOCK_DECL(void, scheduler_release_channel, (channel_t *chan)); + +/* + * Ways for a channel to interact with the scheduling system. A channel only + * really knows (i) whether or not it has cells it wants to send, and + * (ii) whether or not it would like to write. + */ +void scheduler_channel_wants_writes(channel_t *chan); +MOCK_DECL(void, scheduler_channel_doesnt_want_writes, (channel_t *chan)); +MOCK_DECL(void, scheduler_channel_has_waiting_cells, (channel_t *chan)); + +/***************************************************************************** + * Private scheduler functions + * + * These functions are only visible to the scheduling system, the current + * scheduler implementation, and tests. + *****************************************************************************/ #ifdef SCHEDULER_PRIVATE_ -MOCK_DECL(STATIC int, scheduler_compare_channels, + +/********************************* + * Defined in scheduler.c + *********************************/ + +/* Triggers a BUG() and extra information with chan if available. */ +#define SCHED_BUG(cond, chan) \ + (PREDICT_UNLIKELY(cond) ? \ + ((BUG(cond)) ? (scheduler_bug_occurred(chan), 1) : 0) : 0) + +void scheduler_bug_occurred(const channel_t *chan); + +smartlist_t *get_channels_pending(void); +MOCK_DECL(int, scheduler_compare_channels, (const void *c1_v, const void *c2_v)); -STATIC uint64_t scheduler_get_queue_heuristic(void); -STATIC void scheduler_update_queue_heuristic(time_t now); +void scheduler_ev_active(int flags); +void scheduler_ev_add(const struct timeval *next_run); #ifdef TOR_UNIT_TESTS extern smartlist_t *channels_pending; extern struct event *run_sched_ev; -extern uint64_t queue_heuristic; -extern time_t queue_heuristic_timestamp; -#endif -#endif +extern const scheduler_t *the_scheduler; +void scheduler_touch_channel(channel_t *chan); +#endif /* defined(TOR_UNIT_TESTS) */ + +/********************************* + * Defined in scheduler_kist.c + *********************************/ + +#ifdef SCHEDULER_KIST_PRIVATE + +/* Socket table entry which holds information of a channel's socket and kernel + * TCP information. Only used by KIST. */ +typedef struct socket_table_ent_s { + HT_ENTRY(socket_table_ent_s) node; + const channel_t *chan; + /* Amount written this scheduling run */ + uint64_t written; + /* Amount that can be written this scheduling run */ + uint64_t limit; + /* TCP info from the kernel */ + uint32_t cwnd; + uint32_t unacked; + uint32_t mss; + uint32_t notsent; +} socket_table_ent_t; + +typedef HT_HEAD(outbuf_table_s, outbuf_table_ent_s) outbuf_table_t; + +MOCK_DECL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)); +MOCK_DECL(void, channel_write_to_kernel, (channel_t *chan)); +MOCK_DECL(void, update_socket_info_impl, (socket_table_ent_t *ent)); + +int scheduler_can_use_kist(void); +void scheduler_kist_set_full_mode(void); +void scheduler_kist_set_lite_mode(void); +scheduler_t *get_kist_scheduler(void); +int kist_scheduler_run_interval(void); + +#ifdef TOR_UNIT_TESTS +extern int32_t sched_run_interval; +#endif /* TOR_UNIT_TESTS */ + +#endif /* defined(SCHEDULER_KIST_PRIVATE) */ + +/********************************* + * Defined in scheduler_vanilla.c + *********************************/ + +scheduler_t *get_vanilla_scheduler(void); + +#endif /* defined(SCHEDULER_PRIVATE_) */ #endif /* !defined(TOR_SCHEDULER_H) */ diff --git a/src/or/scheduler_kist.c b/src/or/scheduler_kist.c new file mode 100644 index 0000000000..c79b413b88 --- /dev/null +++ b/src/or/scheduler_kist.c @@ -0,0 +1,844 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define SCHEDULER_KIST_PRIVATE + +#include <event2/event.h> + +#include "or.h" +#include "buffers.h" +#include "config.h" +#include "connection.h" +#include "networkstatus.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#include "channeltls.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +#define TLS_PER_CELL_OVERHEAD 29 + +#ifdef HAVE_KIST_SUPPORT +/* Kernel interface needed for KIST. */ +#include <netinet/tcp.h> +#include <linux/sockios.h> +#endif /* HAVE_KIST_SUPPORT */ + +/***************************************************************************** + * Data structures and supporting functions + *****************************************************************************/ + +/* Socket_table hash table stuff. The socket_table keeps track of per-socket + * limit information imposed by kist and used by kist. */ + +static uint32_t +socket_table_ent_hash(const socket_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +socket_table_ent_eq(const socket_table_ent_t *a, const socket_table_ent_t *b) +{ + return a->chan == b->chan; +} + +typedef HT_HEAD(socket_table_s, socket_table_ent_s) socket_table_t; + +static socket_table_t socket_table = HT_INITIALIZER(); + +HT_PROTOTYPE(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq) +HT_GENERATE2(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/* outbuf_table hash table stuff. The outbuf_table keeps track of which + * channels have data sitting in their outbuf so the kist scheduler can force + * a write from outbuf to kernel periodically during a run and at the end of a + * run. */ + +typedef struct outbuf_table_ent_s { + HT_ENTRY(outbuf_table_ent_s) node; + channel_t *chan; +} outbuf_table_ent_t; + +static uint32_t +outbuf_table_ent_hash(const outbuf_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +outbuf_table_ent_eq(const outbuf_table_ent_t *a, const outbuf_table_ent_t *b) +{ + return a->chan->global_identifier == b->chan->global_identifier; +} + +HT_PROTOTYPE(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq) +HT_GENERATE2(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Store the last time the scheduler was run so we can decide when to next run + * the scheduler based on it. */ +static monotime_t scheduler_last_run; +/* This is a factor for the extra_space calculation in kist per-socket limits. + * It is the number of extra congestion windows we want to write to the kernel. + */ +static double sock_buf_size_factor = 1.0; +/* How often the scheduler runs. */ +STATIC int sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + +#ifdef HAVE_KIST_SUPPORT +/* Indicate if KIST lite mode is on or off. We can disable it at runtime. + * Important to have because of the KISTLite -> KIST possible transition. */ +static unsigned int kist_lite_mode = 0; +/* Indicate if we don't have the kernel support. This can happen if the kernel + * changed and it doesn't recognized the values passed to the syscalls needed + * by KIST. In that case, fallback to the naive approach. */ +static unsigned int kist_no_kernel_support = 0; +#else /* !(defined(HAVE_KIST_SUPPORT)) */ +static unsigned int kist_lite_mode = 1; +#endif /* defined(HAVE_KIST_SUPPORT) */ + +/***************************************************************************** + * Internally called function implementations + *****************************************************************************/ + +/* Little helper function to get the length of a channel's output buffer */ +static inline size_t +channel_outbuf_length(channel_t *chan) +{ + /* In theory, this can not happen because we can not scheduler a channel + * without a connection that has its outbuf initialized. Just in case, bug + * on this so we can understand a bit more why it happened. */ + if (SCHED_BUG(BASE_CHAN_TO_TLS(chan)->conn == NULL, chan)) { + return 0; + } + return buf_datalen(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn)->outbuf); +} + +/* Little helper function for HT_FOREACH_FN. */ +static int +each_channel_write_to_kernel(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + channel_write_to_kernel(ent->chan); + return 0; /* Returning non-zero removes the element from the table. */ +} + +/* Free the given outbuf table entry ent. */ +static int +free_outbuf_info_by_ent(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing outbuf table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Free the given socket table entry ent. */ +static int +free_socket_info_by_ent(socket_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing socket table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Clean up socket_table. Probably because the KIST sched impl is going away */ +static void +free_all_socket_info(void) +{ + HT_FOREACH_FN(socket_table_s, &socket_table, free_socket_info_by_ent, NULL); + HT_CLEAR(socket_table_s, &socket_table); +} + +static socket_table_ent_t * +socket_table_search(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t search, *ent = NULL; + search.chan = chan; + ent = HT_FIND(socket_table_s, table, &search); + return ent; +} + +/* Free a socket entry in table for the given chan. */ +static void +free_socket_info_by_chan(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) + return; + log_debug(LD_SCHED, "scheduler free socket info for chan=%" PRIu64, + chan->global_identifier); + HT_REMOVE(socket_table_s, table, ent); + free_socket_info_by_ent(ent, NULL); +} + +/* Perform system calls for the given socket in order to calculate kist's + * per-socket limit as documented in the function body. */ +MOCK_IMPL(void, +update_socket_info_impl, (socket_table_ent_t *ent)) +{ +#ifdef HAVE_KIST_SUPPORT + int64_t tcp_space, extra_space; + const tor_socket_t sock = + TO_CONN(BASE_CHAN_TO_TLS((channel_t *) ent->chan)->conn)->s; + struct tcp_info tcp; + socklen_t tcp_info_len = sizeof(tcp); + + if (kist_no_kernel_support || kist_lite_mode) { + goto fallback; + } + + /* Gather information */ + if (getsockopt(sock, SOL_TCP, TCP_INFO, (void *)&(tcp), &tcp_info_len) < 0) { + if (errno == EINVAL) { + /* Oops, this option is not provided by the kernel, we'll have to + * disable KIST entirely. This can happen if tor was built on a machine + * with the support previously or if the kernel was updated and lost the + * support. */ + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + kist_no_kernel_support = 1; + } + goto fallback; + } + if (ioctl(sock, SIOCOUTQNSD, &(ent->notsent)) < 0) { + if (errno == EINVAL) { + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + /* Same reason as the above. */ + kist_no_kernel_support = 1; + } + goto fallback; + } + ent->cwnd = tcp.tcpi_snd_cwnd; + ent->unacked = tcp.tcpi_unacked; + ent->mss = tcp.tcpi_snd_mss; + + /* In order to reduce outbound kernel queuing delays and thus improve Tor's + * ability to prioritize circuits, KIST wants to set a socket write limit + * that is near the amount that the socket would be able to immediately send + * into the Internet. + * + * We first calculate how much the socket could send immediately (assuming + * completely full packets) according to the congestion window and the number + * of unacked packets. + * + * Then we add a little extra space in a controlled way. We do this so any + * when the kernel gets ACKs back for data currently sitting in the "TCP + * space", it will already have some more data to send immediately. It will + * not have to wait for the scheduler to run again. The amount of extra space + * is a factor of the current congestion window. With the suggested + * sock_buf_size_factor value of 1.0, we allow at most 2*cwnd bytes to sit in + * the kernel: 1 cwnd on the wire waiting for ACKs and 1 cwnd ready and + * waiting to be sent when those ACKs finally come. + * + * In the below diagram, we see some bytes in the TCP-space (denoted by '*') + * that have be sent onto the wire and are waiting for ACKs. We have a little + * more room in "TCP space" that we can fill with data that will be + * immediately sent. We also see the "extra space" KIST calculates. The sum + * of the empty "TCP space" and the "extra space" is the kist-imposed write + * limit for this socket. + * + * <----------------kernel-outbound-socket-queue----------------| + * <*********---------------------------------------------------| + * |----TCP-space-----|----extra-space-----| + * |------------------| + * ^ ((cwnd - unacked) * mss) bytes + * |--------------------| + * ^ ((cwnd * mss) * factor) bytes + */ + + /* These values from the kernel are uint32_t, they will always fit into a + * int64_t tcp_space variable but if the congestion window cwnd is smaller + * than the unacked packets, the remaining TCP space is set to 0. */ + if (ent->cwnd >= ent->unacked) { + tcp_space = (ent->cwnd - ent->unacked) * (int64_t)(ent->mss); + } else { + tcp_space = 0; + } + + /* The clamp_double_to_int64 makes sure the first part fits into an int64_t. + * In fact, if sock_buf_size_factor is still forced to be >= 0 in config.c, + * then it will be positive for sure. Then we subtract a uint32_t. Getting a + * negative value is OK, see after how it is being handled. */ + extra_space = + clamp_double_to_int64( + (ent->cwnd * (int64_t)ent->mss) * sock_buf_size_factor) - + ent->notsent; + if ((tcp_space + extra_space) < 0) { + /* This means that the "notsent" queue is just too big so we shouldn't put + * more in the kernel for now. */ + ent->limit = 0; + } else { + /* The positive sum of two int64_t will always fit into an uint64_t. + * And we know this will always be positive, since we checked above. */ + ent->limit = (uint64_t)tcp_space + (uint64_t)extra_space; + } + return; + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + goto fallback; +#endif /* defined(HAVE_KIST_SUPPORT) */ + + fallback: + /* If all of a sudden we don't have kist support, we just zero out all the + * variables for this socket since we don't know what they should be. We + * also allow the socket to write as much as it can from the estimated + * number of cells the lower layer can accept, effectively returning it to + * Vanilla scheduler behavior. */ + ent->cwnd = ent->unacked = ent->mss = ent->notsent = 0; + /* This function calls the specialized channel object (currently channeltls) + * and ask how many cells it can write on the outbuf which we then multiply + * by the size of the cells for this channel. The cast is because this + * function requires a non-const channel object, meh. */ + ent->limit = channel_num_cells_writeable((channel_t *) ent->chan) * + (get_cell_network_size(ent->chan->wide_circ_ids) + + TLS_PER_CELL_OVERHEAD); +} + +/* Given a socket that isn't in the table, add it. + * Given a socket that is in the table, re-init values that need init-ing + * every scheduling run + */ +static void +init_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) { + log_debug(LD_SCHED, "scheduler init socket info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(socket_table_s, table, ent); + } + ent->written = 0; +} + +/* Add chan to the outbuf table if it isn't already in it. If it is, then don't + * do anything */ +static void +outbuf_table_add(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (!ent) { + log_debug(LD_SCHED, "scheduler init outbuf info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(outbuf_table_s, table, ent); + } +} + +static void +outbuf_table_remove(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (ent) { + HT_REMOVE(outbuf_table_s, table, ent); + free_outbuf_info_by_ent(ent, NULL); + } +} + +/* Set the scheduler running interval. */ +static void +set_scheduler_run_interval(void) +{ + int old_sched_run_interval = sched_run_interval; + sched_run_interval = kist_scheduler_run_interval(); + if (old_sched_run_interval != sched_run_interval) { + log_info(LD_SCHED, "Scheduler KIST changing its running interval " + "from %" PRId32 " to %" PRId32, + old_sched_run_interval, sched_run_interval); + } +} + +/* Return true iff the channel hasn’t hit its kist-imposed write limit yet */ +static int +socket_can_write(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return 1; // Just return true, saying that kist wouldn't limit the socket + } + + /* We previously calculated a write limit for this socket. In the below + * calculation, first determine how much room is left in bytes. Then divide + * that by the amount of space a cell takes. If there's room for at least 1 + * cell, then KIST will allow the socket to write. */ + int64_t kist_limit_space = + (int64_t) (ent->limit - ent->written) / + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD); + return kist_limit_space > 0; +} + +/* Update the channel's socket kernel information. */ +static void +update_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist for some reason so nothing to do. + } + update_socket_info_impl(ent); + log_debug(LD_SCHED, "chan=%" PRIu64 " updated socket info, limit: %" PRIu64 + ", cwnd: %" PRIu32 ", unacked: %" PRIu32 + ", notsent: %" PRIu32 ", mss: %" PRIu32, + ent->chan->global_identifier, ent->limit, ent->cwnd, ent->unacked, + ent->notsent, ent->mss); +} + +/* Increment the channel's socket written value by the number of bytes. */ +static void +update_socket_written(socket_table_t *table, channel_t *chan, size_t bytes) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist so nothing to do. + } + + log_debug(LD_SCHED, "chan=%" PRIu64 " wrote %lu bytes, old was %" PRIi64, + chan->global_identifier, (unsigned long) bytes, ent->written); + + ent->written += bytes; +} + +/* + * A naive KIST impl would write every single cell all the way to the kernel. + * That would take a lot of system calls. A less bad KIST impl would write a + * channel's outbuf to the kernel only when we are switching to a different + * channel. But if we have two channels with equal priority, we end up writing + * one cell for each and bouncing back and forth. This KIST impl avoids that + * by only writing a channel's outbuf to the kernel if it has 8 cells or more + * in it. + */ +MOCK_IMPL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)) +{ + outbuf_table_add(table, chan); + /* CELL_MAX_NETWORK_SIZE * 8 because we only want to write the outbuf to the + * kernel if there's 8 or more cells waiting */ + return channel_outbuf_length(chan) > (CELL_MAX_NETWORK_SIZE * 8); +} + +/* Little helper function to write a channel's outbuf all the way to the + * kernel */ +MOCK_IMPL(void, channel_write_to_kernel, (channel_t *chan)) +{ + log_debug(LD_SCHED, "Writing %lu bytes to kernel for chan %" PRIu64, + (unsigned long)channel_outbuf_length(chan), + chan->global_identifier); + connection_handle_write(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn), 0); +} + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/* Function of the scheduler interface: free_all() */ +static void +kist_free_all(void) +{ + free_all_socket_info(); +} + +/* Function of the scheduler interface: on_channel_free() */ +static void +kist_on_channel_free(const channel_t *chan) +{ + free_socket_info_by_chan(&socket_table, chan); +} + +/* Function of the scheduler interface: on_new_consensus() */ +static void +kist_scheduler_on_new_consensus(void) +{ + set_scheduler_run_interval(); +} + +/* Function of the scheduler interface: on_new_options() */ +static void +kist_scheduler_on_new_options(void) +{ + sock_buf_size_factor = get_options()->KISTSockBufSizeFactor; + + /* Calls kist_scheduler_run_interval which calls get_options(). */ + set_scheduler_run_interval(); +} + +/* Function of the scheduler interface: init() */ +static void +kist_scheduler_init(void) +{ + /* When initializing the scheduler, the last run could be 0 because it is + * declared static or a value in the past that was set when it was last + * used. In both cases, we want to initialize it to now so we don't risk + * using the value 0 which doesn't play well with our monotonic time + * interface. + * + * One side effect is that the first scheduler run will be at the next tick + * that is in now + 10 msec (KIST_SCHED_RUN_INTERVAL_DEFAULT) by default. */ + monotime_get(&scheduler_last_run); + + kist_scheduler_on_new_options(); + IF_BUG_ONCE(sched_run_interval == 0) { + log_warn(LD_SCHED, "We are initing the KIST scheduler and noticed the " + "KISTSchedRunInterval is telling us to not use KIST. That's " + "weird! We'll continue using KIST, but at %" PRId32 "ms.", + KIST_SCHED_RUN_INTERVAL_DEFAULT); + sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + } +} + +/* Function of the scheduler interface: schedule() */ +static void +kist_scheduler_schedule(void) +{ + struct monotime_t now; + struct timeval next_run; + int64_t diff; + + if (!have_work()) { + return; + } + monotime_get(&now); + + /* If time is really monotonic, we can never have now being smaller than the + * last scheduler run. The scheduler_last_run at first is set to 0. + * Unfortunately, not all platforms guarantee monotonic time so we log at + * info level but don't make it more noisy. */ + diff = monotime_diff_msec(&scheduler_last_run, &now); + if (diff < 0) { + log_info(LD_SCHED, "Monotonic time between now and last run of scheduler " + "is negative: %" PRId64 ". Setting diff to 0.", diff); + diff = 0; + } + if (diff < sched_run_interval) { + next_run.tv_sec = 0; + /* Takes 1000 ms -> us. This will always be valid because diff can NOT be + * negative and can NOT be bigger than sched_run_interval so values can + * only go from 1000 usec (diff set to interval - 1) to 100000 usec (diff + * set to 0) for the maximum allowed run interval (100ms). */ + next_run.tv_usec = (int) ((sched_run_interval - diff) * 1000); + /* Re-adding an event reschedules it. It does not duplicate it. */ + scheduler_ev_add(&next_run); + } else { + scheduler_ev_active(EV_TIMEOUT); + } +} + +/* Function of the scheduler interface: run() */ +static void +kist_scheduler_run(void) +{ + /* Define variables */ + channel_t *chan = NULL; // current working channel + /* The last distinct chan served in a sched loop. */ + channel_t *prev_chan = NULL; + int flush_result; // temporarily store results from flush calls + /* Channels to be re-adding to pending at the end */ + smartlist_t *to_readd = NULL; + smartlist_t *cp = get_channels_pending(); + + outbuf_table_t outbuf_table = HT_INITIALIZER(); + + /* For each pending channel, collect new kernel information */ + SMARTLIST_FOREACH_BEGIN(cp, const channel_t *, pchan) { + init_socket_info(&socket_table, pchan); + update_socket_info(&socket_table, pchan); + } SMARTLIST_FOREACH_END(pchan); + + log_debug(LD_SCHED, "Running the scheduler. %d channels pending", + smartlist_len(cp)); + + /* The main scheduling loop. Loop until there are no more pending channels */ + while (smartlist_len(cp) > 0) { + /* get best channel */ + chan = smartlist_pqueue_pop(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + if (SCHED_BUG(!chan, NULL)) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + outbuf_table_add(&outbuf_table, chan); + + /* if we have switched to a new channel, consider writing the previous + * channel's outbuf to the kernel. */ + if (!prev_chan) { + prev_chan = chan; + } + if (prev_chan != chan) { + if (channel_should_write_to_kernel(&outbuf_table, prev_chan)) { + channel_write_to_kernel(prev_chan); + outbuf_table_remove(&outbuf_table, prev_chan); + } + prev_chan = chan; + } + + /* Only flush and write if the per-socket limit hasn't been hit */ + if (socket_can_write(&socket_table, chan)) { + /* flush to channel queue/outbuf */ + flush_result = (int)channel_flush_some_cells(chan, 1); // 1 for num cells + /* XXX: While flushing cells, it is possible that the connection write + * fails leading to the channel to be closed which triggers a release + * and free its entry in the socket table. And because of a engineering + * design issue, the error is not propagated back so we don't get an + * error at this point. So before we continue, make sure the channel is + * open and if not just ignore it. See #23751. */ + if (!CHANNEL_IS_OPEN(chan)) { + /* Channel isn't open so we put it back in IDLE mode. It is either + * renegotiating its TLS session or about to be released. */ + chan->scheduler_state = SCHED_CHAN_IDLE; + continue; + } + /* flush_result has the # cells flushed */ + if (flush_result > 0) { + update_socket_written(&socket_table, chan, flush_result * + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD)); + } else { + /* XXX: This can happen because tor sometimes does flush in an + * opportunistic way cells from the circuit to the outbuf so the + * channel can end up here without having anything to flush nor needed + * to write to the kernel. Hopefully we'll fix that soon but for now + * we have to handle this case which happens kind of often. */ + log_debug(LD_SCHED, + "We didn't flush anything on a chan that we think " + "can write and wants to write. The channel's state is '%s' " + "and in scheduler state %d. We're going to mark it as " + "waiting_for_cells (as that's most likely the issue) and " + "stop scheduling it this round.", + channel_state_to_string(chan->state), + chan->scheduler_state); + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + continue; + } + } + + /* Decide what to do with the channel now */ + + if (!channel_more_to_flush(chan) && + !socket_can_write(&socket_table, chan)) { + + /* Case 1: no more cells to send, and cannot write */ + + /* + * You might think we should put the channel in SCHED_CHAN_IDLE. And + * you're probably correct. While implementing KIST, we found that the + * scheduling system would sometimes lose track of channels when we did + * that. We suspect it has to do with the difference between "can't + * write because socket/outbuf is full" and KIST's "can't write because + * we've arbitrarily decided that that's enough for now." Sometimes + * channels run out of cells at the same time they hit their + * kist-imposed write limit and maybe the rest of Tor doesn't put the + * channel back in pending when it is supposed to. + * + * This should be investigated again. It is as simple as changing + * SCHED_CHAN_WAITING_FOR_CELLS to SCHED_CHAN_IDLE and seeing if Tor + * starts having serious throughput issues. Best done in shadow/chutney. + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!channel_more_to_flush(chan)) { + + /* Case 2: no more cells to send, but still open for writes */ + + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!socket_can_write(&socket_table, chan)) { + + /* Case 3: cells to send, but cannot write */ + + /* + * We want to write, but can't. If we left the channel in + * channels_pending, we would never exit the scheduling loop. We need to + * add it to a temporary list of channels to be added to channels_pending + * after the scheduling loop is over. They can hopefully be taken care of + * in the next scheduling round. + */ + if (!to_readd) { + to_readd = smartlist_new(); + } + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_to_write", + chan->global_identifier); + } else { + + /* Case 4: cells to send, and still open for writes */ + + chan->scheduler_state = SCHED_CHAN_PENDING; + if (!SCHED_BUG(chan->sched_heap_idx != -1, chan)) { + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), chan); + } + } + } /* End of main scheduling loop */ + + /* Write the outbuf of any channels that still have data */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, each_channel_write_to_kernel, + NULL); + /* We are done with it. */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, free_outbuf_info_by_ent, NULL); + HT_CLEAR(outbuf_table_s, &outbuf_table); + + log_debug(LD_SCHED, "len pending=%d, len to_readd=%d", + smartlist_len(cp), + (to_readd ? smartlist_len(to_readd) : -1)); + + /* Re-add any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + if (!smartlist_contains(cp, readd_chan)) { + if (!SCHED_BUG(chan->sched_heap_idx != -1, chan)) { + /* XXXX Note that the check above is in theory redundant with + * the smartlist_contains check. But let's make sure we're + * not messing anything up, and leave them both for now. */ + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), readd_chan); + } + } + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + monotime_get(&scheduler_last_run); +} + +/***************************************************************************** + * Externally called function implementations not called through scheduler_t + *****************************************************************************/ + +/* Stores the kist scheduler function pointers. */ +static scheduler_t kist_scheduler = { + .type = SCHEDULER_KIST, + .free_all = kist_free_all, + .on_channel_free = kist_on_channel_free, + .init = kist_scheduler_init, + .on_new_consensus = kist_scheduler_on_new_consensus, + .schedule = kist_scheduler_schedule, + .run = kist_scheduler_run, + .on_new_options = kist_scheduler_on_new_options, +}; + +/* Return the KIST scheduler object. If it didn't exists, return a newly + * allocated one but init() is not called. */ +scheduler_t * +get_kist_scheduler(void) +{ + return &kist_scheduler; +} + +/* Check the torrc (and maybe consensus) for the configured KIST scheduler run + * interval. + * - If torrc > 0, then return the positive torrc value (should use KIST, and + * should use the set value) + * - If torrc == 0, then look in the consensus for what the value should be. + * - If == 0, then return 0 (don't use KIST) + * - If > 0, then return the positive consensus value + * - If consensus doesn't say anything, return 10 milliseconds, default. + */ +int +kist_scheduler_run_interval(void) +{ + int run_interval = get_options()->KISTSchedRunInterval; + + if (run_interval != 0) { + log_debug(LD_SCHED, "Found KISTSchedRunInterval=%" PRId32 " in torrc. " + "Using that.", run_interval); + return run_interval; + } + + log_debug(LD_SCHED, "KISTSchedRunInterval=0, turning to the consensus."); + + /* Will either be the consensus value or the default. Note that 0 can be + * returned which means the consensus wants us to NOT use KIST. */ + return networkstatus_get_param(NULL, "KISTSchedRunInterval", + KIST_SCHED_RUN_INTERVAL_DEFAULT, + KIST_SCHED_RUN_INTERVAL_MIN, + KIST_SCHED_RUN_INTERVAL_MAX); +} + +/* Set KISTLite mode that is KIST without kernel support. */ +void +scheduler_kist_set_lite_mode(void) +{ + kist_lite_mode = 1; + kist_scheduler.type = SCHEDULER_KIST_LITE; + log_info(LD_SCHED, + "Setting KIST scheduler without kernel support (KISTLite mode)"); +} + +/* Set KIST mode that is KIST with kernel support. */ +void +scheduler_kist_set_full_mode(void) +{ + kist_lite_mode = 0; + kist_scheduler.type = SCHEDULER_KIST; + log_info(LD_SCHED, + "Setting KIST scheduler with kernel support (KIST mode)"); +} + +#ifdef HAVE_KIST_SUPPORT + +/* Return true iff the scheduler subsystem should use KIST. */ +int +scheduler_can_use_kist(void) +{ + if (kist_no_kernel_support) { + /* We have no kernel support so we can't use KIST. */ + return 0; + } + + /* We do have the support, time to check if we can get the interval that the + * consensus can be disabling. */ + int run_interval = kist_scheduler_run_interval(); + log_debug(LD_SCHED, "Determined KIST sched_run_interval should be " + "%" PRId32 ". Can%s use KIST.", + run_interval, (run_interval > 0 ? "" : " not")); + return run_interval > 0; +} + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + +int +scheduler_can_use_kist(void) +{ + return 0; +} + +#endif /* defined(HAVE_KIST_SUPPORT) */ + diff --git a/src/or/scheduler_vanilla.c b/src/or/scheduler_vanilla.c new file mode 100644 index 0000000000..303b3dbba8 --- /dev/null +++ b/src/or/scheduler_vanilla.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include <event2/event.h> + +#include "or.h" +#include "config.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Maximum cells to flush in a single call to channel_flush_some_cells(); */ +#define MAX_FLUSH_CELLS 1000 + +/***************************************************************************** + * Externally called function implementations + *****************************************************************************/ + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/** Re-trigger the scheduler in a way safe to use from the callback */ + +static void +vanilla_scheduler_schedule(void) +{ + if (!have_work()) { + return; + } + + /* Activate our event so it can process channels. */ + scheduler_ev_active(EV_TIMEOUT); +} + +static void +vanilla_scheduler_run(void) +{ + int n_cells, n_chans_before, n_chans_after; + ssize_t flushed, flushed_this_time; + smartlist_t *cp = get_channels_pending(); + smartlist_t *to_readd = NULL; + channel_t *chan = NULL; + + log_debug(LD_SCHED, "We have a chance to run the scheduler"); + + n_chans_before = smartlist_len(cp); + + while (smartlist_len(cp) > 0) { + /* Pop off a channel */ + chan = smartlist_pqueue_pop(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + IF_BUG_ONCE(!chan) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + + /* Figure out how many cells we can write */ + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + + flushed = 0; + while (flushed < n_cells) { + flushed_this_time = + channel_flush_some_cells(chan, + MIN(MAX_FLUSH_CELLS, (size_t) n_cells - flushed)); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } + + if (flushed < n_cells) { + /* We ran out of cells to flush */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* The channel may still have some cells */ + if (channel_more_to_flush(chan)) { + /* The channel goes to either pending or waiting_to_write */ + if (channel_num_cells_writeable(chan) > 0) { + /* Add it back to pending later */ + if (!to_readd) to_readd = smartlist_new(); + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "is still pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* It's waiting to be able to write more */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_to_write from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } else { + /* No cells left; it can go to idle or waiting_for_cells */ + if (channel_num_cells_writeable(chan) > 0) { + /* + * It can still accept writes, so it goes to + * waiting_for_cells + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* + * We exactly filled up the output queue with all available + * cells; go to idle. + */ + chan->scheduler_state = SCHED_CHAN_IDLE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "become idle from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } + } + + log_debug(LD_SCHED, + "Scheduler flushed %d cells onto pending channel " + U64_FORMAT " at %p", + (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + /* Put it back to WAITING_TO_WRITE */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + } + } + + /* Readd any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + readd_chan); + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + n_chans_after = smartlist_len(cp); + log_debug(LD_SCHED, "Scheduler handled %d of %d pending channels", + n_chans_before - n_chans_after, n_chans_before); +} + +/* Stores the vanilla scheduler function pointers. */ +static scheduler_t vanilla_scheduler = { + .type = SCHEDULER_VANILLA, + .free_all = NULL, + .on_channel_free = NULL, + .init = NULL, + .on_new_consensus = NULL, + .schedule = vanilla_scheduler_schedule, + .run = vanilla_scheduler_run, + .on_new_options = NULL, +}; + +scheduler_t * +get_vanilla_scheduler(void) +{ + return &vanilla_scheduler; +} + diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 5f6b03f1ba..b3f62a8fd8 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -192,7 +192,7 @@ verify_commit_and_reveal(const sr_commit_t *commit) /* 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)) { + SR_REVEAL_BASE64_LEN, commit->alg) < 0) { /* Unable to digest the reveal blob, this is unlikely. */ goto invalid; } @@ -230,9 +230,7 @@ commit_decode(const char *encoded, sr_commit_t *commit) { int decoded_len = 0; size_t offset = 0; - /* XXX: Needs two extra bytes for the base64 decode calculation matches - * the binary length once decoded. #17868. */ - char b64_decoded[SR_COMMIT_LEN + 2]; + char b64_decoded[SR_COMMIT_LEN]; tor_assert(encoded); tor_assert(commit); @@ -284,9 +282,7 @@ STATIC int reveal_decode(const char *encoded, sr_commit_t *commit) { int decoded_len = 0; - /* XXX: Needs two extra bytes for the base64 decode calculation matches - * the binary length once decoded. #17868. */ - char b64_decoded[SR_REVEAL_LEN + 2]; + char b64_decoded[SR_REVEAL_LEN]; tor_assert(encoded); tor_assert(commit); @@ -502,6 +498,20 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase) 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. */ @@ -932,7 +942,7 @@ sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert) /* 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)) { + SR_REVEAL_BASE64_LEN, commit->alg) < 0) { goto error; } @@ -1012,7 +1022,7 @@ sr_compute_srv(void) SMARTLIST_FOREACH(chunks, char *, s, tor_free(s)); smartlist_free(chunks); if (crypto_digest256(hashed_reveals, reveals, strlen(reveals), - SR_DIGEST_ALG)) { + SR_DIGEST_ALG) < 0) { goto end; } current_srv = generate_srv(hashed_reveals, reveal_num, @@ -1323,13 +1333,7 @@ sr_act_post_consensus(const networkstatus_t *consensus) } /* Prepare our state so that it's ready for the next voting period. */ - { - voting_schedule_t *voting_schedule = - get_voting_schedule(options,time(NULL), LOG_NOTICE); - time_t interval_starts = voting_schedule->interval_starts; - sr_state_update(interval_starts); - voting_schedule_free(voting_schedule); - } + sr_state_update(dirvote_get_next_valid_after_time()); } /* Initialize shared random subsystem. This MUST be called early in the boot @@ -1348,6 +1352,84 @@ sr_save_and_cleanup(void) 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 @@ -1359,5 +1441,5 @@ set_num_srv_agreements(int32_t value) num_srv_agreements_from_vote = value; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 9885934cc7..c0992489cb 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_SHARED_RANDOM_H @@ -36,17 +36,14 @@ /* Length of base64 encoded commit NOT including the NUL terminated byte. * Formula is taken from base64_encode_size. This adds up to 56 bytes. */ -#define SR_COMMIT_BASE64_LEN \ - (((SR_COMMIT_LEN - 1) / 3) * 4 + 4) +#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 \ - (((SR_REVEAL_LEN - 1) / 3) * 4 + 4) +#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 \ - (((DIGEST256_LEN - 1) / 3) * 4 + 4) +#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) @@ -129,6 +126,13 @@ const char *sr_commit_get_rsa_fpr(const sr_commit_t *commit) 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 */ @@ -156,7 +160,7 @@ STATIC int should_keep_commit(const sr_commit_t *commit, sr_phase_t phase); STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit); -#endif /* SHARED_RANDOM_PRIVATE */ +#endif /* defined(SHARED_RANDOM_PRIVATE) */ #ifdef TOR_UNIT_TESTS @@ -164,5 +168,5 @@ void set_num_srv_agreements(int32_t value); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_SHARED_RANDOM_H */ +#endif /* !defined(TOR_SHARED_RANDOM_H) */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 8438d46404..812c6e4e00 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -40,10 +40,14 @@ static const char dstate_commit_key[] = "Commit"; static const char dstate_prev_srv_key[] = "SharedRandPreviousValue"; static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; +/** dummy instance of sr_disk_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); + /* These next two are duplicates or near-duplicates from config.c */ #define VAR(name, conftype, member, initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \ + initvalue CONF_TEST_MEMBERS(sr_disk_state_t, conftype, member) } /* As VAR, but the option name and member name are the same. */ #define V(member, conftype, initvalue) \ VAR(#member, conftype, member, initvalue) @@ -70,21 +74,22 @@ static config_var_t state_vars[] = { V(SharedRandValues, LINELIST_V, NULL), VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL), VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /* "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { "__extra", CONFIG_TYPE_LINELIST, - STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL + offsetof(sr_disk_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(sr_disk_state_t, LINELIST, ExtraLines) }; /* Configuration format of sr_disk_state_t. */ static const config_format_t state_format = { sizeof(sr_disk_state_t), SR_DISK_STATE_MAGIC, - STRUCT_OFFSET(sr_disk_state_t, magic_), + offsetof(sr_disk_state_t, magic_), NULL, NULL, state_vars, @@ -133,27 +138,56 @@ get_voting_interval(void) /* Given the time <b>now</b>, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. */ -static time_t -get_start_time_of_current_round(time_t now) +STATIC time_t +get_start_time_of_current_round(void) { const or_options_t *options = get_options(); int voting_interval = get_voting_interval(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - /* First, get the start time of the next round */ - time_t next_start = new_voting_schedule->interval_starts; + time_t next_start = dirvote_get_next_valid_after_time(); /* Now roll back next_start by a voting interval to find the start time of the current round. */ time_t curr_start = dirvote_get_start_of_next_interval( next_start - voting_interval - 1, voting_interval, options->TestingV3AuthVotingStartOffset); + return curr_start; +} - voting_schedule_free(new_voting_schedule); +/** Return the start time of the current SR protocol run. For example, if the + * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this + * function should return 23/06/2017 00:00:00. */ +time_t +sr_state_get_start_time_of_current_protocol_run(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + time_t beginning_of_current_round = get_start_time_of_current_round(); - return curr_start; + /* Get current SR protocol round */ + int current_round = (now / voting_interval) % total_rounds; + + /* Get start time by subtracting the time elapsed from the beginning of the + protocol run */ + time_t time_elapsed_since_start_of_run = current_round * voting_interval; + return beginning_of_current_round - time_elapsed_since_start_of_run; +} + +/** Return the time (in seconds) it takes to complete a full SR protocol phase + * (e.g. the commit phase). */ +unsigned int +sr_state_get_phase_duration(void) +{ + return SHARED_RANDOM_N_ROUNDS * get_voting_interval(); +} + +/** Return the time (in seconds) it takes to complete a full SR protocol run */ +unsigned int +sr_state_get_protocol_run_duration(void) +{ + int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + return total_protocol_rounds * get_voting_interval(); } /* Return the time we should expire the state file created at <b>now</b>. @@ -167,7 +201,7 @@ get_state_valid_until_time(time_t now) voting_interval = get_voting_interval(); /* Find the time the current round started. */ - beginning_of_current_round = get_start_time_of_current_round(now); + beginning_of_current_round = get_start_time_of_current_round(); /* Find how many rounds are left till the end of the protocol run */ current_round = (now / voting_interval) % total_rounds; @@ -1330,7 +1364,7 @@ sr_state_init(int save_to_disk, int read_from_disk) /* We have a state in memory, let's make sure it's updated for the current * and next voting round. */ { - time_t valid_after = get_next_valid_after_time(now); + time_t valid_after = dirvote_get_next_valid_after_time(); sr_state_update(valid_after); } return 0; @@ -1356,5 +1390,5 @@ get_sr_state(void) return sr_state; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 43a7f1d284..866725c435 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Tor Project, Inc. */ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_SHARED_RANDOM_STATE_H @@ -77,7 +77,7 @@ typedef struct sr_state_t { typedef struct sr_disk_state_t { uint32_t magic_; /* Version of the protocol. */ - uint32_t Version; + int Version; /* Version of our running tor. */ char *TorVersion; /* Creation time of this state */ @@ -121,11 +121,16 @@ int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); +time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +unsigned int sr_state_get_phase_duration(void); +unsigned int sr_state_get_protocol_run_duration(void); + #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_start_time_of_current_round(void); STATIC time_t get_state_valid_until_time(time_t now); STATIC const char *get_phase_str(sr_phase_t phase); @@ -134,14 +139,14 @@ STATIC void new_protocol_run(time_t valid_after); STATIC void state_rotate_srv(void); STATIC int is_phase_transition(sr_phase_t next_phase); -#endif /* SHARED_RANDOM_STATE_PRIVATE */ +#endif /* defined(SHARED_RANDOM_STATE_PRIVATE) */ #ifdef TOR_UNIT_TESTS STATIC void set_sr_phase(sr_phase_t phase); STATIC sr_state_t *get_sr_state(void); -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* TOR_SHARED_RANDOM_STATE_H */ +#endif /* !defined(TOR_SHARED_RANDOM_STATE_H) */ diff --git a/src/or/statefile.c b/src/or/statefile.c index 8fa4324b25..97bd9cac36 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 */ /** @@ -34,6 +34,7 @@ #include "config.h" #include "confparse.h" #include "connection.h" +#include "control.h" #include "entrynodes.h" #include "hibernate.h" #include "rephist.h" @@ -53,10 +54,14 @@ static config_abbrev_t state_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_state_t); + /*XXXX these next two are duplicates or near-duplicates from config.c */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \ + initvalue CONF_TEST_MEMBERS(or_state_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) @@ -85,6 +90,8 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), + V(HidServRevCounter, LINELIST, NULL), + V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), V(BWHistoryReadValues, CSV, ""), @@ -102,6 +109,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), @@ -111,7 +120,8 @@ static config_var_t state_vars_[] = { V(CircuitBuildAbandonedCount, UINT, "0"), VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + + END_OF_CONFIG_VARS }; #undef VAR @@ -129,14 +139,15 @@ static int or_state_validate_cb(void *old_options, void *options, /** "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL + "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(or_state_t, LINELIST, ExtraLines) }; /** Configuration format for or_state_t. */ static const config_format_t state_format = { sizeof(or_state_t), OR_STATE_MAGIC, - STRUCT_OFFSET(or_state_t, magic_), + offsetof(or_state_t, magic_), state_abbrevs_, NULL, state_vars_, @@ -400,10 +411,15 @@ or_state_load(void) log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); /* Warn the user if their clock has been set backwards, * they could be tricked into using old consensuses */ - time_t apparent_skew = new_state->LastWritten - time(NULL); - if (apparent_skew > 0) + time_t apparent_skew = time(NULL) - new_state->LastWritten; + if (apparent_skew < 0) { + /* Initialize bootstrap event reporting because we might call + * clock_skew_warning() before the bootstrap state is + * initialized, causing an assertion failure. */ + control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); clock_skew_warning(NULL, (long)apparent_skew, 1, LD_GENERAL, "local state file", fname); + } } else { log_info(LD_GENERAL, "Initialized state"); } @@ -655,8 +671,6 @@ save_transport_to_state(const char *transport, *next = line = tor_malloc_zero(sizeof(config_line_t)); line->key = tor_strdup("TransportProxy"); tor_asprintf(&line->value, "%s %s", transport, fmt_addrport(addr, port)); - - next = &(line->next); } if (!get_options()->AvoidDiskWrites) diff --git a/src/or/statefile.h b/src/or/statefile.h index b13743481d..574afb3622 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 @@ -24,5 +24,5 @@ STATIC void or_state_free(or_state_t *state); STATIC or_state_t *or_state_new(void); #endif -#endif +#endif /* !defined(TOR_STATEFILE_H) */ diff --git a/src/or/status.c b/src/or/status.c index fa2238b9f9..52763a7042 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -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 */ /** diff --git a/src/or/status.h b/src/or/status.h index b97e835037..49da6abc0f 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 @@ -14,5 +14,5 @@ STATIC char *secs_to_uptime(long secs); STATIC char *bytes_to_usage(uint64_t bytes); #endif -#endif +#endif /* !defined(TOR_STATUS_H) */ diff --git a/src/or/tor_main.c b/src/or/tor_main.c index d67eda2ac9..a3a8838602 100644 --- a/src/or/tor_main.c +++ b/src/or/tor_main.c @@ -1,6 +1,6 @@ /* Copyright 2001-2004 Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2016, The Tor Project, Inc. */ + * Copyright (c) 2007-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ extern const char tor_git_revision[]; diff --git a/src/or/torcert.c b/src/or/torcert.c index a6a33c675a..212534d311 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" @@ -57,29 +76,39 @@ tor_cert_sign_impl(const ed25519_keypair_t *signing_key, ed25519_signature_t signature; if (ed25519_sign(&signature, encoded, real_len-ED25519_SIG_LEN, signing_key)<0) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Can't sign certificate"); goto err; + /* LCOV_EXCL_STOP */ } memcpy(sig, signature.sig, ED25519_SIG_LEN); torcert = tor_cert_parse(encoded, real_len); if (! torcert) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Generated a certificate we cannot parse"); goto err; + /* LCOV_EXCL_STOP */ } if (tor_cert_checksig(torcert, &signing_key->pubkey, now) < 0) { - log_warn(LD_BUG, "Generated a certificate whose signature we can't check"); + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Generated a certificate whose signature we can't " + "check: %s", tor_cert_describe_signature_status(torcert)); goto err; + /* LCOV_EXCL_STOP */ } tor_free(encoded); goto done; + /* LCOV_EXCL_START */ err: tor_cert_free(torcert); torcert = NULL; + /* LCOV_EXCL_STOP */ + done: ed25519_cert_free(cert); tor_free(encoded); @@ -137,7 +166,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 +198,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 +225,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 +243,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; @@ -223,6 +268,24 @@ tor_cert_checksig(tor_cert_t *cert, } } +/** Return a string describing the status of the signature on <b>cert</b> + * + * Will always be "unchecked" unless tor_cert_checksig has been called. + */ +const char * +tor_cert_describe_signature_status(const tor_cert_t *cert) +{ + if (cert->cert_expired) { + return "expired"; + } else if (cert->sig_bad) { + return "mis-signed"; + } else if (cert->sig_ok) { + return "okay"; + } else { + return "unchecked"; + } +} + /** Return a new copy of <b>cert</b> */ tor_cert_t * tor_cert_dup(const tor_cert_t *cert) @@ -255,6 +318,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 +330,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 +348,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 +374,352 @@ 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. + */ +MOCK_IMPL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)) +{ + rsa_ed_crosscert_t *cc = NULL; + int rv; + +#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) { + /* LCOV_EXCL_START */ + log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!"); + goto err; + /* LCOV_EXCL_STOP */ + } + + /* Put everything together in a NUL terminated string. */ + 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..ac227db209 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,11 +60,13 @@ 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); +const char *tor_cert_describe_signature_status(const tor_cert_t *cert); tor_cert_t *tor_cert_dup(const tor_cert_t *cert); int tor_cert_eq(const tor_cert_t *cert1, const tor_cert_t *cert2); @@ -71,6 +76,31 @@ 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); - -#endif +MOCK_DECL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)); + +or_handshake_certs_t *or_handshake_certs_new(void); +void or_handshake_certs_free(or_handshake_certs_t *certs); +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 /* !defined(TORCERT_H_INCLUDED) */ diff --git a/src/or/transports.c b/src/or/transports.c index 7a52b737e4..5fb24e11a0 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; @@ -528,12 +527,12 @@ launch_managed_proxy(managed_proxy_t *mp) (const char **)mp->argv, env, &mp->process_handle); -#else +#else /* !(defined(_WIN32)) */ retval = tor_spawn_background(mp->argv[0], (const char **)mp->argv, env, &mp->process_handle); -#endif +#endif /* defined(_WIN32) */ process_environment_free(env); @@ -1095,8 +1094,6 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) transport = transport_new(&tor_addr, port, method_name, PROXY_NONE, args_string); - if (!transport) - goto err; smartlist_add(mp->transports, transport); @@ -1187,8 +1184,6 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) } transport = transport_new(&tor_addr, port, method_name, socks_ver, NULL); - if (!transport) - goto err; smartlist_add(mp->transports, transport); @@ -1322,7 +1317,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 = diff --git a/src/or/transports.h b/src/or/transports.h index 7de90dcbec..e368e447c3 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 */ /** @@ -133,7 +133,7 @@ STATIC char* get_pt_proxy_uri(void); STATIC void free_execve_args(char **arg); -#endif +#endif /* defined(PT_PRIVATE) */ -#endif +#endif /* !defined(TOR_TRANSPORTS_H) */ |