diff options
Diffstat (limited to 'src/or')
35 files changed, 3030 insertions, 1328 deletions
diff --git a/src/or/Makefile.am b/src/or/Makefile.am deleted file mode 100644 index 3cc789a1be..0000000000 --- a/src/or/Makefile.am +++ /dev/null @@ -1,158 +0,0 @@ -bin_PROGRAMS = tor -noinst_LIBRARIES = libtor.a - -if BUILD_NT_SERVICES -tor_platform_source=ntmain.c -else -tor_platform_source= -endif - -EXTRA_DIST=ntmain.c or_sha1.i Makefile.nmake - -if USE_EXTERNAL_EVDNS -evdns_source= -else -evdns_source=eventdns.c -endif - -libtor_a_SOURCES = \ - buffers.c \ - circuitbuild.c \ - circuitlist.c \ - circuituse.c \ - command.c \ - config.c \ - connection.c \ - connection_edge.c \ - connection_or.c \ - control.c \ - cpuworker.c \ - directory.c \ - dirserv.c \ - dirvote.c \ - dns.c \ - dnsserv.c \ - geoip.c \ - hibernate.c \ - main.c \ - microdesc.c \ - networkstatus.c \ - nodelist.c \ - onion.c \ - transports.c \ - policies.c \ - reasons.c \ - relay.c \ - rendclient.c \ - rendcommon.c \ - rendmid.c \ - rendservice.c \ - rephist.c \ - router.c \ - routerlist.c \ - routerparse.c \ - status.c \ - $(evdns_source) \ - $(tor_platform_source) \ - config_codedigest.c - -#libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \ -# ../common/libor-event.a - - -tor_SOURCES = tor_main.c - -AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ - -DLOCALSTATEDIR="\"$(localstatedir)\"" \ - -DBINDIR="\"$(bindir)\"" - -# -L flags need to go in LDFLAGS. -l flags need to go in LDADD. -# This seems to matter nowhere but on windows, but I assure you that it -# matters a lot there, and is quite hard to debug if you forget to do it. - - -tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ -tor_LDADD = ./libtor.a ../common/libor.a ../common/libor-crypto.a \ - ../common/libor-event.a \ - @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ - -noinst_HEADERS = \ - buffers.h \ - circuitbuild.h \ - circuitlist.h \ - circuituse.h \ - command.h \ - config.h \ - connection.h \ - connection_edge.h \ - connection_or.h \ - control.h \ - cpuworker.h \ - directory.h \ - dirserv.h \ - dirvote.h \ - dns.h \ - dnsserv.h \ - eventdns.h \ - eventdns_tor.h \ - geoip.h \ - hibernate.h \ - main.h \ - microdesc.h \ - networkstatus.h \ - nodelist.h \ - ntmain.h \ - onion.h \ - or.h \ - transports.h \ - policies.h \ - reasons.h \ - relay.h \ - rendclient.h \ - rendcommon.h \ - rendmid.h \ - rendservice.h \ - rephist.h \ - router.h \ - routerlist.h \ - routerparse.h \ - status.h \ - micro-revision.i - -config_codedigest.o: or_sha1.i - -tor_main.o: micro-revision.i - -micro-revision.i: FORCE - @rm -f micro-revision.tmp; \ - if test -d "$(top_srcdir)/.git" && \ - test -x "`which git 2>&1;true`"; then \ - HASH="`cd "$(top_srcdir)" && git rev-parse --short=16 HEAD`"; \ - echo \"$$HASH\" > micro-revision.tmp; \ - fi; \ - if test ! -f micro-revision.tmp ; then \ - if test ! -f micro-revision.i ; then \ - echo '""' > micro-revision.i; \ - fi; \ - elif test ! -f micro-revision.i || \ - test x"`cat micro-revision.tmp`" != x"`cat micro-revision.i`"; then \ - mv micro-revision.tmp micro-revision.i; \ - fi; true - -or_sha1.i: $(tor_SOURCES) $(libtor_a_SOURCES) - if test "@SHA1SUM@" != none; then \ - (cd "$(srcdir)" && "@SHA1SUM@" $(tor_SOURCES) $(libtor_a_SOURCES)) | \ - "@SED@" -n 's/^\(.*\)$$/"\1\\n"/p' > or_sha1.i; \ - elif test "@OPENSSL@" != none; then \ - (cd "$(srcdir)" && "@OPENSSL@" sha1 $(tor_SOURCES) $(libtor_a_SOURCES)) | \ - "@SED@" -n 's/SHA1(\(.*\))= \(.*\)/"\2 \1\\n"/p' > or_sha1.i; \ - else \ - rm or_sha1.i; \ - touch or_sha1.i; \ - fi - -CLEANFILES = micro-revision.i - -#Dummy target to ensure that micro-revision.i _always_ gets built. -FORCE: diff --git a/src/or/buffers.c b/src/or/buffers.c index 9acc22971a..a8d06cef1e 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -192,8 +192,6 @@ chunk_new_with_alloc_size(size_t alloc) freelist->lowest_length = freelist->cur_length; ++freelist->n_hit; } else { - /* XXXX take advantage of tor_malloc_roundup, once we know how that - * affects freelists. */ if (freelist) ++freelist->n_alloc; else @@ -216,7 +214,7 @@ static INLINE chunk_t * chunk_new_with_alloc_size(size_t alloc) { chunk_t *ch; - ch = tor_malloc_roundup(&alloc); + ch = tor_malloc(alloc); ch->next = NULL; ch->datalen = 0; ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index e5576018a6..2a449b0cc0 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2608,12 +2608,12 @@ pathbias_get_scale_threshold(const or_options_t *options) static int pathbias_get_scale_factor(const or_options_t *options) { -#define DFLT_PATH_BIAS_SCALE_FACTOR 4 +#define DFLT_PATH_BIAS_SCALE_FACTOR 2 if (options->PathBiasScaleFactor >= 1) return options->PathBiasScaleFactor; else return networkstatus_get_param(NULL, "pb_scalefactor", - DFLT_PATH_BIAS_SCALE_THRESHOLD, 1, INT32_MAX); + DFLT_PATH_BIAS_SCALE_FACTOR, 1, INT32_MAX); } static const char * @@ -2645,6 +2645,14 @@ pathbias_count_first_hop(origin_circuit_t *circ) RATELIM_INIT(FIRST_HOP_NOTICE_INTERVAL); char *rate_msg = NULL; + /* We can't do path bias accounting without entry guards. + * Testing and controller circuits also have no guards. */ + if (get_options()->UseEntryGuards == 0 || + circ->_base.purpose == CIRCUIT_PURPOSE_TESTING || + circ->_base.purpose == CIRCUIT_PURPOSE_CONTROLLER) { + return 0; + } + /* Completely ignore one hop circuits */ if (circ->build_state->onehop_tunnel) { tor_assert(circ->build_state->desired_path_len == 1); @@ -2739,6 +2747,14 @@ pathbias_count_success(origin_circuit_t *circ) RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); char *rate_msg = NULL; + /* We can't do path bias accounting without entry guards. + * Testing and controller circuits also have no guards. */ + if (get_options()->UseEntryGuards == 0 || + circ->_base.purpose == CIRCUIT_PURPOSE_TESTING || + circ->_base.purpose == CIRCUIT_PURPOSE_CONTROLLER) { + return; + } + /* Ignore one hop circuits */ if (circ->build_state->onehop_tunnel) { tor_assert(circ->build_state->desired_path_len == 1); @@ -2772,12 +2788,15 @@ pathbias_count_success(origin_circuit_t *circ) } if (guard->first_hops < guard->circuit_successes) { - log_info(LD_BUG, "Unexpectedly high circuit_successes (%u/%u) " + log_notice(LD_BUG, "Unexpectedly high circuit_successes (%u/%u) " "for guard %s=%s", guard->circuit_successes, guard->first_hops, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } - } else { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + } else if (circ->_base.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { if ((rate_msg = rate_limit_log(&success_notice_limit, approx_time()))) { log_info(LD_BUG, @@ -2822,9 +2841,11 @@ entry_guard_inc_first_hop_count(entry_guard_t *guard) if (guard->circuit_successes/((double)guard->first_hops) < pathbias_get_disable_rate(options)) { - log_info(LD_PROTOCOL, + /* This message is currently disabled by default. */ + log_warn(LD_PROTOCOL, "Extremely low circuit success rate %u/%u for guard %s=%s. " - "This might indicate an attack, or a bug.", + "This indicates either an overloaded guard, an attack, or " + "a bug.", guard->circuit_successes, guard->first_hops, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); @@ -2835,7 +2856,7 @@ entry_guard_inc_first_hop_count(entry_guard_t *guard) < pathbias_get_notice_rate(options) && !guard->path_bias_notice) { guard->path_bias_notice = 1; - log_info(LD_PROTOCOL, + log_notice(LD_PROTOCOL, "Low circuit success rate %u/%u for guard %s=%s.", guard->circuit_successes, guard->first_hops, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); @@ -2845,8 +2866,18 @@ entry_guard_inc_first_hop_count(entry_guard_t *guard) /* If we get a ton of circuits, just scale everything down */ if (guard->first_hops > (unsigned)pathbias_get_scale_threshold(options)) { const int scale_factor = pathbias_get_scale_factor(options); - guard->first_hops /= scale_factor; - guard->circuit_successes /= scale_factor; + /* For now, only scale if there will be no rounding error... + * XXX024: We want to switch to a real moving average for 0.2.4. */ + if ((guard->first_hops % scale_factor) == 0 && + (guard->circuit_successes % scale_factor) == 0) { + log_info(LD_PROTOCOL, + "Scaling pathbias counts to (%u/%u)/%d for guard %s=%s", + guard->circuit_successes, guard->first_hops, + scale_factor, guard->nickname, hex_str(guard->identity, + DIGEST_LEN)); + guard->first_hops /= scale_factor; + guard->circuit_successes /= scale_factor; + } } guard->first_hops++; log_info(LD_PROTOCOL, "Got success count %u/%u for guard %s=%s", @@ -3761,12 +3792,10 @@ onion_extend_cpath(origin_circuit_t *circ) } else if (cur_len == 0) { /* picking first node */ const node_t *r = choose_good_entry_server(purpose, state); if (r) { - /* If we're extending to a bridge, use the preferred address - rather than the primary, for potentially extending to an IPv6 - bridge. */ - int use_pref_addr = (r->ri != NULL && - r->ri->purpose == ROUTER_PURPOSE_BRIDGE); - info = extend_info_from_node(r, use_pref_addr); + /* If we're a client, use the preferred address rather than the + primary address, for potentially connecting to an IPv6 OR + port. */ + info = extend_info_from_node(r, server_mode(get_options()) == 0); tor_assert(info); } } else { @@ -3832,47 +3861,45 @@ extend_info_alloc(const char *nickname, const char *digest, return info; } -/** Allocate and return a new extend_info_t that can be used to build - * a circuit to or through the router <b>r</b>. Use the primary - * address of the router unless <b>for_direct_connect</b> is true, in - * which case the preferred address is used instead. */ +/** Allocate and return a new extend_info that can be used to build a + * circuit to or through the node <b>node</b>. Use the primary address + * of the node (i.e. its IPv4 address) unless + * <b>for_direct_connect</b> is true, in which case the preferred + * address is used instead. May return NULL if there is not enough + * info about <b>node</b> to extend to it--for example, if there is no + * routerinfo_t or microdesc_t. + **/ extend_info_t * -extend_info_from_router(const routerinfo_t *r, int for_direct_connect) +extend_info_from_node(const node_t *node, int for_direct_connect) { tor_addr_port_t ap; - tor_assert(r); + + if (node->ri == NULL && (node->rs == NULL || node->md == NULL)) + return NULL; if (for_direct_connect) - router_get_pref_orport(r, &ap); + node_get_pref_orport(node, &ap); else - router_get_prim_orport(r, &ap); - return extend_info_alloc(r->nickname, r->cache_info.identity_digest, - r->onion_pkey, &ap.addr, ap.port); -} + node_get_prim_orport(node, &ap); -/** Allocate and return a new extend_info that can be used to build a - * circuit to or through the node <b>node</b>. Use the primary address - * of the node unless <b>for_direct_connect</b> is true, in which case - * the preferred address is used instead. May return NULL if there is - * not enough info about <b>node</b> to extend to it--for example, if - * there is no routerinfo_t or microdesc_t. - **/ -extend_info_t * -extend_info_from_node(const node_t *node, int for_direct_connect) -{ - if (node->ri) { - return extend_info_from_router(node->ri, for_direct_connect); - } else if (node->rs && node->md) { - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, node->rs->addr); + log_debug(LD_CIRC, "using %s:%d for %s", + fmt_and_decorate_addr(&ap.addr), ap.port, + node->ri ? node->ri->nickname : node->rs->nickname); + + if (node->ri) + return extend_info_alloc(node->ri->nickname, + node->identity, + node->ri->onion_pkey, + &ap.addr, + ap.port); + else if (node->rs && node->md) return extend_info_alloc(node->rs->nickname, node->identity, node->md->onion_pkey, - &addr, - node->rs->or_port); - } else { + &ap.addr, + ap.port); + else return NULL; - } } /** Release storage held by an extend_info_t struct. */ @@ -5144,203 +5171,37 @@ bridge_free(bridge_info_t *bridge) tor_free(bridge); } -/** A list of pluggable transports found in torrc. */ -static smartlist_t *transport_list = NULL; - -/** Mark every entry of the transport list to be removed on our next call to - * sweep_transport_list unless it has first been un-marked. */ -void -mark_transport_list(void) -{ - if (!transport_list) - transport_list = smartlist_new(); - SMARTLIST_FOREACH(transport_list, transport_t *, t, - t->marked_for_removal = 1); -} - -/** Remove every entry of the transport list that was marked with - * mark_transport_list if it has not subsequently been un-marked. */ -void -sweep_transport_list(void) -{ - if (!transport_list) - transport_list = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, t) { - if (t->marked_for_removal) { - SMARTLIST_DEL_CURRENT(transport_list, t); - transport_free(t); - } - } SMARTLIST_FOREACH_END(t); -} - -/** Initialize the pluggable transports list to empty, creating it if - * needed. */ -void -clear_transport_list(void) -{ - if (!transport_list) - transport_list = smartlist_new(); - SMARTLIST_FOREACH(transport_list, transport_t *, t, transport_free(t)); - smartlist_clear(transport_list); -} - -/** Free the pluggable transport struct <b>transport</b>. */ -void -transport_free(transport_t *transport) -{ - if (!transport) - return; - - tor_free(transport->name); - tor_free(transport); -} - -/** Returns the transport in our transport list that has the name <b>name</b>. - * Else returns NULL. */ -transport_t * -transport_get_by_name(const char *name) +/** 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) { - tor_assert(name); - - if (!transport_list) + if (!bridge_list) return NULL; - - SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, transport) { - if (!strcmp(transport->name, name)) - return transport; - } SMARTLIST_FOREACH_END(transport); - - return NULL; -} - -/** Returns a transport_t struct for a transport proxy supporting the - protocol <b>name</b> listening at <b>addr</b>:<b>port</b> using - SOCKS version <b>socks_ver</b>. */ -transport_t * -transport_new(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver) -{ - transport_t *t = tor_malloc_zero(sizeof(transport_t)); - - tor_addr_copy(&t->addr, addr); - t->port = port; - t->name = tor_strdup(name); - t->socks_version = socks_ver; - - return t; -} - -/** Resolve any conflicts that the insertion of transport <b>t</b> - * might cause. - * Return 0 if <b>t</b> is OK and should be registered, 1 if there is - * a transport identical to <b>t</b> already registered and -1 if - * <b>t</b> cannot be added due to conflicts. */ -static int -transport_resolve_conflicts(transport_t *t) -{ - /* This is how we resolve transport conflicts: - - If there is already a transport with the same name and addrport, - we either have duplicate torrc lines OR we are here post-HUP and - this transport was here pre-HUP as well. In any case, mark the - old transport so that it doesn't get removed and ignore the new - one. Our caller has to free the new transport so we return '1' to - signify this. - - If there is already a transport with the same name but different - addrport: - * if it's marked for removal, it means that it either has a lower - priority than 't' in torrc (otherwise the mark would have been - cleared by the paragraph above), or it doesn't exist at all in - the post-HUP torrc. We destroy the old transport and register 't'. - * if it's *not* marked for removal, it means that it was newly - added in the post-HUP torrc or that it's of higher priority, in - this case we ignore 't'. */ - transport_t *t_tmp = transport_get_by_name(t->name); - if (t_tmp) { /* same name */ - if (tor_addr_eq(&t->addr, &t_tmp->addr) && (t->port == t_tmp->port)) { - /* same name *and* addrport */ - t_tmp->marked_for_removal = 0; - return 1; - } else { /* same name but different addrport */ - if (t_tmp->marked_for_removal) { /* marked for removal */ - log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " - "but there was already a transport marked for deletion at " - "'%s:%u'. We deleted the old transport and registered the " - "new one.", t->name, fmt_addr(&t->addr), t->port, - fmt_addr(&t_tmp->addr), t_tmp->port); - smartlist_remove(transport_list, t_tmp); - transport_free(t_tmp); - } else { /* *not* marked for removal */ - log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " - "but the same transport already exists at '%s:%u'. " - "Skipping.", t->name, fmt_addr(&t->addr), t->port, - fmt_addr(&t_tmp->addr), t_tmp->port); - return -1; + 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; } - } - - return 0; -} - -/** Add transport <b>t</b> to the internal list of pluggable - * transports. - * Returns 0 if the transport was added correctly, 1 if the same - * transport was already registered (in this case the caller must - * free the transport) and -1 if there was an error. */ -int -transport_add(transport_t *t) -{ - int r; - tor_assert(t); - - r = transport_resolve_conflicts(t); - - switch (r) { - case 0: /* should register transport */ - if (!transport_list) - transport_list = smartlist_new(); - smartlist_add(transport_list, t); - return 0; - default: /* let our caller know the return code */ - return r; - } -} - -/** Remember a new pluggable transport proxy at <b>addr</b>:<b>port</b>. - * <b>name</b> is set to the name of the protocol this proxy uses. - * <b>socks_ver</b> is set to the SOCKS version of the proxy. */ -int -transport_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver) -{ - transport_t *t = transport_new(addr, port, name, socks_ver); - - int r = transport_add(t); - - switch (r) { - case -1: - default: - log_notice(LD_GENERAL, "Could not add transport %s at %s:%u. Skipping.", - t->name, fmt_addr(&t->addr), t->port); - transport_free(t); - return -1; - case 1: - log_info(LD_GENERAL, "Succesfully registered transport %s at %s:%u.", - t->name, fmt_addr(&t->addr), t->port); - transport_free(t); /* falling */ - return 0; - case 0: - log_info(LD_GENERAL, "Succesfully registered transport %s at %s:%u.", - t->name, fmt_addr(&t->addr), t->port); - return 0; - } + SMARTLIST_FOREACH_END(bridge); + return NULL; } -/** Return a bridge pointer if <b>ri</b> is one of our known bridges - * (either by comparing keys if possible, else by comparing addr/port). - * Else 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. */ static bridge_info_t * get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, uint16_t port, @@ -5366,11 +5227,13 @@ get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, static bridge_info_t * get_configured_bridge_by_routerinfo(const routerinfo_t *ri) { - tor_addr_port_t ap; - - router_get_pref_orport(ri, &ap); - return get_configured_bridge_by_addr_port_digest(&ap.addr, ap.port, - ri->cache_info.identity_digest); + 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. */ @@ -5384,30 +5247,12 @@ routerinfo_is_a_configured_bridge(const routerinfo_t *ri) int node_is_a_configured_bridge(const node_t *node) { - int retval = 0; /* Negative. */ - smartlist_t *orports = NULL; - - if (!node) - goto out; - - orports = node_get_all_orports(node); - if (orports == NULL) - goto out; - - SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, orport) { - if (get_configured_bridge_by_addr_port_digest(&orport->addr, orport->port, - node->identity) != NULL) { - retval = 1; - goto out; - } - } SMARTLIST_FOREACH_END(orport); - - out: - if (orports != NULL) { - SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); - smartlist_free(orports); - orports = NULL; - } + 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; } @@ -5769,21 +5614,26 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } } - /* Indicate that we prefer connecting to this bridge over the - protocol that the bridge address indicates. Last bridge - descriptor handled wins. */ - ri->ipv6_preferred = tor_addr_family(&bridge->addr) == AF_INET6; + /* Mark bridge as preferably connected to over IPv6 if its IPv6 + address is in a Bridge line and ClientPreferIPv6ORPort is + set. Unless both is true, a potential IPv6 OR port of this + bridge won't get selected. + + XXX ipv6_preferred is never reset (#6757) */ + if (get_options()->ClientPreferIPv6ORPort == 1 && + tor_addr_family(&bridge->addr) == AF_INET6) + node->ipv6_preferred = 1; /* 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; - router_get_pref_orport(ri, &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:%d).", ri->nickname, - ri->ipv6_preferred ? "IPv6" : "IPv4", + node->ipv6_preferred ? "IPv6" : "IPv4", fmt_addr(&ap.addr), ap.port); } } @@ -5973,10 +5823,7 @@ entry_guards_free_all(void) entry_guards = NULL; } clear_bridge_list(); - clear_transport_list(); smartlist_free(bridge_list); - smartlist_free(transport_list); bridge_list = NULL; - transport_list = NULL; } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 984d04a99e..b3ebf094a8 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -12,21 +12,6 @@ #ifndef _TOR_CIRCUITBUILD_H #define _TOR_CIRCUITBUILD_H -/** Represents a pluggable transport proxy used by a bridge. */ -typedef struct { - /** SOCKS version: One of PROXY_SOCKS4, PROXY_SOCKS5. */ - int socks_version; - /** Name of pluggable transport protocol */ - char *name; - /** Address of proxy */ - tor_addr_t addr; - /** Port of proxy */ - uint16_t port; - /** Boolean: We are re-parsing our transport list, and we are going to remove - * this one if we don't find it in the list of configured transports. */ - unsigned marked_for_removal : 1; -} transport_t; - char *circuit_list_path(origin_circuit_t *circ, int verbose); char *circuit_list_path_for_controller(origin_circuit_t *circ); void circuit_log_path(int severity, unsigned int domain, @@ -59,10 +44,7 @@ void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop); extend_info_t *extend_info_alloc(const char *nickname, const char *digest, crypto_pk_t *onion_key, const tor_addr_t *addr, uint16_t port); -extend_info_t *extend_info_from_router(const routerinfo_t *r, - int for_direct_connect); -extend_info_t *extend_info_from_node(const node_t *node, - int for_direct_connect); +extend_info_t *extend_info_from_node(const node_t *r, int for_direct_connect); extend_info_t *extend_info_dup(extend_info_t *info); void extend_info_free(extend_info_t *info); const node_t *build_state_get_exit_node(cpath_build_state_t *state); @@ -82,8 +64,6 @@ int getinfo_helper_entry_guards(control_connection_t *conn, void mark_bridge_list(void); void sweep_bridge_list(void); -void mark_transport_list(void); -void sweep_transport_list(void); int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); int node_is_a_configured_bridge(const node_t *node); @@ -151,21 +131,12 @@ void circuit_build_times_network_circ_success(circuit_build_times_t *cbt); /* DOCDOC circuit_build_times_get_bw_scale */ int circuit_build_times_get_bw_scale(networkstatus_t *ns); -void clear_transport_list(void); -int transport_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver); -int transport_add(transport_t *t); -void transport_free(transport_t *transport); -transport_t *transport_new(const tor_addr_t *addr, uint16_t port, - const char *name, int socks_ver); - /* DOCDOC find_transport_name_by_bridge_addrport */ const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); - +struct transport_t; int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, - const transport_t **transport); -transport_t *transport_get_by_name(const char *name); + const struct transport_t **transport); #endif diff --git a/src/or/command.c b/src/or/command.c index abf664c1e2..88603c924a 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -808,7 +808,7 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) time_t now = time(NULL); long apparent_skew = 0; - uint32_t my_apparent_addr = 0; + tor_addr_t my_apparent_addr = TOR_ADDR_NULL; if (conn->link_proto < 2) { log_fn(LOG_PROTOCOL_WARN, LD_OR, @@ -868,7 +868,9 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) connection_mark_for_close(TO_CONN(conn)); return; } else if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { - my_apparent_addr = ntohl(get_uint32(my_addr_ptr)); + tor_addr_from_ipv4n(&my_apparent_addr, get_uint32(my_addr_ptr)); + } 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); } n_other_addrs = (uint8_t) *cp++; @@ -921,7 +923,6 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) /* XXX maybe act on my_apparent_addr, if the source is sufficiently * trustworthy. */ - (void)my_apparent_addr; if (connection_or_set_state_open(conn)<0) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got good NETINFO cell from %s:%d; but " @@ -931,10 +932,13 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) connection_mark_for_close(TO_CONN(conn)); } else { log_info(LD_OR, "Got good NETINFO cell from %s:%d; OR connection is now " - "open, using protocol version %d. Its ID digest is %s", + "open, using protocol version %d. Its ID digest is %s. " + "Our address is apparently %s.", safe_str_client(conn->_base.address), conn->_base.port, (int)conn->link_proto, - hex_str(conn->identity_digest, DIGEST_LEN)); + hex_str(conn->identity_digest, DIGEST_LEN), + tor_addr_is_null(&my_apparent_addr) ? + "<none>" : fmt_and_decorate_addr(&my_apparent_addr)); } assert_connection_ok(TO_CONN(conn),time(NULL)); } diff --git a/src/or/config.c b/src/or/config.c index b9170f4200..f21016d48f 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -204,6 +204,8 @@ static config_var_t _option_vars[] = { V(AuthDirListBadExits, BOOL, "0"), V(AuthDirMaxServersPerAddr, UINT, "2"), V(AuthDirMaxServersPerAuthAddr,UINT, "5"), + V(AuthDirHasIPv6Connectivity, AUTOBOOL, "auto"), + V(AuthDirPublishIPv6, BOOL, "0"), VAR("AuthoritativeDirectory", BOOL, AuthoritativeDir, "0"), V(AutomapHostsOnResolve, BOOL, "0"), V(AutomapHostsSuffixes, CSV, ".onion,.exit"), @@ -223,8 +225,10 @@ static config_var_t _option_vars[] = { V(CircuitPriorityHalflife, DOUBLE, "-100.0"), /*negative:'Use default'*/ V(ClientDNSRejectInternalAddresses, BOOL,"1"), V(ClientOnly, BOOL, "0"), + V(ClientPreferIPv6ORPort, BOOL, "0"), V(ClientRejectInternalAddresses, BOOL, "1"), V(ClientTransportPlugin, LINELIST, NULL), + V(ClientUseIPv6, BOOL, "0"), V(ConsensusParams, STRING, NULL), V(ConnLimit, UINT, "1000"), V(ConnDirectionStatistics, BOOL, "0"), @@ -1554,7 +1558,7 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); /* reload keys as needed for rendezvous services. */ - if (rend_service_load_keys()<0) { + if (rend_service_load_all_keys()<0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } @@ -4059,7 +4063,7 @@ options_validate(or_options_t *old_options, or_options_t *options, log_notice(LD_GENERAL, "Tor is not configured as a relay but you specified" " a ServerTransportPlugin line (%s). The ServerTransportPlugin " "line will be ignored.", - esc_for_log(options->ServerTransportPlugin->value)); + escaped(options->ServerTransportPlugin->value)); } if (options->ConstrainedSockets) { @@ -7296,6 +7300,20 @@ getinfo_helper_config(control_connection_t *conn, *answer = smartlist_join_strings(sl, "", 0, NULL); SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); + } else if (!strcmp(question, "config/defaults")) { + smartlist_t *sl = smartlist_new(); + int i; + for (i = 0; _option_vars[i].name; ++i) { + const config_var_t *var = &_option_vars[i]; + if (var->initvalue != NULL) { + char *val = esc_for_log(var->initvalue); + smartlist_add_asprintf(sl, "%s %s\n",var->name,val); + tor_free(val); + } + } + *answer = smartlist_join_strings(sl, "", 0, NULL); + SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); + smartlist_free(sl); } return 0; } diff --git a/src/or/connection.c b/src/or/connection.c index 364e4912da..7a39e5e2df 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -34,6 +34,7 @@ #include "rendcommon.h" #include "rephist.h" #include "router.h" +#include "transports.h" #include "routerparse.h" #ifdef USE_BUFFEREVENTS @@ -238,7 +239,16 @@ dir_connection_new(int socket_family) } /** Allocate and return a new or_connection_t, initialized as by - * connection_init(). */ + * connection_init(). + * + * Set timestamp_last_added_nonpadding to now. + * + * Assign a pseudorandom next_circ_id between 0 and 2**15. + * + * Initialize active_circuit_pqueue. + * + * Set active_circuit_pqueue_last_recalibrated to current cell_ewma tick. + */ or_connection_t * or_connection_new(int socket_family) { @@ -256,7 +266,10 @@ or_connection_new(int socket_family) } /** Allocate and return a new entry_connection_t, initialized as by - * connection_init(). */ + * connection_init(). + * + * Allocate space to store the socks_request. + */ entry_connection_t * entry_connection_new(int type, int socket_family) { @@ -338,14 +351,11 @@ connection_new(int type, int socket_family) /** Initializes conn. (you must call connection_add() to link it into the main * array). * + * Set conn-\>magic to the correct value. + * * Set conn-\>type to <b>type</b>. Set conn-\>s and conn-\>conn_array_index to * -1 to signify they are not yet assigned. * - * If conn is not a listener type, allocate buffers for it. If it's - * an AP type, allocate space to store the socks_request. - * - * Assign a pseudorandom next_circ_id between 0 and 2**15. - * * Initialize conn's timestamps to now. */ static void @@ -1089,7 +1099,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, * nmap does). We want to detect that, and not go on with the connection. */ static int -check_sockaddr(struct sockaddr *sa, int len, int level) +check_sockaddr(const struct sockaddr *sa, int len, int level) { int ok = 1; @@ -1202,11 +1212,6 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; } - if (check_sockaddr_family_match(remote->sa_family, conn) < 0) { - tor_close_socket(news); - return 0; - } - tor_addr_from_sockaddr(&addr, remote, &port); /* process entrance policies here, before we even create the connection */ @@ -3360,13 +3365,6 @@ connection_flush(connection_t *conn) return connection_handle_write(conn, 1); } -/** OpenSSL TLS record size is 16383; this is close. The goal here is to - * push data out as soon as we know there's enough for a TLS record, so - * during periods of high load we won't read entire megabytes from - * input before pushing any data out. It also has the feature of not - * growing huge outbufs unless something is slow. */ -#define MIN_TLS_FLUSHLEN 15872 - /** Append <b>len</b> bytes of <b>string</b> onto <b>conn</b>'s * outbuf, and ask it to start writing. * @@ -3375,10 +3373,9 @@ connection_flush(connection_t *conn) * negative, this is the last data to be compressed, and the connection's zlib * state should be flushed. * - * If it's an OR conn and an entire TLS record is ready, then try to - * flush the record now. Similarly, 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. + * 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. */ void _connection_write_to_buf_impl(const char *string, size_t len, @@ -3446,7 +3443,6 @@ _connection_write_to_buf_impl(const char *string, size_t len, if (zlib) { conn->outbuf_flushlen += buf_datalen(conn->outbuf) - old_datalen; } else { - ssize_t extra = 0; conn->outbuf_flushlen += len; /* Should we try flushing the outbuf now? */ @@ -3456,14 +3452,7 @@ _connection_write_to_buf_impl(const char *string, size_t len, return; } - if (conn->type == CONN_TYPE_OR && - conn->outbuf_flushlen-len < MIN_TLS_FLUSHLEN && - conn->outbuf_flushlen >= MIN_TLS_FLUSHLEN) { - /* We just pushed outbuf_flushlen to MIN_TLS_FLUSHLEN or above; - * we can send out a full TLS frame now if we like. */ - extra = conn->outbuf_flushlen - MIN_TLS_FLUSHLEN; - conn->outbuf_flushlen = MIN_TLS_FLUSHLEN; - } else if (conn->type == CONN_TYPE_CONTROL && + if (conn->type == CONN_TYPE_CONTROL && !connection_is_rate_limited(conn) && conn->outbuf_flushlen-len < 1<<16 && conn->outbuf_flushlen >= 1<<16) { @@ -3483,10 +3472,6 @@ _connection_write_to_buf_impl(const char *string, size_t len, } return; } - if (extra) { - conn->outbuf_flushlen += extra; - connection_start_writing(conn); - } } } diff --git a/src/or/connection_or.c b/src/or/connection_or.c index d016387935..da27cba32d 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1540,7 +1540,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, return -1; } if (authdir_mode_tests_reachability(options)) { - dirserv_orconn_tls_done(conn->_base.address, conn->_base.port, + dirserv_orconn_tls_done(&conn->_base.addr, conn->_base.port, (const char*)peer_id); } @@ -1988,12 +1988,19 @@ connection_or_send_netinfo(or_connection_t *conn) if ((public_server_mode(get_options()) || !conn->is_outgoing) && (me = router_get_my_routerinfo())) { tor_addr_t my_addr; - *out++ = 1; /* only one address is supported. */ + *out++ = 1 + !tor_addr_is_null(&me->ipv6_addr); tor_addr_from_ipv4h(&my_addr, me->addr); len = append_address_to_payload(out, &my_addr); if (len < 0) return -1; + out += len; + + if (!tor_addr_is_null(&me->ipv6_addr)) { + len = append_address_to_payload(out, &me->ipv6_addr); + if (len < 0) + return -1; + } } else { *out = 0; } diff --git a/src/or/control.c b/src/or/control.c index 913d18a7fc..4a0f8d70a1 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -1243,6 +1243,27 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len, return 0; } +struct signal_t { + int sig; + const char *signal_name; +}; + +static const struct signal_t signal_table[] = { + { SIGHUP, "RELOAD" }, + { SIGHUP, "HUP" }, + { SIGINT, "SHUTDOWN" }, + { SIGUSR1, "DUMP" }, + { SIGUSR1, "USR1" }, + { SIGUSR2, "DEBUG" }, + { SIGUSR2, "USR2" }, + { SIGTERM, "HALT" }, + { SIGTERM, "TERM" }, + { SIGTERM, "INT" }, + { SIGNEWNYM, "NEWNYM" }, + { SIGCLEARDNSCACHE, "CLEARDNSCACHE"}, + { 0, NULL }, +}; + /** Called when we get a SIGNAL command. React to the provided signal, and * report success or failure. (If the signal results in a shutdown, success * may not be reported.) */ @@ -1250,7 +1271,8 @@ static int handle_control_signal(control_connection_t *conn, uint32_t len, const char *body) { - int sig; + int sig = -1; + int i; int n = 0; char *s; @@ -1259,27 +1281,19 @@ handle_control_signal(control_connection_t *conn, uint32_t len, while (body[n] && ! TOR_ISSPACE(body[n])) ++n; s = tor_strndup(body, n); - if (!strcasecmp(s, "RELOAD") || !strcasecmp(s, "HUP")) - sig = SIGHUP; - else if (!strcasecmp(s, "SHUTDOWN") || !strcasecmp(s, "INT")) - sig = SIGINT; - else if (!strcasecmp(s, "DUMP") || !strcasecmp(s, "USR1")) - sig = SIGUSR1; - else if (!strcasecmp(s, "DEBUG") || !strcasecmp(s, "USR2")) - sig = SIGUSR2; - else if (!strcasecmp(s, "HALT") || !strcasecmp(s, "TERM")) - sig = SIGTERM; - else if (!strcasecmp(s, "NEWNYM")) - sig = SIGNEWNYM; - else if (!strcasecmp(s, "CLEARDNSCACHE")) - sig = SIGCLEARDNSCACHE; - else { + + for (i = 0; signal_table[i].signal_name != NULL; ++i) { + if (!strcasecmp(s, signal_table[i].signal_name)) { + sig = signal_table[i].sig; + break; + } + } + + if (sig < 0) connection_printf_to_buf(conn, "552 Unrecognized signal code \"%s\"\r\n", s); - sig = -1; - } tor_free(s); - if (sig<0) + if (sig < 0) return 0; send_control_done(conn); @@ -1440,6 +1454,16 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, *answer = smartlist_join_strings(event_names, " ", 0, NULL); smartlist_free(event_names); + } else if (!strcmp(question, "signal/names")) { + smartlist_t *signal_names = smartlist_new(); + int j; + for (j = 0; signal_table[j].signal_name != NULL; ++j) { + smartlist_add(signal_names, (char*)signal_table[j].signal_name); + } + + *answer = smartlist_join_strings(signal_names, " ", 0, NULL); + + smartlist_free(signal_names); } else if (!strcmp(question, "features/names")) { *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS"); } else if (!strcmp(question, "address")) { @@ -2130,10 +2154,14 @@ static const getinfo_item_t getinfo_items[] = { PREFIX("config/", config, "Current configuration values."), DOC("config/names", "List of configuration options, types, and documentation."), + DOC("config/defaults", + "List of default values for configuration options. " + "See also config/names"), ITEM("info/names", misc, "List of GETINFO options, types, and documentation."), ITEM("events/names", misc, "Events that the controller can ask for with SETEVENTS."), + ITEM("signal/names", misc, "Signal names recognized by the SIGNAL command"), ITEM("features/names", misc, "What arguments can USEFEATURE take?"), PREFIX("desc/id/", dir, "Router descriptors by ID."), PREFIX("desc/name/", dir, "Router descriptors by nickname."), diff --git a/src/or/dirserv.c b/src/or/dirserv.c index f1c9c6232d..f229e5995f 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -62,6 +62,16 @@ static cached_dir_t *the_directory = NULL; /** For authoritative directories: the current (v1) network status. */ static cached_dir_t the_runningrouters; +/** Array of start and end of consensus methods used for supported + microdescriptor formats. */ +static const struct consensus_method_range_t { + int low; + int high; +} microdesc_consensus_methods[] = { + {MIN_METHOD_FOR_MICRODESC, MIN_METHOD_FOR_A_LINES - 1}, + {MIN_METHOD_FOR_A_LINES, MAX_SUPPORTED_CONSENSUS_METHOD}, + {-1, -1}}; + static void directory_remove_invalid(void); static cached_dir_t *dirserv_regenerate_directory(void); static char *format_versions_list(config_line_t *ln); @@ -980,6 +990,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) unreachable. */ int answer; + const or_options_t *options = get_options(); node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest); tor_assert(node); @@ -988,17 +999,27 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) answer = ! we_are_hibernating(); } else if (router->is_hibernating && (router->cache_info.published_on + - HIBERNATION_PUBLICATION_SKEW) > router->last_reachable) { + HIBERNATION_PUBLICATION_SKEW) > node->last_reachable) { /* A hibernating router is down unless we (somehow) had contact with it * since it declared itself to be hibernating. */ answer = 0; - } else if (get_options()->AssumeReachable) { + } else if (options->AssumeReachable) { /* If AssumeReachable, everybody is up unless they say they are down! */ answer = 1; } else { - /* Otherwise, a router counts as up if we found it reachable in the last - REACHABLE_TIMEOUT seconds. */ - answer = (now < router->last_reachable + REACHABLE_TIMEOUT); + /* Otherwise, a router counts as up if we found all announced OR + ports reachable in the last REACHABLE_TIMEOUT seconds. + + XXX prop186 For now there's always one IPv4 and at most one + IPv6 OR port. + + If we're not on IPv6, don't consider reachability of potential + IPv6 OR port since that'd kill all dual stack relays until a + majority of the dir auths have IPv6 connectivity. */ + answer = (now < node->last_reachable + REACHABLE_TIMEOUT && + (options->AuthDirHasIPv6Connectivity == 0 || + tor_addr_is_null(&router->ipv6_addr) || + now < node->last_reachable6 + REACHABLE_TIMEOUT)); } if (!answer && running_long_enough_to_decide_unreachable()) { @@ -1008,11 +1029,13 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) REACHABILITY_TEST_CYCLE_PERIOD seconds, then the router has probably been down since at least that time after we last successfully reached it. + + XXX ipv6 */ time_t when = now; - if (router->last_reachable && - router->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD < now) - when = router->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD; + if (node->last_reachable && + node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD < now) + when = node->last_reachable + REACHABILITY_TEST_CYCLE_PERIOD; rep_hist_note_router_unreachable(router->cache_info.identity_digest, when); } @@ -2040,7 +2063,7 @@ version_from_platform(const char *platform) * non-NULL, add a "v" line for the platform. Return 0 on success, -1 on * failure. * - * The format argument has three possible values: + * The format argument has one of the following values: * NS_V2 - Output an entry suitable for a V2 NS opinion document * NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry * NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc @@ -2079,15 +2102,34 @@ routerstatus_format_entry(char *buf, size_t buf_len, log_warn(LD_BUG, "Not enough space in buffer."); return -1; } + cp = buf + strlen(buf); /* TODO: Maybe we want to pass in what we need to build the rest of * this here, instead of in the caller. Then we could use the * networkstatus_type_t values, with an additional control port value * added -MP */ - if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC) + + /* V3 microdesc consensuses don't have "a" lines. */ + if (format == NS_V3_CONSENSUS_MICRODESC) + return 0; + + /* Possible "a" line. At most one for now. */ + if (!tor_addr_is_null(&rs->ipv6_addr)) { + const char *addr_str = fmt_and_decorate_addr(&rs->ipv6_addr); + r = tor_snprintf(cp, buf_len - (cp-buf), + "a %s:%d\n", + addr_str, + (int)rs->ipv6_orport); + if (r<0) { + log_warn(LD_BUG, "Not enough space in buffer."); + return -1; + } + cp += strlen(cp); + } + + if (format == NS_V3_CONSENSUS) return 0; - cp = buf + strlen(buf); /* NOTE: Whenever this list expands, be sure to increase MAX_FLAG_LINE_LEN*/ r = tor_snprintf(cp, buf_len - (cp-buf), "s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", @@ -2114,7 +2156,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, /* length of "opt v \n" */ #define V_LINE_OVERHEAD 7 if (version && strlen(version) < MAX_V_LINE_LEN - V_LINE_OVERHEAD) { - if (tor_snprintf(cp, buf_len - (cp-buf), "opt v %s\n", version)<0) { + if (tor_snprintf(cp, buf_len - (cp-buf), "v %s\n", version)<0) { log_warn(LD_BUG, "Unable to print router version."); return -1; } @@ -2453,6 +2495,16 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname)); rs->or_port = ri->or_port; rs->dir_port = ri->dir_port; + if (options->AuthDirPublishIPv6 == 1 && + !tor_addr_is_null(&ri->ipv6_addr) && + (options->AuthDirHasIPv6Connectivity == 0 || + node->last_reachable6 >= now - REACHABLE_TIMEOUT)) { + /* We're configured for publishing IPv6 OR ports. There's an IPv6 + OR port and it's reachable (or we know that we're not on IPv6) + so copy it to the routerstatus. */ + tor_addr_copy(&rs->ipv6_addr, &ri->ipv6_addr); + rs->ipv6_orport = ri->ipv6_orport; + } } /** Routerstatus <b>rs</b> is part of a group of routers that are on @@ -2715,6 +2767,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, microdescriptors = smartlist_new(); SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { + const struct consensus_method_range_t *cmr = NULL; if (ri->cache_info.published_on >= cutoff) { routerstatus_t *rs; vote_routerstatus_t *vrs; @@ -2736,15 +2789,20 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, rs->is_flagged_running = 0; vrs->version = version_from_platform(ri->platform); - md = dirvote_create_microdescriptor(ri); - if (md) { - char buf[128]; - vote_microdesc_hash_t *h; - dirvote_format_microdesc_vote_line(buf, sizeof(buf), md); - h = tor_malloc(sizeof(vote_microdesc_hash_t)); - h->microdesc_hash_line = tor_strdup(buf); - h->next = NULL; - vrs->microdesc = h; + for (cmr = microdesc_consensus_methods; + cmr->low != -1 && cmr->high != -1; + cmr++) { + md = dirvote_create_microdescriptor(ri, cmr->low); + if (md) { + char buf[128]; + vote_microdesc_hash_t *h; + dirvote_format_microdesc_vote_line(buf, sizeof(buf), md, + cmr->low, cmr->high); + h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); + h->microdesc_hash_line = tor_strdup(buf); + h->next = vrs->microdesc; + vrs->microdesc = h; + } md->last_listed = now; smartlist_add(microdescriptors, md); } @@ -3273,36 +3331,42 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, * Inform the reachability checker that we could get to this guy. */ void -dirserv_orconn_tls_done(const char *address, +dirserv_orconn_tls_done(const tor_addr_t *addr, uint16_t or_port, const char *digest_rcvd) { - routerinfo_t *ri; + node_t *node = NULL; + tor_addr_port_t orport; + routerinfo_t *ri = NULL; time_t now = time(NULL); - tor_assert(address); + tor_assert(addr); tor_assert(digest_rcvd); - ri = router_get_mutable_by_digest(digest_rcvd); - - if (ri == NULL) + node = node_get_mutable_by_id(digest_rcvd); + if (node == NULL || node->ri == NULL) return; + ri = node->ri; - if (!strcasecmp(address, ri->address) && or_port == ri->or_port) { + tor_addr_copy(&orport.addr, addr); + orport.port = or_port; + if (router_has_orport(ri, &orport)) { /* Found the right router. */ 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 -- mark it as reachable. */ - tor_addr_t addr, *addrp=NULL; log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.", router_describe(ri), - address, ri->or_port); - if (tor_addr_parse(&addr, ri->address) != -1) - addrp = &addr; - else - log_warn(LD_BUG, "Couldn't parse IP address \"%s\"", ri->address); - rep_hist_note_router_reachable(digest_rcvd, addrp, or_port, now); - ri->last_reachable = now; + tor_addr_to_str(addrstr, addr, sizeof(addrstr), 1), + ri->or_port); + if (tor_addr_family(addr) == AF_INET) { + rep_hist_note_router_reachable(digest_rcvd, addr, or_port, now); + node->last_reachable = now; + } else if (tor_addr_family(addr) == AF_INET6) { + /* No rephist for IPv6. */ + node->last_reachable6 = now; + } } } } @@ -3325,7 +3389,7 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri, /* It just came out of hibernation; launch a reachability test */ return 1; } - if (! routers_have_same_or_addr(ri, ri_old)) { + if (! routers_have_same_or_addrs(ri, ri_old)) { /* Address or port changed; launch a reachability test */ return 1; } @@ -3338,15 +3402,35 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri, void dirserv_single_reachability_test(time_t now, routerinfo_t *router) { + node_t *node = NULL; tor_addr_t router_addr; + + tor_assert(router); + node = node_get_mutable_by_id(router->cache_info.identity_digest); + tor_assert(node); + + /* IPv4. */ log_debug(LD_OR,"Testing reachability of %s at %s:%u.", router->nickname, router->address, router->or_port); /* Remember when we started trying to determine reachability */ - if (!router->testing_since) - router->testing_since = now; + if (!node->testing_since) + node->testing_since = now; tor_addr_from_ipv4h(&router_addr, router->addr); connection_or_connect(&router_addr, router->or_port, router->cache_info.identity_digest); + + /* Possible IPv6. */ + if (!tor_addr_is_null(&router->ipv6_addr)) { + char addrstr[TOR_ADDR_BUF_LEN]; + log_debug(LD_OR, "Testing reachability of %s at %s:%u.", + router->nickname, + tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1), + router->ipv6_orport); + if (!node->testing_since6) + node->testing_since6 = now; + connection_or_connect(&router->ipv6_addr, router->ipv6_orport, + router->cache_info.identity_digest); + } } /** Auth dir server only: load balance such that we only diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 22269b2009..8508c938a8 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -107,7 +107,7 @@ int dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, int is_extrainfo); int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, const char **msg); -void dirserv_orconn_tls_done(const char *address, +void dirserv_orconn_tls_done(const tor_addr_t *addr, uint16_t or_port, const char *digest_rcvd); int dirserv_should_launch_reachability_test(const routerinfo_t *ri, diff --git a/src/or/dirvote.c b/src/or/dirvote.c index b3de90b5c0..082a1efc0a 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -53,29 +53,6 @@ static int dirvote_compute_consensuses(void); static int dirvote_publish_consensus(void); static char *make_consensus_method_list(int low, int high, const char *sep); -/** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 13 - -/** Lowest consensus method that contains a 'directory-footer' marker */ -#define MIN_METHOD_FOR_FOOTER 9 - -/** Lowest consensus method that contains bandwidth weights */ -#define MIN_METHOD_FOR_BW_WEIGHTS 9 - -/** Lowest consensus method that contains consensus params */ -#define MIN_METHOD_FOR_PARAMS 7 - -/** Lowest consensus method that generates microdescriptors */ -#define MIN_METHOD_FOR_MICRODESC 8 - -/** Lowest consensus method that ensures a majority of authorities voted - * for a param. */ -#define MIN_METHOD_FOR_MAJORITY_PARAMS 12 - -/** Lowest consensus method where microdesc consensuses omit any entry - * with no microdesc. */ -#define MIN_METHOD_FOR_MANDATORY_MICRODESC 13 - /* ===== * Voting * =====*/ @@ -430,6 +407,21 @@ _compare_vote_rs(const void **_a, const void **_b) return compare_vote_rs(a,b); } +/** Helper for sorting OR ports. */ +static int +_compare_orports(const void **_a, const void **_b) +{ + const tor_addr_port_t *a = *_a, *b = *_b; + int r; + + if ((r = tor_addr_compare(&a->addr, &b->addr, CMP_EXACT))) + return r; + if ((r = (((int) b->port) - ((int) a->port)))) + return r; + + return 0; +} + /** Given a list of vote_routerstatus_t, all for the same router identity, * return whichever is most frequent, breaking ties in favor of more * recently published vote_routerstatus_t and in case of ties there, @@ -437,7 +429,8 @@ _compare_vote_rs(const void **_a, const void **_b) */ static vote_routerstatus_t * compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, - char *microdesc_digest256_out) + char *microdesc_digest256_out, + tor_addr_port_t *best_alt_orport_out) { vote_routerstatus_t *most = NULL, *cur = NULL; int most_n = 0, cur_n = 0; @@ -473,6 +466,38 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, tor_assert(most); + /* If we're producing "a" lines, vote on potential alternative (sets + * of) OR port(s) in the winning routerstatuses. + * + * XXX prop186 There's at most one alternative OR port (_the_ IPv6 + * port) for now. */ + if (consensus_method >= MIN_METHOD_FOR_A_LINES && best_alt_orport_out) { + smartlist_t *alt_orports = smartlist_new(); + const tor_addr_port_t *most_alt_orport = NULL; + + SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) { + if (compare_vote_rs(most, rs) == 0 && + !tor_addr_is_null(&rs->status.ipv6_addr) + && rs->status.ipv6_orport) { + smartlist_add(alt_orports, tor_addr_port_new(&rs->status.ipv6_addr, + rs->status.ipv6_orport)); + } + } SMARTLIST_FOREACH_END(rs); + + smartlist_sort(alt_orports, _compare_orports); + most_alt_orport = smartlist_get_most_frequent(alt_orports, _compare_orports); + if (most_alt_orport) { + memcpy(best_alt_orport_out, most_alt_orport, sizeof(tor_addr_port_t)); + log_debug(LD_DIR, "\"a\" line winner for %s is %s:%d", + most->status.nickname, + fmt_and_decorate_addr(&most_alt_orport->addr), + most_alt_orport->port); + } + + SMARTLIST_FOREACH(alt_orports, tor_addr_port_t *, ap, tor_free(ap)); + smartlist_free(alt_orports); + } + if (consensus_method >= MIN_METHOD_FOR_MICRODESC && microdesc_digest256_out) { smartlist_t *digests = smartlist_new(); @@ -1685,6 +1710,7 @@ networkstatus_compute_consensus(smartlist_t *votes, int n_listing = 0; int i; char microdesc_digest[DIGEST256_LEN]; + tor_addr_port_t alt_orport = {TOR_ADDR_NULL, 0}; /* Of the next-to-be-considered digest in each voter, which is first? */ SMARTLIST_FOREACH(votes, networkstatus_t *, v, { @@ -1754,7 +1780,7 @@ networkstatus_compute_consensus(smartlist_t *votes, * routerinfo and its contents are. */ memset(microdesc_digest, 0, sizeof(microdesc_digest)); rs = compute_routerstatus_consensus(matching_descs, consensus_method, - microdesc_digest); + microdesc_digest, &alt_orport); /* Copy bits of that into rs_out. */ memset(&rs_out, 0, sizeof(rs_out)); tor_assert(fast_memeq(lowest_id, rs->status.identity_digest,DIGEST_LEN)); @@ -1765,6 +1791,10 @@ networkstatus_compute_consensus(smartlist_t *votes, rs_out.published_on = rs->status.published_on; rs_out.dir_port = rs->status.dir_port; rs_out.or_port = rs->status.or_port; + if (consensus_method >= MIN_METHOD_FOR_A_LINES) { + tor_addr_copy(&rs_out.ipv6_addr, &alt_orport.addr); + rs_out.ipv6_orport = alt_orport.port; + } rs_out.has_bandwidth = 0; rs_out.has_exitsummary = 0; @@ -3510,7 +3540,7 @@ dirvote_get_vote(const char *fp, int flags) * particular method. **/ microdesc_t * -dirvote_create_microdescriptor(const routerinfo_t *ri) +dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method) { microdesc_t *result = NULL; char *key = NULL, *summary = NULL, *family = NULL; @@ -3526,6 +3556,12 @@ dirvote_create_microdescriptor(const routerinfo_t *ri) smartlist_add_asprintf(chunks, "onion-key\n%s", key); + if (consensus_method >= MIN_METHOD_FOR_A_LINES && + !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport) + smartlist_add_asprintf(chunks, "a %s:%d\n", + fmt_and_decorate_addr(&ri->ipv6_addr), + ri->ipv6_orport); + if (family) smartlist_add_asprintf(chunks, "family %s\n", family); @@ -3559,33 +3595,36 @@ dirvote_create_microdescriptor(const routerinfo_t *ri) return result; } -/** Cached space-separated string to hold */ -static char *microdesc_consensus_methods = NULL; - /** Format the appropriate vote line to describe the microdescriptor <b>md</b> * in a consensus vote document. Write it into the <b>out_len</b>-byte buffer * in <b>out</b>. Return -1 on failure and the number of characters written * on success. */ ssize_t -dirvote_format_microdesc_vote_line(char *out, size_t out_len, - const microdesc_t *md) +dirvote_format_microdesc_vote_line(char *out_buf, size_t out_buf_len, + const microdesc_t *md, + int consensus_method_low, + int consensus_method_high) { + int ret = -1; char d64[BASE64_DIGEST256_LEN+1]; - if (!microdesc_consensus_methods) { - microdesc_consensus_methods = - make_consensus_method_list(MIN_METHOD_FOR_MICRODESC, - MAX_SUPPORTED_CONSENSUS_METHOD, - ","); - tor_assert(microdesc_consensus_methods); - } + char *microdesc_consensus_methods = + make_consensus_method_list(consensus_method_low, + consensus_method_high, + ","); + tor_assert(microdesc_consensus_methods); + if (digest256_to_base64(d64, md->digest)<0) - return -1; + goto out; - if (tor_snprintf(out, out_len, "m %s sha256=%s\n", + if (tor_snprintf(out_buf, out_buf_len, "m %s sha256=%s\n", microdesc_consensus_methods, d64)<0) - return -1; + goto out; - return strlen(out); + ret = strlen(out_buf); + + out: + tor_free(microdesc_consensus_methods); + return ret; } /** If <b>vrs</b> has a hash made for the consensus method <b>method</b> with diff --git a/src/or/dirvote.h b/src/or/dirvote.h index e6f9700614..b3feeff060 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -19,6 +19,32 @@ /** Smallest allowable voting interval. */ #define MIN_VOTE_INTERVAL 300 +/** The highest consensus method that we currently support. */ +#define MAX_SUPPORTED_CONSENSUS_METHOD 14 + +/** Lowest consensus method that contains a 'directory-footer' marker */ +#define MIN_METHOD_FOR_FOOTER 9 + +/** Lowest consensus method that contains bandwidth weights */ +#define MIN_METHOD_FOR_BW_WEIGHTS 9 + +/** Lowest consensus method that contains consensus params */ +#define MIN_METHOD_FOR_PARAMS 7 + +/** Lowest consensus method that generates microdescriptors */ +#define MIN_METHOD_FOR_MICRODESC 8 + +/** Lowest consensus method that ensures a majority of authorities voted + * for a param. */ +#define MIN_METHOD_FOR_MAJORITY_PARAMS 12 + +/** Lowest consensus method where microdesc consensuses omit any entry + * with no microdesc. */ +#define MIN_METHOD_FOR_MANDATORY_MICRODESC 13 + +/** Lowest consensus method that contains "a" lines. */ +#define MIN_METHOD_FOR_A_LINES 14 + void dirvote_free_all(void); /* vote manipulation */ @@ -70,10 +96,11 @@ networkstatus_t * dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, authority_cert_t *cert); -microdesc_t *dirvote_create_microdescriptor(const routerinfo_t *ri); +microdesc_t *dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method); ssize_t dirvote_format_microdesc_vote_line(char *out, size_t out_len, - const microdesc_t *md); - + const microdesc_t *md, + int consensus_method_low, + int consensus_method_high); int vote_routerstatus_find_microdesc_hash(char *digest256_out, const vote_routerstatus_t *vrs, int method, diff --git a/src/or/include.am b/src/or/include.am new file mode 100644 index 0000000000..65ff684925 --- /dev/null +++ b/src/or/include.am @@ -0,0 +1,160 @@ +bin_PROGRAMS+= src/or/tor +noinst_LIBRARIES+= src/or/libtor.a + +if BUILD_NT_SERVICES +tor_platform_source=src/or/ntmain.c +else +tor_platform_source= +endif + +EXTRA_DIST+= src/or/ntmain.c src/or/or_sha1.i src/or/Makefile.nmake + +if USE_EXTERNAL_EVDNS +evdns_source= +else +evdns_source=src/or/eventdns.c +endif + +src_or_libtor_a_SOURCES = \ + src/or/buffers.c \ + src/or/circuitbuild.c \ + src/or/circuitlist.c \ + src/or/circuituse.c \ + src/or/command.c \ + src/or/config.c \ + src/or/connection.c \ + src/or/connection_edge.c \ + src/or/connection_or.c \ + src/or/control.c \ + src/or/cpuworker.c \ + src/or/directory.c \ + src/or/dirserv.c \ + src/or/dirvote.c \ + src/or/dns.c \ + src/or/dnsserv.c \ + src/or/geoip.c \ + src/or/hibernate.c \ + src/or/main.c \ + src/or/microdesc.c \ + src/or/networkstatus.c \ + src/or/nodelist.c \ + src/or/onion.c \ + src/or/transports.c \ + src/or/policies.c \ + src/or/reasons.c \ + src/or/relay.c \ + src/or/rendclient.c \ + src/or/rendcommon.c \ + src/or/rendmid.c \ + src/or/rendservice.c \ + src/or/rephist.c \ + src/or/replaycache.c \ + src/or/router.c \ + src/or/routerlist.c \ + src/or/routerparse.c \ + src/or/status.c \ + $(evdns_source) \ + $(tor_platform_source) \ + src/or/config_codedigest.c + +#libtor_a_LIBADD = ../common/libor.a ../common/libor-crypto.a \ +# ../common/libor-event.a + + +src_or_tor_SOURCES = src/or/tor_main.c +AM_CPPFLAGS += -I$(srcdir)/src/or -Isrc/or + +src/or/tor_main.c: micro-revision.i + +AM_CPPFLAGS += -DSHARE_DATADIR="\"$(datadir)\"" \ + -DLOCALSTATEDIR="\"$(localstatedir)\"" \ + -DBINDIR="\"$(bindir)\"" + +# -L flags need to go in LDFLAGS. -l flags need to go in LDADD. +# This seems to matter nowhere but on windows, but I assure you that it +# matters a lot there, and is quite hard to debug if you forget to do it. + + +src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ +src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-crypto.a \ + src/common/libor-event.a \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ + +noinst_HEADERS+= \ + src/or/buffers.h \ + src/or/circuitbuild.h \ + src/or/circuitlist.h \ + src/or/circuituse.h \ + src/or/command.h \ + src/or/config.h \ + src/or/connection.h \ + src/or/connection_edge.h \ + src/or/connection_or.h \ + src/or/control.h \ + src/or/cpuworker.h \ + src/or/directory.h \ + src/or/dirserv.h \ + src/or/dirvote.h \ + src/or/dns.h \ + src/or/dnsserv.h \ + src/or/eventdns.h \ + src/or/eventdns_tor.h \ + src/or/geoip.h \ + src/or/hibernate.h \ + src/or/main.h \ + src/or/microdesc.h \ + src/or/networkstatus.h \ + src/or/nodelist.h \ + src/or/ntmain.h \ + src/or/onion.h \ + src/or/or.h \ + src/or/transports.h \ + src/or/policies.h \ + src/or/reasons.h \ + src/or/relay.h \ + src/or/rendclient.h \ + src/or/rendcommon.h \ + src/or/rendmid.h \ + src/or/rendservice.h \ + src/or/rephist.h \ + src/or/replaycache.h \ + src/or/router.h \ + src/or/routerlist.h \ + src/or/routerparse.h \ + src/or/status.h \ + micro-revision.i + +src/or/config_codedigest.o: src/or/or_sha1.i + +micro-revision.i: FORCE + @rm -f micro-revision.tmp; \ + if test -d "$(top_srcdir)/.git" && \ + test -x "`which git 2>&1;true`"; then \ + HASH="`cd "$(top_srcdir)" && git rev-parse --short=16 HEAD`"; \ + echo \"$$HASH\" > micro-revision.tmp; \ + fi; \ + if test ! -f micro-revision.tmp ; then \ + if test ! -f micro-revision.i ; then \ + echo '""' > micro-revision.i; \ + fi; \ + elif test ! -f micro-revision.i || \ + test x"`cat micro-revision.tmp`" != x"`cat micro-revision.i`"; then \ + mv micro-revision.tmp micro-revision.i; \ + fi; true + +src/or/or_sha1.i: $(src_or_tor_SOURCES) $(src_or_libtor_a_SOURCES) + $(AM_V_GEN)if test "@SHA1SUM@" != none; then \ + (cd "$(srcdir)" && "@SHA1SUM@" $(src_or_tor_SOURCES) $(src_or_libtor_a_SOURCES)) | \ + "@SED@" -n 's/^\(.*\)$$/"\1\\n"/p' > src/or/or_sha1.i; \ + elif test "@OPENSSL@" != none; then \ + (cd "$(srcdir)" && "@OPENSSL@" sha1 $(src_or_tor_SOURCES) $(src_or_libtor_a_SOURCES)) | \ + "@SED@" -n 's/SHA1(\(.*\))= \(.*\)/"\2 \1\\n"/p' > src/or/or_sha1.i; \ + else \ + rm src/or/or_sha1.i; \ + touch src/or/or_sha1.i; \ + fi + +CLEANFILES+= micro-revision.i + +FORCE: diff --git a/src/or/main.c b/src/or/main.c index 20a1e086a4..f3624f6cfb 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -953,7 +953,8 @@ directory_info_has_arrived(time_t now, int from_cache) const or_options_t *options = get_options(); if (!router_have_minimum_dir_info()) { - int quiet = directory_too_idle_to_fetch_descriptors(options, now); + int quiet = from_cache || + directory_too_idle_to_fetch_descriptors(options, now); log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR, "I learned some more directory information, but not enough to " "build a circuit: %s", get_dir_info_status_string()); @@ -1561,7 +1562,8 @@ run_scheduled_events(time_t now) /** 12. write the heartbeat message */ if (options->HeartbeatPeriod && time_to_next_heartbeat <= now) { - log_heartbeat(now); + if (time_to_next_heartbeat) /* don't log the first heartbeat */ + log_heartbeat(now); time_to_next_heartbeat = now+options->HeartbeatPeriod; } } diff --git a/src/or/microdesc.c b/src/or/microdesc.c index b4d22c1c62..3bda9cbfaf 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -323,8 +323,8 @@ microdesc_cache_reload(microdesc_cache_t *cache) } tor_free(journal_content); } - log_notice(LD_DIR, "Reloaded microdescriptor cache. Found %d descriptors.", - total); + log_info(LD_DIR, "Reloaded microdescriptor cache. Found %d descriptors.", + total); microdesc_cache_rebuild(cache, 0 /* don't force */); diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d17850888d..e35039bdb8 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -115,19 +115,48 @@ node_get_or_create(const char *identity_digest) return node; } -/** Add <b>ri</b> to the nodelist. */ +/** Called when a node's address changes. */ +static void +node_addrs_changed(node_t *node) +{ + node->last_reachable = node->last_reachable6 = 0; + node->testing_since = node->testing_since6 = 0; + node->country = -1; +} + +/** Add <b>ri</b> to an appropriate node in the nodelist. If we replace an + * old routerinfo, and <b>ri_old_out</b> is not NULL, set *<b>ri_old_out</b> + * to the previous routerinfo. + */ node_t * -nodelist_add_routerinfo(routerinfo_t *ri) +nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) { node_t *node; + const char *id_digest; + int had_router = 0; + tor_assert(ri); + init_nodelist(); - node = node_get_or_create(ri->cache_info.identity_digest); + id_digest = ri->cache_info.identity_digest; + node = node_get_or_create(id_digest); + + if (node->ri) { + if (!routers_have_same_or_addrs(node->ri, ri)) { + node_addrs_changed(node); + } + had_router = 1; + if (ri_old_out) + *ri_old_out = node->ri; + } else { + if (ri_old_out) + *ri_old_out = NULL; + } node->ri = ri; if (node->country == -1) node_set_country(node); - if (authdir_mode(get_options())) { + if (authdir_mode(get_options()) && !had_router) { const char *discard=NULL; uint32_t status = dirserv_router_get_status(ri, &discard); dirserv_set_node_flags_from_authoritative_status(node, status); @@ -167,7 +196,7 @@ nodelist_add_microdesc(microdesc_t *md) return node; } -/** Tell the nodelist that the current usable consensus to <b>ns</b>. +/** Tell the nodelist that the current usable consensus is <b>ns</b>. * This makes the nodelist change all of the routerstatus entries for * the nodes, drop nodes that no longer have enough info to get used, * and grab microdescriptors into nodes as appropriate. @@ -177,6 +206,7 @@ nodelist_set_consensus(networkstatus_t *ns) { const or_options_t *options = get_options(); int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + int client = !server_mode(options); init_nodelist(); if (ns->flavor == FLAV_MICRODESC) @@ -213,6 +243,11 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_bad_directory = rs->is_bad_directory; node->is_bad_exit = rs->is_bad_exit; node->is_hs_dir = rs->is_hs_dir; + node->ipv6_preferred = 0; + if (client && options->ClientPreferIPv6ORPort == 1 && + (tor_addr_is_null(&rs->ipv6_addr) == 0 || + (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0))) + node->ipv6_preferred = 1; } } SMARTLIST_FOREACH_END(rs); @@ -231,7 +266,8 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_valid = node->is_running = node->is_hs_dir = node->is_fast = node->is_stable = node->is_possible_guard = node->is_exit = - node->is_bad_exit = node->is_bad_directory = 0; + node->is_bad_exit = node->is_bad_directory = + node->ipv6_preferred = 0; } } } SMARTLIST_FOREACH_END(node); @@ -682,19 +718,6 @@ node_get_all_orports(const node_t *node) return sl; } -/** Copy the primary (IPv4) OR port (IP address and TCP port) for - * <b>node</b> into *<b>ap_out</b>. */ -void -node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out) -{ - if (node->ri) { - router_get_prim_orport(node->ri, ap_out); - } else if (node->rs) { - tor_addr_from_ipv4h(&ap_out->addr, node->rs->addr); - ap_out->port = node->rs->or_port; - } -} - /** Wrapper around node_get_prim_orport for backward compatibility. */ void @@ -718,36 +741,6 @@ node_get_prim_addr_ipv4h(const node_t *node) return 0; } -/** Copy the preferred OR port (IP address and TCP port) for - * <b>node</b> into <b>ap_out</b>. */ -void -node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out) -{ - if (node->ri) { - router_get_pref_orport(node->ri, ap_out); - } else if (node->rs) { - /* No IPv6 in routerstatus_t yet. XXXprop186 ok for private - bridges but needs fixing */ - tor_addr_from_ipv4h(&ap_out->addr, node->rs->addr); - ap_out->port = node->rs->or_port; - } -} - -/** Copy the preferred IPv6 OR port (address and TCP port) for - * <b>node</b> into *<b>ap_out</b>. */ -void -node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out) -{ - if (node->ri) { - router_get_pref_ipv6_orport(node->ri, ap_out); - } else if (node->rs) { - /* No IPv6 in routerstatus_t yet. XXXprop186 ok for private - bridges but needs fixing */ - tor_addr_make_unspec(&ap_out->addr); - ap_out->port = 0; - } -} - /** Copy a string representation of an IP address for <b>node</b> into * the <b>len</b>-byte buffer at <b>buf</b>. */ void @@ -818,3 +811,97 @@ node_get_declared_family(const node_t *node) return NULL; } +/** Return 1 if we prefer the IPv6 address and OR TCP port of + * <b>node</b>, else 0. + * + * We prefer the IPv6 address if the router has an IPv6 address and + * i) the node_t says that we do prefer IPv6 + * or + * ii) the router has no IPv4 address. */ +int +node_ipv6_preferred(const node_t *node) +{ + tor_addr_port_t ipv4_addr; + node_assert_ok(node); + + if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr)) { + if (node->ri) + return !tor_addr_is_null(&node->ri->ipv6_addr); + if (node->md) + return !tor_addr_is_null(&node->md->ipv6_addr); + if (node->rs) + return !tor_addr_is_null(&node->rs->ipv6_addr); + } + return 0; +} + +/** Copy the primary (IPv4) OR port (IP address and TCP port) for + * <b>node</b> into *<b>ap_out</b>. Return 0 if a valid address and + * port was copied, else return non-zero.*/ +int +node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out) +{ + node_assert_ok(node); + tor_assert(ap_out); + + if (node->ri) { + if (node->ri->addr == 0 || node->ri->or_port == 0) + return -1; + tor_addr_from_ipv4h(&ap_out->addr, node->ri->addr); + ap_out->port = node->ri->or_port; + return 0; + } + if (node->rs) { + if (node->rs->addr == 0 || node->rs->or_port == 0) + return -1; + tor_addr_from_ipv4h(&ap_out->addr, node->rs->addr); + ap_out->port = node->rs->or_port; + return 0; + } + return -1; +} + +/** Copy the preferred OR port (IP address and TCP port) for + * <b>node</b> into *<b>ap_out</b>. */ +void +node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out) +{ + tor_assert(ap_out); + + /* Cheap implementation of config option ClientUseIPv6 -- simply + don't prefer IPv6 when ClientUseIPv6 is not set. (See #4455 for + more on this subject.) Note that this filter is too strict since + we're hindering not only clients! Erring on the safe side + shouldn't be a problem though. XXX move this check to where + outgoing connections are made? -LN */ + if (get_options()->ClientUseIPv6 == 1 && node_ipv6_preferred(node)) + node_get_pref_ipv6_orport(node, ap_out); + else + node_get_prim_orport(node, ap_out); +} + +/** Copy the preferred IPv6 OR port (IP address and TCP port) for + * <b>node</b> into *<b>ap_out</b>. */ +void +node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out) +{ + node_assert_ok(node); + tor_assert(ap_out); + + /* We prefer the microdesc over a potential routerstatus here. They + are not being synchronised atm so there might be a chance that + they differ at some point, f.ex. when flipping + UseMicrodescriptors? -LN */ + + if (node->ri) { + tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr); + ap_out->port = node->ri->ipv6_orport; + } else if (node->md) { + tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr); + ap_out->port = node->md->ipv6_orport; + } else if (node->rs) { + tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); + ap_out->port = node->rs->ipv6_orport; + } +} + diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 1e9da88d4e..fb65fa5483 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -5,17 +5,21 @@ /* See LICENSE for licensing information */ /** - * \file microdesc.h - * \brief Header file for microdesc.c. + * \file nodelist.h + * \brief Header file for nodelist.c. **/ #ifndef _TOR_NODELIST_H #define _TOR_NODELIST_H +#define node_assert_ok(n) STMT_BEGIN { \ + tor_assert((n)->ri || (n)->rs); \ + } STMT_END + node_t *node_get_mutable_by_id(const char *identity_digest); 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 *nodelist_add_routerinfo(routerinfo_t *ri); +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); @@ -38,18 +42,18 @@ int node_get_purpose(const node_t *node); int node_is_me(const node_t *node); int node_exit_policy_rejects_all(const node_t *node); smartlist_t *node_get_all_orports(const node_t *node); -void node_get_prim_orport(const node_t *node, tor_addr_port_t *addr_port_out); -void node_get_pref_orport(const node_t *node, tor_addr_port_t *addr_port_out); -void node_get_pref_ipv6_orport(const node_t *node, - tor_addr_port_t *addr_port_out); -uint32_t node_get_prim_addr_ipv4h(const node_t *node); int node_allows_single_hop_exits(const node_t *node); const char *node_get_nickname(const node_t *node); 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); +int node_ipv6_preferred(const node_t *node); +int node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out); smartlist_t *nodelist_get_list(void); diff --git a/src/or/or.h b/src/or/or.h index 9074083a04..4bf99e2eaa 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -98,6 +98,7 @@ #include "address.h" #include "compat_libevent.h" #include "ht.h" +#include "replaycache.h" /* These signals are defined to help handle_control_signal work. */ @@ -1769,8 +1770,6 @@ typedef struct { /** True if, after we have added this router, we should re-launch * tests for it. */ unsigned int needs_retest_if_added:1; - /** True if ipv6_addr:ipv6_orport is preferred. */ - unsigned int ipv6_preferred:1; /** Tor can use this router for general positions in circuits; we got it * from a directory server as usual, or we're an authority and a server @@ -1793,15 +1792,6 @@ typedef struct { * things; see notes on ROUTER_PURPOSE_* macros above. */ uint8_t purpose; - - /* The below items are used only by authdirservers for - * reachability testing. */ - - /** When was the last time we could reach this OR? */ - time_t last_reachable; - /** When did we start testing reachability for this OR? */ - time_t testing_since; - } routerinfo_t; /** Information needed to keep and cache a signed extra-info document. */ @@ -1833,6 +1823,8 @@ typedef struct routerstatus_t { uint32_t addr; /**< IPv4 address for this router. */ uint16_t or_port; /**< OR port for this router. */ uint16_t dir_port; /**< Directory port for this router. */ + tor_addr_t ipv6_addr; /**< IPv6 address for this router. */ + uint16_t ipv6_orport; /**<IPV6 OR port for this router. */ unsigned int is_authority:1; /**< True iff this router is an authority. */ unsigned int is_exit:1; /**< True iff this router is a good exit. */ unsigned int is_stable:1; /**< True iff this router stays up a long time. */ @@ -1968,6 +1960,10 @@ typedef struct microdesc_t { /** As routerinfo_t.onion_pkey */ crypto_pk_t *onion_pkey; + /** As routerinfo_t.ipv6_add */ + tor_addr_t ipv6_addr; + /** As routerinfo_t.ipv6_orport */ + uint16_t ipv6_orport; /** As routerinfo_t.family */ smartlist_t *family; /** Exit policy summary */ @@ -2006,13 +2002,13 @@ typedef struct node_t { routerstatus_t *rs; /* local info: copied from routerstatus, then possibly frobbed based - * on experience. Authorities set this stuff directly. */ + * on experience. Authorities set this stuff directly. Note that + * these reflect knowledge of the primary (IPv4) OR port only. */ unsigned int is_running:1; /**< As far as we know, is this OR currently * running? */ unsigned int is_valid:1; /**< Has a trusted dirserver validated this OR? - * (For Authdir: Have we validated this OR?) - */ + * (For Authdir: Have we validated this OR?) */ unsigned int is_fast:1; /** Do we think this is a fast OR? */ unsigned int is_stable:1; /** Do we think this is a stable OR? */ unsigned int is_possible_guard:1; /**< Do we think this is an OK guard? */ @@ -2035,8 +2031,23 @@ typedef struct node_t { /* Local info: derived. */ + /** True if the IPv6 OR port is preferred over the IPv4 OR port. */ + unsigned int ipv6_preferred:1; + /** According to the geoip db what country is this router in? */ + /* XXXprop186 what is this suppose to mean with multiple OR ports? */ country_t country; + + /* The below items are used only by authdirservers for + * reachability testing. */ + + /** When was the last time we could reach this OR? */ + time_t last_reachable; /* IPv4. */ + time_t last_reachable6; /* IPv6. */ + + /** When did we start testing reachability for this OR? */ + time_t testing_since; /* IPv4. */ + time_t testing_since6; /* IPv6. */ } node_t; /** How many times will we try to download a router's descriptor before giving @@ -3312,6 +3323,8 @@ typedef struct { int AuthDirMaxServersPerAuthAddr; /**< Do not permit more than this * number of servers per IP address shared * with an authority. */ + int AuthDirHasIPv6Connectivity; /**< Autoboolean: are we on IPv6? */ + int AuthDirPublishIPv6; /**< Boolean: should we list IPv6 OR ports? */ /** If non-zero, always vote the Fast flag for any relay advertising * this amount of capacity or more. */ @@ -3478,6 +3491,13 @@ typedef struct { * over randomly chosen exits. */ int ClientRejectInternalAddresses; + /** If true, clients may connect over IPv6. XXX we don't really + enforce this -- clients _may_ set up outgoing IPv6 connections + even when this option is not set. */ + int ClientUseIPv6; + /** If true, prefer an IPv6 OR port over an IPv4 one. */ + int ClientPreferIPv6ORPort; + /** The length of time that we think a consensus should be fresh. */ int V3AuthVotingInterval; /** The length of time we think it will take to distribute votes. */ @@ -4251,12 +4271,15 @@ typedef struct rend_intro_point_t { * intro point. */ unsigned int rend_service_note_removing_intro_point_called : 1; - /** (Service side only) A digestmap recording the INTRODUCE2 cells - * this intro point's circuit has received. Each key is the digest - * of the RSA-encrypted part of a received INTRODUCE2 cell; each - * value is a pointer to the time_t at which the cell was received. - * This digestmap is used to prevent replay attacks. */ - digestmap_t *accepted_intro_rsa_parts; + /** (Service side only) A replay cache recording the RSA-encrypted parts + * of INTRODUCE2 cells this intro point's circuit has received. This is + * used to prevent replay attacks. */ + replaycache_t *accepted_intro_rsa_parts; + + /** (Service side only) Count of INTRODUCE2 cells accepted from this + * intro point. + */ + int accepted_introduce2_count; /** (Service side only) The time at which this intro point was first * published, or -1 if this intro point has not yet been diff --git a/src/or/policies.c b/src/or/policies.c index 6e984211ba..568bc88a05 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -1008,8 +1008,7 @@ policy_write_item(char *buf, size_t buflen, addr_policy_t *policy, else addrpart = addrbuf; - result = tor_snprintf(buf, buflen, "%s%s%s %s", - (is_ip6&&format_for_desc)?"opt ":"", + result = tor_snprintf(buf, buflen, "%s%s %s", is_accept ? "accept" : "reject", (is_ip6&&format_for_desc)?"6":"", addrpart); diff --git a/src/or/relay.c b/src/or/relay.c index 3d261c2650..90129660b4 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1175,7 +1175,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, case RELAY_COMMAND_EXTEND: { static uint64_t total_n_extend=0, total_nonearly=0; total_n_extend++; - if (conn) { + if (rh.stream_id) { log_fn(LOG_PROTOCOL_WARN, domain, "'extend' cell received for non-zero stream. Dropping."); return 0; @@ -1267,7 +1267,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (layer_hint) { if (layer_hint->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + /*XXXX024: Downgrade this back to LOG_PROTOCOL_WARN after a while*/ + log_fn(LOG_WARN, LD_PROTOCOL, "Bug/attack: unexpected sendme cell from exit relay. " "Closing circ."); return -END_CIRC_REASON_TORPROTOCOL; @@ -1279,7 +1280,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } else { if (circ->package_window + CIRCWINDOW_INCREMENT > CIRCWINDOW_START_MAX) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + /*XXXX024: Downgrade this back to LOG_PROTOCOL_WARN after a while*/ + log_fn(LOG_WARN, LD_PROTOCOL, "Bug/attack: unexpected sendme cell from client. " "Closing circ."); return -END_CIRC_REASON_TORPROTOCOL; diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 6c751be27d..5b3b92e406 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -132,6 +132,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, crypt_path_t *cpath; off_t dh_offset; crypto_pk_t *intro_key = NULL; + int status = 0; tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY); @@ -161,7 +162,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, } } - return -1; + status = -1; + goto cleanup; } /* first 20 bytes of payload are the hash of Bob's pk */ @@ -184,13 +186,16 @@ rend_client_send_introduction(origin_circuit_t *introcirc, smartlist_len(entry->parsed->intro_nodes)); if (rend_client_reextend_intro_circuit(introcirc)) { + status = -2; goto perm_err; } else { - return -1; + status = -1; + goto cleanup; } } if (crypto_pk_get_digest(intro_key, payload)<0) { log_warn(LD_BUG, "Internal error: couldn't hash public key."); + status = -2; goto perm_err; } @@ -202,10 +207,12 @@ rend_client_send_introduction(origin_circuit_t *introcirc, cpath->magic = CRYPT_PATH_MAGIC; if (!(cpath->dh_handshake_state = crypto_dh_new(DH_TYPE_REND))) { log_warn(LD_BUG, "Internal error: couldn't allocate DH."); + status = -2; goto perm_err; } if (crypto_dh_generate_public(cpath->dh_handshake_state)<0) { log_warn(LD_BUG, "Internal error: couldn't generate g^x."); + status = -2; goto perm_err; } } @@ -256,6 +263,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, if (crypto_dh_get_public(cpath->dh_handshake_state, tmp+dh_offset, DH_KEY_LEN)<0) { log_warn(LD_BUG, "Internal error: couldn't extract g^x."); + status = -2; goto perm_err; } @@ -269,6 +277,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, PK_PKCS1_OAEP_PADDING, 0); if (r<0) { log_warn(LD_BUG,"Internal error: hybrid pk encrypt failed."); + status = -2; goto perm_err; } @@ -288,7 +297,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, introcirc->cpath->prev)<0) { /* introcirc is already marked for close. leave rendcirc alone. */ log_warn(LD_BUG, "Couldn't send INTRODUCE1 cell"); - return -2; + status = -2; + goto cleanup; } /* Now, we wait for an ACK or NAK on this circuit. */ @@ -299,12 +309,17 @@ rend_client_send_introduction(origin_circuit_t *introcirc, * state. */ introcirc->_base.timestamp_dirty = time(NULL); - return 0; + goto cleanup; + perm_err: if (!introcirc->_base.marked_for_close) circuit_mark_for_close(TO_CIRCUIT(introcirc), END_CIRC_REASON_INTERNAL); circuit_mark_for_close(TO_CIRCUIT(rendcirc), END_CIRC_REASON_INTERNAL); - return -2; + cleanup: + memset(payload, 0, sizeof(payload)); + memset(tmp, 0, sizeof(tmp)); + + return status; } /** Called when a rendezvous circuit is open; sends a establish @@ -659,10 +674,17 @@ rend_client_refetch_v2_renddesc(const rend_data_t *rend_query) time(NULL), chosen_replica) < 0) { log_warn(LD_REND, "Internal error: Computing v2 rendezvous " "descriptor ID did not succeed."); - return; + /* + * Hmm, can this write anything to descriptor_id and still fail? + * Let's clear it just to be safe. + * + * From here on, any returns should goto done which clears + * descriptor_id so we don't leave key-derived material on the stack. + */ + goto done; } if (directory_get_from_hs_dir(descriptor_id, rend_query) != 0) - return; /* either success or failure, but we're done */ + goto done; /* either success or failure, but we're done */ } /* If we come here, there are no hidden service directories left. */ log_info(LD_REND, "Could not pick one of the responsible hidden " @@ -670,6 +692,10 @@ rend_client_refetch_v2_renddesc(const rend_data_t *rend_query) "we already tried them all unsuccessfully."); /* Close pending connections. */ rend_client_desc_trynow(rend_query->onion_address); + + done: + memset(descriptor_id, 0, sizeof(descriptor_id)); + return; } @@ -1172,11 +1198,11 @@ rend_parse_service_authorization(const or_options_t *options, strmap_t *parsed = strmap_new(); smartlist_t *sl = smartlist_new(); rend_service_authorization_t *auth = NULL; + char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2]; + char descriptor_cookie_base64ext[REND_DESC_COOKIE_LEN_BASE64+2+1]; for (line = options->HidServAuth; line; line = line->next) { char *onion_address, *descriptor_cookie; - char descriptor_cookie_tmp[REND_DESC_COOKIE_LEN+2]; - char descriptor_cookie_base64ext[REND_DESC_COOKIE_LEN_BASE64+2+1]; int auth_type_val = 0; auth = NULL; SMARTLIST_FOREACH(sl, char *, c, tor_free(c);); @@ -1253,6 +1279,8 @@ rend_parse_service_authorization(const or_options_t *options, } else { strmap_free(parsed, rend_service_authorization_strmap_item_free); } + memset(descriptor_cookie_tmp, 0, sizeof(descriptor_cookie_tmp)); + memset(descriptor_cookie_base64ext, 0, sizeof(descriptor_cookie_base64ext)); return res; } diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 4722690c15..f6b1bf9f65 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -439,7 +439,7 @@ rend_intro_point_free(rend_intro_point_t *intro) crypto_pk_free(intro->intro_key); if (intro->accepted_intro_rsa_parts != NULL) { - digestmap_free(intro->accepted_intro_rsa_parts, _tor_free); + replaycache_free(intro->accepted_intro_rsa_parts); } tor_free(intro); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 6af4778dfc..bd8b13a762 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -7,6 +7,8 @@ * \brief The hidden-service side of rendezvous functionality. **/ +#define RENDSERVICE_PRIVATE + #include "or.h" #include "circuitbuild.h" #include "circuitlist.h" @@ -21,6 +23,7 @@ #include "router.h" #include "relay.h" #include "rephist.h" +#include "replaycache.h" #include "routerlist.h" #include "routerparse.h" @@ -28,9 +31,33 @@ static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest); static rend_intro_point_t *find_intro_point(origin_circuit_t *circ); +static extend_info_t *find_rp_for_intro( + const rend_intro_cell_t *intro, + uint8_t *need_free_out, char **err_msg_out); + static int intro_point_accepted_intro_count(rend_intro_point_t *intro); static int intro_point_should_expire_now(rend_intro_point_t *intro, time_t now); +struct rend_service_t; +static int rend_service_load_keys(struct rend_service_t *s); +static int rend_service_load_auth_keys(struct rend_service_t *s, + const char *hfname); + +static ssize_t rend_service_parse_intro_for_v0_or_v1( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out); +static ssize_t rend_service_parse_intro_for_v2( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out); +static ssize_t rend_service_parse_intro_for_v3( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out); /** Represents the mapping from a virtual port of a rendezvous service to * a real port on some IP. @@ -91,16 +118,12 @@ typedef struct rend_service_t { * up-to-date. */ time_t next_upload_time; /**< Scheduled next hidden service descriptor * upload time. */ - /** Map from digests of Diffie-Hellman values INTRODUCE2 to time_t - * of when they were received. Clients may send INTRODUCE1 cells - * for the same rendezvous point through two or more different - * introduction points; when they do, this digestmap keeps us from - * launching multiple simultaneous attempts to connect to the same - * rend point. */ - digestmap_t *accepted_intro_dh_parts; - /** Time at which we last removed expired values from - * accepted_intro_dh_parts. */ - time_t last_cleaned_accepted_intro_dh_parts; + /** Replay cache for Diffie-Hellman values of INTRODUCE2 cells, to + * detect repeats. Clients may send INTRODUCE1 cells for the same + * rendezvous point through two or more different introduction points; + * when they do, this keeps us from launching multiple simultaneous attempts + * to connect to the same rend point. */ + replaycache_t *accepted_intro_dh_parts; } rend_service_t; /** A list of rend_service_t's for services run on this OP. @@ -135,7 +158,9 @@ rend_authorized_client_free(rend_authorized_client_t *client) return; if (client->client_key) crypto_pk_free(client->client_key); + tor_strclear(client->client_name); tor_free(client->client_name); + memset(client->descriptor_cookie, 0, sizeof(client->descriptor_cookie)); tor_free(client); } @@ -171,7 +196,9 @@ rend_service_free(rend_service_t *service) rend_authorized_client_free(c);); smartlist_free(service->clients); } - digestmap_free(service->accepted_intro_dh_parts, _tor_free); + if (service->accepted_intro_dh_parts) { + replaycache_free(service->accepted_intro_dh_parts); + } tor_free(service); } @@ -609,231 +636,274 @@ rend_service_update_descriptor(rend_service_t *service) /** Load and/or generate private keys for all hidden services, possibly * including keys for client authorization. Return 0 on success, -1 on - * failure. - */ + * failure. */ int -rend_service_load_keys(void) +rend_service_load_all_keys(void) { - int r = 0; - char fname[512]; - char buf[1500]; - SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) { if (s->private_key) continue; log_info(LD_REND, "Loading hidden-service keys from \"%s\"", s->directory); - /* Check/create directory */ - if (check_private_dir(s->directory, CPD_CREATE, get_options()->User) < 0) + if (rend_service_load_keys(s) < 0) return -1; + } SMARTLIST_FOREACH_END(s); + + return 0; +} + +/** Load and/or generate private keys for the hidden service <b>s</b>, + * possibly including keys for client authorization. Return 0 on success, -1 + * on failure. */ +static int +rend_service_load_keys(rend_service_t *s) +{ + char fname[512]; + char buf[128]; + + /* Check/create directory */ + if (check_private_dir(s->directory, CPD_CREATE, get_options()->User) < 0) + return -1; + + /* Load key */ + if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || + strlcat(fname,PATH_SEPARATOR"private_key",sizeof(fname)) + >= sizeof(fname)) { + log_warn(LD_CONFIG, "Directory name too long to store key file: \"%s\".", + s->directory); + return -1; + } + s->private_key = init_key_from_file(fname, 1, LOG_ERR); + if (!s->private_key) + return -1; + + /* Create service file */ + if (rend_get_service_id(s->private_key, s->service_id)<0) { + log_warn(LD_BUG, "Internal error: couldn't encode service ID."); + return -1; + } + if (crypto_pk_get_digest(s->private_key, s->pk_digest)<0) { + log_warn(LD_BUG, "Couldn't compute hash of public key."); + return -1; + } + if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || + strlcat(fname,PATH_SEPARATOR"hostname",sizeof(fname)) + >= sizeof(fname)) { + log_warn(LD_CONFIG, "Directory name too long to store hostname file:" + " \"%s\".", s->directory); + return -1; + } + + tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id); + if (write_str_to_file(fname,buf,0)<0) { + log_warn(LD_CONFIG, "Could not write onion address to hostname file."); + memset(buf, 0, sizeof(buf)); + return -1; + } + memset(buf, 0, sizeof(buf)); - /* Load key */ - if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || - strlcat(fname,PATH_SEPARATOR"private_key",sizeof(fname)) - >= sizeof(fname)) { - log_warn(LD_CONFIG, "Directory name too long to store key file: \"%s\".", - s->directory); + /* If client authorization is configured, load or generate keys. */ + if (s->auth_type != REND_NO_AUTH) { + if (rend_service_load_auth_keys(s, fname) < 0) return -1; + } + + return 0; +} + +/** Load and/or generate client authorization keys for the hidden service + * <b>s</b>, which stores its hostname in <b>hfname</b>. Return 0 on success, + * -1 on failure. */ +static int +rend_service_load_auth_keys(rend_service_t *s, const char *hfname) +{ + int r = 0; + char cfname[512]; + char *client_keys_str = NULL; + strmap_t *parsed_clients = strmap_new(); + FILE *cfile, *hfile; + open_file_t *open_cfile = NULL, *open_hfile = NULL; + char extended_desc_cookie[REND_DESC_COOKIE_LEN+1]; + char desc_cook_out[3*REND_DESC_COOKIE_LEN_BASE64+1]; + char service_id[16+1]; + char buf[1500]; + + /* Load client keys and descriptor cookies, if available. */ + if (tor_snprintf(cfname, sizeof(cfname), "%s"PATH_SEPARATOR"client_keys", + s->directory)<0) { + log_warn(LD_CONFIG, "Directory name too long to store client keys " + "file: \"%s\".", s->directory); + goto err; + } + client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL); + if (client_keys_str) { + if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) { + log_warn(LD_CONFIG, "Previously stored client_keys file could not " + "be parsed."); + goto err; + } else { + log_info(LD_CONFIG, "Parsed %d previously stored client entries.", + strmap_size(parsed_clients)); } - s->private_key = init_key_from_file(fname, 1, LOG_ERR); - if (!s->private_key) - return -1; + } - /* Create service file */ - if (rend_get_service_id(s->private_key, s->service_id)<0) { - log_warn(LD_BUG, "Internal error: couldn't encode service ID."); - return -1; + /* Prepare client_keys and hostname files. */ + if (!(cfile = start_writing_to_stdio_file(cfname, + OPEN_FLAGS_REPLACE | O_TEXT, + 0600, &open_cfile))) { + log_warn(LD_CONFIG, "Could not open client_keys file %s", + escaped(cfname)); + goto err; + } + + if (!(hfile = start_writing_to_stdio_file(hfname, + OPEN_FLAGS_REPLACE | O_TEXT, + 0600, &open_hfile))) { + log_warn(LD_CONFIG, "Could not open hostname file %s", escaped(hfname)); + goto err; + } + + /* Either use loaded keys for configured clients or generate new + * ones if a client is new. */ + SMARTLIST_FOREACH_BEGIN(s->clients, rend_authorized_client_t *, client) { + rend_authorized_client_t *parsed = + strmap_get(parsed_clients, client->client_name); + int written; + size_t len; + /* Copy descriptor cookie from parsed entry or create new one. */ + if (parsed) { + memcpy(client->descriptor_cookie, parsed->descriptor_cookie, + REND_DESC_COOKIE_LEN); + } else { + crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN); } - if (crypto_pk_get_digest(s->private_key, s->pk_digest)<0) { - log_warn(LD_BUG, "Couldn't compute hash of public key."); - return -1; + if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, + client->descriptor_cookie, + REND_DESC_COOKIE_LEN) < 0) { + log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); + goto err; } - if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || - strlcat(fname,PATH_SEPARATOR"hostname",sizeof(fname)) - >= sizeof(fname)) { - log_warn(LD_CONFIG, "Directory name too long to store hostname file:" - " \"%s\".", s->directory); - return -1; + /* Copy client key from parsed entry or create new one if required. */ + if (parsed && parsed->client_key) { + client->client_key = crypto_pk_dup_key(parsed->client_key); + } else if (s->auth_type == REND_STEALTH_AUTH) { + /* Create private key for client. */ + crypto_pk_t *prkey = NULL; + if (!(prkey = crypto_pk_new())) { + log_warn(LD_BUG,"Error constructing client key"); + goto err; + } + if (crypto_pk_generate_key(prkey)) { + log_warn(LD_BUG,"Error generating client key"); + crypto_pk_free(prkey); + goto err; + } + if (crypto_pk_check_key(prkey) <= 0) { + log_warn(LD_BUG,"Generated client key seems invalid"); + crypto_pk_free(prkey); + goto err; + } + client->client_key = prkey; } - tor_snprintf(buf, sizeof(buf),"%s.onion\n", s->service_id); - if (write_str_to_file(fname,buf,0)<0) { - log_warn(LD_CONFIG, "Could not write onion address to hostname file."); - return -1; + /* Add entry to client_keys file. */ + desc_cook_out[strlen(desc_cook_out)-1] = '\0'; /* Remove newline. */ + written = tor_snprintf(buf, sizeof(buf), + "client-name %s\ndescriptor-cookie %s\n", + client->client_name, desc_cook_out); + if (written < 0) { + log_warn(LD_BUG, "Could not write client entry."); + goto err; } - - /* If client authorization is configured, load or generate keys. */ - if (s->auth_type != REND_NO_AUTH) { - char *client_keys_str = NULL; - strmap_t *parsed_clients = strmap_new(); - char cfname[512]; - FILE *cfile, *hfile; - open_file_t *open_cfile = NULL, *open_hfile = NULL; - - /* Load client keys and descriptor cookies, if available. */ - if (tor_snprintf(cfname, sizeof(cfname), "%s"PATH_SEPARATOR"client_keys", - s->directory)<0) { - log_warn(LD_CONFIG, "Directory name too long to store client keys " - "file: \"%s\".", s->directory); + if (client->client_key) { + char *client_key_out = NULL; + if (crypto_pk_write_private_key_to_string(client->client_key, + &client_key_out, &len) != 0) { + log_warn(LD_BUG, "Internal error: " + "crypto_pk_write_private_key_to_string() failed."); goto err; } - client_keys_str = read_file_to_str(cfname, RFTS_IGNORE_MISSING, NULL); - if (client_keys_str) { - if (rend_parse_client_keys(parsed_clients, client_keys_str) < 0) { - log_warn(LD_CONFIG, "Previously stored client_keys file could not " - "be parsed."); - goto err; - } else { - log_info(LD_CONFIG, "Parsed %d previously stored client entries.", - strmap_size(parsed_clients)); - tor_free(client_keys_str); - } - } - - /* Prepare client_keys and hostname files. */ - if (!(cfile = start_writing_to_stdio_file(cfname, - OPEN_FLAGS_REPLACE | O_TEXT, - 0600, &open_cfile))) { - log_warn(LD_CONFIG, "Could not open client_keys file %s", - escaped(cfname)); + if (rend_get_service_id(client->client_key, service_id)<0) { + log_warn(LD_BUG, "Internal error: couldn't encode service ID."); + /* + * len is string length, not buffer length, but last byte is NUL + * anyway. + */ + memset(client_key_out, 0, len); + tor_free(client_key_out); goto err; } - if (!(hfile = start_writing_to_stdio_file(fname, - OPEN_FLAGS_REPLACE | O_TEXT, - 0600, &open_hfile))) { - log_warn(LD_CONFIG, "Could not open hostname file %s", escaped(fname)); + written = tor_snprintf(buf + written, sizeof(buf) - written, + "client-key\n%s", client_key_out); + memset(client_key_out, 0, len); + tor_free(client_key_out); + if (written < 0) { + log_warn(LD_BUG, "Could not write client entry."); goto err; } + } - /* Either use loaded keys for configured clients or generate new - * ones if a client is new. */ - SMARTLIST_FOREACH_BEGIN(s->clients, rend_authorized_client_t *, client) - { - char desc_cook_out[3*REND_DESC_COOKIE_LEN_BASE64+1]; - char service_id[16+1]; - rend_authorized_client_t *parsed = - strmap_get(parsed_clients, client->client_name); - int written; - size_t len; - /* Copy descriptor cookie from parsed entry or create new one. */ - if (parsed) { - memcpy(client->descriptor_cookie, parsed->descriptor_cookie, - REND_DESC_COOKIE_LEN); - } else { - crypto_rand(client->descriptor_cookie, REND_DESC_COOKIE_LEN); - } - if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, - client->descriptor_cookie, - REND_DESC_COOKIE_LEN) < 0) { - log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); - strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); - return -1; - } - /* Copy client key from parsed entry or create new one if required. */ - if (parsed && parsed->client_key) { - client->client_key = crypto_pk_dup_key(parsed->client_key); - } else if (s->auth_type == REND_STEALTH_AUTH) { - /* Create private key for client. */ - crypto_pk_t *prkey = NULL; - if (!(prkey = crypto_pk_new())) { - log_warn(LD_BUG,"Error constructing client key"); - goto err; - } - if (crypto_pk_generate_key(prkey)) { - log_warn(LD_BUG,"Error generating client key"); - crypto_pk_free(prkey); - goto err; - } - if (crypto_pk_check_key(prkey) <= 0) { - log_warn(LD_BUG,"Generated client key seems invalid"); - crypto_pk_free(prkey); - goto err; - } - client->client_key = prkey; - } - /* Add entry to client_keys file. */ - desc_cook_out[strlen(desc_cook_out)-1] = '\0'; /* Remove newline. */ - written = tor_snprintf(buf, sizeof(buf), - "client-name %s\ndescriptor-cookie %s\n", - client->client_name, desc_cook_out); - if (written < 0) { - log_warn(LD_BUG, "Could not write client entry."); - goto err; - } - if (client->client_key) { - char *client_key_out = NULL; - crypto_pk_write_private_key_to_string(client->client_key, - &client_key_out, &len); - if (rend_get_service_id(client->client_key, service_id)<0) { - log_warn(LD_BUG, "Internal error: couldn't encode service ID."); - tor_free(client_key_out); - goto err; - } - written = tor_snprintf(buf + written, sizeof(buf) - written, - "client-key\n%s", client_key_out); - tor_free(client_key_out); - if (written < 0) { - log_warn(LD_BUG, "Could not write client entry."); - goto err; - } - } - - if (fputs(buf, cfile) < 0) { - log_warn(LD_FS, "Could not append client entry to file: %s", - strerror(errno)); - goto err; - } - - /* Add line to hostname file. */ - if (s->auth_type == REND_BASIC_AUTH) { - /* Remove == signs (newline has been removed above). */ - desc_cook_out[strlen(desc_cook_out)-2] = '\0'; - tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", - s->service_id, desc_cook_out, client->client_name); - } else { - char extended_desc_cookie[REND_DESC_COOKIE_LEN+1]; - memcpy(extended_desc_cookie, client->descriptor_cookie, - REND_DESC_COOKIE_LEN); - extended_desc_cookie[REND_DESC_COOKIE_LEN] = - ((int)s->auth_type - 1) << 4; - if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, - extended_desc_cookie, - REND_DESC_COOKIE_LEN+1) < 0) { - log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); - goto err; - } - desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove A= and - newline. */ - tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", - service_id, desc_cook_out, client->client_name); - } + if (fputs(buf, cfile) < 0) { + log_warn(LD_FS, "Could not append client entry to file: %s", + strerror(errno)); + goto err; + } - if (fputs(buf, hfile)<0) { - log_warn(LD_FS, "Could not append host entry to file: %s", - strerror(errno)); - goto err; - } + /* Add line to hostname file. */ + if (s->auth_type == REND_BASIC_AUTH) { + /* Remove == signs (newline has been removed above). */ + desc_cook_out[strlen(desc_cook_out)-2] = '\0'; + tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", + s->service_id, desc_cook_out, client->client_name); + } else { + memcpy(extended_desc_cookie, client->descriptor_cookie, + REND_DESC_COOKIE_LEN); + extended_desc_cookie[REND_DESC_COOKIE_LEN] = + ((int)s->auth_type - 1) << 4; + if (base64_encode(desc_cook_out, 3*REND_DESC_COOKIE_LEN_BASE64+1, + extended_desc_cookie, + REND_DESC_COOKIE_LEN+1) < 0) { + log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); + goto err; } - SMARTLIST_FOREACH_END(client); + desc_cook_out[strlen(desc_cook_out)-3] = '\0'; /* Remove A= and + newline. */ + tor_snprintf(buf, sizeof(buf),"%s.onion %s # client: %s\n", + service_id, desc_cook_out, client->client_name); + } - goto done; - err: - r = -1; - done: - tor_free(client_keys_str); - strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); - if (r<0) { - if (open_cfile) - abort_writing_to_file(open_cfile); - if (open_hfile) - abort_writing_to_file(open_hfile); - return r; - } else { - finish_writing_to_file(open_cfile); - finish_writing_to_file(open_hfile); - } + if (fputs(buf, hfile)<0) { + log_warn(LD_FS, "Could not append host entry to file: %s", + strerror(errno)); + goto err; } - } SMARTLIST_FOREACH_END(s); + } SMARTLIST_FOREACH_END(client); + + finish_writing_to_file(open_cfile); + finish_writing_to_file(open_hfile); + + goto done; + err: + r = -1; + if (open_cfile) + abort_writing_to_file(open_cfile); + if (open_hfile) + abort_writing_to_file(open_hfile); + done: + if (client_keys_str) { + tor_strclear(client_keys_str); + tor_free(client_keys_str); + } + strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); + + memset(cfname, 0, sizeof(cfname)); + + /* Clear stack buffers that held key-derived material. */ + memset(buf, 0, sizeof(buf)); + memset(desc_cook_out, 0, sizeof(desc_cook_out)); + memset(service_id, 0, sizeof(service_id)); + memset(extended_desc_cookie, 0, sizeof(extended_desc_cookie)); + return r; } @@ -906,26 +976,6 @@ rend_check_authorization(rend_service_t *service, return 1; } -/** Remove elements from <b>service</b>'s replay cache that are old enough to - * be noticed by timestamp checking. */ -static void -clean_accepted_intro_dh_parts(rend_service_t *service, time_t now) -{ - const time_t cutoff = now - REND_REPLAY_TIME_INTERVAL; - - service->last_cleaned_accepted_intro_dh_parts = now; - if (!service->accepted_intro_dh_parts) - return; - - DIGESTMAP_FOREACH_MODIFY(service->accepted_intro_dh_parts, digest, - time_t *, t) { - if (*t < cutoff) { - tor_free(t); - MAP_DEL_CURRENT(digest); - } - } DIGESTMAP_FOREACH_END; -} - /** Called when <b>intro</b> will soon be removed from * <b>service</b>'s list of intro points. */ static void @@ -1033,42 +1083,55 @@ rend_service_note_removing_intro_point(rend_service_t *service, /** Respond to an INTRODUCE2 cell by launching a circuit to the chosen * rendezvous point. */ - /* XXXX024 this function sure could use some organizing. -RD */ int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, size_t request_len) { - char *ptr, *r_cookie; - extend_info_t *extend_info = NULL; + /* Global status stuff */ + int status = 0, result; + const or_options_t *options = get_options(); + char *err_msg = NULL; + const char *stage_descr = NULL; + int reason = END_CIRC_REASON_TORPROTOCOL; + /* Service/circuit/key stuff we can learn before parsing */ + char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; + rend_service_t *service = NULL; + rend_intro_point_t *intro_point = NULL; + crypto_pk_t *intro_key = NULL; + /* Parsed cell */ + rend_intro_cell_t *parsed_req = NULL; + /* Rendezvous point */ + extend_info_t *rp = NULL; + /* + * We need to look up and construct the extend_info_t for v0 and v1, + * but all the info is in the cell and it's constructed by the parser + * for v2 and v3, so freeing it would be a double-free. Use this to + * keep track of whether we should free it. + */ + uint8_t need_rp_free = 0; + /* XXX not handled yet */ char buf[RELAY_PAYLOAD_SIZE]; char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */ - rend_service_t *service; - rend_intro_point_t *intro_point; - int r, i, v3_shift = 0; - size_t len, keylen; + int i; crypto_dh_t *dh = NULL; origin_circuit_t *launched = NULL; crypt_path_t *cpath = NULL; - char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; char hexcookie[9]; int circ_needs_uptime; - int reason = END_CIRC_REASON_TORPROTOCOL; - crypto_pk_t *intro_key; char intro_key_digest[DIGEST_LEN]; - int auth_type; size_t auth_len = 0; char auth_data[REND_DESC_COOKIE_LEN]; - crypto_digest_t *digest = NULL; time_t now = time(NULL); char diffie_hellman_hash[DIGEST_LEN]; - time_t *access_time; - const or_options_t *options = get_options(); + time_t elapsed; + int replay; + /* Do some initial validation and logging before we parse the cell */ if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) { log_warn(LD_PROTOCOL, "Got an INTRODUCE2 over a non-introduction circuit %d.", circuit->_base.n_circ_id); - return -1; + goto err; } #ifndef NON_ANONYMOUS_MODE_ENABLED @@ -1076,218 +1139,145 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, #endif tor_assert(circuit->rend_data); + /* 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); - log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.", - escaped(serviceid), circuit->_base.n_circ_id); - - /* min key length plus digest length plus nickname length */ - if (request_len < DIGEST_LEN+REND_COOKIE_LEN+(MAX_NICKNAME_LEN+1)+ - DH_KEY_LEN+42) { - log_warn(LD_PROTOCOL, "Got a truncated INTRODUCE2 cell on circ %d.", - circuit->_base.n_circ_id); - return -1; - } /* 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(circuit->rend_data->rend_pk_digest); if (!service) { - log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro " + log_warn(LD_BUG, + "Internal error: Got an INTRODUCE2 cell on an intro " "circ for an unrecognized service %s.", escaped(serviceid)); - return -1; - } - - /* use intro key instead of service key. */ - intro_key = circuit->intro_key; - - /* first DIGEST_LEN bytes of request is intro or service pk digest */ - crypto_pk_get_digest(intro_key, intro_key_digest); - if (tor_memneq(intro_key_digest, request, DIGEST_LEN)) { - base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, - (char*)request, REND_SERVICE_ID_LEN); - log_warn(LD_REND, "Got an INTRODUCE2 cell for the wrong service (%s).", - escaped(serviceid)); - return -1; - } - - keylen = crypto_pk_keysize(intro_key); - if (request_len < keylen+DIGEST_LEN) { - log_warn(LD_PROTOCOL, - "PK-encrypted portion of INTRODUCE2 cell was truncated."); - return -1; + goto err; } intro_point = find_intro_point(circuit); if (intro_point == NULL) { - log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro circ " - "(for service %s) with no corresponding rend_intro_point_t.", + log_warn(LD_BUG, + "Internal error: Got an INTRODUCE2 cell on an " + "intro circ (for service %s) with no corresponding " + "rend_intro_point_t.", escaped(serviceid)); - return -1; + goto err; } - if (!service->accepted_intro_dh_parts) - service->accepted_intro_dh_parts = digestmap_new(); + log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.", + escaped(serviceid), circuit->_base.n_circ_id); - if (!intro_point->accepted_intro_rsa_parts) - intro_point->accepted_intro_rsa_parts = digestmap_new(); + /* use intro key instead of service key. */ + intro_key = circuit->intro_key; - { - char pkpart_digest[DIGEST_LEN]; - /* Check for replay of PK-encrypted portion. */ - crypto_digest(pkpart_digest, (char*)request+DIGEST_LEN, keylen); - access_time = digestmap_get(intro_point->accepted_intro_rsa_parts, - pkpart_digest); - if (access_time != NULL) { - log_warn(LD_REND, "Possible replay detected! We received an " - "INTRODUCE2 cell with same PK-encrypted part %d seconds ago. " - "Dropping cell.", (int)(now-*access_time)); - return -1; - } - access_time = tor_malloc(sizeof(time_t)); - *access_time = now; - digestmap_set(intro_point->accepted_intro_rsa_parts, - pkpart_digest, access_time); + tor_free(err_msg); + stage_descr = NULL; + + stage_descr = "early parsing"; + /* Early parsing pass (get pk, ciphertext); type 2 is INTRODUCE2 */ + parsed_req = + rend_service_begin_parse_intro(request, request_len, 2, &err_msg); + if (!parsed_req) { + goto log_error; + } else if (err_msg) { + log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id); + tor_free(err_msg); + } + + stage_descr = "early validation"; + /* Early validation of pk/ciphertext part */ + result = rend_service_validate_intro_early(parsed_req, &err_msg); + if (result < 0) { + goto log_error; + } else if (err_msg) { + log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id); + tor_free(err_msg); + } + + /* make sure service replay caches are present */ + if (!service->accepted_intro_dh_parts) { + service->accepted_intro_dh_parts = + replaycache_new(REND_REPLAY_TIME_INTERVAL, + REND_REPLAY_TIME_INTERVAL); + } + + if (!intro_point->accepted_intro_rsa_parts) { + intro_point->accepted_intro_rsa_parts = replaycache_new(0, 0); + } + + /* check for replay of PK-encrypted portion. */ + replay = replaycache_add_test_and_elapsed( + intro_point->accepted_intro_rsa_parts, + parsed_req->ciphertext, (int)parsed_req->ciphertext_len, + &elapsed); + + if (replay) { + log_warn(LD_REND, + "Possible replay detected! We received an " + "INTRODUCE2 cell with same PK-encrypted part %d " + "seconds ago. Dropping cell.", + (int)elapsed); + goto err; } - /* Next N bytes is encrypted with service key */ - note_crypto_pk_op(REND_SERVER); - r = crypto_pk_private_hybrid_decrypt( - intro_key,buf,sizeof(buf), - (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN, - PK_PKCS1_OAEP_PADDING,1); - if (r<0) { - log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell."); - return -1; + stage_descr = "decryption"; + /* Now try to decrypt it */ + result = rend_service_decrypt_intro(parsed_req, intro_key, &err_msg); + if (result < 0) { + goto log_error; + } else if (err_msg) { + log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id); + tor_free(err_msg); } - len = r; - if (*buf == 3) { - /* Version 3 INTRODUCE2 cell. */ - v3_shift = 1; - auth_type = buf[1]; - switch (auth_type) { - case REND_BASIC_AUTH: - /* fall through */ - case REND_STEALTH_AUTH: - auth_len = ntohs(get_uint16(buf+2)); - if (auth_len != REND_DESC_COOKIE_LEN) { - log_info(LD_REND, "Wrong auth data size %d, should be %d.", - (int)auth_len, REND_DESC_COOKIE_LEN); - return -1; - } - memcpy(auth_data, buf+4, sizeof(auth_data)); - v3_shift += 2+REND_DESC_COOKIE_LEN; - break; - case REND_NO_AUTH: - break; - default: - log_info(LD_REND, "Unknown authorization type '%d'", auth_type); - } - - /* Skip the timestamp field. We no longer use it. */ - v3_shift += 4; - } - if (*buf == 2 || *buf == 3) { - /* Version 2 INTRODUCE2 cell. */ - int klen; - extend_info = tor_malloc_zero(sizeof(extend_info_t)); - tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+v3_shift+1)); - extend_info->port = ntohs(get_uint16(buf+v3_shift+5)); - memcpy(extend_info->identity_digest, buf+v3_shift+7, - DIGEST_LEN); - extend_info->nickname[0] = '$'; - base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1, - extend_info->identity_digest, DIGEST_LEN); - - klen = ntohs(get_uint16(buf+v3_shift+7+DIGEST_LEN)); - if ((int)len != v3_shift+7+DIGEST_LEN+2+klen+20+128) { - log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.", - (int)len, *buf); - reason = END_CIRC_REASON_TORPROTOCOL; - goto err; - } - extend_info->onion_key = - crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen); - if (!extend_info->onion_key) { - log_warn(LD_PROTOCOL, "Error decoding onion key in version %d " - "INTRODUCE2 cell.", *buf); - reason = END_CIRC_REASON_TORPROTOCOL; - goto err; - } - ptr = buf+v3_shift+7+DIGEST_LEN+2+klen; - len -= v3_shift+7+DIGEST_LEN+2+klen; - } else { - char *rp_nickname; - size_t nickname_field_len; - const node_t *node; - int version; - if (*buf == 1) { - rp_nickname = buf+1; - nickname_field_len = MAX_HEX_NICKNAME_LEN+1; - version = 1; - } else { - nickname_field_len = MAX_NICKNAME_LEN+1; - rp_nickname = buf; - version = 0; - } - ptr=memchr(rp_nickname,0,nickname_field_len); - if (!ptr || ptr == rp_nickname) { - log_warn(LD_PROTOCOL, - "Couldn't find a nul-padded nickname in INTRODUCE2 cell."); - return -1; - } - if ((version == 0 && !is_legal_nickname(rp_nickname)) || - (version == 1 && !is_legal_nickname_or_hexdigest(rp_nickname))) { - log_warn(LD_PROTOCOL, "Bad nickname in INTRODUCE2 cell."); - return -1; - } - /* Okay, now we know that a nickname is at the start of the buffer. */ - ptr = rp_nickname+nickname_field_len; - len -= nickname_field_len; - len -= rp_nickname - buf; /* also remove header space used by version, if - * any */ - node = node_get_by_nickname(rp_nickname, 0); - if (!node) { - log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.", - escaped_safe_str_client(rp_nickname)); - /* XXXX Add a no-such-router reason? */ - reason = END_CIRC_REASON_TORPROTOCOL; - goto err; - } - extend_info = extend_info_from_node(node, 0); + stage_descr = "late parsing"; + /* Parse the plaintext */ + result = rend_service_parse_intro_plaintext(parsed_req, &err_msg); + if (result < 0) { + goto log_error; + } else if (err_msg) { + log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id); + tor_free(err_msg); } - if (len != REND_COOKIE_LEN+DH_KEY_LEN) { - log_warn(LD_PROTOCOL, "Bad length %u for INTRODUCE2 cell.", (int)len); - reason = END_CIRC_REASON_TORPROTOCOL; - goto err; + stage_descr = "late validation"; + /* Validate the parsed plaintext parts */ + result = rend_service_validate_intro_late(parsed_req, &err_msg); + if (result < 0) { + goto log_error; + } else if (err_msg) { + log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id); + tor_free(err_msg); } + stage_descr = NULL; + + /* Increment INTRODUCE2 counter */ + ++(intro_point->accepted_introduce2_count); + + /* Find the rendezvous point */ + rp = find_rp_for_intro(parsed_req, &need_rp_free, &err_msg); + if (!rp) + goto log_error; /* Check if we'd refuse to talk to this router */ if (options->StrictNodes && - routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) { + routerset_contains_extendinfo(options->ExcludeNodes, rp)) { log_warn(LD_REND, "Client asked to rendezvous at a relay that we " "exclude, and StrictNodes is set. Refusing service."); reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */ goto err; } - r_cookie = ptr; - base16_encode(hexcookie,9,r_cookie,4); - - /* Determine hash of Diffie-Hellman, part 1 to detect replays. */ - digest = crypto_digest_new(); - crypto_digest_add_bytes(digest, ptr+REND_COOKIE_LEN, DH_KEY_LEN); - crypto_digest_get_digest(digest, diffie_hellman_hash, DIGEST_LEN); - crypto_digest_free(digest); + base16_encode(hexcookie, 9, (const char *)(parsed_req->rc), 4); /* Check whether there is a past request with the same Diffie-Hellman, * part 1. */ - access_time = digestmap_get(service->accepted_intro_dh_parts, - diffie_hellman_hash); - if (access_time != NULL) { + replay = replaycache_add_test_and_elapsed( + service->accepted_intro_dh_parts, + parsed_req->dh, DH_KEY_LEN, + &elapsed); + + if (replay) { /* A Tor client will send a new INTRODUCE1 cell with the same rend * cookie and DH public key as its previous one if its intro circ * times out while in state CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT . @@ -1299,21 +1289,10 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, "INTRODUCE2 cell with same first part of " "Diffie-Hellman handshake %d seconds ago. Dropping " "cell.", - (int) (now - *access_time)); + (int) elapsed); goto err; } - /* Add request to access history, including time and hash of Diffie-Hellman, - * part 1, and possibly remove requests from the history that are older than - * one hour. */ - access_time = tor_malloc(sizeof(time_t)); - *access_time = now; - digestmap_set(service->accepted_intro_dh_parts, - diffie_hellman_hash, access_time); - if (service->last_cleaned_accepted_intro_dh_parts + REND_REPLAY_TIME_INTERVAL - < now) - clean_accepted_intro_dh_parts(service, now); - /* If the service performs client authorization, check included auth data. */ if (service->clients) { if (auth_len > 0) { @@ -1341,7 +1320,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, reason = END_CIRC_REASON_INTERNAL; goto err; } - if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN, + if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, + (char *)(parsed_req->dh), DH_KEY_LEN, keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { log_warn(LD_BUG, "Internal error: couldn't complete DH handshake"); @@ -1360,7 +1340,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; launched = circuit_launch_by_extend_info( - CIRCUIT_PURPOSE_S_CONNECT_REND, extend_info, flags); + CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags); if (launched) break; @@ -1368,7 +1348,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, if (!launched) { /* give up */ log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous " "point %s for service %s.", - safe_str_client(extend_info_describe(extend_info)), + safe_str_client(extend_info_describe(rp)), serviceid); reason = END_CIRC_REASON_CONNECTFAILED; goto err; @@ -1376,7 +1356,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, log_info(LD_REND, "Accepted intro; launching circuit to %s " "(cookie %s) for service %s.", - safe_str_client(extend_info_describe(extend_info)), + safe_str_client(extend_info_describe(rp)), hexcookie, serviceid); tor_assert(launched->build_state); /* Fill in the circuit's state. */ @@ -1384,7 +1364,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, memcpy(launched->rend_data->rend_pk_digest, circuit->rend_data->rend_pk_digest, DIGEST_LEN); - memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN); + memcpy(launched->rend_data->rend_cookie, parsed_req->rc, REND_COOKIE_LEN); strlcpy(launched->rend_data->onion_address, service->service_id, sizeof(launched->rend_data->onion_address)); @@ -1402,19 +1382,878 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0) goto err; memcpy(cpath->handshake_digest, keys, DIGEST_LEN); - if (extend_info) extend_info_free(extend_info); - memset(keys, 0, sizeof(keys)); - return 0; + goto done; + + log_error: + if (!err_msg) { + if (stage_descr) { + tor_asprintf(&err_msg, + "unknown %s error for INTRODUCE2", stage_descr); + } else { + err_msg = tor_strdup("unknown error for INTRODUCE2"); + } + } + + log_warn(LD_REND, "%s on circ %d", err_msg, circuit->_base.n_circ_id); err: - memset(keys, 0, sizeof(keys)); + status = -1; if (dh) crypto_dh_free(dh); - if (launched) + if (launched) { circuit_mark_for_close(TO_CIRCUIT(launched), reason); - if (extend_info) extend_info_free(extend_info); + } + tor_free(err_msg); + + done: + memset(keys, 0, sizeof(keys)); + memset(buf, 0, sizeof(buf)); + memset(serviceid, 0, sizeof(serviceid)); + memset(hexcookie, 0, sizeof(hexcookie)); + memset(intro_key_digest, 0, sizeof(intro_key_digest)); + memset(auth_data, 0, sizeof(auth_data)); + memset(diffie_hellman_hash, 0, sizeof(diffie_hellman_hash)); + + /* Free the parsed cell */ + if (parsed_req) { + rend_service_free_intro(parsed_req); + parsed_req = NULL; + } + + /* Free rp if we must */ + if (need_rp_free) extend_info_free(rp); + + return status; +} + +/** Given a parsed and decrypted INTRODUCE2, find the rendezvous point or + * return NULL and an error string if we can't. + */ + +static extend_info_t * +find_rp_for_intro(const rend_intro_cell_t *intro, + uint8_t *need_free_out, char **err_msg_out) +{ + extend_info_t *rp = NULL; + char *err_msg = NULL; + const char *rp_nickname = NULL; + const node_t *node = NULL; + uint8_t need_free = 0; + + if (!intro || !need_free_out) { + if (err_msg_out) + err_msg = tor_strdup("Bad parameters to find_rp_for_intro()"); + + goto err; + } + + if (intro->version == 0 || intro->version == 1) { + if (intro->version == 1) rp_nickname = (const char *)(intro->u.v1.rp); + else rp_nickname = (const char *)(intro->u.v0.rp); + + node = node_get_by_nickname(rp_nickname, 0); + if (!node) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "Couldn't find router %s named in INTRODUCE2 cell", + escaped_safe_str_client(rp_nickname)); + } + + goto err; + } + + rp = extend_info_from_node(node, 0); + if (!rp) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "Could build extend_info_t for router %s named " + "in INTRODUCE2 cell", + escaped_safe_str_client(rp_nickname)); + } + + goto err; + } else { + need_free = 1; + } + } else if (intro->version == 2) { + rp = intro->u.v2.extend_info; + } else if (intro->version == 3) { + rp = intro->u.v3.extend_info; + } else { + if (err_msg_out) { + tor_asprintf(&err_msg, + "Unknown version %d in INTRODUCE2 cell", + (int)(intro->version)); + } + + goto err; + } + + goto done; + + err: + if (err_msg_out) *err_msg_out = err_msg; + else tor_free(err_msg); + + done: + if (rp && need_free_out) *need_free_out = need_free; + + return rp; +} + +/** Remove unnecessary parts from a rend_intro_cell_t - the ciphertext if + * already decrypted, the plaintext too if already parsed + */ + +void +rend_service_compact_intro(rend_intro_cell_t *request) +{ + if (!request) return; + + if ((request->plaintext && request->plaintext_len > 0) || + request->parsed) { + tor_free(request->ciphertext); + request->ciphertext_len = 0; + } + + if (request->parsed) { + tor_free(request->plaintext); + request->plaintext_len = 0; + } +} + +/** Free a parsed INTRODUCE1 or INTRODUCE2 cell that was allocated by + * rend_service_parse_intro(). + */ +void +rend_service_free_intro(rend_intro_cell_t *request) +{ + if (!request) { + log_info(LD_BUG, "rend_service_free_intro() called with NULL request!"); + return; + } + + /* Free ciphertext */ + tor_free(request->ciphertext); + request->ciphertext_len = 0; + + /* Have plaintext? */ + if (request->plaintext) { + /* Zero it out just to be safe */ + memset(request->plaintext, 0, request->plaintext_len); + tor_free(request->plaintext); + request->plaintext_len = 0; + } + + /* Have parsed plaintext? */ + if (request->parsed) { + switch (request->version) { + case 0: + case 1: + /* + * Nothing more to do; these formats have no further pointers + * in them. + */ + break; + case 2: + extend_info_free(request->u.v2.extend_info); + request->u.v2.extend_info = NULL; + break; + case 3: + if (request->u.v3.auth_data) { + memset(request->u.v3.auth_data, 0, request->u.v3.auth_len); + tor_free(request->u.v3.auth_data); + } + + extend_info_free(request->u.v3.extend_info); + request->u.v3.extend_info = NULL; + break; + default: + log_info(LD_BUG, + "rend_service_free_intro() saw unknown protocol " + "version %d.", + request->version); + } + } + + /* Zero it out to make sure sensitive stuff doesn't hang around in memory */ + memset(request, 0, sizeof(*request)); + + tor_free(request); +} + +/** Parse an INTRODUCE1 or INTRODUCE2 cell into a newly allocated + * rend_intro_cell_t structure. Free it with rend_service_free_intro() + * when finished. The type parameter should be 1 or 2 to indicate whether + * this is INTRODUCE1 or INTRODUCE2. This parses only the non-encrypted + * parts; after this, call rend_service_decrypt_intro() with a key, then + * rend_service_parse_intro_plaintext() to finish parsing. The optional + * err_msg_out parameter is set to a string suitable for log output + * if parsing fails. This function does some validation, but only + * that which depends solely on the contents of the cell and the + * key; it can be unit-tested. Further validation is done in + * rend_service_validate_intro(). + */ + +rend_intro_cell_t * +rend_service_begin_parse_intro(const uint8_t *request, + size_t request_len, + uint8_t type, + char **err_msg_out) +{ + rend_intro_cell_t *rv = NULL; + char *err_msg = NULL; + + if (!request || request_len <= 0) goto err; + if (!(type == 1 || type == 2)) goto err; + + /* First, check that the cell is long enough to be a sensible INTRODUCE */ + + /* min key length plus digest length plus nickname length */ + if (request_len < + (DIGEST_LEN + REND_COOKIE_LEN + (MAX_NICKNAME_LEN + 1) + + DH_KEY_LEN + 42)) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "got a truncated INTRODUCE%d cell", + (int)type); + } + goto err; + } + + /* Allocate a new parsed cell structure */ + rv = tor_malloc_zero(sizeof(*rv)); + + /* Set the type */ + rv->type = type; + + /* Copy in the ID */ + memcpy(rv->pk, request, DIGEST_LEN); + + /* Copy in the ciphertext */ + rv->ciphertext = tor_malloc(request_len - DIGEST_LEN); + memcpy(rv->ciphertext, request + DIGEST_LEN, request_len - DIGEST_LEN); + rv->ciphertext_len = request_len - DIGEST_LEN; + + goto done; + + err: + if (rv) rend_service_free_intro(rv); + rv = NULL; + if (err_msg_out && !err_msg) { + tor_asprintf(&err_msg, + "unknown INTRODUCE%d error", + (int)type); + } + + done: + if (err_msg_out) *err_msg_out = err_msg; + else tor_free(err_msg); + + return rv; +} + +/** Parse the version-specific parts of a v0 or v1 INTRODUCE1 or INTRODUCE2 + * cell + */ + +static ssize_t +rend_service_parse_intro_for_v0_or_v1( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out) +{ + const char *rp_nickname, *endptr; + size_t nickname_field_len, ver_specific_len; + + if (intro->version == 1) { + ver_specific_len = MAX_HEX_NICKNAME_LEN + 2; + rp_nickname = ((const char *)buf) + 1; + nickname_field_len = MAX_HEX_NICKNAME_LEN + 1; + } else if (intro->version == 0) { + ver_specific_len = MAX_NICKNAME_LEN + 1; + rp_nickname = (const char *)buf; + nickname_field_len = MAX_NICKNAME_LEN + 1; + } else { + if (err_msg_out) + tor_asprintf(err_msg_out, + "rend_service_parse_intro_for_v0_or_v1() called with " + "bad version %d on INTRODUCE%d cell (this is a bug)", + intro->version, + (int)(intro->type)); + goto err; + } + + if (plaintext_len < ver_specific_len) { + if (err_msg_out) + tor_asprintf(err_msg_out, + "short plaintext of encrypted part in v1 INTRODUCE%d " + "cell (%lu bytes, needed %lu)", + (int)(intro->type), + (unsigned long)plaintext_len, + (unsigned long)ver_specific_len); + goto err; + } + + endptr = memchr(rp_nickname, 0, nickname_field_len); + if (!endptr || endptr == rp_nickname) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "couldn't find a nul-padded nickname in " + "INTRODUCE%d cell", + (int)(intro->type)); + } + goto err; + } + + if ((intro->version == 0 && + !is_legal_nickname(rp_nickname)) || + (intro->version == 1 && + !is_legal_nickname_or_hexdigest(rp_nickname))) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "bad nickname in INTRODUCE%d cell", + (int)(intro->type)); + } + goto err; + } + + if (intro->version == 1) { + memcpy(intro->u.v1.rp, rp_nickname, endptr - rp_nickname + 1); + } else { + memcpy(intro->u.v0.rp, rp_nickname, endptr - rp_nickname + 1); + } + + return ver_specific_len; + + err: + return -1; +} + +/** Parse the version-specific parts of a v2 INTRODUCE1 or INTRODUCE2 cell + */ + +static ssize_t +rend_service_parse_intro_for_v2( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out) +{ + unsigned int klen; + extend_info_t *extend_info = NULL; + ssize_t ver_specific_len; + + /* + * We accept version 3 too so that the v3 parser can call this with + * and adjusted buffer for the latter part of a v3 cell, which is + * identical to a v2 cell. + */ + if (!(intro->version == 2 || + intro->version == 3)) { + if (err_msg_out) + tor_asprintf(err_msg_out, + "rend_service_parse_intro_for_v2() called with " + "bad version %d on INTRODUCE%d cell (this is a bug)", + intro->version, + (int)(intro->type)); + goto err; + } + + /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */ + if (plaintext_len < 7 + DIGEST_LEN + 2) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "truncated plaintext of encrypted parted of " + "version %d INTRODUCE%d cell", + intro->version, + (int)(intro->type)); + } + + goto err; + } + + extend_info = tor_malloc_zero(sizeof(extend_info_t)); + tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf + 1)); + extend_info->port = ntohs(get_uint16(buf + 5)); + memcpy(extend_info->identity_digest, buf + 7, DIGEST_LEN); + extend_info->nickname[0] = '$'; + base16_encode(extend_info->nickname + 1, sizeof(extend_info->nickname) - 1, + extend_info->identity_digest, DIGEST_LEN); + klen = ntohs(get_uint16(buf + 7 + DIGEST_LEN)); + + /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */ + if (plaintext_len < 7 + DIGEST_LEN + 2 + klen) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "truncated plaintext of encrypted parted of " + "version %d INTRODUCE%d cell", + intro->version, + (int)(intro->type)); + } + + goto err; + } + + extend_info->onion_key = + crypto_pk_asn1_decode((const char *)(buf + 7 + DIGEST_LEN + 2), klen); + if (!extend_info->onion_key) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "error decoding onion key in version %d " + "INTRODUCE%d cell", + intro->version, + (intro->type)); + } + + goto err; + } + + ver_specific_len = 7+DIGEST_LEN+2+klen; + + if (intro->version == 2) intro->u.v2.extend_info = extend_info; + else intro->u.v3.extend_info = extend_info; + + return ver_specific_len; + + err: + extend_info_free(extend_info); + + return -1; +} + +/** Parse the version-specific parts of a v3 INTRODUCE1 or INTRODUCE2 cell + */ + +static ssize_t +rend_service_parse_intro_for_v3( + rend_intro_cell_t *intro, + const uint8_t *buf, + size_t plaintext_len, + char **err_msg_out) +{ + ssize_t adjust, v2_ver_specific_len, ts_offset; + + /* This should only be called on v3 cells */ + if (intro->version != 3) { + if (err_msg_out) + tor_asprintf(err_msg_out, + "rend_service_parse_intro_for_v3() called with " + "bad version %d on INTRODUCE%d cell (this is a bug)", + intro->version, + (int)(intro->type)); + goto err; + } + + /* + * Check that we have at least enough to get auth_len: + * + * 1 octet for version, 1 for auth_type, 2 for auth_len + */ + if (plaintext_len < 4) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "truncated plaintext of encrypted parted of " + "version %d INTRODUCE%d cell", + intro->version, + (int)(intro->type)); + } + + goto err; + } + + /* + * The rend_client_send_introduction() function over in rendclient.c is + * broken (i.e., fails to match the spec) in such a way that we can't + * change it without breaking the protocol. Specifically, it doesn't + * emit auth_len when auth-type is REND_NO_AUTH, so everything is off + * by two bytes after that. Calculate ts_offset and do everything from + * the timestamp on relative to that to handle this dain bramage. + */ + + intro->u.v3.auth_type = buf[1]; + if (intro->u.v3.auth_type != REND_NO_AUTH) { + intro->u.v3.auth_len = ntohs(get_uint16(buf + 2)); + ts_offset = 4 + intro->u.v3.auth_len; + } else { + intro->u.v3.auth_len = 0; + ts_offset = 2; + } + + /* Check that auth len makes sense for this auth type */ + if (intro->u.v3.auth_type == REND_BASIC_AUTH || + intro->u.v3.auth_type == REND_STEALTH_AUTH) { + if (intro->u.v3.auth_len != REND_DESC_COOKIE_LEN) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "wrong auth data size %d for INTRODUCE%d cell, " + "should be %d", + (int)(intro->u.v3.auth_len), + (int)(intro->type), + REND_DESC_COOKIE_LEN); + } + + goto err; + } + } + + /* Check that we actually have everything up to the timestamp */ + if (plaintext_len < (size_t)(ts_offset)) { + if (err_msg_out) { + tor_asprintf(err_msg_out, + "truncated plaintext of encrypted parted of " + "version %d INTRODUCE%d cell", + intro->version, + (int)(intro->type)); + } + + goto err; + } + + if (intro->u.v3.auth_type != REND_NO_AUTH && + intro->u.v3.auth_len > 0) { + /* Okay, we can go ahead and copy auth_data */ + intro->u.v3.auth_data = tor_malloc(intro->u.v3.auth_len); + /* + * We know we had an auth_len field in this case, so 4 is + * always right. + */ + memcpy(intro->u.v3.auth_data, buf + 4, intro->u.v3.auth_len); + } + + /* + * Apparently we don't use the timestamp any more, but might as well copy + * over just in case we ever care about it. + */ + intro->u.v3.timestamp = ntohl(get_uint32(buf + ts_offset)); + + /* + * From here on, the format is as in v2, so we call the v2 parser with + * adjusted buffer and length. We are 4 + ts_offset octets in, but the + * v2 parser expects to skip over a version byte at the start, so we + * adjust by 3 + ts_offset. + */ + adjust = 3 + ts_offset; + + v2_ver_specific_len = + rend_service_parse_intro_for_v2(intro, + buf + adjust, plaintext_len - adjust, + err_msg_out); + + /* Success in v2 parser */ + if (v2_ver_specific_len >= 0) return v2_ver_specific_len + adjust; + /* Failure in v2 parser; it will have provided an err_msg */ + else return v2_ver_specific_len; + + err: return -1; } +/** Table of parser functions for version-specific parts of an INTRODUCE2 + * cell. + */ + +static ssize_t + (*intro_version_handlers[])( + rend_intro_cell_t *, + const uint8_t *, + size_t, + char **) = +{ rend_service_parse_intro_for_v0_or_v1, + rend_service_parse_intro_for_v0_or_v1, + rend_service_parse_intro_for_v2, + rend_service_parse_intro_for_v3 }; + +/** Decrypt the encrypted part of an INTRODUCE1 or INTRODUCE2 cell, + * return 0 if successful, or < 0 and write an error message to + * *err_msg_out if provided. + */ + +int +rend_service_decrypt_intro( + rend_intro_cell_t *intro, + crypto_pk_t *key, + char **err_msg_out) +{ + char *err_msg = NULL; + uint8_t key_digest[DIGEST_LEN]; + char service_id[REND_SERVICE_ID_LEN_BASE32+1]; + ssize_t key_len; + uint8_t buf[RELAY_PAYLOAD_SIZE]; + int result, status = 0; + + if (!intro || !key) { + if (err_msg_out) { + err_msg = + tor_strdup("rend_service_decrypt_intro() called with bad " + "parameters"); + } + + status = -2; + goto err; + } + + /* Make sure we have ciphertext */ + if (!(intro->ciphertext) || intro->ciphertext_len <= 0) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "rend_intro_cell_t was missing ciphertext for " + "INTRODUCE%d cell", + (int)(intro->type)); + } + status = -3; + goto err; + } + + /* 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 (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) { + if (err_msg_out) { + base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1, + (char*)(intro->pk), REND_SERVICE_ID_LEN); + tor_asprintf(&err_msg, + "got an INTRODUCE%d cell for the wrong service (%s)", + (int)(intro->type), + escaped(service_id)); + } + + status = -4; + goto err; + } + + /* Make sure the encrypted part is long enough to decrypt */ + + key_len = crypto_pk_keysize(key); + if (intro->ciphertext_len < key_len) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "got an INTRODUCE%d cell with a truncated PK-encrypted " + "part", + (int)(intro->type)); + } + + status = -5; + goto err; + } + + /* Decrypt the encrypted part */ + + note_crypto_pk_op(REND_SERVER); + result = + crypto_pk_private_hybrid_decrypt( + key, (char *)buf, sizeof(buf), + (const char *)(intro->ciphertext), intro->ciphertext_len, + PK_PKCS1_OAEP_PADDING, 1); + if (result < 0) { + if (err_msg_out) { + tor_asprintf(&err_msg, + "couldn't decrypt INTRODUCE%d cell", + (int)(intro->type)); + } + status = -6; + goto err; + } + intro->plaintext_len = result; + intro->plaintext = tor_malloc(intro->plaintext_len); + memcpy(intro->plaintext, buf, intro->plaintext_len); + + goto done; + + err: + if (err_msg_out && !err_msg) { + tor_asprintf(&err_msg, + "unknown INTRODUCE%d error decrypting encrypted part", + (int)(intro->type)); + } + if (status >= 0) status = -1; + + done: + if (err_msg_out) *err_msg_out = err_msg; + else tor_free(err_msg); + + /* clean up potentially sensitive material */ + memset(buf, 0, sizeof(buf)); + memset(key_digest, 0, sizeof(key_digest)); + memset(service_id, 0, sizeof(service_id)); + + return status; +} + +/** Parse the plaintext of the encrypted part of an INTRODUCE1 or + * INTRODUCE2 cell, return 0 if successful, or < 0 and write an error + * message to *err_msg_out if provided. + */ + +int +rend_service_parse_intro_plaintext( + rend_intro_cell_t *intro, + char **err_msg_out) +{ + char *err_msg = NULL; + ssize_t ver_specific_len, ver_invariant_len; + uint8_t version; + int status = 0; + + if (!intro) { + if (err_msg_out) { + err_msg = + tor_strdup("rend_service_parse_intro_plaintext() called with NULL " + "rend_intro_cell_t"); + } + + status = -2; + goto err; + } + + /* Check that we have plaintext */ + if (!(intro->plaintext) || intro->plaintext_len <= 0) { + if (err_msg_out) { + err_msg = tor_strdup("rend_intro_cell_t was missing plaintext"); + } + status = -3; + goto err; + } + + /* In all formats except v0, the first byte is a version number */ + version = intro->plaintext[0]; + + /* v0 has no version byte (stupid...), so handle it as a fallback */ + if (version > 3) version = 0; + + /* Copy the version into the parsed cell structure */ + intro->version = version; + + /* Call the version-specific parser from the table */ + ver_specific_len = + intro_version_handlers[version](intro, + intro->plaintext, intro->plaintext_len, + &err_msg); + if (ver_specific_len < 0) { + status = -4; + goto err; + } + + /** The rendezvous cookie and Diffie-Hellman stuff are version-invariant + * and at the end of the plaintext of the encrypted part of the cell. + */ + + ver_invariant_len = intro->plaintext_len - ver_specific_len; + if (ver_invariant_len < REND_COOKIE_LEN + DH_KEY_LEN) { + tor_asprintf(&err_msg, + "decrypted plaintext of INTRODUCE%d cell was truncated (%ld bytes)", + (int)(intro->type), + (long)(intro->plaintext_len)); + status = -5; + goto err; + } else if (ver_invariant_len > REND_COOKIE_LEN + DH_KEY_LEN) { + tor_asprintf(&err_msg, + "decrypted plaintext of INTRODUCE%d cell was too long (%ld bytes)", + (int)(intro->type), + (long)(intro->plaintext_len)); + status = -6; + } else { + memcpy(intro->rc, + intro->plaintext + ver_specific_len, + REND_COOKIE_LEN); + memcpy(intro->dh, + intro->plaintext + ver_specific_len + REND_COOKIE_LEN, + DH_KEY_LEN); + } + + /* Flag it as being fully parsed */ + intro->parsed = 1; + + goto done; + + err: + if (err_msg_out && !err_msg) { + tor_asprintf(&err_msg, + "unknown INTRODUCE%d error parsing encrypted part", + (int)(intro->type)); + } + if (status >= 0) status = -1; + + done: + if (err_msg_out) *err_msg_out = err_msg; + else tor_free(err_msg); + + return status; +} + +/** Do validity checks on a parsed intro cell before decryption; some of + * these are not done in rend_service_begin_parse_intro() itself because + * they depend on a lot of other state and would make it hard to unit test. + * Returns >= 0 if successful or < 0 if the intro cell is invalid, and + * optionally writes out an error message for logging. If an err_msg + * pointer is provided, it is the caller's responsibility to free any + * provided message. + */ + +int +rend_service_validate_intro_early(const rend_intro_cell_t *intro, + char **err_msg_out) +{ + int status = 0; + + if (!intro) { + if (err_msg_out) + *err_msg_out = + tor_strdup("NULL intro cell passed to " + "rend_service_validate_intro_early()"); + + status = -1; + goto err; + } + + /* TODO */ + + err: + return status; +} + +/** Do validity checks on a parsed intro cell after decryption; some of + * these are not done in rend_service_parse_intro_plaintext() itself because + * they depend on a lot of other state and would make it hard to unit test. + * Returns >= 0 if successful or < 0 if the intro cell is invalid, and + * optionally writes out an error message for logging. If an err_msg + * pointer is provided, it is the caller's responsibility to free any + * provided message. + */ + +int +rend_service_validate_intro_late(const rend_intro_cell_t *intro, + char **err_msg_out) +{ + int status = 0; + + if (!intro) { + if (err_msg_out) + *err_msg_out = + tor_strdup("NULL intro cell passed to " + "rend_service_validate_intro_late()"); + + status = -1; + goto err; + } + + if (intro->version == 3 && intro->parsed) { + if (!(intro->u.v3.auth_type == REND_NO_AUTH || + intro->u.v3.auth_type == REND_BASIC_AUTH || + intro->u.v3.auth_type == REND_STEALTH_AUTH)) { + /* This is an informative message, not an error, as in the old code */ + if (err_msg_out) + tor_asprintf(err_msg_out, + "unknown authorization type %d", + intro->u.v3.auth_type); + } + } + + err: + return status; +} + /** 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. */ @@ -1600,8 +2439,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) this case, we might as well close the thing. */ log_info(LD_CIRC|LD_REND, "We have just finished an introduction " "circuit, but we already have enough. Closing it."); - circuit_mark_for_close(TO_CIRCUIT(circuit), END_CIRC_REASON_NONE); - return; + reason = END_CIRC_REASON_NONE; + goto err; } else { tor_assert(circuit->build_state->is_internal); log_info(LD_CIRC|LD_REND, "We have just finished an introduction " @@ -1622,7 +2461,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) } circuit_has_opened(circuit); - return; + goto done; } } @@ -1668,9 +2507,16 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) goto err; } - return; + goto done; + err: circuit_mark_for_close(TO_CIRCUIT(circuit), reason); + done: + memset(buf, 0, sizeof(buf)); + memset(auth, 0, sizeof(auth)); + memset(serviceid, 0, sizeof(serviceid)); + + return; } /** Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a @@ -1813,9 +2659,16 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* Change the circuit purpose. */ circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_S_REND_JOINED); - return; + goto done; + err: circuit_mark_for_close(TO_CIRCUIT(circuit), reason); + done: + memset(buf, 0, sizeof(buf)); + memset(serviceid, 0, sizeof(serviceid)); + memset(hexcookie, 0, sizeof(hexcookie)); + + return; } /* @@ -2091,11 +2944,7 @@ upload_service_descriptor(rend_service_t *service) static int intro_point_accepted_intro_count(rend_intro_point_t *intro) { - if (intro->accepted_intro_rsa_parts == NULL) { - return 0; - } else { - return digestmap_size(intro->accepted_intro_rsa_parts); - } + return intro->accepted_introduce2_count; } /** Return non-zero iff <b>intro</b> should 'expire' now (i.e. we diff --git a/src/or/rendservice.h b/src/or/rendservice.h index e5848785a8..0d6eddaee6 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -12,9 +12,67 @@ #ifndef _TOR_RENDSERVICE_H #define _TOR_RENDSERVICE_H +#include "or.h" + +typedef struct rend_intro_cell_s rend_intro_cell_t; + +#ifdef RENDSERVICE_PRIVATE + +/* This can be used for both INTRODUCE1 and INTRODUCE2 */ + +struct rend_intro_cell_s { + /* Is this an INTRODUCE1 or INTRODUCE2? (set to 1 or 2) */ + uint8_t type; + /* Public key digest */ + uint8_t pk[DIGEST_LEN]; + /* Optionally, store ciphertext here */ + uint8_t *ciphertext; + ssize_t ciphertext_len; + /* Optionally, store plaintext */ + uint8_t *plaintext; + ssize_t plaintext_len; + /* Have we parsed the plaintext? */ + uint8_t parsed; + /* intro protocol version (0, 1, 2 or 3) */ + uint8_t version; + /* Version-specific parts */ + union { + struct { + /* Rendezvous point nickname */ + uint8_t rp[20]; + } v0; + struct { + /* Rendezvous point nickname or hex-encoded key digest */ + uint8_t rp[42]; + } v1; + struct { + /* The extend_info_t struct has everything v2 uses */ + extend_info_t *extend_info; + } v2; + struct { + /* Auth type used */ + uint8_t auth_type; + /* Length of auth data */ + uint16_t auth_len; + /* Auth data */ + uint8_t *auth_data; + /* timestamp */ + uint32_t timestamp; + /* Rendezvous point's IP address/port, identity digest and onion key */ + extend_info_t *extend_info; + } v3; + } u; + /* Rendezvous cookie */ + uint8_t rc[REND_COOKIE_LEN]; + /* Diffie-Hellman data */ + uint8_t dh[DH_KEY_LEN]; +}; + +#endif + int num_rend_services(void); int rend_config_services(const or_options_t *options, int validate_only); -int rend_service_load_keys(void); +int rend_service_load_all_keys(void); void rend_services_introduce(void); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); @@ -27,6 +85,21 @@ int rend_service_intro_established(origin_circuit_t *circuit, void rend_service_rendezvous_has_opened(origin_circuit_t *circuit); int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, size_t request_len); +void rend_service_compact_intro(rend_intro_cell_t *request); +int rend_service_decrypt_intro(rend_intro_cell_t *request, + crypto_pk_t *key, + char **err_msg_out); +void rend_service_free_intro(rend_intro_cell_t *request); +rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request, + size_t request_len, + uint8_t type, + char **err_msg_out); +int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro, + char **err_msg_out); +int rend_service_validate_intro_early(const rend_intro_cell_t *intro, + char **err_msg_out); +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); int rend_service_set_connection_addr_port(edge_connection_t *conn, origin_circuit_t *circ); diff --git a/src/or/rephist.c b/src/or/rephist.c index 720d14cf45..1c5b511100 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1136,7 +1136,7 @@ rep_hist_load_mtbf_data(time_t now) wfu_timebuf[0] = '\0'; if (format == 1) { - n = sscanf(line, "%40s %ld %lf S=%10s %8s", + n = tor_sscanf(line, "%40s %ld %lf S=%10s %8s", hexbuf, &wrl, &trw, mtbf_timebuf, mtbf_timebuf+11); if (n != 3 && n != 5) { log_warn(LD_HIST, "Couldn't scan line %s", escaped(line)); @@ -1153,7 +1153,7 @@ rep_hist_load_mtbf_data(time_t now) wfu_idx = find_next_with(lines, i+1, "+WFU "); if (mtbf_idx >= 0) { const char *mtbfline = smartlist_get(lines, mtbf_idx); - n = sscanf(mtbfline, "+MTBF %lu %lf S=%10s %8s", + n = tor_sscanf(mtbfline, "+MTBF %lu %lf S=%10s %8s", &wrl, &trw, mtbf_timebuf, mtbf_timebuf+11); if (n == 2 || n == 4) { have_mtbf = 1; @@ -1164,7 +1164,7 @@ rep_hist_load_mtbf_data(time_t now) } if (wfu_idx >= 0) { const char *wfuline = smartlist_get(lines, wfu_idx); - n = sscanf(wfuline, "+WFU %lu %lu S=%10s %8s", + n = tor_sscanf(wfuline, "+WFU %lu %lu S=%10s %8s", &wt_uptime, &total_wt_time, wfu_timebuf, wfu_timebuf+11); if (n == 2 || n == 4) { @@ -1531,7 +1531,7 @@ rep_hist_get_bandwidth_lines(void) const char *desc = NULL; size_t len; - /* opt [dirreq-](read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n... */ + /* [dirreq-](read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n... */ /* The n,n,n part above. Largest representation of a uint64_t is 20 chars * long, plus the comma. */ #define MAX_HIST_VALUE_LEN 21*NUM_TOTALS diff --git a/src/or/replaycache.c b/src/or/replaycache.c new file mode 100644 index 0000000000..09104a9373 --- /dev/null +++ b/src/or/replaycache.c @@ -0,0 +1,215 @@ + /* Copyright (c) 2012, The Tor Project, Inc. */ + /* See LICENSE for licensing information */ + +/* + * \file replaycache.c + * + * \brief Self-scrubbing replay cache for rendservice.c + */ + +#define REPLAYCACHE_PRIVATE + +#include "or.h" +#include "replaycache.h" + +/** Free the replaycache r and all of its entries. + */ + +void +replaycache_free(replaycache_t *r) +{ + if (!r) { + log_info(LD_BUG, "replaycache_free() called on NULL"); + return; + } + + if (r->digests_seen) digestmap_free(r->digests_seen, _tor_free); + + tor_free(r); +} + +/** Allocate a new, empty replay detection cache, where horizon is the time + * for entries to age out and interval is the time after which the cache + * should be scrubbed for old entries. + */ + +replaycache_t * +replaycache_new(time_t horizon, time_t interval) +{ + replaycache_t *r = NULL; + + if (horizon < 0) { + log_info(LD_BUG, "replaycache_new() called with negative" + " horizon parameter"); + goto err; + } + + if (interval < 0) { + log_info(LD_BUG, "replaycache_new() called with negative interval" + " parameter"); + interval = 0; + } + + r = tor_malloc(sizeof(*r)); + r->scrub_interval = interval; + r->scrubbed = 0; + r->horizon = horizon; + r->digests_seen = digestmap_new(); + + err: + return r; +} + +/** See documentation for replaycache_add_and_test() + */ + +int +replaycache_add_and_test_internal( + time_t present, replaycache_t *r, const void *data, int len, + time_t *elapsed) +{ + int rv = 0; + char digest[DIGEST_LEN]; + time_t *access_time; + + /* sanity check */ + if (present <= 0 || !r || !data || len <= 0) { + log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid" + " parameters; please fix this."); + goto done; + } + + /* compute digest */ + crypto_digest(digest, (const char *)data, len); + + /* check map */ + access_time = digestmap_get(r->digests_seen, digest); + + /* seen before? */ + if (access_time != NULL) { + /* + * If it's far enough in the past, no hit. If the horizon is zero, we + * never expire. + */ + if (*access_time >= present - r->horizon || r->horizon == 0) { + /* replay cache hit, return 1 */ + rv = 1; + /* If we want to output an elapsed time, do so */ + if (elapsed) { + if (present >= *access_time) { + *elapsed = present - *access_time; + } else { + /* We shouldn't really be seeing hits from the future, but... */ + *elapsed = 0; + } + } + } + /* + * If it's ahead of the cached time, update + */ + if (*access_time < present) { + *access_time = present; + } + } else { + /* No, so no hit and update the digest map with the current time */ + access_time = tor_malloc(sizeof(*access_time)); + *access_time = present; + digestmap_set(r->digests_seen, digest, access_time); + } + + /* now scrub the cache if it's time */ + replaycache_scrub_if_needed_internal(present, r); + + done: + return rv; +} + +/** See documentation for replaycache_scrub_if_needed() + */ + +void +replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) +{ + digestmap_iter_t *itr = NULL; + const char *digest; + void *valp; + time_t *access_time; + char scrub_this; + + /* sanity check */ + if (!r || !(r->digests_seen)) { + log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with" + " stupid parameters; please fix this."); + return; + } + + /* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */ + if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return; + + /* if we're never expiring, don't bother scrubbing */ + if (r->horizon == 0) return; + + /* okay, scrub time */ + itr = digestmap_iter_init(r->digests_seen); + while (!digestmap_iter_done(itr)) { + scrub_this = 0; + digestmap_iter_get(itr, &digest, &valp); + access_time = (time_t *)valp; + if (access_time) { + /* aged out yet? */ + if (*access_time < present - r->horizon) scrub_this = 1; + } else { + /* Buh? Get rid of it, anyway */ + log_info(LD_BUG, "replaycache_scrub_if_needed_internal() saw a NULL" + " entry in the digestmap."); + scrub_this = 1; + } + + if (scrub_this) { + /* Advance the iterator and remove this one */ + itr = digestmap_iter_next_rmv(r->digests_seen, itr); + /* Free the value removed */ + tor_free(access_time); + } else { + /* Just advance the iterator */ + itr = digestmap_iter_next(r->digests_seen, itr); + } + } + + /* update scrubbed timestamp */ + if (present > r->scrubbed) r->scrubbed = present; +} + +/** Test the buffer of length len point to by data against the replay cache r; + * the digest of the buffer will be added to the cache at the current time, + * and the function will return 1 if it was already seen within the cache's + * horizon, or 0 otherwise. + */ + +int +replaycache_add_and_test(replaycache_t *r, const void *data, int len) +{ + return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL); +} + +/** Like replaycache_add_and_test(), but if it's a hit also return the time + * elapsed since this digest was last seen. + */ + +int +replaycache_add_test_and_elapsed( + replaycache_t *r, const void *data, int len, time_t *elapsed) +{ + return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed); +} + +/** Scrub aged entries out of r if sufficiently long has elapsed since r was + * last scrubbed. + */ + +void +replaycache_scrub_if_needed(replaycache_t *r) +{ + replaycache_scrub_if_needed_internal(time(NULL), r); +} + diff --git a/src/or/replaycache.h b/src/or/replaycache.h new file mode 100644 index 0000000000..9f3107c513 --- /dev/null +++ b/src/or/replaycache.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file replaycache.h + * \brief Header file for replaycache.c. + **/ + +#ifndef _TOR_REPLAYCACHE_H +#define _TOR_REPLAYCACHE_H + +typedef struct replaycache_s replaycache_t; + +#ifdef REPLAYCACHE_PRIVATE + +struct replaycache_s { + /* Scrub interval */ + time_t scrub_interval; + /* Last scrubbed */ + time_t scrubbed; + /* + * Horizon + * (don't return true on digests in the cache but older than this) + */ + time_t horizon; + /* + * Digest map: keys are digests, values are times the digest was last seen + */ + digestmap_t *digests_seen; +}; + +#endif /* REPLAYCACHE_PRIVATE */ + +/* replaycache_t free/new */ + +void replaycache_free(replaycache_t *r); +replaycache_t * replaycache_new(time_t horizon, time_t interval); + +#ifdef REPLAYCACHE_PRIVATE + +/* + * replaycache_t internal functions: + * + * These take the time to treat as the present as an argument for easy unit + * testing. For everything else, use the wrappers below instead. + */ + +int replaycache_add_and_test_internal( + time_t present, replaycache_t *r, const void *data, int len, + time_t *elapsed); +void replaycache_scrub_if_needed_internal( + time_t present, replaycache_t *r); + +#endif /* REPLAYCACHE_PRIVATE */ + +/* + * replaycache_t methods + */ + +int replaycache_add_and_test(replaycache_t *r, const void *data, int len); +int replaycache_add_test_and_elapsed( + replaycache_t *r, const void *data, int len, time_t *elapsed); +void replaycache_scrub_if_needed(replaycache_t *r); + +#endif + diff --git a/src/or/router.c b/src/or/router.c index 38f1cdd495..2cb0e26686 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -27,6 +27,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "transports.h" /** * \file router.c @@ -879,6 +880,21 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) return advertising ? dir_port : 0; } +/** Allocate and return a new extend_info_t that can be used to build + * a circuit to or through the router <b>r</b>. Use the primary + * address of the router unless <b>for_direct_connect</b> is true, in + * which case the preferred address is used instead. */ +static extend_info_t * +extend_info_from_router(const routerinfo_t *r) +{ + tor_addr_port_t ap; + tor_assert(r); + + router_get_prim_orport(r, &ap); + return extend_info_alloc(r->nickname, r->cache_info.identity_digest, + r->onion_pkey, &ap.addr, ap.port); +} + /** Some time has passed, or we just got new directory information. * See if we currently believe our ORPort or DirPort to be * unreachable. If so, launch a new test for it. @@ -920,12 +936,11 @@ consider_testing_reachability(int test_or, int test_dir) } if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) { - extend_info_t *ei; + extend_info_t *ei = extend_info_from_router(me); + /* XXX IPv6 self testing */ log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", me->address, me->or_port); - /* XXX IPv6 self testing IPv6 orports will need pref_addr */ - ei = extend_info_from_router(me, 0); circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei, CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); extend_info_free(ei); @@ -1539,8 +1554,9 @@ router_rebuild_descriptor(int force) ri->cache_info.published_on = time(NULL); ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from * main thread */ - if (options->BridgeRelay) { - /* For now, only bridges advertise an ipv6 or-address. And only one. */ + + /* For now, at most one IPv6 or-address is being advertised. */ + { const port_cfg_t *ipv6_orport = NULL; SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { if (p->type == CONN_TYPE_OR_LISTENER && @@ -1565,6 +1581,7 @@ router_rebuild_descriptor(int force) ri->ipv6_orport = ipv6_orport->port; } } + ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key()); if (crypto_pk_get_digest(ri->identity_pkey, ri->cache_info.identity_digest)<0) { @@ -2054,9 +2071,9 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, "router %s %s %d 0 %d\n" "%s" "platform %s\n" - "opt protocols Link 1 2 Circuit 1\n" + "protocols Link 1 2 Circuit 1\n" "published %s\n" - "opt fingerprint %s\n" + "fingerprint %s\n" "uptime %ld\n" "bandwidth %d %d %d\n" "%s%s%s%s" @@ -2075,15 +2092,15 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, (int) router->bandwidthrate, (int) router->bandwidthburst, (int) router->bandwidthcapacity, - has_extra_info_digest ? "opt extra-info-digest " : "", + has_extra_info_digest ? "extra-info-digest " : "", has_extra_info_digest ? extra_info_digest : "", has_extra_info_digest ? "\n" : "", - options->DownloadExtraInfo ? "opt caches-extra-info\n" : "", + options->DownloadExtraInfo ? "caches-extra-info\n" : "", onion_pkey, identity_pkey, family_line, - we_are_hibernating() ? "opt hibernating 1\n" : "", - options->HidServDirectoryV2 ? "opt hidden-service-dir\n" : "", - options->AllowSingleHopExits ? "opt allow-single-hop-exits\n" : ""); + we_are_hibernating() ? "hibernating 1\n" : "", + options->HidServDirectoryV2 ? "hidden-service-dir\n" : "", + options->AllowSingleHopExits ? "allow-single-hop-exits\n" : ""); tor_free(family_line); tor_free(onion_pkey); @@ -2194,40 +2211,24 @@ router_get_prim_orport(const routerinfo_t *router, tor_addr_port_t *ap_out) ap_out->port = router->or_port; } -/** Return 1 if we prefer the IPv6 address and OR TCP port of - * <b>router</b>, else 0. - * - * We prefer the IPv6 address if the router has one and - * i) the routerinfo_t says so - * or - * ii) the router has no IPv4 address. */ +/** Return 1 if any of <b>router</b>'s addresses are <b>addr</b>. + * Otherwise return 0. */ int -router_ipv6_preferred(const routerinfo_t *router) +router_has_addr(const routerinfo_t *router, const tor_addr_t *addr) { - return (!tor_addr_is_null(&router->ipv6_addr) - && (router->ipv6_preferred || router->addr == 0)); -} - -/** Copy the preferred OR port (IP address and TCP port) for - * <b>router</b> into *<b>addr_out</b>. */ -void -router_get_pref_orport(const routerinfo_t *router, tor_addr_port_t *ap_out) -{ - if (router_ipv6_preferred(router)) - router_get_pref_ipv6_orport(router, ap_out); - else - router_get_prim_orport(router, ap_out); + return + tor_addr_eq_ipv4h(addr, router->addr) || + tor_addr_eq(&router->ipv6_addr, addr); } -/** Copy the preferred IPv6 OR port (IP address and TCP port) for - * <b>router</b> into *<b>ap_out</b>. */ -void -router_get_pref_ipv6_orport(const routerinfo_t *router, - tor_addr_port_t *ap_out) +int +router_has_orport(const routerinfo_t *router, const tor_addr_port_t *orport) { - tor_assert(ap_out != NULL); - tor_addr_copy(&ap_out->addr, &router->ipv6_addr); - ap_out->port = router->ipv6_orport; + return + (tor_addr_eq_ipv4h(&orport->addr, router->addr) && + orport->port == router->or_port) || + (tor_addr_eq(&orport->addr, &router->ipv6_addr) && + orport->port == router->ipv6_orport); } /** Load the contents of <b>filename</b>, find the last line starting with @@ -2345,6 +2346,13 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, } } + /* Add information about the pluggable transports we support. */ + if (options->ServerTransportPlugin) { + char *pluggable_transports = pt_get_extra_info_descriptor_string(); + if (pluggable_transports) + smartlist_add(chunks, pluggable_transports); + } + if (should_record_bridge_info(options) && write_stats_to_extrainfo) { const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); if (bridge_stats) { @@ -2754,3 +2762,30 @@ router_free_all(void) } } +/** Return a smartlist of tor_addr_port_t's with all the OR ports of + <b>ri</b>. Note that freeing of the items in the list as well as + the smartlist itself is the callers responsibility. + + XXX duplicating code from node_get_all_orports(). */ +smartlist_t * +router_get_all_orports(const routerinfo_t *ri) +{ + smartlist_t *sl = smartlist_new(); + tor_assert(ri); + + if (ri->addr != 0) { + tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); + tor_addr_from_ipv4h(&ap->addr, ri->addr); + ap->port = ri->or_port; + smartlist_add(sl, ap); + } + if (!tor_addr_is_null(&ri->ipv6_addr)) { + tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); + tor_addr_copy(&ap->addr, &ri->ipv6_addr); + ap->port = ri->or_port; + smartlist_add(sl, ap); + } + + return sl; +} + diff --git a/src/or/router.h b/src/or/router.h index 69805d6f2d..c43c308496 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -86,15 +86,13 @@ int router_pick_published_address(const or_options_t *options, uint32_t *addr); int router_rebuild_descriptor(int force); int router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, crypto_pk_t *ident_key); -void router_get_prim_orport(const routerinfo_t *router, - tor_addr_port_t *addr_port_out); -void router_get_pref_orport(const routerinfo_t *router, - tor_addr_port_t *addr_port_out); -void router_get_pref_ipv6_orport(const routerinfo_t *router, - tor_addr_port_t *addr_port_out); -int router_ipv6_preferred(const routerinfo_t *router); int extrainfo_dump_to_string(char **s, extrainfo_t *extrainfo, crypto_pk_t *ident_key); +void router_get_prim_orport(const routerinfo_t *router, + tor_addr_port_t *ap_out); +int router_has_addr(const routerinfo_t *router, const tor_addr_t *addr); +int router_has_orport(const routerinfo_t *router, + const tor_addr_port_t *orport); int is_legal_nickname(const char *s); int is_legal_nickname_or_hexdigest(const char *s); int is_legal_hexdigest(const char *s); @@ -132,6 +130,8 @@ void router_free_all(void); const char *router_purpose_to_string(uint8_t p); uint8_t router_purpose_from_string(const char *s); +smartlist_t *router_get_all_orports(const routerinfo_t *ri); + #ifdef ROUTER_PRIVATE /* Used only by router.c and test.c */ void get_platform_str(char *platform, size_t len); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 3c39e362df..3aea606207 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1343,9 +1343,11 @@ mark_all_trusteddirservers_up(void) /** Return true iff r1 and r2 have the same address and OR port. */ int -routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2) +routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2) { - return r1->addr == r2->addr && r1->or_port == r2->or_port; + return r1->addr == r2->addr && r1->or_port == r2->or_port && + tor_addr_eq(&r1->ipv6_addr, &r2->ipv6_addr) && + r1->ipv6_orport == r2->ipv6_orport; } /** Reset all internal variables used to count failed downloads of network @@ -2890,7 +2892,7 @@ routerlist_insert(routerlist_t *rl, routerinfo_t *ri) &ri->cache_info); smartlist_add(rl->routers, ri); ri->cache_info.routerlist_index = smartlist_len(rl->routers) - 1; - nodelist_add_routerinfo(ri); + nodelist_set_routerinfo(ri, NULL); router_dir_info_changed(); #ifdef DEBUG_ROUTERLIST routerlist_assert_ok(rl); @@ -3119,8 +3121,11 @@ routerlist_replace(routerlist_t *rl, routerinfo_t *ri_old, tor_assert(0 <= idx && idx < smartlist_len(rl->routers)); tor_assert(smartlist_get(rl->routers, idx) == ri_old); - nodelist_remove_routerinfo(ri_old); - nodelist_add_routerinfo(ri_new); + { + routerinfo_t *ri_old_tmp=NULL; + nodelist_set_routerinfo(ri_new, &ri_old_tmp); + tor_assert(ri_old == ri_old_tmp); + } router_dir_info_changed(); if (idx >= 0) { @@ -3457,11 +3462,6 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, /* Same key, and either new, or listed in the consensus. */ log_debug(LD_DIR, "Replacing entry for router %s", router_describe(router)); - if (routers_have_same_or_addr(router, old_router)) { - /* these carry over when the address and orport are unchanged. */ - router->last_reachable = old_router->last_reachable; - router->testing_since = old_router->testing_since; - } routerlist_replace(routerlist, old_router, router); if (!from_cache) { signed_desc_append_to_journal(&router->cache_info, @@ -4440,11 +4440,6 @@ launch_descriptor_downloads(int purpose, } } } - /* XXX should we consider having even the dir mirrors delay - * a little bit, so we don't load the authorities as much? -RD - * I don't think so. If we do, clients that want those descriptors may - * not actually find them if the caches haven't got them yet. -NM - */ if (! should_delay && n_downloadable) { int i, n_per_request; @@ -4484,9 +4479,9 @@ launch_descriptor_downloads(int purpose, rtr_plural = "s"; log_info(LD_DIR, - "Launching %d request%s for %d router%s, %d at a time", - CEIL_DIV(n_downloadable, n_per_request), - req_plural, n_downloadable, rtr_plural, n_per_request); + "Launching %d request%s for %d %s%s, %d at a time", + CEIL_DIV(n_downloadable, n_per_request), req_plural, + n_downloadable, descname, rtr_plural, n_per_request); smartlist_sort_digests(downloadable); for (i=0; i < n_downloadable; i += n_per_request) { initiate_descriptor_downloads(source, purpose, diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 8dcc6eb026..e84b0405d4 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -36,7 +36,7 @@ const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int router_get_my_share_of_directory_requests(double *v2_share_out, double *v3_share_out); void router_reset_status_download_failures(void); -int routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2); +int routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2); int router_nickname_is_in_list(const routerinfo_t *router, const char *list); const routerinfo_t *routerlist_find_my_routerinfo(void); const node_t *router_find_exact_exit_enclave(const char *address, diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 60a2eae75f..7bee08434f 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -67,6 +67,7 @@ typedef enum { K_OR_ADDRESS, K_P, K_R, + K_A, K_S, K_V, K_W, @@ -338,6 +339,7 @@ static token_rule_t extrainfo_token_table[] = { static token_rule_t rtrstatus_token_table[] = { T01("p", K_P, CONCAT_ARGS, NO_OBJ ), T1( "r", K_R, GE(7), NO_OBJ ), + T0N("a", K_A, GE(1), NO_OBJ ), T1( "s", K_S, ARGS, NO_OBJ ), T01("v", K_V, CONCAT_ARGS, NO_OBJ ), T01("w", K_W, ARGS, NO_OBJ ), @@ -522,6 +524,7 @@ static token_rule_t networkstatus_detached_signature_token_table[] = { /** List of tokens recognized in microdescriptors */ static token_rule_t microdesc_token_table[] = { T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), + T0N("a", K_A, GE(1), NO_OBJ ), T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("p", K_P, CONCAT_ARGS, NO_OBJ ), A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), @@ -1257,6 +1260,42 @@ dump_distinct_digest_count(int severity) #endif } +/** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's + * with at least one argument (use GE(1) in setup). If found, store + * address and port number to <b>addr_out</b> and + * <b>port_out</b>. Return number of OR ports found. */ +static int +find_single_ipv6_orport(const smartlist_t *list, + tor_addr_t *addr_out, + uint16_t *port_out) +{ + int ret = 0; + tor_assert(list != NULL); + tor_assert(addr_out != NULL); + tor_assert(port_out != NULL); + + SMARTLIST_FOREACH_BEGIN(list, directory_token_t *, t) { + tor_addr_t a; + maskbits_t bits; + uint16_t port_min, port_max; + tor_assert(t->n_args >= 1); + /* XXXX Prop186 the full spec allows much more than this. */ + if (tor_addr_parse_mask_ports(t->args[0], &a, &bits, &port_min, + &port_max) == AF_INET6 && + bits == 128 && + port_min == port_max) { + /* Okay, this is one we can understand. Use it and ignore + any potential more addresses in list. */ + tor_addr_copy(addr_out, &a); + *port_out = port_min; + ret = 1; + break; + } + } SMARTLIST_FOREACH_END(t); + + return ret; +} + /** Helper function: reads a single router entry from *<b>s</b> ... * *<b>end</b>. Mallocs a new router and returns it if all goes well, else * returns NULL. If <b>cache_copy</b> is true, duplicate the contents of @@ -1513,21 +1552,8 @@ router_parse_entry_from_string(const char *s, const char *end, { smartlist_t *or_addresses = find_all_by_keyword(tokens, K_OR_ADDRESS); if (or_addresses) { - SMARTLIST_FOREACH_BEGIN(or_addresses, directory_token_t *, t) { - tor_addr_t a; - maskbits_t bits; - uint16_t port_min, port_max; - /* XXXX Prop186 the full spec allows much more than this. */ - if (tor_addr_parse_mask_ports(t->args[0], &a, &bits, &port_min, - &port_max) == AF_INET6 && - bits == 128 && - port_min == port_max) { - /* Okay, this is one we can understand. */ - tor_addr_copy(&router->ipv6_addr, &a); - router->ipv6_orport = port_min; - break; - } - } SMARTLIST_FOREACH_END(t); + find_single_ipv6_orport(or_addresses, &router->ipv6_addr, + &router->ipv6_orport); smartlist_free(or_addresses); } } @@ -2060,6 +2086,14 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset], 10,0,65535,NULL,NULL); + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &rs->ipv6_addr, &rs->ipv6_orport); + smartlist_free(a_lines); + } + } + tok = find_opt_by_keyword(tokens, K_S); if (tok && vote) { int i; @@ -4421,6 +4455,14 @@ microdescs_parse_from_string(const char *s, const char *eos, md->onion_pkey = tok->key; tok->key = NULL; + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); + smartlist_free(a_lines); + } + } + if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { int i; md->family = smartlist_new(); diff --git a/src/or/transports.c b/src/or/transports.c index 4ba239562a..dd07a917ee 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -39,13 +39,17 @@ * transport_t structs. * * When the managed proxy stops spitting METHOD lines (signified by a - * '{S,C}METHODS DONE' message) we register all the transports - * collected to the circuitbuild.c subsystem. At this point, the - * pointers to transport_t can be transformed into dangling pointers - * at any point by the circuitbuild.c subsystem, and so we replace all - * transport_t pointers with strings describing the transport names. - * We can still go from a transport name to a transport_t using the - * fact that each transport name uniquely identifies a transport_t. + * '{S,C}METHODS DONE' message) we pass copies of its transports to + * the bridge subsystem. We keep copies of the 'transport_t's on the + * managed proxy to be able to associate the proxy with its + * transports, and we pass copies to the bridge subsystem so that + * transports can be associated with bridges. + * [ XXX We should try see whether the two copies are really needed + * and maybe cut it into a single copy of the 'transport_t' shared + * between the managed proxy and the bridge subsystem. Preliminary + * analysis shows that both copies are needed with the current code + * logic, because of race conditions that can cause dangling + * pointers. ] * * <b>In even more detail, this is what happens when a SIGHUP * occurs:</b> @@ -127,6 +131,219 @@ static INLINE void free_execve_args(char **arg); protocol version. */ #define PROTO_VERSION_ONE 1 +/** A list of pluggable transports found in torrc. */ +static smartlist_t *transport_list = NULL; + +/** Returns a transport_t struct for a transport proxy supporting the + protocol <b>name</b> listening at <b>addr</b>:<b>port</b> using + SOCKS version <b>socks_ver</b>. */ +static transport_t * +transport_new(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver) +{ + transport_t *t = tor_malloc_zero(sizeof(transport_t)); + + tor_addr_copy(&t->addr, addr); + t->port = port; + t->name = tor_strdup(name); + t->socks_version = socks_ver; + + return t; +} + +/** Free the pluggable transport struct <b>transport</b>. */ +void +transport_free(transport_t *transport) +{ + if (!transport) + return; + + tor_free(transport->name); + tor_free(transport); +} + +/** Mark every entry of the transport list to be removed on our next call to + * sweep_transport_list unless it has first been un-marked. */ +void +mark_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH(transport_list, transport_t *, t, + t->marked_for_removal = 1); +} + +/** Remove every entry of the transport list that was marked with + * mark_transport_list if it has not subsequently been un-marked. */ +void +sweep_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, t) { + if (t->marked_for_removal) { + SMARTLIST_DEL_CURRENT(transport_list, t); + transport_free(t); + } + } SMARTLIST_FOREACH_END(t); +} + +/** Initialize the pluggable transports list to empty, creating it if + * needed. */ +static void +clear_transport_list(void) +{ + if (!transport_list) + transport_list = smartlist_new(); + SMARTLIST_FOREACH(transport_list, transport_t *, t, transport_free(t)); + smartlist_clear(transport_list); +} + +/** Return a deep copy of <b>transport</b>. */ +static transport_t * +transport_copy(const transport_t *transport) +{ + transport_t *new_transport = NULL; + + tor_assert(transport); + + new_transport = tor_malloc_zero(sizeof(transport_t)); + + new_transport->socks_version = transport->socks_version; + new_transport->name = tor_strdup(transport->name); + tor_addr_copy(&new_transport->addr, &transport->addr); + new_transport->port = transport->port; + new_transport->marked_for_removal = transport->marked_for_removal; + + return new_transport; +} + +/** Returns the transport in our transport list that has the name <b>name</b>. + * Else returns NULL. */ +transport_t * +transport_get_by_name(const char *name) +{ + tor_assert(name); + + if (!transport_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(transport_list, transport_t *, transport) { + if (!strcmp(transport->name, name)) + return transport; + } SMARTLIST_FOREACH_END(transport); + + return NULL; +} + +/** Resolve any conflicts that the insertion of transport <b>t</b> + * might cause. + * Return 0 if <b>t</b> is OK and should be registered, 1 if there is + * a transport identical to <b>t</b> already registered and -1 if + * <b>t</b> cannot be added due to conflicts. */ +static int +transport_resolve_conflicts(const transport_t *t) +{ + /* This is how we resolve transport conflicts: + + If there is already a transport with the same name and addrport, + we either have duplicate torrc lines OR we are here post-HUP and + this transport was here pre-HUP as well. In any case, mark the + old transport so that it doesn't get removed and ignore the new + one. Our caller has to free the new transport so we return '1' to + signify this. + + If there is already a transport with the same name but different + addrport: + * if it's marked for removal, it means that it either has a lower + priority than 't' in torrc (otherwise the mark would have been + cleared by the paragraph above), or it doesn't exist at all in + the post-HUP torrc. We destroy the old transport and register 't'. + * if it's *not* marked for removal, it means that it was newly + added in the post-HUP torrc or that it's of higher priority, in + this case we ignore 't'. */ + transport_t *t_tmp = transport_get_by_name(t->name); + if (t_tmp) { /* same name */ + if (tor_addr_eq(&t->addr, &t_tmp->addr) && (t->port == t_tmp->port)) { + /* same name *and* addrport */ + t_tmp->marked_for_removal = 0; + return 1; + } else { /* same name but different addrport */ + if (t_tmp->marked_for_removal) { /* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " + "but there was already a transport marked for deletion at " + "'%s:%u'. We deleted the old transport and registered the " + "new one.", t->name, fmt_addr(&t->addr), t->port, + fmt_addr(&t_tmp->addr), t_tmp->port); + smartlist_remove(transport_list, t_tmp); + transport_free(t_tmp); + } else { /* *not* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " + "but the same transport already exists at '%s:%u'. " + "Skipping.", t->name, fmt_addr(&t->addr), t->port, + fmt_addr(&t_tmp->addr), t_tmp->port); + return -1; + } + } + } + + return 0; +} + +/** Add transport <b>t</b> to the internal list of pluggable + * transports. + * Returns 0 if the transport was added correctly, 1 if the same + * transport was already registered (in this case the caller must + * free the transport) and -1 if there was an error. */ +static int +transport_add(transport_t *t) +{ + int r; + tor_assert(t); + + r = transport_resolve_conflicts(t); + + switch (r) { + case 0: /* should register transport */ + if (!transport_list) + transport_list = smartlist_new(); + smartlist_add(transport_list, t); + return 0; + default: /* let our caller know the return code */ + return r; + } +} + +/** Remember a new pluggable transport proxy at <b>addr</b>:<b>port</b>. + * <b>name</b> is set to the name of the protocol this proxy uses. + * <b>socks_ver</b> is set to the SOCKS version of the proxy. */ +int +transport_add_from_config(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver) +{ + transport_t *t = transport_new(addr, port, name, socks_ver); + + int r = transport_add(t); + + switch (r) { + case -1: + default: + log_notice(LD_GENERAL, "Could not add transport %s at %s:%u. Skipping.", + t->name, fmt_addr(&t->addr), t->port); + transport_free(t); + return -1; + case 1: + log_info(LD_GENERAL, "Succesfully registered transport %s at %s:%u.", + t->name, fmt_addr(&t->addr), t->port); + transport_free(t); /* falling */ + return 0; + case 0: + log_info(LD_GENERAL, "Succesfully registered transport %s at %s:%u.", + t->name, fmt_addr(&t->addr), t->port); + return 0; + } +} + /** List of unconfigured managed proxies. */ static smartlist_t *managed_proxy_list = NULL; /** Number of still unconfigured proxies. */ @@ -217,11 +434,11 @@ proxy_needs_restart(const managed_proxy_t *mp) { /* mp->transport_to_launch is populated with the names of the transports that must be launched *after* the SIGHUP. - mp->transports is populated with the names of the transports that - were launched *before* the SIGHUP. + mp->transports is populated with the transports that were + launched *before* the SIGHUP. - If the two lists contain the same strings, we don't need to - restart the proxy, since it already does what we want. */ + Check if all the transports that need to be launched are already + launched: */ tor_assert(smartlist_len(mp->transports_to_launch) > 0); tor_assert(mp->conf_state == PT_PROTO_COMPLETED); @@ -229,11 +446,11 @@ proxy_needs_restart(const managed_proxy_t *mp) if (smartlist_len(mp->transports_to_launch) != smartlist_len(mp->transports)) goto needs_restart; - SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, char *, t_t_l) { - if (!smartlist_string_isin(mp->transports, t_t_l)) + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + if (!smartlist_string_isin(mp->transports_to_launch, t->name)) goto needs_restart; - } SMARTLIST_FOREACH_END(t_t_l); + } SMARTLIST_FOREACH_END(t); return 0; @@ -245,6 +462,7 @@ 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; @@ -255,16 +473,17 @@ proxy_prepare_for_restart(managed_proxy_t *mp) tor_process_handle_destroy(mp->process_handle, 1); mp->process_handle = NULL; - /* destroy all its old transports. we no longer use them. */ - SMARTLIST_FOREACH_BEGIN(mp->transports, const char *, t_name) { - t_tmp = transport_get_by_name(t_name); + /* destroy all its registered transports, since we will no longer + use them. */ + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + t_tmp = transport_get_by_name(t->name); if (t_tmp) t_tmp->marked_for_removal = 1; - } SMARTLIST_FOREACH_END(t_name); + } SMARTLIST_FOREACH_END(t); sweep_transport_list(); - /* free the transport names in mp->transports */ - SMARTLIST_FOREACH(mp->transports, char *, t_name, tor_free(t_name)); + /* free the transport in mp->transports */ + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); smartlist_clear(mp->transports); /* flag it as an infant proxy so that it gets launched on next tick */ @@ -315,6 +534,7 @@ launch_managed_proxy(managed_proxy_t *mp) void pt_configure_remaining_proxies(void) { + int at_least_a_proxy_config_finished = 0; smartlist_t *tmp = smartlist_new(); log_debug(LD_CONFIG, "Configuring remaining managed proxies (%d)!", @@ -352,11 +572,17 @@ pt_configure_remaining_proxies(void) if (!proxy_configuration_finished(mp)) configure_proxy(mp); + if (proxy_configuration_finished(mp)) + at_least_a_proxy_config_finished = 1; + } SMARTLIST_FOREACH_END(mp); smartlist_free(tmp); check_if_restarts_needed = 0; assert_unconfigured_count_ok(); + + if (at_least_a_proxy_config_finished) + mark_my_descriptor_dirty("configured managed proxies"); } #ifdef _WIN32 @@ -468,68 +694,48 @@ configure_proxy(managed_proxy_t *mp) /** Register server managed proxy <b>mp</b> transports to state */ static void -register_server_proxy(managed_proxy_t *mp) +register_server_proxy(const managed_proxy_t *mp) { - /* After we register this proxy's transports, we switch its - mp->transports to a list containing strings of its transport - names. (See transports.h) */ - smartlist_t *sm_tmp = smartlist_new(); - tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { save_transport_to_state(t->name, &t->addr, t->port); log_notice(LD_GENERAL, "Registered server transport '%s' at '%s:%d'", t->name, fmt_addr(&t->addr), (int)t->port); - smartlist_add(sm_tmp, tor_strdup(t->name)); } SMARTLIST_FOREACH_END(t); - - /* Since server proxies don't register their transports in the - circuitbuild.c subsystem, it's our duty to free them when we - switch mp->transports to strings. */ - SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); - smartlist_free(mp->transports); - - mp->transports = sm_tmp; } /** Register all the transports supported by client managed proxy * <b>mp</b> to the bridge subsystem. */ static void -register_client_proxy(managed_proxy_t *mp) +register_client_proxy(const managed_proxy_t *mp) { int r; - /* After we register this proxy's transports, we switch its - mp->transports to a list containing strings of its transport - names. (See transports.h) */ - smartlist_t *sm_tmp = smartlist_new(); tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { - r = transport_add(t); + transport_t *transport_tmp = transport_copy(t); + r = transport_add(transport_tmp); switch (r) { case -1: log_notice(LD_GENERAL, "Could not add transport %s. Skipping.", t->name); - transport_free(t); + transport_free(transport_tmp); break; case 0: log_info(LD_GENERAL, "Succesfully registered transport %s", t->name); - smartlist_add(sm_tmp, tor_strdup(t->name)); break; case 1: log_info(LD_GENERAL, "Succesfully registered transport %s", t->name); - smartlist_add(sm_tmp, tor_strdup(t->name)); - transport_free(t); + transport_free(transport_tmp); break; } } SMARTLIST_FOREACH_END(t); - - smartlist_free(mp->transports); - mp->transports = sm_tmp; } /** Register the transports of managed proxy <b>mp</b>. */ static INLINE void -register_proxy(managed_proxy_t *mp) +register_proxy(const managed_proxy_t *mp) { if (mp->is_server) register_server_proxy(mp); @@ -542,10 +748,7 @@ static void managed_proxy_destroy(managed_proxy_t *mp, int also_terminate_process) { - if (mp->conf_state != PT_PROTO_COMPLETED) - SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); - else - SMARTLIST_FOREACH(mp->transports, char *, t_name, tor_free(t_name)); + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); /* free the transports smartlist */ smartlist_free(mp->transports); @@ -1181,6 +1384,64 @@ pt_prepare_proxy_list_for_config_read(void) tor_assert(unconfigured_proxies_n == 0); } +/** Return the pluggable transport string that we should display in + * our extra-info descriptor. If we shouldn't display such a string, + * or we have nothing to display, return NULL. The string is + * allocated on the heap and it's the responsibility of the caller to + * free it. */ +char * +pt_get_extra_info_descriptor_string(void) +{ + char *the_string = NULL; + smartlist_t *string_chunks = NULL; + + if (!managed_proxy_list) + return NULL; + + string_chunks = smartlist_new(); + + /* For each managed proxy, add its transports to the chunks list. */ + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, const managed_proxy_t *, mp) { + if ((!mp->is_server) || (mp->conf_state != PT_PROTO_COMPLETED)) + continue; + + tor_assert(mp->transports); + + SMARTLIST_FOREACH_BEGIN(mp->transports, const transport_t *, t) { + /* If the transport proxy returned "0.0.0.0" as its address, and + * we know our external IP address, use it. Otherwise, use the + * returned address. */ + const char *addr_str = fmt_addr(&t->addr); + uint32_t external_ip_address = 0; + if (tor_addr_is_null(&t->addr) && + router_pick_published_address(get_options(), + &external_ip_address) >= 0) { + /* returned addr was 0.0.0.0 and we found our external IP + address: use it. */ + addr_str = fmt_addr32(external_ip_address); + } + + smartlist_add_asprintf(string_chunks, + "transport %s %s:%u", + t->name, addr_str, t->port); + } SMARTLIST_FOREACH_END(t); + + } SMARTLIST_FOREACH_END(mp); + + if (smartlist_len(string_chunks) == 0) { + smartlist_free(string_chunks); + return NULL; + } + + /* Join all the chunks into the final string. */ + the_string = smartlist_join_strings(string_chunks, "\n", 1, NULL); + + SMARTLIST_FOREACH(string_chunks, char *, s, tor_free(s)); + smartlist_free(string_chunks); + + return the_string; +} + /** The tor config was read. * Destroy all managed proxies that were marked by a previous call to * prepare_proxy_list_for_config_read() and are not used by the new @@ -1204,6 +1465,12 @@ sweep_proxy_list(void) void pt_free_all(void) { + if (transport_list) { + clear_transport_list(); + smartlist_free(transport_list); + transport_list = NULL; + } + if (managed_proxy_list) { /* If the proxy is in PT_PROTO_COMPLETED, it has registered its transports and it's the duty of the circuitbuild.c subsystem to diff --git a/src/or/transports.h b/src/or/transports.h index 02f159a5d6..3fd97f8c2a 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -11,6 +11,30 @@ #ifndef TOR_TRANSPORTS_H #define TOR_TRANSPORTS_H +/** Represents a pluggable transport used by a bridge. */ +typedef struct transport_t { + /** SOCKS version: One of PROXY_SOCKS4, PROXY_SOCKS5. */ + int socks_version; + /** Name of pluggable transport protocol */ + char *name; + /** The IP address where the transport bound and is waiting for + * connections. */ + tor_addr_t addr; + /** Port of proxy */ + uint16_t port; + /** Boolean: We are re-parsing our transport list, and we are going to remove + * this one if we don't find it in the list of configured transports. */ + unsigned marked_for_removal : 1; +} transport_t; + +void mark_transport_list(void); +void sweep_transport_list(void); +int transport_add_from_config(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver); +void transport_free(transport_t *transport); + +transport_t *transport_get_by_name(const char *name); + void pt_kickstart_proxy(const smartlist_t *transport_list, char **proxy_argv, int is_server); @@ -23,6 +47,8 @@ void pt_configure_remaining_proxies(void); int pt_proxies_configuration_pending(void); +char *pt_get_extra_info_descriptor_string(void); + void pt_free_all(void); void pt_prepare_proxy_list_for_config_read(void); @@ -68,28 +94,7 @@ typedef struct { smartlist_t *transports_to_launch; /* The 'transports' list contains all the transports this proxy has - launched. - - Before a managed_proxy_t reaches the PT_PROTO_COMPLETED phase, - this smartlist contains a 'transport_t' for every transport it - has launched. - - When the managed_proxy_t reaches the PT_PROTO_COMPLETED phase, it - registers all its transports to the circuitbuild.c subsystem. At - that point the 'transport_t's are owned by the circuitbuild.c - subsystem. - - To avoid carrying dangling 'transport_t's in this smartlist, - right before the managed_proxy_t reaches the PT_PROTO_COMPLETED - phase we replace all 'transport_t's with strings of their - transport names. - - So, tl;dr: - When (conf_state != PT_PROTO_COMPLETED) this list carries - (transport_t *). - When (conf_state == PT_PROTO_COMPLETED) this list carries - (char *). - */ + launched. */ smartlist_t *transports; } managed_proxy_t; |