diff options
Diffstat (limited to 'src/or')
108 files changed, 29718 insertions, 18170 deletions
diff --git a/src/or/Makefile.am b/src/or/Makefile.am deleted file mode 100644 index 3cc789a1b..000000000 --- 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/Makefile.nmake b/src/or/Makefile.nmake index 3181e79c2..677618e74 100644 --- a/src/or/Makefile.nmake +++ b/src/or/Makefile.nmake @@ -8,15 +8,15 @@ LIBS = ..\..\..\build-alpha\lib\libevent.a \ ..\..\..\build-alpha\lib\libz.a \ ws2_32.lib advapi32.lib shell32.lib -LIBTOR_OBJECTS = buffers.obj circuitbuild.obj circuitlist.obj circuituse.obj \ +LIBTOR_OBJECTS = buffers.obj channel.obj channeltls.obj circuitbuild.obj \ + circuitlist.obj circuitmux.obj circuitmux_ewma.obj circuituse.obj \ command.obj config.obj connection.obj connection_edge.obj \ connection_or.obj control.obj cpuworker.obj directory.obj \ - dirserv.obj dirvote.obj dns.obj dnsserv.obj geoip.obj \ - hibernate.obj main.obj microdesc.obj networkstatus.obj \ - nodelist.obj onion.obj policies.obj reasons.obj relay.obj \ - rendclient.obj rendcommon.obj rendmid.obj rendservice.obj \ - rephist.obj router.obj routerlist.obj routerparse.obj status.obj \ - config_codedigest.obj ntmain.obj + dirserv.obj dirvote.obj dns.obj dnsserv.obj geoip.obj hibernate.obj \ + main.obj microdesc.obj networkstatus.obj nodelist.obj onion.obj \ + policies.obj reasons.obj relay.obj rendclient.obj rendcommon.obj \ + rendmid.obj rendservice.obj rephist.obj router.obj routerlist.obj \ + routerparse.obj status.obj config_codedigest.obj ntmain.obj libtor.lib: $(LIBTOR_OBJECTS) lib $(LIBTOR_OBJECTS) /out:libtor.lib diff --git a/src/or/addressmap.c b/src/or/addressmap.c new file mode 100644 index 000000000..f4c31295a --- /dev/null +++ b/src/or/addressmap.c @@ -0,0 +1,1078 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define ADDRESSMAP_PRIVATE + +#include "or.h" +#include "addressmap.h" +#include "circuituse.h" +#include "config.h" +#include "connection_edge.h" +#include "control.h" +#include "dns.h" +#include "routerset.h" +#include "nodelist.h" + +/** A client-side struct to remember requests to rewrite addresses + * to new addresses. These structs are stored in the hash table + * "addressmap" below. + * + * There are 5 ways to set an address mapping: + * - A MapAddress command from the controller [permanent] + * - An AddressMap directive in the torrc [permanent] + * - When a TrackHostExits torrc directive is triggered [temporary] + * - When a DNS resolve succeeds [temporary] + * - When a DNS resolve fails [temporary] + * + * When an addressmap request is made but one is already registered, + * the new one is replaced only if the currently registered one has + * no "new_address" (that is, it's in the process of DNS resolve), + * or if the new one is permanent (expires==0 or 1). + * + * (We overload the 'expires' field, using "0" for mappings set via + * the configuration file, "1" for mappings set from the control + * interface, and other values for DNS and TrackHostExit mappings that can + * expire.) + * + * A mapping may be 'wildcarded'. If "src_wildcard" is true, then + * any address that ends with a . followed by the key for this entry will + * get remapped by it. If "dst_wildcard" is also true, then only the + * matching suffix of such addresses will get replaced by new_address. + */ +typedef struct { + char *new_address; + time_t expires; + addressmap_entry_source_t source:3; + unsigned src_wildcard:1; + unsigned dst_wildcard:1; + short num_resolve_failures; +} addressmap_entry_t; + +/** Entry for mapping addresses to which virtual address we mapped them to. */ +typedef struct { + char *ipv4_address; + char *ipv6_address; + char *hostname_address; +} virtaddress_entry_t; + +/** A hash table to store client-side address rewrite instructions. */ +static strmap_t *addressmap=NULL; + +/** + * Table mapping addresses to which virtual address, if any, we + * assigned them to. + * + * We maintain the following invariant: if [A,B] is in + * virtaddress_reversemap, then B must be a virtual address, and [A,B] + * must be in addressmap. We do not require that the converse hold: + * if it fails, then we could end up mapping two virtual addresses to + * the same address, which is no disaster. + **/ +static strmap_t *virtaddress_reversemap=NULL; + +/** Initialize addressmap. */ +void +addressmap_init(void) +{ + addressmap = strmap_new(); + virtaddress_reversemap = strmap_new(); +} + +/** Free the memory associated with the addressmap entry <b>_ent</b>. */ +static void +addressmap_ent_free(void *_ent) +{ + addressmap_entry_t *ent; + if (!_ent) + return; + + ent = _ent; + tor_free(ent->new_address); + tor_free(ent); +} + +/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ +static void +addressmap_virtaddress_ent_free(void *_ent) +{ + virtaddress_entry_t *ent; + if (!_ent) + return; + + ent = _ent; + tor_free(ent->ipv4_address); + tor_free(ent->hostname_address); + tor_free(ent); +} + +/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ +static void +addressmap_virtaddress_remove(const char *address, addressmap_entry_t *ent) +{ + if (ent && ent->new_address && + address_is_in_virtual_range(ent->new_address)) { + virtaddress_entry_t *ve = + strmap_get(virtaddress_reversemap, ent->new_address); + /*log_fn(LOG_NOTICE,"remove reverse mapping for %s",ent->new_address);*/ + if (ve) { + if (!strcmp(address, ve->ipv4_address)) + tor_free(ve->ipv4_address); + if (!strcmp(address, ve->hostname_address)) + tor_free(ve->hostname_address); + if (!ve->ipv4_address && !ve->hostname_address) { + tor_free(ve); + strmap_remove(virtaddress_reversemap, ent->new_address); + } + } + } +} + +/** Remove <b>ent</b> (which must be mapped to by <b>address</b>) from the + * client address maps. */ +static void +addressmap_ent_remove(const char *address, addressmap_entry_t *ent) +{ + addressmap_virtaddress_remove(address, ent); + addressmap_ent_free(ent); +} + +/** Unregister all TrackHostExits mappings from any address to + * *.exitname.exit. */ +void +clear_trackexithost_mappings(const char *exitname) +{ + char *suffix = NULL; + if (!addressmap || !exitname) + return; + tor_asprintf(&suffix, ".%s.exit", exitname); + tor_strlower(suffix); + + STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { + if (ent->source == ADDRMAPSRC_TRACKEXIT && + !strcmpend(ent->new_address, suffix)) { + addressmap_ent_remove(address, ent); + MAP_DEL_CURRENT(address); + } + } STRMAP_FOREACH_END; + + tor_free(suffix); +} + +/** Remove all TRACKEXIT mappings from the addressmap for which the target + * host is unknown or no longer allowed, or for which the source address + * is no longer in trackexithosts. */ +void +addressmap_clear_excluded_trackexithosts(const or_options_t *options) +{ + const routerset_t *allow_nodes = options->ExitNodes; + const routerset_t *exclude_nodes = options->ExcludeExitNodesUnion_; + + if (!addressmap) + return; + if (routerset_is_empty(allow_nodes)) + allow_nodes = NULL; + if (allow_nodes == NULL && routerset_is_empty(exclude_nodes)) + return; + + STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { + size_t len; + const char *target = ent->new_address, *dot; + char *nodename; + const node_t *node; + + if (!target) { + /* DNS resolving in progress */ + continue; + } else if (strcmpend(target, ".exit")) { + /* Not a .exit mapping */ + continue; + } else if (ent->source != ADDRMAPSRC_TRACKEXIT) { + /* Not a trackexit mapping. */ + continue; + } + len = strlen(target); + if (len < 6) + continue; /* malformed. */ + dot = target + len - 6; /* dot now points to just before .exit */ + while (dot > target && *dot != '.') + dot--; + if (*dot == '.') dot++; + nodename = tor_strndup(dot, len-5-(dot-target));; + node = node_get_by_nickname(nodename, 0); + tor_free(nodename); + if (!node || + (allow_nodes && !routerset_contains_node(allow_nodes, node)) || + routerset_contains_node(exclude_nodes, node) || + !hostname_in_track_host_exits(options, address)) { + /* We don't know this one, or we want to be rid of it. */ + addressmap_ent_remove(address, ent); + MAP_DEL_CURRENT(address); + } + } STRMAP_FOREACH_END; +} + +/** Return true iff <b>address</b> is one that we are configured to + * automap on resolve according to <b>options</b>. */ +int +addressmap_address_should_automap(const char *address, + const or_options_t *options) +{ + const smartlist_t *suffix_list = options->AutomapHostsSuffixes; + + if (!suffix_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(suffix_list, const char *, suffix) { + if (!strcasecmpend(address, suffix)) + return 1; + } SMARTLIST_FOREACH_END(suffix); + return 0; +} + +/** Remove all AUTOMAP mappings from the addressmap for which the + * source address no longer matches AutomapHostsSuffixes, which is + * no longer allowed by AutomapHostsOnResolve, or for which the + * target address is no longer in the virtual network. */ +void +addressmap_clear_invalid_automaps(const or_options_t *options) +{ + int clear_all = !options->AutomapHostsOnResolve; + const smartlist_t *suffixes = options->AutomapHostsSuffixes; + + if (!addressmap) + return; + + if (!suffixes) + clear_all = 1; /* This should be impossible, but let's be sure. */ + + STRMAP_FOREACH_MODIFY(addressmap, src_address, addressmap_entry_t *, ent) { + int remove = clear_all; + if (ent->source != ADDRMAPSRC_AUTOMAP) + continue; /* not an automap mapping. */ + + if (!remove) { + remove = ! addressmap_address_should_automap(src_address, options); + } + + if (!remove && ! address_is_in_virtual_range(ent->new_address)) + remove = 1; + + if (remove) { + addressmap_ent_remove(src_address, ent); + MAP_DEL_CURRENT(src_address); + } + } STRMAP_FOREACH_END; +} + +/** Remove all entries from the addressmap that were set via the + * configuration file or the command line. */ +void +addressmap_clear_configured(void) +{ + addressmap_get_mappings(NULL, 0, 0, 0); +} + +/** Remove all entries from the addressmap that are set to expire, ever. */ +void +addressmap_clear_transient(void) +{ + addressmap_get_mappings(NULL, 2, TIME_MAX, 0); +} + +/** Clean out entries from the addressmap cache that were + * added long enough ago that they are no longer valid. + */ +void +addressmap_clean(time_t now) +{ + addressmap_get_mappings(NULL, 2, now, 0); +} + +/** Free all the elements in the addressmap, and free the addressmap + * itself. */ +void +addressmap_free_all(void) +{ + strmap_free(addressmap, addressmap_ent_free); + addressmap = NULL; + + strmap_free(virtaddress_reversemap, addressmap_virtaddress_ent_free); + virtaddress_reversemap = NULL; +} + +/** Try to find a match for AddressMap expressions that use + * wildcard notation such as '*.c.d *.e.f' (so 'a.c.d' will map to 'a.e.f') or + * '*.c.d a.b.c' (so 'a.c.d' will map to a.b.c). + * Return the matching entry in AddressMap or NULL if no match is found. + * For expressions such as '*.c.d *.e.f', truncate <b>address</b> 'a.c.d' + * to 'a' before we return the matching AddressMap entry. + * + * This function does not handle the case where a pattern of the form "*.c.d" + * matches the address c.d -- that's done by the main addressmap_rewrite + * function. + */ +static addressmap_entry_t * +addressmap_match_superdomains(char *address) +{ + addressmap_entry_t *val; + char *cp; + + cp = address; + while ((cp = strchr(cp, '.'))) { + /* cp now points to a suffix of address that begins with a . */ + val = strmap_get_lc(addressmap, cp+1); + if (val && val->src_wildcard) { + if (val->dst_wildcard) + *cp = '\0'; + return val; + } + ++cp; + } + return NULL; +} + +/** Look at address, and rewrite it until it doesn't want any + * more rewrites; but don't get into an infinite loop. + * Don't write more than maxlen chars into address. Return true if the + * address changed; false otherwise. Set *<b>expires_out</b> to the + * expiry time of the result, or to <b>time_max</b> if the result does + * not expire. + * + * If <b>exit_source_out</b> is non-null, we set it as follows. If we the + * address starts out as a non-exit address, and we remap it to an .exit + * address at any point, then set *<b>exit_source_out</b> to the + * address_entry_source_t of the first such rule. Set *<b>exit_source_out</b> + * to ADDRMAPSRC_NONE if there is no such rewrite, or if the original address + * was a .exit. + */ +int +addressmap_rewrite(char *address, size_t maxlen, + unsigned flags, + time_t *expires_out, + addressmap_entry_source_t *exit_source_out) +{ + addressmap_entry_t *ent; + int rewrites; + time_t expires = TIME_MAX; + addressmap_entry_source_t exit_source = ADDRMAPSRC_NONE; + char *addr_orig = tor_strdup(address); + char *log_addr_orig = NULL; + + for (rewrites = 0; rewrites < 16; rewrites++) { + int exact_match = 0; + log_addr_orig = tor_strdup(escaped_safe_str_client(address)); + + ent = strmap_get(addressmap, address); + + if (!ent || !ent->new_address) { + ent = addressmap_match_superdomains(address); + } else { + if (ent->src_wildcard && !ent->dst_wildcard && + !strcasecmp(address, ent->new_address)) { + /* This is a rule like *.example.com example.com, and we just got + * "example.com" */ + goto done; + } + + exact_match = 1; + } + + if (!ent || !ent->new_address) { + goto done; + } + + if (ent && ent->source == ADDRMAPSRC_DNS) { + sa_family_t f; + tor_addr_t tmp; + f = tor_addr_parse(&tmp, ent->new_address); + if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) + goto done; + else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + goto done; + } + + if (ent->dst_wildcard && !exact_match) { + strlcat(address, ".", maxlen); + strlcat(address, ent->new_address, maxlen); + } else { + strlcpy(address, ent->new_address, maxlen); + } + + if (!strcmpend(address, ".exit") && + strcmpend(addr_orig, ".exit") && + exit_source == ADDRMAPSRC_NONE) { + exit_source = ent->source; + } + + log_info(LD_APP, "Addressmap: rewriting %s to %s", + log_addr_orig, escaped_safe_str_client(address)); + if (ent->expires > 1 && ent->expires < expires) + expires = ent->expires; + + tor_free(log_addr_orig); + } + log_warn(LD_CONFIG, + "Loop detected: we've rewritten %s 16 times! Using it as-is.", + escaped_safe_str_client(address)); + /* it's fine to rewrite a rewrite, but don't loop forever */ + + done: + tor_free(addr_orig); + tor_free(log_addr_orig); + if (exit_source_out) + *exit_source_out = exit_source; + if (expires_out) + *expires_out = TIME_MAX; + return (rewrites > 0); +} + +/** If we have a cached reverse DNS entry for the address stored in the + * <b>maxlen</b>-byte buffer <b>address</b> (typically, a dotted quad) then + * rewrite to the cached value and return 1. Otherwise return 0. Set + * *<b>expires_out</b> to the expiry time of the result, or to <b>time_max</b> + * if the result does not expire. */ +int +addressmap_rewrite_reverse(char *address, size_t maxlen, unsigned flags, + time_t *expires_out) +{ + char *s, *cp; + addressmap_entry_t *ent; + int r = 0; + { + sa_family_t f; + tor_addr_t tmp; + f = tor_addr_parse(&tmp, address); + if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) + return 0; + else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + return 0; + } + + tor_asprintf(&s, "REVERSE[%s]", address); + ent = strmap_get(addressmap, s); + if (ent) { + cp = tor_strdup(escaped_safe_str_client(ent->new_address)); + log_info(LD_APP, "Rewrote reverse lookup %s -> %s", + escaped_safe_str_client(s), cp); + tor_free(cp); + strlcpy(address, ent->new_address, maxlen); + r = 1; + } + + if (expires_out) + *expires_out = (ent && ent->expires > 1) ? ent->expires : TIME_MAX; + + tor_free(s); + return r; +} + +/** Return 1 if <b>address</b> is already registered, else return 0. If address + * is already registered, and <b>update_expires</b> is non-zero, then update + * the expiry time on the mapping with update_expires if it is a + * mapping created by TrackHostExits. */ +int +addressmap_have_mapping(const char *address, int update_expiry) +{ + addressmap_entry_t *ent; + if (!(ent=strmap_get_lc(addressmap, address))) + return 0; + if (update_expiry && ent->source==ADDRMAPSRC_TRACKEXIT) + ent->expires=time(NULL) + update_expiry; + return 1; +} + +/** Register a request to map <b>address</b> to <b>new_address</b>, + * which will expire on <b>expires</b> (or 0 if never expires from + * config file, 1 if never expires from controller, 2 if never expires + * (virtual address mapping) from the controller.) + * + * <b>new_address</b> should be a newly dup'ed string, which we'll use or + * free as appropriate. We will leave address alone. + * + * If <b>wildcard_addr</b> is true, then the mapping will match any address + * equal to <b>address</b>, or any address ending with a period followed by + * <b>address</b>. If <b>wildcard_addr</b> and <b>wildcard_new_addr</b> are + * both true, the mapping will rewrite addresses that end with + * ".<b>address</b>" into ones that end with ".<b>new_address</b>." + * + * If <b>new_address</b> is NULL, or <b>new_address</b> is equal to + * <b>address</b> and <b>wildcard_addr</b> is equal to + * <b>wildcard_new_addr</b>, remove any mappings that exist from + * <b>address</b>. + * + * + * It is an error to set <b>wildcard_new_addr</b> if <b>wildcard_addr</b> is + * not set. */ +void +addressmap_register(const char *address, char *new_address, time_t expires, + addressmap_entry_source_t source, + const int wildcard_addr, + const int wildcard_new_addr) +{ + addressmap_entry_t *ent; + + if (wildcard_new_addr) + tor_assert(wildcard_addr); + + ent = strmap_get(addressmap, address); + if (!new_address || (!strcasecmp(address,new_address) && + wildcard_addr == wildcard_new_addr)) { + /* Remove the mapping, if any. */ + tor_free(new_address); + if (ent) { + addressmap_ent_remove(address,ent); + strmap_remove(addressmap, address); + } + return; + } + if (!ent) { /* make a new one and register it */ + ent = tor_malloc_zero(sizeof(addressmap_entry_t)); + strmap_set(addressmap, address, ent); + } else if (ent->new_address) { /* we need to clean up the old mapping. */ + if (expires > 1) { + log_info(LD_APP,"Temporary addressmap ('%s' to '%s') not performed, " + "since it's already mapped to '%s'", + safe_str_client(address), + safe_str_client(new_address), + safe_str_client(ent->new_address)); + tor_free(new_address); + return; + } + if (address_is_in_virtual_range(ent->new_address) && + expires != 2) { + /* XXX This isn't the perfect test; we want to avoid removing + * mappings set from the control interface _as virtual mapping */ + addressmap_virtaddress_remove(address, ent); + } + tor_free(ent->new_address); + } /* else { we have an in-progress resolve with no mapping. } */ + + ent->new_address = new_address; + ent->expires = expires==2 ? 1 : expires; + ent->num_resolve_failures = 0; + ent->source = source; + ent->src_wildcard = wildcard_addr ? 1 : 0; + ent->dst_wildcard = wildcard_new_addr ? 1 : 0; + + log_info(LD_CONFIG, "Addressmap: (re)mapped '%s' to '%s'", + safe_str_client(address), + safe_str_client(ent->new_address)); + control_event_address_mapped(address, ent->new_address, expires, NULL); +} + +/** An attempt to resolve <b>address</b> failed at some OR. + * Increment the number of resolve failures we have on record + * for it, and then return that number. + */ +int +client_dns_incr_failures(const char *address) +{ + addressmap_entry_t *ent = strmap_get(addressmap, address); + if (!ent) { + ent = tor_malloc_zero(sizeof(addressmap_entry_t)); + ent->expires = time(NULL) + MAX_DNS_ENTRY_AGE; + strmap_set(addressmap,address,ent); + } + if (ent->num_resolve_failures < SHORT_MAX) + ++ent->num_resolve_failures; /* don't overflow */ + log_info(LD_APP, "Address %s now has %d resolve failures.", + safe_str_client(address), + ent->num_resolve_failures); + return ent->num_resolve_failures; +} + +/** If <b>address</b> is in the client DNS addressmap, reset + * the number of resolve failures we have on record for it. + * This is used when we fail a stream because it won't resolve: + * otherwise future attempts on that address will only try once. + */ +void +client_dns_clear_failures(const char *address) +{ + addressmap_entry_t *ent = strmap_get(addressmap, address); + if (ent) + ent->num_resolve_failures = 0; +} + +/** Record the fact that <b>address</b> resolved to <b>name</b>. + * We can now use this in subsequent streams via addressmap_rewrite() + * so we can more correctly choose an exit that will allow <b>address</b>. + * + * If <b>exitname</b> is defined, then append the addresses with + * ".exitname.exit" before registering the mapping. + * + * If <b>ttl</b> is nonnegative, the mapping will be valid for + * <b>ttl</b>seconds; otherwise, we use the default. + */ +static void +client_dns_set_addressmap_impl(entry_connection_t *for_conn, + const char *address, const char *name, + const char *exitname, + int ttl) +{ + char *extendedaddress=NULL, *extendedval=NULL; + (void)for_conn; + + tor_assert(address); + tor_assert(name); + + if (ttl<0) + ttl = DEFAULT_DNS_TTL; + else + ttl = dns_clip_ttl(ttl); + + if (exitname) { + /* XXXX fails to ever get attempts to get an exit address of + * google.com.digest[=~]nickname.exit; we need a syntax for this that + * won't make strict RFC952-compliant applications (like us) barf. */ + tor_asprintf(&extendedaddress, + "%s.%s.exit", address, exitname); + tor_asprintf(&extendedval, + "%s.%s.exit", name, exitname); + } else { + tor_asprintf(&extendedaddress, + "%s", address); + tor_asprintf(&extendedval, + "%s", name); + } + addressmap_register(extendedaddress, extendedval, + time(NULL) + ttl, ADDRMAPSRC_DNS, 0, 0); + tor_free(extendedaddress); +} + +/** Record the fact that <b>address</b> resolved to <b>val</b>. + * We can now use this in subsequent streams via addressmap_rewrite() + * so we can more correctly choose an exit that will allow <b>address</b>. + * + * If <b>exitname</b> is defined, then append the addresses with + * ".exitname.exit" before registering the mapping. + * + * If <b>ttl</b> is nonnegative, the mapping will be valid for + * <b>ttl</b>seconds; otherwise, we use the default. + */ +void +client_dns_set_addressmap(entry_connection_t *for_conn, + const char *address, + const tor_addr_t *val, + const char *exitname, + int ttl) +{ + tor_addr_t addr_tmp; + char valbuf[TOR_ADDR_BUF_LEN]; + + tor_assert(address); + tor_assert(val); + + if (tor_addr_parse(&addr_tmp, address) >= 0) + return; /* If address was an IP address already, don't add a mapping. */ + + if (tor_addr_family(val) == AF_INET) { + if (! for_conn->cache_ipv4_answers) + return; + } else if (tor_addr_family(val) == AF_INET6) { + if (! for_conn->cache_ipv6_answers) + return; + } + + if (! tor_addr_to_str(valbuf, val, sizeof(valbuf), 1)) + return; + + client_dns_set_addressmap_impl(for_conn, address, valbuf, exitname, ttl); +} + +/** Add a cache entry noting that <b>address</b> (ordinarily a dotted quad) + * resolved via a RESOLVE_PTR request to the hostname <b>v</b>. + * + * If <b>exitname</b> is defined, then append the addresses with + * ".exitname.exit" before registering the mapping. + * + * If <b>ttl</b> is nonnegative, the mapping will be valid for + * <b>ttl</b>seconds; otherwise, we use the default. + */ +void +client_dns_set_reverse_addressmap(entry_connection_t *for_conn, + const char *address, const char *v, + const char *exitname, + int ttl) +{ + char *s = NULL; + { + tor_addr_t tmp_addr; + sa_family_t f = tor_addr_parse(&tmp_addr, address); + if ((f == AF_INET && ! for_conn->cache_ipv4_answers) || + (f == AF_INET6 && ! for_conn->cache_ipv6_answers)) + return; + } + tor_asprintf(&s, "REVERSE[%s]", address); + client_dns_set_addressmap_impl(for_conn, s, v, exitname, ttl); + tor_free(s); +} + +/* By default, we hand out 127.192.0.1 through 127.254.254.254. + * These addresses should map to localhost, so even if the + * application accidentally tried to connect to them directly (not + * via Tor), it wouldn't get too far astray. + * + * These options are configured by parse_virtual_addr_network(). + */ + +static virtual_addr_conf_t virtaddr_conf_ipv4; +static virtual_addr_conf_t virtaddr_conf_ipv6; + +/** Read a netmask of the form 127.192.0.0/10 from "val", and check whether + * it's a valid set of virtual addresses to hand out in response to MAPADDRESS + * requests. Return 0 on success; set *msg (if provided) to a newly allocated + * string and return -1 on failure. If validate_only is false, sets the + * actual virtual address range to the parsed value. */ +int +parse_virtual_addr_network(const char *val, sa_family_t family, + int validate_only, + char **msg) +{ + const int ipv6 = (family == AF_INET6); + tor_addr_t addr; + maskbits_t bits; + const int max_bits = ipv6 ? 40 : 16; + virtual_addr_conf_t *conf = ipv6 ? &virtaddr_conf_ipv6 : &virtaddr_conf_ipv4; + + if (tor_addr_parse_mask_ports(val, 0, &addr, &bits, NULL, NULL) < 0) { + if (msg) + tor_asprintf(msg, "Error parsing VirtualAddressNetwork%s %s", + ipv6?"IPv6":"", val); + return -1; + } + if (tor_addr_family(&addr) != family) { + if (msg) + tor_asprintf(msg, "Incorrect address type for VirtualAddressNetwork%s", + ipv6?"IPv6":""); + return -1; + } +#if 0 + if (port_min != 1 || port_max != 65535) { + if (msg) + tor_asprintf(msg, "Can't specify ports on VirtualAddressNetwork%s", + ipv6?"IPv6":""); + return -1; + } +#endif + + if (bits > max_bits) { + if (msg) + tor_asprintf(msg, "VirtualAddressNetwork%s expects a /%d " + "network or larger",ipv6?"IPv6":"", max_bits); + return -1; + } + + if (validate_only) + return 0; + + tor_addr_copy(&conf->addr, &addr); + conf->bits = bits; + + return 0; +} + +/** + * Return true iff <b>addr</b> is likely to have been returned by + * client_dns_get_unused_address. + **/ +int +address_is_in_virtual_range(const char *address) +{ + tor_addr_t addr; + tor_assert(address); + if (!strcasecmpend(address, ".virtual")) { + return 1; + } else if (tor_addr_parse(&addr, address) >= 0) { + const virtual_addr_conf_t *conf = (tor_addr_family(&addr) == AF_INET6) ? + &virtaddr_conf_ipv6 : &virtaddr_conf_ipv4; + if (tor_addr_compare_masked(&addr, &conf->addr, conf->bits, CMP_EXACT)==0) + return 1; + } + return 0; +} + +/** Return a random address conforming to the virtual address configuration + * in <b>conf</b>. + */ +/* private */ void +get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out) +{ + uint8_t tmp[4]; + const uint8_t *addr_bytes; + uint8_t bytes[16]; + const int ipv6 = tor_addr_family(&conf->addr) == AF_INET6; + const int total_bytes = ipv6 ? 16 : 4; + + tor_assert(conf->bits <= total_bytes * 8); + + /* Set addr_bytes to the bytes of the virtual network, in host order */ + if (ipv6) { + addr_bytes = tor_addr_to_in6_addr8(&conf->addr); + } else { + set_uint32(tmp, tor_addr_to_ipv4n(&conf->addr)); + addr_bytes = tmp; + } + + /* Get an appropriate number of random bytes. */ + crypto_rand((char*)bytes, total_bytes); + + /* Now replace the first "conf->bits" bits of 'bytes' with addr_bytes*/ + if (conf->bits >= 8) + memcpy(bytes, addr_bytes, conf->bits / 8); + if (conf->bits & 7) { + uint8_t mask = 0xff >> (conf->bits & 7); + bytes[conf->bits/8] &= mask; + bytes[conf->bits/8] |= addr_bytes[conf->bits/8] & ~mask; + } + + if (ipv6) + tor_addr_from_ipv6_bytes(addr_out, (char*) bytes); + else + tor_addr_from_ipv4n(addr_out, get_uint32(bytes)); + + tor_assert(tor_addr_compare_masked(addr_out, &conf->addr, + conf->bits, CMP_EXACT)==0); +} + +/** Return a newly allocated string holding an address of <b>type</b> + * (one of RESOLVED_TYPE_{IPV4|HOSTNAME}) that has not yet been mapped, + * and that is very unlikely to be the address of any real host. + * + * May return NULL if we have run out of virtual addresses. + */ +static char * +addressmap_get_virtual_address(int type) +{ + char buf[64]; + tor_assert(addressmap); + + if (type == RESOLVED_TYPE_HOSTNAME) { + char rand[10]; + do { + crypto_rand(rand, sizeof(rand)); + base32_encode(buf,sizeof(buf),rand,sizeof(rand)); + strlcat(buf, ".virtual", sizeof(buf)); + } while (strmap_get(addressmap, buf)); + return tor_strdup(buf); + } else if (type == RESOLVED_TYPE_IPV4 || type == RESOLVED_TYPE_IPV6) { + const int ipv6 = (type == RESOLVED_TYPE_IPV6); + const virtual_addr_conf_t *conf = ipv6 ? + &virtaddr_conf_ipv6 : &virtaddr_conf_ipv4; + + /* Don't try more than 1000 times. This gives us P < 1e-9 for + * failing to get a good address so long as the address space is + * less than ~97.95% full. That's always going to be true under + * sensible circumstances for an IPv6 /10, and it's going to be + * true for an IPv4 /10 as long as we've handed out less than + * 4.08 million addresses. */ + uint32_t attempts = 1000; + + tor_addr_t addr; + + while (attempts--) { + get_random_virtual_addr(conf, &addr); + + if (!ipv6) { + /* Don't hand out any .0 or .255 address. */ + const uint32_t a = tor_addr_to_ipv4h(&addr); + if ((a & 0xff) == 0 || (a & 0xff) == 0xff) + continue; + } + + tor_addr_to_str(buf, &addr, sizeof(buf), 1); + if (!strmap_get(addressmap, buf)) { + /* XXXX This code is to make sure I didn't add an undecorated version + * by mistake. I hope it's needless. */ + char tmp[TOR_ADDR_BUF_LEN]; + tor_addr_to_str(buf, &addr, sizeof(tmp), 0); + if (strmap_get(addressmap, tmp)) { + log_warn(LD_BUG, "%s wasn't in the addressmap, but %s was.", + buf, tmp); + continue; + } + + return tor_strdup(buf); + } + } + log_warn(LD_CONFIG, "Ran out of virtual addresses!"); + return NULL; + } else { + log_warn(LD_BUG, "Called with unsupported address type (%d)", type); + return NULL; + } +} + +/** A controller has requested that we map some address of type + * <b>type</b> to the address <b>new_address</b>. Choose an address + * that is unlikely to be used, and map it, and return it in a newly + * allocated string. If another address of the same type is already + * mapped to <b>new_address</b>, try to return a copy of that address. + * + * The string in <b>new_address</b> may be freed or inserted into a map + * as appropriate. May return NULL if are out of virtual addresses. + **/ +const char * +addressmap_register_virtual_address(int type, char *new_address) +{ + char **addrp; + virtaddress_entry_t *vent; + int vent_needs_to_be_added = 0; + + tor_assert(new_address); + tor_assert(addressmap); + tor_assert(virtaddress_reversemap); + + vent = strmap_get(virtaddress_reversemap, new_address); + if (!vent) { + vent = tor_malloc_zero(sizeof(virtaddress_entry_t)); + vent_needs_to_be_added = 1; + } + + if (type == RESOLVED_TYPE_IPV4) + addrp = &vent->ipv4_address; + else if (type == RESOLVED_TYPE_IPV6) + addrp = &vent->ipv6_address; + else + addrp = &vent->hostname_address; + + if (*addrp) { + addressmap_entry_t *ent = strmap_get(addressmap, *addrp); + if (ent && ent->new_address && + !strcasecmp(new_address, ent->new_address)) { + tor_free(new_address); + tor_assert(!vent_needs_to_be_added); + return tor_strdup(*addrp); + } else { + log_warn(LD_BUG, + "Internal confusion: I thought that '%s' was mapped to by " + "'%s', but '%s' really maps to '%s'. This is a harmless bug.", + safe_str_client(new_address), + safe_str_client(*addrp), + safe_str_client(*addrp), + ent?safe_str_client(ent->new_address):"(nothing)"); + } + } + + tor_free(*addrp); + *addrp = addressmap_get_virtual_address(type); + if (!*addrp) { + tor_free(vent); + tor_free(new_address); + return NULL; + } + log_info(LD_APP, "Registering map from %s to %s", *addrp, new_address); + if (vent_needs_to_be_added) + strmap_set(virtaddress_reversemap, new_address, vent); + addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0); + +#if 0 + { + /* Try to catch possible bugs */ + addressmap_entry_t *ent; + ent = strmap_get(addressmap, *addrp); + tor_assert(ent); + tor_assert(!strcasecmp(ent->new_address,new_address)); + vent = strmap_get(virtaddress_reversemap, new_address); + tor_assert(vent); + tor_assert(!strcasecmp(*addrp, + (type == RESOLVED_TYPE_IPV4) ? + vent->ipv4_address : vent->hostname_address)); + log_info(LD_APP, "Map from %s to %s okay.", + safe_str_client(*addrp), + safe_str_client(new_address)); + } +#endif + + return *addrp; +} + +/** Return 1 if <b>address</b> has funny characters in it like colons. Return + * 0 if it's fine, or if we're configured to allow it anyway. <b>client</b> + * should be true if we're using this address as a client; false if we're + * using it as a server. + */ +int +address_is_invalid_destination(const char *address, int client) +{ + if (client) { + if (get_options()->AllowNonRFC953Hostnames) + return 0; + } else { + if (get_options()->ServerDNSAllowNonRFC953Hostnames) + return 0; + } + + /* It might be an IPv6 address! */ + { + tor_addr_t a; + if (tor_addr_parse(&a, address) >= 0) + return 0; + } + + while (*address) { + if (TOR_ISALNUM(*address) || + *address == '-' || + *address == '.' || + *address == '_') /* Underscore is not allowed, but Windows does it + * sometimes, just to thumb its nose at the IETF. */ + ++address; + else + return 1; + } + return 0; +} + +/** Iterate over all address mappings which have expiry times between + * min_expires and max_expires, inclusive. If sl is provided, add an + * "old-addr new-addr expiry" string to sl for each mapping, omitting + * the expiry time if want_expiry is false. If sl is NULL, remove the + * mappings. + */ +void +addressmap_get_mappings(smartlist_t *sl, time_t min_expires, + time_t max_expires, int want_expiry) +{ + strmap_iter_t *iter; + const char *key; + void *val_; + addressmap_entry_t *val; + + if (!addressmap) + addressmap_init(); + + for (iter = strmap_iter_init(addressmap); !strmap_iter_done(iter); ) { + strmap_iter_get(iter, &key, &val_); + val = val_; + if (val->expires >= min_expires && val->expires <= max_expires) { + if (!sl) { + iter = strmap_iter_next_rmv(addressmap,iter); + addressmap_ent_remove(key, val); + continue; + } else if (val->new_address) { + const char *src_wc = val->src_wildcard ? "*." : ""; + const char *dst_wc = val->dst_wildcard ? "*." : ""; + if (want_expiry) { + if (val->expires < 3 || val->expires == TIME_MAX) + smartlist_add_asprintf(sl, "%s%s %s%s NEVER", + src_wc, key, dst_wc, val->new_address); + else { + char time[ISO_TIME_LEN+1]; + format_iso_time(time, val->expires); + smartlist_add_asprintf(sl, "%s%s %s%s \"%s\"", + src_wc, key, dst_wc, val->new_address, + time); + } + } else { + smartlist_add_asprintf(sl, "%s%s %s%s", + src_wc, key, dst_wc, val->new_address); + } + } + } + iter = strmap_iter_next(addressmap,iter); + } +} + diff --git a/src/or/addressmap.h b/src/or/addressmap.h new file mode 100644 index 000000000..3f1c44571 --- /dev/null +++ b/src/or/addressmap.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_ADDRESSMAP_H +#define TOR_ADDRESSMAP_H + +void addressmap_init(void); +void addressmap_clear_excluded_trackexithosts(const or_options_t *options); +void addressmap_clear_invalid_automaps(const or_options_t *options); +void addressmap_clean(time_t now); +void addressmap_clear_configured(void); +void addressmap_clear_transient(void); +void addressmap_free_all(void); +#define AMR_FLAG_USE_IPV4_DNS (1u<<0) +#define AMR_FLAG_USE_IPV6_DNS (1u<<1) +int addressmap_rewrite(char *address, size_t maxlen, unsigned flags, + time_t *expires_out, + addressmap_entry_source_t *exit_source_out); +int addressmap_rewrite_reverse(char *address, size_t maxlen, unsigned flags, + time_t *expires_out); +int addressmap_have_mapping(const char *address, int update_timeout); + +void addressmap_register(const char *address, char *new_address, + time_t expires, addressmap_entry_source_t source, + const int address_wildcard, + const int new_address_wildcard); +int parse_virtual_addr_network(const char *val, + sa_family_t family, int validate_only, + char **msg); +int client_dns_incr_failures(const char *address); +void client_dns_clear_failures(const char *address); +void client_dns_set_addressmap(entry_connection_t *for_conn, + const char *address, const tor_addr_t *val, + const char *exitname, int ttl); +const char *addressmap_register_virtual_address(int type, char *new_address); +void addressmap_get_mappings(smartlist_t *sl, time_t min_expires, + time_t max_expires, int want_expiry); +int address_is_in_virtual_range(const char *addr); +void clear_trackexithost_mappings(const char *exitname); +void client_dns_set_reverse_addressmap(entry_connection_t *for_conn, + const char *address, const char *v, + const char *exitname, int ttl); +int addressmap_address_should_automap(const char *address, + const or_options_t *options); + +#ifdef ADDRESSMAP_PRIVATE +typedef struct virtual_addr_conf_t { + tor_addr_t addr; + maskbits_t bits; +} virtual_addr_conf_t; + +void get_random_virtual_addr(const virtual_addr_conf_t *conf, + tor_addr_t *addr_out); +#endif + +#endif + diff --git a/src/or/buffers.c b/src/or/buffers.c index ad5ab83e4..3a1b4d54f 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -12,6 +12,7 @@ **/ #define BUFFERS_PRIVATE #include "or.h" +#include "addressmap.h" #include "buffers.h" #include "config.h" #include "connection_edge.h" @@ -192,8 +193,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 +215,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/buffers.h b/src/or/buffers.h index a5886adc7..f31d2910f 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -9,8 +9,8 @@ * \brief Header file for buffers.c. **/ -#ifndef _TOR_BUFFERS_H -#define _TOR_BUFFERS_H +#ifndef TOR_BUFFERS_H +#define TOR_BUFFERS_H buf_t *buf_new(void); buf_t *buf_new_with_capacity(size_t size); diff --git a/src/or/channel.c b/src/or/channel.c new file mode 100644 index 000000000..625d95781 --- /dev/null +++ b/src/or/channel.c @@ -0,0 +1,4094 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file channel.c + * \brief OR-to-OR channel abstraction layer + **/ + +/* + * Define this so channel.h gives us things only channel_t subclasses + * should touch. + */ + +#define TOR_CHANNEL_INTERNAL_ + +#include "or.h" +#include "channel.h" +#include "channeltls.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuitstats.h" +#include "connection_or.h" /* For var_cell_free() */ +#include "circuitmux.h" +#include "entrynodes.h" +#include "geoip.h" +#include "nodelist.h" +#include "relay.h" +#include "rephist.h" +#include "router.h" +#include "routerlist.h" + +/* Cell queue structure */ + +typedef struct cell_queue_entry_s cell_queue_entry_t; +struct cell_queue_entry_s { + SIMPLEQ_ENTRY(cell_queue_entry_s) next; + enum { + CELL_QUEUE_FIXED, + CELL_QUEUE_VAR, + CELL_QUEUE_PACKED + } type; + union { + struct { + cell_t *cell; + } fixed; + struct { + var_cell_t *var_cell; + } var; + struct { + packed_cell_t *packed_cell; + } packed; + } u; +}; + +/* Global lists of channels */ + +/* All channel_t instances */ +static smartlist_t *all_channels = NULL; + +/* All channel_t instances not in ERROR or CLOSED states */ +static smartlist_t *active_channels = NULL; + +/* All channel_t instances in ERROR or CLOSED states */ +static smartlist_t *finished_channels = NULL; + +/* All channel_listener_t instances */ +static smartlist_t *all_listeners = NULL; + +/* All channel_listener_t instances in LISTENING state */ +static smartlist_t *active_listeners = NULL; + +/* All channel_listener_t instances in LISTENING state */ +static smartlist_t *finished_listeners = NULL; + +/* Counter for ID numbers */ +static uint64_t n_channels_allocated = 0; + +/* Digest->channel map + * + * Similar to the one used in connection_or.c, this maps from the identity + * digest of a remote endpoint to a channel_t to that endpoint. Channels + * should be placed here when registered and removed when they close or error. + * If more than one channel exists, follow the next_with_same_id pointer + * as a linked list. + */ +HT_HEAD(channel_idmap, channel_idmap_entry_s) channel_identity_map = + HT_INITIALIZER(); + +typedef struct channel_idmap_entry_s { + HT_ENTRY(channel_idmap_entry_s) node; + uint8_t digest[DIGEST_LEN]; + LIST_HEAD(channel_list_s, channel_s) channel_list; +} channel_idmap_entry_t; + +static INLINE unsigned +channel_idmap_hash(const channel_idmap_entry_t *ent) +{ + const unsigned *a = (const unsigned *)ent->digest; +#if SIZEOF_INT == 4 + return a[0] ^ a[1] ^ a[2] ^ a[3] ^ a[4]; +#elif SIZEOF_INT == 8 + return a[0] ^ a[1]; +#endif +} + +static INLINE int +channel_idmap_eq(const channel_idmap_entry_t *a, + const channel_idmap_entry_t *b) +{ + return tor_memeq(a->digest, b->digest, DIGEST_LEN); +} + +HT_PROTOTYPE(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash, + channel_idmap_eq); +HT_GENERATE(channel_idmap, channel_idmap_entry_s, node, channel_idmap_hash, + channel_idmap_eq, 0.5, tor_malloc, tor_realloc, tor_free_); + +static cell_queue_entry_t * cell_queue_entry_dup(cell_queue_entry_t *q); +static void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); +static int cell_queue_entry_is_padding(cell_queue_entry_t *q); +static cell_queue_entry_t * +cell_queue_entry_new_fixed(cell_t *cell); +static cell_queue_entry_t * +cell_queue_entry_new_var(var_cell_t *var_cell); + +/* Functions to maintain the digest map */ +static void channel_add_to_digest_map(channel_t *chan); +static void channel_remove_from_digest_map(channel_t *chan); + +/* + * Flush cells from just the outgoing queue without trying to get them + * from circuits; used internall by channel_flush_some_cells(). + */ +static ssize_t +channel_flush_some_cells_from_outgoing_queue(channel_t *chan, + ssize_t num_cells); +static void channel_force_free(channel_t *chan); +static void +channel_free_list(smartlist_t *channels, int mark_for_close); +static void +channel_listener_free_list(smartlist_t *channels, int mark_for_close); +static void channel_listener_force_free(channel_listener_t *chan_l); +static void +channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q); + +/*********************************** + * Channel state utility functions * + **********************************/ + +/** + * Indicate whether a given channel state is valid + */ + +int +channel_state_is_valid(channel_state_t state) +{ + int is_valid; + + switch (state) { + case CHANNEL_STATE_CLOSED: + case CHANNEL_STATE_CLOSING: + case CHANNEL_STATE_ERROR: + case CHANNEL_STATE_MAINT: + case CHANNEL_STATE_OPENING: + case CHANNEL_STATE_OPEN: + is_valid = 1; + break; + case CHANNEL_STATE_LAST: + default: + is_valid = 0; + } + + return is_valid; +} + +/** + * Indicate whether a given channel listener state is valid + */ + +int +channel_listener_state_is_valid(channel_listener_state_t state) +{ + int is_valid; + + switch (state) { + case CHANNEL_LISTENER_STATE_CLOSED: + case CHANNEL_LISTENER_STATE_LISTENING: + case CHANNEL_LISTENER_STATE_CLOSING: + case CHANNEL_LISTENER_STATE_ERROR: + is_valid = 1; + break; + case CHANNEL_LISTENER_STATE_LAST: + default: + is_valid = 0; + } + + return is_valid; +} + +/** + * Indicate whether a channel state transition is valid + * + * This function takes two channel states and indicates whether a + * transition between them is permitted (see the state definitions and + * transition table in or.h at the channel_state_t typedef). + */ + +int +channel_state_can_transition(channel_state_t from, channel_state_t to) +{ + int is_valid; + + switch (from) { + case CHANNEL_STATE_CLOSED: + is_valid = (to == CHANNEL_STATE_OPENING); + break; + case CHANNEL_STATE_CLOSING: + is_valid = (to == CHANNEL_STATE_CLOSED || + to == CHANNEL_STATE_ERROR); + break; + case CHANNEL_STATE_ERROR: + is_valid = 0; + break; + case CHANNEL_STATE_MAINT: + is_valid = (to == CHANNEL_STATE_CLOSING || + to == CHANNEL_STATE_ERROR || + to == CHANNEL_STATE_OPEN); + break; + case CHANNEL_STATE_OPENING: + is_valid = (to == CHANNEL_STATE_CLOSING || + to == CHANNEL_STATE_ERROR || + to == CHANNEL_STATE_OPEN); + break; + case CHANNEL_STATE_OPEN: + is_valid = (to == CHANNEL_STATE_CLOSING || + to == CHANNEL_STATE_ERROR || + to == CHANNEL_STATE_MAINT); + break; + case CHANNEL_STATE_LAST: + default: + is_valid = 0; + } + + return is_valid; +} + +/** + * Indicate whether a channel listener state transition is valid + * + * This function takes two channel listener states and indicates whether a + * transition between them is permitted (see the state definitions and + * transition table in or.h at the channel_listener_state_t typedef). + */ + +int +channel_listener_state_can_transition(channel_listener_state_t from, + channel_listener_state_t to) +{ + int is_valid; + + switch (from) { + case CHANNEL_LISTENER_STATE_CLOSED: + is_valid = (to == CHANNEL_LISTENER_STATE_LISTENING); + break; + case CHANNEL_LISTENER_STATE_CLOSING: + is_valid = (to == CHANNEL_LISTENER_STATE_CLOSED || + to == CHANNEL_LISTENER_STATE_ERROR); + break; + case CHANNEL_LISTENER_STATE_ERROR: + is_valid = 0; + break; + case CHANNEL_LISTENER_STATE_LISTENING: + is_valid = (to == CHANNEL_LISTENER_STATE_CLOSING || + to == CHANNEL_LISTENER_STATE_ERROR); + break; + case CHANNEL_LISTENER_STATE_LAST: + default: + is_valid = 0; + } + + return is_valid; +} + +/** + * Return a human-readable description for a channel state + */ + +const char * +channel_state_to_string(channel_state_t state) +{ + const char *descr; + + switch (state) { + case CHANNEL_STATE_CLOSED: + descr = "closed"; + break; + case CHANNEL_STATE_CLOSING: + descr = "closing"; + break; + case CHANNEL_STATE_ERROR: + descr = "channel error"; + break; + case CHANNEL_STATE_MAINT: + descr = "temporarily suspended for maintenance"; + break; + case CHANNEL_STATE_OPENING: + descr = "opening"; + break; + case CHANNEL_STATE_OPEN: + descr = "open"; + break; + case CHANNEL_STATE_LAST: + default: + descr = "unknown or invalid channel state"; + } + + return descr; +} + +/** + * Return a human-readable description for a channel listenier state + */ + +const char * +channel_listener_state_to_string(channel_listener_state_t state) +{ + const char *descr; + + switch (state) { + case CHANNEL_LISTENER_STATE_CLOSED: + descr = "closed"; + break; + case CHANNEL_LISTENER_STATE_CLOSING: + descr = "closing"; + break; + case CHANNEL_LISTENER_STATE_ERROR: + descr = "channel listener error"; + break; + case CHANNEL_LISTENER_STATE_LISTENING: + descr = "listening"; + break; + case CHANNEL_LISTENER_STATE_LAST: + default: + descr = "unknown or invalid channel listener state"; + } + + return descr; +} + +/*************************************** + * Channel registration/unregistration * + ***************************************/ + +/** + * Register a channel + * + * This function registers a newly created channel in the global lists/maps + * of active channels. + */ + +void +channel_register(channel_t *chan) +{ + tor_assert(chan); + + /* No-op if already registered */ + if (chan->registered) return; + + log_debug(LD_CHANNEL, + "Registering channel %p (ID " U64_FORMAT ") " + "in state %s (%d) with digest %s", + chan, U64_PRINTF_ARG(chan->global_identifier), + channel_state_to_string(chan->state), chan->state, + hex_str(chan->identity_digest, DIGEST_LEN)); + + /* Make sure we have all_channels, then add it */ + if (!all_channels) all_channels = smartlist_new(); + smartlist_add(all_channels, chan); + + /* Is it finished? */ + if (chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) { + /* Put it in the finished list, creating it if necessary */ + if (!finished_channels) finished_channels = smartlist_new(); + smartlist_add(finished_channels, chan); + } else { + /* Put it in the active list, creating it if necessary */ + if (!active_channels) active_channels = smartlist_new(); + smartlist_add(active_channels, chan); + + if (chan->state != CHANNEL_STATE_CLOSING) { + /* It should have a digest set */ + if (!tor_digest_is_zero(chan->identity_digest)) { + /* Yeah, we're good, add it to the map */ + channel_add_to_digest_map(chan); + } else { + log_info(LD_CHANNEL, + "Channel %p (global ID " U64_FORMAT ") " + "in state %s (%d) registered with no identity digest", + chan, U64_PRINTF_ARG(chan->global_identifier), + channel_state_to_string(chan->state), chan->state); + } + } + } + + /* Mark it as registered */ + chan->registered = 1; +} + +/** + * Unregister a channel + * + * This function removes a channel from the global lists and maps and is used + * when freeing a closed/errored channel. + */ + +void +channel_unregister(channel_t *chan) +{ + tor_assert(chan); + + /* No-op if not registered */ + if (!(chan->registered)) return; + + /* Is it finished? */ + if (chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) { + /* Get it out of the finished list */ + if (finished_channels) smartlist_remove(finished_channels, chan); + } else { + /* Get it out of the active list */ + if (active_channels) smartlist_remove(active_channels, chan); + } + + /* Get it out of all_channels */ + if (all_channels) smartlist_remove(all_channels, chan); + + /* Mark it as unregistered */ + chan->registered = 0; + + /* Should it be in the digest map? */ + if (!tor_digest_is_zero(chan->identity_digest) && + !(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)) { + /* Remove it */ + channel_remove_from_digest_map(chan); + } +} + +/** + * Register a channel listener + * + * This function registers a newly created channel listner in the global + * lists/maps of active channel listeners. + */ + +void +channel_listener_register(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + /* No-op if already registered */ + if (chan_l->registered) return; + + log_debug(LD_CHANNEL, + "Registering channel listener %p (ID " U64_FORMAT ") " + "in state %s (%d)", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier), + channel_listener_state_to_string(chan_l->state), + chan_l->state); + + /* Make sure we have all_channels, then add it */ + if (!all_listeners) all_listeners = smartlist_new(); + smartlist_add(all_listeners, chan_l); + + /* Is it finished? */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) { + /* Put it in the finished list, creating it if necessary */ + if (!finished_listeners) finished_listeners = smartlist_new(); + smartlist_add(finished_listeners, chan_l); + } else { + /* Put it in the active list, creating it if necessary */ + if (!active_listeners) active_listeners = smartlist_new(); + smartlist_add(active_listeners, chan_l); + } + + /* Mark it as registered */ + chan_l->registered = 1; +} + +/** + * Unregister a channel listener + * + * This function removes a channel listener from the global lists and maps + * and is used when freeing a closed/errored channel listener. + */ + +void +channel_listener_unregister(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + /* No-op if not registered */ + if (!(chan_l->registered)) return; + + /* Is it finished? */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) { + /* Get it out of the finished list */ + if (finished_listeners) smartlist_remove(finished_listeners, chan_l); + } else { + /* Get it out of the active list */ + if (active_listeners) smartlist_remove(active_listeners, chan_l); + } + + /* Get it out of all_channels */ + if (all_listeners) smartlist_remove(all_listeners, chan_l); + + /* Mark it as unregistered */ + chan_l->registered = 0; +} + +/********************************* + * Channel digest map maintenance + *********************************/ + +/** + * Add a channel to the digest map + * + * This function adds a channel to the digest map and inserts it into the + * correct linked list if channels with that remote endpoint identity digest + * already exist. + */ + +static void +channel_add_to_digest_map(channel_t *chan) +{ + channel_idmap_entry_t *ent, search; + + tor_assert(chan); + + /* Assert that the state makes sense */ + tor_assert(!(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)); + + /* Assert that there is a digest */ + tor_assert(!tor_digest_is_zero(chan->identity_digest)); + + memcpy(search.digest, chan->identity_digest, DIGEST_LEN); + ent = HT_FIND(channel_idmap, &channel_identity_map, &search); + if (! ent) { + ent = tor_malloc(sizeof(channel_idmap_entry_t)); + memcpy(ent->digest, chan->identity_digest, DIGEST_LEN); + LIST_INIT(&ent->channel_list); + HT_INSERT(channel_idmap, &channel_identity_map, ent); + } + LIST_INSERT_HEAD(&ent->channel_list, chan, next_with_same_id); + + log_debug(LD_CHANNEL, + "Added channel %p (global ID " U64_FORMAT ") " + "to identity map in state %s (%d) with digest %s", + chan, U64_PRINTF_ARG(chan->global_identifier), + channel_state_to_string(chan->state), chan->state, + hex_str(chan->identity_digest, DIGEST_LEN)); +} + +/** + * Remove a channel from the digest map + * + * This function removes a channel from the digest map and the linked list of + * channels for that digest if more than one exists. + */ + +static void +channel_remove_from_digest_map(channel_t *chan) +{ + channel_idmap_entry_t *ent, search; + + tor_assert(chan); + + /* Assert that there is a digest */ + tor_assert(!tor_digest_is_zero(chan->identity_digest)); + +#if 0 + /* Make sure we have a map */ + if (!channel_identity_map) { + /* + * No identity map, so we can't find it by definition. This + * case is similar to digestmap_get() failing below. + */ + log_warn(LD_BUG, + "Trying to remove channel %p (global ID " U64_FORMAT ") " + "with digest %s from identity map, but didn't have any identity " + "map", + chan, U64_PRINTF_ARG(chan->global_identifier), + hex_str(chan->identity_digest, DIGEST_LEN)); + /* Clear out its next/prev pointers */ + if (chan->next_with_same_id) { + chan->next_with_same_id->prev_with_same_id = chan->prev_with_same_id; + } + if (chan->prev_with_same_id) { + chan->prev_with_same_id->next_with_same_id = chan->next_with_same_id; + } + chan->next_with_same_id = NULL; + chan->prev_with_same_id = NULL; + + return; + } +#endif + + /* Pull it out of its list, wherever that list is */ + LIST_REMOVE(chan, next_with_same_id); + + memcpy(search.digest, chan->identity_digest, DIGEST_LEN); + ent = HT_FIND(channel_idmap, &channel_identity_map, &search); + + /* Look for it in the map */ + if (ent) { + /* Okay, it's here */ + + if (LIST_EMPTY(&ent->channel_list)) { + HT_REMOVE(channel_idmap, &channel_identity_map, ent); + tor_free(ent); + } + + log_debug(LD_CHANNEL, + "Removed channel %p (global ID " U64_FORMAT ") from " + "identity map in state %s (%d) with digest %s", + chan, U64_PRINTF_ARG(chan->global_identifier), + channel_state_to_string(chan->state), chan->state, + hex_str(chan->identity_digest, DIGEST_LEN)); + } else { + /* Shouldn't happen */ + log_warn(LD_BUG, + "Trying to remove channel %p (global ID " U64_FORMAT ") with " + "digest %s from identity map, but couldn't find any with " + "that digest", + chan, U64_PRINTF_ARG(chan->global_identifier), + hex_str(chan->identity_digest, DIGEST_LEN)); + } +} + +/**************************** + * Channel lookup functions * + ***************************/ + +/** + * Find channel by global ID + * + * This function searches for a channel by the global_identifier assigned + * at initialization time. This identifier is unique for the lifetime of the + * Tor process. + */ + +channel_t * +channel_find_by_global_id(uint64_t global_identifier) +{ + channel_t *rv = NULL; + + if (all_channels && smartlist_len(all_channels) > 0) { + SMARTLIST_FOREACH_BEGIN(all_channels, channel_t *, curr) { + if (curr->global_identifier == global_identifier) { + rv = curr; + break; + } + } SMARTLIST_FOREACH_END(curr); + } + + return rv; +} + +/** + * Find channel by digest of the remote endpoint + * + * This function looks up a channel by the digest of its remote endpoint in + * the channel digest map. It's possible that more than one channel to a + * given endpoint exists. Use channel_next_with_digest() to walk the list. + */ + +channel_t * +channel_find_by_remote_digest(const char *identity_digest) +{ + channel_t *rv = NULL; + channel_idmap_entry_t *ent, search; + + tor_assert(identity_digest); + + memcpy(search.digest, identity_digest, DIGEST_LEN); + ent = HT_FIND(channel_idmap, &channel_identity_map, &search); + if (ent) { + rv = LIST_FIRST(&ent->channel_list); + } + + return rv; +} + +/** + * Get next channel with digest + * + * This function takes a channel and finds the next channel in the list + * with the same digest. + */ + +channel_t * +channel_next_with_digest(channel_t *chan) +{ + tor_assert(chan); + + return LIST_NEXT(chan, next_with_same_id); +} + +/** + * Initialize a channel + * + * This function should be called by subclasses to set up some per-channel + * variables. I.e., this is the superclass constructor. Before this, the + * channel should be allocated with tor_malloc_zero(). + */ + +void +channel_init(channel_t *chan) +{ + tor_assert(chan); + + /* Assign an ID and bump the counter */ + chan->global_identifier = n_channels_allocated++; + + /* Init timestamp */ + chan->timestamp_last_added_nonpadding = time(NULL); + + /* Init next_circ_id */ + chan->next_circ_id = crypto_rand_int(1 << 15); + + /* Initialize queues. */ + SIMPLEQ_INIT(&chan->incoming_queue); + SIMPLEQ_INIT(&chan->outgoing_queue); + + /* Initialize list entries. */ + memset(&chan->next_with_same_id, 0, sizeof(chan->next_with_same_id)); + + /* Timestamp it */ + channel_timestamp_created(chan); +} + +/** + * Initialize a channel listener + * + * This function should be called by subclasses to set up some per-channel + * variables. I.e., this is the superclass constructor. Before this, the + * channel listener should be allocated with tor_malloc_zero(). + */ + +void +channel_init_listener(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + /* Assign an ID and bump the counter */ + chan_l->global_identifier = n_channels_allocated++; + + /* Timestamp it */ + channel_listener_timestamp_created(chan_l); +} + +/** + * Free a channel; nothing outside of channel.c and subclasses should call + * this - it frees channels after they have closed and been unregistered. + */ + +void +channel_free(channel_t *chan) +{ + if (!chan) return; + + /* It must be closed or errored */ + tor_assert(chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + /* It must be deregistered */ + tor_assert(!(chan->registered)); + + log_debug(LD_CHANNEL, + "Freeing channel " U64_FORMAT " at %p", + U64_PRINTF_ARG(chan->global_identifier), chan); + + /* + * Get rid of cmux policy before we do anything, so cmux policies don't + * see channels in weird half-freed states. + */ + if (chan->cmux) { + circuitmux_set_policy(chan->cmux, NULL); + } + + /* Call a free method if there is one */ + if (chan->free) chan->free(chan); + + channel_clear_remote_end(chan); + + /* Get rid of cmux */ + if (chan->cmux) { + circuitmux_detach_all_circuits(chan->cmux); + circuitmux_free(chan->cmux); + chan->cmux = NULL; + } + + /* We're in CLOSED or ERROR, so the cell queue is already empty */ + + tor_free(chan); +} + +/** + * Free a channel listener; nothing outside of channel.c and subclasses + * should call this - it frees channel listeners after they have closed and + * been unregistered. + */ + +void +channel_listener_free(channel_listener_t *chan_l) +{ + if (!chan_l) return; + + log_debug(LD_CHANNEL, + "Freeing channel_listener_t " U64_FORMAT " at %p", + U64_PRINTF_ARG(chan_l->global_identifier), + chan_l); + + /* It must be closed or errored */ + tor_assert(chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR); + /* It must be deregistered */ + tor_assert(!(chan_l->registered)); + + /* Call a free method if there is one */ + if (chan_l->free) chan_l->free(chan_l); + + /* + * We're in CLOSED or ERROR, so the incoming channel queue is already + * empty. + */ + + tor_free(chan_l); +} + +/** + * Free a channel and skip the state/registration asserts; this internal- + * use-only function should be called only from channel_free_all() when + * shutting down the Tor process. + */ + +static void +channel_force_free(channel_t *chan) +{ + cell_queue_entry_t *cell, *cell_tmp; + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Force-freeing channel " U64_FORMAT " at %p", + U64_PRINTF_ARG(chan->global_identifier), chan); + + /* + * Get rid of cmux policy before we do anything, so cmux policies don't + * see channels in weird half-freed states. + */ + if (chan->cmux) { + circuitmux_set_policy(chan->cmux, NULL); + } + + /* Call a free method if there is one */ + if (chan->free) chan->free(chan); + + channel_clear_remote_end(chan); + + /* Get rid of cmux */ + if (chan->cmux) { + circuitmux_free(chan->cmux); + chan->cmux = NULL; + } + + /* We might still have a cell queue; kill it */ + SIMPLEQ_FOREACH_SAFE(cell, &chan->incoming_queue, next, cell_tmp) { + cell_queue_entry_free(cell, 0); + } + SIMPLEQ_INIT(&chan->incoming_queue); + + /* Outgoing cell queue is similar, but we can have to free packed cells */ + SIMPLEQ_FOREACH_SAFE(cell, &chan->outgoing_queue, next, cell_tmp) { + cell_queue_entry_free(cell, 0); + } + SIMPLEQ_INIT(&chan->outgoing_queue); + + tor_free(chan); +} + +/** + * Free a channel listener and skip the state/reigstration asserts; this + * internal-use-only function should be called only from channel_free_all() + * when shutting down the Tor process. + */ + +static void +channel_listener_force_free(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + log_debug(LD_CHANNEL, + "Force-freeing channel_listener_t " U64_FORMAT " at %p", + U64_PRINTF_ARG(chan_l->global_identifier), + chan_l); + + /* Call a free method if there is one */ + if (chan_l->free) chan_l->free(chan_l); + + /* + * The incoming list just gets emptied and freed; we request close on + * any channels we find there, but since we got called while shutting + * down they will get deregistered and freed elsewhere anyway. + */ + if (chan_l->incoming_list) { + SMARTLIST_FOREACH_BEGIN(chan_l->incoming_list, + channel_t *, qchan) { + channel_mark_for_close(qchan); + } SMARTLIST_FOREACH_END(qchan); + + smartlist_free(chan_l->incoming_list); + chan_l->incoming_list = NULL; + } + + tor_free(chan_l); +} + +/** + * Return the current registered listener for a channel listener + * + * This function returns a function pointer to the current registered + * handler for new incoming channels on a channel listener. + */ + +channel_listener_fn_ptr +channel_listener_get_listener_fn(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + if (chan_l->state == CHANNEL_LISTENER_STATE_LISTENING) + return chan_l->listener; + + return NULL; +} + +/** + * Set the listener for a channel listener + * + * This function sets the handler for new incoming channels on a channel + * listener. + */ + +void +channel_listener_set_listener_fn(channel_listener_t *chan_l, + channel_listener_fn_ptr listener) +{ + tor_assert(chan_l); + tor_assert(chan_l->state == CHANNEL_LISTENER_STATE_LISTENING); + + log_debug(LD_CHANNEL, + "Setting listener callback for channel listener %p " + "(global ID " U64_FORMAT ") to %p", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier), + listener); + + chan_l->listener = listener; + if (chan_l->listener) channel_listener_process_incoming(chan_l); +} + +/** + * Return the fixed-length cell handler for a channel + * + * This function gets the handler for incoming fixed-length cells installed + * on a channel. + */ + +channel_cell_handler_fn_ptr +channel_get_cell_handler(channel_t *chan) +{ + tor_assert(chan); + + if (chan->state == CHANNEL_STATE_OPENING || + chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) + return chan->cell_handler; + + return NULL; +} + +/** + * Return the variable-length cell handler for a channel + * + * This function gets the handler for incoming variable-length cells + * installed on a channel. + */ + +channel_var_cell_handler_fn_ptr +channel_get_var_cell_handler(channel_t *chan) +{ + tor_assert(chan); + + if (chan->state == CHANNEL_STATE_OPENING || + chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) + return chan->var_cell_handler; + + return NULL; +} + +/** + * Set both cell handlers for a channel + * + * This function sets both the fixed-length and variable length cell handlers + * for a channel and processes any incoming cells that had been blocked in the + * queue because none were available. + */ + +void +channel_set_cell_handlers(channel_t *chan, + channel_cell_handler_fn_ptr cell_handler, + channel_var_cell_handler_fn_ptr + var_cell_handler) +{ + int try_again = 0; + + tor_assert(chan); + tor_assert(chan->state == CHANNEL_STATE_OPENING || + chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT); + + log_debug(LD_CHANNEL, + "Setting cell_handler callback for channel %p to %p", + chan, cell_handler); + log_debug(LD_CHANNEL, + "Setting var_cell_handler callback for channel %p to %p", + chan, var_cell_handler); + + /* Should we try the queue? */ + if (cell_handler && + cell_handler != chan->cell_handler) try_again = 1; + if (var_cell_handler && + var_cell_handler != chan->var_cell_handler) try_again = 1; + + /* Change them */ + chan->cell_handler = cell_handler; + chan->var_cell_handler = var_cell_handler; + + /* Re-run the queue if we have one and there's any reason to */ + if (! SIMPLEQ_EMPTY(&chan->incoming_queue) && + try_again && + (chan->cell_handler || + chan->var_cell_handler)) channel_process_cells(chan); +} + +/* + * On closing channels + * + * There are three functions that close channels, for use in + * different circumstances: + * + * - Use channel_mark_for_close() for most cases + * - Use channel_close_from_lower_layer() if you are connection_or.c + * and the other end closes the underlying connection. + * - Use channel_close_for_error() if you are connection_or.c and + * some sort of error has occurred. + */ + +/** + * Mark a channel for closure + * + * This function tries to close a channel_t; it will go into the CLOSING + * state, and eventually the lower layer should put it into the CLOSED or + * ERROR state. Then, channel_run_cleanup() will eventually free it. + */ + +void +channel_mark_for_close(channel_t *chan) +{ + tor_assert(chan != NULL); + tor_assert(chan->close != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel %p (global ID " U64_FORMAT ") " + "by request", + chan, U64_PRINTF_ARG(chan->global_identifier)); + + /* Note closing by request from above */ + chan->reason_for_closing = CHANNEL_CLOSE_REQUESTED; + + /* Change state to CLOSING */ + channel_change_state(chan, CHANNEL_STATE_CLOSING); + + /* Tell the lower layer */ + chan->close(chan); + + /* + * It's up to the lower layer to change state to CLOSED or ERROR when we're + * ready; we'll try to free channels that are in the finished list from + * channel_run_cleanup(). The lower layer should do this by calling + * channel_closed(). + */ +} + +/** + * Mark a channel listener for closure + * + * This function tries to close a channel_listener_t; it will go into the + * CLOSING state, and eventually the lower layer should put it into the CLOSED + * or ERROR state. Then, channel_run_cleanup() will eventually free it. + */ + +void +channel_listener_mark_for_close(channel_listener_t *chan_l) +{ + tor_assert(chan_l != NULL); + tor_assert(chan_l->close != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSING || + chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel listener %p (global ID " U64_FORMAT ") " + "by request", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier)); + + /* Note closing by request from above */ + chan_l->reason_for_closing = CHANNEL_LISTENER_CLOSE_REQUESTED; + + /* Change state to CLOSING */ + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSING); + + /* Tell the lower layer */ + chan_l->close(chan_l); + + /* + * It's up to the lower layer to change state to CLOSED or ERROR when we're + * ready; we'll try to free channels that are in the finished list from + * channel_run_cleanup(). The lower layer should do this by calling + * channel_listener_closed(). + */ +} + +/** + * Close a channel from the lower layer + * + * Notify the channel code that the channel is being closed due to a non-error + * condition in the lower layer. This does not call the close() method, since + * the lower layer already knows. + */ + +void +channel_close_from_lower_layer(channel_t *chan) +{ + tor_assert(chan != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel %p (global ID " U64_FORMAT ") " + "due to lower-layer event", + chan, U64_PRINTF_ARG(chan->global_identifier)); + + /* Note closing by event from below */ + chan->reason_for_closing = CHANNEL_CLOSE_FROM_BELOW; + + /* Change state to CLOSING */ + channel_change_state(chan, CHANNEL_STATE_CLOSING); +} + +/** + * Close a channel listener from the lower layer + * + * Notify the channel code that the channel listener is being closed due to a + * non-error condition in the lower layer. This does not call the close() + * method, since the lower layer already knows. + */ + +void +channel_listener_close_from_lower_layer(channel_listener_t *chan_l) +{ + tor_assert(chan_l != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSING || + chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel listener %p (global ID " U64_FORMAT ") " + "due to lower-layer event", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier)); + + /* Note closing by event from below */ + chan_l->reason_for_closing = CHANNEL_LISTENER_CLOSE_FROM_BELOW; + + /* Change state to CLOSING */ + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSING); +} + +/** + * Notify that the channel is being closed due to an error condition + * + * This function is called by the lower layer implementing the transport + * when a channel must be closed due to an error condition. This does not + * call the channel's close method, since the lower layer already knows. + */ + +void +channel_close_for_error(channel_t *chan) +{ + tor_assert(chan != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel %p due to lower-layer error", + chan); + + /* Note closing by event from below */ + chan->reason_for_closing = CHANNEL_CLOSE_FOR_ERROR; + + /* Change state to CLOSING */ + channel_change_state(chan, CHANNEL_STATE_CLOSING); +} + +/** + * Notify that the channel listener is being closed due to an error condition + * + * This function is called by the lower layer implementing the transport + * when a channel listener must be closed due to an error condition. This + * does not call the channel listener's close method, since the lower layer + * already knows. + */ + +void +channel_listener_close_for_error(channel_listener_t *chan_l) +{ + tor_assert(chan_l != NULL); + + /* If it's already in CLOSING, CLOSED or ERROR, this is a no-op */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSING || + chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) return; + + log_debug(LD_CHANNEL, + "Closing channel listener %p (global ID " U64_FORMAT ") " + "due to lower-layer error", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier)); + + /* Note closing by event from below */ + chan_l->reason_for_closing = CHANNEL_LISTENER_CLOSE_FOR_ERROR; + + /* Change state to CLOSING */ + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSING); +} + +/** + * Notify that the lower layer is finished closing the channel + * + * This function should be called by the lower layer when a channel + * is finished closing and it should be regarded as inactive and + * freed by the channel code. + */ + +void +channel_closed(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + + /* No-op if already inactive */ + if (chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) return; + + if (chan->reason_for_closing == CHANNEL_CLOSE_FOR_ERROR) { + /* Inform any pending (not attached) circs that they should + * give up. */ + circuit_n_chan_done(chan, 0); + } + /* Now close all the attached circuits on it. */ + circuit_unlink_all_from_channel(chan, END_CIRC_REASON_CHANNEL_CLOSED); + + if (chan->reason_for_closing != CHANNEL_CLOSE_FOR_ERROR) { + channel_change_state(chan, CHANNEL_STATE_CLOSED); + } else { + channel_change_state(chan, CHANNEL_STATE_ERROR); + } +} + +/** + * Notify that the lower layer is finished closing the channel listener + * + * This function should be called by the lower layer when a channel listener + * is finished closing and it should be regarded as inactive and + * freed by the channel code. + */ + +void +channel_listener_closed(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + tor_assert(chan_l->state == CHANNEL_LISTENER_STATE_CLOSING || + chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR); + + /* No-op if already inactive */ + if (chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR) return; + + if (chan_l->reason_for_closing != CHANNEL_LISTENER_CLOSE_FOR_ERROR) { + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSED); + } else { + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_ERROR); + } +} + +/** + * Clear the identity_digest of a channel + * + * This function clears the identity digest of the remote endpoint for a + * channel; this is intended for use by the lower layer. + */ + +void +channel_clear_identity_digest(channel_t *chan) +{ + int state_not_in_map; + + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Clearing remote endpoint digest on channel %p with " + "global ID " U64_FORMAT, + chan, U64_PRINTF_ARG(chan->global_identifier)); + + state_not_in_map = + (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + + if (!state_not_in_map && chan->registered && + !tor_digest_is_zero(chan->identity_digest)) + /* if it's registered get it out of the digest map */ + channel_remove_from_digest_map(chan); + + memset(chan->identity_digest, 0, + sizeof(chan->identity_digest)); +} + +/** + * Set the identity_digest of a channel + * + * This function sets the identity digest of the remote endpoint for a + * channel; this is intended for use by the lower layer. + */ + +void +channel_set_identity_digest(channel_t *chan, + const char *identity_digest) +{ + int was_in_digest_map, should_be_in_digest_map, state_not_in_map; + + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Setting remote endpoint digest on channel %p with " + "global ID " U64_FORMAT " to digest %s", + chan, U64_PRINTF_ARG(chan->global_identifier), + identity_digest ? + hex_str(identity_digest, DIGEST_LEN) : "(null)"); + + state_not_in_map = + (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + was_in_digest_map = + !state_not_in_map && + chan->registered && + !tor_digest_is_zero(chan->identity_digest); + should_be_in_digest_map = + !state_not_in_map && + chan->registered && + (identity_digest && + !tor_digest_is_zero(identity_digest)); + + if (was_in_digest_map) + /* We should always remove it; we'll add it back if we're writing + * in a new digest. + */ + channel_remove_from_digest_map(chan); + + if (identity_digest) { + memcpy(chan->identity_digest, + identity_digest, + sizeof(chan->identity_digest)); + } else { + memset(chan->identity_digest, 0, + sizeof(chan->identity_digest)); + } + + /* Put it in the digest map if we should */ + if (should_be_in_digest_map) + channel_add_to_digest_map(chan); +} + +/** + * Clear the remote end metadata (identity_digest/nickname) of a channel + * + * This function clears all the remote end info from a channel; this is + * intended for use by the lower layer. + */ + +void +channel_clear_remote_end(channel_t *chan) +{ + int state_not_in_map; + + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Clearing remote endpoint identity on channel %p with " + "global ID " U64_FORMAT, + chan, U64_PRINTF_ARG(chan->global_identifier)); + + state_not_in_map = + (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + + if (!state_not_in_map && chan->registered && + !tor_digest_is_zero(chan->identity_digest)) + /* if it's registered get it out of the digest map */ + channel_remove_from_digest_map(chan); + + memset(chan->identity_digest, 0, + sizeof(chan->identity_digest)); + tor_free(chan->nickname); +} + +/** + * Set the remote end metadata (identity_digest/nickname) of a channel + * + * This function sets new remote end info on a channel; this is intended + * for use by the lower layer. + */ + +void +channel_set_remote_end(channel_t *chan, + const char *identity_digest, + const char *nickname) +{ + int was_in_digest_map, should_be_in_digest_map, state_not_in_map; + + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Setting remote endpoint identity on channel %p with " + "global ID " U64_FORMAT " to nickname %s, digest %s", + chan, U64_PRINTF_ARG(chan->global_identifier), + nickname ? nickname : "(null)", + identity_digest ? + hex_str(identity_digest, DIGEST_LEN) : "(null)"); + + state_not_in_map = + (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR); + was_in_digest_map = + !state_not_in_map && + chan->registered && + !tor_digest_is_zero(chan->identity_digest); + should_be_in_digest_map = + !state_not_in_map && + chan->registered && + (identity_digest && + !tor_digest_is_zero(identity_digest)); + + if (was_in_digest_map) + /* We should always remove it; we'll add it back if we're writing + * in a new digest. + */ + channel_remove_from_digest_map(chan); + + if (identity_digest) { + memcpy(chan->identity_digest, + identity_digest, + sizeof(chan->identity_digest)); + + } else { + memset(chan->identity_digest, 0, + sizeof(chan->identity_digest)); + } + + tor_free(chan->nickname); + if (nickname) + chan->nickname = tor_strdup(nickname); + + /* Put it in the digest map if we should */ + if (should_be_in_digest_map) + channel_add_to_digest_map(chan); +} + +/** + * Duplicate a cell queue entry; this is a shallow copy intended for use + * in channel_write_cell_queue_entry(). + */ + +static cell_queue_entry_t * +cell_queue_entry_dup(cell_queue_entry_t *q) +{ + cell_queue_entry_t *rv = NULL; + + tor_assert(q); + + rv = tor_malloc(sizeof(*rv)); + memcpy(rv, q, sizeof(*rv)); + + return rv; +} + +/** + * Free a cell_queue_entry_t; the handed_off parameter indicates whether + * the contents were passed to the lower layer (it is responsible for + * them) or not (we should free). + */ + +static void +cell_queue_entry_free(cell_queue_entry_t *q, int handed_off) +{ + if (!q) return; + + if (!handed_off) { + /* + * If we handed it off, the recipient becomes responsible (or + * with packed cells the channel_t subclass calls packed_cell + * free after writing out its contents; see, e.g., + * channel_tls_write_packed_cell_method(). Otherwise, we have + * to take care of it here if possible. + */ + switch (q->type) { + case CELL_QUEUE_FIXED: + if (q->u.fixed.cell) { + /* + * There doesn't seem to be a cell_free() function anywhere in the + * pre-channel code; just use tor_free() + */ + tor_free(q->u.fixed.cell); + } + break; + case CELL_QUEUE_PACKED: + if (q->u.packed.packed_cell) { + packed_cell_free(q->u.packed.packed_cell); + } + break; + case CELL_QUEUE_VAR: + if (q->u.var.var_cell) { + /* + * This one's in connection_or.c; it'd be nice to figure out the + * whole flow of cells from one end to the other and factor the + * cell memory management functions like this out of the specific + * TLS lower layer. + */ + var_cell_free(q->u.var.var_cell); + } + break; + default: + /* + * Nothing we can do if we don't know the type; this will + * have been warned about elsewhere. + */ + break; + } + } + tor_free(q); +} + +/** + * Check whether a cell queue entry is padding; this is a helper function + * for channel_write_cell_queue_entry() + */ + +static int +cell_queue_entry_is_padding(cell_queue_entry_t *q) +{ + tor_assert(q); + + if (q->type == CELL_QUEUE_FIXED) { + if (q->u.fixed.cell) { + if (q->u.fixed.cell->command == CELL_PADDING || + q->u.fixed.cell->command == CELL_VPADDING) { + return 1; + } + } + } else if (q->type == CELL_QUEUE_VAR) { + if (q->u.var.var_cell) { + if (q->u.var.var_cell->command == CELL_PADDING || + q->u.var.var_cell->command == CELL_VPADDING) { + return 1; + } + } + } + + return 0; +} + +/** + * Allocate a new cell queue entry for a fixed-size cell + */ + +static cell_queue_entry_t * +cell_queue_entry_new_fixed(cell_t *cell) +{ + cell_queue_entry_t *q = NULL; + + tor_assert(cell); + + q = tor_malloc(sizeof(*q)); + q->type = CELL_QUEUE_FIXED; + q->u.fixed.cell = cell; + + return q; +} + +/** + * Allocate a new cell queue entry for a variable-size cell + */ + +static cell_queue_entry_t * +cell_queue_entry_new_var(var_cell_t *var_cell) +{ + cell_queue_entry_t *q = NULL; + + tor_assert(var_cell); + + q = tor_malloc(sizeof(*q)); + q->type = CELL_QUEUE_VAR; + q->u.var.var_cell = var_cell; + + return q; +} + +/** + * Write to a channel based on a cell_queue_entry_t + * + * Given a cell_queue_entry_t filled out by the caller, try to send the cell + * and queue it if we can't. + */ + +static void +channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) +{ + int result = 0, sent = 0; + cell_queue_entry_t *tmp = NULL; + + tor_assert(chan); + tor_assert(q); + + /* Assert that the state makes sense for a cell write */ + tor_assert(chan->state == CHANNEL_STATE_OPENING || + chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT); + + /* Increment the timestamp unless it's padding */ + if (!cell_queue_entry_is_padding(q)) { + chan->timestamp_last_added_nonpadding = approx_time(); + } + + /* Can we send it right out? If so, try */ + if (SIMPLEQ_EMPTY(&chan->outgoing_queue) && + chan->state == CHANNEL_STATE_OPEN) { + /* Pick the right write function for this cell type and save the result */ + switch (q->type) { + case CELL_QUEUE_FIXED: + tor_assert(chan->write_cell); + tor_assert(q->u.fixed.cell); + result = chan->write_cell(chan, q->u.fixed.cell); + break; + case CELL_QUEUE_PACKED: + tor_assert(chan->write_packed_cell); + tor_assert(q->u.packed.packed_cell); + result = chan->write_packed_cell(chan, q->u.packed.packed_cell); + break; + case CELL_QUEUE_VAR: + tor_assert(chan->write_var_cell); + tor_assert(q->u.var.var_cell); + result = chan->write_var_cell(chan, q->u.var.var_cell); + break; + default: + tor_assert(1); + } + + /* Check if we got it out */ + if (result > 0) { + sent = 1; + /* Timestamp for transmission */ + channel_timestamp_xmit(chan); + /* If we're here the queue is empty, so it's drained too */ + channel_timestamp_drained(chan); + /* Update the counter */ + ++(chan->n_cells_xmitted); + } + } + + if (!sent) { + /* Not sent, queue it */ + /* + * We have to copy the queue entry passed in, since the caller probably + * used the stack. + */ + tmp = cell_queue_entry_dup(q); + SIMPLEQ_INSERT_TAIL(&chan->outgoing_queue, tmp, next); + /* Try to process the queue? */ + if (chan->state == CHANNEL_STATE_OPEN) channel_flush_cells(chan); + } +} + +/** + * Write a cell to a channel + * + * Write a fixed-length cell to a channel using the write_cell() method. + * This is equivalent to the pre-channels connection_or_write_cell_to_buf(); + * it is called by the transport-independent code to deliver a cell to a + * channel for transmission. + */ + +void +channel_write_cell(channel_t *chan, cell_t *cell) +{ + cell_queue_entry_t q; + + tor_assert(chan); + tor_assert(cell); + + log_debug(LD_CHANNEL, + "Writing cell_t %p to channel %p with global ID " + U64_FORMAT, + cell, chan, U64_PRINTF_ARG(chan->global_identifier)); + + q.type = CELL_QUEUE_FIXED; + q.u.fixed.cell = cell; + channel_write_cell_queue_entry(chan, &q); +} + +/** + * Write a packed cell to a channel + * + * Write a packed cell to a channel using the write_cell() method. This is + * called by the transport-independent code to deliver a packed cell to a + * channel for transmission. + */ + +void +channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell) +{ + cell_queue_entry_t q; + + tor_assert(chan); + tor_assert(packed_cell); + + log_debug(LD_CHANNEL, + "Writing packed_cell_t %p to channel %p with global ID " + U64_FORMAT, + packed_cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + + q.type = CELL_QUEUE_PACKED; + q.u.packed.packed_cell = packed_cell; + channel_write_cell_queue_entry(chan, &q); +} + +/** + * Write a variable-length cell to a channel + * + * Write a variable-length cell to a channel using the write_cell() method. + * This is equivalent to the pre-channels + * connection_or_write_var_cell_to_buf(); it's called by the transport- + * independent code to deliver a var_cell to a channel for transmission. + */ + +void +channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) +{ + cell_queue_entry_t q; + + tor_assert(chan); + tor_assert(var_cell); + + log_debug(LD_CHANNEL, + "Writing var_cell_t %p to channel %p with global ID " + U64_FORMAT, + var_cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + + q.type = CELL_QUEUE_VAR; + q.u.var.var_cell = var_cell; + channel_write_cell_queue_entry(chan, &q); +} + +/** + * Change channel state + * + * This internal and subclass use only function is used to change channel + * state, performing all transition validity checks and whatever actions + * are appropriate to the state transition in question. + */ + +void +channel_change_state(channel_t *chan, channel_state_t to_state) +{ + channel_state_t from_state; + unsigned char was_active, is_active; + unsigned char was_in_id_map, is_in_id_map; + + tor_assert(chan); + from_state = chan->state; + + tor_assert(channel_state_is_valid(from_state)); + tor_assert(channel_state_is_valid(to_state)); + tor_assert(channel_state_can_transition(chan->state, to_state)); + + /* Check for no-op transitions */ + if (from_state == to_state) { + log_debug(LD_CHANNEL, + "Got no-op transition from \"%s\" to itself on channel %p" + "(global ID " U64_FORMAT ")", + channel_state_to_string(to_state), + chan, U64_PRINTF_ARG(chan->global_identifier)); + return; + } + + /* If we're going to a closing or closed state, we must have a reason set */ + if (to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { + tor_assert(chan->reason_for_closing != CHANNEL_NOT_CLOSING); + } + + /* + * We need to maintain the queues here for some transitions: + * when we enter CHANNEL_STATE_OPEN (especially from CHANNEL_STATE_MAINT) + * we may have a backlog of cells to transmit, so drain the queues in + * that case, and when going to CHANNEL_STATE_CLOSED the subclass + * should have made sure to finish sending things (or gone to + * CHANNEL_STATE_ERROR if not possible), so we assert for that here. + */ + + log_debug(LD_CHANNEL, + "Changing state of channel %p (global ID " U64_FORMAT + ") from \"%s\" to \"%s\"", + chan, + U64_PRINTF_ARG(chan->global_identifier), + channel_state_to_string(chan->state), + channel_state_to_string(to_state)); + + chan->state = to_state; + + /* Need to add to the right lists if the channel is registered */ + if (chan->registered) { + was_active = !(from_state == CHANNEL_STATE_CLOSED || + from_state == CHANNEL_STATE_ERROR); + is_active = !(to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR); + + /* Need to take off active list and put on finished list? */ + if (was_active && !is_active) { + if (active_channels) smartlist_remove(active_channels, chan); + if (!finished_channels) finished_channels = smartlist_new(); + smartlist_add(finished_channels, chan); + } + /* Need to put on active list? */ + else if (!was_active && is_active) { + if (finished_channels) smartlist_remove(finished_channels, chan); + if (!active_channels) active_channels = smartlist_new(); + smartlist_add(active_channels, chan); + } + + if (!tor_digest_is_zero(chan->identity_digest)) { + /* Now we need to handle the identity map */ + was_in_id_map = !(from_state == CHANNEL_STATE_CLOSING || + from_state == CHANNEL_STATE_CLOSED || + from_state == CHANNEL_STATE_ERROR); + is_in_id_map = !(to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR); + + if (!was_in_id_map && is_in_id_map) channel_add_to_digest_map(chan); + else if (was_in_id_map && !is_in_id_map) + channel_remove_from_digest_map(chan); + } + } + + /* Tell circuits if we opened and stuff */ + if (to_state == CHANNEL_STATE_OPEN) { + channel_do_open_actions(chan); + + /* Check for queued cells to process */ + if (! SIMPLEQ_EMPTY(&chan->incoming_queue)) + channel_process_cells(chan); + if (! SIMPLEQ_EMPTY(&chan->outgoing_queue)) + channel_flush_cells(chan); + } else if (to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { + /* Assert that all queues are empty */ + tor_assert(SIMPLEQ_EMPTY(&chan->incoming_queue)); + tor_assert(SIMPLEQ_EMPTY(&chan->outgoing_queue)); + } +} + +/** + * Change channel listener state + * + * This internal and subclass use only function is used to change channel + * listener state, performing all transition validity checks and whatever + * actions are appropriate to the state transition in question. + */ + +void +channel_listener_change_state(channel_listener_t *chan_l, + channel_listener_state_t to_state) +{ + channel_listener_state_t from_state; + unsigned char was_active, is_active; + + tor_assert(chan_l); + from_state = chan_l->state; + + tor_assert(channel_listener_state_is_valid(from_state)); + tor_assert(channel_listener_state_is_valid(to_state)); + tor_assert(channel_listener_state_can_transition(chan_l->state, to_state)); + + /* Check for no-op transitions */ + if (from_state == to_state) { + log_debug(LD_CHANNEL, + "Got no-op transition from \"%s\" to itself on channel " + "listener %p (global ID " U64_FORMAT ")", + channel_listener_state_to_string(to_state), + chan_l, U64_PRINTF_ARG(chan_l->global_identifier)); + return; + } + + /* If we're going to a closing or closed state, we must have a reason set */ + if (to_state == CHANNEL_LISTENER_STATE_CLOSING || + to_state == CHANNEL_LISTENER_STATE_CLOSED || + to_state == CHANNEL_LISTENER_STATE_ERROR) { + tor_assert(chan_l->reason_for_closing != CHANNEL_LISTENER_NOT_CLOSING); + } + + /* + * We need to maintain the queues here for some transitions: + * when we enter CHANNEL_STATE_OPEN (especially from CHANNEL_STATE_MAINT) + * we may have a backlog of cells to transmit, so drain the queues in + * that case, and when going to CHANNEL_STATE_CLOSED the subclass + * should have made sure to finish sending things (or gone to + * CHANNEL_STATE_ERROR if not possible), so we assert for that here. + */ + + log_debug(LD_CHANNEL, + "Changing state of channel listener %p (global ID " U64_FORMAT + "from \"%s\" to \"%s\"", + chan_l, U64_PRINTF_ARG(chan_l->global_identifier), + channel_listener_state_to_string(chan_l->state), + channel_listener_state_to_string(to_state)); + + chan_l->state = to_state; + + /* Need to add to the right lists if the channel listener is registered */ + if (chan_l->registered) { + was_active = !(from_state == CHANNEL_LISTENER_STATE_CLOSED || + from_state == CHANNEL_LISTENER_STATE_ERROR); + is_active = !(to_state == CHANNEL_LISTENER_STATE_CLOSED || + to_state == CHANNEL_LISTENER_STATE_ERROR); + + /* Need to take off active list and put on finished list? */ + if (was_active && !is_active) { + if (active_listeners) smartlist_remove(active_listeners, chan_l); + if (!finished_listeners) finished_listeners = smartlist_new(); + smartlist_add(finished_listeners, chan_l); + } + /* Need to put on active list? */ + else if (!was_active && is_active) { + if (finished_listeners) smartlist_remove(finished_listeners, chan_l); + if (!active_listeners) active_listeners = smartlist_new(); + smartlist_add(active_listeners, chan_l); + } + } + + if (to_state == CHANNEL_LISTENER_STATE_CLOSED || + to_state == CHANNEL_LISTENER_STATE_ERROR) { + /* Assert that the queue is empty */ + tor_assert(!(chan_l->incoming_list) || + smartlist_len(chan_l->incoming_list) == 0); + } +} + +/** + * Try to flush cells to the lower layer + * + * this is called by the lower layer to indicate that it wants more cells; + * it will try to write up to num_cells cells from the channel's cell queue or + * from circuits active on that channel, or as many as it has available if + * num_cells == -1. + */ + +#define MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED 256 + +ssize_t +channel_flush_some_cells(channel_t *chan, ssize_t num_cells) +{ + unsigned int unlimited = 0; + ssize_t flushed = 0; + int num_cells_from_circs, clamped_num_cells; + + tor_assert(chan); + + if (num_cells < 0) unlimited = 1; + if (!unlimited && num_cells <= flushed) goto done; + + /* If we aren't in CHANNEL_STATE_OPEN, nothing goes through */ + if (chan->state == CHANNEL_STATE_OPEN) { + /* Try to flush as much as we can that's already queued */ + flushed += channel_flush_some_cells_from_outgoing_queue(chan, + (unlimited ? -1 : num_cells - flushed)); + if (!unlimited && num_cells <= flushed) goto done; + + if (circuitmux_num_cells(chan->cmux) > 0) { + /* Calculate number of cells, including clamp */ + if (unlimited) { + clamped_num_cells = MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED; + } else { + if (num_cells - flushed > + MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED) { + clamped_num_cells = MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED; + } else { + clamped_num_cells = (int)(num_cells - flushed); + } + } + /* Try to get more cells from any active circuits */ + num_cells_from_circs = channel_flush_from_first_active_circuit( + chan, clamped_num_cells); + + /* If it claims we got some, process the queue again */ + if (num_cells_from_circs > 0) { + flushed += channel_flush_some_cells_from_outgoing_queue(chan, + (unlimited ? -1 : num_cells - flushed)); + } + } + } + + done: + return flushed; +} + +/** + * Flush cells from just the channel's outgoing cell queue + * + * This gets called from channel_flush_some_cells() above to flush cells + * just from the queue without trying for active_circuits. + */ + +static ssize_t +channel_flush_some_cells_from_outgoing_queue(channel_t *chan, + ssize_t num_cells) +{ + unsigned int unlimited = 0; + ssize_t flushed = 0; + cell_queue_entry_t *q = NULL; + + tor_assert(chan); + tor_assert(chan->write_cell); + tor_assert(chan->write_packed_cell); + tor_assert(chan->write_var_cell); + + if (num_cells < 0) unlimited = 1; + if (!unlimited && num_cells <= flushed) return 0; + + /* If we aren't in CHANNEL_STATE_OPEN, nothing goes through */ + if (chan->state == CHANNEL_STATE_OPEN) { + while ((unlimited || num_cells > flushed) && + NULL != (q = SIMPLEQ_FIRST(&chan->outgoing_queue))) { + + if (1) { + /* + * Okay, we have a good queue entry, try to give it to the lower + * layer. + */ + switch (q->type) { + case CELL_QUEUE_FIXED: + if (q->u.fixed.cell) { + if (chan->write_cell(chan, + q->u.fixed.cell)) { + ++flushed; + channel_timestamp_xmit(chan); + ++(chan->n_cells_xmitted); + cell_queue_entry_free(q, 1); + q = NULL; + } + /* Else couldn't write it; leave it on the queue */ + } else { + /* This shouldn't happen */ + log_info(LD_CHANNEL, + "Saw broken cell queue entry of type CELL_QUEUE_FIXED " + "with no cell on channel %p " + "(global ID " U64_FORMAT ").", + chan, U64_PRINTF_ARG(chan->global_identifier)); + /* Throw it away */ + cell_queue_entry_free(q, 0); + q = NULL; + } + break; + case CELL_QUEUE_PACKED: + if (q->u.packed.packed_cell) { + if (chan->write_packed_cell(chan, + q->u.packed.packed_cell)) { + ++flushed; + channel_timestamp_xmit(chan); + ++(chan->n_cells_xmitted); + cell_queue_entry_free(q, 1); + q = NULL; + } + /* Else couldn't write it; leave it on the queue */ + } else { + /* This shouldn't happen */ + log_info(LD_CHANNEL, + "Saw broken cell queue entry of type CELL_QUEUE_PACKED " + "with no cell on channel %p " + "(global ID " U64_FORMAT ").", + chan, U64_PRINTF_ARG(chan->global_identifier)); + /* Throw it away */ + cell_queue_entry_free(q, 0); + q = NULL; + } + break; + case CELL_QUEUE_VAR: + if (q->u.var.var_cell) { + if (chan->write_var_cell(chan, + q->u.var.var_cell)) { + ++flushed; + channel_timestamp_xmit(chan); + ++(chan->n_cells_xmitted); + cell_queue_entry_free(q, 1); + q = NULL; + } + /* Else couldn't write it; leave it on the queue */ + } else { + /* This shouldn't happen */ + log_info(LD_CHANNEL, + "Saw broken cell queue entry of type CELL_QUEUE_VAR " + "with no cell on channel %p " + "(global ID " U64_FORMAT ").", + chan, U64_PRINTF_ARG(chan->global_identifier)); + /* Throw it away */ + cell_queue_entry_free(q, 0); + q = NULL; + } + break; + default: + /* Unknown type, log and free it */ + log_info(LD_CHANNEL, + "Saw an unknown cell queue entry type %d on channel %p " + "(global ID " U64_FORMAT "; ignoring it." + " Someone should fix this.", + q->type, chan, U64_PRINTF_ARG(chan->global_identifier)); + cell_queue_entry_free(q, 0); + q = NULL; + } + + /* if q got NULLed out, we used it and should remove the queue entry */ + if (!q) SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); + /* No cell removed from list, so we can't go on any further */ + else break; + } + } + } + + /* Did we drain the queue? */ + if (SIMPLEQ_EMPTY(&chan->outgoing_queue)) { + channel_timestamp_drained(chan); + } + + return flushed; +} + +/** + * Flush as many cells as we possibly can from the queue + * + * This tries to flush as many cells from the queue as the lower layer + * will take. It just calls channel_flush_some_cells_from_outgoing_queue() + * in unlimited mode. + */ + +void +channel_flush_cells(channel_t *chan) +{ + channel_flush_some_cells_from_outgoing_queue(chan, -1); +} + +/** + * Check if any cells are available + * + * This gets used from the lower layer to check if any more cells are + * available. + */ + +int +channel_more_to_flush(channel_t *chan) +{ + tor_assert(chan); + + /* Check if we have any queued */ + if (! SIMPLEQ_EMPTY(&chan->incoming_queue)) + return 1; + + /* Check if any circuits would like to queue some */ + if (circuitmux_num_cells(chan->cmux) > 0) return 1; + + /* Else no */ + return 0; +} + +/** + * Notify the channel we're done flushing the output in the lower layer + * + * Connection.c will call this when we've flushed the output; there's some + * dirreq-related maintenance to do. + */ + +void +channel_notify_flushed(channel_t *chan) +{ + tor_assert(chan); + + if (chan->dirreq_id != 0) + geoip_change_dirreq_state(chan->dirreq_id, + DIRREQ_TUNNELED, + DIRREQ_CHANNEL_BUFFER_FLUSHED); +} + +/** + * Process the queue of incoming channels on a listener + * + * Use a listener's registered callback to process as many entries in the + * queue of incoming channels as possible. + */ + +void +channel_listener_process_incoming(channel_listener_t *listener) +{ + tor_assert(listener); + + /* + * CHANNEL_LISTENER_STATE_CLOSING permitted because we drain the queue + * while closing a listener. + */ + tor_assert(listener->state == CHANNEL_LISTENER_STATE_LISTENING || + listener->state == CHANNEL_LISTENER_STATE_CLOSING); + tor_assert(listener->listener); + + log_debug(LD_CHANNEL, + "Processing queue of incoming connections for channel " + "listener %p (global ID " U64_FORMAT ")", + listener, U64_PRINTF_ARG(listener->global_identifier)); + + if (!(listener->incoming_list)) return; + + SMARTLIST_FOREACH_BEGIN(listener->incoming_list, + channel_t *, chan) { + tor_assert(chan); + + log_debug(LD_CHANNEL, + "Handling incoming channel %p (" U64_FORMAT ") " + "for listener %p (" U64_FORMAT ")", + chan, + U64_PRINTF_ARG(chan->global_identifier), + listener, + U64_PRINTF_ARG(listener->global_identifier)); + /* Make sure this is set correctly */ + channel_mark_incoming(chan); + listener->listener(listener, chan); + } SMARTLIST_FOREACH_END(chan); + + smartlist_free(listener->incoming_list); + listener->incoming_list = NULL; +} + +/** + * Take actions required when a channel becomes open + * + * Handle actions we should do when we know a channel is open; a lot of + * this comes from the old connection_or_set_state_open() of connection_or.c. + * + * Because of this mechanism, future channel_t subclasses should take care + * not to change a channel to from CHANNEL_STATE_OPENING to CHANNEL_STATE_OPEN + * until there is positive confirmation that the network is operational. + * In particular, anything UDP-based should not make this transition until a + * packet is received from the other side. + */ + +void +channel_do_open_actions(channel_t *chan) +{ + tor_addr_t remote_addr; + int started_here, not_using = 0; + time_t now = time(NULL); + + tor_assert(chan); + + started_here = channel_is_outgoing(chan); + + if (started_here) { + circuit_build_times_network_is_live(&circ_times); + rep_hist_note_connect_succeeded(chan->identity_digest, now); + if (entry_guard_register_connect_status( + chan->identity_digest, 1, 0, now) < 0) { + /* Close any circuits pending on this channel. We leave it in state + * 'open' though, because it didn't actually *fail* -- we just + * chose not to use it. */ + log_debug(LD_OR, + "New entry guard was reachable, but closing this " + "connection so we can retry the earlier entry guards."); + circuit_n_chan_done(chan, 0); + not_using = 1; + } + router_set_status(chan->identity_digest, 1); + } else { + /* only report it to the geoip module if it's not a known router */ + if (!router_get_by_id_digest(chan->identity_digest)) { + if (channel_get_addr_if_possible(chan, &remote_addr)) { + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &remote_addr, + now); + } + /* Otherwise the underlying transport can't tell us this, so skip it */ + } + } + + if (!not_using) circuit_n_chan_done(chan, 1); +} + +/** + * Queue an incoming channel on a listener + * + * Internal and subclass use only function to queue an incoming channel from + * a listener. A subclass of channel_listener_t should call this when a new + * incoming channel is created. + */ + +void +channel_listener_queue_incoming(channel_listener_t *listener, + channel_t *incoming) +{ + int need_to_queue = 0; + + tor_assert(listener); + tor_assert(listener->state == CHANNEL_LISTENER_STATE_LISTENING); + tor_assert(incoming); + + log_debug(LD_CHANNEL, + "Queueing incoming channel %p (global ID " U64_FORMAT ") on " + "channel listener %p (global ID " U64_FORMAT ")", + incoming, U64_PRINTF_ARG(incoming->global_identifier), + listener, U64_PRINTF_ARG(listener->global_identifier)); + + /* Do we need to queue it, or can we just call the listener right away? */ + if (!(listener->listener)) need_to_queue = 1; + if (listener->incoming_list && + (smartlist_len(listener->incoming_list) > 0)) + need_to_queue = 1; + + /* If we need to queue and have no queue, create one */ + if (need_to_queue && !(listener->incoming_list)) { + listener->incoming_list = smartlist_new(); + } + + /* Bump the counter and timestamp it */ + channel_listener_timestamp_active(listener); + channel_listener_timestamp_accepted(listener); + ++(listener->n_accepted); + + /* If we don't need to queue, process it right away */ + if (!need_to_queue) { + tor_assert(listener->listener); + listener->listener(listener, incoming); + } + /* + * Otherwise, we need to queue; queue and then process the queue if + * we can. + */ + else { + tor_assert(listener->incoming_list); + smartlist_add(listener->incoming_list, incoming); + if (listener->listener) channel_listener_process_incoming(listener); + } +} + +/** + * Process queued incoming cells + * + * Process as many queued cells as we can from the incoming + * cell queue. + */ + +void +channel_process_cells(channel_t *chan) +{ + cell_queue_entry_t *q; + tor_assert(chan); + tor_assert(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_MAINT || + chan->state == CHANNEL_STATE_OPEN); + + log_debug(LD_CHANNEL, + "Processing as many incoming cells as we can for channel %p", + chan); + + /* Nothing we can do if we have no registered cell handlers */ + if (!(chan->cell_handler || + chan->var_cell_handler)) return; + /* Nothing we can do if we have no cells */ + if (SIMPLEQ_EMPTY(&chan->incoming_queue)) return; + + /* + * Process cells until we're done or find one we have no current handler + * for. + */ + while (NULL != (q = SIMPLEQ_FIRST(&chan->incoming_queue))) { + tor_assert(q); + tor_assert(q->type == CELL_QUEUE_FIXED || + q->type == CELL_QUEUE_VAR); + + if (q->type == CELL_QUEUE_FIXED && + chan->cell_handler) { + /* Handle a fixed-length cell */ + SIMPLEQ_REMOVE_HEAD(&chan->incoming_queue, next); + tor_assert(q->u.fixed.cell); + log_debug(LD_CHANNEL, + "Processing incoming cell_t %p for channel %p (global ID " + U64_FORMAT ")", + q->u.fixed.cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + chan->cell_handler(chan, q->u.fixed.cell); + tor_free(q); + } else if (q->type == CELL_QUEUE_VAR && + chan->var_cell_handler) { + /* Handle a variable-length cell */ + SIMPLEQ_REMOVE_HEAD(&chan->incoming_queue, next); + tor_assert(q->u.var.var_cell); + log_debug(LD_CHANNEL, + "Processing incoming var_cell_t %p for channel %p (global ID " + U64_FORMAT ")", + q->u.var.var_cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + chan->var_cell_handler(chan, q->u.var.var_cell); + tor_free(q); + } else { + /* Can't handle this one */ + break; + } + } +} + +/** + * Queue incoming cell + * + * This should be called by a channel_t subclass to queue an incoming fixed- + * length cell for processing, and process it if possible. + */ + +void +channel_queue_cell(channel_t *chan, cell_t *cell) +{ + int need_to_queue = 0; + cell_queue_entry_t *q; + + tor_assert(chan); + tor_assert(cell); + tor_assert(chan->state == CHANNEL_STATE_OPEN); + + /* Do we need to queue it, or can we just call the handler right away? */ + if (!(chan->cell_handler)) need_to_queue = 1; + if (! SIMPLEQ_EMPTY(&chan->incoming_queue)) + need_to_queue = 1; + + /* Timestamp for receiving */ + channel_timestamp_recv(chan); + + /* Update the counter */ + ++(chan->n_cells_recved); + + /* If we don't need to queue we can just call cell_handler */ + if (!need_to_queue) { + tor_assert(chan->cell_handler); + log_debug(LD_CHANNEL, + "Directly handling incoming cell_t %p for channel %p " + "(global ID " U64_FORMAT ")", + cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + chan->cell_handler(chan, cell); + } else { + /* Otherwise queue it and then process the queue if possible. */ + q = cell_queue_entry_new_fixed(cell); + log_debug(LD_CHANNEL, + "Queueing incoming cell_t %p for channel %p " + "(global ID " U64_FORMAT ")", + cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + SIMPLEQ_INSERT_TAIL(&chan->incoming_queue, q, next); + if (chan->cell_handler || + chan->var_cell_handler) { + channel_process_cells(chan); + } + } +} + +/** + * Queue incoming variable-length cell + * + * This should be called by a channel_t subclass to queue an incoming + * variable-length cell for processing, and process it if possible. + */ + +void +channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) +{ + int need_to_queue = 0; + cell_queue_entry_t *q; + + tor_assert(chan); + tor_assert(var_cell); + tor_assert(chan->state == CHANNEL_STATE_OPEN); + + /* Do we need to queue it, or can we just call the handler right away? */ + if (!(chan->var_cell_handler)) need_to_queue = 1; + if (! SIMPLEQ_EMPTY(&chan->incoming_queue)) + need_to_queue = 1; + + /* Timestamp for receiving */ + channel_timestamp_recv(chan); + + /* Update the counter */ + ++(chan->n_cells_recved); + + /* If we don't need to queue we can just call cell_handler */ + if (!need_to_queue) { + tor_assert(chan->var_cell_handler); + log_debug(LD_CHANNEL, + "Directly handling incoming var_cell_t %p for channel %p " + "(global ID " U64_FORMAT ")", + var_cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + chan->var_cell_handler(chan, var_cell); + } else { + /* Otherwise queue it and then process the queue if possible. */ + q = cell_queue_entry_new_var(var_cell); + log_debug(LD_CHANNEL, + "Queueing incoming var_cell_t %p for channel %p " + "(global ID " U64_FORMAT ")", + var_cell, chan, + U64_PRINTF_ARG(chan->global_identifier)); + SIMPLEQ_INSERT_TAIL(&chan->incoming_queue, q, next); + if (chan->cell_handler || + chan->var_cell_handler) { + channel_process_cells(chan); + } + } +} + +/** + * Send destroy cell on a channel + * + * Write a destroy cell with circ ID <b>circ_id</b> and reason <b>reason</b> + * onto channel <b>chan</b>. Don't perform range-checking on reason: + * we may want to propagate reasons from other cells. + */ + +int +channel_send_destroy(circid_t circ_id, channel_t *chan, int reason) +{ + cell_t cell; + + tor_assert(chan); + + /* Check to make sure we can send on this channel first */ + if (!(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)) { + memset(&cell, 0, sizeof(cell_t)); + cell.circ_id = circ_id; + cell.command = CELL_DESTROY; + cell.payload[0] = (uint8_t) reason; + log_debug(LD_OR, + "Sending destroy (circID %d) on channel %p " + "(global ID " U64_FORMAT ")", + circ_id, chan, + U64_PRINTF_ARG(chan->global_identifier)); + + channel_write_cell(chan, &cell); + } else { + log_warn(LD_BUG, + "Someone called channel_send_destroy() for circID %d " + "on a channel " U64_FORMAT " at %p in state %s (%d)", + circ_id, U64_PRINTF_ARG(chan->global_identifier), + chan, channel_state_to_string(chan->state), + chan->state); + } + + return 0; +} + +/** + * Dump channel statistics to the log + * + * This is called from dumpstats() in main.c and spams the log with + * statistics on channels. + */ + +void +channel_dumpstats(int severity) +{ + if (all_channels && smartlist_len(all_channels) > 0) { + log(severity, LD_GENERAL, + "Dumping statistics about %d channels:", + smartlist_len(all_channels)); + log(severity, LD_GENERAL, + "%d are active, and %d are done and waiting for cleanup", + (active_channels != NULL) ? + smartlist_len(active_channels) : 0, + (finished_channels != NULL) ? + smartlist_len(finished_channels) : 0); + + SMARTLIST_FOREACH(all_channels, channel_t *, chan, + channel_dump_statistics(chan, severity)); + + log(severity, LD_GENERAL, + "Done spamming about channels now"); + } else { + log(severity, LD_GENERAL, + "No channels to dump"); + } +} + +/** + * Dump channel listener statistics to the log + * + * This is called from dumpstats() in main.c and spams the log with + * statistics on channel listeners. + */ + +void +channel_listener_dumpstats(int severity) +{ + if (all_listeners && smartlist_len(all_listeners) > 0) { + log(severity, LD_GENERAL, + "Dumping statistics about %d channel listeners:", + smartlist_len(all_listeners)); + log(severity, LD_GENERAL, + "%d are active and %d are done and waiting for cleanup", + (active_listeners != NULL) ? + smartlist_len(active_listeners) : 0, + (finished_listeners != NULL) ? + smartlist_len(finished_listeners) : 0); + + SMARTLIST_FOREACH(all_listeners, channel_listener_t *, chan_l, + channel_listener_dump_statistics(chan_l, severity)); + + log(severity, LD_GENERAL, + "Done spamming about channel listeners now"); + } else { + log(severity, LD_GENERAL, + "No channel listeners to dump"); + } +} + +/** + * Set the cmux policy on all active channels + */ + +void +channel_set_cmux_policy_everywhere(circuitmux_policy_t *pol) +{ + if (!active_channels) return; + + SMARTLIST_FOREACH_BEGIN(active_channels, channel_t *, curr) { + if (curr->cmux) { + circuitmux_set_policy(curr->cmux, pol); + } + } SMARTLIST_FOREACH_END(curr); +} + +/** + * Clean up channels + * + * This gets called periodically from run_scheduled_events() in main.c; + * it cleans up after closed channels. + */ + +void +channel_run_cleanup(void) +{ + channel_t *tmp = NULL; + + /* Check if we need to do anything */ + if (!finished_channels || smartlist_len(finished_channels) == 0) return; + + /* Iterate through finished_channels and get rid of them */ + SMARTLIST_FOREACH_BEGIN(finished_channels, channel_t *, curr) { + tmp = curr; + /* Remove it from the list */ + SMARTLIST_DEL_CURRENT(finished_channels, curr); + /* Also unregister it */ + channel_unregister(tmp); + /* ... and free it */ + channel_free(tmp); + } SMARTLIST_FOREACH_END(curr); +} + +/** + * Clean up channel listeners + * + * This gets called periodically from run_scheduled_events() in main.c; + * it cleans up after closed channel listeners. + */ + +void +channel_listener_run_cleanup(void) +{ + channel_listener_t *tmp = NULL; + + /* Check if we need to do anything */ + if (!finished_listeners || smartlist_len(finished_listeners) == 0) return; + + /* Iterate through finished_channels and get rid of them */ + SMARTLIST_FOREACH_BEGIN(finished_listeners, channel_listener_t *, curr) { + tmp = curr; + /* Remove it from the list */ + SMARTLIST_DEL_CURRENT(finished_listeners, curr); + /* Also unregister it */ + channel_listener_unregister(tmp); + /* ... and free it */ + channel_listener_free(tmp); + } SMARTLIST_FOREACH_END(curr); +} + +/** + * Free a list of channels for channel_free_all() + */ + +static void +channel_free_list(smartlist_t *channels, int mark_for_close) +{ + if (!channels) return; + + SMARTLIST_FOREACH_BEGIN(channels, channel_t *, curr) { + /* Deregister and free it */ + tor_assert(curr); + log_debug(LD_CHANNEL, + "Cleaning up channel %p (global ID " U64_FORMAT ") " + "in state %s (%d)", + curr, U64_PRINTF_ARG(curr->global_identifier), + channel_state_to_string(curr->state), curr->state); + /* Detach circuits early so they can find the channel */ + if (curr->cmux) { + circuitmux_detach_all_circuits(curr->cmux); + } + channel_unregister(curr); + if (mark_for_close) { + if (!(curr->state == CHANNEL_STATE_CLOSING || + curr->state == CHANNEL_STATE_CLOSED || + curr->state == CHANNEL_STATE_ERROR)) { + channel_mark_for_close(curr); + } + channel_force_free(curr); + } else channel_free(curr); + } SMARTLIST_FOREACH_END(curr); +} + +/** + * Free a list of channel listeners for channel_free_all() + */ + +static void +channel_listener_free_list(smartlist_t *listeners, int mark_for_close) +{ + if (!listeners) return; + + SMARTLIST_FOREACH_BEGIN(listeners, channel_listener_t *, curr) { + /* Deregister and free it */ + tor_assert(curr); + log_debug(LD_CHANNEL, + "Cleaning up channel listener %p (global ID " U64_FORMAT ") " + "in state %s (%d)", + curr, U64_PRINTF_ARG(curr->global_identifier), + channel_listener_state_to_string(curr->state), curr->state); + channel_listener_unregister(curr); + if (mark_for_close) { + if (!(curr->state == CHANNEL_LISTENER_STATE_CLOSING || + curr->state == CHANNEL_LISTENER_STATE_CLOSED || + curr->state == CHANNEL_LISTENER_STATE_ERROR)) { + channel_listener_mark_for_close(curr); + } + channel_listener_force_free(curr); + } else channel_listener_free(curr); + } SMARTLIST_FOREACH_END(curr); +} + +/** + * Close all channels and free everything + * + * This gets called from tor_free_all() in main.c to clean up on exit. + * It will close all registered channels and free associated storage, + * then free the all_channels, active_channels, listening_channels and + * finished_channels lists and also channel_identity_map. + */ + +void +channel_free_all(void) +{ + log_debug(LD_CHANNEL, + "Shutting down channels..."); + + /* First, let's go for finished channels */ + if (finished_channels) { + channel_free_list(finished_channels, 0); + smartlist_free(finished_channels); + finished_channels = NULL; + } + + /* Now the finished listeners */ + if (finished_listeners) { + channel_listener_free_list(finished_listeners, 0); + smartlist_free(finished_listeners); + finished_listeners = NULL; + } + + /* Now all active channels */ + if (active_channels) { + channel_free_list(active_channels, 1); + smartlist_free(active_channels); + active_channels = NULL; + } + + /* Now all active listeners */ + if (active_listeners) { + channel_listener_free_list(active_listeners, 1); + smartlist_free(active_listeners); + active_listeners = NULL; + } + + /* Now all channels, in case any are left over */ + if (all_channels) { + channel_free_list(all_channels, 1); + smartlist_free(all_channels); + all_channels = NULL; + } + + /* Now all listeners, in case any are left over */ + if (all_listeners) { + channel_listener_free_list(all_listeners, 1); + smartlist_free(all_listeners); + all_listeners = NULL; + } + + /* Now free channel_identity_map */ + log_debug(LD_CHANNEL, + "Freeing channel_identity_map"); + /* Geez, anything still left over just won't die ... let it leak then */ + HT_CLEAR(channel_idmap, &channel_identity_map); + + log_debug(LD_CHANNEL, + "Done cleaning up after channels"); +} + +/** + * Connect to a given addr/port/digest + * + * This sets up a new outgoing channel; in the future if multiple + * channel_t subclasses are available, this is where the selection policy + * should go. It may also be desirable to fold port into tor_addr_t + * or make a new type including a tor_addr_t and port, so we have a + * single abstract object encapsulating all the protocol details of + * how to contact an OR. + */ + +channel_t * +channel_connect(const tor_addr_t *addr, uint16_t port, + const char *id_digest) +{ + return channel_tls_connect(addr, port, id_digest); +} + +/** + * Decide which of two channels to prefer for extending a circuit + * + * This function is called while extending a circuit and returns true iff + * a is 'better' than b. The most important criterion here is that a + * canonical channel is always better than a non-canonical one, but the + * number of circuits and the age are used as tie-breakers. + * + * This is based on the former connection_or_is_better() of connection_or.c + */ + +int +channel_is_better(time_t now, channel_t *a, channel_t *b, + int forgive_new_connections) +{ + int a_grace, b_grace; + int a_is_canonical, b_is_canonical; + int a_has_circs, b_has_circs; + + /* + * Do not definitively deprecate a new channel with no circuits on it + * until this much time has passed. + */ +#define NEW_CHAN_GRACE_PERIOD (15*60) + + tor_assert(a); + tor_assert(b); + + /* Check if one is canonical and the other isn't first */ + a_is_canonical = channel_is_canonical(a); + b_is_canonical = channel_is_canonical(b); + + if (a_is_canonical && !b_is_canonical) return 1; + if (!a_is_canonical && b_is_canonical) return 0; + + /* + * Okay, if we're here they tied on canonicity. Next we check if + * they have any circuits, and if one does and the other doesn't, + * we prefer the one that does, unless we are forgiving and the + * one that has no circuits is in its grace period. + */ + + a_has_circs = (channel_num_circuits(a) > 0); + b_has_circs = (channel_num_circuits(b) > 0); + a_grace = (forgive_new_connections && + (now < channel_when_created(a) + NEW_CHAN_GRACE_PERIOD)); + b_grace = (forgive_new_connections && + (now < channel_when_created(b) + NEW_CHAN_GRACE_PERIOD)); + + if (a_has_circs && !b_has_circs && !b_grace) return 1; + if (!a_has_circs && b_has_circs && !a_grace) return 0; + + /* They tied on circuits too; just prefer whichever is newer */ + + if (channel_when_created(a) > channel_when_created(b)) return 1; + else return 0; +} + +/** + * Get a channel to extend a circuit + * + * Pick a suitable channel to extend a circuit to given the desired digest + * the address we believe is correct for that digest; this tries to see + * if we already have one for the requested endpoint, but if there is no good + * channel, set *msg_out to a message describing the channel's state + * and our next action, and set *launch_out to a boolean indicated whether + * the caller should try to launch a new channel with channel_connect(). + */ + +channel_t * +channel_get_for_extend(const char *digest, + const tor_addr_t *target_addr, + const char **msg_out, + int *launch_out) +{ + channel_t *chan, *best = NULL; + int n_inprogress_goodaddr = 0, n_old = 0; + int n_noncanonical = 0, n_possible = 0; + time_t now = approx_time(); + + tor_assert(msg_out); + tor_assert(launch_out); + + chan = channel_find_by_remote_digest(digest); + + /* Walk the list, unrefing the old one and refing the new at each + * iteration. + */ + for (; chan; chan = channel_next_with_digest(chan)) { + tor_assert(tor_memeq(chan->identity_digest, + digest, DIGEST_LEN)); + + if (chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR) + continue; + + /* Never return a channel on which the other end appears to be + * a client. */ + if (channel_is_client(chan)) { + continue; + } + + /* Never return a non-open connection. */ + if (chan->state != CHANNEL_STATE_OPEN) { + /* If the address matches, don't launch a new connection for this + * circuit. */ + if (!channel_matches_target_addr_for_extend(chan, target_addr)) + ++n_inprogress_goodaddr; + continue; + } + + /* Never return a connection that shouldn't be used for circs. */ + if (channel_is_bad_for_new_circs(chan)) { + ++n_old; + continue; + } + + /* Never return a non-canonical connection using a recent link protocol + * if the address is not what we wanted. + * + * The channel_is_canonical_is_reliable() function asks the lower layer + * if we should trust channel_is_canonical(). The below is from the + * comments of the old circuit_or_get_for_extend() and applies when + * the lower-layer transport is channel_tls_t. + * + * (For old link protocols, we can't rely on is_canonical getting + * set properly if we're talking to the right address, since we might + * have an out-of-date descriptor, and we will get no NETINFO cell to + * tell us about the right address.) + */ + if (!channel_is_canonical(chan) && + channel_is_canonical_is_reliable(chan) && + !channel_matches_target_addr_for_extend(chan, target_addr)) { + ++n_noncanonical; + continue; + } + + ++n_possible; + + if (!best) { + best = chan; /* If we have no 'best' so far, this one is good enough. */ + continue; + } + + if (channel_is_better(now, chan, best, 0)) + best = chan; + } + + if (best) { + *msg_out = "Connection is fine; using it."; + *launch_out = 0; + return best; + } else if (n_inprogress_goodaddr) { + *msg_out = "Connection in progress; waiting."; + *launch_out = 0; + return NULL; + } else if (n_old || n_noncanonical) { + *msg_out = "Connections all too old, or too non-canonical. " + " Launching a new one."; + *launch_out = 1; + return NULL; + } else { + *msg_out = "Not connected. Connecting."; + *launch_out = 1; + return NULL; + } +} + +/** + * Describe the transport subclass for a channel + * + * Invoke a method to get a string description of the lower-layer + * transport for this channel. + */ + +const char * +channel_describe_transport(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->describe_transport); + + return chan->describe_transport(chan); +} + +/** + * Describe the transport subclass for a channel listener + * + * Invoke a method to get a string description of the lower-layer + * transport for this channel listener. + */ + +const char * +channel_listener_describe_transport(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + tor_assert(chan_l->describe_transport); + + return chan_l->describe_transport(chan_l); +} + +/** + * Return the number of entries in <b>queue</b> + */ +static int +chan_cell_queue_len(const chan_cell_queue_t *queue) +{ + int r = 0; + cell_queue_entry_t *cell; + SIMPLEQ_FOREACH(cell, queue, next) + ++r; + return r; +} + +/** + * Dump channel statistics + * + * Dump statistics for one channel to the log + */ + +void +channel_dump_statistics(channel_t *chan, int severity) +{ + double avg, interval, age; + time_t now = time(NULL); + tor_addr_t remote_addr; + int have_remote_addr; + char *remote_addr_str; + + tor_assert(chan); + + age = (double)(now - chan->timestamp_created); + + log(severity, LD_GENERAL, + "Channel " U64_FORMAT " (at %p) with transport %s is in state " + "%s (%d)", + U64_PRINTF_ARG(chan->global_identifier), chan, + channel_describe_transport(chan), + channel_state_to_string(chan->state), chan->state); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " was created at " U64_FORMAT + " (" U64_FORMAT " seconds ago) " + "and last active at " U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->timestamp_created), + U64_PRINTF_ARG(now - chan->timestamp_created), + U64_PRINTF_ARG(chan->timestamp_active), + U64_PRINTF_ARG(now - chan->timestamp_active)); + + /* Handle digest and nickname */ + if (!tor_digest_is_zero(chan->identity_digest)) { + if (chan->nickname) { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " says it is connected " + "to an OR with digest %s and nickname %s", + U64_PRINTF_ARG(chan->global_identifier), + hex_str(chan->identity_digest, DIGEST_LEN), + chan->nickname); + } else { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " says it is connected " + "to an OR with digest %s and no known nickname", + U64_PRINTF_ARG(chan->global_identifier), + hex_str(chan->identity_digest, DIGEST_LEN)); + } + } else { + if (chan->nickname) { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " does not know the digest" + " of the OR it is connected to, but reports its nickname is %s", + U64_PRINTF_ARG(chan->global_identifier), + chan->nickname); + } else { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " does not know the digest" + " or the nickname of the OR it is connected to", + U64_PRINTF_ARG(chan->global_identifier)); + } + } + + /* Handle remote address and descriptions */ + have_remote_addr = channel_get_addr_if_possible(chan, &remote_addr); + if (have_remote_addr) { + char *actual = tor_strdup(channel_get_actual_remote_descr(chan)); + remote_addr_str = tor_dup_addr(&remote_addr); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " says its remote address" + " is %s, and gives a canonical description of \"%s\" and an " + "actual description of \"%s\"", + U64_PRINTF_ARG(chan->global_identifier), + remote_addr_str, + channel_get_canonical_remote_descr(chan), + actual); + tor_free(remote_addr_str); + tor_free(actual); + } else { + char *actual = tor_strdup(channel_get_actual_remote_descr(chan)); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " does not know its remote " + "address, but gives a canonical description of \"%s\" and an " + "actual description of \"%s\"", + U64_PRINTF_ARG(chan->global_identifier), + channel_get_canonical_remote_descr(chan), + actual); + tor_free(actual); + } + + /* Handle marks */ + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has these marks: %s %s %s " + "%s %s %s", + U64_PRINTF_ARG(chan->global_identifier), + channel_is_bad_for_new_circs(chan) ? + "bad_for_new_circs" : "!bad_for_new_circs", + channel_is_canonical(chan) ? + "canonical" : "!canonical", + channel_is_canonical_is_reliable(chan) ? + "is_canonical_is_reliable" : + "!is_canonical_is_reliable", + channel_is_client(chan) ? + "client" : "!client", + channel_is_local(chan) ? + "local" : "!local", + channel_is_incoming(chan) ? + "incoming" : "outgoing"); + + /* Describe queues */ + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has %d queued incoming cells" + " and %d queued outgoing cells", + U64_PRINTF_ARG(chan->global_identifier), + chan_cell_queue_len(&chan->incoming_queue), + chan_cell_queue_len(&chan->outgoing_queue)); + + /* Describe circuits */ + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has %d active circuits out of" + " %d in total", + U64_PRINTF_ARG(chan->global_identifier), + (chan->cmux != NULL) ? + circuitmux_num_active_circuits(chan->cmux) : 0, + (chan->cmux != NULL) ? + circuitmux_num_circuits(chan->cmux) : 0); + + /* Describe timestamps */ + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " was last used by a " + "client at " U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->timestamp_client), + U64_PRINTF_ARG(now - chan->timestamp_client)); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " was last drained at " + U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->timestamp_drained), + U64_PRINTF_ARG(now - chan->timestamp_drained)); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " last received a cell " + "at " U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->timestamp_recv), + U64_PRINTF_ARG(now - chan->timestamp_recv)); + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " last trasmitted a cell " + "at " U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->timestamp_xmit), + U64_PRINTF_ARG(now - chan->timestamp_xmit)); + + /* Describe counters and rates */ + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has received " + U64_FORMAT " cells and transmitted " U64_FORMAT, + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->n_cells_recved), + U64_PRINTF_ARG(chan->n_cells_xmitted)); + if (now > chan->timestamp_created && + chan->timestamp_created > 0) { + if (chan->n_cells_recved > 0) { + avg = (double)(chan->n_cells_recved) / age; + if (avg >= 1.0) { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "cells received per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } else if (avg >= 0.0) { + interval = 1.0 / avg; + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "seconds between received cells", + U64_PRINTF_ARG(chan->global_identifier), interval); + } + } + if (chan->n_cells_xmitted > 0) { + avg = (double)(chan->n_cells_xmitted) / age; + if (avg >= 1.0) { + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "cells transmitted per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } else if (avg >= 0.0) { + interval = 1.0 / avg; + log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "seconds between transmitted cells", + U64_PRINTF_ARG(chan->global_identifier), interval); + } + } + } + + /* Dump anything the lower layer has to say */ + channel_dump_transport_statistics(chan, severity); +} + +/** + * Dump channel listener statistics + * + * Dump statistics for one channel listener to the log + */ + +void +channel_listener_dump_statistics(channel_listener_t *chan_l, int severity) +{ + double avg, interval, age; + time_t now = time(NULL); + + tor_assert(chan_l); + + age = (double)(now - chan_l->timestamp_created); + + log(severity, LD_GENERAL, + "Channel listener " U64_FORMAT " (at %p) with transport %s is in " + "state %s (%d)", + U64_PRINTF_ARG(chan_l->global_identifier), chan_l, + channel_listener_describe_transport(chan_l), + channel_listener_state_to_string(chan_l->state), chan_l->state); + log(severity, LD_GENERAL, + " * Channel listener " U64_FORMAT " was created at " U64_FORMAT + " (" U64_FORMAT " seconds ago) " + "and last active at " U64_FORMAT " (" U64_FORMAT " seconds ago)", + U64_PRINTF_ARG(chan_l->global_identifier), + U64_PRINTF_ARG(chan_l->timestamp_created), + U64_PRINTF_ARG(now - chan_l->timestamp_created), + U64_PRINTF_ARG(chan_l->timestamp_active), + U64_PRINTF_ARG(now - chan_l->timestamp_active)); + + log(severity, LD_GENERAL, + " * Channel listener " U64_FORMAT " last accepted an incoming " + "channel at " U64_FORMAT " (" U64_FORMAT " seconds ago) " + "and has accepted " U64_FORMAT " channels in total", + U64_PRINTF_ARG(chan_l->global_identifier), + U64_PRINTF_ARG(chan_l->timestamp_accepted), + U64_PRINTF_ARG(now - chan_l->timestamp_accepted), + U64_PRINTF_ARG(chan_l->n_accepted)); + + /* + * If it's sensible to do so, get the rate of incoming channels on this + * listener + */ + if (now > chan_l->timestamp_created && + chan_l->timestamp_created > 0 && + chan_l->n_accepted > 0) { + avg = (double)(chan_l->n_accepted) / age; + if (avg >= 1.0) { + log(severity, LD_GENERAL, + " * Channel listener " U64_FORMAT " has averaged %f incoming " + "channels per second", + U64_PRINTF_ARG(chan_l->global_identifier), avg); + } else if (avg >= 0.0) { + interval = 1.0 / avg; + log(severity, LD_GENERAL, + " * Channel listener " U64_FORMAT " has averaged %f seconds " + "between incoming channels", + U64_PRINTF_ARG(chan_l->global_identifier), interval); + } + } + + /* Dump anything the lower layer has to say */ + channel_listener_dump_transport_statistics(chan_l, severity); +} + +/** + * Invoke transport-specific stats dump for channel + * + * If there is a lower-layer statistics dump method, invoke it + */ + +void +channel_dump_transport_statistics(channel_t *chan, int severity) +{ + tor_assert(chan); + + if (chan->dumpstats) chan->dumpstats(chan, severity); +} + +/** + * Invoke transport-specific stats dump for channel listener + * + * If there is a lower-layer statistics dump method, invoke it + */ + +void +channel_listener_dump_transport_statistics(channel_listener_t *chan_l, + int severity) +{ + tor_assert(chan_l); + + if (chan_l->dumpstats) chan_l->dumpstats(chan_l, severity); +} + +/** + * Return text description of the remote endpoint + * + * This function return a test provided by the lower layer of the remote + * endpoint for this channel; it should specify the actual address connected + * to/from. + * + * Subsequent calls to channel_get_{actual,canonical}_remote_{address,descr} + * may invalidate the return value from this function. + */ +const char * +channel_get_actual_remote_descr(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->get_remote_descr); + + /* Param 1 indicates the actual description */ + return chan->get_remote_descr(chan, GRD_FLAG_ORIGINAL); +} + +/** + * Return the text address of the remote endpoint. + * + * Subsequent calls to channel_get_{actual,canonical}_remote_{address,descr} + * may invalidate the return value from this function. + */ +const char * +channel_get_actual_remote_address(channel_t *chan) +{ + /* Param 1 indicates the actual description */ + return chan->get_remote_descr(chan, GRD_FLAG_ORIGINAL|GRD_FLAG_ADDR_ONLY); +} + +/** + * Return text description of the remote endpoint canonical address + * + * This function return a test provided by the lower layer of the remote + * endpoint for this channel; it should use the known canonical address for + * this OR's identity digest if possible. + * + * Subsequent calls to channel_get_{actual,canonical}_remote_{address,descr} + * may invalidate the return value from this function. + */ +const char * +channel_get_canonical_remote_descr(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->get_remote_descr); + + /* Param 0 indicates the canonicalized description */ + return chan->get_remote_descr(chan, 0); +} + +/** + * Get remote address if possible. + * + * Write the remote address out to a tor_addr_t if the underlying transport + * supports this operation, and return 1. Return 0 if the underlying transport + * doesn't let us do this. + */ +int +channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out) +{ + tor_assert(chan); + tor_assert(addr_out); + + if (chan->get_remote_addr) + return chan->get_remote_addr(chan, addr_out); + /* Else no support, method not implemented */ + else return 0; +} + +/** + * Check if there are outgoing queue writes on this channel + * + * Indicate if either we have queued cells, or if not, whether the underlying + * lower-layer transport thinks it has an output queue. + */ + +int +channel_has_queued_writes(channel_t *chan) +{ + int has_writes = 0; + + tor_assert(chan); + tor_assert(chan->has_queued_writes); + + if (! SIMPLEQ_EMPTY(&chan->outgoing_queue)) { + has_writes = 1; + } else { + /* Check with the lower layer */ + has_writes = chan->has_queued_writes(chan); + } + + return has_writes; +} + +/** + * Check the is_bad_for_new_circs flag + * + * This function returns the is_bad_for_new_circs flag of the specified + * channel. + */ + +int +channel_is_bad_for_new_circs(channel_t *chan) +{ + tor_assert(chan); + + return chan->is_bad_for_new_circs; +} + +/** + * Mark a channel as bad for new circuits + * + * Set the is_bad_for_new_circs_flag on chan. + */ + +void +channel_mark_bad_for_new_circs(channel_t *chan) +{ + tor_assert(chan); + + chan->is_bad_for_new_circs = 1; +} + +/** + * Get the client flag + * + * This returns the client flag of a channel, which will be set if + * command_process_create_cell() in command.c thinks this is a connection + * from a client. + */ + +int +channel_is_client(channel_t *chan) +{ + tor_assert(chan); + + return chan->is_client; +} + +/** + * Set the client flag + * + * Mark a channel as being from a client + */ + +void +channel_mark_client(channel_t *chan) +{ + tor_assert(chan); + + chan->is_client = 1; +} + +/** + * Get the canonical flag for a channel + * + * This returns the is_canonical for a channel; this flag is determined by + * the lower layer and can't be set in a transport-independent way. + */ + +int +channel_is_canonical(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->is_canonical); + + return chan->is_canonical(chan, 0); +} + +/** + * Test if the canonical flag is reliable + * + * This function asks if the lower layer thinks it's safe to trust the + * result of channel_is_canonical() + */ + +int +channel_is_canonical_is_reliable(channel_t *chan) +{ + tor_assert(chan); + tor_assert(chan->is_canonical); + + return chan->is_canonical(chan, 1); +} + +/** + * Test incoming flag + * + * This function gets the incoming flag; this is set when a listener spawns + * a channel. If this returns true the channel was remotely initiated. + */ + +int +channel_is_incoming(channel_t *chan) +{ + tor_assert(chan); + + return chan->is_incoming; +} + +/** + * Set the incoming flag + * + * This function is called when a channel arrives on a listening channel + * to mark it as incoming. + */ + +void +channel_mark_incoming(channel_t *chan) +{ + tor_assert(chan); + + chan->is_incoming = 1; +} + +/** + * Test local flag + * + * This function gets the local flag; the lower layer should set this when + * setting up the channel if is_local_addr() is true for all of the + * destinations it will communicate with on behalf of this channel. It's + * used to decide whether to declare the network reachable when seeing incoming + * traffic on the channel. + */ + +int +channel_is_local(channel_t *chan) +{ + tor_assert(chan); + + return chan->is_local; +} + +/** + * Set the local flag + * + * This internal-only function should be called by the lower layer if the + * channel is to a local address. See channel_is_local() above or the + * description of the is_local bit in channel.h + */ + +void +channel_mark_local(channel_t *chan) +{ + tor_assert(chan); + + chan->is_local = 1; +} + +/** + * Test outgoing flag + * + * This function gets the outgoing flag; this is the inverse of the incoming + * bit set when a listener spawns a channel. If this returns true the channel + * was locally initiated. + */ + +int +channel_is_outgoing(channel_t *chan) +{ + tor_assert(chan); + + return !(chan->is_incoming); +} + +/** + * Mark a channel as outgoing + * + * This function clears the incoming flag and thus marks a channel as + * outgoing. + */ + +void +channel_mark_outgoing(channel_t *chan) +{ + tor_assert(chan); + + chan->is_incoming = 0; +} + +/********************* + * Timestamp updates * + ********************/ + +/** + * Update the created timestamp for a channel + * + * This updates the channel's created timestamp and should only be called + * from channel_init(). + */ + +void +channel_timestamp_created(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_created = now; +} + +/** + * Update the created timestamp for a channel listener + * + * This updates the channel listener's created timestamp and should only be + * called from channel_init_listener(). + */ + +void +channel_listener_timestamp_created(channel_listener_t *chan_l) +{ + time_t now = time(NULL); + + tor_assert(chan_l); + + chan_l->timestamp_created = now; +} + +/** + * Update the last active timestamp for a channel + * + * This function updates the channel's last active timestamp; it should be + * called by the lower layer whenever there is activity on the channel which + * does not lead to a cell being transmitted or received; the active timestamp + * is also updated from channel_timestamp_recv() and channel_timestamp_xmit(), + * but it should be updated for things like the v3 handshake and stuff that + * produce activity only visible to the lower layer. + */ + +void +channel_timestamp_active(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_active = now; +} + +/** + * Update the last active timestamp for a channel listener + */ + +void +channel_listener_timestamp_active(channel_listener_t *chan_l) +{ + time_t now = time(NULL); + + tor_assert(chan_l); + + chan_l->timestamp_active = now; +} + +/** + * Update the last accepted timestamp. + * + * This function updates the channel listener's last accepted timestamp; it + * should be called whenever a new incoming channel is accepted on a + * listener. + */ + +void +channel_listener_timestamp_accepted(channel_listener_t *chan_l) +{ + time_t now = time(NULL); + + tor_assert(chan_l); + + chan_l->timestamp_active = now; + chan_l->timestamp_accepted = now; +} + +/** + * Update client timestamp + * + * This function is called by relay.c to timestamp a channel that appears to + * be used as a client. + */ + +void +channel_timestamp_client(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_client = now; +} + +/** + * Update the last drained timestamp + * + * This is called whenever we transmit a cell which leaves the outgoing cell + * queue completely empty. It also updates the xmit time and the active time. + */ + +void +channel_timestamp_drained(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_active = now; + chan->timestamp_drained = now; + chan->timestamp_xmit = now; +} + +/** + * Update the recv timestamp + * + * This is called whenever we get an incoming cell from the lower layer. + * This also updates the active timestamp. + */ + +void +channel_timestamp_recv(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_active = now; + chan->timestamp_recv = now; +} + +/** + * Update the xmit timestamp + * This is called whenever we pass an outgoing cell to the lower layer. This + * also updates the active timestamp. + */ + +void +channel_timestamp_xmit(channel_t *chan) +{ + time_t now = time(NULL); + + tor_assert(chan); + + chan->timestamp_active = now; + chan->timestamp_xmit = now; +} + +/*************************************************************** + * Timestamp queries - see above for definitions of timestamps * + **************************************************************/ + +/** + * Query created timestamp for a channel + */ + +time_t +channel_when_created(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_created; +} + +/** + * Query created timestamp for a channel listener + */ + +time_t +channel_listener_when_created(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + return chan_l->timestamp_created; +} + +/** + * Query last active timestamp for a channel + */ + +time_t +channel_when_last_active(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_active; +} + +/** + * Query last active timestamp for a channel listener + */ + +time_t +channel_listener_when_last_active(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + return chan_l->timestamp_active; +} + +/** + * Query last accepted timestamp for a channel listener + */ + +time_t +channel_listener_when_last_accepted(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + return chan_l->timestamp_accepted; +} + +/** + * Query client timestamp + */ + +time_t +channel_when_last_client(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_client; +} + +/** + * Query drained timestamp + */ + +time_t +channel_when_last_drained(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_drained; +} + +/** + * Query recv timestamp + */ + +time_t +channel_when_last_recv(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_recv; +} + +/** + * Query xmit timestamp + */ + +time_t +channel_when_last_xmit(channel_t *chan) +{ + tor_assert(chan); + + return chan->timestamp_xmit; +} + +/** + * Query accepted counter + */ + +uint64_t +channel_listener_count_accepted(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + return chan_l->n_accepted; +} + +/** + * Query received cell counter + */ + +uint64_t +channel_count_recved(channel_t *chan) +{ + tor_assert(chan); + + return chan->n_cells_recved; +} + +/** + * Query transmitted cell counter + */ + +uint64_t +channel_count_xmitted(channel_t *chan) +{ + tor_assert(chan); + + return chan->n_cells_xmitted; +} + +/** + * Check if a channel matches an extend_info_t + * + * This function calls the lower layer and asks if this channel matches a + * given extend_info_t. + */ + +int +channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info) +{ + tor_assert(chan); + tor_assert(chan->matches_extend_info); + tor_assert(extend_info); + + return chan->matches_extend_info(chan, extend_info); +} + +/** + * Check if a channel matches a given target address + * + * This function calls into the lower layer and asks if this channel thinks + * it matches a given target address for circuit extension purposes. + */ + +int +channel_matches_target_addr_for_extend(channel_t *chan, + const tor_addr_t *target) +{ + tor_assert(chan); + tor_assert(chan->matches_target); + tor_assert(target); + + return chan->matches_target(chan, target); +} + +/** + * Return the total number of circuits used by a channel + * + * @param chan Channel to query + * @return Number of circuits using this as n_chan or p_chan + */ + +unsigned int +channel_num_circuits(channel_t *chan) +{ + tor_assert(chan); + + return chan->num_n_circuits + + chan->num_p_circuits; +} + +/** + * Set up circuit ID generation + * + * This is called when setting up a channel and replaces the old + * connection_or_set_circid_type() + */ + +void +channel_set_circid_type(channel_t *chan, crypto_pk_t *identity_rcvd) +{ + int started_here; + crypto_pk_t *our_identity; + + tor_assert(chan); + + started_here = channel_is_outgoing(chan); + our_identity = started_here ? + get_tlsclient_identity_key() : get_server_identity_key(); + + if (identity_rcvd) { + if (crypto_pk_cmp_keys(our_identity, identity_rcvd) < 0) { + chan->circ_id_type = CIRC_ID_TYPE_LOWER; + } else { + chan->circ_id_type = CIRC_ID_TYPE_HIGHER; + } + } else { + chan->circ_id_type = CIRC_ID_TYPE_NEITHER; + } +} + diff --git a/src/or/channel.h b/src/or/channel.h new file mode 100644 index 000000000..d2106551a --- /dev/null +++ b/src/or/channel.h @@ -0,0 +1,478 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file channel.h + * \brief Header file for channel.c + **/ + +#ifndef TOR_CHANNEL_H +#define TOR_CHANNEL_H + +#include "or.h" +#include "tor_queue.h" +#include "circuitmux.h" + +/* Channel handler function pointer typedefs */ +typedef void (*channel_listener_fn_ptr)(channel_listener_t *, channel_t *); +typedef void (*channel_cell_handler_fn_ptr)(channel_t *, cell_t *); +typedef void (*channel_var_cell_handler_fn_ptr)(channel_t *, var_cell_t *); + +struct cell_queue_entry_s; +SIMPLEQ_HEAD(chan_cell_queue, cell_queue_entry_s) incoming_queue; +typedef struct chan_cell_queue chan_cell_queue_t; + +/* + * Channel struct; see the channel_t typedef in or.h. A channel is an + * abstract interface for the OR-to-OR connection, similar to connection_or_t, + * but without the strong coupling to the underlying TLS implementation. They + * are constructed by calling a protocol-specific function to open a channel + * to a particular node, and once constructed support the abstract operations + * defined below. + */ + +struct channel_s { + /* Magic number for type-checking cast macros */ + uint32_t magic; + + /* Current channel state */ + channel_state_t state; + + /* Globally unique ID number for a channel over the lifetime of a Tor + * process. + */ + uint64_t global_identifier; + + /* Should we expect to see this channel in the channel lists? */ + unsigned char registered:1; + + /** Why did we close? + */ + enum { + CHANNEL_NOT_CLOSING = 0, + CHANNEL_CLOSE_REQUESTED, + CHANNEL_CLOSE_FROM_BELOW, + CHANNEL_CLOSE_FOR_ERROR + } reason_for_closing; + + /* Timestamps for both cell channels and listeners */ + time_t timestamp_created; /* Channel created */ + time_t timestamp_active; /* Any activity */ + + /* Methods implemented by the lower layer */ + + /* Free a channel */ + void (*free)(channel_t *); + /* Close an open channel */ + void (*close)(channel_t *); + /* Describe the transport subclass for this channel */ + const char * (*describe_transport)(channel_t *); + /* Optional method to dump transport-specific statistics on the channel */ + void (*dumpstats)(channel_t *, int); + + /* Registered handlers for incoming cells */ + channel_cell_handler_fn_ptr cell_handler; + channel_var_cell_handler_fn_ptr var_cell_handler; + + /* Methods implemented by the lower layer */ + + /* + * Ask the underlying transport what the remote endpoint address is, in + * a tor_addr_t. This is optional and subclasses may leave this NULL. + * If they implement it, they should write the address out to the + * provided tor_addr_t *, and return 1 if successful or 0 if no address + * available. + */ + int (*get_remote_addr)(channel_t *, tor_addr_t *); +#define GRD_FLAG_ORIGINAL 1 +#define GRD_FLAG_ADDR_ONLY 2 + /* + * Get a text description of the remote endpoint; canonicalized if the flag + * GRD_FLAG_ORIGINAL is not set, or the one we originally connected + * to/received from if it is. If GRD_FLAG_ADDR_ONLY is set, we return only + * the original address. + */ + const char * (*get_remote_descr)(channel_t *, int); + /* Check if the lower layer has queued writes */ + int (*has_queued_writes)(channel_t *); + /* + * If the second param is zero, ask the lower layer if this is + * 'canonical', for a transport-specific definition of canonical; if + * it is 1, ask if the answer to the preceding query is safe to rely + * on. + */ + int (*is_canonical)(channel_t *, int); + /* Check if this channel matches a specified extend_info_t */ + int (*matches_extend_info)(channel_t *, extend_info_t *); + /* Check if this channel matches a target address when extending */ + int (*matches_target)(channel_t *, const tor_addr_t *); + /* Write a cell to an open channel */ + int (*write_cell)(channel_t *, cell_t *); + /* Write a packed cell to an open channel */ + int (*write_packed_cell)(channel_t *, packed_cell_t *); + /* Write a variable-length cell to an open channel */ + int (*write_var_cell)(channel_t *, var_cell_t *); + + /* + * Hash of the public RSA key for the other side's identity key, or + * zeroes if the other side hasn't shown us a valid identity key. + */ + char identity_digest[DIGEST_LEN]; + /* Nickname of the OR on the other side, or NULL if none. */ + char *nickname; + + /* + * Linked list of channels with the same identity digest, for the + * digest->channel map + */ + LIST_ENTRY(channel_s) next_with_same_id; + + /* List of incoming cells to handle */ + chan_cell_queue_t incoming_queue; + + /* List of queued outgoing cells */ + chan_cell_queue_t outgoing_queue; + + /* Circuit mux for circuits sending on this channel */ + circuitmux_t *cmux; + + /* Circuit ID generation stuff for use by circuitbuild.c */ + + /* + * When we send CREATE cells along this connection, which half of the + * space should we use? + */ + circ_id_type_t circ_id_type:2; + /* + * Which circ_id do we try to use next on this connection? This is + * always in the range 0..1<<15-1. + */ + circid_t next_circ_id; + + /* For how many circuits are we n_chan? What about p_chan? */ + unsigned int num_n_circuits, num_p_circuits; + + /* + * True iff this channel shouldn't get any new circs attached to it, + * because the connection is too old, or because there's a better one. + * More generally, this flag is used to note an unhealthy connection; + * for example, if a bad connection fails we shouldn't assume that the + * router itself has a problem. + */ + unsigned int is_bad_for_new_circs:1; + + /** True iff we have decided that the other end of this connection + * is a client. Channels with this flag set should never be used + * to satisfy an EXTEND request. */ + unsigned int is_client:1; + + /** Set if the channel was initiated remotely (came from a listener) */ + unsigned int is_incoming:1; + + /** Set by lower layer if this is local; i.e., everything it communicates + * with for this channel returns true for is_local_addr(). This is used + * to decide whether to declare reachability when we receive something on + * this channel in circuitbuild.c + */ + unsigned int is_local:1; + + /** Channel timestamps for cell channels */ + time_t timestamp_client; /* Client used this, according to relay.c */ + time_t timestamp_drained; /* Output queue empty */ + time_t timestamp_recv; /* Cell received from lower layer */ + time_t timestamp_xmit; /* Cell sent to lower layer */ + + /* Timestamp for relay.c */ + time_t timestamp_last_added_nonpadding; + + /** Unique ID for measuring direct network status requests;vtunneled ones + * come over a circuit_t, which has a dirreq_id field as well, but is a + * distinct namespace. */ + uint64_t dirreq_id; + + /** Channel counters for cell channels */ + uint64_t n_cells_recved; + uint64_t n_cells_xmitted; +}; + +struct channel_listener_s { + /* Current channel listener state */ + channel_listener_state_t state; + + /* Globally unique ID number for a channel over the lifetime of a Tor + * process. + */ + uint64_t global_identifier; + + /* Should we expect to see this channel in the channel lists? */ + unsigned char registered:1; + + /** Why did we close? + */ + enum { + CHANNEL_LISTENER_NOT_CLOSING = 0, + CHANNEL_LISTENER_CLOSE_REQUESTED, + CHANNEL_LISTENER_CLOSE_FROM_BELOW, + CHANNEL_LISTENER_CLOSE_FOR_ERROR + } reason_for_closing; + + /* Timestamps for both cell channels and listeners */ + time_t timestamp_created; /* Channel created */ + time_t timestamp_active; /* Any activity */ + + /* Methods implemented by the lower layer */ + + /* Free a channel */ + void (*free)(channel_listener_t *); + /* Close an open channel */ + void (*close)(channel_listener_t *); + /* Describe the transport subclass for this channel */ + const char * (*describe_transport)(channel_listener_t *); + /* Optional method to dump transport-specific statistics on the channel */ + void (*dumpstats)(channel_listener_t *, int); + + /* Registered listen handler to call on incoming connection */ + channel_listener_fn_ptr listener; + + /* List of pending incoming connections */ + smartlist_t *incoming_list; + + /* Timestamps for listeners */ + time_t timestamp_accepted; + + /* Counters for listeners */ + uint64_t n_accepted; +}; + +/* Channel state manipulations */ + +int channel_state_is_valid(channel_state_t state); +int channel_listener_state_is_valid(channel_listener_state_t state); + +int channel_state_can_transition(channel_state_t from, channel_state_t to); +int channel_listener_state_can_transition(channel_listener_state_t from, + channel_listener_state_t to); + +const char * channel_state_to_string(channel_state_t state); +const char * +channel_listener_state_to_string(channel_listener_state_t state); + +/* Abstract channel operations */ + +void channel_mark_for_close(channel_t *chan); +void channel_write_cell(channel_t *chan, cell_t *cell); +void channel_write_packed_cell(channel_t *chan, packed_cell_t *cell); +void channel_write_var_cell(channel_t *chan, var_cell_t *cell); + +void channel_listener_mark_for_close(channel_listener_t *chan_l); + +/* Channel callback registrations */ + +/* Listener callback */ +channel_listener_fn_ptr +channel_listener_get_listener_fn(channel_listener_t *chan); + +void channel_listener_set_listener_fn(channel_listener_t *chan, + channel_listener_fn_ptr listener); + +/* Incoming cell callbacks */ +channel_cell_handler_fn_ptr channel_get_cell_handler(channel_t *chan); + +channel_var_cell_handler_fn_ptr +channel_get_var_cell_handler(channel_t *chan); + +void channel_set_cell_handlers(channel_t *chan, + channel_cell_handler_fn_ptr cell_handler, + channel_var_cell_handler_fn_ptr + var_cell_handler); + +/* Clean up closed channels and channel listeners periodically; these are + * called from run_scheduled_events() in main.c. + */ +void channel_run_cleanup(void); +void channel_listener_run_cleanup(void); + +/* Close all channels and deallocate everything */ +void channel_free_all(void); + +/* Dump some statistics in the log */ +void channel_dumpstats(int severity); +void channel_listener_dumpstats(int severity); + +/* Set the cmux policy on all active channels */ +void channel_set_cmux_policy_everywhere(circuitmux_policy_t *pol); + +#ifdef TOR_CHANNEL_INTERNAL_ + +/* Channel operations for subclasses and internal use only */ + +/* Initialize a newly allocated channel - do this first in subclass + * constructors. + */ + +void channel_init(channel_t *chan); +void channel_init_listener(channel_listener_t *chan); + +/* Channel registration/unregistration */ +void channel_register(channel_t *chan); +void channel_unregister(channel_t *chan); + +/* Channel listener registration/unregistration */ +void channel_listener_register(channel_listener_t *chan_l); +void channel_listener_unregister(channel_listener_t *chan_l); + +/* Close from below */ +void channel_close_from_lower_layer(channel_t *chan); +void channel_close_for_error(channel_t *chan); +void channel_closed(channel_t *chan); + +void channel_listener_close_from_lower_layer(channel_listener_t *chan_l); +void channel_listener_close_for_error(channel_listener_t *chan_l); +void channel_listener_closed(channel_listener_t *chan_l); + +/* Free a channel */ +void channel_free(channel_t *chan); +void channel_listener_free(channel_listener_t *chan_l); + +/* State/metadata setters */ + +void channel_change_state(channel_t *chan, channel_state_t to_state); +void channel_clear_identity_digest(channel_t *chan); +void channel_clear_remote_end(channel_t *chan); +void channel_mark_local(channel_t *chan); +void channel_mark_incoming(channel_t *chan); +void channel_mark_outgoing(channel_t *chan); +void channel_set_identity_digest(channel_t *chan, + const char *identity_digest); +void channel_set_remote_end(channel_t *chan, + const char *identity_digest, + const char *nickname); + +void channel_listener_change_state(channel_listener_t *chan_l, + channel_listener_state_t to_state); + +/* Timestamp updates */ +void channel_timestamp_created(channel_t *chan); +void channel_timestamp_active(channel_t *chan); +void channel_timestamp_drained(channel_t *chan); +void channel_timestamp_recv(channel_t *chan); +void channel_timestamp_xmit(channel_t *chan); + +void channel_listener_timestamp_created(channel_listener_t *chan_l); +void channel_listener_timestamp_active(channel_listener_t *chan_l); +void channel_listener_timestamp_accepted(channel_listener_t *chan_l); + +/* Incoming channel handling */ +void channel_listener_process_incoming(channel_listener_t *listener); +void channel_listener_queue_incoming(channel_listener_t *listener, + channel_t *incoming); + +/* Incoming cell handling */ +void channel_process_cells(channel_t *chan); +void channel_queue_cell(channel_t *chan, cell_t *cell); +void channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell); + +/* Outgoing cell handling */ +void channel_flush_cells(channel_t *chan); + +/* Request from lower layer for more cells if available */ +ssize_t channel_flush_some_cells(channel_t *chan, ssize_t num_cells); + +/* Query if data available on this channel */ +int channel_more_to_flush(channel_t *chan); + +/* Notify flushed outgoing for dirreq handling */ +void channel_notify_flushed(channel_t *chan); + +/* Handle stuff we need to do on open like notifying circuits */ +void channel_do_open_actions(channel_t *chan); + +#endif + +/* Helper functions to perform operations on channels */ + +int channel_send_destroy(circid_t circ_id, channel_t *chan, + int reason); + +/* + * Outside abstract interfaces that should eventually get turned into + * something transport/address format independent. + */ + +channel_t * channel_connect(const tor_addr_t *addr, uint16_t port, + const char *id_digest); + +channel_t * channel_get_for_extend(const char *digest, + const tor_addr_t *target_addr, + const char **msg_out, + int *launch_out); + +/* Ask which of two channels is better for circuit-extension purposes */ +int channel_is_better(time_t now, + channel_t *a, channel_t *b, + int forgive_new_connections); + +/** Channel lookups + */ + +channel_t * channel_find_by_global_id(uint64_t global_identifier); +channel_t * channel_find_by_remote_digest(const char *identity_digest); + +/** For things returned by channel_find_by_remote_digest(), walk the list. + */ +channel_t * channel_next_with_digest(channel_t *chan); + +/* + * Metadata queries/updates + */ + +const char * channel_describe_transport(channel_t *chan); +void channel_dump_statistics(channel_t *chan, int severity); +void channel_dump_transport_statistics(channel_t *chan, int severity); +const char * channel_get_actual_remote_descr(channel_t *chan); +const char * channel_get_actual_remote_address(channel_t *chan); +int channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out); +const char * channel_get_canonical_remote_descr(channel_t *chan); +int channel_has_queued_writes(channel_t *chan); +int channel_is_bad_for_new_circs(channel_t *chan); +void channel_mark_bad_for_new_circs(channel_t *chan); +int channel_is_canonical(channel_t *chan); +int channel_is_canonical_is_reliable(channel_t *chan); +int channel_is_client(channel_t *chan); +int channel_is_local(channel_t *chan); +int channel_is_incoming(channel_t *chan); +int channel_is_outgoing(channel_t *chan); +void channel_mark_client(channel_t *chan); +int channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info); +int channel_matches_target_addr_for_extend(channel_t *chan, + const tor_addr_t *target); +unsigned int channel_num_circuits(channel_t *chan); +void channel_set_circid_type(channel_t *chan, crypto_pk_t *identity_rcvd); +void channel_timestamp_client(channel_t *chan); + +const char * channel_listener_describe_transport(channel_listener_t *chan_l); +void channel_listener_dump_statistics(channel_listener_t *chan_l, + int severity); +void channel_listener_dump_transport_statistics(channel_listener_t *chan_l, + int severity); + +/* Timestamp queries */ +time_t channel_when_created(channel_t *chan); +time_t channel_when_last_active(channel_t *chan); +time_t channel_when_last_client(channel_t *chan); +time_t channel_when_last_drained(channel_t *chan); +time_t channel_when_last_recv(channel_t *chan); +time_t channel_when_last_xmit(channel_t *chan); + +time_t channel_listener_when_created(channel_listener_t *chan_l); +time_t channel_listener_when_last_active(channel_listener_t *chan_l); +time_t channel_listener_when_last_accepted(channel_listener_t *chan_l); + +/* Counter queries */ +uint64_t channel_count_recved(channel_t *chan); +uint64_t channel_count_xmitted(channel_t *chan); + +uint64_t channel_listener_count_accepted(channel_listener_t *chan_l); + +#endif + diff --git a/src/or/channeltls.c b/src/or/channeltls.c new file mode 100644 index 000000000..f6069e003 --- /dev/null +++ b/src/or/channeltls.c @@ -0,0 +1,1934 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file channeltls.c + * \brief channel_t concrete subclass using or_connection_t + **/ + +/* + * Define this so channel.h gives us things only channel_t subclasses + * should touch. + */ + +#define TOR_CHANNEL_INTERNAL_ + +#include "or.h" +#include "channel.h" +#include "channeltls.h" +#include "circuitmux.h" +#include "circuitmux_ewma.h" +#include "config.h" +#include "connection.h" +#include "connection_or.h" +#include "control.h" +#include "relay.h" +#include "router.h" +#include "routerlist.h" + +/** How many CELL_PADDING cells have we received, ever? */ +uint64_t stats_n_padding_cells_processed = 0; +/** How many CELL_VERSIONS cells have we received, ever? */ +uint64_t stats_n_versions_cells_processed = 0; +/** How many CELL_NETINFO cells have we received, ever? */ +uint64_t stats_n_netinfo_cells_processed = 0; +/** How many CELL_VPADDING cells have we received, ever? */ +uint64_t stats_n_vpadding_cells_processed = 0; +/** How many CELL_CERTS cells have we received, ever? */ +uint64_t stats_n_certs_cells_processed = 0; +/** How many CELL_AUTH_CHALLENGE cells have we received, ever? */ +uint64_t stats_n_auth_challenge_cells_processed = 0; +/** How many CELL_AUTHENTICATE cells have we received, ever? */ +uint64_t stats_n_authenticate_cells_processed = 0; +/** How many CELL_AUTHORIZE cells have we received, ever? */ +uint64_t stats_n_authorize_cells_processed = 0; + +/** Active listener, if any */ +channel_listener_t *channel_tls_listener = NULL; + +/* Utility function declarations */ +static void channel_tls_common_init(channel_tls_t *tlschan); + +/* channel_tls_t method declarations */ + +static void channel_tls_close_method(channel_t *chan); +static const char * channel_tls_describe_transport_method(channel_t *chan); +static int +channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out); +static const char * +channel_tls_get_remote_descr_method(channel_t *chan, int flags); +static int channel_tls_has_queued_writes_method(channel_t *chan); +static int channel_tls_is_canonical_method(channel_t *chan, int req); +static int +channel_tls_matches_extend_info_method(channel_t *chan, + extend_info_t *extend_info); +static int channel_tls_matches_target_method(channel_t *chan, + const tor_addr_t *target); +static int channel_tls_write_cell_method(channel_t *chan, + cell_t *cell); +static int channel_tls_write_packed_cell_method(channel_t *chan, + packed_cell_t *packed_cell); +static int channel_tls_write_var_cell_method(channel_t *chan, + var_cell_t *var_cell); + +/* channel_listener_tls_t method declarations */ + +static void channel_tls_listener_close_method(channel_listener_t *chan_l); +static const char * +channel_tls_listener_describe_transport_method(channel_listener_t *chan_l); + +/** Handle incoming cells for the handshake stuff here rather than + * passing them on up. */ + +static void channel_tls_process_versions_cell(var_cell_t *cell, + channel_tls_t *tlschan); +static void channel_tls_process_netinfo_cell(cell_t *cell, + channel_tls_t *tlschan); +static void channel_tls_process_certs_cell(var_cell_t *cell, + channel_tls_t *tlschan); +static void channel_tls_process_auth_challenge_cell(var_cell_t *cell, + channel_tls_t *tlschan); +static void channel_tls_process_authenticate_cell(var_cell_t *cell, + channel_tls_t *tlschan); +static int command_allowed_before_handshake(uint8_t command); +static int enter_v3_handshake_with_cell(var_cell_t *cell, + channel_tls_t *tlschan); + +/** + * Do parts of channel_tls_t initialization common to channel_tls_connect() + * and channel_tls_handle_incoming(). + */ + +static void +channel_tls_common_init(channel_tls_t *tlschan) +{ + channel_t *chan; + + tor_assert(tlschan); + + chan = &(tlschan->base_); + channel_init(chan); + chan->magic = TLS_CHAN_MAGIC; + chan->state = CHANNEL_STATE_OPENING; + chan->close = channel_tls_close_method; + chan->describe_transport = channel_tls_describe_transport_method; + chan->get_remote_addr = channel_tls_get_remote_addr_method; + chan->get_remote_descr = channel_tls_get_remote_descr_method; + chan->has_queued_writes = channel_tls_has_queued_writes_method; + chan->is_canonical = channel_tls_is_canonical_method; + chan->matches_extend_info = channel_tls_matches_extend_info_method; + chan->matches_target = channel_tls_matches_target_method; + chan->write_cell = channel_tls_write_cell_method; + chan->write_packed_cell = channel_tls_write_packed_cell_method; + chan->write_var_cell = channel_tls_write_var_cell_method; + + chan->cmux = circuitmux_alloc(); + if (cell_ewma_enabled()) { + circuitmux_set_policy(chan->cmux, &ewma_policy); + } +} + +/** + * Start a new TLS channel + * + * Launch a new OR connection to <b>addr</b>:<b>port</b> and expect to + * handshake with an OR with identity digest <b>id_digest</b>, and wrap + * it in a channel_tls_t. + */ + +channel_t * +channel_tls_connect(const tor_addr_t *addr, uint16_t port, + const char *id_digest) +{ + channel_tls_t *tlschan = tor_malloc_zero(sizeof(*tlschan)); + channel_t *chan = &(tlschan->base_); + + channel_tls_common_init(tlschan); + + log_debug(LD_CHANNEL, + "In channel_tls_connect() for channel %p " + "(global id " U64_FORMAT ")", + tlschan, + U64_PRINTF_ARG(chan->global_identifier)); + + if (is_local_addr(addr)) channel_mark_local(chan); + channel_mark_outgoing(chan); + + /* Set up or_connection stuff */ + tlschan->conn = connection_or_connect(addr, port, id_digest, tlschan); + /* connection_or_connect() will fill in tlschan->conn */ + if (!(tlschan->conn)) { + chan->reason_for_closing = CHANNEL_CLOSE_FOR_ERROR; + channel_change_state(chan, CHANNEL_STATE_ERROR); + goto err; + } + + log_debug(LD_CHANNEL, + "Got orconn %p for channel with global id " U64_FORMAT, + tlschan->conn, U64_PRINTF_ARG(chan->global_identifier)); + + goto done; + + err: + circuitmux_free(chan->cmux); + tor_free(tlschan); + chan = NULL; + + done: + /* If we got one, we should register it */ + if (chan) channel_register(chan); + + return chan; +} + +/** + * Return the current channel_tls_t listener + * + * Returns the current channel listener for incoming TLS connections, or + * NULL if none has been established + */ + +channel_listener_t * +channel_tls_get_listener(void) +{ + return channel_tls_listener; +} + +/** + * Start a channel_tls_t listener if necessary + * + * Return the current channel_tls_t listener, or start one if we haven't yet, + * and return that. + */ + +channel_listener_t * +channel_tls_start_listener(void) +{ + channel_listener_t *listener; + + if (!channel_tls_listener) { + listener = tor_malloc_zero(sizeof(*listener)); + channel_init_listener(listener); + listener->state = CHANNEL_LISTENER_STATE_LISTENING; + listener->close = channel_tls_listener_close_method; + listener->describe_transport = + channel_tls_listener_describe_transport_method; + + channel_tls_listener = listener; + + log_debug(LD_CHANNEL, + "Starting TLS channel listener %p with global id " U64_FORMAT, + listener, U64_PRINTF_ARG(listener->global_identifier)); + + channel_listener_register(listener); + } else listener = channel_tls_listener; + + return listener; +} + +/** + * Free everything on shutdown + * + * Not much to do here, since channel_free_all() takes care of a lot, but let's + * get rid of the listener. + */ + +void +channel_tls_free_all(void) +{ + channel_listener_t *old_listener = NULL; + + log_debug(LD_CHANNEL, + "Shutting down TLS channels..."); + + if (channel_tls_listener) { + /* + * When we close it, channel_tls_listener will get nulled out, so save + * a pointer so we can free it. + */ + old_listener = channel_tls_listener; + log_debug(LD_CHANNEL, + "Closing channel_tls_listener with ID " U64_FORMAT + " at %p.", + U64_PRINTF_ARG(old_listener->global_identifier), + old_listener); + channel_listener_unregister(old_listener); + channel_listener_mark_for_close(old_listener); + channel_listener_free(old_listener); + tor_assert(channel_tls_listener == NULL); + } + + log_debug(LD_CHANNEL, + "Done shutting down TLS channels"); +} + +/** + * Create a new channel around an incoming or_connection_t + */ + +channel_t * +channel_tls_handle_incoming(or_connection_t *orconn) +{ + channel_tls_t *tlschan = tor_malloc_zero(sizeof(*tlschan)); + channel_t *chan = &(tlschan->base_); + + tor_assert(orconn); + tor_assert(!(orconn->chan)); + + channel_tls_common_init(tlschan); + + /* Link the channel and orconn to each other */ + tlschan->conn = orconn; + orconn->chan = tlschan; + + if (is_local_addr(&(TO_CONN(orconn)->addr))) channel_mark_local(chan); + channel_mark_incoming(chan); + + /* If we got one, we should register it */ + if (chan) channel_register(chan); + + return chan; +} + +/********* + * Casts * + ********/ + +/** + * Cast a channel_tls_t to a channel_t. + */ + +channel_t * +channel_tls_to_base(channel_tls_t *tlschan) +{ + if (!tlschan) return NULL; + + return &(tlschan->base_); +} + +/** + * Cast a channel_t to a channel_tls_t, with appropriate type-checking + * asserts. + */ + +channel_tls_t * +channel_tls_from_base(channel_t *chan) +{ + if (!chan) return NULL; + + tor_assert(chan->magic == TLS_CHAN_MAGIC); + + return (channel_tls_t *)(chan); +} + +/******************************************** + * Method implementations for channel_tls_t * + *******************************************/ + +/** + * Close a channel_tls_t + * + * This implements the close method for channel_tls_t + */ + +static void +channel_tls_close_method(channel_t *chan) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + + if (tlschan->conn) connection_or_close_normally(tlschan->conn, 1); + else { + /* Weird - we'll have to change the state ourselves, I guess */ + log_info(LD_CHANNEL, + "Tried to close channel_tls_t %p with NULL conn", + tlschan); + channel_change_state(chan, CHANNEL_STATE_ERROR); + } +} + +/** + * Describe the transport for a channel_tls_t + * + * This returns the string "TLS channel on connection <id>" to the upper + * layer. + */ + +static const char * +channel_tls_describe_transport_method(channel_t *chan) +{ + static char *buf = NULL; + uint64_t id; + channel_tls_t *tlschan; + const char *rv = NULL; + + tor_assert(chan); + + tlschan = BASE_CHAN_TO_TLS(chan); + + if (tlschan->conn) { + id = TO_CONN(tlschan->conn)->global_identifier; + + if (buf) tor_free(buf); + tor_asprintf(&buf, + "TLS channel (connection " U64_FORMAT ")", + U64_PRINTF_ARG(id)); + + rv = buf; + } else { + rv = "TLS channel (no connection)"; + } + + return rv; +} + +/** + * Get the remote address of a channel_tls_t + * + * This implements the get_remote_addr method for channel_tls_t; copy the + * remote endpoint of the channel to addr_out and return 1 (always + * succeeds for this transport). + */ + +static int +channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(addr_out); + tor_assert(tlschan->conn); + + tor_addr_copy(addr_out, &(TO_CONN(tlschan->conn)->addr)); + + return 1; +} + +/** + * Get endpoint description of a channel_tls_t + * + * This implements the get_remote_descr method for channel_tls_t; it returns + * a text description of the remote endpoint of the channel suitable for use + * in log messages. The req parameter is 0 for the canonical address or 1 for + * the actual address seen. + */ + +static const char * +channel_tls_get_remote_descr_method(channel_t *chan, int flags) +{ +#define MAX_DESCR_LEN 32 + + static char buf[MAX_DESCR_LEN + 1]; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + connection_t *conn; + const char *answer = NULL; + char *addr_str; + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + conn = TO_CONN(tlschan->conn); + + switch (flags) { + case 0: + /* Canonical address with port*/ + tor_snprintf(buf, MAX_DESCR_LEN + 1, + "%s:%u", conn->address, conn->port); + answer = buf; + break; + case GRD_FLAG_ORIGINAL: + /* Actual address with port */ + addr_str = tor_dup_addr(&(tlschan->conn->real_addr)); + tor_snprintf(buf, MAX_DESCR_LEN + 1, + "%s:%u", addr_str, conn->port); + tor_free(addr_str); + answer = buf; + break; + case GRD_FLAG_ADDR_ONLY: + /* Canonical address, no port */ + strlcpy(buf, conn->address, sizeof(buf)); + answer = buf; + break; + case GRD_FLAG_ORIGINAL|GRD_FLAG_ADDR_ONLY: + /* Actual address, no port */ + addr_str = tor_dup_addr(&(tlschan->conn->real_addr)); + strlcpy(buf, addr_str, sizeof(buf)); + tor_free(addr_str); + answer = buf; + break; + + default: + /* Something's broken in channel.c */ + tor_assert(1); + } + + return answer; +} + +/** + * Tell the upper layer if we have queued writes + * + * This implements the has_queued_writes method for channel_tls t_; it returns + * 1 iff we have queued writes on the outbuf of the underlying or_connection_t. + */ + +static int +channel_tls_has_queued_writes_method(channel_t *chan) +{ + size_t outbuf_len; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + outbuf_len = connection_get_outbuf_len(TO_CONN(tlschan->conn)); + + return (outbuf_len > 0); +} + +/** + * Tell the upper layer if we're canonical + * + * This implements the is_canonical method for channel_tls_t; if req is zero, + * it returns whether this is a canonical channel, and if it is one it returns + * whether that can be relied upon. + */ + +static int +channel_tls_is_canonical_method(channel_t *chan, int req) +{ + int answer = 0; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + switch (req) { + case 0: + answer = tlschan->conn->is_canonical; + break; + case 1: + /* + * Is the is_canonical bit reliable? In protocols version 2 and up + * we get the canonical address from a NETINFO cell, but in older + * versions it might be based on an obsolete descriptor. + */ + answer = (tlschan->conn->link_proto >= 2); + break; + default: + /* This shouldn't happen; channel.c is broken if it does */ + tor_assert(1); + } + + return answer; +} + +/** + * Check if we match an extend_info_t + * + * This implements the matches_extend_info method for channel_tls_t; the upper + * layer wants to know if this channel matches an extend_info_t. + */ + +static int +channel_tls_matches_extend_info_method(channel_t *chan, + extend_info_t *extend_info) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(extend_info); + + return (tor_addr_eq(&(extend_info->addr), + &(TO_CONN(tlschan->conn)->addr)) && + (extend_info->port == TO_CONN(tlschan->conn)->port)); +} + +/** + * Check if we match a target address + * + * This implements the matches_target method for channel_tls t_; the upper + * layer wants to know if this channel matches a target address when extending + * a circuit. + */ + +static int +channel_tls_matches_target_method(channel_t *chan, + const tor_addr_t *target) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(target); + tor_assert(tlschan->conn); + + return tor_addr_compare(&(tlschan->conn->real_addr), + target, CMP_EXACT); +} + +/** + * Write a cell to a channel_tls_t + * + * This implements the write_cell method for channel_tls_t; given a + * channel_tls_t and a cell_t, transmit the cell_t. + */ + +static int +channel_tls_write_cell_method(channel_t *chan, cell_t *cell) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(cell); + tor_assert(tlschan->conn); + + connection_or_write_cell_to_buf(cell, tlschan->conn); + + return 1; +} + +/** + * Write a packed cell to a channel_tls_t + * + * This implements the write_packed_cell method for channel_tls_t; given a + * channel_tls_t and a packed_cell_t, transmit the packed_cell_t. + */ + +static int +channel_tls_write_packed_cell_method(channel_t *chan, + packed_cell_t *packed_cell) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(packed_cell); + tor_assert(tlschan->conn); + + connection_write_to_buf(packed_cell->body, CELL_NETWORK_SIZE, + TO_CONN(tlschan->conn)); + + /* This is where the cell is finished; used to be done from relay.c */ + packed_cell_free(packed_cell); + + return 1; +} + +/** + * Write a variable-length cell to a channel_tls_t + * + * This implements the write_var_cell method for channel_tls_t; given a + * channel_tls_t and a var_cell_t, transmit the var_cell_t. + */ + +static int +channel_tls_write_var_cell_method(channel_t *chan, var_cell_t *var_cell) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(var_cell); + tor_assert(tlschan->conn); + + connection_or_write_var_cell_to_buf(var_cell, tlschan->conn); + + return 1; +} + +/************************************************* + * Method implementations for channel_listener_t * + ************************************************/ + +/** + * Close a channel_listener_t + * + * This implements the close method for channel_listener_t + */ + +static void +channel_tls_listener_close_method(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + /* + * Listeners we just go ahead and change state through to CLOSED, but + * make sure to check if they're channel_tls_listener to NULL it out. + */ + if (chan_l == channel_tls_listener) + channel_tls_listener = NULL; + + if (!(chan_l->state == CHANNEL_LISTENER_STATE_CLOSING || + chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR)) { + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSING); + } + + if (chan_l->incoming_list) { + SMARTLIST_FOREACH_BEGIN(chan_l->incoming_list, + channel_t *, ichan) { + channel_mark_for_close(ichan); + } SMARTLIST_FOREACH_END(ichan); + + smartlist_free(chan_l->incoming_list); + chan_l->incoming_list = NULL; + } + + if (!(chan_l->state == CHANNEL_LISTENER_STATE_CLOSED || + chan_l->state == CHANNEL_LISTENER_STATE_ERROR)) { + channel_listener_change_state(chan_l, CHANNEL_LISTENER_STATE_CLOSED); + } +} + +/** + * Describe the transport for a channel_listener_t + * + * This returns the string "TLS channel (listening)" to the upper + * layer. + */ + +static const char * +channel_tls_listener_describe_transport_method(channel_listener_t *chan_l) +{ + tor_assert(chan_l); + + return "TLS channel (listening)"; +} + +/******************************************************* + * Functions for handling events on an or_connection_t * + ******************************************************/ + +/** + * Handle an orconn state change + * + * This function will be called by connection_or.c when the or_connection_t + * associated with this channel_tls_t changes state. + */ + +void +channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, + or_connection_t *conn, + uint8_t old_state, + uint8_t state) +{ + channel_t *base_chan; + + tor_assert(chan); + tor_assert(conn); + tor_assert(conn->chan == chan); + tor_assert(chan->conn == conn); + /* -Werror appeasement */ + tor_assert(old_state == old_state); + + base_chan = TLS_CHAN_TO_BASE(chan); + + /* Make sure the base connection state makes sense - shouldn't be error, + * closed or listening. */ + + tor_assert(base_chan->state == CHANNEL_STATE_OPENING || + base_chan->state == CHANNEL_STATE_OPEN || + base_chan->state == CHANNEL_STATE_MAINT || + base_chan->state == CHANNEL_STATE_CLOSING); + + /* Did we just go to state open? */ + if (state == OR_CONN_STATE_OPEN) { + /* + * We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or + * CHANNEL_STATE_MAINT on this. + */ + channel_change_state(base_chan, CHANNEL_STATE_OPEN); + } else { + /* + * Not open, so from CHANNEL_STATE_OPEN we go to CHANNEL_STATE_MAINT, + * otherwise no change. + */ + if (base_chan->state == CHANNEL_STATE_OPEN) { + channel_change_state(base_chan, CHANNEL_STATE_MAINT); + } + } +} + +/** + * Flush cells from a channel_tls_t + * + * Try to flush up to about num_cells cells, and return how many we flushed. + */ + +ssize_t +channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells) +{ + ssize_t flushed = 0; + + tor_assert(chan); + + if (flushed >= num_cells) goto done; + + /* + * If channel_tls_t ever buffers anything below the channel_t layer, flush + * that first here. + */ + + flushed += channel_flush_some_cells(TLS_CHAN_TO_BASE(chan), + num_cells - flushed); + + /* + * If channel_tls_t ever buffers anything below the channel_t layer, check + * how much we actually got and push it on down here. + */ + + done: + return flushed; +} + +/** + * Check if a channel_tls_t has anything to flush + * + * Return true if there is any more to flush on this channel (cells in queue + * or active circuits). + */ + +int +channel_tls_more_to_flush(channel_tls_t *chan) +{ + tor_assert(chan); + + /* + * If channel_tls_t ever buffers anything below channel_t, the + * check for that should go here first. + */ + + return channel_more_to_flush(TLS_CHAN_TO_BASE(chan)); +} + +#ifdef KEEP_TIMING_STATS + +/** + * Timing states wrapper + * + * This is a wrapper function around the actual function that processes the + * <b>cell</b> that just arrived on <b>chan</b>. Increment <b>*time</b> + * by the number of microseconds used by the call to <b>*func(cell, chan)</b>. + */ + +static void +channel_tls_time_process_cell(cell_t *cell, channel_tls_t *chan, int *time, + void (*func)(cell_t *, channel_tls_t *)) +{ + struct timeval start, end; + long time_passed; + + tor_gettimeofday(&start); + + (*func)(cell, chan); + + tor_gettimeofday(&end); + time_passed = tv_udiff(&start, &end) ; + + if (time_passed > 10000) { /* more than 10ms */ + log_debug(LD_OR,"That call just took %ld ms.",time_passed/1000); + } + + if (time_passed < 0) { + log_info(LD_GENERAL,"That call took us back in time!"); + time_passed = 0; + } + + *time += time_passed; +} +#endif + +/** + * Handle an incoming cell on a channel_tls_t + * + * This is called from connection_or.c to handle an arriving cell; it checks + * for cell types specific to the handshake for this transport protocol and + * handles them, and queues all other cells to the channel_t layer, which + * eventually will hand them off to command.c. + */ + +void +channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) +{ + channel_tls_t *chan; + int handshaking; + +#ifdef KEEP_TIMING_STATS +#define PROCESS_CELL(tp, cl, cn) STMT_BEGIN { \ + ++num ## tp; \ + channel_tls_time_process_cell(cl, cn, & tp ## time , \ + channel_tls_process_ ## tp ## _cell); \ + } STMT_END +#else +#define PROCESS_CELL(tp, cl, cn) channel_tls_process_ ## tp ## _cell(cl, cn) +#endif + + tor_assert(cell); + tor_assert(conn); + + chan = conn->chan; + + if (!chan) { + log_warn(LD_CHANNEL, + "Got a cell_t on an OR connection with no channel"); + return; + } + + handshaking = (TO_CONN(conn)->state != OR_CONN_STATE_OPEN); + + if (conn->base_.marked_for_close) + return; + + /* Reject all but VERSIONS and NETINFO when handshaking. */ + /* (VERSIONS should actually be impossible; it's variable-length.) */ + if (handshaking && cell->command != CELL_VERSIONS && + cell->command != CELL_NETINFO) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received unexpected cell command %d in chan state %s / " + "conn state %s; closing the connection.", + (int)cell->command, + channel_state_to_string(TLS_CHAN_TO_BASE(chan)->state), + conn_state_to_string(CONN_TYPE_OR, TO_CONN(conn)->state)); + connection_or_close_for_error(conn, 0); + return; + } + + if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) + or_handshake_state_record_cell(conn->handshake_state, cell, 1); + + switch (cell->command) { + case CELL_PADDING: + ++stats_n_padding_cells_processed; + /* do nothing */ + break; + case CELL_VERSIONS: + tor_fragile_assert(); + break; + case CELL_NETINFO: + ++stats_n_netinfo_cells_processed; + PROCESS_CELL(netinfo, cell, chan); + break; + case CELL_CREATE: + case CELL_CREATE_FAST: + case CELL_CREATED: + case CELL_CREATED_FAST: + case CELL_RELAY: + case CELL_RELAY_EARLY: + case CELL_DESTROY: + case CELL_CREATE2: + case CELL_CREATED2: + /* + * These are all transport independent and we pass them up through the + * channel_t mechanism. They are ultimately handled in command.c. + */ + channel_queue_cell(TLS_CHAN_TO_BASE(chan), cell); + break; + default: + log_fn(LOG_INFO, LD_PROTOCOL, + "Cell of unknown type (%d) received in channeltls.c. " + "Dropping.", + cell->command); + break; + } +} + +/** + * Handle an incoming variable-length cell on a channel_tls_t + * + * Process a <b>var_cell</b> that was just received on <b>conn</b>. Keep + * internal statistics about how many of each cell we've processed so far + * this second, and the total number of microseconds it took to + * process each type of cell. All the var_cell commands are handshake- + * related and live below the channel_t layer, so no variable-length + * cells ever get delivered in the current implementation, but I've left + * the mechanism in place for future use. + */ + +void +channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) +{ + channel_tls_t *chan; + +#ifdef KEEP_TIMING_STATS + /* how many of each cell have we seen so far this second? needs better + * name. */ + static int num_versions = 0, num_certs = 0; + static time_t current_second = 0; /* from previous calls to time */ + time_t now = time(NULL); + + if (current_second == 0) current_second = now; + if (now > current_second) { /* the second has rolled over */ + /* print stats */ + log_info(LD_OR, + "At end of second: %d versions (%d ms), %d certs (%d ms)", + num_versions, versions_time / ((now - current_second) * 1000), + num_certs, certs_time / ((now - current_second) * 1000)); + + num_versions = num_certs = 0; + versions_time = certs_time = 0; + + /* remember which second it is, for next time */ + current_second = now; + } +#endif + + tor_assert(var_cell); + tor_assert(conn); + + chan = conn->chan; + + if (!chan) { + log_warn(LD_CHANNEL, + "Got a var_cell_t on an OR connection with no channel"); + return; + } + + if (TO_CONN(conn)->marked_for_close) + return; + + switch (TO_CONN(conn)->state) { + case OR_CONN_STATE_OR_HANDSHAKING_V2: + if (var_cell->command != CELL_VERSIONS) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received a cell with command %d in unexpected " + "orconn state \"%s\" [%d], channel state \"%s\" [%d]; " + "closing the connection.", + (int)(var_cell->command), + conn_state_to_string(CONN_TYPE_OR, TO_CONN(conn)->state), + TO_CONN(conn)->state, + channel_state_to_string(TLS_CHAN_TO_BASE(chan)->state), + (int)(TLS_CHAN_TO_BASE(chan)->state)); + /* + * The code in connection_or.c will tell channel_t to close for + * error; it will go to CHANNEL_STATE_CLOSING, and then to + * CHANNEL_STATE_ERROR when conn is closed. + */ + connection_or_close_for_error(conn, 0); + return; + } + break; + case OR_CONN_STATE_TLS_HANDSHAKING: + /* If we're using bufferevents, it's entirely possible for us to + * notice "hey, data arrived!" before we notice "hey, the handshake + * finished!" And we need to be accepting both at once to handle both + * the v2 and v3 handshakes. */ + + /* fall through */ + case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: + if (!(command_allowed_before_handshake(var_cell->command))) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received a cell with command %d in unexpected " + "orconn state \"%s\" [%d], channel state \"%s\" [%d]; " + "closing the connection.", + (int)(var_cell->command), + conn_state_to_string(CONN_TYPE_OR, TO_CONN(conn)->state), + (int)(TO_CONN(conn)->state), + channel_state_to_string(TLS_CHAN_TO_BASE(chan)->state), + (int)(TLS_CHAN_TO_BASE(chan)->state)); + /* see above comment about CHANNEL_STATE_ERROR */ + connection_or_close_for_error(conn, 0); + return; + } else { + if (enter_v3_handshake_with_cell(var_cell, chan) < 0) + return; + } + break; + case OR_CONN_STATE_OR_HANDSHAKING_V3: + if (var_cell->command != CELL_AUTHENTICATE) + or_handshake_state_record_var_cell(conn->handshake_state, var_cell, 1); + break; /* Everything is allowed */ + case OR_CONN_STATE_OPEN: + if (conn->link_proto < 3) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received a variable-length cell with command %d in orconn " + "state %s [%d], channel state %s [%d] with link protocol %d; " + "ignoring it.", + (int)(var_cell->command), + conn_state_to_string(CONN_TYPE_OR, TO_CONN(conn)->state), + (int)(TO_CONN(conn)->state), + channel_state_to_string(TLS_CHAN_TO_BASE(chan)->state), + (int)(TLS_CHAN_TO_BASE(chan)->state), + (int)(conn->link_proto)); + return; + } + break; + default: + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received var-length cell with command %d in unexpected " + "orconn state \"%s\" [%d], channel state \"%s\" [%d]; " + "ignoring it.", + (int)(var_cell->command), + conn_state_to_string(CONN_TYPE_OR, TO_CONN(conn)->state), + (int)(TO_CONN(conn)->state), + channel_state_to_string(TLS_CHAN_TO_BASE(chan)->state), + (int)(TLS_CHAN_TO_BASE(chan)->state)); + return; + } + + /* Now handle the cell */ + + switch (var_cell->command) { + case CELL_VERSIONS: + ++stats_n_versions_cells_processed; + PROCESS_CELL(versions, var_cell, chan); + break; + case CELL_VPADDING: + ++stats_n_vpadding_cells_processed; + /* Do nothing */ + break; + case CELL_CERTS: + ++stats_n_certs_cells_processed; + PROCESS_CELL(certs, var_cell, chan); + break; + case CELL_AUTH_CHALLENGE: + ++stats_n_auth_challenge_cells_processed; + PROCESS_CELL(auth_challenge, var_cell, chan); + break; + case CELL_AUTHENTICATE: + ++stats_n_authenticate_cells_processed; + PROCESS_CELL(authenticate, var_cell, chan); + break; + case CELL_AUTHORIZE: + ++stats_n_authorize_cells_processed; + /* Ignored so far. */ + break; + default: + log_fn(LOG_INFO, LD_PROTOCOL, + "Variable-length cell of unknown type (%d) received.", + (int)(var_cell->command)); + break; + } +} + +/** + * Check if this cell type is allowed before the handshake is finished + * + * Return true if <b>command</b> is a cell command that's allowed to start a + * V3 handshake. + */ + +static int +command_allowed_before_handshake(uint8_t command) +{ + switch (command) { + case CELL_VERSIONS: + case CELL_VPADDING: + case CELL_AUTHORIZE: + return 1; + default: + return 0; + } +} + +/** + * Start a V3 handshake on an incoming connection + * + * Called when we as a server receive an appropriate cell while waiting + * either for a cell or a TLS handshake. Set the connection's state to + * "handshaking_v3', initializes the or_handshake_state field as needed, + * and add the cell to the hash of incoming cells.) + */ + +static int +enter_v3_handshake_with_cell(var_cell_t *cell, channel_tls_t *chan) +{ + int started_here = 0; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + + started_here = connection_or_nonopen_was_started_here(chan->conn); + + tor_assert(TO_CONN(chan->conn)->state == OR_CONN_STATE_TLS_HANDSHAKING || + TO_CONN(chan->conn)->state == + OR_CONN_STATE_TLS_SERVER_RENEGOTIATING); + + if (started_here) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a cell while TLS-handshaking, not in " + "OR_HANDSHAKING_V3, on a connection we originated."); + } + connection_or_block_renegotiation(chan->conn); + chan->conn->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; + if (connection_init_or_handshake_state(chan->conn, started_here) < 0) { + connection_or_close_for_error(chan->conn, 0); + return -1; + } + or_handshake_state_record_var_cell(chan->conn->handshake_state, cell, 1); + return 0; +} + +/** + * Process a 'versions' cell. + * + * This function is called to handle an incoming VERSIONS cell; the current + * link protocol version must be 0 to indicate that no version has yet been + * negotiated. We compare the versions in the cell to the list of versions + * we support, pick the highest version we have in common, and continue the + * negotiation from there. + */ + +static void +channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) +{ + int highest_supported_version = 0; + const uint8_t *cp, *end; + int started_here = 0; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + + started_here = connection_or_nonopen_was_started_here(chan->conn); + + if (chan->conn->link_proto != 0 || + (chan->conn->handshake_state && + chan->conn->handshake_state->received_versions)) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a VERSIONS cell on a connection with its version " + "already set to %d; dropping", + (int)(chan->conn->link_proto)); + return; + } + switch (chan->conn->base_.state) + { + case OR_CONN_STATE_OR_HANDSHAKING_V2: + case OR_CONN_STATE_OR_HANDSHAKING_V3: + break; + case OR_CONN_STATE_TLS_HANDSHAKING: + case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: + default: + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "VERSIONS cell while in unexpected state"); + return; + } + + tor_assert(chan->conn->handshake_state); + end = cell->payload + cell->payload_len; + for (cp = cell->payload; cp+1 < end; ++cp) { + uint16_t v = ntohs(get_uint16(cp)); + if (is_or_protocol_version_known(v) && v > highest_supported_version) + highest_supported_version = v; + } + if (!highest_supported_version) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Couldn't find a version in common between my version list and the " + "list in the VERSIONS cell; closing connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } else if (highest_supported_version == 1) { + /* Negotiating version 1 makes no sense, since version 1 has no VERSIONS + * cells. */ + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Used version negotiation protocol to negotiate a v1 connection. " + "That's crazily non-compliant. Closing connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } else if (highest_supported_version < 3 && + chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Negotiated link protocol 2 or lower after doing a v3 TLS " + "handshake. Closing connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } else if (highest_supported_version != 2 && + chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V2) { + /* XXXX This should eventually be a log_protocol_warn */ + log_fn(LOG_WARN, LD_OR, + "Negotiated link with non-2 protocol after doing a v2 TLS " + "handshake with %s. Closing connection.", + fmt_addr(&chan->conn->base_.addr)); + connection_or_close_for_error(chan->conn, 0); + return; + } + + chan->conn->link_proto = highest_supported_version; + chan->conn->handshake_state->received_versions = 1; + + if (chan->conn->link_proto == 2) { + log_info(LD_OR, + "Negotiated version %d with %s:%d; sending NETINFO.", + highest_supported_version, + safe_str_client(chan->conn->base_.address), + chan->conn->base_.port); + + if (connection_or_send_netinfo(chan->conn) < 0) { + connection_or_close_for_error(chan->conn, 0); + return; + } + } else { + const int send_versions = !started_here; + /* If we want to authenticate, send a CERTS cell */ + const int send_certs = !started_here || public_server_mode(get_options()); + /* If we're a relay that got a connection, ask for authentication. */ + const int send_chall = !started_here && public_server_mode(get_options()); + /* If our certs cell will authenticate us, we can send a netinfo cell + * right now. */ + const int send_netinfo = !started_here; + const int send_any = + send_versions || send_certs || send_chall || send_netinfo; + tor_assert(chan->conn->link_proto >= 3); + + log_info(LD_OR, + "Negotiated version %d with %s:%d; %s%s%s%s%s", + highest_supported_version, + safe_str_client(chan->conn->base_.address), + chan->conn->base_.port, + send_any ? "Sending cells:" : "Waiting for CERTS cell", + send_versions ? " VERSIONS" : "", + send_certs ? " CERTS" : "", + send_chall ? " AUTH_CHALLENGE" : "", + send_netinfo ? " NETINFO" : ""); + +#ifdef DISABLE_V3_LINKPROTO_SERVERSIDE + if (1) { + connection_or_close_normally(chan->conn, 1); + return; + } +#endif + + if (send_versions) { + if (connection_or_send_versions(chan->conn, 1) < 0) { + log_warn(LD_OR, "Couldn't send versions cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + } + if (send_certs) { + if (connection_or_send_certs_cell(chan->conn) < 0) { + log_warn(LD_OR, "Couldn't send certs cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + } + if (send_chall) { + if (connection_or_send_auth_challenge_cell(chan->conn) < 0) { + log_warn(LD_OR, "Couldn't send auth_challenge cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + } + if (send_netinfo) { + if (connection_or_send_netinfo(chan->conn) < 0) { + log_warn(LD_OR, "Couldn't send netinfo cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + } + } +} + +/** + * Process a 'netinfo' cell + * + * This function is called to handle an incoming NETINFO cell; read and act + * on its contents, and set the connection state to "open". + */ + +static void +channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) +{ + time_t timestamp; + uint8_t my_addr_type; + uint8_t my_addr_len; + const uint8_t *my_addr_ptr; + const uint8_t *cp, *end; + uint8_t n_other_addrs; + time_t now = time(NULL); + + long apparent_skew = 0; + tor_addr_t my_apparent_addr = TOR_ADDR_NULL; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + + if (chan->conn->link_proto < 2) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a NETINFO cell on %s connection; dropping.", + chan->conn->link_proto == 0 ? "non-versioned" : "a v1"); + return; + } + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V2 && + chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a NETINFO cell on non-handshaking connection; dropping."); + return; + } + tor_assert(chan->conn->handshake_state && + chan->conn->handshake_state->received_versions); + + if (chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { + tor_assert(chan->conn->link_proto >= 3); + if (chan->conn->handshake_state->started_here) { + if (!(chan->conn->handshake_state->authenticated)) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Got a NETINFO cell from server, " + "but no authentication. Closing the connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } + } else { + /* we're the server. If the client never authenticated, we have + some housekeeping to do.*/ + if (!(chan->conn->handshake_state->authenticated)) { + tor_assert(tor_digest_is_zero( + (const char*)(chan->conn->handshake_state-> + authenticated_peer_id))); + channel_set_circid_type(TLS_CHAN_TO_BASE(chan), NULL); + + connection_or_init_conn_from_address(chan->conn, + &(chan->conn->base_.addr), + chan->conn->base_.port, + (const char*)(chan->conn->handshake_state-> + authenticated_peer_id), + 0); + } + } + } + + /* Decode the cell. */ + timestamp = ntohl(get_uint32(cell->payload)); + if (labs(now - chan->conn->handshake_state->sent_versions_at) < 180) { + apparent_skew = now - timestamp; + } + + my_addr_type = (uint8_t) cell->payload[4]; + my_addr_len = (uint8_t) cell->payload[5]; + my_addr_ptr = (uint8_t*) cell->payload + 6; + end = cell->payload + CELL_PAYLOAD_SIZE; + cp = cell->payload + 6 + my_addr_len; + if (cp >= end) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Addresses too long in netinfo cell; closing connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } else if (my_addr_type == RESOLVED_TYPE_IPV4 && my_addr_len == 4) { + 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++; + while (n_other_addrs && cp < end-2) { + /* Consider all the other addresses; if any matches, this connection is + * "canonical." */ + tor_addr_t addr; + const uint8_t *next = + decode_address_from_payload(&addr, cp, (int)(end-cp)); + if (next == NULL) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Bad address in netinfo cell; closing connection."); + connection_or_close_for_error(chan->conn, 0); + return; + } + if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { + chan->conn->is_canonical = 1; + break; + } + cp = next; + --n_other_addrs; + } + + /* Act on apparent skew. */ + /** Warn when we get a netinfo skew with at least this value. */ +#define NETINFO_NOTICE_SKEW 3600 + if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && + router_get_by_id_digest(chan->conn->identity_digest)) { + char dbuf[64]; + int severity; + /*XXXX be smarter about when everybody says we are skewed. */ + if (router_digest_is_trusted_dir(chan->conn->identity_digest)) + severity = LOG_WARN; + else + severity = LOG_INFO; + format_time_interval(dbuf, sizeof(dbuf), apparent_skew); + log_fn(severity, LD_GENERAL, + "Received NETINFO cell with skewed time from " + "server at %s:%d. It seems that our clock is %s by %s, or " + "that theirs is %s. Tor requires an accurate clock to work: " + "please check your time and date settings.", + chan->conn->base_.address, + (int)(chan->conn->base_.port), + apparent_skew > 0 ? "ahead" : "behind", + dbuf, + apparent_skew > 0 ? "behind" : "ahead"); + if (severity == LOG_WARN) /* only tell the controller if an authority */ + control_event_general_status(LOG_WARN, + "CLOCK_SKEW SKEW=%ld SOURCE=OR:%s:%d", + apparent_skew, + chan->conn->base_.address, + chan->conn->base_.port); + } + + /* XXX maybe act on my_apparent_addr, if the source is sufficiently + * trustworthy. */ + + if (connection_or_set_state_open(chan->conn) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Got good NETINFO cell from %s:%d; but " + "was unable to make the OR connection become open.", + safe_str_client(chan->conn->base_.address), + chan->conn->base_.port); + connection_or_close_for_error(chan->conn, 0); + } 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. " + "Our address is apparently %s.", + safe_str_client(chan->conn->base_.address), + chan->conn->base_.port, + (int)(chan->conn->link_proto), + hex_str(TLS_CHAN_TO_BASE(chan)->identity_digest, + DIGEST_LEN), + tor_addr_is_null(&my_apparent_addr) ? + "<none>" : fmt_and_decorate_addr(&my_apparent_addr)); + } + assert_connection_ok(TO_CONN(chan->conn),time(NULL)); +} + +/** + * Process a CERTS cell from a channel. + * + * This function is called to process an incoming CERTS cell on a + * channel_tls_t: + * + * If the other side should not have sent us a CERTS cell, or the cell is + * malformed, or it is supposed to authenticate the TLS key but it doesn't, + * then mark the connection. + * + * If the cell has a good cert chain and we're doing a v3 handshake, then + * store the certificates in or_handshake_state. If this is the client side + * of the connection, we then authenticate the server or mark the connection. + * If it's the server side, wait for an AUTHENTICATE cell. + */ + +static void +channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) +{ + tor_cert_t *link_cert = NULL; + tor_cert_t *id_cert = NULL; + tor_cert_t *auth_cert = NULL; + uint8_t *ptr; + int n_certs, i; + int send_netinfo = 0; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + +#define ERR(s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad CERTS cell from %s:%d: %s", \ + safe_str(chan->conn->base_.address), \ + chan->conn->base_.port, (s)); \ + connection_or_close_for_error(chan->conn, 0); \ + goto err; \ + } while (0) + + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) + ERR("We're not doing a v3 handshake!"); + if (chan->conn->link_proto < 3) + ERR("We're not using link protocol >= 3"); + if (chan->conn->handshake_state->received_certs_cell) + ERR("We already got one"); + if (chan->conn->handshake_state->authenticated) { + /* Should be unreachable, but let's make sure. */ + ERR("We're already authenticated!"); + } + if (cell->payload_len < 1) + ERR("It had no body"); + if (cell->circ_id) + ERR("It had a nonzero circuit ID"); + + n_certs = cell->payload[0]; + ptr = cell->payload + 1; + for (i = 0; i < n_certs; ++i) { + uint8_t cert_type; + uint16_t cert_len; + if (ptr + 3 > cell->payload + cell->payload_len) { + goto truncated; + } + cert_type = *ptr; + cert_len = ntohs(get_uint16(ptr+1)); + if (ptr + 3 + cert_len > cell->payload + cell->payload_len) { + goto truncated; + } + if (cert_type == OR_CERT_TYPE_TLS_LINK || + cert_type == OR_CERT_TYPE_ID_1024 || + cert_type == OR_CERT_TYPE_AUTH_1024) { + tor_cert_t *cert = tor_cert_decode(ptr + 3, cert_len); + if (!cert) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received undecodable certificate in CERTS cell from %s:%d", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + } else { + if (cert_type == OR_CERT_TYPE_TLS_LINK) { + if (link_cert) { + tor_cert_free(cert); + ERR("Too many TLS_LINK certificates"); + } + link_cert = cert; + } else if (cert_type == OR_CERT_TYPE_ID_1024) { + if (id_cert) { + tor_cert_free(cert); + ERR("Too many ID_1024 certificates"); + } + id_cert = cert; + } else if (cert_type == OR_CERT_TYPE_AUTH_1024) { + if (auth_cert) { + tor_cert_free(cert); + ERR("Too many AUTH_1024 certificates"); + } + auth_cert = cert; + } else { + tor_cert_free(cert); + } + } + } + ptr += 3 + cert_len; + continue; + + truncated: + ERR("It ends in the middle of a certificate"); + } + + if (chan->conn->handshake_state->started_here) { + int severity; + if (! (id_cert && link_cert)) + ERR("The certs we wanted were missing"); + /* Okay. We should be able to check the certificates now. */ + if (! tor_tls_cert_matches_key(chan->conn->tls, link_cert)) { + ERR("The link certificate didn't match the TLS public key"); + } + /* Note that this warns more loudly about time and validity if we were + * _trying_ to connect to an authority, not necessarily if we _did_ connect + * to one. */ + if (router_digest_is_trusted_dir( + TLS_CHAN_TO_BASE(chan)->identity_digest)) + severity = LOG_WARN; + else + severity = LOG_PROTOCOL_WARN; + + if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, 0)) + ERR("The link certificate was not valid"); + if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, 1)) + ERR("The ID certificate was not valid"); + + chan->conn->handshake_state->authenticated = 1; + { + const digests_t *id_digests = tor_cert_get_id_digests(id_cert); + crypto_pk_t *identity_rcvd; + if (!id_digests) + ERR("Couldn't compute digests for key in ID cert"); + + identity_rcvd = tor_tls_cert_get_key(id_cert); + if (!identity_rcvd) + ERR("Internal error: Couldn't get RSA key from ID cert."); + memcpy(chan->conn->handshake_state->authenticated_peer_id, + id_digests->d[DIGEST_SHA1], DIGEST_LEN); + channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd); + crypto_pk_free(identity_rcvd); + } + + if (connection_or_client_learned_peer_id(chan->conn, + chan->conn->handshake_state->authenticated_peer_id) < 0) + ERR("Problem setting or checking peer id"); + + log_info(LD_OR, + "Got some good certificates from %s:%d: Authenticated it.", + safe_str(chan->conn->base_.address), chan->conn->base_.port); + + chan->conn->handshake_state->id_cert = id_cert; + id_cert = NULL; + + if (!public_server_mode(get_options())) { + /* If we initiated the connection and we are not a public server, we + * aren't planning to authenticate at all. At this point we know who we + * are talking to, so we can just send a netinfo now. */ + send_netinfo = 1; + } + } else { + if (! (id_cert && auth_cert)) + ERR("The certs we wanted were missing"); + + /* Remember these certificates so we can check an AUTHENTICATE cell */ + if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, 1)) + ERR("The authentication certificate was not valid"); + if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, 1)) + ERR("The ID certificate was not valid"); + + log_info(LD_OR, + "Got some good certificates from %s:%d: " + "Waiting for AUTHENTICATE.", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + /* XXXX check more stuff? */ + + chan->conn->handshake_state->id_cert = id_cert; + chan->conn->handshake_state->auth_cert = auth_cert; + id_cert = auth_cert = NULL; + } + + chan->conn->handshake_state->received_certs_cell = 1; + + if (send_netinfo) { + if (connection_or_send_netinfo(chan->conn) < 0) { + log_warn(LD_OR, "Couldn't send netinfo cell"); + connection_or_close_for_error(chan->conn, 0); + goto err; + } + } + + err: + tor_cert_free(id_cert); + tor_cert_free(link_cert); + tor_cert_free(auth_cert); +#undef ERR +} + +/** + * Process an AUTH_CHALLENGE cell from a channel_tls_t + * + * This function is called to handle an incoming AUTH_CHALLENGE cell on a + * channel_tls_t; if we weren't supposed to get one (for example, because we're + * not the originator of the channel), or it's ill-formed, or we aren't doing + * a v3 handshake, mark the channel. If the cell is well-formed but we don't + * want to authenticate, just drop it. If the cell is well-formed *and* we + * want to authenticate, send an AUTHENTICATE cell and then a NETINFO cell. + */ + +static void +channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan) +{ + int n_types, i, use_type = -1; + uint8_t *cp; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + +#define ERR(s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad AUTH_CHALLENGE cell from %s:%d: %s", \ + safe_str(chan->conn->base_.address), \ + chan->conn->base_.port, (s)); \ + connection_or_close_for_error(chan->conn, 0); \ + return; \ + } while (0) + + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) + ERR("We're not currently doing a v3 handshake"); + if (chan->conn->link_proto < 3) + ERR("We're not using link protocol >= 3"); + if (!(chan->conn->handshake_state->started_here)) + ERR("We didn't originate this connection"); + if (chan->conn->handshake_state->received_auth_challenge) + ERR("We already received one"); + if (!(chan->conn->handshake_state->received_certs_cell)) + ERR("We haven't gotten a CERTS cell yet"); + if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2) + ERR("It was too short"); + if (cell->circ_id) + ERR("It had a nonzero circuit ID"); + + n_types = ntohs(get_uint16(cell->payload + OR_AUTH_CHALLENGE_LEN)); + if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2 + 2*n_types) + ERR("It looks truncated"); + + /* Now see if there is an authentication type we can use */ + cp = cell->payload+OR_AUTH_CHALLENGE_LEN + 2; + for (i = 0; i < n_types; ++i, cp += 2) { + uint16_t authtype = ntohs(get_uint16(cp)); + if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET) + use_type = authtype; + } + + chan->conn->handshake_state->received_auth_challenge = 1; + + if (! public_server_mode(get_options())) { + /* If we're not a public server then we don't want to authenticate on a + connection we originated, and we already sent a NETINFO cell when we + got the CERTS cell. We have nothing more to do. */ + return; + } + + if (use_type >= 0) { + log_info(LD_OR, + "Got an AUTH_CHALLENGE cell from %s:%d: Sending " + "authentication", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + + if (connection_or_send_authenticate_cell(chan->conn, use_type) < 0) { + log_warn(LD_OR, + "Couldn't send authenticate cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + } else { + log_info(LD_OR, + "Got an AUTH_CHALLENGE cell from %s:%d, but we don't " + "know any of its authentication types. Not authenticating.", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + } + + if (connection_or_send_netinfo(chan->conn) < 0) { + log_warn(LD_OR, "Couldn't send netinfo cell"); + connection_or_close_for_error(chan->conn, 0); + return; + } + +#undef ERR +} + +/** + * Process an AUTHENTICATE cell from a channel_tls_t + * + * If it's ill-formed or we weren't supposed to get one or we're not doing a + * v3 handshake, then mark the connection. If it does not authenticate the + * other side of the connection successfully (because it isn't signed right, + * we didn't get a CERTS cell, etc) mark the connection. Otherwise, accept + * the identity of the router on the other side of the connection. + */ + +static void +channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) +{ + uint8_t expected[V3_AUTH_FIXED_PART_LEN]; + const uint8_t *auth; + int authlen; + + tor_assert(cell); + tor_assert(chan); + tor_assert(chan->conn); + +#define ERR(s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad AUTHENTICATE cell from %s:%d: %s", \ + safe_str(chan->conn->base_.address), \ + chan->conn->base_.port, (s)); \ + connection_or_close_for_error(chan->conn, 0); \ + return; \ + } while (0) + + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) + ERR("We're not doing a v3 handshake"); + if (chan->conn->link_proto < 3) + ERR("We're not using link protocol >= 3"); + if (chan->conn->handshake_state->started_here) + ERR("We originated this connection"); + if (chan->conn->handshake_state->received_authenticate) + ERR("We already got one!"); + if (chan->conn->handshake_state->authenticated) { + /* Should be impossible given other checks */ + ERR("The peer is already authenticated"); + } + if (!(chan->conn->handshake_state->received_certs_cell)) + ERR("We never got a certs cell"); + if (chan->conn->handshake_state->auth_cert == NULL) + ERR("We never got an authentication certificate"); + if (chan->conn->handshake_state->id_cert == NULL) + ERR("We never got an identity certificate"); + if (cell->payload_len < 4) + ERR("Cell was way too short"); + + auth = cell->payload; + { + uint16_t type = ntohs(get_uint16(auth)); + uint16_t len = ntohs(get_uint16(auth+2)); + if (4 + len > cell->payload_len) + ERR("Authenticator was truncated"); + + if (type != AUTHTYPE_RSA_SHA256_TLSSECRET) + ERR("Authenticator type was not recognized"); + + auth += 4; + authlen = len; + } + + if (authlen < V3_AUTH_BODY_LEN + 1) + ERR("Authenticator was too short"); + + if (connection_or_compute_authenticate_cell_body( + chan->conn, expected, sizeof(expected), NULL, 1) < 0) + ERR("Couldn't compute expected AUTHENTICATE cell body"); + + if (tor_memneq(expected, auth, sizeof(expected))) + ERR("Some field in the AUTHENTICATE cell body was not as expected"); + + { + crypto_pk_t *pk = tor_tls_cert_get_key( + chan->conn->handshake_state->auth_cert); + char d[DIGEST256_LEN]; + char *signed_data; + size_t keysize; + int signed_len; + + if (!pk) + ERR("Internal error: couldn't get RSA key from AUTH cert."); + crypto_digest256(d, (char*)auth, V3_AUTH_BODY_LEN, DIGEST_SHA256); + + keysize = crypto_pk_keysize(pk); + signed_data = tor_malloc(keysize); + signed_len = crypto_pk_public_checksig(pk, signed_data, keysize, + (char*)auth + V3_AUTH_BODY_LEN, + authlen - V3_AUTH_BODY_LEN); + crypto_pk_free(pk); + if (signed_len < 0) { + tor_free(signed_data); + ERR("Signature wasn't valid"); + } + if (signed_len < DIGEST256_LEN) { + tor_free(signed_data); + ERR("Not enough data was signed"); + } + /* Note that we deliberately allow *more* than DIGEST256_LEN bytes here, + * in case they're later used to hold a SHA3 digest or something. */ + if (tor_memneq(signed_data, d, DIGEST256_LEN)) { + tor_free(signed_data); + ERR("Signature did not match data to be signed."); + } + tor_free(signed_data); + } + + /* Okay, we are authenticated. */ + chan->conn->handshake_state->received_authenticate = 1; + chan->conn->handshake_state->authenticated = 1; + chan->conn->handshake_state->digest_received_data = 0; + { + crypto_pk_t *identity_rcvd = + tor_tls_cert_get_key(chan->conn->handshake_state->id_cert); + const digests_t *id_digests = + tor_cert_get_id_digests(chan->conn->handshake_state->id_cert); + + /* This must exist; we checked key type when reading the cert. */ + tor_assert(id_digests); + + memcpy(chan->conn->handshake_state->authenticated_peer_id, + id_digests->d[DIGEST_SHA1], DIGEST_LEN); + + channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd); + crypto_pk_free(identity_rcvd); + + connection_or_init_conn_from_address(chan->conn, + &(chan->conn->base_.addr), + chan->conn->base_.port, + (const char*)(chan->conn->handshake_state-> + authenticated_peer_id), + 0); + + log_info(LD_OR, + "Got an AUTHENTICATE cell from %s:%d: Looks good.", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + } + +#undef ERR +} + diff --git a/src/or/channeltls.h b/src/or/channeltls.h new file mode 100644 index 000000000..b7e0475de --- /dev/null +++ b/src/or/channeltls.h @@ -0,0 +1,57 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file channeltls.h + * \brief Header file for channeltls.c + **/ + +#ifndef TOR_CHANNELTLS_H +#define TOR_CHANNELTLS_H + +#include "or.h" +#include "channel.h" + +#define BASE_CHAN_TO_TLS(c) (channel_tls_from_base((c))) +#define TLS_CHAN_TO_BASE(c) (channel_tls_to_base((c))) + +#define TLS_CHAN_MAGIC 0x8a192427U + +#ifdef TOR_CHANNEL_INTERNAL_ + +struct channel_tls_s { + /* Base channel_t struct */ + channel_t base_; + /* or_connection_t pointer */ + or_connection_t *conn; +}; + +#endif /* TOR_CHANNEL_INTERNAL_ */ + +channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, + const char *id_digest); +channel_listener_t * channel_tls_get_listener(void); +channel_listener_t * channel_tls_start_listener(void); +channel_t * channel_tls_handle_incoming(or_connection_t *orconn); + +/* Casts */ + +channel_t * channel_tls_to_base(channel_tls_t *tlschan); +channel_tls_t * channel_tls_from_base(channel_t *chan); + +/* Things for connection_or.c to call back into */ +ssize_t channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells); +int channel_tls_more_to_flush(channel_tls_t *chan); +void channel_tls_handle_cell(cell_t *cell, or_connection_t *conn); +void channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, + or_connection_t *conn, + uint8_t old_state, + uint8_t state); +void channel_tls_handle_var_cell(var_cell_t *var_cell, + or_connection_t *conn); + +/* Cleanup at shutdown */ +void channel_tls_free_all(void); + +#endif + diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index f8521c5cf..b304aebde 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -9,22 +9,27 @@ * \brief The actual details of building circuits. **/ -#define CIRCUIT_PRIVATE - #include "or.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuitstats.h" #include "circuituse.h" +#include "command.h" #include "config.h" +#include "confparse.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" #include "control.h" #include "directory.h" +#include "entrynodes.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" +#include "onion_tap.h" +#include "onion_fast.h" #include "policies.h" #include "transports.h" #include "relay.h" @@ -32,1645 +37,52 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" #include "crypto.h" -#undef log -#include <math.h> +#include "connection_edge.h" #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif -#define CBT_BIN_TO_MS(bin) ((bin)*CBT_BIN_WIDTH + (CBT_BIN_WIDTH/2)) - /********* START VARIABLES **********/ -/** Global list of circuit build times */ -// XXXX: Add this as a member for entry_guard_t instead of global? -// Then we could do per-guard statistics, as guards are likely to -// vary in their own latency. The downside of this is that guards -// can change frequently, so we'd be building a lot more circuits -// most likely. -/* XXXX024 Make this static; add accessor functions. */ -circuit_build_times_t circ_times; /** A global list of all circuits at this hop. */ extern circuit_t *global_circuitlist; -/** An entry_guard_t represents our information about a chosen long-term - * first hop, known as a "helper" node in the literature. We can't just - * use a node_t, since we want to remember these even when we - * don't have any directory info. */ -typedef struct { - char nickname[MAX_NICKNAME_LEN+1]; - char identity[DIGEST_LEN]; - time_t chosen_on_date; /**< Approximately when was this guard added? - * "0" if we don't know. */ - char *chosen_by_version; /**< What tor version added this guard? NULL - * if we don't know. */ - unsigned int made_contact : 1; /**< 0 if we have never connected to this - * router, 1 if we have. */ - unsigned int can_retry : 1; /**< Should we retry connecting to this entry, - * in spite of having it marked as unreachable?*/ - unsigned int path_bias_notice : 1; /**< Did we alert the user about path bias - * for this node already? */ - unsigned int path_bias_disabled : 1; /**< Have we disabled this node because - * of path bias issues? */ - time_t bad_since; /**< 0 if this guard is currently usable, or the time at - * which it was observed to become (according to the - * directory or the user configuration) unusable. */ - time_t unreachable_since; /**< 0 if we can connect to this guard, or the - * time at which we first noticed we couldn't - * connect to it. */ - time_t last_attempted; /**< 0 if we can connect to this guard, or the time - * at which we last failed to connect to it. */ - - unsigned first_hops; /**< Number of first hops this guard has completed */ - unsigned circuit_successes; /**< Number of successfully built circuits using - * this guard as first hop. */ -} entry_guard_t; - -/** Information about a configured bridge. Currently this just matches the - * ones in the torrc file, but one day we may be able to learn about new - * bridges on our own, and remember them in the state file. */ -typedef struct { - /** Address of the bridge. */ - tor_addr_t addr; - /** TLS port for the bridge. */ - uint16_t port; - /** Boolean: We are re-parsing our bridge list, and we are going to remove - * this one if we don't find it in the list of configured bridges. */ - unsigned marked_for_removal : 1; - /** Expected identity digest, or all zero bytes if we don't know what the - * digest should be. */ - char identity[DIGEST_LEN]; - - /** Name of pluggable transport protocol taken from its config line. */ - char *transport_name; - - /** When should we next try to fetch a descriptor for this bridge? */ - download_status_t fetch_status; -} bridge_info_t; - -/** A list of our chosen entry guards. */ -static smartlist_t *entry_guards = NULL; -/** A value of 1 means that the entry_guards list has changed - * and those changes need to be flushed to disk. */ -static int entry_guards_dirty = 0; - -/** If set, we're running the unit tests: we should avoid clobbering - * our state file or accessing get_options() or get_or_state() */ -static int unit_tests = 0; - /********* END VARIABLES ************/ +static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, + uint16_t port, + const char *id_digest); static int circuit_deliver_create_cell(circuit_t *circ, - uint8_t cell_type, const char *payload); + const create_cell_t *create_cell, + int relayed); static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); +static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard); +static void pathbias_count_build_success(origin_circuit_t *circ); +static void pathbias_count_successful_close(origin_circuit_t *circ); +static void pathbias_count_collapse(origin_circuit_t *circ); +static void pathbias_count_unusable(origin_circuit_t *circ); -static void entry_guards_changed(void); -static entry_guard_t *entry_guard_get_by_id_digest(const char *digest); - -static void bridge_free(bridge_info_t *bridge); - -static int entry_guard_inc_first_hop_count(entry_guard_t *guard); -static void pathbias_count_success(origin_circuit_t *circ); - -/** - * This function decides if CBT learning should be disabled. It returns - * true if one or more of the following four conditions are met: - * - * 1. If the cbtdisabled consensus parameter is set. - * 2. If the torrc option LearnCircuitBuildTimeout is false. - * 3. If we are a directory authority - * 4. If we fail to write circuit build time history to our state file. - */ -static int -circuit_build_times_disabled(void) -{ - if (unit_tests) { - return 0; - } else { - int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled", - 0, 0, 1); - int config_disabled = !get_options()->LearnCircuitBuildTimeout; - int dirauth_disabled = get_options()->AuthoritativeDir; - int state_disabled = did_last_state_file_write_fail() ? 1 : 0; - - if (consensus_disabled || config_disabled || dirauth_disabled || - state_disabled) { - log_debug(LD_CIRC, - "CircuitBuildTime learning is disabled. " - "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", - consensus_disabled, config_disabled, dirauth_disabled, - state_disabled); - return 1; - } else { - log_debug(LD_CIRC, - "CircuitBuildTime learning is not disabled. " - "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", - consensus_disabled, config_disabled, dirauth_disabled, - state_disabled); - return 0; - } - } -} - -/** - * Retrieve and bounds-check the cbtmaxtimeouts consensus paramter. - * - * Effect: When this many timeouts happen in the last 'cbtrecentcount' - * circuit attempts, the client should discard all of its history and - * begin learning a fresh timeout value. - */ -static int32_t -circuit_build_times_max_timeouts(void) -{ - int32_t cbt_maxtimeouts; - - cbt_maxtimeouts = networkstatus_get_param(NULL, "cbtmaxtimeouts", - CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT, - CBT_MIN_MAX_RECENT_TIMEOUT_COUNT, - CBT_MAX_MAX_RECENT_TIMEOUT_COUNT); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_max_timeouts() called, cbtmaxtimeouts is" - " %d", - cbt_maxtimeouts); - } - - return cbt_maxtimeouts; -} - -/** - * Retrieve and bounds-check the cbtnummodes consensus paramter. - * - * Effect: This value governs how many modes to use in the weighted - * average calculation of Pareto parameter Xm. A value of 3 introduces - * some bias (2-5% of CDF) under ideal conditions, but allows for better - * performance in the event that a client chooses guard nodes of radically - * different performance characteristics. - */ -static int32_t -circuit_build_times_default_num_xm_modes(void) -{ - int32_t num = networkstatus_get_param(NULL, "cbtnummodes", - CBT_DEFAULT_NUM_XM_MODES, - CBT_MIN_NUM_XM_MODES, - CBT_MAX_NUM_XM_MODES); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_default_num_xm_modes() called, cbtnummodes" - " is %d", - num); - } - - return num; -} - -/** - * Retrieve and bounds-check the cbtmincircs consensus paramter. - * - * Effect: This is the minimum number of circuits to build before - * computing a timeout. - */ -static int32_t -circuit_build_times_min_circs_to_observe(void) -{ - int32_t num = networkstatus_get_param(NULL, "cbtmincircs", - CBT_DEFAULT_MIN_CIRCUITS_TO_OBSERVE, - CBT_MIN_MIN_CIRCUITS_TO_OBSERVE, - CBT_MAX_MIN_CIRCUITS_TO_OBSERVE); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_min_circs_to_observe() called, cbtmincircs" - " is %d", - num); - } - - return num; -} - -/** Return true iff <b>cbt</b> has recorded enough build times that we - * want to start acting on the timeout it implies. */ -int -circuit_build_times_enough_to_compute(circuit_build_times_t *cbt) -{ - return cbt->total_build_times >= circuit_build_times_min_circs_to_observe(); -} - -/** - * Retrieve and bounds-check the cbtquantile consensus paramter. - * - * Effect: This is the position on the quantile curve to use to set the - * timeout value. It is a percent (10-99). - */ -double -circuit_build_times_quantile_cutoff(void) -{ - int32_t num = networkstatus_get_param(NULL, "cbtquantile", - CBT_DEFAULT_QUANTILE_CUTOFF, - CBT_MIN_QUANTILE_CUTOFF, - CBT_MAX_QUANTILE_CUTOFF); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_quantile_cutoff() called, cbtquantile" - " is %d", - num); - } - - return num/100.0; -} - -/* DOCDOC circuit_build_times_get_bw_scale */ -int -circuit_build_times_get_bw_scale(networkstatus_t *ns) -{ - return networkstatus_get_param(ns, "bwweightscale", - BW_WEIGHT_SCALE, - BW_MIN_WEIGHT_SCALE, - BW_MAX_WEIGHT_SCALE); -} - -/** - * Retrieve and bounds-check the cbtclosequantile consensus paramter. - * - * Effect: This is the position on the quantile curve to use to set the - * timeout value to use to actually close circuits. It is a percent - * (0-99). - */ -static double -circuit_build_times_close_quantile(void) -{ - int32_t param; - /* Cast is safe - circuit_build_times_quantile_cutoff() is capped */ - int32_t min = (int)tor_lround(100*circuit_build_times_quantile_cutoff()); - param = networkstatus_get_param(NULL, "cbtclosequantile", - CBT_DEFAULT_CLOSE_QUANTILE, - CBT_MIN_CLOSE_QUANTILE, - CBT_MAX_CLOSE_QUANTILE); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_close_quantile() called, cbtclosequantile" - " is %d", param); - } - - if (param < min) { - log_warn(LD_DIR, "Consensus parameter cbtclosequantile is " - "too small, raising to %d", min); - param = min; - } - return param / 100.0; -} - -/** - * Retrieve and bounds-check the cbttestfreq consensus paramter. - * - * Effect: Describes how often in seconds to build a test circuit to - * gather timeout values. Only applies if less than 'cbtmincircs' - * have been recorded. - */ -static int32_t -circuit_build_times_test_frequency(void) -{ - int32_t num = networkstatus_get_param(NULL, "cbttestfreq", - CBT_DEFAULT_TEST_FREQUENCY, - CBT_MIN_TEST_FREQUENCY, - CBT_MAX_TEST_FREQUENCY); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_test_frequency() called, cbttestfreq is %d", - num); - } - - return num; -} - -/** - * Retrieve and bounds-check the cbtmintimeout consensus parameter. - * - * Effect: This is the minimum allowed timeout value in milliseconds. - * The minimum is to prevent rounding to 0 (we only check once - * per second). - */ -static int32_t -circuit_build_times_min_timeout(void) -{ - int32_t num = networkstatus_get_param(NULL, "cbtmintimeout", - CBT_DEFAULT_TIMEOUT_MIN_VALUE, - CBT_MIN_TIMEOUT_MIN_VALUE, - CBT_MAX_TIMEOUT_MIN_VALUE); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_min_timeout() called, cbtmintimeout is %d", - num); - } - - return num; -} - -/** - * Retrieve and bounds-check the cbtinitialtimeout consensus paramter. - * - * Effect: This is the timeout value to use before computing a timeout, - * in milliseconds. - */ -int32_t -circuit_build_times_initial_timeout(void) -{ - int32_t min = circuit_build_times_min_timeout(); - int32_t param = networkstatus_get_param(NULL, "cbtinitialtimeout", - CBT_DEFAULT_TIMEOUT_INITIAL_VALUE, - CBT_MIN_TIMEOUT_INITIAL_VALUE, - CBT_MAX_TIMEOUT_INITIAL_VALUE); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_initial_timeout() called, " - "cbtinitialtimeout is %d", - param); - } - - if (param < min) { - log_warn(LD_DIR, "Consensus parameter cbtinitialtimeout is too small, " - "raising to %d", min); - param = min; - } - return param; -} - -/** - * Retrieve and bounds-check the cbtrecentcount consensus paramter. - * - * Effect: This is the number of circuit build times to keep track of - * for deciding if we hit cbtmaxtimeouts and need to reset our state - * and learn a new timeout. - */ -static int32_t -circuit_build_times_recent_circuit_count(networkstatus_t *ns) -{ - int32_t num; - num = networkstatus_get_param(ns, "cbtrecentcount", - CBT_DEFAULT_RECENT_CIRCUITS, - CBT_MIN_RECENT_CIRCUITS, - CBT_MAX_RECENT_CIRCUITS); - - if (!(get_options()->LearnCircuitBuildTimeout)) { - log_debug(LD_BUG, - "circuit_build_times_recent_circuit_count() called, " - "cbtrecentcount is %d", - num); - } - - return num; -} - -/** - * This function is called when we get a consensus update. - * - * It checks to see if we have changed any consensus parameters - * that require reallocation or discard of previous stats. - */ -void -circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, - networkstatus_t *ns) -{ - int32_t num; - - /* - * First check if we're doing adaptive timeouts at all; nothing to - * update if we aren't. - */ - - if (!circuit_build_times_disabled()) { - num = circuit_build_times_recent_circuit_count(ns); - - if (num > 0) { - if (num != cbt->liveness.num_recent_circs) { - int8_t *recent_circs; - log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " - "circuits we must track to detect network failures from %d " - "to %d.", cbt->liveness.num_recent_circs, num); - - tor_assert(cbt->liveness.timeouts_after_firsthop || - cbt->liveness.num_recent_circs == 0); - - /* - * Technically this is a circular array that we are reallocating - * and memcopying. However, since it only consists of either 1s - * or 0s, and is only used in a statistical test to determine when - * we should discard our history after a sufficient number of 1's - * have been reached, it is fine if order is not preserved or - * elements are lost. - * - * cbtrecentcount should only be changing in cases of severe network - * distress anyway, so memory correctness here is paramount over - * doing acrobatics to preserve the array. - */ - recent_circs = tor_malloc_zero(sizeof(int8_t)*num); - if (cbt->liveness.timeouts_after_firsthop && - cbt->liveness.num_recent_circs > 0) { - memcpy(recent_circs, cbt->liveness.timeouts_after_firsthop, - sizeof(int8_t)*MIN(num, cbt->liveness.num_recent_circs)); - } - - // Adjust the index if it needs it. - if (num < cbt->liveness.num_recent_circs) { - cbt->liveness.after_firsthop_idx = MIN(num-1, - cbt->liveness.after_firsthop_idx); - } - - tor_free(cbt->liveness.timeouts_after_firsthop); - cbt->liveness.timeouts_after_firsthop = recent_circs; - cbt->liveness.num_recent_circs = num; - } - /* else no change, nothing to do */ - } else { /* num == 0 */ - /* - * Weird. This probably shouldn't happen, so log a warning, but try - * to do something sensible anyway. - */ - - log_warn(LD_CIRC, - "The cbtrecentcircs consensus parameter came back zero! " - "This disables adaptive timeouts since we can't keep track of " - "any recent circuits."); - - circuit_build_times_free_timeouts(cbt); - } - } else { - /* - * Adaptive timeouts are disabled; this might be because of the - * LearnCircuitBuildTimes config parameter, and hence permanent, or - * the cbtdisabled consensus parameter, so it may be a new condition. - * Treat it like getting num == 0 above and free the circuit history - * if we have any. - */ - - circuit_build_times_free_timeouts(cbt); - } -} - -/** Make a note that we're running unit tests (rather than running Tor - * itself), so we avoid clobbering our state file. */ -void -circuitbuild_running_unit_tests(void) -{ - unit_tests = 1; -} - -/** - * Return the initial default or configured timeout in milliseconds - */ -static double -circuit_build_times_get_initial_timeout(void) -{ - double timeout; - - /* - * Check if we have LearnCircuitBuildTimeout, and if we don't, - * always use CircuitBuildTimeout, no questions asked. - */ - if (get_options()->LearnCircuitBuildTimeout) { - if (!unit_tests && get_options()->CircuitBuildTimeout) { - timeout = get_options()->CircuitBuildTimeout*1000; - if (timeout < circuit_build_times_min_timeout()) { - log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds", - circuit_build_times_min_timeout()/1000); - timeout = circuit_build_times_min_timeout(); - } - } else { - timeout = circuit_build_times_initial_timeout(); - } - } else { - timeout = get_options()->CircuitBuildTimeout*1000; - } - - return timeout; -} - -/** - * Reset the build time state. - * - * Leave estimated parameters, timeout and network liveness intact - * for future use. - */ -void -circuit_build_times_reset(circuit_build_times_t *cbt) -{ - memset(cbt->circuit_build_times, 0, sizeof(cbt->circuit_build_times)); - cbt->total_build_times = 0; - cbt->build_times_idx = 0; - cbt->have_computed_timeout = 0; -} - -/** - * Initialize the buildtimes structure for first use. - * - * Sets the initial timeout values based on either the config setting, - * the consensus param, or the default (CBT_DEFAULT_TIMEOUT_INITIAL_VALUE). - */ -void -circuit_build_times_init(circuit_build_times_t *cbt) -{ - memset(cbt, 0, sizeof(*cbt)); - /* - * Check if we really are using adaptive timeouts, and don't keep - * track of this stuff if not. - */ - if (!circuit_build_times_disabled()) { - cbt->liveness.num_recent_circs = - circuit_build_times_recent_circuit_count(NULL); - cbt->liveness.timeouts_after_firsthop = - tor_malloc_zero(sizeof(int8_t)*cbt->liveness.num_recent_circs); - } else { - cbt->liveness.num_recent_circs = 0; - cbt->liveness.timeouts_after_firsthop = NULL; - } - cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout(); - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); -} - -/** - * Free the saved timeouts, if the cbtdisabled consensus parameter got turned - * on or something. - */ - -void -circuit_build_times_free_timeouts(circuit_build_times_t *cbt) -{ - if (!cbt) return; - - if (cbt->liveness.timeouts_after_firsthop) { - tor_free(cbt->liveness.timeouts_after_firsthop); - } - - cbt->liveness.num_recent_circs = 0; -} - -#if 0 -/** - * Rewind our build time history by n positions. - */ -static void -circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n) -{ - int i = 0; - - cbt->build_times_idx -= n; - cbt->build_times_idx %= CBT_NCIRCUITS_TO_OBSERVE; - - for (i = 0; i < n; i++) { - cbt->circuit_build_times[(i+cbt->build_times_idx) - %CBT_NCIRCUITS_TO_OBSERVE]=0; - } - - if (cbt->total_build_times > n) { - cbt->total_build_times -= n; - } else { - cbt->total_build_times = 0; - } - - log_info(LD_CIRC, - "Rewound history by %d places. Current index: %d. " - "Total: %d", n, cbt->build_times_idx, cbt->total_build_times); -} -#endif - -/** - * Add a new build time value <b>time</b> to the set of build times. Time - * units are milliseconds. - * - * circuit_build_times <b>cbt</b> is a circular array, so loop around when - * array is full. - */ -int -circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time) -{ - if (time <= 0 || time > CBT_BUILD_TIME_MAX) { - log_warn(LD_BUG, "Circuit build time is too large (%u)." - "This is probably a bug.", time); - tor_fragile_assert(); - return -1; - } - - log_debug(LD_CIRC, "Adding circuit build time %u", time); - - cbt->circuit_build_times[cbt->build_times_idx] = time; - cbt->build_times_idx = (cbt->build_times_idx + 1) % CBT_NCIRCUITS_TO_OBSERVE; - if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE) - cbt->total_build_times++; - - if ((cbt->total_build_times % CBT_SAVE_STATE_EVERY) == 0) { - /* Save state every n circuit builds */ - if (!unit_tests && !get_options()->AvoidDiskWrites) - or_state_mark_dirty(get_or_state(), 0); - } - - return 0; -} - -/** - * Return maximum circuit build time - */ -static build_time_t -circuit_build_times_max(circuit_build_times_t *cbt) -{ - int i = 0; - build_time_t max_build_time = 0; - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] > max_build_time - && cbt->circuit_build_times[i] != CBT_BUILD_ABANDONED) - max_build_time = cbt->circuit_build_times[i]; - } - return max_build_time; -} - -#if 0 -/** Return minimum circuit build time */ -build_time_t -circuit_build_times_min(circuit_build_times_t *cbt) -{ - int i = 0; - build_time_t min_build_time = CBT_BUILD_TIME_MAX; - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] && /* 0 <-> uninitialized */ - cbt->circuit_build_times[i] < min_build_time) - min_build_time = cbt->circuit_build_times[i]; - } - if (min_build_time == CBT_BUILD_TIME_MAX) { - log_warn(LD_CIRC, "No build times less than CBT_BUILD_TIME_MAX!"); - } - return min_build_time; -} -#endif - -/** - * Calculate and return a histogram for the set of build times. - * - * Returns an allocated array of histrogram bins representing - * the frequency of index*CBT_BIN_WIDTH millisecond - * build times. Also outputs the number of bins in nbins. - * - * The return value must be freed by the caller. +/** This function tries to get a channel to the specified endpoint, + * and then calls command_setup_channel() to give it the right + * callbacks. */ -static uint32_t * -circuit_build_times_create_histogram(circuit_build_times_t *cbt, - build_time_t *nbins) +static channel_t * +channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, + const char *id_digest) { - uint32_t *histogram; - build_time_t max_build_time = circuit_build_times_max(cbt); - int i, c; + channel_t *chan; - *nbins = 1 + (max_build_time / CBT_BIN_WIDTH); - histogram = tor_malloc_zero(*nbins * sizeof(build_time_t)); + chan = channel_connect(addr, port, id_digest); + if (chan) command_setup_channel(chan); - // calculate histogram - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] == 0 - || cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) - continue; /* 0 <-> uninitialized */ - - c = (cbt->circuit_build_times[i] / CBT_BIN_WIDTH); - histogram[c]++; - } - - return histogram; -} - -/** - * Return the Pareto start-of-curve parameter Xm. - * - * Because we are not a true Pareto curve, we compute this as the - * weighted average of the N most frequent build time bins. N is either - * 1 if we don't have enough circuit build time data collected, or - * determined by the consensus parameter cbtnummodes (default 3). - */ -static build_time_t -circuit_build_times_get_xm(circuit_build_times_t *cbt) -{ - build_time_t i, nbins; - build_time_t *nth_max_bin; - int32_t bin_counts=0; - build_time_t ret = 0; - uint32_t *histogram = circuit_build_times_create_histogram(cbt, &nbins); - int n=0; - int num_modes = circuit_build_times_default_num_xm_modes(); - - tor_assert(nbins > 0); - tor_assert(num_modes > 0); - - // Only use one mode if < 1000 buildtimes. Not enough data - // for multiple. - if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE) - num_modes = 1; - - nth_max_bin = (build_time_t*)tor_malloc_zero(num_modes*sizeof(build_time_t)); - - /* Determine the N most common build times */ - for (i = 0; i < nbins; i++) { - if (histogram[i] >= histogram[nth_max_bin[0]]) { - nth_max_bin[0] = i; - } - - for (n = 1; n < num_modes; n++) { - if (histogram[i] >= histogram[nth_max_bin[n]] && - (!histogram[nth_max_bin[n-1]] - || histogram[i] < histogram[nth_max_bin[n-1]])) { - nth_max_bin[n] = i; - } - } - } - - for (n = 0; n < num_modes; n++) { - bin_counts += histogram[nth_max_bin[n]]; - ret += CBT_BIN_TO_MS(nth_max_bin[n])*histogram[nth_max_bin[n]]; - log_info(LD_CIRC, "Xm mode #%d: %u %u", n, CBT_BIN_TO_MS(nth_max_bin[n]), - histogram[nth_max_bin[n]]); - } - - /* The following assert is safe, because we don't get called when we - * haven't observed at least CBT_MIN_MIN_CIRCUITS_TO_OBSERVE circuits. */ - tor_assert(bin_counts > 0); - - ret /= bin_counts; - tor_free(histogram); - tor_free(nth_max_bin); - - return ret; -} - -/** - * Output a histogram of current circuit build times to - * the or_state_t state structure. - */ -void -circuit_build_times_update_state(circuit_build_times_t *cbt, - or_state_t *state) -{ - uint32_t *histogram; - build_time_t i = 0; - build_time_t nbins = 0; - config_line_t **next, *line; - - histogram = circuit_build_times_create_histogram(cbt, &nbins); - // write to state - config_free_lines(state->BuildtimeHistogram); - next = &state->BuildtimeHistogram; - *next = NULL; - - state->TotalBuildTimes = cbt->total_build_times; - state->CircuitBuildAbandonedCount = 0; - - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) - state->CircuitBuildAbandonedCount++; - } - - for (i = 0; i < nbins; i++) { - // compress the histogram by skipping the blanks - if (histogram[i] == 0) continue; - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("CircuitBuildTimeBin"); - tor_asprintf(&line->value, "%d %d", - CBT_BIN_TO_MS(i), histogram[i]); - next = &(line->next); - } - - if (!unit_tests) { - if (!get_options()->AvoidDiskWrites) - or_state_mark_dirty(get_or_state(), 0); - } - - tor_free(histogram); -} - -/** - * Shuffle the build times array. - * - * Adapted from http://en.wikipedia.org/wiki/Fisher-Yates_shuffle - */ -static void -circuit_build_times_shuffle_and_store_array(circuit_build_times_t *cbt, - build_time_t *raw_times, - uint32_t num_times) -{ - uint32_t n = num_times; - if (num_times > CBT_NCIRCUITS_TO_OBSERVE) { - log_notice(LD_CIRC, "The number of circuit times that this Tor version " - "uses to calculate build times is less than the number stored " - "in your state file. Decreasing the circuit time history from " - "%lu to %d.", (unsigned long)num_times, - CBT_NCIRCUITS_TO_OBSERVE); - } - - if (n > INT_MAX-1) { - log_warn(LD_CIRC, "For some insane reasons, you had %lu circuit build " - "observations in your state file. That's far too many; probably " - "there's a bug here.", (unsigned long)n); - n = INT_MAX-1; - } - - /* This code can only be run on a compact array */ - while (n-- > 1) { - int k = crypto_rand_int(n + 1); /* 0 <= k <= n. */ - build_time_t tmp = raw_times[k]; - raw_times[k] = raw_times[n]; - raw_times[n] = tmp; - } - - /* Since the times are now shuffled, take a random CBT_NCIRCUITS_TO_OBSERVE - * subset (ie the first CBT_NCIRCUITS_TO_OBSERVE values) */ - for (n = 0; n < MIN(num_times, CBT_NCIRCUITS_TO_OBSERVE); n++) { - circuit_build_times_add_time(cbt, raw_times[n]); - } -} - -/** - * Filter old synthetic timeouts that were created before the - * new right-censored Pareto calculation was deployed. - * - * Once all clients before 0.2.1.13-alpha are gone, this code - * will be unused. - */ -static int -circuit_build_times_filter_timeouts(circuit_build_times_t *cbt) -{ - int num_filtered=0, i=0; - double timeout_rate = 0; - build_time_t max_timeout = 0; - - timeout_rate = circuit_build_times_timeout_rate(cbt); - max_timeout = (build_time_t)cbt->close_ms; - - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] > max_timeout) { - build_time_t replaced = cbt->circuit_build_times[i]; - num_filtered++; - cbt->circuit_build_times[i] = CBT_BUILD_ABANDONED; - - log_debug(LD_CIRC, "Replaced timeout %d with %d", replaced, - cbt->circuit_build_times[i]); - } - } - - log_info(LD_CIRC, - "We had %d timeouts out of %d build times, " - "and filtered %d above the max of %u", - (int)(cbt->total_build_times*timeout_rate), - cbt->total_build_times, num_filtered, max_timeout); - - return num_filtered; -} - -/** - * Load histogram from <b>state</b>, shuffling the resulting array - * after we do so. Use this result to estimate parameters and - * calculate the timeout. - * - * Return -1 on error. - */ -int -circuit_build_times_parse_state(circuit_build_times_t *cbt, - or_state_t *state) -{ - int tot_values = 0; - uint32_t loaded_cnt = 0, N = 0; - config_line_t *line; - unsigned int i; - build_time_t *loaded_times; - int err = 0; - circuit_build_times_init(cbt); - - if (circuit_build_times_disabled()) { - return 0; - } - - /* build_time_t 0 means uninitialized */ - loaded_times = tor_malloc_zero(sizeof(build_time_t)*state->TotalBuildTimes); - - for (line = state->BuildtimeHistogram; line; line = line->next) { - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, line->value, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(args) < 2) { - log_warn(LD_GENERAL, "Unable to parse circuit build times: " - "Too few arguments to CircuitBuildTime"); - err = 1; - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - break; - } else { - const char *ms_str = smartlist_get(args,0); - const char *count_str = smartlist_get(args,1); - uint32_t count, k; - build_time_t ms; - int ok; - ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, - CBT_BUILD_TIME_MAX, &ok, NULL); - if (!ok) { - log_warn(LD_GENERAL, "Unable to parse circuit build times: " - "Unparsable bin number"); - err = 1; - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - break; - } - count = (uint32_t)tor_parse_ulong(count_str, 0, 0, - UINT32_MAX, &ok, NULL); - if (!ok) { - log_warn(LD_GENERAL, "Unable to parse circuit build times: " - "Unparsable bin count"); - err = 1; - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - break; - } - - if (loaded_cnt+count+state->CircuitBuildAbandonedCount - > state->TotalBuildTimes) { - log_warn(LD_CIRC, - "Too many build times in state file. " - "Stopping short before %d", - loaded_cnt+count); - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - break; - } - - for (k = 0; k < count; k++) { - loaded_times[loaded_cnt++] = ms; - } - N++; - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - } - } - - log_info(LD_CIRC, - "Adding %d timeouts.", state->CircuitBuildAbandonedCount); - for (i=0; i < state->CircuitBuildAbandonedCount; i++) { - loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED; - } - - if (loaded_cnt != state->TotalBuildTimes) { - log_warn(LD_CIRC, - "Corrupt state file? Build times count mismatch. " - "Read %d times, but file says %d", loaded_cnt, - state->TotalBuildTimes); - err = 1; - circuit_build_times_reset(cbt); - goto done; - } - - circuit_build_times_shuffle_and_store_array(cbt, loaded_times, loaded_cnt); - - /* Verify that we didn't overwrite any indexes */ - for (i=0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (!cbt->circuit_build_times[i]) - break; - tot_values++; - } - log_info(LD_CIRC, - "Loaded %d/%d values from %d lines in circuit time histogram", - tot_values, cbt->total_build_times, N); - - if (cbt->total_build_times != tot_values - || cbt->total_build_times > CBT_NCIRCUITS_TO_OBSERVE) { - log_warn(LD_CIRC, - "Corrupt state file? Shuffled build times mismatch. " - "Read %d times, but file says %d", tot_values, - state->TotalBuildTimes); - err = 1; - circuit_build_times_reset(cbt); - goto done; - } - - circuit_build_times_set_timeout(cbt); - - if (!state->CircuitBuildAbandonedCount && cbt->total_build_times) { - circuit_build_times_filter_timeouts(cbt); - } - - done: - tor_free(loaded_times); - return err ? -1 : 0; -} - -/** - * Estimates the Xm and Alpha parameters using - * http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation - * - * The notable difference is that we use mode instead of min to estimate Xm. - * This is because our distribution is frechet-like. We claim this is - * an acceptable approximation because we are only concerned with the - * accuracy of the CDF of the tail. - */ -int -circuit_build_times_update_alpha(circuit_build_times_t *cbt) -{ - build_time_t *x=cbt->circuit_build_times; - double a = 0; - int n=0,i=0,abandoned_count=0; - build_time_t max_time=0; - - /* http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation */ - /* We sort of cheat here and make our samples slightly more pareto-like - * and less frechet-like. */ - cbt->Xm = circuit_build_times_get_xm(cbt); - - tor_assert(cbt->Xm > 0); - - for (i=0; i< CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (!x[i]) { - continue; - } - - if (x[i] < cbt->Xm) { - a += tor_mathlog(cbt->Xm); - } else if (x[i] == CBT_BUILD_ABANDONED) { - abandoned_count++; - } else { - a += tor_mathlog(x[i]); - if (x[i] > max_time) - max_time = x[i]; - } - n++; - } - - /* - * We are erring and asserting here because this can only happen - * in codepaths other than startup. The startup state parsing code - * performs this same check, and resets state if it hits it. If we - * hit it at runtime, something serious has gone wrong. - */ - if (n!=cbt->total_build_times) { - log_err(LD_CIRC, "Discrepancy in build times count: %d vs %d", n, - cbt->total_build_times); - } - tor_assert(n==cbt->total_build_times); - - if (max_time <= 0) { - /* This can happen if Xm is actually the *maximum* value in the set. - * It can also happen if we've abandoned every single circuit somehow. - * In either case, tell the caller not to compute a new build timeout. */ - log_warn(LD_BUG, - "Could not determine largest build time (%d). " - "Xm is %dms and we've abandoned %d out of %d circuits.", max_time, - cbt->Xm, abandoned_count, n); - return 0; - } - - a += abandoned_count*tor_mathlog(max_time); - - a -= n*tor_mathlog(cbt->Xm); - // Estimator comes from Eq #4 in: - // "Bayesian estimation based on trimmed samples from Pareto populations" - // by Arturo J. Fernández. We are right-censored only. - a = (n-abandoned_count)/a; - - cbt->alpha = a; - - return 1; -} - -/** - * This is the Pareto Quantile Function. It calculates the point x - * in the distribution such that F(x) = quantile (ie quantile*100% - * of the mass of the density function is below x on the curve). - * - * We use it to calculate the timeout and also to generate synthetic - * values of time for circuits that timeout before completion. - * - * See http://en.wikipedia.org/wiki/Quantile_function, - * http://en.wikipedia.org/wiki/Inverse_transform_sampling and - * http://en.wikipedia.org/wiki/Pareto_distribution#Generating_a_ - * random_sample_from_Pareto_distribution - * That's right. I'll cite wikipedia all day long. - * - * Return value is in milliseconds. - */ -double -circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, - double quantile) -{ - double ret; - tor_assert(quantile >= 0); - tor_assert(1.0-quantile > 0); - tor_assert(cbt->Xm > 0); - - ret = cbt->Xm/pow(1.0-quantile,1.0/cbt->alpha); - if (ret > INT32_MAX) { - ret = INT32_MAX; - } - tor_assert(ret > 0); - return ret; -} - -/** Pareto CDF */ -double -circuit_build_times_cdf(circuit_build_times_t *cbt, double x) -{ - double ret; - tor_assert(cbt->Xm > 0); - ret = 1.0-pow(cbt->Xm/x,cbt->alpha); - tor_assert(0 <= ret && ret <= 1.0); - return ret; -} - -/** - * Generate a synthetic time using our distribution parameters. - * - * The return value will be within the [q_lo, q_hi) quantile points - * on the CDF. - */ -build_time_t -circuit_build_times_generate_sample(circuit_build_times_t *cbt, - double q_lo, double q_hi) -{ - double randval = crypto_rand_double(); - build_time_t ret; - double u; - - /* Generate between [q_lo, q_hi) */ - /*XXXX This is what nextafter is supposed to be for; we should use it on the - * platforms that support it. */ - q_hi -= 1.0/(INT32_MAX); - - tor_assert(q_lo >= 0); - tor_assert(q_hi < 1); - tor_assert(q_lo < q_hi); - - u = q_lo + (q_hi-q_lo)*randval; - - tor_assert(0 <= u && u < 1.0); - /* circuit_build_times_calculate_timeout returns <= INT32_MAX */ - ret = (build_time_t) - tor_lround(circuit_build_times_calculate_timeout(cbt, u)); - tor_assert(ret > 0); - return ret; -} - -/** - * Estimate an initial alpha parameter by solving the quantile - * function with a quantile point and a specific timeout value. - */ -void -circuit_build_times_initial_alpha(circuit_build_times_t *cbt, - double quantile, double timeout_ms) -{ - // Q(u) = Xm/((1-u)^(1/a)) - // Q(0.8) = Xm/((1-0.8))^(1/a)) = CircBuildTimeout - // CircBuildTimeout = Xm/((1-0.8))^(1/a)) - // CircBuildTimeout = Xm*((1-0.8))^(-1/a)) - // ln(CircBuildTimeout) = ln(Xm)+ln(((1-0.8)))*(-1/a) - // -ln(1-0.8)/(ln(CircBuildTimeout)-ln(Xm))=a - tor_assert(quantile >= 0); - tor_assert(cbt->Xm > 0); - cbt->alpha = tor_mathlog(1.0-quantile)/ - (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); - tor_assert(cbt->alpha > 0); -} - -/** - * Returns true if we need circuits to be built - */ -int -circuit_build_times_needs_circuits(circuit_build_times_t *cbt) -{ - /* Return true if < MIN_CIRCUITS_TO_OBSERVE */ - return !circuit_build_times_enough_to_compute(cbt); -} - -/** - * Returns true if we should build a timeout test circuit - * right now. - */ -int -circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt) -{ - return circuit_build_times_needs_circuits(cbt) && - approx_time()-cbt->last_circ_at > circuit_build_times_test_frequency(); -} - -/** - * Called to indicate that the network showed some signs of liveness, - * i.e. we received a cell. - * - * This is used by circuit_build_times_network_check_live() to decide - * if we should record the circuit build timeout or not. - * - * This function is called every time we receive a cell. Avoid - * syscalls, events, and other high-intensity work. - */ -void -circuit_build_times_network_is_live(circuit_build_times_t *cbt) -{ - time_t now = approx_time(); - if (cbt->liveness.nonlive_timeouts > 0) { - log_notice(LD_CIRC, - "Tor now sees network activity. Restoring circuit build " - "timeout recording. Network was down for %d seconds " - "during %d circuit attempts.", - (int)(now - cbt->liveness.network_last_live), - cbt->liveness.nonlive_timeouts); - } - cbt->liveness.network_last_live = now; - cbt->liveness.nonlive_timeouts = 0; -} - -/** - * Called to indicate that we completed a circuit. Because this circuit - * succeeded, it doesn't count as a timeout-after-the-first-hop. - * - * This is used by circuit_build_times_network_check_changed() to determine - * if we had too many recent timeouts and need to reset our learned timeout - * to something higher. - */ -void -circuit_build_times_network_circ_success(circuit_build_times_t *cbt) -{ - /* Check for NULLness because we might not be using adaptive timeouts */ - if (cbt->liveness.timeouts_after_firsthop && - cbt->liveness.num_recent_circs > 0) { - cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx] - = 0; - cbt->liveness.after_firsthop_idx++; - cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs; - } -} - -/** - * A circuit just timed out. If it failed after the first hop, record it - * in our history for later deciding if the network speed has changed. - * - * This is used by circuit_build_times_network_check_changed() to determine - * if we had too many recent timeouts and need to reset our learned timeout - * to something higher. - */ -static void -circuit_build_times_network_timeout(circuit_build_times_t *cbt, - int did_onehop) -{ - /* Check for NULLness because we might not be using adaptive timeouts */ - if (cbt->liveness.timeouts_after_firsthop && - cbt->liveness.num_recent_circs > 0) { - if (did_onehop) { - cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx] - = 1; - cbt->liveness.after_firsthop_idx++; - cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs; - } - } -} - -/** - * A circuit was just forcibly closed. If there has been no recent network - * activity at all, but this circuit was launched back when we thought the - * network was live, increment the number of "nonlive" circuit timeouts. - * - * This is used by circuit_build_times_network_check_live() to decide - * if we should record the circuit build timeout or not. - */ -static void -circuit_build_times_network_close(circuit_build_times_t *cbt, - int did_onehop, time_t start_time) -{ - time_t now = time(NULL); - /* - * Check if this is a timeout that was for a circuit that spent its - * entire existence during a time where we have had no network activity. - */ - if (cbt->liveness.network_last_live < start_time) { - if (did_onehop) { - char last_live_buf[ISO_TIME_LEN+1]; - char start_time_buf[ISO_TIME_LEN+1]; - char now_buf[ISO_TIME_LEN+1]; - format_local_iso_time(last_live_buf, cbt->liveness.network_last_live); - format_local_iso_time(start_time_buf, start_time); - format_local_iso_time(now_buf, now); - log_warn(LD_BUG, - "Circuit somehow completed a hop while the network was " - "not live. Network was last live at %s, but circuit launched " - "at %s. It's now %s.", last_live_buf, start_time_buf, - now_buf); - } - cbt->liveness.nonlive_timeouts++; - if (cbt->liveness.nonlive_timeouts == 1) { - log_notice(LD_CIRC, - "Tor has not observed any network activity for the past %d " - "seconds. Disabling circuit build timeout recording.", - (int)(now - cbt->liveness.network_last_live)); - } else { - log_info(LD_CIRC, - "Got non-live timeout. Current count is: %d", - cbt->liveness.nonlive_timeouts); - } - } -} - -/** - * When the network is not live, we do not record circuit build times. - * - * The network is considered not live if there has been at least one - * circuit build that began and ended (had its close_ms measurement - * period expire) since we last received a cell. - * - * Also has the side effect of rewinding the circuit time history - * in the case of recent liveness changes. - */ -int -circuit_build_times_network_check_live(circuit_build_times_t *cbt) -{ - if (cbt->liveness.nonlive_timeouts > 0) { - return 0; - } - - return 1; -} - -/** - * Returns true if we have seen more than MAX_RECENT_TIMEOUT_COUNT of - * the past RECENT_CIRCUITS time out after the first hop. Used to detect - * if the network connection has changed significantly, and if so, - * resets our circuit build timeout to the default. - * - * Also resets the entire timeout history in this case and causes us - * to restart the process of building test circuits and estimating a - * new timeout. - */ -int -circuit_build_times_network_check_changed(circuit_build_times_t *cbt) -{ - int total_build_times = cbt->total_build_times; - int timeout_count=0; - int i; - - if (cbt->liveness.timeouts_after_firsthop && - cbt->liveness.num_recent_circs > 0) { - /* how many of our recent circuits made it to the first hop but then - * timed out? */ - for (i = 0; i < cbt->liveness.num_recent_circs; i++) { - timeout_count += cbt->liveness.timeouts_after_firsthop[i]; - } - } - - /* If 80% of our recent circuits are timing out after the first hop, - * we need to re-estimate a new initial alpha and timeout. */ - if (timeout_count < circuit_build_times_max_timeouts()) { - return 0; - } - - circuit_build_times_reset(cbt); - if (cbt->liveness.timeouts_after_firsthop && - cbt->liveness.num_recent_circs > 0) { - memset(cbt->liveness.timeouts_after_firsthop, 0, - sizeof(*cbt->liveness.timeouts_after_firsthop)* - cbt->liveness.num_recent_circs); - } - cbt->liveness.after_firsthop_idx = 0; - - /* Check to see if this has happened before. If so, double the timeout - * to give people on abysmally bad network connections a shot at access */ - if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) { - if (cbt->timeout_ms > INT32_MAX/2 || cbt->close_ms > INT32_MAX/2) { - log_warn(LD_CIRC, "Insanely large circuit build timeout value. " - "(timeout = %fmsec, close = %fmsec)", - cbt->timeout_ms, cbt->close_ms); - } else { - cbt->timeout_ms *= 2; - cbt->close_ms *= 2; - } - } else { - cbt->close_ms = cbt->timeout_ms - = circuit_build_times_get_initial_timeout(); - } - - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); - - log_notice(LD_CIRC, - "Your network connection speed appears to have changed. Resetting " - "timeout to %lds after %d timeouts and %d buildtimes.", - tor_lround(cbt->timeout_ms/1000), timeout_count, - total_build_times); - - return 1; -} - -/** - * Count the number of timeouts in a set of cbt data. - */ -double -circuit_build_times_timeout_rate(const circuit_build_times_t *cbt) -{ - int i=0,timeouts=0; - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] >= cbt->timeout_ms) { - timeouts++; - } - } - - if (!cbt->total_build_times) - return 0; - - return ((double)timeouts)/cbt->total_build_times; -} - -/** - * Count the number of closed circuits in a set of cbt data. - */ -double -circuit_build_times_close_rate(const circuit_build_times_t *cbt) -{ - int i=0,closed=0; - for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { - if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) { - closed++; - } - } - - if (!cbt->total_build_times) - return 0; - - return ((double)closed)/cbt->total_build_times; -} - -/** - * Store a timeout as a synthetic value. - * - * Returns true if the store was successful and we should possibly - * update our timeout estimate. - */ -int -circuit_build_times_count_close(circuit_build_times_t *cbt, - int did_onehop, - time_t start_time) -{ - if (circuit_build_times_disabled()) { - cbt->close_ms = cbt->timeout_ms - = circuit_build_times_get_initial_timeout(); - return 0; - } - - /* Record this force-close to help determine if the network is dead */ - circuit_build_times_network_close(cbt, did_onehop, start_time); - - /* Only count timeouts if network is live.. */ - if (!circuit_build_times_network_check_live(cbt)) { - return 0; - } - - circuit_build_times_add_time(cbt, CBT_BUILD_ABANDONED); - return 1; -} - -/** - * Update timeout counts to determine if we need to expire - * our build time history due to excessive timeouts. - * - * We do not record any actual time values at this stage; - * we are only interested in recording the fact that a timeout - * happened. We record the time values via - * circuit_build_times_count_close() and circuit_build_times_add_time(). - */ -void -circuit_build_times_count_timeout(circuit_build_times_t *cbt, - int did_onehop) -{ - if (circuit_build_times_disabled()) { - cbt->close_ms = cbt->timeout_ms - = circuit_build_times_get_initial_timeout(); - return; - } - - /* Register the fact that a timeout just occurred. */ - circuit_build_times_network_timeout(cbt, did_onehop); - - /* If there are a ton of timeouts, we should reset - * the circuit build timeout. */ - circuit_build_times_network_check_changed(cbt); -} - -/** - * Estimate a new timeout based on history and set our timeout - * variable accordingly. - */ -static int -circuit_build_times_set_timeout_worker(circuit_build_times_t *cbt) -{ - build_time_t max_time; - if (!circuit_build_times_enough_to_compute(cbt)) - return 0; - - if (!circuit_build_times_update_alpha(cbt)) - return 0; - - cbt->timeout_ms = circuit_build_times_calculate_timeout(cbt, - circuit_build_times_quantile_cutoff()); - - cbt->close_ms = circuit_build_times_calculate_timeout(cbt, - circuit_build_times_close_quantile()); - - max_time = circuit_build_times_max(cbt); - - /* Sometimes really fast guard nodes give us such a steep curve - * that this ends up being not that much greater than timeout_ms. - * Make it be at least 1 min to handle this case. */ - cbt->close_ms = MAX(cbt->close_ms, circuit_build_times_initial_timeout()); - - if (cbt->timeout_ms > max_time) { - log_info(LD_CIRC, - "Circuit build timeout of %dms is beyond the maximum build " - "time we have ever observed. Capping it to %dms.", - (int)cbt->timeout_ms, max_time); - cbt->timeout_ms = max_time; - } - - if (max_time < INT32_MAX/2 && cbt->close_ms > 2*max_time) { - log_info(LD_CIRC, - "Circuit build measurement period of %dms is more than twice " - "the maximum build time we have ever observed. Capping it to " - "%dms.", (int)cbt->close_ms, 2*max_time); - cbt->close_ms = 2*max_time; - } - - cbt->have_computed_timeout = 1; - return 1; -} - -/** - * Exposed function to compute a new timeout. Dispatches events and - * also filters out extremely high timeout values. - */ -void -circuit_build_times_set_timeout(circuit_build_times_t *cbt) -{ - long prev_timeout = tor_lround(cbt->timeout_ms/1000); - double timeout_rate; - - /* - * Just return if we aren't using adaptive timeouts - */ - if (circuit_build_times_disabled()) - return; - - if (!circuit_build_times_set_timeout_worker(cbt)) - return; - - if (cbt->timeout_ms < circuit_build_times_min_timeout()) { - log_info(LD_CIRC, "Set buildtimeout to low value %fms. Setting to %dms", - cbt->timeout_ms, circuit_build_times_min_timeout()); - cbt->timeout_ms = circuit_build_times_min_timeout(); - if (cbt->close_ms < cbt->timeout_ms) { - /* This shouldn't happen because of MAX() in timeout_worker above, - * but doing it just in case */ - cbt->close_ms = circuit_build_times_initial_timeout(); - } - } - - control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); - - timeout_rate = circuit_build_times_timeout_rate(cbt); - - if (prev_timeout > tor_lround(cbt->timeout_ms/1000)) { - log_info(LD_CIRC, - "Based on %d circuit times, it looks like we don't need to " - "wait so long for circuits to finish. We will now assume a " - "circuit is too slow to use after waiting %ld seconds.", - cbt->total_build_times, - tor_lround(cbt->timeout_ms/1000)); - log_info(LD_CIRC, - "Circuit timeout data: %fms, %fms, Xm: %d, a: %f, r: %f", - cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, - timeout_rate); - } else if (prev_timeout < tor_lround(cbt->timeout_ms/1000)) { - log_info(LD_CIRC, - "Based on %d circuit times, it looks like we need to wait " - "longer for circuits to finish. We will now assume a " - "circuit is too slow to use after waiting %ld seconds.", - cbt->total_build_times, - tor_lround(cbt->timeout_ms/1000)); - log_info(LD_CIRC, - "Circuit timeout data: %fms, %fms, Xm: %d, a: %f, r: %f", - cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, - timeout_rate); - } else { - log_info(LD_CIRC, - "Set circuit build timeout to %lds (%fms, %fms, Xm: %d, a: %f," - " r: %f) based on %d circuit times", - tor_lround(cbt->timeout_ms/1000), - cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, timeout_rate, - cbt->total_build_times); - } + return chan; } /** Iterate over values of circ_id, starting from conn-\>next_circ_id, @@ -1680,26 +92,29 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt) * Return it, or 0 if can't get a unique circ_id. */ static circid_t -get_unique_circ_id_by_conn(or_connection_t *conn) +get_unique_circ_id_by_chan(channel_t *chan) { circid_t test_circ_id; circid_t attempts=0; circid_t high_bit; - tor_assert(conn); - if (conn->circ_id_type == CIRC_ID_TYPE_NEITHER) { - log_warn(LD_BUG, "Trying to pick a circuit ID for a connection from " + tor_assert(chan); + + if (chan->circ_id_type == CIRC_ID_TYPE_NEITHER) { + log_warn(LD_BUG, + "Trying to pick a circuit ID for a connection from " "a client with no identity."); return 0; } - high_bit = (conn->circ_id_type == CIRC_ID_TYPE_HIGHER) ? 1<<15 : 0; + high_bit = + (chan->circ_id_type == CIRC_ID_TYPE_HIGHER) ? 1<<15 : 0; do { /* Sequentially iterate over test_circ_id=1...1<<15-1 until we find a * circID such that (high_bit|test_circ_id) is not already used. */ - test_circ_id = conn->next_circ_id++; + test_circ_id = chan->next_circ_id++; if (test_circ_id == 0 || test_circ_id >= 1<<15) { test_circ_id = 1; - conn->next_circ_id = 2; + chan->next_circ_id = 2; } if (++attempts > 1<<15) { /* Make sure we don't loop forever if all circ_id's are used. This @@ -1709,7 +124,7 @@ get_unique_circ_id_by_conn(or_connection_t *conn) return 0; } test_circ_id |= high_bit; - } while (circuit_id_in_use_on_orconn(test_circ_id, conn)); + } while (circuit_id_in_use_on_channel(test_circ_id, chan)); return test_circ_id; } @@ -1736,8 +151,8 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) circ->build_state->is_internal ? "internal" : "exit", circ->build_state->need_uptime ? " (high-uptime)" : "", circ->build_state->desired_path_len, - circ->_base.state == CIRCUIT_STATE_OPEN ? "" : ", last hop ", - circ->_base.state == CIRCUIT_STATE_OPEN ? "" : + circ->base_.state == CIRCUIT_STATE_OPEN ? "" : ", last hop ", + circ->base_.state == CIRCUIT_STATE_OPEN ? "" : (nickname?nickname:"*unnamed*")); } @@ -1888,9 +303,9 @@ onion_populate_cpath(origin_circuit_t *circ) origin_circuit_t * origin_circuit_init(uint8_t purpose, int flags) { - /* sets circ->p_circ_id and circ->p_conn */ + /* sets circ->p_circ_id and circ->p_chan */ origin_circuit_t *circ = origin_circuit_new(); - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OR_WAIT); + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_CHAN_WAIT); circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t)); circ->build_state->onehop_tunnel = ((flags & CIRCLAUNCH_ONEHOP_TUNNEL) ? 1 : 0); @@ -1900,7 +315,7 @@ origin_circuit_init(uint8_t purpose, int flags) ((flags & CIRCLAUNCH_NEED_CAPACITY) ? 1 : 0); circ->build_state->is_internal = ((flags & CIRCLAUNCH_IS_INTERNAL) ? 1 : 0); - circ->_base.purpose = purpose; + circ->base_.purpose = purpose; return circ; } @@ -1942,7 +357,7 @@ int circuit_handle_first_hop(origin_circuit_t *circ) { crypt_path_t *firsthop; - or_connection_t *n_conn; + channel_t *n_chan; int err_reason = 0; const char *msg = NULL; int should_launch = 0; @@ -1952,29 +367,30 @@ circuit_handle_first_hop(origin_circuit_t *circ) tor_assert(firsthop->extend_info); /* now see if we're already connected to the first OR in 'route' */ - log_debug(LD_CIRC,"Looking for firsthop '%s:%u'", - fmt_addr(&firsthop->extend_info->addr), - firsthop->extend_info->port); + log_debug(LD_CIRC,"Looking for firsthop '%s'", + fmt_addrport(&firsthop->extend_info->addr, + firsthop->extend_info->port)); - n_conn = connection_or_get_for_extend(firsthop->extend_info->identity_digest, - &firsthop->extend_info->addr, - &msg, - &should_launch); + n_chan = channel_get_for_extend(firsthop->extend_info->identity_digest, + &firsthop->extend_info->addr, + &msg, + &should_launch); - if (!n_conn) { + if (!n_chan) { /* not currently connected in a useful way. */ log_info(LD_CIRC, "Next router is %s: %s", safe_str_client(extend_info_describe(firsthop->extend_info)), msg?msg:"???"); - circ->_base.n_hop = extend_info_dup(firsthop->extend_info); + circ->base_.n_hop = extend_info_dup(firsthop->extend_info); if (should_launch) { if (circ->build_state->onehop_tunnel) control_event_bootstrap(BOOTSTRAP_STATUS_CONN_DIR, 0); - n_conn = connection_or_connect(&firsthop->extend_info->addr, - firsthop->extend_info->port, - firsthop->extend_info->identity_digest); - if (!n_conn) { /* connect failed, forget the whole thing */ + n_chan = channel_connect_for_circuit( + &firsthop->extend_info->addr, + firsthop->extend_info->port, + firsthop->extend_info->identity_digest); + if (!n_chan) { /* connect failed, forget the whole thing */ log_info(LD_CIRC,"connect to firsthop failed. Closing."); return -END_CIRC_REASON_CONNECTFAILED; } @@ -1982,13 +398,13 @@ circuit_handle_first_hop(origin_circuit_t *circ) log_debug(LD_CIRC,"connecting in progress (or finished). Good."); /* return success. The onion/circuit/etc will be taken care of - * automatically (may already have been) whenever n_conn reaches + * automatically (may already have been) whenever n_chan reaches * OR_CONN_STATE_OPEN. */ return 0; } else { /* it's already open. use it. */ - tor_assert(!circ->_base.n_hop); - circ->_base.n_conn = n_conn; + tor_assert(!circ->base_.n_hop); + circ->base_.n_chan = n_chan; log_debug(LD_CIRC,"Conn open. Delivering first onion skin."); if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { log_info(LD_CIRC,"circuit_send_next_onion_skin failed."); @@ -2004,48 +420,49 @@ circuit_handle_first_hop(origin_circuit_t *circ) * Status is 1 if connect succeeded, or 0 if connect failed. */ void -circuit_n_conn_done(or_connection_t *or_conn, int status) +circuit_n_chan_done(channel_t *chan, int status) { smartlist_t *pending_circs; int err_reason = 0; - log_debug(LD_CIRC,"or_conn to %s/%s, status=%d", - or_conn->nickname ? or_conn->nickname : "NULL", - or_conn->_base.address, status); + tor_assert(chan); + + log_debug(LD_CIRC,"chan to %s/%s, status=%d", + chan->nickname ? chan->nickname : "NULL", + channel_get_canonical_remote_descr(chan), status); pending_circs = smartlist_new(); - circuit_get_all_pending_on_or_conn(pending_circs, or_conn); + circuit_get_all_pending_on_channel(pending_circs, chan); SMARTLIST_FOREACH_BEGIN(pending_circs, circuit_t *, circ) { /* These checks are redundant wrt get_all_pending_on_or_conn, but I'm * leaving them in in case it's possible for the status of a circuit to * change as we're going down the list. */ - if (circ->marked_for_close || circ->n_conn || !circ->n_hop || - circ->state != CIRCUIT_STATE_OR_WAIT) + if (circ->marked_for_close || circ->n_chan || !circ->n_hop || + circ->state != CIRCUIT_STATE_CHAN_WAIT) continue; if (tor_digest_is_zero(circ->n_hop->identity_digest)) { /* Look at addr/port. This is an unkeyed connection. */ - if (!tor_addr_eq(&circ->n_hop->addr, &or_conn->_base.addr) || - circ->n_hop->port != or_conn->_base.port) + if (!channel_matches_extend_info(chan, circ->n_hop)) continue; } else { /* We expected a key. See if it's the right one. */ - if (tor_memneq(or_conn->identity_digest, + if (tor_memneq(chan->identity_digest, circ->n_hop->identity_digest, DIGEST_LEN)) continue; } - if (!status) { /* or_conn failed; close circ */ - log_info(LD_CIRC,"or_conn failed. Closing circ."); - circuit_mark_for_close(circ, END_CIRC_REASON_OR_CONN_CLOSED); + if (!status) { /* chan failed; close circ */ + log_info(LD_CIRC,"Channel failed; closing circ."); + circuit_mark_for_close(circ, END_CIRC_REASON_CHANNEL_CLOSED); continue; } log_debug(LD_CIRC, "Found circ, sending create cell."); /* circuit_deliver_create_cell will set n_circ_id and add us to - * orconn_circuid_circuit_map, so we don't need to call - * set_circid_orconn here. */ - circ->n_conn = or_conn; + * chan_circuid_circuit_map, so we don't need to call + * set_circid_chan here. */ + circ->n_chan = chan; extend_info_free(circ->n_hop); circ->n_hop = NULL; @@ -2060,14 +477,13 @@ circuit_n_conn_done(or_connection_t *or_conn, int status) * died? */ } } else { - /* pull the create cell out of circ->onionskin, and send it */ - tor_assert(circ->n_conn_onionskin); - if (circuit_deliver_create_cell(circ,CELL_CREATE, - circ->n_conn_onionskin)<0) { + /* pull the create cell out of circ->n_chan_create_cell, and send it */ + tor_assert(circ->n_chan_create_cell); + if (circuit_deliver_create_cell(circ, circ->n_chan_create_cell, 1)<0) { circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); continue; } - tor_free(circ->n_conn_onionskin); + tor_free(circ->n_chan_create_cell); circuit_set_state(circ, CIRCUIT_STATE_OPEN); } } @@ -2076,44 +492,63 @@ circuit_n_conn_done(or_connection_t *or_conn, int status) smartlist_free(pending_circs); } -/** Find a new circid that isn't currently in use on the circ->n_conn +/** Find a new circid that isn't currently in use on the circ->n_chan * for the outgoing - * circuit <b>circ</b>, and deliver a cell of type <b>cell_type</b> - * (either CELL_CREATE or CELL_CREATE_FAST) with payload <b>payload</b> - * to this circuit. - * Return -1 if we failed to find a suitable circid, else return 0. + * circuit <b>circ</b>, and deliver the cell <b>create_cell</b> to this + * circuit. If <b>relayed</b> is true, this is a create cell somebody + * gave us via an EXTEND cell, so we shouldn't worry if we don't understand + * it. Return -1 if we failed to find a suitable circid, else return 0. */ static int -circuit_deliver_create_cell(circuit_t *circ, uint8_t cell_type, - const char *payload) +circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, + int relayed) { cell_t cell; circid_t id; + int r; tor_assert(circ); - tor_assert(circ->n_conn); - tor_assert(payload); - tor_assert(cell_type == CELL_CREATE || cell_type == CELL_CREATE_FAST); + tor_assert(circ->n_chan); + tor_assert(create_cell); + tor_assert(create_cell->cell_type == CELL_CREATE || + create_cell->cell_type == CELL_CREATE_FAST || + create_cell->cell_type == CELL_CREATE2); - id = get_unique_circ_id_by_conn(circ->n_conn); + id = get_unique_circ_id_by_chan(circ->n_chan); if (!id) { log_warn(LD_CIRC,"failed to get unique circID."); return -1; } log_debug(LD_CIRC,"Chosen circID %u.", id); - circuit_set_n_circid_orconn(circ, id, circ->n_conn); + circuit_set_n_circid_chan(circ, id, circ->n_chan); memset(&cell, 0, sizeof(cell_t)); - cell.command = cell_type; + r = relayed ? create_cell_format_relayed(&cell, create_cell) + : create_cell_format(&cell, create_cell); + if (r < 0) { + log_warn(LD_CIRC,"Couldn't format create cell"); + return -1; + } cell.circ_id = circ->n_circ_id; - memcpy(cell.payload, payload, ONIONSKIN_CHALLENGE_LEN); - append_cell_to_circuit_queue(circ, circ->n_conn, &cell, + append_cell_to_circuit_queue(circ, circ->n_chan, &cell, CELL_DIRECTION_OUT, 0); if (CIRCUIT_IS_ORIGIN(circ)) { + /* Update began timestamp for circuits starting their first hop */ + if (TO_ORIGIN_CIRCUIT(circ)->cpath->state == CPATH_STATE_CLOSED) { + if (circ->n_chan->state != CHANNEL_STATE_OPEN) { + log_warn(LD_CIRC, + "Got first hop for a circuit without an opened channel. " + "State: %s.", channel_state_to_string(circ->n_chan->state)); + tor_fragile_assert(); + } + + tor_gettimeofday(&circ->timestamp_began); + } + /* mark it so it gets better rate limiting treatment. */ - circ->n_conn->client_used = time(NULL); + channel_timestamp_client(circ->n_chan); } return 0; @@ -2185,6 +620,73 @@ circuit_timeout_want_to_count_circ(origin_circuit_t *circ) && circ->build_state->desired_path_len == DEFAULT_ROUTE_LEN; } +#ifdef CURVE25519_ENABLED +/** Return true if the ntor handshake is enabled in the configuration, or if + * it's been set to "auto" in the configuration and it's enabled in the + * consensus. */ +static int +circuits_can_use_ntor(void) +{ + const or_options_t *options = get_options(); + if (options->UseNTorHandshake != -1) + return options->UseNTorHandshake; + return networkstatus_get_param(NULL, "UseNTorHandshake", 0, 0, 1); +} +#endif + +/** Decide whether to use a TAP or ntor handshake for connecting to <b>ei</b> + * directly, and set *<b>cell_type_out</b> and *<b>handshake_type_out</b> + * accordingly. */ +static void +circuit_pick_create_handshake(uint8_t *cell_type_out, + uint16_t *handshake_type_out, + const extend_info_t *ei) +{ +#ifdef CURVE25519_ENABLED + if (!tor_mem_is_zero((const char*)ei->curve25519_onion_key.public_key, + CURVE25519_PUBKEY_LEN) && + circuits_can_use_ntor()) { + *cell_type_out = CELL_CREATE2; + *handshake_type_out = ONION_HANDSHAKE_TYPE_NTOR; + return; + } +#else + (void) ei; +#endif + + *cell_type_out = CELL_CREATE; + *handshake_type_out = ONION_HANDSHAKE_TYPE_TAP; +} + +/** Decide whether to use a TAP or ntor handshake for connecting to <b>ei</b> + * directly, and set *<b>handshake_type_out</b> accordingly. Decide whether, + * in extending through <b>node</b> to do so, we should use an EXTEND2 or an + * EXTEND cell to do so, and set *<b>cell_type_out</b> and + * *<b>create_cell_type_out</b> accordingly. */ +static void +circuit_pick_extend_handshake(uint8_t *cell_type_out, + uint8_t *create_cell_type_out, + uint16_t *handshake_type_out, + const node_t *node_prev, + const extend_info_t *ei) +{ + uint8_t t; + circuit_pick_create_handshake(&t, handshake_type_out, ei); + /* XXXX024 The check for whether the node has a curve25519 key is a bad + * proxy for whether it can do extend2 cells; once a version that + * handles extend2 cells is out, remove it. */ + if (node_prev && + *handshake_type_out != ONION_HANDSHAKE_TYPE_TAP && + (node_has_curve25519_onion_key(node_prev) || + (node_prev->rs && node_prev->rs->version_supports_extend2_cells))) { + *cell_type_out = RELAY_COMMAND_EXTEND2; + *create_cell_type_out = CELL_CREATE2; + } else { + *cell_type_out = RELAY_COMMAND_EXTEND; + *create_cell_type_out = CELL_CREATE; + } +} + /** This is the backbone function for building circuits. * * If circ's first hop is closed, then we need to build a create @@ -2200,49 +702,50 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) { crypt_path_t *hop; const node_t *node; - char payload[2+4+DIGEST_LEN+ONIONSKIN_CHALLENGE_LEN]; - char *onionskin; - size_t payload_len; tor_assert(circ); if (circ->cpath->state == CPATH_STATE_CLOSED) { + /* This is the first hop. */ + create_cell_t cc; int fast; - uint8_t cell_type; + int len; log_debug(LD_CIRC,"First skin; sending create cell."); + memset(&cc, 0, sizeof(cc)); if (circ->build_state->onehop_tunnel) control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); else control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - node = node_get_by_id(circ->_base.n_conn->identity_digest); + node = node_get_by_id(circ->base_.n_chan->identity_digest); fast = should_use_create_fast_for_circuit(circ); if (!fast) { /* We are an OR and we know the right onion key: we should - * send an old slow create cell. + * send a create cell. */ - cell_type = CELL_CREATE; - if (onion_skin_create(circ->cpath->extend_info->onion_key, - &(circ->cpath->dh_handshake_state), - payload) < 0) { - log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); - return - END_CIRC_REASON_INTERNAL; - } + circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, + circ->cpath->extend_info); note_request("cell: create", 1); } else { /* We are not an OR, and we're building the first hop of a circuit to a * new OR: we can be speedy and use CREATE_FAST to save an RSA operation * and a DH operation. */ - cell_type = CELL_CREATE_FAST; - memset(payload, 0, sizeof(payload)); - crypto_rand((char*) circ->cpath->fast_handshake_state, - sizeof(circ->cpath->fast_handshake_state)); - memcpy(payload, circ->cpath->fast_handshake_state, - sizeof(circ->cpath->fast_handshake_state)); + cc.cell_type = CELL_CREATE_FAST; + cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; note_request("cell: create fast", 1); } - if (circuit_deliver_create_cell(TO_CIRCUIT(circ), cell_type, payload) < 0) + len = onion_skin_create(cc.handshake_type, + circ->cpath->extend_info, + &circ->cpath->handshake_state, + cc.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); + return - END_CIRC_REASON_INTERNAL; + } + cc.handshake_len = len; + + if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) return - END_CIRC_REASON_RESOURCELIMIT; circ->cpath->state = CPATH_STATE_AWAITING_KEYS; @@ -2251,10 +754,13 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) fast ? "CREATE_FAST" : "CREATE", node ? node_describe(node) : "<unnamed>"); } else { + extend_cell_t ec; + int len; tor_assert(circ->cpath->state == CPATH_STATE_OPEN); - tor_assert(circ->_base.state == CIRCUIT_STATE_BUILDING); + tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); log_debug(LD_CIRC,"starting to send subsequent skin."); hop = onion_next_hop_in_cpath(circ->cpath); + memset(&ec, 0, sizeof(ec)); if (!hop) { /* done building the circuit. whew. */ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); @@ -2262,7 +768,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) struct timeval end; long timediff; tor_gettimeofday(&end); - timediff = tv_mdiff(&circ->_base.timestamp_created, &end); + timediff = tv_mdiff(&circ->base_.timestamp_began, &end); /* * If the circuit build time is much greater than we would have cut @@ -2272,8 +778,8 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) { log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " "Assuming clock jump. Purpose %d (%s)", timediff, - circ->_base.purpose, - circuit_purpose_to_string(circ->_base.purpose)); + circ->base_.purpose, + circuit_purpose_to_string(circ->base_.purpose)); } else if (!circuit_build_times_disabled()) { /* Only count circuit times if the network is live */ if (circuit_build_times_network_check_live(&circ_times)) { @@ -2281,7 +787,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) circuit_build_times_set_timeout(&circ_times); } - if (circ->_base.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { circuit_build_times_network_circ_success(&circ_times); } } @@ -2309,13 +815,17 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) } } - pathbias_count_success(circ); + pathbias_count_build_success(circ); circuit_rep_hist_note_result(circ); circuit_has_opened(circ); /* do other actions as necessary */ /* We're done with measurement circuits here. Just close them */ - if (circ->_base.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* If a measurement circ ever gets back to us, consider it + * succeeded for path bias */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + } return 0; } @@ -2324,29 +834,50 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) return - END_CIRC_REASON_INTERNAL; } - set_uint32(payload, tor_addr_to_ipv4n(&hop->extend_info->addr)); - set_uint16(payload+4, htons(hop->extend_info->port)); - - onionskin = payload+2+4; - memcpy(payload+2+4+ONIONSKIN_CHALLENGE_LEN, - hop->extend_info->identity_digest, DIGEST_LEN); - payload_len = 2+4+ONIONSKIN_CHALLENGE_LEN+DIGEST_LEN; - - if (onion_skin_create(hop->extend_info->onion_key, - &(hop->dh_handshake_state), onionskin) < 0) { + { + const node_t *prev_node; + prev_node = node_get_by_id(hop->prev->extend_info->identity_digest); + circuit_pick_extend_handshake(&ec.cell_type, + &ec.create_cell.cell_type, + &ec.create_cell.handshake_type, + prev_node, + hop->extend_info); + } + + tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); + ec.orport_ipv4.port = hop->extend_info->port; + tor_addr_make_unspec(&ec.orport_ipv6.addr); + memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); + + len = onion_skin_create(ec.create_cell.handshake_type, + hop->extend_info, + &hop->handshake_state, + ec.create_cell.onionskin); + if (len < 0) { log_warn(LD_CIRC,"onion_skin_create failed."); return - END_CIRC_REASON_INTERNAL; } + ec.create_cell.handshake_len = len; log_info(LD_CIRC,"Sending extend relay cell."); note_request("cell: extend", 1); - /* send it to hop->prev, because it will transfer - * it to a create cell and then send to hop */ - if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), - RELAY_COMMAND_EXTEND, - payload, payload_len, hop->prev) < 0) - return 0; /* circuit is closed */ + { + uint8_t command = 0; + uint16_t payload_len=0; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { + log_warn(LD_CIRC,"Couldn't format extend cell"); + return -END_CIRC_REASON_INTERNAL; + } + /* send it to hop->prev, because it will transfer + * it to a create cell and then send to hop */ + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), + command, + (char*)payload, payload_len, + hop->prev) < 0) + return 0; /* circuit is closed */ + } hop->state = CPATH_STATE_AWAITING_KEYS; } return 0; @@ -2383,19 +914,15 @@ circuit_note_clock_jumped(int seconds_elapsed) int circuit_extend(cell_t *cell, circuit_t *circ) { - or_connection_t *n_conn; + channel_t *n_chan; relay_header_t rh; - char *onionskin; - char *id_digest=NULL; - uint32_t n_addr32; - uint16_t n_port; - tor_addr_t n_addr; + extend_cell_t ec; const char *msg = NULL; int should_launch = 0; - if (circ->n_conn) { + if (circ->n_chan) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "n_conn already set. Bug/attack. Closing."); + "n_chan already set. Bug/attack. Closing."); return -1; } if (circ->n_hop) { @@ -2412,27 +939,21 @@ circuit_extend(cell_t *cell, circuit_t *circ) relay_header_unpack(&rh, cell->payload); - if (rh.length < 4+2+ONIONSKIN_CHALLENGE_LEN+DIGEST_LEN) { + if (extend_cell_parse(&ec, rh.command, + cell->payload+RELAY_HEADER_SIZE, + rh.length) < 0) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Wrong length %d on extend cell. Closing circuit.", - rh.length); + "Can't parse extend cell. Closing circuit."); return -1; } - n_addr32 = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE)); - n_port = ntohs(get_uint16(cell->payload+RELAY_HEADER_SIZE+4)); - onionskin = (char*) cell->payload+RELAY_HEADER_SIZE+4+2; - id_digest = (char*) cell->payload+RELAY_HEADER_SIZE+4+2+ - ONIONSKIN_CHALLENGE_LEN; - tor_addr_from_ipv4h(&n_addr, n_addr32); - - if (!n_port || !n_addr32) { + if (!ec.orport_ipv4.port || tor_addr_is_null(&ec.orport_ipv4.addr)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Client asked me to extend to zero destination port or addr."); return -1; } - if (tor_addr_is_internal(&n_addr, 0) && + if (tor_addr_is_internal(&ec.orport_ipv4.addr, 0) && !get_options()->ExtendAllowPrivateAddresses) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Client asked me to extend to a private address"); @@ -2445,7 +966,7 @@ circuit_extend(cell_t *cell, circuit_t *circ) * fingerprints -- a) because it opens the user up to a mitm attack, * and b) because it lets an attacker force the relay to hold open a * new TLS connection for each extend request. */ - if (tor_digest_is_zero(id_digest)) { + if (tor_digest_is_zero((const char*)ec.node_id)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Client asked me to extend without specifying an id_digest."); return -1; @@ -2454,55 +975,64 @@ circuit_extend(cell_t *cell, circuit_t *circ) /* Next, check if we're being asked to connect to the hop that the * extend cell came from. There isn't any reason for that, and it can * assist circular-path attacks. */ - if (tor_memeq(id_digest, TO_OR_CIRCUIT(circ)->p_conn->identity_digest, - DIGEST_LEN)) { + if (tor_memeq(ec.node_id, + TO_OR_CIRCUIT(circ)->p_chan->identity_digest, + DIGEST_LEN)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Client asked me to extend back to the previous hop."); return -1; } - n_conn = connection_or_get_for_extend(id_digest, - &n_addr, - &msg, - &should_launch); + n_chan = channel_get_for_extend((const char*)ec.node_id, + &ec.orport_ipv4.addr, + &msg, + &should_launch); + + if (!n_chan) { + log_debug(LD_CIRC|LD_OR,"Next router (%s): %s", + fmt_addrport(&ec.orport_ipv4.addr,ec.orport_ipv4.port), + msg?msg:"????"); - if (!n_conn) { - log_debug(LD_CIRC|LD_OR,"Next router (%s:%d): %s", - fmt_addr(&n_addr), (int)n_port, msg?msg:"????"); + circ->n_hop = extend_info_new(NULL /*nickname*/, + (const char*)ec.node_id, + NULL /*onion_key*/, + NULL /*curve25519_key*/, + &ec.orport_ipv4.addr, + ec.orport_ipv4.port); - circ->n_hop = extend_info_alloc(NULL /*nickname*/, - id_digest, - NULL /*onion_key*/, - &n_addr, n_port); + circ->n_chan_create_cell = tor_memdup(&ec.create_cell, + sizeof(ec.create_cell)); - circ->n_conn_onionskin = tor_malloc(ONIONSKIN_CHALLENGE_LEN); - memcpy(circ->n_conn_onionskin, onionskin, ONIONSKIN_CHALLENGE_LEN); - circuit_set_state(circ, CIRCUIT_STATE_OR_WAIT); + circuit_set_state(circ, CIRCUIT_STATE_CHAN_WAIT); if (should_launch) { /* we should try to open a connection */ - n_conn = connection_or_connect(&n_addr, n_port, id_digest); - if (!n_conn) { - log_info(LD_CIRC,"Launching n_conn failed. Closing circuit."); + n_chan = channel_connect_for_circuit(&ec.orport_ipv4.addr, + ec.orport_ipv4.port, + (const char*)ec.node_id); + if (!n_chan) { + log_info(LD_CIRC,"Launching n_chan failed. Closing circuit."); circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED); return 0; } log_debug(LD_CIRC,"connecting in progress (or finished). Good."); } /* return success. The onion/circuit/etc will be taken care of - * automatically (may already have been) whenever n_conn reaches + * automatically (may already have been) whenever n_chan reaches * OR_CONN_STATE_OPEN. */ return 0; } tor_assert(!circ->n_hop); /* Connection is already established. */ - circ->n_conn = n_conn; - log_debug(LD_CIRC,"n_conn is %s:%u", - n_conn->_base.address,n_conn->_base.port); + circ->n_chan = n_chan; + log_debug(LD_CIRC, + "n_chan is %s", + channel_get_canonical_remote_descr(n_chan)); - if (circuit_deliver_create_cell(circ, CELL_CREATE, onionskin) < 0) + if (circuit_deliver_create_cell(circ, &ec.create_cell, 1) < 0) return -1; + return 0; } @@ -2556,12 +1086,12 @@ circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, return 0; } -/** The minimum number of first hop completions before we start +/** The minimum number of circuit attempts before we start * thinking about warning about path bias and dropping guards */ static int pathbias_get_min_circs(const or_options_t *options) { -#define DFLT_PATH_BIAS_MIN_CIRC 20 +#define DFLT_PATH_BIAS_MIN_CIRC 150 if (options->PathBiasCircThreshold >= 5) return options->PathBiasCircThreshold; else @@ -2570,10 +1100,11 @@ pathbias_get_min_circs(const or_options_t *options) 5, INT32_MAX); } +/** The circuit success rate below which we issue a notice */ static double pathbias_get_notice_rate(const or_options_t *options) { -#define DFLT_PATH_BIAS_NOTICE_PCT 40 +#define DFLT_PATH_BIAS_NOTICE_PCT 70 if (options->PathBiasNoticeRate >= 0.0) return options->PathBiasNoticeRate; else @@ -2581,23 +1112,62 @@ pathbias_get_notice_rate(const or_options_t *options) DFLT_PATH_BIAS_NOTICE_PCT, 0, 100)/100.0; } +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** The circuit success rate below which we issue a warn */ static double -pathbias_get_disable_rate(const or_options_t *options) +pathbias_get_warn_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_WARN_PCT 50 + if (options->PathBiasWarnRate >= 0.0) + return options->PathBiasWarnRate; + else + return networkstatus_get_param(NULL, "pb_warnpct", + DFLT_PATH_BIAS_WARN_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * The extreme rate is the rate at which we would drop the guard, + * if pb_dropguard is also set. Otherwise we just warn. + */ +double +pathbias_get_extreme_rate(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_EXTREME_PCT 30 + if (options->PathBiasExtremeRate >= 0.0) + return options->PathBiasExtremeRate; + else + return networkstatus_get_param(NULL, "pb_extremepct", + DFLT_PATH_BIAS_EXTREME_PCT, 0, 100)/100.0; +} + +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +/** + * If 1, we actually disable use of guards that fall below + * the extreme_pct. + */ +int +pathbias_get_dropguards(const or_options_t *options) { -// XXX: This needs tuning based on use + experimentation before we set it -#define DFLT_PATH_BIAS_DISABLE_PCT 0 - if (options->PathBiasDisableRate >= 0.0) - return options->PathBiasDisableRate; +#define DFLT_PATH_BIAS_DROP_GUARDS 0 + if (options->PathBiasDropGuards >= 0) + return options->PathBiasDropGuards; else - return networkstatus_get_param(NULL, "pb_disablepct", - DFLT_PATH_BIAS_DISABLE_PCT, 0, 100)/100.0; + return networkstatus_get_param(NULL, "pb_dropguards", + DFLT_PATH_BIAS_DROP_GUARDS, 0, 1); } +/** + * This is the number of circuits at which we scale our + * counts by mult_factor/scale_factor. Note, this count is + * not exact, as we only perform the scaling in the event + * of no integer truncation. + */ static int pathbias_get_scale_threshold(const or_options_t *options) { -#define DFLT_PATH_BIAS_SCALE_THRESHOLD 200 - if (options->PathBiasScaleThreshold >= 2) +#define DFLT_PATH_BIAS_SCALE_THRESHOLD 300 + if (options->PathBiasScaleThreshold >= 10) return options->PathBiasScaleThreshold; else return networkstatus_get_param(NULL, "pb_scalecircs", @@ -2605,61 +1175,151 @@ pathbias_get_scale_threshold(const or_options_t *options) INT32_MAX); } +/** + * The scale factor is the denominator for our scaling + * of circuit counts for our path bias window. + * + * Note that our use of doubles for the path bias state + * file means that powers of 2 work best here. + */ 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); } +/** + * The mult factor is the numerator for our scaling + * of circuit counts for our path bias window. It + * allows us to scale by fractions. + */ +static int +pathbias_get_mult_factor(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_MULT_FACTOR 1 + if (options->PathBiasMultFactor >= 1) + return options->PathBiasMultFactor; + else + return networkstatus_get_param(NULL, "pb_multfactor", + DFLT_PATH_BIAS_MULT_FACTOR, 1, + pathbias_get_scale_factor(options)); +} + +/** + * If this parameter is set to a true value (default), we use the + * successful_circuits_closed. Otherwise, we use the success_count. + */ +static int +pathbias_use_close_counts(const or_options_t *options) +{ +#define DFLT_PATH_BIAS_USE_CLOSE_COUNTS 1 + if (options->PathBiasUseCloseCounts >= 0) + return options->PathBiasUseCloseCounts; + else + return networkstatus_get_param(NULL, "pb_useclosecounts", + DFLT_PATH_BIAS_USE_CLOSE_COUNTS, 0, 1); +} + +/** + * Convert a Guard's path state to string. + */ static const char * pathbias_state_to_string(path_state_t state) { switch (state) { case PATH_STATE_NEW_CIRC: return "new"; - case PATH_STATE_DID_FIRST_HOP: - return "first hop"; - case PATH_STATE_SUCCEEDED: - return "succeeded"; + case PATH_STATE_BUILD_ATTEMPTED: + return "build attempted"; + case PATH_STATE_BUILD_SUCCEEDED: + return "build succeeded"; + case PATH_STATE_USE_SUCCEEDED: + return "use succeeded"; + case PATH_STATE_USE_FAILED: + return "use failed"; } return "unknown"; } /** - * Check our circuit state to see if this is a successful first hop. - * If so, record it in the current guard's path bias first_hop count. + * This function decides if a circuit has progressed far enough to count + * as a circuit "attempt". As long as end-to-end tagging is possible, + * we assume the adversary will use it over hop-to-hop failure. Therefore, + * we only need to account bias for the last hop. This should make us + * much more resilient to ambient circuit failure, and also make that + * failure easier to measure (we only need to measure Exit failure rates). + */ +static int +pathbias_is_new_circ_attempt(origin_circuit_t *circ) +{ +#define N2N_TAGGING_IS_POSSIBLE +#ifdef N2N_TAGGING_IS_POSSIBLE + /* cpath is a circular list. We want circs with more than one hop, + * and the second hop must be waiting for keys still (it's just + * about to get them). */ + return circ->cpath->next != circ->cpath && + circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; +#else + /* If tagging attacks are no longer possible, we probably want to + * count bias from the first hop. However, one could argue that + * timing-based tagging is still more useful than per-hop failure. + * In which case, we'd never want to use this. + */ + return circ->cpath->state == CPATH_STATE_AWAITING_KEYS; +#endif +} + +/** + * Decide if the path bias code should count a circuit. * - * Also check for several potential error cases for bug #6475. + * @returns 1 if we should count it, 0 otherwise. */ static int -pathbias_count_first_hop(origin_circuit_t *circ) +pathbias_should_count(origin_circuit_t *circ) { -#define FIRST_HOP_NOTICE_INTERVAL (600) - static ratelim_t first_hop_notice_limit = - RATELIM_INIT(FIRST_HOP_NOTICE_INTERVAL); +#define PATHBIAS_COUNT_INTERVAL (600) + static ratelim_t count_limit = + RATELIM_INIT(PATHBIAS_COUNT_INTERVAL); char *rate_msg = NULL; + /* We can't do path bias accounting without entry guards. + * Testing and controller circuits also have no guards. + * + * We also don't count server-side rends, because their + * endpoint could be chosen maliciously. + * Similarly, we can't count client-side intro attempts, + * because clients can be manipulated into connecting to + * malicious intro points. */ + if (get_options()->UseEntryGuards == 0 || + circ->base_.purpose == CIRCUIT_PURPOSE_TESTING || + circ->base_.purpose == CIRCUIT_PURPOSE_CONTROLLER || + circ->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || + circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED || + (circ->base_.purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && + circ->base_.purpose <= CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + return 0; + } + /* Completely ignore one hop circuits */ if (circ->build_state->onehop_tunnel || circ->build_state->desired_path_len == 1) { /* Check for inconsistency */ if (circ->build_state->desired_path_len != 1 || !circ->build_state->onehop_tunnel) { - if ((rate_msg = rate_limit_log(&first_hop_notice_limit, - approx_time()))) { - log_info(LD_BUG, + if ((rate_msg = rate_limit_log(&count_limit, approx_time()))) { + log_notice(LD_BUG, "One-hop circuit has length %d. Path state is %s. " "Circuit is a %s currently %s.%s", circ->build_state->desired_path_len, pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } @@ -2668,78 +1328,89 @@ pathbias_count_first_hop(origin_circuit_t *circ) return 0; } - if (circ->cpath->state == CPATH_STATE_AWAITING_KEYS) { + return 1; +} + +/** + * Check our circuit state to see if this is a successful circuit attempt. + * If so, record it in the current guard's path bias circ_attempt count. + * + * Also check for several potential error cases for bug #6475. + */ +static int +pathbias_count_circ_attempt(origin_circuit_t *circ) +{ +#define CIRC_ATTEMPT_NOTICE_INTERVAL (600) + static ratelim_t circ_attempt_notice_limit = + RATELIM_INIT(CIRC_ATTEMPT_NOTICE_INTERVAL); + char *rate_msg = NULL; + + if (!pathbias_should_count(circ)) { + return 0; + } + + if (pathbias_is_new_circ_attempt(circ)) { /* Help track down the real cause of bug #6475: */ - if (circ->has_opened && circ->path_state != PATH_STATE_DID_FIRST_HOP) { - if ((rate_msg = rate_limit_log(&first_hop_notice_limit, + if (circ->has_opened && circ->path_state != PATH_STATE_BUILD_ATTEMPTED) { + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, approx_time()))) { log_info(LD_BUG, "Opened circuit is in strange path state %s. " "Circuit is a %s currently %s.%s", pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } } - /* Don't count cannibalized circs for path bias */ + /* Don't re-count cannibalized circs.. */ if (!circ->has_opened) { - entry_guard_t *guard; + entry_guard_t *guard = NULL; + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } else if (circ->base_.n_chan) { + guard = + entry_guard_get_by_id_digest(circ->base_.n_chan->identity_digest); + } - guard = entry_guard_get_by_id_digest( - circ->_base.n_conn->identity_digest); if (guard) { if (circ->path_state == PATH_STATE_NEW_CIRC) { - circ->path_state = PATH_STATE_DID_FIRST_HOP; + circ->path_state = PATH_STATE_BUILD_ATTEMPTED; - if (entry_guard_inc_first_hop_count(guard) < 0) { + if (entry_guard_inc_circ_attempt_count(guard) < 0) { /* Bogus guard; we already warned. */ return -END_CIRC_REASON_TORPROTOCOL; } } else { - if ((rate_msg = rate_limit_log(&first_hop_notice_limit, + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, approx_time()))) { log_info(LD_BUG, "Unopened circuit has strange path state %s. " "Circuit is a %s currently %s.%s", pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } } } else { - if ((rate_msg = rate_limit_log(&first_hop_notice_limit, + if ((rate_msg = rate_limit_log(&circ_attempt_notice_limit, approx_time()))) { - log_info(LD_BUG, + log_info(LD_CIRC, "Unopened circuit has no known guard. " "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } } } - } else { - /* Help track down the real cause of bug #6475: */ - if (circ->path_state == PATH_STATE_NEW_CIRC) { - if ((rate_msg = rate_limit_log(&first_hop_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "A %s circuit is in cpath state %d (opened: %d). " - "Circuit is a %s currently %s.%s", - pathbias_state_to_string(circ->path_state), - circ->cpath->state, circ->has_opened, - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), - rate_msg); - tor_free(rate_msg); - } - } } return 0; @@ -2753,48 +1424,33 @@ pathbias_count_first_hop(origin_circuit_t *circ) * Also check for several potential error cases for bug #6475. */ static void -pathbias_count_success(origin_circuit_t *circ) +pathbias_count_build_success(origin_circuit_t *circ) { #define SUCCESS_NOTICE_INTERVAL (600) static ratelim_t success_notice_limit = RATELIM_INIT(SUCCESS_NOTICE_INTERVAL); char *rate_msg = NULL; + entry_guard_t *guard = NULL; - /* Ignore one hop circuits */ - if (circ->build_state->onehop_tunnel || - circ->build_state->desired_path_len == 1) { - /* Check for consistency */ - if (circ->build_state->desired_path_len != 1 || - !circ->build_state->onehop_tunnel) { - if ((rate_msg = rate_limit_log(&success_notice_limit, - approx_time()))) { - log_info(LD_BUG, - "One-hop circuit has length %d. Path state is %s. " - "Circuit is a %s currently %s.%s", - circ->build_state->desired_path_len, - pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), - rate_msg); - tor_free(rate_msg); - } - tor_fragile_assert(); - } + if (!pathbias_should_count(circ)) { return; } - /* Don't count cannibalized/reused circs for path bias */ + /* Don't count cannibalized/reused circs for path bias + * build success.. They get counted under use success */ if (!circ->has_opened) { - entry_guard_t *guard = - entry_guard_get_by_id_digest(circ->_base.n_conn->identity_digest); + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } if (guard) { - if (circ->path_state == PATH_STATE_DID_FIRST_HOP) { - circ->path_state = PATH_STATE_SUCCEEDED; - guard->circuit_successes++; + if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) { + circ->path_state = PATH_STATE_BUILD_SUCCEEDED; + guard->circ_successes++; - log_info(LD_PROTOCOL, "Got success count %u/%u for guard %s=%s", - guard->circuit_successes, guard->first_hops, + log_info(LD_CIRC, "Got success count %f/%f for guard %s=%s", + guard->circ_successes, guard->circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); } else { if ((rate_msg = rate_limit_log(&success_notice_limit, @@ -2803,41 +1459,44 @@ pathbias_count_success(origin_circuit_t *circ) "Succeeded circuit is in strange path state %s. " "Circuit is a %s currently %s.%s", pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } } - if (guard->first_hops < guard->circuit_successes) { - log_info(LD_BUG, "Unexpectedly high circuit_successes (%u/%u) " + if (guard->circ_attempts < guard->circ_successes) { + log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) " "for guard %s=%s", - guard->circuit_successes, guard->first_hops, + guard->circ_successes, guard->circ_attempts, 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, + log_info(LD_CIRC, "Completed circuit has no known guard. " "Circuit is a %s currently %s.%s", - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } } } else { - if (circ->path_state != PATH_STATE_SUCCEEDED) { + if (circ->path_state < PATH_STATE_BUILD_SUCCEEDED) { if ((rate_msg = rate_limit_log(&success_notice_limit, approx_time()))) { log_info(LD_BUG, "Opened circuit is in strange path state %s. " "Circuit is a %s currently %s.%s", pathbias_state_to_string(circ->path_state), - circuit_purpose_to_string(circ->_base.purpose), - circuit_state_to_string(circ->_base.state), + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state), rate_msg); tor_free(rate_msg); } @@ -2845,59 +1504,562 @@ pathbias_count_success(origin_circuit_t *circ) } } +/** + * Send a probe down a circuit that the client attempted to use, + * but for which the stream timed out/failed. The probe is a + * RELAY_BEGIN cell with a 0.a.b.c destination address, which + * the exit will reject and reply back, echoing that address. + * + * The reason for such probes is because it is possible to bias + * a user's paths simply by causing timeouts, and these timeouts + * are not possible to differentiate from unresponsive servers. + * + * The probe is sent at the end of the circuit lifetime for two + * reasons: to prevent cyptographic taggers from being able to + * drop cells to cause timeouts, and to prevent easy recognition + * of probes before any real client traffic happens. + * + * Returns -1 if we couldn't probe, 0 otherwise. + */ +static int +pathbias_send_usable_probe(circuit_t *circ) +{ + /* Based on connection_ap_handshake_send_begin() */ + char payload[CELL_PAYLOAD_SIZE]; + int payload_len; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + crypt_path_t *cpath_layer = NULL; + char *probe_nonce = NULL; + + tor_assert(ocirc); + + cpath_layer = ocirc->cpath->prev; + + if (cpath_layer->state != CPATH_STATE_OPEN) { + /* This can happen for cannibalized circuits. Their + * last hop isn't yet open */ + log_info(LD_CIRC, + "Got pathbias probe request for unopened circuit %d. " + "Opened %d, len %d", ocirc->global_identifier, + ocirc->has_opened, ocirc->build_state->desired_path_len); + return -1; + } + + /* We already went down this road. */ + if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING && + ocirc->pathbias_probe_id) { + log_info(LD_CIRC, + "Got pathbias probe request for circuit %d with " + "outstanding probe", ocirc->global_identifier); + return -1; + } + + circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + /* Update timestamp for circuit_expire_building to kill us */ + tor_gettimeofday(&circ->timestamp_began); + + /* Generate a random address for the nonce */ + crypto_rand((char*)ô->pathbias_probe_nonce, + sizeof(ocirc->pathbias_probe_nonce)); + ocirc->pathbias_probe_nonce &= 0x00ffffff; + probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce); + + tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce); + payload_len = (int)strlen(payload)+1; + + // XXX: need this? Can we assume ipv4 will always be supported? + // If not, how do we tell? + //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { + // set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); + // payload_len += 4; + //} + + /* Generate+Store stream id, make sure it's non-zero */ + ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc); + + if (ocirc->pathbias_probe_id==0) { + log_warn(LD_CIRC, + "Ran out of stream IDs on circuit %u during " + "pathbias probe attempt.", ocirc->global_identifier); + tor_free(probe_nonce); + return -1; + } + + log_info(LD_CIRC, + "Sending pathbias testing cell to %s:25 on stream %d for circ %d.", + probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier); + tor_free(probe_nonce); + + /* Send a test relay cell */ + if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ, + RELAY_COMMAND_BEGIN, payload, + payload_len, cpath_layer) < 0) { + log_notice(LD_CIRC, + "Failed to send pathbias probe cell on circuit %d.", + ocirc->global_identifier); + return -1; + } + + /* Mark it freshly dirty so it doesn't get expired in the meantime */ + circ->timestamp_dirty = time(NULL); + + return 0; +} + +/** + * Check the response to a pathbias probe, to ensure the + * cell is recognized and the nonce and other probe + * characteristics are as expected. + * + * If the response is valid, return 0. Otherwise return < 0. + */ +int +pathbias_check_probe_response(circuit_t *circ, const cell_t *cell) +{ + /* Based on connection_edge_process_relay_cell() */ + relay_header_t rh; + int reason; + uint32_t ipv4_host; + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + + tor_assert(cell); + tor_assert(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + + relay_header_unpack(&rh, cell->payload); + + reason = rh.length > 0 ? + get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC; + + if (rh.command == RELAY_COMMAND_END && + reason == END_STREAM_REASON_EXITPOLICY && + ocirc->pathbias_probe_id == rh.stream_id) { + + /* Check length+extract host: It is in network order after the reason code. + * See connection_edge_end(). */ + if (rh.length < 9) { /* reason+ipv4+dns_ttl */ + log_notice(LD_PROTOCOL, + "Short path bias probe response length field (%d).", rh.length); + return - END_CIRC_REASON_TORPROTOCOL; + } + + ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); + + /* Check nonce */ + if (ipv4_host == ocirc->pathbias_probe_nonce) { + ocirc->path_state = PATH_STATE_USE_SUCCEEDED; + circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); + log_info(LD_CIRC, + "Got valid path bias probe back for circ %d, stream %d.", + ocirc->global_identifier, ocirc->pathbias_probe_id); + return 0; + } else { + log_notice(LD_CIRC, + "Got strange probe value 0x%x vs 0x%x back for circ %d, " + "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce, + ocirc->global_identifier, ocirc->pathbias_probe_id); + return -1; + } + } + log_info(LD_CIRC, + "Got another cell back back on pathbias probe circuit %d: " + "Command: %d, Reason: %d, Stream-id: %d", + ocirc->global_identifier, rh.command, reason, rh.stream_id); + return -1; +} + +/** + * Check if a circuit was used and/or closed successfully. + * + * If we attempted to use the circuit to carry a stream but failed + * for whatever reason, or if the circuit mysteriously died before + * we could attach any streams, record these two cases. + * + * If we *have* successfully used the circuit, or it appears to + * have been closed by us locally, count it as a success. + * + * Returns 0 if we're done making decisions with the circ, + * or -1 if we want to probe it first. + */ +int +pathbias_check_close(origin_circuit_t *ocirc, int reason) +{ + circuit_t *circ = ô->base_; + + if (!pathbias_should_count(ocirc)) { + return 0; + } + + if (ocirc->path_state == PATH_STATE_BUILD_SUCCEEDED) { + if (circ->timestamp_dirty) { + if (pathbias_send_usable_probe(circ) == 0) + return -1; + else + pathbias_count_unusable(ocirc); + + /* Any circuit where there were attempted streams but no successful + * streams could be bias */ + log_info(LD_CIRC, + "Circuit %d closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + + } else { + if (reason & END_CIRC_REASON_FLAG_REMOTE) { + /* Unused remote circ close reasons all could be bias */ + log_info(LD_CIRC, + "Circuit %d remote-closed without successful use for reason %d. " + "Circuit purpose %d currently %d,%s. Len %d.", + ocirc->global_identifier, + reason, circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else if ((reason & ~END_CIRC_REASON_FLAG_REMOTE) + == END_CIRC_REASON_CHANNEL_CLOSED && + circ->n_chan && + circ->n_chan->reason_for_closing + != CHANNEL_CLOSE_REQUESTED) { + /* If we didn't close the channel ourselves, it could be bias */ + /* XXX: Only count bias if the network is live? + * What about clock jumps/suspends? */ + log_info(LD_CIRC, + "Circuit %d's channel closed without successful use for reason " + "%d, channel reason %d. Circuit purpose %d currently %d,%s. Len " + "%d.", ocirc->global_identifier, + reason, circ->n_chan->reason_for_closing, + circ->purpose, ocirc->has_opened, + circuit_state_to_string(circ->state), + ocirc->build_state->desired_path_len); + pathbias_count_collapse(ocirc); + } else { + pathbias_count_successful_close(ocirc); + } + } + } else if (ocirc->path_state == PATH_STATE_USE_SUCCEEDED) { + pathbias_count_successful_close(ocirc); + } + + return 0; +} + +/** + * Count a successfully closed circuit. + */ +static void +pathbias_count_successful_close(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + /* In the long run: circuit_success ~= successful_circuit_close + + * circ_failure + stream_failure */ + guard->successful_circuits_closed++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Successfully closed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count a circuit that fails after it is built, but before it can + * carry any traffic. + * + * This is needed because there are ways to destroy a + * circuit after it has successfully completed. Right now, this is + * used for purely informational/debugging purposes. + */ +static void +pathbias_count_collapse(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->collapsed_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Destroyed circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +static void +pathbias_count_unusable(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + if (!pathbias_should_count(circ)) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->unusable_circuits++; + entry_guards_changed(); + } else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + /* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to + * CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here. + * No need to log that case. */ + log_info(LD_CIRC, + "Stream-failing circuit has no known guard. " + "Circuit is a %s currently %s", + circuit_purpose_to_string(circ->base_.purpose), + circuit_state_to_string(circ->base_.state)); + } +} + +/** + * Count timeouts for path bias log messages. + * + * These counts are purely informational. + */ +void +pathbias_count_timeout(origin_circuit_t *circ) +{ + entry_guard_t *guard = NULL; + + if (!pathbias_should_count(circ)) { + return; + } + + /* For hidden service circs, they can actually be used + * successfully and then time out later (because + * the other side declines to use them). */ + if (circ->path_state == PATH_STATE_USE_SUCCEEDED) { + return; + } + + if (circ->cpath && circ->cpath->extend_info) { + guard = entry_guard_get_by_id_digest( + circ->cpath->extend_info->identity_digest); + } + + if (guard) { + guard->timeouts++; + entry_guards_changed(); + } +} + +/** + * Return the number of circuits counted as successfully closed for + * this guard. + * + * Also add in the currently open circuits to give them the benefit + * of the doubt. + */ +double +pathbias_get_closed_count(entry_guard_t *guard) +{ + circuit_t *circ = global_circuitlist; + int open_circuits = 0; + + /* Count currently open circuits. Give them the benefit of the doubt */ + for ( ; circ; circ = circ->next) { + origin_circuit_t *ocirc = NULL; + if (!CIRCUIT_IS_ORIGIN(circ) || /* didn't originate here */ + circ->marked_for_close) /* already counted */ + continue; + + ocirc = TO_ORIGIN_CIRCUIT(circ); + + if (!ocirc->cpath || !ocirc->cpath->extend_info) + continue; + + if (ocirc->path_state >= PATH_STATE_BUILD_SUCCEEDED && + fast_memeq(guard->identity, + ocirc->cpath->extend_info->identity_digest, + DIGEST_LEN)) { + open_circuits++; + } + } + + return guard->successful_circuits_closed + open_circuits; +} + +/** + * This function checks the consensus parameters to decide + * if it should return guard->circ_successes or + * guard->successful_circuits_closed. + */ +double +pathbias_get_success_count(entry_guard_t *guard) +{ + if (pathbias_use_close_counts(get_options())) { + return pathbias_get_closed_count(guard); + } else { + return guard->circ_successes; + } +} + /** Increment the number of times we successfully extended a circuit to * 'guard', first checking if the failure rate is high enough that we should * eliminate the guard. Return -1 if the guard looks no good; return 0 if the * guard looks fine. */ static int -entry_guard_inc_first_hop_count(entry_guard_t *guard) +entry_guard_inc_circ_attempt_count(entry_guard_t *guard) { const or_options_t *options = get_options(); entry_guards_changed(); - if (guard->first_hops > (unsigned)pathbias_get_min_circs(options)) { + if (guard->circ_attempts > pathbias_get_min_circs(options)) { /* Note: We rely on the < comparison here to allow us to set a 0 * rate and disable the feature entirely. If refactoring, don't * change to <= */ - if (guard->circuit_successes/((double)guard->first_hops) - < pathbias_get_disable_rate(options)) { - - log_info(LD_PROTOCOL, - "Extremely low circuit success rate %u/%u for guard %s=%s. " - "This might indicate an attack, or a bug.", - guard->circuit_successes, guard->first_hops, guard->nickname, - hex_str(guard->identity, DIGEST_LEN)); - - guard->path_bias_disabled = 1; - guard->bad_since = approx_time(); - return -1; - } else if (guard->circuit_successes/((double)guard->first_hops) - < pathbias_get_notice_rate(options) - && !guard->path_bias_notice) { - guard->path_bias_notice = 1; - log_info(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)); + if (pathbias_get_success_count(guard)/guard->circ_attempts + < pathbias_get_extreme_rate(options)) { + /* Dropping is currently disabled by default. */ + if (pathbias_get_dropguards(options)) { + if (!guard->path_bias_disabled) { + log_warn(LD_CIRC, + "Your Guard %s=%s is failing an extremely large amount of " + "circuits. To avoid potential route manipluation attacks, " + "Tor has disabled use of this guard. " + "Success counts are %ld/%ld. %ld circuits completed, %ld " + "were unusable, %ld collapsed, and %ld timed out. For " + "reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_closed_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + guard->path_bias_disabled = 1; + guard->bad_since = approx_time(); + return -1; + } + } else if (!guard->path_bias_extreme) { + guard->path_bias_extreme = 1; + log_warn(LD_CIRC, + "Your Guard %s=%s is failing an extremely large amount of " + "circuits. This could indicate a route manipulation attack, " + "extreme network overload, or a bug. " + "Success counts are %ld/%ld. %ld circuits completed, %ld " + "were unusable, %ld collapsed, and %ld timed out. For " + "reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_closed_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + } + } else if (pathbias_get_success_count(guard)/((double)guard->circ_attempts) + < pathbias_get_warn_rate(options)) { + if (!guard->path_bias_warned) { + guard->path_bias_warned = 1; + log_warn(LD_CIRC, + "Your Guard %s=%s is failing a very large amount of " + "circuits. Most likely this means the Tor network is " + "overloaded, but it could also mean an attack against " + "you or the potentially the guard itself. " + "Success counts are %ld/%ld. %ld circuits completed, %ld " + "were unusable, %ld collapsed, and %ld timed out. For " + "reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_closed_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + } + } else if (pathbias_get_success_count(guard)/((double)guard->circ_attempts) + < pathbias_get_notice_rate(options)) { + if (!guard->path_bias_noticed) { + guard->path_bias_noticed = 1; + log_notice(LD_CIRC, + "Your Guard %s=%s is failing more circuits than usual. " + "Most likely this means the Tor network is overloaded. " + "Success counts are %ld/%ld. %ld circuits completed, %ld " + "were unusable, %ld collapsed, and %ld timed out. For " + "reference, your timeout cutoff is %ld seconds.", + guard->nickname, hex_str(guard->identity, DIGEST_LEN), + tor_lround(pathbias_get_closed_count(guard)), + tor_lround(guard->circ_attempts), + tor_lround(guard->circ_successes), + tor_lround(guard->unusable_circuits), + tor_lround(guard->collapsed_circuits), + tor_lround(guard->timeouts), + tor_lround(circ_times.close_ms/1000)); + } } } /* If we get a ton of circuits, just scale everything down */ - if (guard->first_hops > (unsigned)pathbias_get_scale_threshold(options)) { + if (guard->circ_attempts > pathbias_get_scale_threshold(options)) { const int scale_factor = pathbias_get_scale_factor(options); - 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", - guard->circuit_successes, guard->first_hops, guard->nickname, + const int mult_factor = pathbias_get_mult_factor(options); + log_info(LD_CIRC, + "Scaling pathbias counts to (%f/%f)*(%d/%d) for guard %s=%s", + guard->circ_successes, guard->circ_attempts, + mult_factor, scale_factor, guard->nickname, + hex_str(guard->identity, DIGEST_LEN)); + + guard->circ_attempts *= mult_factor; + guard->circ_successes *= mult_factor; + guard->timeouts *= mult_factor; + guard->successful_circuits_closed *= mult_factor; + guard->collapsed_circuits *= mult_factor; + guard->unusable_circuits *= mult_factor; + + guard->circ_attempts /= scale_factor; + guard->circ_successes /= scale_factor; + guard->timeouts /= scale_factor; + guard->successful_circuits_closed /= scale_factor; + guard->collapsed_circuits /= scale_factor; + guard->unusable_circuits /= scale_factor; + } + guard->circ_attempts++; + log_info(LD_CIRC, "Got success count %f/%f for guard %s=%s", + guard->circ_successes, guard->circ_attempts, guard->nickname, hex_str(guard->identity, DIGEST_LEN)); return 0; } /** A created or extended cell came back to us on the circuit, and it included - * <b>reply</b> as its body. (If <b>reply_type</b> is CELL_CREATED, the body + * reply_cell as its body. (If <b>reply_type</b> is CELL_CREATED, the body * contains (the second DH key, plus KH). If <b>reply_type</b> is * CELL_CREATED_FAST, the body contains a secret y and a hash H(x|y).) * @@ -2907,14 +2069,14 @@ entry_guard_inc_first_hop_count(entry_guard_t *guard) * Return - reason if we want to mark circ for close, else return 0. */ int -circuit_finish_handshake(origin_circuit_t *circ, uint8_t reply_type, - const uint8_t *reply) +circuit_finish_handshake(origin_circuit_t *circ, + const created_cell_t *reply) { char keys[CPATH_KEY_MATERIAL_LEN]; crypt_path_t *hop; int rv; - if ((rv = pathbias_count_first_hop(circ)) < 0) + if ((rv = pathbias_count_circ_attempt(circ)) < 0) return rv; if (circ->cpath->state == CPATH_STATE_AWAITING_KEYS) { @@ -2928,39 +2090,25 @@ circuit_finish_handshake(origin_circuit_t *circ, uint8_t reply_type, } tor_assert(hop->state == CPATH_STATE_AWAITING_KEYS); - if (reply_type == CELL_CREATED && hop->dh_handshake_state) { - if (onion_skin_client_handshake(hop->dh_handshake_state, (char*)reply,keys, - DIGEST_LEN*2+CIPHER_KEY_LEN*2) < 0) { + { + if (onion_skin_client_handshake(hop->handshake_state.tag, + &hop->handshake_state, + reply->reply, reply->handshake_len, + (uint8_t*)keys, sizeof(keys), + (uint8_t*)hop->rend_circ_nonce) < 0) { log_warn(LD_CIRC,"onion_skin_client_handshake failed."); return -END_CIRC_REASON_TORPROTOCOL; } - /* Remember hash of g^xy */ - memcpy(hop->handshake_digest, reply+DH_KEY_LEN, DIGEST_LEN); - } else if (reply_type == CELL_CREATED_FAST && !hop->dh_handshake_state) { - if (fast_client_handshake(hop->fast_handshake_state, reply, - (uint8_t*)keys, - DIGEST_LEN*2+CIPHER_KEY_LEN*2) < 0) { - log_warn(LD_CIRC,"fast_client_handshake failed."); - return -END_CIRC_REASON_TORPROTOCOL; - } - memcpy(hop->handshake_digest, reply+DIGEST_LEN, DIGEST_LEN); - } else { - log_warn(LD_PROTOCOL,"CREATED cell type did not match CREATE cell type."); - return -END_CIRC_REASON_TORPROTOCOL; } - crypto_dh_free(hop->dh_handshake_state); /* don't need it anymore */ - hop->dh_handshake_state = NULL; - - memset(hop->fast_handshake_state, 0, sizeof(hop->fast_handshake_state)); + onion_handshake_state_release(&hop->handshake_state); if (circuit_init_cpath_crypto(hop, keys, 0)<0) { return -END_CIRC_REASON_TORPROTOCOL; } hop->state = CPATH_STATE_OPEN; - log_info(LD_CIRC,"Finished building %scircuit hop:", - (reply_type == CELL_CREATED_FAST) ? "fast " : ""); + log_info(LD_CIRC,"Finished building circuit hop:"); circuit_log_path(LOG_INFO,LD_CIRC,circ); control_event_circuit_status(circ, CIRC_EVENT_EXTENDED, 0); @@ -2974,7 +2122,7 @@ circuit_finish_handshake(origin_circuit_t *circ, uint8_t reply_type, * just give up: for circ to close, and return 0. */ int -circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer) +circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) { // crypt_path_t *victim; // connection_t *stream; @@ -2987,7 +2135,7 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer) * just give up. */ circuit_mark_for_close(TO_CIRCUIT(circ), - END_CIRC_REASON_FLAG_REMOTE|END_CIRC_REASON_OR_CONN_CLOSED); + END_CIRC_REASON_FLAG_REMOTE|reason); return 0; #if 0 @@ -3020,24 +2168,25 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer) * cell back. */ int -onionskin_answer(or_circuit_t *circ, uint8_t cell_type, const char *payload, - const char *keys) +onionskin_answer(or_circuit_t *circ, + const created_cell_t *created_cell, + const char *keys, + const uint8_t *rend_circ_nonce) { cell_t cell; crypt_path_t *tmp_cpath; + if (created_cell_format(&cell, created_cell) < 0) { + log_warn(LD_BUG,"couldn't format created cell"); + return -1; + } + cell.circ_id = circ->p_circ_id; + tmp_cpath = tor_malloc_zero(sizeof(crypt_path_t)); tmp_cpath->magic = CRYPT_PATH_MAGIC; - memset(&cell, 0, sizeof(cell_t)); - cell.command = cell_type; - cell.circ_id = circ->p_circ_id; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - memcpy(cell.payload, payload, - cell_type == CELL_CREATED ? ONIONSKIN_REPLY_LEN : DIGEST_LEN*2); - log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", (unsigned int)get_uint32(keys), (unsigned int)get_uint32(keys+20)); @@ -3053,20 +2202,17 @@ onionskin_answer(or_circuit_t *circ, uint8_t cell_type, const char *payload, tmp_cpath->magic = 0; tor_free(tmp_cpath); - if (cell_type == CELL_CREATED) - memcpy(circ->handshake_digest, cell.payload+DH_KEY_LEN, DIGEST_LEN); - else - memcpy(circ->handshake_digest, cell.payload+DIGEST_LEN, DIGEST_LEN); + memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); - circ->is_first_hop = (cell_type == CELL_CREATED_FAST); + circ->is_first_hop = (created_cell->cell_type == CELL_CREATED_FAST); append_cell_to_circuit_queue(TO_CIRCUIT(circ), - circ->p_conn, &cell, CELL_DIRECTION_IN, 0); + circ->p_chan, &cell, CELL_DIRECTION_IN, 0); log_debug(LD_CIRC,"Finished sending '%s' cell.", circ->is_first_hop ? "created_fast" : "created"); - if (!is_local_addr(&circ->p_conn->_base.addr) && - !connection_or_nonopen_was_started_here(circ->p_conn)) { + if (!channel_is_local(circ->p_chan) && + !channel_is_outgoing(circ->p_chan)) { /* record that we could process create cells from a non-local conn * that we didn't initiate; presumably this means that create cells * can reach us too. */ @@ -3268,7 +2414,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) n_supported[i] = -1; continue; } - if (routerset_contains_node(options->_ExcludeExitNodesUnion, node)) { + if (routerset_contains_node(options->ExcludeExitNodesUnion_, node)) { n_supported[i] = -1; continue; /* user asked us not to use it, no matter what */ } @@ -3285,7 +2431,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) * we'll retry later in this function with need_update and * need_capacity set to 0. */ } - if (!(node->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) { + if (!(node->is_valid || options->AllowInvalid_ & ALLOW_INVALID_EXIT)) { /* if it's invalid and we don't want it */ n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- invalid router.", @@ -3371,7 +2517,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) } log_notice(LD_CIRC, "All routers are down or won't exit%s -- " "choosing a doomed exit at random.", - options->_ExcludeExitNodesUnion ? " or are Excluded" : ""); + options->ExcludeExitNodesUnion_ ? " or are Excluded" : ""); } supporting = smartlist_new(); needed_ports = circuit_get_unhandled_ports(time(NULL)); @@ -3410,7 +2556,7 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) log_warn(LD_CIRC, "No specified %sexit routers seem to be running: " "can't choose an exit.", - options->_ExcludeExitNodesUnion ? "non-excluded " : ""); + options->ExcludeExitNodesUnion_ ? "non-excluded " : ""); } return NULL; } @@ -3438,14 +2584,14 @@ choose_good_exit_server(uint8_t purpose, switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: - if (options->_AllowInvalid & ALLOW_INVALID_MIDDLE) + if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE) flags |= CRN_ALLOW_INVALID; if (is_internal) /* pick it like a middle hop */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); else return choose_good_exit_server_general(need_uptime,need_capacity); case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - if (options->_AllowInvalid & ALLOW_INVALID_RENDEZVOUS) + if (options->AllowInvalid_ & ALLOW_INVALID_RENDEZVOUS) flags |= CRN_ALLOW_INVALID; return router_choose_random_node(NULL, options->ExcludeNodes, flags); } @@ -3462,7 +2608,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) const or_options_t *options = get_options(); routerset_t *rs = options->ExcludeNodes; const char *description; - uint8_t purpose = circ->_base.purpose; + uint8_t purpose = circ->base_.purpose; if (circ->build_state->onehop_tunnel) return; @@ -3482,7 +2628,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) if (circ->build_state->is_internal) return; description = "requested exit node"; - rs = options->_ExcludeExitNodesUnion; + rs = options->ExcludeExitNodesUnion_; break; case CIRCUIT_PURPOSE_C_INTRODUCING: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: @@ -3499,7 +2645,7 @@ warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) description = "chosen rendezvous point"; break; case CIRCUIT_PURPOSE_CONTROLLER: - rs = options->_ExcludeExitNodesUnion; + rs = options->ExcludeExitNodesUnion_; description = "controller-selected circuit target"; break; } @@ -3541,7 +2687,7 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel."); state->desired_path_len = 1; } else { - int r = new_route_len(circ->_base.purpose, exit, nodelist_get_list()); + int r = new_route_len(circ->base_.purpose, exit, nodelist_get_list()); if (r < 1) /* must be at least 1 */ return -1; state->desired_path_len = r; @@ -3554,7 +2700,7 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) exit = extend_info_dup(exit); } else { /* we have to decide one */ const node_t *node = - choose_good_exit_server(circ->_base.purpose, state->need_uptime, + choose_good_exit_server(circ->base_.purpose, state->need_uptime, state->need_capacity, state->is_internal); if (!node) { log_warn(LD_CIRC,"failed to choose an exit server"); @@ -3597,6 +2743,9 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) { int err_reason = 0; warn_if_last_router_excluded(circ, exit); + + tor_gettimeofday(&circ->base_.timestamp_began); + circuit_append_new_exit(circ, exit); circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); if ((err_reason = circuit_send_next_onion_skin(circ))<0) { @@ -3605,6 +2754,11 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) circuit_mark_for_close(TO_CIRCUIT(circ), -err_reason); return -1; } + + /* Set timestamp_dirty, so we can check it for path use bias */ + if (!circ->base_.timestamp_dirty) + circ->base_.timestamp_dirty = time(NULL); + return 0; } @@ -3675,8 +2829,8 @@ choose_good_middle_server(uint8_t purpose, smartlist_t *excluded; const or_options_t *options = get_options(); router_crn_flags_t flags = CRN_NEED_DESC; - tor_assert(_CIRCUIT_PURPOSE_MIN <= purpose && - purpose <= _CIRCUIT_PURPOSE_MAX); + tor_assert(CIRCUIT_PURPOSE_MIN_ <= purpose && + purpose <= CIRCUIT_PURPOSE_MAX_); log_debug(LD_CIRC, "Contemplating intermediate hop: random choice."); excluded = smartlist_new(); @@ -3693,7 +2847,7 @@ choose_good_middle_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (state->need_capacity) flags |= CRN_NEED_CAPACITY; - if (options->_AllowInvalid & ALLOW_INVALID_MIDDLE) + if (options->AllowInvalid_ & ALLOW_INVALID_MIDDLE) flags |= CRN_ALLOW_INVALID; choice = router_choose_random_node(excluded, options->ExcludeNodes, flags); smartlist_free(excluded); @@ -3708,7 +2862,8 @@ choose_good_middle_server(uint8_t purpose, * If <b>state</b> is NULL, we're choosing a router to serve as an entry * guard, not for any particular circuit. */ -static const node_t * +/* XXXX024 I'd like to have this be static again, but entrynodes.c needs it. */ +const node_t * choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) { const node_t *choice; @@ -3740,8 +2895,8 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) }); } /* and exclude current entry guards and their families, if applicable */ - if (options->UseEntryGuards && entry_guards) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, + if (options->UseEntryGuards) { + SMARTLIST_FOREACH(get_entry_guards(), const entry_guard_t *, entry, { if ((node = node_get_by_id(entry->identity))) { nodelist_add_node_and_family(excluded, node); @@ -3755,7 +2910,7 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) if (state->need_capacity) flags |= CRN_NEED_CAPACITY; } - if (options->_AllowInvalid & ALLOW_INVALID_ENTRY) + if (options->AllowInvalid_ & ALLOW_INVALID_ENTRY) flags |= CRN_ALLOW_INVALID; choice = router_choose_random_node(excluded, options->ExcludeNodes, flags); @@ -3783,7 +2938,7 @@ onion_next_hop_in_cpath(crypt_path_t *cpath) static int onion_extend_cpath(origin_circuit_t *circ) { - uint8_t purpose = circ->_base.purpose; + uint8_t purpose = circ->base_.purpose; cpath_build_state_t *state = circ->build_state; int cur_len = circuit_get_cpath_len(circ); extend_info_t *info = NULL; @@ -3802,12 +2957,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 { @@ -3858,9 +3011,10 @@ onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice) /** Allocate a new extend_info object based on the various arguments. */ extend_info_t * -extend_info_alloc(const char *nickname, const char *digest, - crypto_pk_t *onion_key, - const tor_addr_t *addr, uint16_t port) +extend_info_new(const char *nickname, const char *digest, + crypto_pk_t *onion_key, + const curve25519_public_key_t *curve25519_key, + const tor_addr_t *addr, uint16_t port) { extend_info_t *info = tor_malloc_zero(sizeof(extend_info_t)); memcpy(info->identity_digest, digest, DIGEST_LEN); @@ -3868,52 +3022,59 @@ extend_info_alloc(const char *nickname, const char *digest, strlcpy(info->nickname, nickname, sizeof(info->nickname)); if (onion_key) info->onion_key = crypto_pk_dup_key(onion_key); +#ifdef CURVE25519_ENABLED + if (curve25519_key) + memcpy(&info->curve25519_onion_key, curve25519_key, + sizeof(curve25519_public_key_t)); +#else + (void)curve25519_key; +#endif tor_addr_copy(&info->addr, addr); info->port = port; 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); - return extend_info_alloc(node->rs->nickname, + log_debug(LD_CIRC, "using %s for %s", + fmt_addrport(&ap.addr, ap.port), + node->ri ? node->ri->nickname : node->rs->nickname); + + if (node->ri) + return extend_info_new(node->ri->nickname, + node->identity, + node->ri->onion_pkey, + node->ri->onion_curve25519_pkey, + &ap.addr, + ap.port); + else if (node->rs && node->md) + return extend_info_new(node->rs->nickname, node->identity, node->md->onion_pkey, - &addr, - node->rs->or_port); - } else { + node->md->onion_curve25519_pkey, + &ap.addr, + ap.port); + else return NULL; - } } /** Release storage held by an extend_info_t struct. */ @@ -3966,2067 +3127,3 @@ build_state_get_exit_nickname(cpath_build_state_t *state) return state->chosen_exit->nickname; } -/** Check whether the entry guard <b>e</b> is usable, given the directory - * authorities' opinion about the router (stored in <b>ri</b>) and the user's - * configuration (in <b>options</b>). Set <b>e</b>->bad_since - * accordingly. Return true iff the entry guard's status changes. - * - * If it's not usable, set *<b>reason</b> to a static string explaining why. - */ -static int -entry_guard_set_status(entry_guard_t *e, const node_t *node, - time_t now, const or_options_t *options, - const char **reason) -{ - char buf[HEX_DIGEST_LEN+1]; - int changed = 0; - - *reason = NULL; - - /* Do we want to mark this guard as bad? */ - if (!node) - *reason = "unlisted"; - else if (!node->is_running) - *reason = "down"; - else if (options->UseBridges && (!node->ri || - node->ri->purpose != ROUTER_PURPOSE_BRIDGE)) - *reason = "not a bridge"; - else if (options->UseBridges && !node_is_a_configured_bridge(node)) - *reason = "not a configured bridge"; - else if (!options->UseBridges && !node->is_possible_guard && - !routerset_contains_node(options->EntryNodes,node)) - *reason = "not recommended as a guard"; - else if (routerset_contains_node(options->ExcludeNodes, node)) - *reason = "excluded"; - else if (e->path_bias_disabled) - *reason = "path-biased"; - - if (*reason && ! e->bad_since) { - /* Router is newly bad. */ - base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN); - log_info(LD_CIRC, "Entry guard %s (%s) is %s: marking as unusable.", - e->nickname, buf, *reason); - - e->bad_since = now; - control_event_guard(e->nickname, e->identity, "BAD"); - changed = 1; - } else if (!*reason && e->bad_since) { - /* There's nothing wrong with the router any more. */ - base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN); - log_info(LD_CIRC, "Entry guard %s (%s) is no longer unusable: " - "marking as ok.", e->nickname, buf); - - e->bad_since = 0; - control_event_guard(e->nickname, e->identity, "GOOD"); - changed = 1; - } - return changed; -} - -/** Return true iff enough time has passed since we last tried to connect - * to the unreachable guard <b>e</b> that we're willing to try again. */ -static int -entry_is_time_to_retry(entry_guard_t *e, time_t now) -{ - long diff; - if (e->last_attempted < e->unreachable_since) - return 1; - diff = now - e->unreachable_since; - if (diff < 6*60*60) - return now > (e->last_attempted + 60*60); - else if (diff < 3*24*60*60) - return now > (e->last_attempted + 4*60*60); - else if (diff < 7*24*60*60) - return now > (e->last_attempted + 18*60*60); - else - return now > (e->last_attempted + 36*60*60); -} - -/** Return the node corresponding to <b>e</b>, if <b>e</b> is - * working well enough that we are willing to use it as an entry - * right now. (Else return NULL.) In particular, it must be - * - Listed as either up or never yet contacted; - * - Present in the routerlist; - * - Listed as 'stable' or 'fast' by the current dirserver consensus, - * if demanded by <b>need_uptime</b> or <b>need_capacity</b> - * (unless it's a configured EntryNode); - * - Allowed by our current ReachableORAddresses config option; and - * - Currently thought to be reachable by us (unless <b>assume_reachable</b> - * is true). - * - * If the answer is no, set *<b>msg</b> to an explanation of why. - */ -static INLINE const node_t * -entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, - int assume_reachable, const char **msg) -{ - const node_t *node; - const or_options_t *options = get_options(); - tor_assert(msg); - - if (e->path_bias_disabled) { - *msg = "path-biased"; - return NULL; - } - if (e->bad_since) { - *msg = "bad"; - return NULL; - } - /* no good if it's unreachable, unless assume_unreachable or can_retry. */ - if (!assume_reachable && !e->can_retry && - e->unreachable_since && !entry_is_time_to_retry(e, time(NULL))) { - *msg = "unreachable"; - return NULL; - } - node = node_get_by_id(e->identity); - if (!node || !node_has_descriptor(node)) { - *msg = "no descriptor"; - return NULL; - } - if (get_options()->UseBridges) { - if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) { - *msg = "not a bridge"; - return NULL; - } - if (!node_is_a_configured_bridge(node)) { - *msg = "not a configured bridge"; - return NULL; - } - } else { /* !get_options()->UseBridges */ - if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) { - *msg = "not general-purpose"; - return NULL; - } - } - if (routerset_contains_node(options->EntryNodes, node)) { - /* they asked for it, they get it */ - need_uptime = need_capacity = 0; - } - if (node_is_unreliable(node, need_uptime, need_capacity, 0)) { - *msg = "not fast/stable"; - return NULL; - } - if (!fascist_firewall_allows_node(node)) { - *msg = "unreachable by config"; - return NULL; - } - return node; -} - -/** Return the number of entry guards that we think are usable. */ -static int -num_live_entry_guards(void) -{ - int n = 0; - const char *msg; - if (! entry_guards) - return 0; - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - { - if (entry_is_live(entry, 0, 1, 0, &msg)) - ++n; - }); - return n; -} - -/** If <b>digest</b> matches the identity of any node in the - * entry_guards list, return that node. Else return NULL. */ -static entry_guard_t * -entry_guard_get_by_id_digest(const char *digest) -{ - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - if (tor_memeq(digest, entry->identity, DIGEST_LEN)) - return entry; - ); - return NULL; -} - -/** Dump a description of our list of entry guards to the log at level - * <b>severity</b>. */ -static void -log_entry_guards(int severity) -{ - smartlist_t *elements = smartlist_new(); - char *s; - - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) - { - const char *msg = NULL; - if (entry_is_live(e, 0, 1, 0, &msg)) - smartlist_add_asprintf(elements, "%s [%s] (up %s)", - e->nickname, - hex_str(e->identity, DIGEST_LEN), - e->made_contact ? "made-contact" : "never-contacted"); - else - smartlist_add_asprintf(elements, "%s [%s] (%s, %s)", - e->nickname, - hex_str(e->identity, DIGEST_LEN), - msg, - e->made_contact ? "made-contact" : "never-contacted"); - } - SMARTLIST_FOREACH_END(e); - - s = smartlist_join_strings(elements, ",", 0, NULL); - SMARTLIST_FOREACH(elements, char*, cp, tor_free(cp)); - smartlist_free(elements); - log_fn(severity,LD_CIRC,"%s",s); - tor_free(s); -} - -/** Called when one or more guards that we would previously have used for some - * purpose are no longer in use because a higher-priority guard has become - * usable again. */ -static void -control_event_guard_deferred(void) -{ - /* XXXX We don't actually have a good way to figure out _how many_ entries - * are live for some purpose. We need an entry_is_even_slightly_live() - * function for this to work right. NumEntryGuards isn't reliable: if we - * need guards with weird properties, we can have more than that number - * live. - **/ -#if 0 - int n = 0; - const char *msg; - const or_options_t *options = get_options(); - if (!entry_guards) - return; - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, - { - if (entry_is_live(entry, 0, 1, 0, &msg)) { - if (n++ == options->NumEntryGuards) { - control_event_guard(entry->nickname, entry->identity, "DEFERRED"); - return; - } - } - }); -#endif -} - -/** Add a new (preferably stable and fast) router to our - * entry_guards list. Return a pointer to the router if we succeed, - * or NULL if we can't find any more suitable entries. - * - * If <b>chosen</b> is defined, use that one, and if it's not - * already in our entry_guards list, put it at the *beginning*. - * Else, put the one we pick at the end of the list. */ -static const node_t * -add_an_entry_guard(const node_t *chosen, int reset_status, int prepend) -{ - const node_t *node; - entry_guard_t *entry; - - if (chosen) { - node = chosen; - entry = entry_guard_get_by_id_digest(node->identity); - if (entry) { - if (reset_status) { - entry->bad_since = 0; - entry->can_retry = 1; - } - return NULL; - } - } else { - node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); - if (!node) - return NULL; - } - entry = tor_malloc_zero(sizeof(entry_guard_t)); - log_info(LD_CIRC, "Chose %s as new entry guard.", - node_describe(node)); - strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); - memcpy(entry->identity, node->identity, DIGEST_LEN); - /* Choose expiry time smudged over the past month. The goal here - * is to a) spread out when Tor clients rotate their guards, so they - * don't all select them on the same day, and b) avoid leaving a - * precise timestamp in the state file about when we first picked - * this guard. For details, see the Jan 2010 or-dev thread. */ - entry->chosen_on_date = time(NULL) - crypto_rand_int(3600*24*30); - entry->chosen_by_version = tor_strdup(VERSION); - if (prepend) - smartlist_insert(entry_guards, 0, entry); - else - smartlist_add(entry_guards, entry); - control_event_guard(entry->nickname, entry->identity, "NEW"); - control_event_guard_deferred(); - log_entry_guards(LOG_INFO); - return node; -} - -/** If the use of entry guards is configured, choose more entry guards - * until we have enough in the list. */ -static void -pick_entry_guards(const or_options_t *options) -{ - int changed = 0; - - tor_assert(entry_guards); - - while (num_live_entry_guards() < options->NumEntryGuards) { - if (!add_an_entry_guard(NULL, 0, 0)) - break; - changed = 1; - } - if (changed) - entry_guards_changed(); -} - -/** How long (in seconds) do we allow an entry guard to be nonfunctional, - * unlisted, excluded, or otherwise nonusable before we give up on it? */ -#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) - -/** Release all storage held by <b>e</b>. */ -static void -entry_guard_free(entry_guard_t *e) -{ - if (!e) - return; - tor_free(e->chosen_by_version); - tor_free(e); -} - -/** Remove any entry guard which was selected by an unknown version of Tor, - * or which was selected by a version of Tor that's known to select - * entry guards badly, or which was selected more 2 months ago. */ -/* XXXX The "obsolete guards" and "chosen long ago guards" things should - * probably be different functions. */ -static int -remove_obsolete_entry_guards(time_t now) -{ - int changed = 0, i; - - for (i = 0; i < smartlist_len(entry_guards); ++i) { - entry_guard_t *entry = smartlist_get(entry_guards, i); - const char *ver = entry->chosen_by_version; - const char *msg = NULL; - tor_version_t v; - int version_is_bad = 0, date_is_bad = 0; - if (!ver) { - msg = "does not say what version of Tor it was selected by"; - version_is_bad = 1; - } else if (tor_version_parse(ver, &v)) { - msg = "does not seem to be from any recognized version of Tor"; - version_is_bad = 1; - } else { - char *tor_ver = NULL; - tor_asprintf(&tor_ver, "Tor %s", ver); - if ((tor_version_as_new_as(tor_ver, "0.1.0.10-alpha") && - !tor_version_as_new_as(tor_ver, "0.1.2.16-dev")) || - (tor_version_as_new_as(tor_ver, "0.2.0.0-alpha") && - !tor_version_as_new_as(tor_ver, "0.2.0.6-alpha")) || - /* above are bug 440; below are bug 1217 */ - (tor_version_as_new_as(tor_ver, "0.2.1.3-alpha") && - !tor_version_as_new_as(tor_ver, "0.2.1.23")) || - (tor_version_as_new_as(tor_ver, "0.2.2.0-alpha") && - !tor_version_as_new_as(tor_ver, "0.2.2.7-alpha"))) { - msg = "was selected without regard for guard bandwidth"; - version_is_bad = 1; - } - tor_free(tor_ver); - } - if (!version_is_bad && entry->chosen_on_date + 3600*24*60 < now) { - /* It's been 2 months since the date listed in our state file. */ - msg = "was selected several months ago"; - date_is_bad = 1; - } - - if (version_is_bad || date_is_bad) { /* we need to drop it */ - char dbuf[HEX_DIGEST_LEN+1]; - tor_assert(msg); - base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); - log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC, - "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.", - entry->nickname, dbuf, msg, ver?escaped(ver):"none"); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i--); - log_entry_guards(LOG_INFO); - changed = 1; - } - } - - return changed ? 1 : 0; -} - -/** Remove all entry guards that have been down or unlisted for so - * long that we don't think they'll come up again. Return 1 if we - * removed any, or 0 if we did nothing. */ -static int -remove_dead_entry_guards(time_t now) -{ - char dbuf[HEX_DIGEST_LEN+1]; - char tbuf[ISO_TIME_LEN+1]; - int i; - int changed = 0; - - for (i = 0; i < smartlist_len(entry_guards); ) { - entry_guard_t *entry = smartlist_get(entry_guards, i); - if (entry->bad_since && - ! entry->path_bias_disabled && - entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { - - base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); - format_local_iso_time(tbuf, entry->bad_since); - log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted " - "since %s local time; removing.", - entry->nickname, dbuf, tbuf); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i); - log_entry_guards(LOG_INFO); - changed = 1; - } else - ++i; - } - return changed ? 1 : 0; -} - -/** A new directory or router-status has arrived; update the down/listed - * status of the entry guards. - * - * An entry is 'down' if the directory lists it as nonrunning. - * An entry is 'unlisted' if the directory doesn't include it. - * - * Don't call this on startup; only on a fresh download. Otherwise we'll - * think that things are unlisted. - */ -void -entry_guards_compute_status(const or_options_t *options, time_t now) -{ - int changed = 0; - digestmap_t *reasons; - - if (! entry_guards) - return; - - if (options->EntryNodes) /* reshuffle the entry guard list if needed */ - entry_nodes_should_be_added(); - - reasons = digestmap_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) - { - const node_t *r = node_get_by_id(entry->identity); - const char *reason = NULL; - if (entry_guard_set_status(entry, r, now, options, &reason)) - changed = 1; - - if (entry->bad_since) - tor_assert(reason); - if (reason) - digestmap_set(reasons, entry->identity, (char*)reason); - } - SMARTLIST_FOREACH_END(entry); - - if (remove_dead_entry_guards(now)) - changed = 1; - if (remove_obsolete_entry_guards(now)) - changed = 1; - - if (changed) { - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { - const char *reason = digestmap_get(reasons, entry->identity); - const char *live_msg = ""; - const node_t *r = entry_is_live(entry, 0, 1, 0, &live_msg); - log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.", - entry->nickname, - hex_str(entry->identity, DIGEST_LEN), - entry->unreachable_since ? "unreachable" : "reachable", - entry->bad_since ? "unusable" : "usable", - reason ? ", ": "", - reason ? reason : "", - r ? "live" : "not live / ", - r ? "" : live_msg); - } SMARTLIST_FOREACH_END(entry); - log_info(LD_CIRC, " (%d/%d entry guards are usable/new)", - num_live_entry_guards(), smartlist_len(entry_guards)); - log_entry_guards(LOG_INFO); - entry_guards_changed(); - } - - digestmap_free(reasons, NULL); -} - -/** Called when a connection to an OR with the identity digest <b>digest</b> - * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0). - * If the OR is an entry, change that entry's up/down status. - * Return 0 normally, or -1 if we want to tear down the new connection. - * - * If <b>mark_relay_status</b>, also call router_set_status() on this - * relay. - * - * XXX024 change succeeded and mark_relay_status into 'int flags'. - */ -int -entry_guard_register_connect_status(const char *digest, int succeeded, - int mark_relay_status, time_t now) -{ - int changed = 0; - int refuse_conn = 0; - int first_contact = 0; - entry_guard_t *entry = NULL; - int idx = -1; - char buf[HEX_DIGEST_LEN+1]; - - if (! entry_guards) - return 0; - - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - tor_assert(e); - if (tor_memeq(e->identity, digest, DIGEST_LEN)) { - entry = e; - idx = e_sl_idx; - break; - } - } SMARTLIST_FOREACH_END(e); - - if (!entry) - return 0; - - base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN); - - if (succeeded) { - if (entry->unreachable_since) { - log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again. Good.", - entry->nickname, buf); - entry->can_retry = 0; - entry->unreachable_since = 0; - entry->last_attempted = now; - control_event_guard(entry->nickname, entry->identity, "UP"); - changed = 1; - } - if (!entry->made_contact) { - entry->made_contact = 1; - first_contact = changed = 1; - } - } else { /* ! succeeded */ - if (!entry->made_contact) { - /* We've never connected to this one. */ - log_info(LD_CIRC, - "Connection to never-contacted entry guard '%s' (%s) failed. " - "Removing from the list. %d/%d entry guards usable/new.", - entry->nickname, buf, - num_live_entry_guards()-1, smartlist_len(entry_guards)-1); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, idx); - log_entry_guards(LOG_INFO); - changed = 1; - } else if (!entry->unreachable_since) { - log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). " - "Marking as unreachable.", entry->nickname, buf); - entry->unreachable_since = entry->last_attempted = now; - control_event_guard(entry->nickname, entry->identity, "DOWN"); - changed = 1; - entry->can_retry = 0; /* We gave it an early chance; no good. */ - } else { - char tbuf[ISO_TIME_LEN+1]; - format_iso_time(tbuf, entry->unreachable_since); - log_debug(LD_CIRC, "Failed to connect to unreachable entry guard " - "'%s' (%s). It has been unreachable since %s.", - entry->nickname, buf, tbuf); - entry->last_attempted = now; - entry->can_retry = 0; /* We gave it an early chance; no good. */ - } - } - - /* if the caller asked us to, also update the is_running flags for this - * relay */ - if (mark_relay_status) - router_set_status(digest, succeeded); - - if (first_contact) { - /* We've just added a new long-term entry guard. Perhaps the network just - * came back? We should give our earlier entries another try too, - * and close this connection so we don't use it before we've given - * the others a shot. */ - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - if (e == entry) - break; - if (e->made_contact) { - const char *msg; - const node_t *r = entry_is_live(e, 0, 1, 1, &msg); - if (r && e->unreachable_since) { - refuse_conn = 1; - e->can_retry = 1; - } - } - } SMARTLIST_FOREACH_END(e); - if (refuse_conn) { - log_info(LD_CIRC, - "Connected to new entry guard '%s' (%s). Marking earlier " - "entry guards up. %d/%d entry guards usable/new.", - entry->nickname, buf, - num_live_entry_guards(), smartlist_len(entry_guards)); - log_entry_guards(LOG_INFO); - changed = 1; - } - } - - if (changed) - entry_guards_changed(); - return refuse_conn ? -1 : 0; -} - -/** When we try to choose an entry guard, should we parse and add - * config's EntryNodes first? */ -static int should_add_entry_nodes = 0; - -/** Called when the value of EntryNodes changes in our configuration. */ -void -entry_nodes_should_be_added(void) -{ - log_info(LD_CIRC, "EntryNodes config option set. Putting configured " - "relays at the front of the entry guard list."); - should_add_entry_nodes = 1; -} - -/** Adjust the entry guards list so that it only contains entries from - * EntryNodes, adding new entries from EntryNodes to the list as needed. */ -static void -entry_guards_set_from_config(const or_options_t *options) -{ - smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps; - smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list; - tor_assert(entry_guards); - - should_add_entry_nodes = 0; - - if (!options->EntryNodes) { - /* It's possible that a controller set EntryNodes, thus making - * should_add_entry_nodes set, then cleared it again, all before the - * call to choose_random_entry() that triggered us. If so, just return. - */ - return; - } - - { - char *string = routerset_to_string(options->EntryNodes); - log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string); - tor_free(string); - } - - entry_nodes = smartlist_new(); - worse_entry_nodes = smartlist_new(); - entry_fps = smartlist_new(); - old_entry_guards_on_list = smartlist_new(); - old_entry_guards_not_on_list = smartlist_new(); - - /* Split entry guards into those on the list and those not. */ - - routerset_get_all_nodes(entry_nodes, options->EntryNodes, - options->ExcludeNodes, 0); - SMARTLIST_FOREACH(entry_nodes, const node_t *,node, - smartlist_add(entry_fps, (void*)node->identity)); - - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { - if (smartlist_digest_isin(entry_fps, e->identity)) - smartlist_add(old_entry_guards_on_list, e); - else - smartlist_add(old_entry_guards_not_on_list, e); - }); - - /* Remove all currently configured guard nodes, excluded nodes, unreachable - * nodes, or non-Guard nodes from entry_nodes. */ - SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { - if (entry_guard_get_by_id_digest(node->identity)) { - SMARTLIST_DEL_CURRENT(entry_nodes, node); - continue; - } else if (routerset_contains_node(options->ExcludeNodes, node)) { - SMARTLIST_DEL_CURRENT(entry_nodes, node); - continue; - } else if (!fascist_firewall_allows_node(node)) { - SMARTLIST_DEL_CURRENT(entry_nodes, node); - continue; - } else if (! node->is_possible_guard) { - smartlist_add(worse_entry_nodes, (node_t*)node); - SMARTLIST_DEL_CURRENT(entry_nodes, node); - } - } SMARTLIST_FOREACH_END(node); - - /* Now build the new entry_guards list. */ - smartlist_clear(entry_guards); - /* First, the previously configured guards that are in EntryNodes. */ - smartlist_add_all(entry_guards, old_entry_guards_on_list); - /* Next, scramble the rest of EntryNodes, putting the guards first. */ - smartlist_shuffle(entry_nodes); - smartlist_shuffle(worse_entry_nodes); - smartlist_add_all(entry_nodes, worse_entry_nodes); - - /* Next, the rest of EntryNodes */ - SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { - add_an_entry_guard(node, 0, 0); - if (smartlist_len(entry_guards) > options->NumEntryGuards * 10) - break; - } SMARTLIST_FOREACH_END(node); - log_notice(LD_GENERAL, "%d entries in guards", smartlist_len(entry_guards)); - /* Finally, free the remaining previously configured guards that are not in - * EntryNodes. */ - SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, - entry_guard_free(e)); - - smartlist_free(entry_nodes); - smartlist_free(worse_entry_nodes); - smartlist_free(entry_fps); - smartlist_free(old_entry_guards_on_list); - smartlist_free(old_entry_guards_not_on_list); - entry_guards_changed(); -} - -/** Return 0 if we're fine adding arbitrary routers out of the - * directory to our entry guard list, or return 1 if we have a - * list already and we must stick to it. - */ -int -entry_list_is_constrained(const or_options_t *options) -{ - if (options->EntryNodes) - return 1; - if (options->UseBridges) - return 1; - return 0; -} - -/** Pick a live (up and listed) entry guard from entry_guards. If - * <b>state</b> is non-NULL, this is for a specific circuit -- - * make sure not to pick this circuit's exit or any node in the - * exit's family. If <b>state</b> is NULL, we're looking for a random - * guard (likely a bridge). */ -const node_t * -choose_random_entry(cpath_build_state_t *state) -{ - const or_options_t *options = get_options(); - smartlist_t *live_entry_guards = smartlist_new(); - smartlist_t *exit_family = smartlist_new(); - const node_t *chosen_exit = - state?build_state_get_exit_node(state) : NULL; - const node_t *node = NULL; - int need_uptime = state ? state->need_uptime : 0; - int need_capacity = state ? state->need_capacity : 0; - int preferred_min, consider_exit_family = 0; - - if (chosen_exit) { - nodelist_add_node_and_family(exit_family, chosen_exit); - consider_exit_family = 1; - } - - if (!entry_guards) - entry_guards = smartlist_new(); - - if (should_add_entry_nodes) - entry_guards_set_from_config(options); - - if (!entry_list_is_constrained(options) && - smartlist_len(entry_guards) < options->NumEntryGuards) - pick_entry_guards(options); - - retry: - smartlist_clear(live_entry_guards); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { - const char *msg; - node = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); - if (!node) - continue; /* down, no point */ - if (node == chosen_exit) - continue; /* don't pick the same node for entry and exit */ - if (consider_exit_family && smartlist_isin(exit_family, node)) - continue; /* avoid relays that are family members of our exit */ -#if 0 /* since EntryNodes is always strict now, this clause is moot */ - if (options->EntryNodes && - !routerset_contains_node(options->EntryNodes, node)) { - /* We've come to the end of our preferred entry nodes. */ - if (smartlist_len(live_entry_guards)) - goto choose_and_finish; /* only choose from the ones we like */ - if (options->StrictNodes) { - /* in theory this case should never happen, since - * entry_guards_set_from_config() drops unwanted relays */ - tor_fragile_assert(); - } else { - log_info(LD_CIRC, - "No relays from EntryNodes available. Using others."); - } - } -#endif - smartlist_add(live_entry_guards, (void*)node); - if (!entry->made_contact) { - /* Always start with the first not-yet-contacted entry - * guard. Otherwise we might add several new ones, pick - * the second new one, and now we've expanded our entry - * guard list without needing to. */ - goto choose_and_finish; - } - if (smartlist_len(live_entry_guards) >= options->NumEntryGuards) - goto choose_and_finish; /* we have enough */ - } SMARTLIST_FOREACH_END(entry); - - if (entry_list_is_constrained(options)) { - /* If we prefer the entry nodes we've got, and we have at least - * one choice, that's great. Use it. */ - preferred_min = 1; - } else { - /* Try to have at least 2 choices available. This way we don't - * get stuck with a single live-but-crummy entry and just keep - * using him. - * (We might get 2 live-but-crummy entry guards, but so be it.) */ - preferred_min = 2; - } - - if (smartlist_len(live_entry_guards) < preferred_min) { - if (!entry_list_is_constrained(options)) { - /* still no? try adding a new entry then */ - /* XXX if guard doesn't imply fast and stable, then we need - * to tell add_an_entry_guard below what we want, or it might - * be a long time til we get it. -RD */ - node = add_an_entry_guard(NULL, 0, 0); - if (node) { - entry_guards_changed(); - /* XXX we start over here in case the new node we added shares - * a family with our exit node. There's a chance that we'll just - * load up on entry guards here, if the network we're using is - * one big family. Perhaps we should teach add_an_entry_guard() - * to understand nodes-to-avoid-if-possible? -RD */ - goto retry; - } - } - if (!node && need_uptime) { - need_uptime = 0; /* try without that requirement */ - goto retry; - } - if (!node && need_capacity) { - /* still no? last attempt, try without requiring capacity */ - need_capacity = 0; - goto retry; - } -#if 0 - /* Removing this retry logic: if we only allow one exit, and it is in the - same family as all our entries, then we are just plain not going to win - here. */ - if (!node && entry_list_is_constrained(options) && consider_exit_family) { - /* still no? if we're using bridges or have strictentrynodes - * set, and our chosen exit is in the same family as all our - * bridges/entry guards, then be flexible about families. */ - consider_exit_family = 0; - goto retry; - } -#endif - /* live_entry_guards may be empty below. Oh well, we tried. */ - } - - choose_and_finish: - if (entry_list_is_constrained(options)) { - /* We need to weight by bandwidth, because our bridges or entryguards - * were not already selected proportional to their bandwidth. */ - node = node_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD); - } else { - /* We choose uniformly at random here, because choose_good_entry_server() - * already weights its choices by bandwidth, so we don't want to - * *double*-weight our guard selection. */ - node = smartlist_choose(live_entry_guards); - } - smartlist_free(live_entry_guards); - smartlist_free(exit_family); - return node; -} - -/** Parse <b>state</b> and learn about the entry guards it describes. - * If <b>set</b> is true, and there are no errors, replace the global - * entry_list with what we find. - * On success, return 0. On failure, alloc into *<b>msg</b> a string - * describing the error, and return -1. - */ -int -entry_guards_parse_state(or_state_t *state, int set, char **msg) -{ - entry_guard_t *node = NULL; - smartlist_t *new_entry_guards = smartlist_new(); - config_line_t *line; - time_t now = time(NULL); - const char *state_version = state->TorVersion; - digestmap_t *added_by = digestmap_new(); - - *msg = NULL; - for (line = state->EntryGuards; line; line = line->next) { - if (!strcasecmp(line->key, "EntryGuard")) { - smartlist_t *args = smartlist_new(); - node = tor_malloc_zero(sizeof(entry_guard_t)); - /* all entry guards on disk have been contacted */ - node->made_contact = 1; - smartlist_add(new_entry_guards, node); - smartlist_split_string(args, line->value, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(args)<2) { - *msg = tor_strdup("Unable to parse entry nodes: " - "Too few arguments to EntryGuard"); - } else if (!is_legal_nickname(smartlist_get(args,0))) { - *msg = tor_strdup("Unable to parse entry nodes: " - "Bad nickname for EntryGuard"); - } else { - strlcpy(node->nickname, smartlist_get(args,0), MAX_NICKNAME_LEN+1); - if (base16_decode(node->identity, DIGEST_LEN, smartlist_get(args,1), - strlen(smartlist_get(args,1)))<0) { - *msg = tor_strdup("Unable to parse entry nodes: " - "Bad hex digest for EntryGuard"); - } - } - SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); - smartlist_free(args); - if (*msg) - break; - } else if (!strcasecmp(line->key, "EntryGuardDownSince") || - !strcasecmp(line->key, "EntryGuardUnlistedSince")) { - time_t when; - time_t last_try = 0; - if (!node) { - *msg = tor_strdup("Unable to parse entry nodes: " - "EntryGuardDownSince/UnlistedSince without EntryGuard"); - break; - } - if (parse_iso_time(line->value, &when)<0) { - *msg = tor_strdup("Unable to parse entry nodes: " - "Bad time in EntryGuardDownSince/UnlistedSince"); - break; - } - if (when > now) { - /* It's a bad idea to believe info in the future: you can wind - * up with timeouts that aren't allowed to happen for years. */ - continue; - } - if (strlen(line->value) >= ISO_TIME_LEN+ISO_TIME_LEN+1) { - /* ignore failure */ - (void) parse_iso_time(line->value+ISO_TIME_LEN+1, &last_try); - } - if (!strcasecmp(line->key, "EntryGuardDownSince")) { - node->unreachable_since = when; - node->last_attempted = last_try; - } else { - node->bad_since = when; - } - } else if (!strcasecmp(line->key, "EntryGuardAddedBy")) { - char d[DIGEST_LEN]; - /* format is digest version date */ - if (strlen(line->value) < HEX_DIGEST_LEN+1+1+1+ISO_TIME_LEN) { - log_warn(LD_BUG, "EntryGuardAddedBy line is not long enough."); - continue; - } - if (base16_decode(d, sizeof(d), line->value, HEX_DIGEST_LEN)<0 || - line->value[HEX_DIGEST_LEN] != ' ') { - log_warn(LD_BUG, "EntryGuardAddedBy line %s does not begin with " - "hex digest", escaped(line->value)); - continue; - } - digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1)); - } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { - const or_options_t *options = get_options(); - unsigned hop_cnt, success_cnt; - - if (!node) { - *msg = tor_strdup("Unable to parse entry nodes: " - "EntryGuardPathBias without EntryGuard"); - break; - } - - if (tor_sscanf(line->value, "%u %u", &success_cnt, &hop_cnt) != 2) { - log_warn(LD_GENERAL, "Unable to parse guard path bias info: " - "Misformated EntryGuardPathBias %s", escaped(line->value)); - continue; - } - - node->first_hops = hop_cnt; - node->circuit_successes = success_cnt; - log_info(LD_GENERAL, "Read %u/%u path bias for node %s", - node->circuit_successes, node->first_hops, node->nickname); - /* Note: We rely on the < comparison here to allow us to set a 0 - * rate and disable the feature entirely. If refactoring, don't - * change to <= */ - if (node->circuit_successes/((double)node->first_hops) - < pathbias_get_disable_rate(options)) { - node->path_bias_disabled = 1; - log_info(LD_GENERAL, - "Path bias is too high (%u/%u); disabling node %s", - node->circuit_successes, node->first_hops, node->nickname); - } - - } else { - log_warn(LD_BUG, "Unexpected key %s", line->key); - } - } - - SMARTLIST_FOREACH_BEGIN(new_entry_guards, entry_guard_t *, e) { - char *sp; - char *val = digestmap_get(added_by, e->identity); - if (val && (sp = strchr(val, ' '))) { - time_t when; - *sp++ = '\0'; - if (parse_iso_time(sp, &when)<0) { - log_warn(LD_BUG, "Can't read time %s in EntryGuardAddedBy", sp); - } else { - e->chosen_by_version = tor_strdup(val); - e->chosen_on_date = when; - } - } else { - if (state_version) { - e->chosen_by_version = tor_strdup(state_version); - e->chosen_on_date = time(NULL) - crypto_rand_int(3600*24*30); - } - } - if (e->path_bias_disabled && !e->bad_since) - e->bad_since = time(NULL); - } - SMARTLIST_FOREACH_END(e); - - if (*msg || !set) { - SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e, - entry_guard_free(e)); - smartlist_free(new_entry_guards); - } else { /* !err && set */ - if (entry_guards) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, - entry_guard_free(e)); - smartlist_free(entry_guards); - } - entry_guards = new_entry_guards; - entry_guards_dirty = 0; - /* XXX024 hand new_entry_guards to this func, and move it up a - * few lines, so we don't have to re-dirty it */ - if (remove_obsolete_entry_guards(now)) - entry_guards_dirty = 1; - } - digestmap_free(added_by, _tor_free); - return *msg ? -1 : 0; -} - -/** Our list of entry guards has changed, or some element of one - * of our entry guards has changed. Write the changes to disk within - * the next few minutes. - */ -static void -entry_guards_changed(void) -{ - time_t when; - entry_guards_dirty = 1; - - /* or_state_save() will call entry_guards_update_state(). */ - when = get_options()->AvoidDiskWrites ? time(NULL) + 3600 : time(NULL)+600; - or_state_mark_dirty(get_or_state(), when); -} - -/** If the entry guard info has not changed, do nothing and return. - * Otherwise, free the EntryGuards piece of <b>state</b> and create - * a new one out of the global entry_guards list, and then mark - * <b>state</b> dirty so it will get saved to disk. - */ -void -entry_guards_update_state(or_state_t *state) -{ - config_line_t **next, *line; - if (! entry_guards_dirty) - return; - - config_free_lines(state->EntryGuards); - next = &state->EntryGuards; - *next = NULL; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - char dbuf[HEX_DIGEST_LEN+1]; - if (!e->made_contact) - continue; /* don't write this one to disk */ - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("EntryGuard"); - base16_encode(dbuf, sizeof(dbuf), e->identity, DIGEST_LEN); - tor_asprintf(&line->value, "%s %s", e->nickname, dbuf); - next = &(line->next); - if (e->unreachable_since) { - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("EntryGuardDownSince"); - line->value = tor_malloc(ISO_TIME_LEN+1+ISO_TIME_LEN+1); - format_iso_time(line->value, e->unreachable_since); - if (e->last_attempted) { - line->value[ISO_TIME_LEN] = ' '; - format_iso_time(line->value+ISO_TIME_LEN+1, e->last_attempted); - } - next = &(line->next); - } - if (e->bad_since) { - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("EntryGuardUnlistedSince"); - line->value = tor_malloc(ISO_TIME_LEN+1); - format_iso_time(line->value, e->bad_since); - next = &(line->next); - } - if (e->chosen_on_date && e->chosen_by_version && - !strchr(e->chosen_by_version, ' ')) { - char d[HEX_DIGEST_LEN+1]; - char t[ISO_TIME_LEN+1]; - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("EntryGuardAddedBy"); - base16_encode(d, sizeof(d), e->identity, DIGEST_LEN); - format_iso_time(t, e->chosen_on_date); - tor_asprintf(&line->value, "%s %s %s", - d, e->chosen_by_version, t); - next = &(line->next); - } - if (e->first_hops) { - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("EntryGuardPathBias"); - tor_asprintf(&line->value, "%u %u", - e->circuit_successes, e->first_hops); - next = &(line->next); - } - - } SMARTLIST_FOREACH_END(e); - if (!get_options()->AvoidDiskWrites) - or_state_mark_dirty(get_or_state(), 0); - entry_guards_dirty = 0; -} - -/** If <b>question</b> is the string "entry-guards", then dump - * to *<b>answer</b> a newly allocated string describing all of - * the nodes in the global entry_guards list. See control-spec.txt - * for details. - * For backward compatibility, we also handle the string "helper-nodes". - * */ -int -getinfo_helper_entry_guards(control_connection_t *conn, - const char *question, char **answer, - const char **errmsg) -{ - (void) conn; - (void) errmsg; - - if (!strcmp(question,"entry-guards") || - !strcmp(question,"helper-nodes")) { - smartlist_t *sl = smartlist_new(); - char tbuf[ISO_TIME_LEN+1]; - char nbuf[MAX_VERBOSE_NICKNAME_LEN+1]; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - const char *status = NULL; - time_t when = 0; - const node_t *node; - - if (!e->made_contact) { - status = "never-connected"; - } else if (e->bad_since) { - when = e->bad_since; - status = "unusable"; - } else { - status = "up"; - } - - node = node_get_by_id(e->identity); - if (node) { - node_get_verbose_nickname(node, nbuf); - } else { - nbuf[0] = '$'; - base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN); - /* e->nickname field is not very reliable if we don't know about - * this router any longer; don't include it. */ - } - - if (when) { - format_iso_time(tbuf, when); - smartlist_add_asprintf(sl, "%s %s %s\n", nbuf, status, tbuf); - } else { - smartlist_add_asprintf(sl, "%s %s\n", nbuf, status); - } - } SMARTLIST_FOREACH_END(e); - *answer = smartlist_join_strings(sl, "", 0, NULL); - SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); - smartlist_free(sl); - } - return 0; -} - -/** A list of configured bridges. Whenever we actually get a descriptor - * for one, we add it as an entry guard. Note that the order of bridges - * in this list does not necessarily correspond to the order of bridges - * in the torrc. */ -static smartlist_t *bridge_list = NULL; - -/** Mark every entry of the bridge list to be removed on our next call to - * sweep_bridge_list unless it has first been un-marked. */ -void -mark_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, - b->marked_for_removal = 1); -} - -/** Remove every entry of the bridge list that was marked with - * mark_bridge_list if it has not subsequently been un-marked. */ -void -sweep_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { - if (b->marked_for_removal) { - SMARTLIST_DEL_CURRENT(bridge_list, b); - bridge_free(b); - } - } SMARTLIST_FOREACH_END(b); -} - -/** Initialize the bridge list to empty, creating it if needed. */ -static void -clear_bridge_list(void) -{ - if (!bridge_list) - bridge_list = smartlist_new(); - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b)); - smartlist_clear(bridge_list); -} - -/** Free the bridge <b>bridge</b>. */ -static void -bridge_free(bridge_info_t *bridge) -{ - if (!bridge) - return; - - tor_free(bridge->transport_name); - 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) -{ - 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; -} - -/** 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 */ - char *new_transport_addr = tor_strdup(fmt_addr(&t->addr)); - 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, new_transport_addr, t->port, - fmt_addr(&t_tmp->addr), t_tmp->port); - smartlist_remove(transport_list, t_tmp); - transport_free(t_tmp); - tor_free(new_transport_addr); - } 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, new_transport_addr, t->port, - fmt_addr(&t_tmp->addr), t_tmp->port); - tor_free(new_transport_addr); - 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. */ -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; - } -} - -/** 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. */ -static bridge_info_t * -get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, - uint16_t port, - const char *digest) -{ - if (!bridge_list) - return NULL; - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) - { - if (tor_digest_is_zero(bridge->identity) && - !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && - bridge->port == port) - return bridge; - if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) - return bridge; - } - SMARTLIST_FOREACH_END(bridge); - return NULL; -} - -/** Wrapper around get_configured_bridge_by_addr_port_digest() to look - * it up via router descriptor <b>ri</b>. */ -static bridge_info_t * -get_configured_bridge_by_routerinfo(const routerinfo_t *ri) -{ - 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); -} - -/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */ -int -routerinfo_is_a_configured_bridge(const routerinfo_t *ri) -{ - return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; -} - -/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */ -int -node_is_a_configured_bridge(const node_t *node) -{ - int retval = 0; /* 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; - } - return retval; -} - -/** We made a connection to a router at <b>addr</b>:<b>port</b> - * without knowing its digest. Its digest turned out to be <b>digest</b>. - * If it was a bridge, and we still don't know its digest, record it. - */ -void -learned_router_identity(const tor_addr_t *addr, uint16_t port, - const char *digest) -{ - bridge_info_t *bridge = - get_configured_bridge_by_addr_port_digest(addr, port, digest); - if (bridge && tor_digest_is_zero(bridge->identity)) { - memcpy(bridge->identity, digest, DIGEST_LEN); - log_notice(LD_DIR, "Learned fingerprint %s for bridge %s:%d", - hex_str(digest, DIGEST_LEN), fmt_addr(addr), port); - } -} - -/** Return true if <b>bridge</b> has the same identity digest as - * <b>digest</b>. If <b>digest</b> is NULL, it matches - * bridges with unspecified identity digests. */ -static int -bridge_has_digest(const bridge_info_t *bridge, const char *digest) -{ - if (digest) - return tor_memeq(digest, bridge->identity, DIGEST_LEN); - else - return tor_digest_is_zero(bridge->identity); -} - -/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional - * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously - * existing bridge with the same address and port, and warn the user as - * appropriate. - */ -static void -bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, - const char *digest, const char *transport_name) -{ - /* Iterate the already-registered bridge list: - - If you find a bridge with the same adress and port, mark it for - removal. It doesn't make sense to have two active bridges with - the same IP:PORT. If the bridge in question has a different - digest or transport than <b>digest</b>/<b>transport_name</b>, - it's probably a misconfiguration and we should warn the user. - */ - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - if (bridge->marked_for_removal) - continue; - - if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { - - bridge->marked_for_removal = 1; - - if (!bridge_has_digest(bridge, digest) || - strcmp_opt(bridge->transport_name, transport_name)) { - /* warn the user */ - char *bridge_description_new, *bridge_description_old; - tor_asprintf(&bridge_description_new, "%s:%u:%s:%s", - fmt_addr(addr), port, - digest ? hex_str(digest, DIGEST_LEN) : "", - transport_name ? transport_name : ""); - tor_asprintf(&bridge_description_old, "%s:%u:%s:%s", - fmt_addr(&bridge->addr), bridge->port, - tor_digest_is_zero(bridge->identity) ? - "" : hex_str(bridge->identity,DIGEST_LEN), - bridge->transport_name ? bridge->transport_name : ""); - - log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict" - " with the already registered bridge '%s'. We will discard" - " the old bridge and keep '%s'. If this is not what you" - " wanted, please change your configuration file accordingly.", - bridge_description_new, bridge_description_old, - bridge_description_new); - - tor_free(bridge_description_new); - tor_free(bridge_description_old); - } - } - } SMARTLIST_FOREACH_END(bridge); -} - -/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b> - * is set, it tells us the identity key too. If we already had the - * bridge in our list, unmark it, and don't actually add anything new. - * If <b>transport_name</b> is non-NULL - the bridge is associated with a - * pluggable transport - we assign the transport to the bridge. */ -void -bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, const char *transport_name) -{ - bridge_info_t *b; - - bridge_resolve_conflicts(addr, port, digest, transport_name); - - b = tor_malloc_zero(sizeof(bridge_info_t)); - tor_addr_copy(&b->addr, addr); - b->port = port; - if (digest) - memcpy(b->identity, digest, DIGEST_LEN); - if (transport_name) - b->transport_name = tor_strdup(transport_name); - b->fetch_status.schedule = DL_SCHED_BRIDGE; - if (!bridge_list) - bridge_list = smartlist_new(); - - smartlist_add(bridge_list, b); -} - -/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */ -static int -routerset_contains_bridge(const routerset_t *routerset, - const bridge_info_t *bridge) -{ - int result; - extend_info_t *extinfo; - tor_assert(bridge); - if (!routerset) - return 0; - - extinfo = extend_info_alloc( - NULL, bridge->identity, NULL, &bridge->addr, bridge->port); - result = routerset_contains_extendinfo(routerset, extinfo); - extend_info_free(extinfo); - return result; -} - -/** If <b>digest</b> is one of our known bridges, return it. */ -static bridge_info_t * -find_bridge_by_digest(const char *digest) -{ - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, - { - if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) - return bridge; - }); - return NULL; -} - -/* DOCDOC find_transport_name_by_bridge_addrport */ -const char * -find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) -{ - if (!bridge_list) - return NULL; - - SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { - if (tor_addr_eq(&bridge->addr, addr) && - (bridge->port == port)) - return bridge->transport_name; - } SMARTLIST_FOREACH_END(bridge); - - return NULL; -} - -/** If <b>addr</b> and <b>port</b> match the address and port of a - * bridge of ours that uses pluggable transports, place its transport - * in <b>transport</b>. - * - * Return 0 on success (found a transport, or found a bridge with no - * transport, or found no bridge); return -1 if we should be using a - * transport, but the transport could not be found. - */ -int -find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, - const transport_t **transport) -{ - *transport = NULL; - if (!bridge_list) - return 0; - - SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { - if (tor_addr_eq(&bridge->addr, addr) && - (bridge->port == port)) { /* bridge matched */ - if (bridge->transport_name) { /* it also uses pluggable transports */ - *transport = transport_get_by_name(bridge->transport_name); - if (*transport == NULL) { /* it uses pluggable transports, but - the transport could not be found! */ - return -1; - } - return 0; - } else { /* bridge matched, but it doesn't use transports. */ - break; - } - } - } SMARTLIST_FOREACH_END(bridge); - - *transport = NULL; - return 0; -} - -/** We need to ask <b>bridge</b> for its server descriptor. */ -static void -launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) -{ - char *address; - const or_options_t *options = get_options(); - - if (connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &bridge->addr, bridge->port, - DIR_PURPOSE_FETCH_SERVERDESC)) - return; /* it's already on the way */ - - if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { - download_status_mark_impossible(&bridge->fetch_status); - log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", - safe_str_client(fmt_addr(&bridge->addr))); - return; - } - - address = tor_dup_addr(&bridge->addr); - - directory_initiate_command(address, &bridge->addr, - bridge->port, 0, - 0, /* does not matter */ - 1, bridge->identity, - DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, - 0, "authority.z", NULL, 0, 0); - tor_free(address); -} - -/** Fetching the bridge descriptor from the bridge authority returned a - * "not found". Fall back to trying a direct fetch. */ -void -retry_bridge_descriptor_fetch_directly(const char *digest) -{ - bridge_info_t *bridge = find_bridge_by_digest(digest); - if (!bridge) - return; /* not found? oh well. */ - - launch_direct_bridge_descriptor_fetch(bridge); -} - -/** For each bridge in our list for which we don't currently have a - * descriptor, fetch a new copy of its descriptor -- either directly - * from the bridge or via a bridge authority. */ -void -fetch_bridge_descriptors(const or_options_t *options, time_t now) -{ - int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO); - int ask_bridge_directly; - int can_use_bridge_authority; - - if (!bridge_list) - return; - - /* If we still have unconfigured managed proxies, don't go and - connect to a bridge. */ - if (pt_proxies_configuration_pending()) - return; - - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) - { - if (!download_status_is_ready(&bridge->fetch_status, now, - IMPOSSIBLE_TO_DOWNLOAD)) - continue; /* don't bother, no need to retry yet */ - if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { - download_status_mark_impossible(&bridge->fetch_status); - log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", - safe_str_client(fmt_addr(&bridge->addr))); - continue; - } - - /* schedule another fetch as if this one will fail, in case it does */ - download_status_failed(&bridge->fetch_status, 0); - - can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && - num_bridge_auths; - ask_bridge_directly = !can_use_bridge_authority || - !options->UpdateBridgesFromAuthority; - log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)", - ask_bridge_directly, tor_digest_is_zero(bridge->identity), - !options->UpdateBridgesFromAuthority, !num_bridge_auths); - - if (ask_bridge_directly && - !fascist_firewall_allows_address_or(&bridge->addr, bridge->port)) { - log_notice(LD_DIR, "Bridge at '%s:%d' isn't reachable by our " - "firewall policy. %s.", fmt_addr(&bridge->addr), - bridge->port, - can_use_bridge_authority ? - "Asking bridge authority instead" : "Skipping"); - if (can_use_bridge_authority) - ask_bridge_directly = 0; - else - continue; - } - - if (ask_bridge_directly) { - /* we need to ask the bridge itself for its descriptor. */ - launch_direct_bridge_descriptor_fetch(bridge); - } else { - /* We have a digest and we want to ask an authority. We could - * combine all the requests into one, but that may give more - * hints to the bridge authority than we want to give. */ - char resource[10 + HEX_DIGEST_LEN]; - memcpy(resource, "fp/", 3); - base16_encode(resource+3, HEX_DIGEST_LEN+1, - bridge->identity, DIGEST_LEN); - memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3); - log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", - resource); - directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, resource, 0); - } - } - SMARTLIST_FOREACH_END(bridge); -} - -/** If our <b>bridge</b> is configured to be a different address than - * the bridge gives in <b>node</b>, rewrite the routerinfo - * we received to use the address we meant to use. Now we handle - * multihomed bridges better. - */ -static void -rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) -{ - /* XXXX move this function. */ - /* XXXX overridden addresses should really live in the node_t, so that the - * routerinfo_t and the microdesc_t can be immutable. But we can only - * do that safely if we know that no function that connects to an OR - * does so through an address from any source other than node_get_addr(). - */ - tor_addr_t addr; - - if (node->ri) { - routerinfo_t *ri = node->ri; - tor_addr_from_ipv4h(&addr, ri->addr); - - if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && - bridge->port == ri->or_port) || - (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) && - bridge->port == ri->ipv6_orport)) { - /* they match, so no need to do anything */ - } else { - if (tor_addr_family(&bridge->addr) == AF_INET) { - ri->addr = tor_addr_to_ipv4h(&bridge->addr); - tor_free(ri->address); - ri->address = tor_dup_ip(ri->addr); - ri->or_port = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerinfo for '%s' to match configured " - "address %s:%d.", - ri->nickname, ri->address, ri->or_port); - } else if (tor_addr_family(&bridge->addr) == AF_INET6) { - tor_addr_copy(&ri->ipv6_addr, &bridge->addr); - ri->ipv6_orport = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerinfo for '%s' to match configured " - "address %s:%d.", - ri->nickname, fmt_addr(&ri->ipv6_addr), ri->ipv6_orport); - } else { - log_err(LD_BUG, "Address family not supported: %d.", - tor_addr_family(&bridge->addr)); - return; - } - } - - /* 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; - - /* 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); - 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", - fmt_addr(&ap.addr), ap.port); - } - } - if (node->rs) { - routerstatus_t *rs = node->rs; - tor_addr_from_ipv4h(&addr, rs->addr); - - if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && - bridge->port == rs->or_port) { - /* they match, so no need to do anything */ - } else { - rs->addr = tor_addr_to_ipv4h(&bridge->addr); - rs->or_port = bridge->port; - log_info(LD_DIR, - "Adjusted bridge routerstatus for '%s' to match " - "configured address %s:%d.", - rs->nickname, fmt_addr(&bridge->addr), rs->or_port); - } - } -} - -/** We just learned a descriptor for a bridge. See if that - * digest is in our entry guard list, and add it if not. */ -void -learned_bridge_descriptor(routerinfo_t *ri, int from_cache) -{ - tor_assert(ri); - tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); - if (get_options()->UseBridges) { - int first = !any_bridge_descriptors_known(); - bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); - time_t now = time(NULL); - router_set_status(ri->cache_info.identity_digest, 1); - - if (bridge) { /* if we actually want to use this one */ - node_t *node; - /* it's here; schedule its re-fetch for a long time from now. */ - if (!from_cache) - download_status_reset(&bridge->fetch_status); - - node = node_get_mutable_by_id(ri->cache_info.identity_digest); - tor_assert(node); - rewrite_node_address_for_bridge(bridge, node); - add_an_entry_guard(node, 1, 1); - - log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, - from_cache ? "cached" : "fresh", router_describe(ri)); - /* set entry->made_contact so if it goes down we don't drop it from - * our entry node list */ - entry_guard_register_connect_status(ri->cache_info.identity_digest, - 1, 0, now); - if (first) - routerlist_retry_directory_downloads(now); - } - } -} - -/** Return 1 if any of our entry guards have descriptors that - * are marked with purpose 'bridge' and are running. Else return 0. - * - * We use this function to decide if we're ready to start building - * circuits through our bridges, or if we need to wait until the - * directory "server/authority" requests finish. */ -int -any_bridge_descriptors_known(void) -{ - tor_assert(get_options()->UseBridges); - return choose_random_entry(NULL)!=NULL ? 1 : 0; -} - -/** Return 1 if there are any directory conns fetching bridge descriptors - * that aren't marked for close. We use this to guess if we should tell - * the controller that we have a problem. */ -int -any_pending_bridge_descriptor_fetches(void) -{ - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn->type == CONN_TYPE_DIR && - conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC && - TO_DIR_CONN(conn)->router_purpose == ROUTER_PURPOSE_BRIDGE && - !conn->marked_for_close && - conn->linked && - conn->linked_conn && !conn->linked_conn->marked_for_close) { - log_debug(LD_DIR, "found one: %s", conn->address); - return 1; - } - } SMARTLIST_FOREACH_END(conn); - return 0; -} - -/** Return 1 if we have at least one descriptor for an entry guard - * (bridge or member of EntryNodes) and all descriptors we know are - * down. Else return 0. If <b>act</b> is 1, then mark the down guards - * up; else just observe and report. */ -static int -entries_retry_helper(const or_options_t *options, int act) -{ - const node_t *node; - int any_known = 0; - int any_running = 0; - int need_bridges = options->UseBridges != 0; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - node = node_get_by_id(e->identity); - if (node && node_has_descriptor(node) && - node_is_bridge(node) == need_bridges) { - any_known = 1; - if (node->is_running) - any_running = 1; /* some entry is both known and running */ - else if (act) { - /* Mark all current connections to this OR as unhealthy, since - * otherwise there could be one that started 30 seconds - * ago, and in 30 seconds it will time out, causing us to mark - * the node down and undermine the retry attempt. We mark even - * the established conns, since if the network just came back - * we'll want to attach circuits to fresh conns. */ - connection_or_set_bad_connections(node->identity, 1); - - /* mark this entry node for retry */ - router_set_status(node->identity, 1); - e->can_retry = 1; - e->bad_since = 0; - } - } - } SMARTLIST_FOREACH_END(e); - log_debug(LD_DIR, "%d: any_known %d, any_running %d", - act, any_known, any_running); - return any_known && !any_running; -} - -/** Do we know any descriptors for our bridges / entrynodes, and are - * all the ones we have descriptors for down? */ -int -entries_known_but_down(const or_options_t *options) -{ - tor_assert(entry_list_is_constrained(options)); - return entries_retry_helper(options, 0); -} - -/** Mark all down known bridges / entrynodes up. */ -void -entries_retry_all(const or_options_t *options) -{ - tor_assert(entry_list_is_constrained(options)); - entries_retry_helper(options, 1); -} - -/** Return true if we've ever had a bridge running a Tor version that can't - * provide microdescriptors to us. In that case fall back to asking for - * full descriptors. Eventually all bridges will support microdescriptors - * and we can take this check out; see bug 4013. */ -int -any_bridges_dont_support_microdescriptors(void) -{ - const node_t *node; - static int ever_answered_yes = 0; - if (!get_options()->UseBridges || !entry_guards) - return 0; - if (ever_answered_yes) - return 1; /* if we ever answer 'yes', always answer 'yes' */ - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - node = node_get_by_id(e->identity); - if (node && node->ri && - node_is_bridge(node) && node_is_a_configured_bridge(node) && - !tor_version_supports_microdescriptors(node->ri->platform)) { - /* This is one of our current bridges, and we know enough about - * it to know that it won't be able to answer our microdescriptor - * questions. */ - ever_answered_yes = 1; - return 1; - } - } SMARTLIST_FOREACH_END(e); - return 0; -} - -/** Release all storage held by the list of entry guards and related - * memory structs. */ -void -entry_guards_free_all(void) -{ - if (entry_guards) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, - entry_guard_free(e)); - smartlist_free(entry_guards); - entry_guards = NULL; - } - 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 984d04a99..8d2b986b7 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -9,23 +9,8 @@ * \brief Header file for circuitbuild.c. **/ -#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; +#ifndef TOR_CIRCUITBUILD_H +#define TOR_CIRCUITBUILD_H char *circuit_list_path(origin_circuit_t *circ, int verbose); char *circuit_list_path_for_controller(origin_circuit_t *circ); @@ -37,7 +22,7 @@ origin_circuit_t *circuit_establish_circuit(uint8_t purpose, extend_info_t *exit, int flags); int circuit_handle_first_hop(origin_circuit_t *circ); -void circuit_n_conn_done(or_connection_t *or_conn, int status); +void circuit_n_chan_done(channel_t *chan, int status); int inform_testing_reachability(void); int circuit_timeout_want_to_count_circ(origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); @@ -45,127 +30,38 @@ void circuit_note_clock_jumped(int seconds_elapsed); int circuit_extend(cell_t *cell, circuit_t *circ); int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, int reverse); -int circuit_finish_handshake(origin_circuit_t *circ, uint8_t cell_type, - const uint8_t *reply); -int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer); -int onionskin_answer(or_circuit_t *circ, uint8_t cell_type, - const char *payload, const char *keys); +struct created_cell_t; +int circuit_finish_handshake(origin_circuit_t *circ, + const struct created_cell_t *created_cell); +int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, + int reason); +int onionskin_answer(or_circuit_t *circ, + const struct created_cell_t *created_cell, + const char *keys, + const uint8_t *rend_circ_nonce); int circuit_all_predicted_ports_handled(time_t now, int *need_uptime, int *need_capacity); int circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *info); int circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *info); void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop); -extend_info_t *extend_info_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_new(const char *nickname, const char *digest, + crypto_pk_t *onion_key, + const curve25519_public_key_t *curve25519_key, + const tor_addr_t *addr, uint16_t port); +extend_info_t *extend_info_from_node(const node_t *r, int for_direct_connect); extend_info_t *extend_info_dup(extend_info_t *info); void extend_info_free(extend_info_t *info); const node_t *build_state_get_exit_node(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); -void entry_guards_compute_status(const or_options_t *options, time_t now); -int entry_guard_register_connect_status(const char *digest, int succeeded, - int mark_relay_status, time_t now); -void entry_nodes_should_be_added(void); -int entry_list_is_constrained(const or_options_t *options); -const node_t *choose_random_entry(cpath_build_state_t *state); -int entry_guards_parse_state(or_state_t *state, int set, char **msg); -void entry_guards_update_state(or_state_t *state); -int getinfo_helper_entry_guards(control_connection_t *conn, - const char *question, char **answer, - const char **errmsg); - -void mark_bridge_list(void); -void sweep_bridge_list(void); -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); -void learned_router_identity(const tor_addr_t *addr, uint16_t port, - const char *digest); -void bridge_add_from_config(const tor_addr_t *addr, uint16_t port, - const char *digest, - const char *transport_name); -void retry_bridge_descriptor_fetch_directly(const char *digest); -void fetch_bridge_descriptors(const or_options_t *options, time_t now); -void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); -int any_bridge_descriptors_known(void); -int any_pending_bridge_descriptor_fetches(void); -int entries_known_but_down(const or_options_t *options); -void entries_retry_all(const or_options_t *options); - -int any_bridges_dont_support_microdescriptors(void); - -void entry_guards_free_all(void); - -extern circuit_build_times_t circ_times; -int circuit_build_times_enough_to_compute(circuit_build_times_t *cbt); -void circuit_build_times_update_state(circuit_build_times_t *cbt, - or_state_t *state); -int circuit_build_times_parse_state(circuit_build_times_t *cbt, - or_state_t *state); -void circuit_build_times_count_timeout(circuit_build_times_t *cbt, - int did_onehop); -int circuit_build_times_count_close(circuit_build_times_t *cbt, - int did_onehop, time_t start_time); -void circuit_build_times_set_timeout(circuit_build_times_t *cbt); -int circuit_build_times_add_time(circuit_build_times_t *cbt, - build_time_t time); -int circuit_build_times_needs_circuits(circuit_build_times_t *cbt); - -int circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt); -void circuit_build_times_init(circuit_build_times_t *cbt); -void circuit_build_times_free_timeouts(circuit_build_times_t *cbt); -void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, - networkstatus_t *ns); -double circuit_build_times_timeout_rate(const circuit_build_times_t *cbt); -double circuit_build_times_close_rate(const circuit_build_times_t *cbt); - -#ifdef CIRCUIT_PRIVATE -double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, - double quantile); -build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, - double q_lo, double q_hi); -void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, - double quantile, double time_ms); -int circuit_build_times_update_alpha(circuit_build_times_t *cbt); -double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); -void circuitbuild_running_unit_tests(void); -void circuit_build_times_reset(circuit_build_times_t *cbt); - -/* Network liveness functions */ -int circuit_build_times_network_check_changed(circuit_build_times_t *cbt); -#endif - -/* Network liveness functions */ -void circuit_build_times_network_is_live(circuit_build_times_t *cbt); -int circuit_build_times_network_check_live(circuit_build_times_t *cbt); -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); - -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 node_t *choose_good_entry_server(uint8_t purpose, + cpath_build_state_t *state); +double pathbias_get_extreme_rate(const or_options_t *options); +int pathbias_get_dropguards(const or_options_t *options); +void pathbias_count_timeout(origin_circuit_t *circ); +int pathbias_check_close(origin_circuit_t *circ, int reason); +int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell); #endif diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 93ba69dcf..90719d2b6 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -10,9 +10,11 @@ **/ #include "or.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" +#include "circuitstats.h" #include "connection.h" #include "config.h" #include "connection_edge.h" @@ -21,11 +23,13 @@ #include "networkstatus.h" #include "nodelist.h" #include "onion.h" +#include "onion_fast.h" #include "relay.h" #include "rendclient.h" #include "rendcommon.h" #include "rephist.h" #include "routerlist.h" +#include "routerset.h" #include "ht.h" /********* START VARIABLES **********/ @@ -33,8 +37,8 @@ /** A global list of all circuits at this hop. */ circuit_t *global_circuitlist=NULL; -/** A list of all the circuits in CIRCUIT_STATE_OR_WAIT. */ -static smartlist_t *circuits_pending_or_conns=NULL; +/** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ +static smartlist_t *circuits_pending_chans = NULL; static void circuit_free(circuit_t *circ); static void circuit_free_cpath(crypt_path_t *cpath); @@ -43,154 +47,190 @@ static void cpath_ref_decref(crypt_path_reference_t *cpath_ref); /********* END VARIABLES ************/ -/** A map from OR connection and circuit ID to circuit. (Lookup performance is +/** A map from channel and circuit ID to circuit. (Lookup performance is * very important here, since we need to do it every time a cell arrives.) */ -typedef struct orconn_circid_circuit_map_t { - HT_ENTRY(orconn_circid_circuit_map_t) node; - or_connection_t *or_conn; +typedef struct chan_circid_circuit_map_t { + HT_ENTRY(chan_circid_circuit_map_t) node; + channel_t *chan; circid_t circ_id; circuit_t *circuit; -} orconn_circid_circuit_map_t; +} chan_circid_circuit_map_t; -/** Helper for hash tables: compare the OR connection and circuit ID for a and +/** Helper for hash tables: compare the channel and circuit ID for a and * b, and return less than, equal to, or greater than zero appropriately. */ static INLINE int -_orconn_circid_entries_eq(orconn_circid_circuit_map_t *a, - orconn_circid_circuit_map_t *b) +chan_circid_entries_eq_(chan_circid_circuit_map_t *a, + chan_circid_circuit_map_t *b) { - return a->or_conn == b->or_conn && a->circ_id == b->circ_id; + return a->chan == b->chan && a->circ_id == b->circ_id; } /** Helper: return a hash based on circuit ID and the pointer value of - * or_conn in <b>a</b>. */ + * chan in <b>a</b>. */ static INLINE unsigned int -_orconn_circid_entry_hash(orconn_circid_circuit_map_t *a) +chan_circid_entry_hash_(chan_circid_circuit_map_t *a) { - return (((unsigned)a->circ_id)<<8) ^ (unsigned)(uintptr_t)(a->or_conn); + return (((unsigned)a->circ_id)<<8) ^ (unsigned)(uintptr_t)(a->chan); } -/** Map from [orconn,circid] to circuit. */ -static HT_HEAD(orconn_circid_map, orconn_circid_circuit_map_t) - orconn_circid_circuit_map = HT_INITIALIZER(); -HT_PROTOTYPE(orconn_circid_map, orconn_circid_circuit_map_t, node, - _orconn_circid_entry_hash, _orconn_circid_entries_eq) -HT_GENERATE(orconn_circid_map, orconn_circid_circuit_map_t, node, - _orconn_circid_entry_hash, _orconn_circid_entries_eq, 0.6, +/** Map from [chan,circid] to circuit. */ +static HT_HEAD(chan_circid_map, chan_circid_circuit_map_t) + chan_circid_map = HT_INITIALIZER(); +HT_PROTOTYPE(chan_circid_map, chan_circid_circuit_map_t, node, + chan_circid_entry_hash_, chan_circid_entries_eq_) +HT_GENERATE(chan_circid_map, chan_circid_circuit_map_t, node, + chan_circid_entry_hash_, chan_circid_entries_eq_, 0.6, malloc, realloc, free) -/** The most recently returned entry from circuit_get_by_circid_orconn; +/** The most recently returned entry from circuit_get_by_circid_chan; * used to improve performance when many cells arrive in a row from the * same circuit. */ -orconn_circid_circuit_map_t *_last_circid_orconn_ent = NULL; +chan_circid_circuit_map_t *_last_circid_chan_ent = NULL; -/** Implementation helper for circuit_set_{p,n}_circid_orconn: A circuit ID - * and/or or_connection for circ has just changed from <b>old_conn, old_id</b> - * to <b>conn, id</b>. Adjust the conn,circid map as appropriate, removing +/** Implementation helper for circuit_set_{p,n}_circid_channel: A circuit ID + * and/or channel for circ has just changed from <b>old_chan, old_id</b> + * to <b>chan, id</b>. Adjust the chan,circid map as appropriate, removing * the old entry (if any) and adding a new one. */ static void -circuit_set_circid_orconn_helper(circuit_t *circ, int direction, - circid_t id, - or_connection_t *conn) +circuit_set_circid_chan_helper(circuit_t *circ, int direction, + circid_t id, + channel_t *chan) { - orconn_circid_circuit_map_t search; - orconn_circid_circuit_map_t *found; - or_connection_t *old_conn, **conn_ptr; + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *found; + channel_t *old_chan, **chan_ptr; circid_t old_id, *circid_ptr; - int was_active, make_active; + int make_active, attached = 0; if (direction == CELL_DIRECTION_OUT) { - conn_ptr = &circ->n_conn; + chan_ptr = &circ->n_chan; circid_ptr = &circ->n_circ_id; - was_active = circ->next_active_on_n_conn != NULL; - make_active = circ->n_conn_cells.n > 0; + make_active = circ->n_chan_cells.n > 0; } else { or_circuit_t *c = TO_OR_CIRCUIT(circ); - conn_ptr = &c->p_conn; + chan_ptr = &c->p_chan; circid_ptr = &c->p_circ_id; - was_active = c->next_active_on_p_conn != NULL; - make_active = c->p_conn_cells.n > 0; + make_active = c->p_chan_cells.n > 0; } - old_conn = *conn_ptr; + old_chan = *chan_ptr; old_id = *circid_ptr; - if (id == old_id && conn == old_conn) + if (id == old_id && chan == old_chan) return; - if (_last_circid_orconn_ent && - ((old_id == _last_circid_orconn_ent->circ_id && - old_conn == _last_circid_orconn_ent->or_conn) || - (id == _last_circid_orconn_ent->circ_id && - conn == _last_circid_orconn_ent->or_conn))) { - _last_circid_orconn_ent = NULL; + if (_last_circid_chan_ent && + ((old_id == _last_circid_chan_ent->circ_id && + old_chan == _last_circid_chan_ent->chan) || + (id == _last_circid_chan_ent->circ_id && + chan == _last_circid_chan_ent->chan))) { + _last_circid_chan_ent = NULL; } - if (old_conn) { /* we may need to remove it from the conn-circid map */ - tor_assert(old_conn->_base.magic == OR_CONNECTION_MAGIC); + if (old_chan) { + /* + * If we're changing channels or ID and had an old channel and a non + * zero old ID and weren't marked for close (i.e., we should have been + * attached), detach the circuit. ID changes require this because + * circuitmux hashes on (channel_id, circuit_id). + */ + if (old_id != 0 && (old_chan != chan || old_id != id) && + !(circ->marked_for_close)) { + tor_assert(old_chan->cmux); + circuitmux_detach_circuit(old_chan->cmux, circ); + } + + /* we may need to remove it from the conn-circid map */ search.circ_id = old_id; - search.or_conn = old_conn; - found = HT_REMOVE(orconn_circid_map, &orconn_circid_circuit_map, &search); + search.chan = old_chan; + found = HT_REMOVE(chan_circid_map, &chan_circid_map, &search); if (found) { tor_free(found); - --old_conn->n_circuits; + if (direction == CELL_DIRECTION_OUT) { + /* One fewer circuits use old_chan as n_chan */ + --(old_chan->num_n_circuits); + } else { + /* One fewer circuits use old_chan as p_chan */ + --(old_chan->num_p_circuits); + } } - if (was_active && old_conn != conn) - make_circuit_inactive_on_conn(circ,old_conn); } /* Change the values only after we have possibly made the circuit inactive - * on the previous conn. */ - *conn_ptr = conn; + * on the previous chan. */ + *chan_ptr = chan; *circid_ptr = id; - if (conn == NULL) + if (chan == NULL) return; /* now add the new one to the conn-circid map */ search.circ_id = id; - search.or_conn = conn; - found = HT_FIND(orconn_circid_map, &orconn_circid_circuit_map, &search); + search.chan = chan; + found = HT_FIND(chan_circid_map, &chan_circid_map, &search); if (found) { found->circuit = circ; } else { - found = tor_malloc_zero(sizeof(orconn_circid_circuit_map_t)); + found = tor_malloc_zero(sizeof(chan_circid_circuit_map_t)); found->circ_id = id; - found->or_conn = conn; + found->chan = chan; found->circuit = circ; - HT_INSERT(orconn_circid_map, &orconn_circid_circuit_map, found); + HT_INSERT(chan_circid_map, &chan_circid_map, found); + } + + /* + * Attach to the circuitmux if we're changing channels or IDs and + * have a new channel and ID to use and the circuit is not marked for + * close. + */ + if (chan && id != 0 && (old_chan != chan || old_id != id) && + !(circ->marked_for_close)) { + tor_assert(chan->cmux); + circuitmux_attach_circuit(chan->cmux, circ, direction); + attached = 1; } - if (make_active && old_conn != conn) - make_circuit_active_on_conn(circ,conn); - ++conn->n_circuits; + /* + * This is a no-op if we have no cells, but if we do it marks us active to + * the circuitmux + */ + if (make_active && attached) + update_circuit_on_cmux(circ, direction); + + /* Adjust circuit counts on new channel */ + if (direction == CELL_DIRECTION_OUT) { + ++chan->num_n_circuits; + } else { + ++chan->num_p_circuits; + } } /** Set the p_conn field of a circuit <b>circ</b>, along * with the corresponding circuit ID, and add the circuit as appropriate - * to the (orconn,id)-\>circuit map. */ + * to the (chan,id)-\>circuit map. */ void -circuit_set_p_circid_orconn(or_circuit_t *circ, circid_t id, - or_connection_t *conn) +circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, + channel_t *chan) { - circuit_set_circid_orconn_helper(TO_CIRCUIT(circ), CELL_DIRECTION_IN, - id, conn); + circuit_set_circid_chan_helper(TO_CIRCUIT(circ), CELL_DIRECTION_IN, + id, chan); - if (conn) - tor_assert(bool_eq(circ->p_conn_cells.n, circ->next_active_on_p_conn)); + if (chan) + tor_assert(bool_eq(circ->p_chan_cells.n, circ->next_active_on_p_chan)); } /** Set the n_conn field of a circuit <b>circ</b>, along * with the corresponding circuit ID, and add the circuit as appropriate - * to the (orconn,id)-\>circuit map. */ + * to the (chan,id)-\>circuit map. */ void -circuit_set_n_circid_orconn(circuit_t *circ, circid_t id, - or_connection_t *conn) +circuit_set_n_circid_chan(circuit_t *circ, circid_t id, + channel_t *chan) { - circuit_set_circid_orconn_helper(circ, CELL_DIRECTION_OUT, id, conn); + circuit_set_circid_chan_helper(circ, CELL_DIRECTION_OUT, id, chan); - if (conn) - tor_assert(bool_eq(circ->n_conn_cells.n, circ->next_active_on_n_conn)); + if (chan) + tor_assert(bool_eq(circ->n_chan_cells.n, circ->next_active_on_n_chan)); } /** Change the state of <b>circ</b> to <b>state</b>, adding it to or removing @@ -201,18 +241,18 @@ circuit_set_state(circuit_t *circ, uint8_t state) tor_assert(circ); if (state == circ->state) return; - if (!circuits_pending_or_conns) - circuits_pending_or_conns = smartlist_new(); - if (circ->state == CIRCUIT_STATE_OR_WAIT) { + if (!circuits_pending_chans) + circuits_pending_chans = smartlist_new(); + if (circ->state == CIRCUIT_STATE_CHAN_WAIT) { /* remove from waiting-circuit list. */ - smartlist_remove(circuits_pending_or_conns, circ); + smartlist_remove(circuits_pending_chans, circ); } - if (state == CIRCUIT_STATE_OR_WAIT) { + if (state == CIRCUIT_STATE_CHAN_WAIT) { /* add to waiting-circuit list. */ - smartlist_add(circuits_pending_or_conns, circ); + smartlist_add(circuits_pending_chans, circ); } if (state == CIRCUIT_STATE_OPEN) - tor_assert(!circ->n_conn_onionskin); + tor_assert(!circ->n_chan_create_cell); circ->state = state; } @@ -231,51 +271,53 @@ circuit_add(circuit_t *circ) } } -/** Append to <b>out</b> all circuits in state OR_WAIT waiting for +/** Append to <b>out</b> all circuits in state CHAN_WAIT waiting for * the given connection. */ void -circuit_get_all_pending_on_or_conn(smartlist_t *out, or_connection_t *or_conn) +circuit_get_all_pending_on_channel(smartlist_t *out, channel_t *chan) { tor_assert(out); - tor_assert(or_conn); + tor_assert(chan); - if (!circuits_pending_or_conns) + if (!circuits_pending_chans) return; - SMARTLIST_FOREACH_BEGIN(circuits_pending_or_conns, circuit_t *, circ) { + SMARTLIST_FOREACH_BEGIN(circuits_pending_chans, circuit_t *, circ) { if (circ->marked_for_close) continue; if (!circ->n_hop) continue; - tor_assert(circ->state == CIRCUIT_STATE_OR_WAIT); + tor_assert(circ->state == CIRCUIT_STATE_CHAN_WAIT); if (tor_digest_is_zero(circ->n_hop->identity_digest)) { /* Look at addr/port. This is an unkeyed connection. */ - if (!tor_addr_eq(&circ->n_hop->addr, &or_conn->_base.addr) || - circ->n_hop->port != or_conn->_base.port) + if (!channel_matches_extend_info(chan, circ->n_hop)) continue; } else { /* We expected a key. See if it's the right one. */ - if (tor_memneq(or_conn->identity_digest, - circ->n_hop->identity_digest, DIGEST_LEN)) + if (tor_memneq(chan->identity_digest, + circ->n_hop->identity_digest, DIGEST_LEN)) continue; } smartlist_add(out, circ); } SMARTLIST_FOREACH_END(circ); } -/** Return the number of circuits in state OR_WAIT, waiting for the given - * connection. */ +/** Return the number of circuits in state CHAN_WAIT, waiting for the given + * channel. */ int -circuit_count_pending_on_or_conn(or_connection_t *or_conn) +circuit_count_pending_on_channel(channel_t *chan) { int cnt; smartlist_t *sl = smartlist_new(); - circuit_get_all_pending_on_or_conn(sl, or_conn); + + tor_assert(chan); + + circuit_get_all_pending_on_channel(sl, chan); cnt = smartlist_len(sl); smartlist_free(sl); log_debug(LD_CIRC,"or_conn to %s at %s, %d pending circs", - or_conn->nickname ? or_conn->nickname : "NULL", - or_conn->_base.address, + chan->nickname ? chan->nickname : "NULL", + channel_get_canonical_remote_descr(chan), cnt); return cnt; } @@ -310,7 +352,7 @@ circuit_close_all_marked(void) /** Return the head of the global linked list of circuits. */ circuit_t * -_circuit_get_global_list(void) +circuit_get_global_list_(void) { return global_circuitlist; } @@ -323,7 +365,7 @@ circuit_state_to_string(int state) switch (state) { case CIRCUIT_STATE_BUILDING: return "doing handshakes"; case CIRCUIT_STATE_ONIONSKIN_PENDING: return "processing the onion"; - case CIRCUIT_STATE_OR_WAIT: return "connecting to server"; + case CIRCUIT_STATE_CHAN_WAIT: return "connecting to server"; case CIRCUIT_STATE_OPEN: return "open"; default: log_warn(LD_BUG, "Unknown circuit state %d", state); @@ -372,6 +414,8 @@ circuit_purpose_to_controller_string(uint8_t purpose) return "MEASURE_TIMEOUT"; case CIRCUIT_PURPOSE_CONTROLLER: return "CONTROLLER"; + case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: + return "PATH_BIAS_TESTING"; default: tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose); @@ -399,6 +443,7 @@ circuit_purpose_to_controller_hs_state_string(uint8_t purpose) case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT: case CIRCUIT_PURPOSE_TESTING: case CIRCUIT_PURPOSE_CONTROLLER: + case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: return NULL; case CIRCUIT_PURPOSE_INTRO_POINT: @@ -514,15 +559,14 @@ init_circuit_base(circuit_t *circ) { tor_gettimeofday(&circ->timestamp_created); + // Gets reset when we send CREATE_FAST. + // circuit_expire_building() expects these to be equal + // until the orconn is built. + circ->timestamp_began = circ->timestamp_created; + circ->package_window = circuit_initial_package_window(); circ->deliver_window = CIRCWINDOW_START; - /* Initialize the cell_ewma_t structure */ - circ->n_cell_ewma.last_adjusted_tick = cell_ewma_get_tick(); - circ->n_cell_ewma.cell_count = 0.0; - circ->n_cell_ewma.heap_index = -1; - circ->n_cell_ewma.is_for_p_conn = 0; - circuit_add(circ); } @@ -538,7 +582,7 @@ origin_circuit_new(void) static uint32_t n_circuits_allocated = 1; circ = tor_malloc_zero(sizeof(origin_circuit_t)); - circ->_base.magic = ORIGIN_CIRCUIT_MAGIC; + circ->base_.magic = ORIGIN_CIRCUIT_MAGIC; circ->next_stream_id = crypto_rand_int(1<<16); circ->global_identifier = n_circuits_allocated++; @@ -555,31 +599,21 @@ origin_circuit_new(void) /** Allocate a new or_circuit_t, connected to <b>p_conn</b> as * <b>p_circ_id</b>. If <b>p_conn</b> is NULL, the circuit is unattached. */ or_circuit_t * -or_circuit_new(circid_t p_circ_id, or_connection_t *p_conn) +or_circuit_new(circid_t p_circ_id, channel_t *p_chan) { /* CircIDs */ or_circuit_t *circ; circ = tor_malloc_zero(sizeof(or_circuit_t)); - circ->_base.magic = OR_CIRCUIT_MAGIC; + circ->base_.magic = OR_CIRCUIT_MAGIC; - if (p_conn) - circuit_set_p_circid_orconn(circ, p_circ_id, p_conn); + if (p_chan) + circuit_set_p_circid_chan(circ, p_circ_id, p_chan); circ->remaining_relay_early_cells = MAX_RELAY_EARLY_CELLS_PER_CIRCUIT; init_circuit_base(TO_CIRCUIT(circ)); - /* Initialize the cell_ewma_t structure */ - - /* Initialize the cell counts to 0 */ - circ->p_cell_ewma.cell_count = 0.0; - circ->p_cell_ewma.last_adjusted_tick = cell_ewma_get_tick(); - circ->p_cell_ewma.is_for_p_conn = 1; - - /* It's not in any heap yet. */ - circ->p_cell_ewma.heap_index = -1; - return circ; } @@ -635,27 +669,27 @@ circuit_free(circuit_t *circ) if (ocirc->rend_splice) { or_circuit_t *other = ocirc->rend_splice; - tor_assert(other->_base.magic == OR_CIRCUIT_MAGIC); + tor_assert(other->base_.magic == OR_CIRCUIT_MAGIC); other->rend_splice = NULL; } /* remove from map. */ - circuit_set_p_circid_orconn(ocirc, 0, NULL); + circuit_set_p_circid_chan(ocirc, 0, NULL); /* Clear cell queue _after_ removing it from the map. Otherwise our * "active" checks will be violated. */ - cell_queue_clear(ô->p_conn_cells); + cell_queue_clear(ô->p_chan_cells); } extend_info_free(circ->n_hop); - tor_free(circ->n_conn_onionskin); + tor_free(circ->n_chan_create_cell); /* Remove from map. */ - circuit_set_n_circid_orconn(circ, 0, NULL); + circuit_set_n_circid_chan(circ, 0, NULL); /* Clear cell queue _after_ removing it from the map. Otherwise our * "active" checks will be violated. */ - cell_queue_clear(&circ->n_conn_cells); + cell_queue_clear(&circ->n_chan_cells); memwipe(mem, 0xAA, memlen); /* poison memory */ tor_free(mem); @@ -701,10 +735,10 @@ circuit_free_all(void) global_circuitlist = next; } - smartlist_free(circuits_pending_or_conns); - circuits_pending_or_conns = NULL; + smartlist_free(circuits_pending_chans); + circuits_pending_chans = NULL; - HT_CLEAR(orconn_circid_map, &orconn_circid_circuit_map); + HT_CLEAR(chan_circid_map, &chan_circid_map); } /** Deallocate space associated with the cpath node <b>victim</b>. */ @@ -718,7 +752,8 @@ circuit_free_cpath_node(crypt_path_t *victim) crypto_cipher_free(victim->b_crypto); crypto_digest_free(victim->f_digest); crypto_digest_free(victim->b_digest); - crypto_dh_free(victim->dh_handshake_state); + onion_handshake_state_release(&victim->handshake_state); + crypto_dh_free(victim->rend_dh_handshake_state); extend_info_free(victim->extend_info); memwipe(victim, 0xBB, sizeof(crypt_path_t)); /* poison memory */ @@ -741,14 +776,18 @@ cpath_ref_decref(crypt_path_reference_t *cpath_ref) * of information about circuit <b>circ</b>. */ static void -circuit_dump_details(int severity, circuit_t *circ, int conn_array_index, - const char *type, int this_circid, int other_circid) +circuit_dump_conn_details(int severity, + circuit_t *circ, + int conn_array_index, + const char *type, + int this_circid, + int other_circid) { log(severity, LD_CIRC, "Conn %d has %s circuit: circID %d (other side %d), " "state %d (%s), born %ld:", conn_array_index, type, this_circid, other_circid, circ->state, circuit_state_to_string(circ->state), - (long)circ->timestamp_created.tv_sec); + (long)circ->timestamp_began.tv_sec); if (CIRCUIT_IS_ORIGIN(circ)) { /* circ starts at this node */ circuit_log_path(severity, LD_CIRC, TO_ORIGIN_CIRCUIT(circ)); } @@ -763,50 +802,101 @@ circuit_dump_by_conn(connection_t *conn, int severity) circuit_t *circ; edge_connection_t *tmpconn; - for (circ=global_circuitlist;circ;circ = circ->next) { + for (circ = global_circuitlist; circ; circ = circ->next) { circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; - if (circ->marked_for_close) + + if (circ->marked_for_close) { continue; + } - if (! CIRCUIT_IS_ORIGIN(circ)) + if (!CIRCUIT_IS_ORIGIN(circ)) { p_circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + } - if (! CIRCUIT_IS_ORIGIN(circ) && TO_OR_CIRCUIT(circ)->p_conn && - TO_CONN(TO_OR_CIRCUIT(circ)->p_conn) == conn) - circuit_dump_details(severity, circ, conn->conn_array_index, "App-ward", - p_circ_id, n_circ_id); if (CIRCUIT_IS_ORIGIN(circ)) { for (tmpconn=TO_ORIGIN_CIRCUIT(circ)->p_streams; tmpconn; tmpconn=tmpconn->next_stream) { if (TO_CONN(tmpconn) == conn) { - circuit_dump_details(severity, circ, conn->conn_array_index, - "App-ward", p_circ_id, n_circ_id); + circuit_dump_conn_details(severity, circ, conn->conn_array_index, + "App-ward", p_circ_id, n_circ_id); } } } - if (circ->n_conn && TO_CONN(circ->n_conn) == conn) - circuit_dump_details(severity, circ, conn->conn_array_index, "Exit-ward", - n_circ_id, p_circ_id); + if (! CIRCUIT_IS_ORIGIN(circ)) { for (tmpconn=TO_OR_CIRCUIT(circ)->n_streams; tmpconn; tmpconn=tmpconn->next_stream) { if (TO_CONN(tmpconn) == conn) { - circuit_dump_details(severity, circ, conn->conn_array_index, - "Exit-ward", n_circ_id, p_circ_id); + circuit_dump_conn_details(severity, circ, conn->conn_array_index, + "Exit-ward", n_circ_id, p_circ_id); } } } - if (!circ->n_conn && circ->n_hop && - tor_addr_eq(&circ->n_hop->addr, &conn->addr) && - circ->n_hop->port == conn->port && - conn->type == CONN_TYPE_OR && - tor_memeq(TO_OR_CONN(conn)->identity_digest, - circ->n_hop->identity_digest, DIGEST_LEN)) { - circuit_dump_details(severity, circ, conn->conn_array_index, - (circ->state == CIRCUIT_STATE_OPEN && - !CIRCUIT_IS_ORIGIN(circ)) ? - "Endpoint" : "Pending", - n_circ_id, p_circ_id); + } +} + +/** A helper function for circuit_dump_by_chan() below. Log a bunch + * of information about circuit <b>circ</b>. + */ +static void +circuit_dump_chan_details(int severity, + circuit_t *circ, + channel_t *chan, + const char *type, + int this_circid, + int other_circid) +{ + log(severity, LD_CIRC, "Conn %p has %s circuit: circID %d (other side %d), " + "state %d (%s), born %ld:", + chan, type, this_circid, other_circid, circ->state, + circuit_state_to_string(circ->state), + (long)circ->timestamp_began.tv_sec); + if (CIRCUIT_IS_ORIGIN(circ)) { /* circ starts at this node */ + circuit_log_path(severity, LD_CIRC, TO_ORIGIN_CIRCUIT(circ)); + } +} + +/** Log, at severity <b>severity</b>, information about each circuit + * that is connected to <b>chan</b>. + */ +void +circuit_dump_by_chan(channel_t *chan, int severity) +{ + circuit_t *circ; + + tor_assert(chan); + + for (circ = global_circuitlist; circ; circ = circ->next) { + circid_t n_circ_id = circ->n_circ_id, p_circ_id = 0; + + if (circ->marked_for_close) { + continue; + } + + if (!CIRCUIT_IS_ORIGIN(circ)) { + p_circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + } + + if (! CIRCUIT_IS_ORIGIN(circ) && TO_OR_CIRCUIT(circ)->p_chan && + TO_OR_CIRCUIT(circ)->p_chan == chan) { + circuit_dump_chan_details(severity, circ, chan, "App-ward", + p_circ_id, n_circ_id); + } + + if (circ->n_chan && circ->n_chan == chan) { + circuit_dump_chan_details(severity, circ, chan, "Exit-ward", + n_circ_id, p_circ_id); + } + + if (!circ->n_chan && circ->n_hop && + channel_matches_extend_info(chan, circ->n_hop) && + tor_memeq(chan->identity_digest, + circ->n_hop->identity_digest, DIGEST_LEN)) { + circuit_dump_chan_details(severity, circ, chan, + (circ->state == CIRCUIT_STATE_OPEN && + !CIRCUIT_IS_ORIGIN(circ)) ? + "Endpoint" : "Pending", + n_circ_id, p_circ_id); } } } @@ -831,27 +921,39 @@ circuit_get_by_global_id(uint32_t id) /** Return a circ such that: * - circ-\>n_circ_id or circ-\>p_circ_id is equal to <b>circ_id</b>, and - * - circ is attached to <b>conn</b>, either as p_conn or n_conn. + * - circ is attached to <b>chan</b>, either as p_chan or n_chan. * Return NULL if no such circuit exists. */ static INLINE circuit_t * -circuit_get_by_circid_orconn_impl(circid_t circ_id, or_connection_t *conn) +circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan) { - orconn_circid_circuit_map_t search; - orconn_circid_circuit_map_t *found; + chan_circid_circuit_map_t search; + chan_circid_circuit_map_t *found; - if (_last_circid_orconn_ent && - circ_id == _last_circid_orconn_ent->circ_id && - conn == _last_circid_orconn_ent->or_conn) { - found = _last_circid_orconn_ent; + if (_last_circid_chan_ent && + circ_id == _last_circid_chan_ent->circ_id && + chan == _last_circid_chan_ent->chan) { + found = _last_circid_chan_ent; } else { search.circ_id = circ_id; - search.or_conn = conn; - found = HT_FIND(orconn_circid_map, &orconn_circid_circuit_map, &search); - _last_circid_orconn_ent = found; + search.chan = chan; + found = HT_FIND(chan_circid_map, &chan_circid_map, &search); + _last_circid_chan_ent = found; } - if (found && found->circuit) + if (found && found->circuit) { + log_debug(LD_CIRC, + "circuit_get_by_circid_channel_impl() returning circuit %p for" + " circ_id %d, channel ID " U64_FORMAT " (%p)", + found->circuit, circ_id, + U64_PRINTF_ARG(chan->global_identifier), chan); return found->circuit; + } + + log_debug(LD_CIRC, + "circuit_get_by_circid_channel_impl() found nothing for" + " circ_id %d, channel ID " U64_FORMAT " (%p)", + circ_id, + U64_PRINTF_ARG(chan->global_identifier), chan); return NULL; /* The rest of this checks for bugs. Disabled by default. */ @@ -861,15 +963,15 @@ circuit_get_by_circid_orconn_impl(circid_t circ_id, or_connection_t *conn) for (circ=global_circuitlist;circ;circ = circ->next) { if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - if (or_circ->p_conn == conn && or_circ->p_circ_id == circ_id) { + if (or_circ->p_chan == chan && or_circ->p_circ_id == circ_id) { log_warn(LD_BUG, - "circuit matches p_conn, but not in hash table (Bug!)"); + "circuit matches p_chan, but not in hash table (Bug!)"); return circ; } } - if (circ->n_conn == conn && circ->n_circ_id == circ_id) { + if (circ->n_chan == chan && circ->n_circ_id == circ_id) { log_warn(LD_BUG, - "circuit matches n_conn, but not in hash table (Bug!)"); + "circuit matches n_chan, but not in hash table (Bug!)"); return circ; } } @@ -879,26 +981,38 @@ circuit_get_by_circid_orconn_impl(circid_t circ_id, or_connection_t *conn) /** Return a circ such that: * - circ-\>n_circ_id or circ-\>p_circ_id is equal to <b>circ_id</b>, and - * - circ is attached to <b>conn</b>, either as p_conn or n_conn. + * - circ is attached to <b>chan</b>, either as p_chan or n_chan. * - circ is not marked for close. * Return NULL if no such circuit exists. */ circuit_t * -circuit_get_by_circid_orconn(circid_t circ_id, or_connection_t *conn) +circuit_get_by_circid_channel(circid_t circ_id, channel_t *chan) { - circuit_t *circ = circuit_get_by_circid_orconn_impl(circ_id, conn); + circuit_t *circ = circuit_get_by_circid_channel_impl(circ_id, chan); if (!circ || circ->marked_for_close) return NULL; else return circ; } +/** Return a circ such that: + * - circ-\>n_circ_id or circ-\>p_circ_id is equal to <b>circ_id</b>, and + * - circ is attached to <b>chan</b>, either as p_chan or n_chan. + * Return NULL if no such circuit exists. + */ +circuit_t * +circuit_get_by_circid_channel_even_if_marked(circid_t circ_id, + channel_t *chan) +{ + return circuit_get_by_circid_channel_impl(circ_id, chan); +} + /** Return true iff the circuit ID <b>circ_id</b> is currently used by a - * circuit, marked or not, on <b>conn</b>. */ + * circuit, marked or not, on <b>chan</b>. */ int -circuit_id_in_use_on_orconn(circid_t circ_id, or_connection_t *conn) +circuit_id_in_use_on_channel(circid_t circ_id, channel_t *chan) { - return circuit_get_by_circid_orconn_impl(circ_id, conn) != NULL; + return circuit_get_by_circid_channel_impl(circ_id, chan) != NULL; } /** Return the circuit that a given edge connection is using. */ @@ -915,27 +1029,32 @@ circuit_get_by_edge_conn(edge_connection_t *conn) return circ; } -/** For each circuit that has <b>conn</b> as n_conn or p_conn, unlink the - * circuit from the orconn,circid map, and mark it for close if it hasn't +/** For each circuit that has <b>chan</b> as n_chan or p_chan, unlink the + * circuit from the chan,circid map, and mark it for close if it hasn't * been marked already. */ void -circuit_unlink_all_from_or_conn(or_connection_t *conn, int reason) +circuit_unlink_all_from_channel(channel_t *chan, int reason) { circuit_t *circ; - connection_or_unlink_all_active_circs(conn); + channel_unlink_all_circuits(chan); for (circ = global_circuitlist; circ; circ = circ->next) { int mark = 0; - if (circ->n_conn == conn) { - circuit_set_n_circid_orconn(circ, 0, NULL); - mark = 1; + if (circ->n_chan == chan) { + circuit_set_n_circid_chan(circ, 0, NULL); + mark = 1; + + /* If we didn't request this closure, pass the remote + * bit to mark_for_close. */ + if (chan->reason_for_closing != CHANNEL_CLOSE_REQUESTED) + reason |= END_CIRC_REASON_FLAG_REMOTE; } if (! CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - if (or_circ->p_conn == conn) { - circuit_set_p_circid_orconn(or_circ, 0, NULL); + if (or_circ->p_chan == chan) { + circuit_set_p_circid_chan(or_circ, 0, NULL); mark = 1; } } @@ -1059,7 +1178,7 @@ origin_circuit_t * circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags) { - circuit_t *_circ; + circuit_t *circ_; origin_circuit_t *best=NULL; int need_uptime = (flags & CIRCLAUNCH_NEED_UPTIME) != 0; int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0; @@ -1075,13 +1194,13 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, "capacity %d, internal %d", purpose, need_uptime, need_capacity, internal); - for (_circ=global_circuitlist; _circ; _circ = _circ->next) { - if (CIRCUIT_IS_ORIGIN(_circ) && - _circ->state == CIRCUIT_STATE_OPEN && - !_circ->marked_for_close && - _circ->purpose == CIRCUIT_PURPOSE_C_GENERAL && - !_circ->timestamp_dirty) { - origin_circuit_t *circ = TO_ORIGIN_CIRCUIT(_circ); + for (circ_=global_circuitlist; circ_; circ_ = circ_->next) { + if (CIRCUIT_IS_ORIGIN(circ_) && + circ_->state == CIRCUIT_STATE_OPEN && + !circ_->marked_for_close && + circ_->purpose == CIRCUIT_PURPOSE_C_GENERAL && + !circ_->timestamp_dirty) { + origin_circuit_t *circ = TO_ORIGIN_CIRCUIT(circ_); if ((!need_uptime || circ->build_state->need_uptime) && (!need_capacity || circ->build_state->need_capacity) && (internal == circ->build_state->is_internal) && @@ -1215,7 +1334,7 @@ circuit_expire_all_dirty_circs(void) * rendezvous stream), then mark the other circuit to close as well. */ void -_circuit_mark_for_close(circuit_t *circ, int reason, int line, +circuit_mark_for_close_(circuit_t *circ, int reason, int line, const char *file) { int orig_reason = reason; /* Passed to the controller */ @@ -1238,7 +1357,13 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, } reason = END_CIRC_REASON_NONE; } + if (CIRCUIT_IS_ORIGIN(circ)) { + if (pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason) == -1) { + /* Don't close it yet, we need to test it first */ + return; + } + /* We don't send reasons when closing circuits at the origin. */ reason = END_CIRC_REASON_NONE; } @@ -1246,7 +1371,7 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, if (reason & END_CIRC_REASON_FLAG_REMOTE) reason &= ~END_CIRC_REASON_FLAG_REMOTE; - if (reason < _END_CIRC_REASON_MIN || reason > _END_CIRC_REASON_MAX) { + if (reason < END_CIRC_REASON_MIN_ || reason > END_CIRC_REASON_MAX_) { if (!(orig_reason & END_CIRC_REASON_FLAG_REMOTE)) log_warn(LD_BUG, "Reason %d out of range at %s:%d", reason, file, line); reason = END_CIRC_REASON_NONE; @@ -1266,9 +1391,9 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, circuit_rep_hist_note_result(ocirc); } } - if (circ->state == CIRCUIT_STATE_OR_WAIT) { - if (circuits_pending_or_conns) - smartlist_remove(circuits_pending_or_conns, circ); + if (circ->state == CIRCUIT_STATE_CHAN_WAIT) { + if (circuits_pending_chans) + smartlist_remove(circuits_pending_chans, circ); } if (CIRCUIT_IS_ORIGIN(circ)) { control_event_circuit_status(TO_ORIGIN_CIRCUIT(circ), @@ -1305,9 +1430,15 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, INTRO_POINT_FAILURE_UNREACHABLE); } } - if (circ->n_conn) { - circuit_clear_cell_queue(circ, circ->n_conn); - connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason); + if (circ->n_chan) { + circuit_clear_cell_queue(circ, circ->n_chan); + /* Only send destroy if the channel isn't closing anyway */ + if (!(circ->n_chan->state == CHANNEL_STATE_CLOSING || + circ->n_chan->state == CHANNEL_STATE_CLOSED || + circ->n_chan->state == CHANNEL_STATE_ERROR)) { + channel_send_destroy(circ->n_circ_id, circ->n_chan, reason); + } + circuitmux_detach_circuit(circ->n_chan->cmux, circ); } if (! CIRCUIT_IS_ORIGIN(circ)) { @@ -1320,7 +1451,7 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, while (or_circ->resolving_streams) { conn = or_circ->resolving_streams; or_circ->resolving_streams = conn->next_stream; - if (!conn->_base.marked_for_close) { + if (!conn->base_.marked_for_close) { /* The client will see a DESTROY, and infer that the connections * are closing because the circuit is getting torn down. No need * to send an end cell. */ @@ -1332,9 +1463,15 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, conn->on_circuit = NULL; } - if (or_circ->p_conn) { - circuit_clear_cell_queue(circ, or_circ->p_conn); - connection_or_send_destroy(or_circ->p_circ_id, or_circ->p_conn, reason); + if (or_circ->p_chan) { + circuit_clear_cell_queue(circ, or_circ->p_chan); + /* Only send destroy if the channel isn't closing anyway */ + if (!(or_circ->p_chan->state == CHANNEL_STATE_CLOSING || + or_circ->p_chan->state == CHANNEL_STATE_CLOSED || + or_circ->p_chan->state == CHANNEL_STATE_ERROR)) { + channel_send_destroy(or_circ->p_circ_id, or_circ->p_chan, reason); + } + circuitmux_detach_circuit(or_circ->p_chan->cmux, circ); } } else { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); @@ -1350,7 +1487,7 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, if (!CIRCUIT_IS_ORIGIN(circ)) { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (or_circ->rend_splice) { - if (!or_circ->rend_splice->_base.marked_for_close) { + if (!or_circ->rend_splice->base_.marked_for_close) { /* do this after marking this circuit, to avoid infinite recursion. */ circuit_mark_for_close(TO_CIRCUIT(or_circ->rend_splice), reason); } @@ -1376,7 +1513,8 @@ assert_cpath_layer_ok(const crypt_path_t *cp) tor_assert(cp->b_crypto); /* fall through */ case CPATH_STATE_CLOSED: - tor_assert(!cp->dh_handshake_state); + /*XXXX Assert that there's no handshake_state either. */ + tor_assert(!cp->rend_dh_handshake_state); break; case CPATH_STATE_AWAITING_KEYS: /* tor_assert(cp->dh_handshake_state); */ @@ -1424,8 +1562,8 @@ assert_circuit_ok(const circuit_t *c) tor_assert(c); tor_assert(c->magic == ORIGIN_CIRCUIT_MAGIC || c->magic == OR_CIRCUIT_MAGIC); - tor_assert(c->purpose >= _CIRCUIT_PURPOSE_MIN && - c->purpose <= _CIRCUIT_PURPOSE_MAX); + tor_assert(c->purpose >= CIRCUIT_PURPOSE_MIN_ && + c->purpose <= CIRCUIT_PURPOSE_MAX_); { /* Having a separate variable for this pleases GCC 4.2 in ways I hope I @@ -1437,33 +1575,33 @@ assert_circuit_ok(const circuit_t *c) or_circ = TO_OR_CIRCUIT(nonconst_circ); } - if (c->n_conn) { + if (c->n_chan) { tor_assert(!c->n_hop); if (c->n_circ_id) { /* We use the _impl variant here to make sure we don't fail on marked * circuits, which would not be returned by the regular function. */ - circuit_t *c2 = circuit_get_by_circid_orconn_impl(c->n_circ_id, - c->n_conn); + circuit_t *c2 = circuit_get_by_circid_channel_impl(c->n_circ_id, + c->n_chan); tor_assert(c == c2); } } - if (or_circ && or_circ->p_conn) { + if (or_circ && or_circ->p_chan) { if (or_circ->p_circ_id) { /* ibid */ - circuit_t *c2 = circuit_get_by_circid_orconn_impl(or_circ->p_circ_id, - or_circ->p_conn); + circuit_t *c2 = circuit_get_by_circid_channel_impl(or_circ->p_circ_id, + or_circ->p_chan); tor_assert(c == c2); } } if (or_circ) for (conn = or_circ->n_streams; conn; conn = conn->next_stream) - tor_assert(conn->_base.type == CONN_TYPE_EXIT); + tor_assert(conn->base_.type == CONN_TYPE_EXIT); tor_assert(c->deliver_window >= 0); tor_assert(c->package_window >= 0); if (c->state == CIRCUIT_STATE_OPEN) { - tor_assert(!c->n_conn_onionskin); + tor_assert(!c->n_chan_create_cell); if (or_circ) { tor_assert(or_circ->n_crypto); tor_assert(or_circ->p_crypto); @@ -1471,12 +1609,12 @@ assert_circuit_ok(const circuit_t *c) tor_assert(or_circ->p_digest); } } - if (c->state == CIRCUIT_STATE_OR_WAIT && !c->marked_for_close) { - tor_assert(circuits_pending_or_conns && - smartlist_isin(circuits_pending_or_conns, c)); + if (c->state == CIRCUIT_STATE_CHAN_WAIT && !c->marked_for_close) { + tor_assert(circuits_pending_chans && + smartlist_isin(circuits_pending_chans, c)); } else { - tor_assert(!circuits_pending_or_conns || - !smartlist_isin(circuits_pending_or_conns, c)); + tor_assert(!circuits_pending_chans || + !smartlist_isin(circuits_pending_chans, c)); } if (origin_circ && origin_circ->cpath) { assert_cpath_ok(origin_circ->cpath); diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 6e7735476..a885af2fa 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -9,29 +9,33 @@ * \brief Header file for circuitlist.c. **/ -#ifndef _TOR_CIRCUITLIST_H -#define _TOR_CIRCUITLIST_H +#ifndef TOR_CIRCUITLIST_H +#define TOR_CIRCUITLIST_H -circuit_t * _circuit_get_global_list(void); +circuit_t * circuit_get_global_list_(void); const char *circuit_state_to_string(int state); const char *circuit_purpose_to_controller_string(uint8_t purpose); const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose); const char *circuit_purpose_to_string(uint8_t purpose); void circuit_dump_by_conn(connection_t *conn, int severity); -void circuit_set_p_circid_orconn(or_circuit_t *circ, circid_t id, - or_connection_t *conn); -void circuit_set_n_circid_orconn(circuit_t *circ, circid_t id, - or_connection_t *conn); +void circuit_dump_by_chan(channel_t *chan, int severity); +void circuit_set_p_circid_chan(or_circuit_t *circ, circid_t id, + channel_t *chan); +void circuit_set_n_circid_chan(circuit_t *circ, circid_t id, + channel_t *chan); void circuit_set_state(circuit_t *circ, uint8_t state); void circuit_close_all_marked(void); int32_t circuit_initial_package_window(void); origin_circuit_t *origin_circuit_new(void); -or_circuit_t *or_circuit_new(circid_t p_circ_id, or_connection_t *p_conn); -circuit_t *circuit_get_by_circid_orconn(circid_t circ_id, - or_connection_t *conn); -int circuit_id_in_use_on_orconn(circid_t circ_id, or_connection_t *conn); +or_circuit_t *or_circuit_new(circid_t p_circ_id, channel_t *p_chan); +circuit_t *circuit_get_by_circid_channel(circid_t circ_id, + channel_t *chan); +circuit_t * +circuit_get_by_circid_channel_even_if_marked(circid_t circ_id, + channel_t *chan); +int circuit_id_in_use_on_channel(circid_t circ_id, channel_t *chan); circuit_t *circuit_get_by_edge_conn(edge_connection_t *conn); -void circuit_unlink_all_from_or_conn(or_connection_t *conn, int reason); +void circuit_unlink_all_from_channel(channel_t *chan, int reason); origin_circuit_t *circuit_get_by_global_id(uint32_t id); origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( const rend_data_t *rend_data); @@ -43,16 +47,16 @@ origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); void circuit_mark_all_unused_circs(void); void circuit_expire_all_dirty_circs(void); -void _circuit_mark_for_close(circuit_t *circ, int reason, +void circuit_mark_for_close_(circuit_t *circ, int reason, int line, const char *file); int circuit_get_cpath_len(origin_circuit_t *circ); crypt_path_t *circuit_get_cpath_hop(origin_circuit_t *circ, int hopnum); -void circuit_get_all_pending_on_or_conn(smartlist_t *out, - or_connection_t *or_conn); -int circuit_count_pending_on_or_conn(or_connection_t *or_conn); +void circuit_get_all_pending_on_channel(smartlist_t *out, + channel_t *chan); +int circuit_count_pending_on_channel(channel_t *chan); #define circuit_mark_for_close(c, reason) \ - _circuit_mark_for_close((c), (reason), __LINE__, _SHORT_FILE_) + circuit_mark_for_close_((c), (reason), __LINE__, SHORT_FILE__) void assert_cpath_layer_ok(const crypt_path_t *cp); void assert_circuit_ok(const circuit_t *c); diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c new file mode 100644 index 000000000..f3b6b7cd7 --- /dev/null +++ b/src/or/circuitmux.c @@ -0,0 +1,1745 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitmux.c + * \brief Circuit mux/cell selection abstraction + **/ + +#include "or.h" +#include "channel.h" +#include "circuitlist.h" +#include "circuitmux.h" + +/* + * Private typedefs for circuitmux.c + */ + +/* + * Map of muxinfos for circuitmux_t to use; struct is defined below (name + * of struct must match HT_HEAD line). + */ +typedef struct chanid_circid_muxinfo_map chanid_circid_muxinfo_map_t; + +/* + * Hash table entry (yeah, calling it chanid_circid_muxinfo_s seems to + * break the hash table code). + */ +typedef struct chanid_circid_muxinfo_t chanid_circid_muxinfo_t; + +/* + * Anything the mux wants to store per-circuit in the map; right now just + * a count of queued cells. + */ + +typedef struct circuit_muxinfo_s circuit_muxinfo_t; + +/* + * Structures for circuitmux.c + */ + +/* + * A circuitmux is a collection of circuits; it tracks which subset + * of the attached circuits are 'active' (i.e., have cells available + * to transmit) and how many cells on each. It expoes three distinct + * interfaces to other components: + * + * To channels, which each have a circuitmux_t, the supported operations + * are: + * + * circuitmux_get_first_active_circuit(): + * + * Pick one of the circuitmux's active circuits to send cells from. + * + * circuitmux_notify_xmit_cells(): + * + * Notify the circuitmux that cells have been sent on a circuit. + * + * To circuits, the exposed operations are: + * + * circuitmux_attach_circuit(): + * + * Attach a circuit to the circuitmux; this will allocate any policy- + * specific data wanted for this circuit and add it to the active + * circuits list if it has queued cells. + * + * circuitmux_detach_circuit(): + * + * Detach a circuit from the circuitmux, freeing associated structures. + * + * circuitmux_clear_num_cells(): + * + * Clear the circuitmux's cell counter for this circuit. + * + * circuitmux_set_num_cells(): + * + * Set the circuitmux's cell counter for this circuit. + * + * See circuitmux.h for the circuitmux_policy_t data structure, which contains + * a table of function pointers implementing a circuit selection policy, and + * circuitmux_ewma.c for an example of a circuitmux policy. Circuitmux + * policies can be manipulated with: + * + * circuitmux_get_policy(): + * + * Return the current policy for a circuitmux_t, if any. + * + * circuitmux_clear_policy(): + * + * Remove a policy installed on a circuitmux_t, freeing all associated + * data. The circuitmux will revert to the built-in round-robin behavior. + * + * circuitmux_set_policy(): + * + * Install a policy on a circuitmux_t; the appropriate callbacks will be + * made to attach all existing circuits to the new policy. + * + */ + +struct circuitmux_s { + /* Keep count of attached, active circuits */ + unsigned int n_circuits, n_active_circuits; + + /* Total number of queued cells on all circuits */ + unsigned int n_cells; + + /* + * Map from (channel ID, circuit ID) pairs to circuit_muxinfo_t + */ + chanid_circid_muxinfo_map_t *chanid_circid_map; + + /* + * Double-linked ring of circuits with queued cells waiting for room to + * free up on this connection's outbuf. Every time we pull cells from + * a circuit, we advance this pointer to the next circuit in the ring. + */ + struct circuit_t *active_circuits_head, *active_circuits_tail; + + /* + * Circuitmux policy; if this is non-NULL, it can override the built- + * in round-robin active circuits behavior. This is how EWMA works in + * the new circuitmux_t world. + */ + const circuitmux_policy_t *policy; + + /* Policy-specific data */ + circuitmux_policy_data_t *policy_data; +}; + +/* + * This struct holds whatever we want to store per attached circuit on a + * circuitmux_t; right now, just the count of queued cells and the direction. + */ + +struct circuit_muxinfo_s { + /* Count of cells on this circuit at last update */ + unsigned int cell_count; + /* Direction of flow */ + cell_direction_t direction; + /* Policy-specific data */ + circuitmux_policy_circ_data_t *policy_data; + /* Mark bit for consistency checker */ + unsigned int mark:1; +}; + +/* + * A map from channel ID and circuit ID to a circuit_muxinfo_t for that + * circuit. + */ + +struct chanid_circid_muxinfo_t { + HT_ENTRY(chanid_circid_muxinfo_t) node; + uint64_t chan_id; + circid_t circ_id; + circuit_muxinfo_t muxinfo; +}; + +/* + * Internal-use #defines + */ + +#ifdef CMUX_PARANOIA +#define circuitmux_assert_okay_paranoid(cmux) \ + circuitmux_assert_okay(cmux) +#else +#define circuitmux_assert_okay_paranoid(cmux) +#endif + +/* + * Static function declarations + */ + +static INLINE int +chanid_circid_entries_eq(chanid_circid_muxinfo_t *a, + chanid_circid_muxinfo_t *b); +static INLINE unsigned int +chanid_circid_entry_hash(chanid_circid_muxinfo_t *a); +static chanid_circid_muxinfo_t * +circuitmux_find_map_entry(circuitmux_t *cmux, circuit_t *circ); +static void +circuitmux_make_circuit_active(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction); +static void +circuitmux_make_circuit_inactive(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction); +static INLINE void +circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction); +static INLINE circuit_t ** +circuitmux_next_active_circ_p(circuitmux_t *cmux, circuit_t *circ); +static INLINE circuit_t ** +circuitmux_prev_active_circ_p(circuitmux_t *cmux, circuit_t *circ); +static void circuitmux_assert_okay_pass_one(circuitmux_t *cmux); +static void circuitmux_assert_okay_pass_two(circuitmux_t *cmux); +static void circuitmux_assert_okay_pass_three(circuitmux_t *cmux); + +/* Function definitions */ + +/** + * Linked list helpers + */ + +/** + * Move an active circuit to the tail of the cmux's active circuits list; + * used by circuitmux_notify_xmit_cells(). + */ + +static INLINE void +circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction) +{ + circuit_t **next_p = NULL, **prev_p = NULL; + circuit_t **next_prev = NULL, **prev_next = NULL; + circuit_t **tail_next = NULL; + or_circuit_t *or_circ = NULL; + + tor_assert(cmux); + tor_assert(circ); + + circuitmux_assert_okay_paranoid(cmux); + + /* Figure out our next_p and prev_p for this cmux/direction */ + if (direction) { + if (direction == CELL_DIRECTION_OUT) { + tor_assert(circ->n_mux == cmux); + next_p = &(circ->next_active_on_n_chan); + prev_p = &(circ->prev_active_on_n_chan); + } else { + or_circ = TO_OR_CIRCUIT(circ); + tor_assert(or_circ->p_mux == cmux); + next_p = &(or_circ->next_active_on_p_chan); + prev_p = &(or_circ->prev_active_on_p_chan); + } + } else { + if (circ->n_mux == cmux) { + next_p = &(circ->next_active_on_n_chan); + prev_p = &(circ->prev_active_on_n_chan); + direction = CELL_DIRECTION_OUT; + } else { + or_circ = TO_OR_CIRCUIT(circ); + tor_assert(or_circ->p_mux == cmux); + next_p = &(or_circ->next_active_on_p_chan); + prev_p = &(or_circ->prev_active_on_p_chan); + direction = CELL_DIRECTION_IN; + } + } + tor_assert(next_p); + tor_assert(prev_p); + + /* Check if this really is an active circuit */ + if ((*next_p == NULL && *prev_p == NULL) && + !(circ == cmux->active_circuits_head || + circ == cmux->active_circuits_tail)) { + /* Not active, no-op */ + return; + } + + /* Check if this is already the tail */ + if (circ == cmux->active_circuits_tail) return; + + /* Okay, we have to move it; figure out next_prev and prev_next */ + if (*next_p) next_prev = circuitmux_prev_active_circ_p(cmux, *next_p); + if (*prev_p) prev_next = circuitmux_next_active_circ_p(cmux, *prev_p); + /* Adjust the previous node's next pointer, if any */ + if (prev_next) *prev_next = *next_p; + /* Otherwise, we were the head */ + else cmux->active_circuits_head = *next_p; + /* Adjust the next node's previous pointer, if any */ + if (next_prev) *next_prev = *prev_p; + /* We're out of the list; now re-attach at the tail */ + /* Adjust our next and prev pointers */ + *next_p = NULL; + *prev_p = cmux->active_circuits_tail; + /* Set the next pointer of the tail, or the head if none */ + if (cmux->active_circuits_tail) { + tail_next = circuitmux_next_active_circ_p(cmux, + cmux->active_circuits_tail); + *tail_next = circ; + } else { + cmux->active_circuits_head = circ; + } + /* Set the tail to this circuit */ + cmux->active_circuits_tail = circ; + + circuitmux_assert_okay_paranoid(cmux); +} + +static INLINE circuit_t ** +circuitmux_next_active_circ_p(circuitmux_t *cmux, circuit_t *circ) +{ + tor_assert(cmux); + tor_assert(circ); + + if (circ->n_mux == cmux) return &(circ->next_active_on_n_chan); + else { + tor_assert(TO_OR_CIRCUIT(circ)->p_mux == cmux); + return &(TO_OR_CIRCUIT(circ)->next_active_on_p_chan); + } +} + +static INLINE circuit_t ** +circuitmux_prev_active_circ_p(circuitmux_t *cmux, circuit_t *circ) +{ + tor_assert(cmux); + tor_assert(circ); + + if (circ->n_mux == cmux) return &(circ->prev_active_on_n_chan); + else { + tor_assert(TO_OR_CIRCUIT(circ)->p_mux == cmux); + return &(TO_OR_CIRCUIT(circ)->prev_active_on_p_chan); + } +} + +/** + * Helper for chanid_circid_cell_count_map_t hash table: compare the channel + * ID and circuit ID for a and b, and return less than, equal to, or greater + * than zero appropriately. + */ + +static INLINE int +chanid_circid_entries_eq(chanid_circid_muxinfo_t *a, + chanid_circid_muxinfo_t *b) +{ + return a->chan_id == b->chan_id && a->circ_id == b->circ_id; +} + +/** + * Helper: return a hash based on circuit ID and channel ID in a. + */ + +static INLINE unsigned int +chanid_circid_entry_hash(chanid_circid_muxinfo_t *a) +{ + return (((unsigned int)(a->circ_id) << 8) ^ + ((unsigned int)((a->chan_id >> 32) & 0xffffffff)) ^ + ((unsigned int)(a->chan_id & 0xffffffff))); +} + +/* Declare the struct chanid_circid_muxinfo_map type */ +HT_HEAD(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t); + +/* Emit a bunch of hash table stuff */ +HT_PROTOTYPE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, + chanid_circid_entry_hash, chanid_circid_entries_eq); +HT_GENERATE(chanid_circid_muxinfo_map, chanid_circid_muxinfo_t, node, + chanid_circid_entry_hash, chanid_circid_entries_eq, 0.6, + malloc, realloc, free); + +/* + * Circuitmux alloc/free functions + */ + +/** + * Allocate a new circuitmux_t + */ + +circuitmux_t * +circuitmux_alloc(void) +{ + circuitmux_t *rv = NULL; + + rv = tor_malloc_zero(sizeof(*rv)); + rv->chanid_circid_map = tor_malloc_zero(sizeof(*( rv->chanid_circid_map))); + HT_INIT(chanid_circid_muxinfo_map, rv->chanid_circid_map); + + return rv; +} + +/** + * Detach all circuits from a circuitmux (use before circuitmux_free()) + */ + +void +circuitmux_detach_all_circuits(circuitmux_t *cmux) +{ + chanid_circid_muxinfo_t **i = NULL, *to_remove; + channel_t *chan = NULL; + circuit_t *circ = NULL; + + tor_assert(cmux); + /* + * Don't circuitmux_assert_okay_paranoid() here; this gets called when + * channels are being freed and have already been unregistered, so + * the channel ID lookups it does will fail. + */ + + i = HT_START(chanid_circid_muxinfo_map, cmux->chanid_circid_map); + while (i) { + to_remove = *i; + if (to_remove) { + /* Find a channel and circuit */ + chan = channel_find_by_global_id(to_remove->chan_id); + if (chan) { + circ = + circuit_get_by_circid_channel_even_if_marked(to_remove->circ_id, + chan); + if (circ) { + /* Clear the circuit's mux for this direction */ + if (to_remove->muxinfo.direction == CELL_DIRECTION_OUT) { + /* + * Update active_circuits et al.; this does policy notifies, so + * comes before freeing policy data + */ + + if (to_remove->muxinfo.cell_count > 0) { + circuitmux_make_circuit_inactive(cmux, circ, CELL_DIRECTION_OUT); + } + + /* Clear n_mux */ + circ->n_mux = NULL; + } else if (circ->magic == OR_CIRCUIT_MAGIC) { + /* + * Update active_circuits et al.; this does policy notifies, so + * comes before freeing policy data + */ + + if (to_remove->muxinfo.cell_count > 0) { + circuitmux_make_circuit_inactive(cmux, circ, CELL_DIRECTION_IN); + } + + /* + * It has a sensible p_chan and direction == CELL_DIRECTION_IN, + * so clear p_mux. + */ + TO_OR_CIRCUIT(circ)->p_mux = NULL; + } else { + /* Complain and move on */ + log_warn(LD_CIRC, + "Circuit %d/channel " U64_FORMAT " had direction == " + "CELL_DIRECTION_IN, but isn't an or_circuit_t", + to_remove->circ_id, + U64_PRINTF_ARG(to_remove->chan_id)); + } + + /* Free policy-specific data if we have it */ + if (to_remove->muxinfo.policy_data) { + /* + * If we have policy data, assert that we have the means to + * free it + */ + tor_assert(cmux->policy); + tor_assert(cmux->policy->free_circ_data); + /* Call free_circ_data() */ + cmux->policy->free_circ_data(cmux, + cmux->policy_data, + circ, + to_remove->muxinfo.policy_data); + to_remove->muxinfo.policy_data = NULL; + } + } else { + /* Complain and move on */ + log_warn(LD_CIRC, + "Couldn't find circuit %d (for channel " U64_FORMAT ")", + to_remove->circ_id, + U64_PRINTF_ARG(to_remove->chan_id)); + } + } else { + /* Complain and move on */ + log_warn(LD_CIRC, + "Couldn't find channel " U64_FORMAT " (for circuit id %d)", + U64_PRINTF_ARG(to_remove->chan_id), + to_remove->circ_id); + } + + /* Assert that we don't have un-freed policy data for this circuit */ + tor_assert(to_remove->muxinfo.policy_data == NULL); + } + + i = HT_NEXT_RMV(chanid_circid_muxinfo_map, cmux->chanid_circid_map, i); + + /* Free it */ + tor_free(to_remove); + } + + cmux->n_circuits = 0; + cmux->n_active_circuits = 0; + cmux->n_cells = 0; +} + +/** + * Free a circuitmux_t; the circuits must be detached first with + * circuitmux_detach_all_circuits(). + */ + +void +circuitmux_free(circuitmux_t *cmux) +{ + if (!cmux) return; + + tor_assert(cmux->n_circuits == 0); + tor_assert(cmux->n_active_circuits == 0); + + /* + * Free policy-specific data if we have any; we don't + * need to do circuitmux_set_policy(cmux, NULL) to cover + * the circuits because they would have been handled in + * circuitmux_detach_all_circuits() before this was + * called. + */ + if (cmux->policy && cmux->policy->free_cmux_data) { + if (cmux->policy_data) { + cmux->policy->free_cmux_data(cmux, cmux->policy_data); + cmux->policy_data = NULL; + } + } else tor_assert(cmux->policy_data == NULL); + + if (cmux->chanid_circid_map) { + HT_CLEAR(chanid_circid_muxinfo_map, cmux->chanid_circid_map); + tor_free(cmux->chanid_circid_map); + } + + tor_free(cmux); +} + +/* + * Circuitmux policy control functions + */ + +/** + * Remove any policy installed on cmux; all policy data will be freed and + * cmux behavior will revert to the built-in round-robin active_circuits + * mechanism. + */ + +void +circuitmux_clear_policy(circuitmux_t *cmux) +{ + tor_assert(cmux); + + /* Internally, this is just setting policy to NULL */ + if (cmux->policy) { + circuitmux_set_policy(cmux, NULL); + } +} + +/** + * Return the policy currently installed on a circuitmux_t + */ + +const circuitmux_policy_t * +circuitmux_get_policy(circuitmux_t *cmux) +{ + tor_assert(cmux); + + return cmux->policy; +} + +/** + * Set policy; allocate for new policy, detach all circuits from old policy + * if any, attach them to new policy, and free old policy data. + */ + +void +circuitmux_set_policy(circuitmux_t *cmux, + const circuitmux_policy_t *pol) +{ + const circuitmux_policy_t *old_pol = NULL, *new_pol = NULL; + circuitmux_policy_data_t *old_pol_data = NULL, *new_pol_data = NULL; + chanid_circid_muxinfo_t **i = NULL; + channel_t *chan = NULL; + uint64_t last_chan_id_searched = 0; + circuit_t *circ = NULL; + + tor_assert(cmux); + + /* Set up variables */ + old_pol = cmux->policy; + old_pol_data = cmux->policy_data; + new_pol = pol; + + /* Check if this is the trivial case */ + if (old_pol == new_pol) return; + + /* Allocate data for new policy, if any */ + if (new_pol && new_pol->alloc_cmux_data) { + /* + * If alloc_cmux_data is not null, then we expect to get some policy + * data. Assert that we also have free_cmux_data so we can free it + * when the time comes, and allocate it. + */ + tor_assert(new_pol->free_cmux_data); + new_pol_data = new_pol->alloc_cmux_data(cmux); + tor_assert(new_pol_data); + } + + /* Install new policy and new policy data on cmux */ + cmux->policy = new_pol; + cmux->policy_data = new_pol_data; + + /* Iterate over all circuits, attaching/detaching each one */ + i = HT_START(chanid_circid_muxinfo_map, cmux->chanid_circid_map); + while (i) { + /* Assert that this entry isn't NULL */ + tor_assert(*i); + + /* + * Get the channel; since normal case is all circuits on the mux share a + * channel, we cache last_chan_id_searched + */ + if (!chan || last_chan_id_searched != (*i)->chan_id) { + chan = channel_find_by_global_id((*i)->chan_id); + last_chan_id_searched = (*i)->chan_id; + } + tor_assert(chan); + + /* Get the circuit */ + circ = circuit_get_by_circid_channel_even_if_marked((*i)->circ_id, chan); + tor_assert(circ); + + /* Need to tell old policy it becomes inactive (i.e., it is active) ? */ + if (old_pol && old_pol->notify_circ_inactive && + (*i)->muxinfo.cell_count > 0) { + old_pol->notify_circ_inactive(cmux, old_pol_data, circ, + (*i)->muxinfo.policy_data); + } + + /* Need to free old policy data? */ + if ((*i)->muxinfo.policy_data) { + /* Assert that we have the means to free it if we have policy data */ + tor_assert(old_pol); + tor_assert(old_pol->free_circ_data); + /* Free it */ + old_pol->free_circ_data(cmux, old_pol_data, circ, + (*i)->muxinfo.policy_data); + (*i)->muxinfo.policy_data = NULL; + } + + /* Need to allocate new policy data? */ + if (new_pol && new_pol->alloc_circ_data) { + /* + * If alloc_circ_data is not null, we expect to get some per-circuit + * policy data. Assert that we also have free_circ_data so we can + * free it when the time comes, and allocate it. + */ + tor_assert(new_pol->free_circ_data); + (*i)->muxinfo.policy_data = + new_pol->alloc_circ_data(cmux, new_pol_data, circ, + (*i)->muxinfo.direction, + (*i)->muxinfo.cell_count); + } + + /* Need to make active on new policy? */ + if (new_pol && new_pol->notify_circ_active && + (*i)->muxinfo.cell_count > 0) { + new_pol->notify_circ_active(cmux, new_pol_data, circ, + (*i)->muxinfo.policy_data); + } + + /* Advance to next circuit map entry */ + i = HT_NEXT(chanid_circid_muxinfo_map, cmux->chanid_circid_map, i); + } + + /* Free data for old policy, if any */ + if (old_pol_data) { + /* + * If we had old policy data, we should have an old policy and a free + * function for it. + */ + tor_assert(old_pol); + tor_assert(old_pol->free_cmux_data); + old_pol->free_cmux_data(cmux, old_pol_data); + old_pol_data = NULL; + } +} + +/* + * Circuitmux/circuit attachment status inquiry functions + */ + +/** + * Query the direction of an attached circuit + */ + +cell_direction_t +circuitmux_attached_circuit_direction(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t *hashent = NULL; + + /* Try to find a map entry */ + hashent = circuitmux_find_map_entry(cmux, circ); + + /* + * This function should only be called on attached circuits; assert that + * we had a map entry. + */ + tor_assert(hashent); + + /* Return the direction from the map entry */ + return hashent->muxinfo.direction; +} + +/** + * Find an entry in the cmux's map for this circuit or return NULL if there + * is none. + */ + +static chanid_circid_muxinfo_t * +circuitmux_find_map_entry(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t search, *hashent = NULL; + + /* Sanity-check parameters */ + tor_assert(cmux); + tor_assert(cmux->chanid_circid_map); + tor_assert(circ); + + /* Check if we have n_chan */ + if (circ->n_chan) { + /* Okay, let's see if it's attached for n_chan/n_circ_id */ + search.chan_id = circ->n_chan->global_identifier; + search.circ_id = circ->n_circ_id; + + /* Query */ + hashent = HT_FIND(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + &search); + } + + /* Found something? */ + if (hashent) { + /* + * Assert that the direction makes sense for a hashent we found by + * n_chan/n_circ_id before we return it. + */ + tor_assert(hashent->muxinfo.direction == CELL_DIRECTION_OUT); + } else { + /* Not there, have we got a p_chan/p_circ_id to try? */ + if (circ->magic == OR_CIRCUIT_MAGIC) { + search.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + /* Check for p_chan */ + if (TO_OR_CIRCUIT(circ)->p_chan) { + search.chan_id = TO_OR_CIRCUIT(circ)->p_chan->global_identifier; + /* Okay, search for that */ + hashent = HT_FIND(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + &search); + /* Find anything? */ + if (hashent) { + /* Assert that the direction makes sense before we return it */ + tor_assert(hashent->muxinfo.direction == CELL_DIRECTION_IN); + } + } + } + } + + /* Okay, hashent is it if it was there */ + return hashent; +} + +/** + * Query whether a circuit is attached to a circuitmux + */ + +int +circuitmux_is_circuit_attached(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t *hashent = NULL; + + /* Look if it's in the circuit map */ + hashent = circuitmux_find_map_entry(cmux, circ); + + return (hashent != NULL); +} + +/** + * Query whether a circuit is active on a circuitmux + */ + +int +circuitmux_is_circuit_active(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t *hashent = NULL; + int is_active = 0; + + tor_assert(cmux); + tor_assert(circ); + + /* Look if it's in the circuit map */ + hashent = circuitmux_find_map_entry(cmux, circ); + if (hashent) { + /* Check the number of cells on this circuit */ + is_active = (hashent->muxinfo.cell_count > 0); + } + /* else not attached, so not active */ + + return is_active; +} + +/** + * Query number of available cells for a circuit on a circuitmux + */ + +unsigned int +circuitmux_num_cells_for_circuit(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t *hashent = NULL; + unsigned int n_cells = 0; + + tor_assert(cmux); + tor_assert(circ); + + /* Look if it's in the circuit map */ + hashent = circuitmux_find_map_entry(cmux, circ); + if (hashent) { + /* Just get the cell count for this circuit */ + n_cells = hashent->muxinfo.cell_count; + } + /* else not attached, so 0 cells */ + + return n_cells; +} + +/** + * Query total number of available cells on a circuitmux + */ + +unsigned int +circuitmux_num_cells(circuitmux_t *cmux) +{ + tor_assert(cmux); + + return cmux->n_cells; +} + +/** + * Query total number of circuits active on a circuitmux + */ + +unsigned int +circuitmux_num_active_circuits(circuitmux_t *cmux) +{ + tor_assert(cmux); + + return cmux->n_active_circuits; +} + +/** + * Query total number of circuits attached to a circuitmux + */ + +unsigned int +circuitmux_num_circuits(circuitmux_t *cmux) +{ + tor_assert(cmux); + + return cmux->n_circuits; +} + +/* + * Functions for circuit code to call to update circuit status + */ + +/** + * Attach a circuit to a circuitmux, for the specified direction. + */ + +void +circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction) +{ + channel_t *chan = NULL; + uint64_t channel_id; + circid_t circ_id; + chanid_circid_muxinfo_t search, *hashent = NULL; + unsigned int cell_count; + + tor_assert(cmux); + tor_assert(circ); + tor_assert(direction == CELL_DIRECTION_IN || + direction == CELL_DIRECTION_OUT); + circuitmux_assert_okay_paranoid(cmux); + + /* + * Figure out which channel we're using, and get the circuit's current + * cell count and circuit ID; assert that the circuit is not already + * attached to another mux. + */ + if (direction == CELL_DIRECTION_OUT) { + /* It's n_chan */ + chan = circ->n_chan; + cell_count = circ->n_chan_cells.n; + circ_id = circ->n_circ_id; + } else { + /* We want p_chan */ + chan = TO_OR_CIRCUIT(circ)->p_chan; + cell_count = TO_OR_CIRCUIT(circ)->p_chan_cells.n; + circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + } + /* Assert that we did get a channel */ + tor_assert(chan); + /* Assert that the circuit ID is sensible */ + tor_assert(circ_id != 0); + + /* Get the channel ID */ + channel_id = chan->global_identifier; + + /* See if we already have this one */ + search.chan_id = channel_id; + search.circ_id = circ_id; + hashent = HT_FIND(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + &search); + + if (hashent) { + /* + * This circuit was already attached to this cmux; make sure the + * directions match and update the cell count and active circuit count. + */ + log_info(LD_CIRC, + "Circuit %u on channel " U64_FORMAT " was already attached to " + "cmux %p (trying to attach to %p)", + circ_id, U64_PRINTF_ARG(channel_id), + ((direction == CELL_DIRECTION_OUT) ? + circ->n_mux : TO_OR_CIRCUIT(circ)->p_mux), + cmux); + + /* + * The mux pointer on this circuit and the direction in result should + * match; otherwise assert. + */ + if (direction == CELL_DIRECTION_OUT) tor_assert(circ->n_mux == cmux); + else tor_assert(TO_OR_CIRCUIT(circ)->p_mux == cmux); + tor_assert(hashent->muxinfo.direction == direction); + + /* + * Looks okay; just update the cell count and active circuits if we must + */ + if (hashent->muxinfo.cell_count > 0 && cell_count == 0) { + --(cmux->n_active_circuits); + circuitmux_make_circuit_inactive(cmux, circ, direction); + } else if (hashent->muxinfo.cell_count == 0 && cell_count > 0) { + ++(cmux->n_active_circuits); + circuitmux_make_circuit_active(cmux, circ, direction); + } + cmux->n_cells -= hashent->muxinfo.cell_count; + cmux->n_cells += cell_count; + hashent->muxinfo.cell_count = cell_count; + } else { + /* + * New circuit; add an entry and update the circuit/active circuit + * counts. + */ + log_debug(LD_CIRC, + "Attaching circuit %u on channel " U64_FORMAT " to cmux %p", + circ_id, U64_PRINTF_ARG(channel_id), cmux); + + /* + * Assert that the circuit doesn't already have a mux for this + * direction. + */ + if (direction == CELL_DIRECTION_OUT) tor_assert(circ->n_mux == NULL); + else tor_assert(TO_OR_CIRCUIT(circ)->p_mux == NULL); + + /* Insert it in the map */ + hashent = tor_malloc_zero(sizeof(*hashent)); + hashent->chan_id = channel_id; + hashent->circ_id = circ_id; + hashent->muxinfo.cell_count = cell_count; + hashent->muxinfo.direction = direction; + /* Allocate policy specific circuit data if we need it */ + if (cmux->policy && cmux->policy->alloc_circ_data) { + /* Assert that we have the means to free policy-specific data */ + tor_assert(cmux->policy->free_circ_data); + /* Allocate it */ + hashent->muxinfo.policy_data = + cmux->policy->alloc_circ_data(cmux, + cmux->policy_data, + circ, + direction, + cell_count); + /* If we wanted policy data, it's an error not to get any */ + tor_assert(hashent->muxinfo.policy_data); + } + HT_INSERT(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + hashent); + + /* Set the circuit's mux for this direction */ + if (direction == CELL_DIRECTION_OUT) circ->n_mux = cmux; + else TO_OR_CIRCUIT(circ)->p_mux = cmux; + + /* Make sure the next/prev pointers are NULL */ + if (direction == CELL_DIRECTION_OUT) { + circ->next_active_on_n_chan = NULL; + circ->prev_active_on_n_chan = NULL; + } else { + TO_OR_CIRCUIT(circ)->next_active_on_p_chan = NULL; + TO_OR_CIRCUIT(circ)->prev_active_on_p_chan = NULL; + } + + /* Update counters */ + ++(cmux->n_circuits); + if (cell_count > 0) { + ++(cmux->n_active_circuits); + circuitmux_make_circuit_active(cmux, circ, direction); + } + cmux->n_cells += cell_count; + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/** + * Detach a circuit from a circuitmux and update all counters as needed; + * no-op if not attached. + */ + +void +circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ) +{ + chanid_circid_muxinfo_t search, *hashent = NULL; + /* + * Use this to keep track of whether we found it for n_chan or + * p_chan for consistency checking. + */ + cell_direction_t last_searched_direction; + + tor_assert(cmux); + tor_assert(cmux->chanid_circid_map); + tor_assert(circ); + circuitmux_assert_okay_paranoid(cmux); + + /* See if we have it for n_chan/n_circ_id */ + if (circ->n_chan) { + search.chan_id = circ->n_chan->global_identifier; + search.circ_id = circ->n_circ_id; + hashent = HT_FIND(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + &search); + last_searched_direction = CELL_DIRECTION_OUT; + } + + /* Got one? If not, see if it's an or_circuit_t and try p_chan/p_circ_id */ + if (!hashent) { + if (circ->magic == OR_CIRCUIT_MAGIC) { + search.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + if (TO_OR_CIRCUIT(circ)->p_chan) { + search.chan_id = TO_OR_CIRCUIT(circ)->p_chan->global_identifier; + hashent = HT_FIND(chanid_circid_muxinfo_map, + cmux->chanid_circid_map, + &search); + last_searched_direction = CELL_DIRECTION_IN; + } + } + } + + /* + * If hashent isn't NULL, we have a circuit to detach; don't remove it from + * the map until later of circuitmux_make_circuit_inactive() breaks. + */ + if (hashent) { + /* Update counters */ + --(cmux->n_circuits); + if (hashent->muxinfo.cell_count > 0) { + --(cmux->n_active_circuits); + /* This does policy notifies, so comes before freeing policy data */ + circuitmux_make_circuit_inactive(cmux, circ, last_searched_direction); + } + cmux->n_cells -= hashent->muxinfo.cell_count; + + /* Free policy-specific data if we have it */ + if (hashent->muxinfo.policy_data) { + /* If we have policy data, assert that we have the means to free it */ + tor_assert(cmux->policy); + tor_assert(cmux->policy->free_circ_data); + /* Call free_circ_data() */ + cmux->policy->free_circ_data(cmux, + cmux->policy_data, + circ, + hashent->muxinfo.policy_data); + hashent->muxinfo.policy_data = NULL; + } + + /* Consistency check: the direction must match the direction searched */ + tor_assert(last_searched_direction == hashent->muxinfo.direction); + /* Clear the circuit's mux for this direction */ + if (last_searched_direction == CELL_DIRECTION_OUT) circ->n_mux = NULL; + else TO_OR_CIRCUIT(circ)->p_mux = NULL; + + /* Now remove it from the map */ + HT_REMOVE(chanid_circid_muxinfo_map, cmux->chanid_circid_map, hashent); + + /* Free the hash entry */ + tor_free(hashent); + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/** + * Make a circuit active; update active list and policy-specific info, but + * we don't mess with the counters or hash table here. + */ + +static void +circuitmux_make_circuit_active(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction) +{ + circuit_t **next_active = NULL, **prev_active = NULL, **next_prev = NULL; + circuitmux_t *circuit_cmux = NULL; + chanid_circid_muxinfo_t *hashent = NULL; + channel_t *chan = NULL; + circid_t circ_id; + int already_active; + + tor_assert(cmux); + tor_assert(circ); + tor_assert(direction == CELL_DIRECTION_OUT || + direction == CELL_DIRECTION_IN); + /* + * Don't circuitmux_assert_okay_paranoid(cmux) here because the cell count + * already got changed and we have to update the list for it to be consistent + * again. + */ + + /* Get the right set of active list links for this direction */ + if (direction == CELL_DIRECTION_OUT) { + next_active = &(circ->next_active_on_n_chan); + prev_active = &(circ->prev_active_on_n_chan); + circuit_cmux = circ->n_mux; + chan = circ->n_chan; + circ_id = circ->n_circ_id; + } else { + next_active = &(TO_OR_CIRCUIT(circ)->next_active_on_p_chan); + prev_active = &(TO_OR_CIRCUIT(circ)->prev_active_on_p_chan); + circuit_cmux = TO_OR_CIRCUIT(circ)->p_mux; + chan = TO_OR_CIRCUIT(circ)->p_chan; + circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + } + + /* Assert that it is attached to this mux and a channel */ + tor_assert(cmux == circuit_cmux); + tor_assert(chan != NULL); + + /* + * Check if the circuit really was inactive; if it's active, at least one + * of the next_active and prev_active pointers will not be NULL, or this + * circuit will be either the head or tail of the list for this cmux. + */ + already_active = (*prev_active != NULL || *next_active != NULL || + cmux->active_circuits_head == circ || + cmux->active_circuits_tail == circ); + + /* If we're already active, log a warning and finish */ + if (already_active) { + log_warn(LD_CIRC, + "Circuit %d on channel " U64_FORMAT " was already active", + circ_id, U64_PRINTF_ARG(chan->global_identifier)); + return; + } + + /* + * This is going at the head of the list; if the old head is not NULL, + * then its prev pointer should point to this. + */ + *next_active = cmux->active_circuits_head; /* Next is old head */ + *prev_active = NULL; /* Prev is NULL (this will be the head) */ + if (cmux->active_circuits_head) { + /* The list had an old head; update its prev pointer */ + next_prev = + circuitmux_prev_active_circ_p(cmux, cmux->active_circuits_head); + tor_assert(next_prev); + *next_prev = circ; + } else { + /* The list was empty; this becomes the tail as well */ + cmux->active_circuits_tail = circ; + } + /* This becomes the new head of the list */ + cmux->active_circuits_head = circ; + + /* Policy-specific notification */ + if (cmux->policy && + cmux->policy->notify_circ_active) { + /* Okay, we need to check the circuit for policy data now */ + hashent = circuitmux_find_map_entry(cmux, circ); + /* We should have found something */ + tor_assert(hashent); + /* Notify */ + cmux->policy->notify_circ_active(cmux, cmux->policy_data, + circ, hashent->muxinfo.policy_data); + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/** + * Make a circuit inactive; update active list and policy-specific info, but + * we don't mess with the counters or hash table here. + */ + +static void +circuitmux_make_circuit_inactive(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction) +{ + circuit_t **next_active = NULL, **prev_active = NULL; + circuit_t **next_prev = NULL, **prev_next = NULL; + circuitmux_t *circuit_cmux = NULL; + chanid_circid_muxinfo_t *hashent = NULL; + channel_t *chan = NULL; + circid_t circ_id; + int already_inactive; + + tor_assert(cmux); + tor_assert(circ); + tor_assert(direction == CELL_DIRECTION_OUT || + direction == CELL_DIRECTION_IN); + /* + * Don't circuitmux_assert_okay_paranoid(cmux) here because the cell count + * already got changed and we have to update the list for it to be consistent + * again. + */ + + /* Get the right set of active list links for this direction */ + if (direction == CELL_DIRECTION_OUT) { + next_active = &(circ->next_active_on_n_chan); + prev_active = &(circ->prev_active_on_n_chan); + circuit_cmux = circ->n_mux; + chan = circ->n_chan; + circ_id = circ->n_circ_id; + } else { + next_active = &(TO_OR_CIRCUIT(circ)->next_active_on_p_chan); + prev_active = &(TO_OR_CIRCUIT(circ)->prev_active_on_p_chan); + circuit_cmux = TO_OR_CIRCUIT(circ)->p_mux; + chan = TO_OR_CIRCUIT(circ)->p_chan; + circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; + } + + /* Assert that it is attached to this mux and a channel */ + tor_assert(cmux == circuit_cmux); + tor_assert(chan != NULL); + + /* + * Check if the circuit really was active; if it's inactive, the + * next_active and prev_active pointers will be NULL and this circuit + * will not be the head or tail of the list for this cmux. + */ + already_inactive = (*prev_active == NULL && *next_active == NULL && + cmux->active_circuits_head != circ && + cmux->active_circuits_tail != circ); + + /* If we're already inactive, log a warning and finish */ + if (already_inactive) { + log_warn(LD_CIRC, + "Circuit %d on channel " U64_FORMAT " was already inactive", + circ_id, U64_PRINTF_ARG(chan->global_identifier)); + return; + } + + /* Remove from the list; first get next_prev and prev_next */ + if (*next_active) { + /* + * If there's a next circuit, its previous circuit becomes this + * circuit's previous circuit. + */ + next_prev = circuitmux_prev_active_circ_p(cmux, *next_active); + } else { + /* Else, the tail becomes this circuit's previous circuit */ + next_prev = &(cmux->active_circuits_tail); + } + + /* Got next_prev, now prev_next */ + if (*prev_active) { + /* + * If there's a previous circuit, its next circuit becomes this circuit's + * next circuit. + */ + prev_next = circuitmux_next_active_circ_p(cmux, *prev_active); + } else { + /* Else, the head becomes this circuit's next circuit */ + prev_next = &(cmux->active_circuits_head); + } + + /* Assert that we got sensible values for the next/prev pointers */ + tor_assert(next_prev != NULL); + tor_assert(prev_next != NULL); + + /* Update the next/prev pointers - this removes circ from the list */ + *next_prev = *prev_active; + *prev_next = *next_active; + + /* Now null out prev_active/next_active */ + *prev_active = NULL; + *next_active = NULL; + + /* Policy-specific notification */ + if (cmux->policy && + cmux->policy->notify_circ_inactive) { + /* Okay, we need to check the circuit for policy data now */ + hashent = circuitmux_find_map_entry(cmux, circ); + /* We should have found something */ + tor_assert(hashent); + /* Notify */ + cmux->policy->notify_circ_inactive(cmux, cmux->policy_data, + circ, hashent->muxinfo.policy_data); + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/** + * Clear the cell counter for a circuit on a circuitmux + */ + +void +circuitmux_clear_num_cells(circuitmux_t *cmux, circuit_t *circ) +{ + /* This is the same as setting the cell count to zero */ + circuitmux_set_num_cells(cmux, circ, 0); +} + +/** + * Set the cell counter for a circuit on a circuitmux + */ + +void +circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, + unsigned int n_cells) +{ + chanid_circid_muxinfo_t *hashent = NULL; + + tor_assert(cmux); + tor_assert(circ); + + circuitmux_assert_okay_paranoid(cmux); + + /* Search for this circuit's entry */ + hashent = circuitmux_find_map_entry(cmux, circ); + /* Assert that we found one */ + tor_assert(hashent); + + /* Update cmux cell counter */ + cmux->n_cells -= hashent->muxinfo.cell_count; + cmux->n_cells += n_cells; + + /* Do we need to notify a cmux policy? */ + if (cmux->policy && cmux->policy->notify_set_n_cells) { + /* Call notify_set_n_cells */ + cmux->policy->notify_set_n_cells(cmux, + cmux->policy_data, + circ, + hashent->muxinfo.policy_data, + n_cells); + } + + /* + * Update cmux active circuit counter: is the old cell count > 0 and the + * new cell count == 0 ? + */ + if (hashent->muxinfo.cell_count > 0 && n_cells == 0) { + --(cmux->n_active_circuits); + hashent->muxinfo.cell_count = n_cells; + circuitmux_make_circuit_inactive(cmux, circ, hashent->muxinfo.direction); + /* Is the old cell count == 0 and the new cell count > 0 ? */ + } else if (hashent->muxinfo.cell_count == 0 && n_cells > 0) { + ++(cmux->n_active_circuits); + hashent->muxinfo.cell_count = n_cells; + circuitmux_make_circuit_active(cmux, circ, hashent->muxinfo.direction); + } else { + /* + * Update the entry cell count like this so we can put a + * circuitmux_assert_okay_paranoid inside make_circuit_(in)active() too. + */ + hashent->muxinfo.cell_count = n_cells; + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/* + * Functions for channel code to call to get a circuit to transmit from or + * notify that cells have been transmitted. + */ + +/** + * Pick a circuit to send from, using the active circuits list or a + * circuitmux policy if one is available. This is called from channel.c. + */ + +circuit_t * +circuitmux_get_first_active_circuit(circuitmux_t *cmux) +{ + circuit_t *circ = NULL; + + tor_assert(cmux); + + if (cmux->n_active_circuits > 0) { + /* We also must have a cell available for this to be the case */ + tor_assert(cmux->n_cells > 0); + /* Do we have a policy-provided circuit selector? */ + if (cmux->policy && cmux->policy->pick_active_circuit) { + circ = cmux->policy->pick_active_circuit(cmux, cmux->policy_data); + } + /* Fall back on the head of the active circuits list */ + if (!circ) { + tor_assert(cmux->active_circuits_head); + circ = cmux->active_circuits_head; + } + } else tor_assert(cmux->n_cells == 0); + + return circ; +} + +/** + * Notify the circuitmux that cells have been sent on a circuit; this + * is called from channel.c. + */ + +void +circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, + unsigned int n_cells) +{ + chanid_circid_muxinfo_t *hashent = NULL; + int becomes_inactive = 0; + + tor_assert(cmux); + tor_assert(circ); + circuitmux_assert_okay_paranoid(cmux); + + if (n_cells == 0) return; + + /* + * To handle this, we have to: + * + * 1.) Adjust the circuit's cell counter in the cmux hash table + * 2.) Move the circuit to the tail of the active_circuits linked list + * for this cmux, or make the circuit inactive if the cell count + * went to zero. + * 3.) Call cmux->policy->notify_xmit_cells(), if any + */ + + /* Find the hash entry */ + hashent = circuitmux_find_map_entry(cmux, circ); + /* Assert that we found one */ + tor_assert(hashent); + + /* Adjust the cell counter and assert that we had that many cells to send */ + tor_assert(n_cells <= hashent->muxinfo.cell_count); + hashent->muxinfo.cell_count -= n_cells; + /* Do we need to make the circuit inactive? */ + if (hashent->muxinfo.cell_count == 0) becomes_inactive = 1; + /* Adjust the mux cell counter */ + cmux->n_cells -= n_cells; + + /* If we aren't making it inactive later, move it to the tail of the list */ + if (!becomes_inactive) { + circuitmux_move_active_circ_to_tail(cmux, circ, + hashent->muxinfo.direction); + } + + /* + * We call notify_xmit_cells() before making the circuit inactive if needed, + * so the policy can always count on this coming in on an active circuit. + */ + if (cmux->policy && cmux->policy->notify_xmit_cells) { + cmux->policy->notify_xmit_cells(cmux, cmux->policy_data, circ, + hashent->muxinfo.policy_data, + n_cells); + } + + /* + * Now make the circuit inactive if needed; this will call the policy's + * notify_circ_inactive() if present. + */ + if (becomes_inactive) { + --(cmux->n_active_circuits); + circuitmux_make_circuit_inactive(cmux, circ, hashent->muxinfo.direction); + } + + circuitmux_assert_okay_paranoid(cmux); +} + +/* + * Circuitmux consistency checking assertions + */ + +/** + * Check that circuitmux data structures are consistent and fail with an + * assert if not. + */ + +void +circuitmux_assert_okay(circuitmux_t *cmux) +{ + tor_assert(cmux); + + /* + * Pass 1: iterate the hash table; for each entry: + * a) Check that the circuit has this cmux for n_mux or p_mux + * b) If the cell_count is > 0, set the mark bit; otherwise clear it + * c) Also check activeness (cell_count > 0 should be active) + * d) Count the number of circuits, active circuits and queued cells + * and at the end check that they match the counters in the cmux. + * + * Pass 2: iterate the active circuits list; for each entry, + * make sure the circuit is attached to this mux and appears + * in the hash table. Make sure the mark bit is 1, and clear + * it in the hash table entry. Consistency-check the linked + * list pointers. + * + * Pass 3: iterate the hash table again; assert if any active circuits + * (mark bit set to 1) are discovered that weren't cleared in pass 2 + * (don't appear in the linked list). + */ + + circuitmux_assert_okay_pass_one(cmux); + circuitmux_assert_okay_pass_two(cmux); + circuitmux_assert_okay_pass_three(cmux); +} + +/** + * Do the first pass of circuitmux_assert_okay(); see the comment in that + * function. + */ + +static void +circuitmux_assert_okay_pass_one(circuitmux_t *cmux) +{ + chanid_circid_muxinfo_t **i = NULL; + uint64_t chan_id; + channel_t *chan; + circid_t circ_id; + circuit_t *circ; + or_circuit_t *or_circ; + unsigned int circ_is_active; + circuit_t **next_p, **prev_p; + unsigned int n_circuits, n_active_circuits, n_cells; + + tor_assert(cmux); + tor_assert(cmux->chanid_circid_map); + + /* Reset the counters */ + n_circuits = n_active_circuits = n_cells = 0; + /* Start iterating the hash table */ + i = HT_START(chanid_circid_muxinfo_map, cmux->chanid_circid_map); + while (i) { + /* Assert that the hash table entry isn't null */ + tor_assert(*i); + + /* Get the channel and circuit id */ + chan_id = (*i)->chan_id; + circ_id = (*i)->circ_id; + + /* Find the channel and circuit, assert that they exist */ + chan = channel_find_by_global_id(chan_id); + tor_assert(chan); + circ = circuit_get_by_circid_channel_even_if_marked(circ_id, chan); + tor_assert(circ); + /* Clear the circ_is_active bit to start */ + circ_is_active = 0; + + /* Assert that we know which direction this is going */ + tor_assert((*i)->muxinfo.direction == CELL_DIRECTION_OUT || + (*i)->muxinfo.direction == CELL_DIRECTION_IN); + + if ((*i)->muxinfo.direction == CELL_DIRECTION_OUT) { + /* We should be n_mux on this circuit */ + tor_assert(cmux == circ->n_mux); + tor_assert(chan == circ->n_chan); + /* Get next and prev for next test */ + next_p = &(circ->next_active_on_n_chan); + prev_p = &(circ->prev_active_on_n_chan); + } else { + /* This should be an or_circuit_t and we should be p_mux */ + or_circ = TO_OR_CIRCUIT(circ); + tor_assert(cmux == or_circ->p_mux); + tor_assert(chan == or_circ->p_chan); + /* Get next and prev for next test */ + next_p = &(or_circ->next_active_on_p_chan); + prev_p = &(or_circ->prev_active_on_p_chan); + } + + /* + * Should this circuit be active? I.e., does the mux know about > 0 + * cells on it? + */ + circ_is_active = ((*i)->muxinfo.cell_count > 0); + + /* It should be in the linked list iff it's active */ + if (circ_is_active) { + /* Either we have a next link or we are the tail */ + tor_assert(*next_p || (circ == cmux->active_circuits_tail)); + /* Either we have a prev link or we are the head */ + tor_assert(*prev_p || (circ == cmux->active_circuits_head)); + /* Increment the active circuits counter */ + ++n_active_circuits; + } else { + /* Shouldn't be in list, so no next or prev link */ + tor_assert(!(*next_p)); + tor_assert(!(*prev_p)); + /* And can't be head or tail */ + tor_assert(circ != cmux->active_circuits_head); + tor_assert(circ != cmux->active_circuits_tail); + } + + /* Increment the circuits counter */ + ++n_circuits; + /* Adjust the cell counter */ + n_cells += (*i)->muxinfo.cell_count; + + /* Set the mark bit to circ_is_active */ + (*i)->muxinfo.mark = circ_is_active; + + /* Advance to the next entry */ + i = HT_NEXT(chanid_circid_muxinfo_map, cmux->chanid_circid_map, i); + } + + /* Now check the counters */ + tor_assert(n_cells == cmux->n_cells); + tor_assert(n_circuits == cmux->n_circuits); + tor_assert(n_active_circuits == cmux->n_active_circuits); +} + +/** + * Do the second pass of circuitmux_assert_okay(); see the comment in that + * function. + */ + +static void +circuitmux_assert_okay_pass_two(circuitmux_t *cmux) +{ + circuit_t *curr_circ, *prev_circ = NULL, *next_circ; + or_circuit_t *curr_or_circ; + uint64_t curr_chan_id; + circid_t curr_circ_id; + circuit_t **next_p, **prev_p; + channel_t *chan; + unsigned int n_active_circuits = 0; + cell_direction_t direction; + chanid_circid_muxinfo_t search, *hashent = NULL; + + tor_assert(cmux); + tor_assert(cmux->chanid_circid_map); + + /* + * Walk the linked list of active circuits in cmux; keep track of the + * previous circuit seen for consistency checking purposes. Count them + * to make sure the number in the linked list matches + * cmux->n_active_circuits. + */ + curr_circ = cmux->active_circuits_head; + while (curr_circ) { + /* Reset some things */ + chan = NULL; + curr_or_circ = NULL; + next_circ = NULL; + next_p = prev_p = NULL; + direction = 0; + + /* Figure out if this is n_mux or p_mux */ + if (cmux == curr_circ->n_mux) { + /* Get next_p and prev_p */ + next_p = &(curr_circ->next_active_on_n_chan); + prev_p = &(curr_circ->prev_active_on_n_chan); + /* Get the channel */ + chan = curr_circ->n_chan; + /* Get the circuit id */ + curr_circ_id = curr_circ->n_circ_id; + /* Remember the direction */ + direction = CELL_DIRECTION_OUT; + } else { + /* We must be p_mux and this must be an or_circuit_t */ + curr_or_circ = TO_OR_CIRCUIT(curr_circ); + tor_assert(cmux == curr_or_circ->p_mux); + /* Get next_p and prev_p */ + next_p = &(curr_or_circ->next_active_on_p_chan); + prev_p = &(curr_or_circ->prev_active_on_p_chan); + /* Get the channel */ + chan = curr_or_circ->p_chan; + /* Get the circuit id */ + curr_circ_id = curr_or_circ->p_circ_id; + /* Remember the direction */ + direction = CELL_DIRECTION_IN; + } + + /* Assert that we got a channel and get the channel ID */ + tor_assert(chan); + curr_chan_id = chan->global_identifier; + + /* Assert that prev_p points to last circuit we saw */ + tor_assert(*prev_p == prev_circ); + /* If that's NULL, assert that we are the head */ + if (!(*prev_p)) tor_assert(curr_circ == cmux->active_circuits_head); + + /* Get the next circuit */ + next_circ = *next_p; + /* If it's NULL, assert that we are the tail */ + if (!(*next_p)) tor_assert(curr_circ == cmux->active_circuits_tail); + + /* Now find the hash table entry for this circuit */ + search.chan_id = curr_chan_id; + search.circ_id = curr_circ_id; + hashent = HT_FIND(chanid_circid_muxinfo_map, cmux->chanid_circid_map, + &search); + + /* Assert that we have one */ + tor_assert(hashent); + + /* Assert that the direction matches */ + tor_assert(direction == hashent->muxinfo.direction); + + /* Assert that the hash entry got marked in pass one */ + tor_assert(hashent->muxinfo.mark); + + /* Clear the mark */ + hashent->muxinfo.mark = 0; + + /* Increment the counter */ + ++n_active_circuits; + + /* Advance to the next active circuit and update prev_circ */ + prev_circ = curr_circ; + curr_circ = next_circ; + } + + /* Assert that the counter matches the cmux */ + tor_assert(n_active_circuits == cmux->n_active_circuits); +} + +/** + * Do the third pass of circuitmux_assert_okay(); see the comment in that + * function. + */ + +static void +circuitmux_assert_okay_pass_three(circuitmux_t *cmux) +{ + chanid_circid_muxinfo_t **i = NULL; + + tor_assert(cmux); + tor_assert(cmux->chanid_circid_map); + + /* Start iterating the hash table */ + i = HT_START(chanid_circid_muxinfo_map, cmux->chanid_circid_map); + + /* Advance through each entry */ + while (i) { + /* Assert that it isn't null */ + tor_assert(*i); + + /* + * Assert that this entry is not marked - i.e., that either we didn't + * think it should be active in pass one or we saw it in the active + * circuits linked list. + */ + tor_assert(!((*i)->muxinfo.mark)); + + /* Advance to the next entry */ + i = HT_NEXT(chanid_circid_muxinfo_map, cmux->chanid_circid_map, i); + } +} + diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h new file mode 100644 index 000000000..ebab1ba7d --- /dev/null +++ b/src/or/circuitmux.h @@ -0,0 +1,136 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitmux.h + * \brief Header file for circuitmux.c + **/ + +#ifndef TOR_CIRCUITMUX_H +#define TOR_CIRCUITMUX_H + +#include "or.h" + +typedef struct circuitmux_policy_s circuitmux_policy_t; +typedef struct circuitmux_policy_data_s circuitmux_policy_data_t; +typedef struct circuitmux_policy_circ_data_s circuitmux_policy_circ_data_t; + +struct circuitmux_policy_s { + /* Allocate cmux-wide policy-specific data */ + circuitmux_policy_data_t * (*alloc_cmux_data)(circuitmux_t *cmux); + /* Free cmux-wide policy-specific data */ + void (*free_cmux_data)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data); + /* Allocate circuit policy-specific data for a newly attached circuit */ + circuitmux_policy_circ_data_t * + (*alloc_circ_data)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + cell_direction_t direction, + unsigned int cell_count); + /* Free circuit policy-specific data */ + void (*free_circ_data)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); + /* Notify that a circuit has become active/inactive */ + void (*notify_circ_active)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); + void (*notify_circ_inactive)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); + /* Notify of arriving/transmitted cells on a circuit */ + void (*notify_set_n_cells)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data, + unsigned int n_cells); + void (*notify_xmit_cells)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data, + unsigned int n_cells); + /* Choose a circuit */ + circuit_t * (*pick_active_circuit)(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data); +}; + +/* + * Circuitmux policy implementations can subclass this to store circuitmux- + * wide data; it just has the magic number in the base struct. + */ + +struct circuitmux_policy_data_s { + uint32_t magic; +}; + +/* + * Circuitmux policy implementations can subclass this to store circuit- + * specific data; it just has the magic number in the base struct. + */ + +struct circuitmux_policy_circ_data_s { + uint32_t magic; +}; + +/* + * Upcast #defines for the above types + */ + +/** + * Convert a circuitmux_policy_data_t subtype to a circuitmux_policy_data_t. + */ + +#define TO_CMUX_POL_DATA(x) (&((x)->base_)) + +/** + * Convert a circuitmux_policy_circ_data_t subtype to a + * circuitmux_policy_circ_data_t. + */ + +#define TO_CMUX_POL_CIRC_DATA(x) (&((x)->base_)) + +/* Consistency check */ +void circuitmux_assert_okay(circuitmux_t *cmux); + +/* Create/destroy */ +circuitmux_t * circuitmux_alloc(void); +void circuitmux_detach_all_circuits(circuitmux_t *cmux); +void circuitmux_free(circuitmux_t *cmux); + +/* Policy control */ +void circuitmux_clear_policy(circuitmux_t *cmux); +const circuitmux_policy_t * circuitmux_get_policy(circuitmux_t *cmux); +void circuitmux_set_policy(circuitmux_t *cmux, + const circuitmux_policy_t *pol); + +/* Status inquiries */ +cell_direction_t circuitmux_attached_circuit_direction( + circuitmux_t *cmux, + circuit_t *circ); +int circuitmux_is_circuit_attached(circuitmux_t *cmux, circuit_t *circ); +int circuitmux_is_circuit_active(circuitmux_t *cmux, circuit_t *circ); +unsigned int circuitmux_num_cells_for_circuit(circuitmux_t *cmux, + circuit_t *circ); +unsigned int circuitmux_num_cells(circuitmux_t *cmux); +unsigned int circuitmux_num_circuits(circuitmux_t *cmux); +unsigned int circuitmux_num_active_circuits(circuitmux_t *cmux); + +/* Channel interface */ +circuit_t * circuitmux_get_first_active_circuit(circuitmux_t *cmux); +void circuitmux_notify_xmit_cells(circuitmux_t *cmux, circuit_t *circ, + unsigned int n_cells); + +/* Circuit interface */ +void circuitmux_attach_circuit(circuitmux_t *cmux, circuit_t *circ, + cell_direction_t direction); +void circuitmux_detach_circuit(circuitmux_t *cmux, circuit_t *circ); +void circuitmux_clear_num_cells(circuitmux_t *cmux, circuit_t *circ); +void circuitmux_set_num_cells(circuitmux_t *cmux, circuit_t *circ, + unsigned int n_cells); + +#endif /* TOR_CIRCUITMUX_H */ + diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c new file mode 100644 index 000000000..e1964d238 --- /dev/null +++ b/src/or/circuitmux_ewma.c @@ -0,0 +1,684 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitmux_ewma.c + * \brief EWMA circuit selection as a circuitmux_t policy + **/ + +#define TOR_CIRCUITMUX_EWMA_C_ + +#include <math.h> + +#include "or.h" +#include "circuitmux.h" +#include "circuitmux_ewma.h" +#include "networkstatus.h" + +/*** EWMA parameter #defines ***/ + +/** How long does a tick last (seconds)? */ +#define EWMA_TICK_LEN 10 + +/** The default per-tick scale factor, if it hasn't been overridden by a + * consensus or a configuration setting. zero means "disabled". */ +#define EWMA_DEFAULT_HALFLIFE 0.0 + +/*** Some useful constant #defines ***/ + +/*DOCDOC*/ +#define EPSILON 0.00001 +/*DOCDOC*/ +#define LOG_ONEHALF -0.69314718055994529 + +/*** EWMA structures ***/ + +typedef struct cell_ewma_s cell_ewma_t; +typedef struct ewma_policy_data_s ewma_policy_data_t; +typedef struct ewma_policy_circ_data_s ewma_policy_circ_data_t; + +/** + * The cell_ewma_t structure keeps track of how many cells a circuit has + * transferred recently. It keeps an EWMA (exponentially weighted moving + * average) of the number of cells flushed from the circuit queue onto a + * connection in channel_flush_from_first_active_circuit(). + */ + +struct cell_ewma_s { + /** The last 'tick' at which we recalibrated cell_count. + * + * A cell sent at exactly the start of this tick has weight 1.0. Cells sent + * since the start of this tick have weight greater than 1.0; ones sent + * earlier have less weight. */ + unsigned int last_adjusted_tick; + /** The EWMA of the cell count. */ + double cell_count; + /** True iff this is the cell count for a circuit's previous + * channel. */ + unsigned int is_for_p_chan : 1; + /** The position of the circuit within the OR connection's priority + * queue. */ + int heap_index; +}; + +struct ewma_policy_data_s { + circuitmux_policy_data_t base_; + + /** + * Priority queue of cell_ewma_t for circuits with queued cells waiting + * for room to free up on the channel that owns this circuitmux. Kept + * in heap order according to EWMA. This was formerly in channel_t, and + * in or_connection_t before that. + */ + smartlist_t *active_circuit_pqueue; + + /** + * The tick on which the cell_ewma_ts in active_circuit_pqueue last had + * their ewma values rescaled. This was formerly in channel_t, and in + * or_connection_t before that. + */ + unsigned int active_circuit_pqueue_last_recalibrated; +}; + +struct ewma_policy_circ_data_s { + circuitmux_policy_circ_data_t base_; + + /** + * The EWMA count for the number of cells flushed from this circuit + * onto this circuitmux. Used to determine which circuit to flush + * from next. This was formerly in circuit_t and or_circuit_t. + */ + cell_ewma_t cell_ewma; + + /** + * Pointer back to the circuit_t this is for; since we're separating + * out circuit selection policy like this, we can't attach cell_ewma_t + * to the circuit_t any more, so we can't use SUBTYPE_P directly to a + * circuit_t like before; instead get it here. + */ + circuit_t *circ; +}; + +#define EWMA_POL_DATA_MAGIC 0x2fd8b16aU +#define EWMA_POL_CIRC_DATA_MAGIC 0x761e7747U + +/*** Downcasts for the above types ***/ + +static ewma_policy_data_t * +TO_EWMA_POL_DATA(circuitmux_policy_data_t *); + +static ewma_policy_circ_data_t * +TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *); + +/** + * Downcast a circuitmux_policy_data_t to an ewma_policy_data_t and assert + * if the cast is impossible. + */ + +static INLINE ewma_policy_data_t * +TO_EWMA_POL_DATA(circuitmux_policy_data_t *pol) +{ + if (!pol) return NULL; + else { + tor_assert(pol->magic == EWMA_POL_DATA_MAGIC); + return DOWNCAST(ewma_policy_data_t, pol); + } +} + +/** + * Downcast a circuitmux_policy_circ_data_t to an ewma_policy_circ_data_t + * and assert if the cast is impossible. + */ + +static INLINE ewma_policy_circ_data_t * +TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *pol) +{ + if (!pol) return NULL; + else { + tor_assert(pol->magic == EWMA_POL_CIRC_DATA_MAGIC); + return DOWNCAST(ewma_policy_circ_data_t, pol); + } +} + +/*** Static declarations for circuitmux_ewma.c ***/ + +static void add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma); +static int compare_cell_ewma_counts(const void *p1, const void *p2); +static unsigned cell_ewma_tick_from_timeval(const struct timeval *now, + double *remainder_out); +static circuit_t * cell_ewma_to_circuit(cell_ewma_t *ewma); +static INLINE double get_scale_factor(unsigned from_tick, unsigned to_tick); +static cell_ewma_t * pop_first_cell_ewma(ewma_policy_data_t *pol); +static void remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma); +static void scale_single_cell_ewma(cell_ewma_t *ewma, unsigned cur_tick); +static void scale_active_circuits(ewma_policy_data_t *pol, + unsigned cur_tick); + +/*** Circuitmux policy methods ***/ + +static circuitmux_policy_data_t * ewma_alloc_cmux_data(circuitmux_t *cmux); +static void ewma_free_cmux_data(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data); +static circuitmux_policy_circ_data_t * +ewma_alloc_circ_data(circuitmux_t *cmux, circuitmux_policy_data_t *pol_data, + circuit_t *circ, cell_direction_t direction, + unsigned int cell_count); +static void +ewma_free_circ_data(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); +static void +ewma_notify_circ_active(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); +static void +ewma_notify_circ_inactive(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data); +static void +ewma_notify_xmit_cells(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data, + unsigned int n_cells); +static circuit_t * +ewma_pick_active_circuit(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data); + +/*** EWMA global variables ***/ + +/** The per-tick scale factor to be used when computing cell-count EWMA + * values. (A cell sent N ticks before the start of the current tick + * has value ewma_scale_factor ** N.) + */ +static double ewma_scale_factor = 0.1; +/* DOCDOC ewma_enabled */ +static int ewma_enabled = 0; + +/*** EWMA circuitmux_policy_t method table ***/ + +circuitmux_policy_t ewma_policy = { + /*.alloc_cmux_data =*/ ewma_alloc_cmux_data, + /*.free_cmux_data =*/ ewma_free_cmux_data, + /*.alloc_circ_data =*/ ewma_alloc_circ_data, + /*.free_circ_data =*/ ewma_free_circ_data, + /*.notify_circ_active =*/ ewma_notify_circ_active, + /*.notify_circ_inactive =*/ ewma_notify_circ_inactive, + /*.notify_set_n_cells =*/ NULL, /* EWMA doesn't need this */ + /*.notify_xmit_cells =*/ ewma_notify_xmit_cells, + /*.pick_active_circuit =*/ ewma_pick_active_circuit +}; + +/*** EWMA method implementations using the below EWMA helper functions ***/ + +/** + * Allocate an ewma_policy_data_t and upcast it to a circuitmux_policy_data_t; + * this is called when setting the policy on a circuitmux_t to ewma_policy. + */ + +static circuitmux_policy_data_t * +ewma_alloc_cmux_data(circuitmux_t *cmux) +{ + ewma_policy_data_t *pol = NULL; + + tor_assert(cmux); + + pol = tor_malloc_zero(sizeof(*pol)); + pol->base_.magic = EWMA_POL_DATA_MAGIC; + pol->active_circuit_pqueue = smartlist_new(); + pol->active_circuit_pqueue_last_recalibrated = cell_ewma_get_tick(); + + return TO_CMUX_POL_DATA(pol); +} + +/** + * Free an ewma_policy_data_t allocated with ewma_alloc_cmux_data() + */ + +static void +ewma_free_cmux_data(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data) +{ + ewma_policy_data_t *pol = NULL; + + tor_assert(cmux); + if (!pol_data) return; + + pol = TO_EWMA_POL_DATA(pol_data); + + smartlist_free(pol->active_circuit_pqueue); + tor_free(pol); +} + +/** + * Allocate an ewma_policy_circ_data_t and upcast it to a + * circuitmux_policy_data_t; this is called when attaching a circuit to a + * circuitmux_t with ewma_policy. + */ + +static circuitmux_policy_circ_data_t * +ewma_alloc_circ_data(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + cell_direction_t direction, + unsigned int cell_count) +{ + ewma_policy_circ_data_t *cdata = NULL; + + tor_assert(cmux); + tor_assert(pol_data); + tor_assert(circ); + tor_assert(direction == CELL_DIRECTION_OUT || + direction == CELL_DIRECTION_IN); + /* Shut the compiler up */ + tor_assert(cell_count == cell_count); + + cdata = tor_malloc_zero(sizeof(*cdata)); + cdata->base_.magic = EWMA_POL_CIRC_DATA_MAGIC; + cdata->circ = circ; + + /* + * Initialize the cell_ewma_t structure (formerly in + * init_circuit_base()) + */ + cdata->cell_ewma.last_adjusted_tick = cell_ewma_get_tick(); + cdata->cell_ewma.cell_count = 0.0; + cdata->cell_ewma.heap_index = -1; + if (direction == CELL_DIRECTION_IN) { + cdata->cell_ewma.is_for_p_chan = 1; + } else { + cdata->cell_ewma.is_for_p_chan = 0; + } + + return TO_CMUX_POL_CIRC_DATA(cdata); +} + +/** + * Free an ewma_policy_circ_data_t allocated with ewma_alloc_circ_data() + */ + +static void +ewma_free_circ_data(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data) + +{ + ewma_policy_circ_data_t *cdata = NULL; + + tor_assert(cmux); + tor_assert(circ); + tor_assert(pol_data); + + if (!pol_circ_data) return; + + cdata = TO_EWMA_POL_CIRC_DATA(pol_circ_data); + + tor_free(cdata); +} + +/** + * Handle circuit activation; this inserts the circuit's cell_ewma into + * the active_circuits_pqueue. + */ + +static void +ewma_notify_circ_active(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data) +{ + ewma_policy_data_t *pol = NULL; + ewma_policy_circ_data_t *cdata = NULL; + + tor_assert(cmux); + tor_assert(pol_data); + tor_assert(circ); + tor_assert(pol_circ_data); + + pol = TO_EWMA_POL_DATA(pol_data); + cdata = TO_EWMA_POL_CIRC_DATA(pol_circ_data); + + add_cell_ewma(pol, &(cdata->cell_ewma)); +} + +/** + * Handle circuit deactivation; this removes the circuit's cell_ewma from + * the active_circuits_pqueue. + */ + +static void +ewma_notify_circ_inactive(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data) +{ + ewma_policy_data_t *pol = NULL; + ewma_policy_circ_data_t *cdata = NULL; + + tor_assert(cmux); + tor_assert(pol_data); + tor_assert(circ); + tor_assert(pol_circ_data); + + pol = TO_EWMA_POL_DATA(pol_data); + cdata = TO_EWMA_POL_CIRC_DATA(pol_circ_data); + + remove_cell_ewma(pol, &(cdata->cell_ewma)); +} + +/** + * Update cell_ewma for this circuit after we've sent some cells, and + * remove/reinsert it in the queue. This used to be done (brokenly, + * see bug 6816) in channel_flush_from_first_active_circuit(). + */ + +static void +ewma_notify_xmit_cells(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data, + circuit_t *circ, + circuitmux_policy_circ_data_t *pol_circ_data, + unsigned int n_cells) +{ + ewma_policy_data_t *pol = NULL; + ewma_policy_circ_data_t *cdata = NULL; + unsigned int tick; + double fractional_tick, ewma_increment; + /* The current (hi-res) time */ + struct timeval now_hires; + cell_ewma_t *cell_ewma, *tmp; + + tor_assert(cmux); + tor_assert(pol_data); + tor_assert(circ); + tor_assert(pol_circ_data); + tor_assert(n_cells > 0); + + pol = TO_EWMA_POL_DATA(pol_data); + cdata = TO_EWMA_POL_CIRC_DATA(pol_circ_data); + + /* Rescale the EWMAs if needed */ + tor_gettimeofday_cached(&now_hires); + tick = cell_ewma_tick_from_timeval(&now_hires, &fractional_tick); + + if (tick != pol->active_circuit_pqueue_last_recalibrated) { + scale_active_circuits(pol, tick); + } + + /* How much do we adjust the cell count in cell_ewma by? */ + ewma_increment = + ((double)(n_cells)) * pow(ewma_scale_factor, -fractional_tick); + + /* Do the adjustment */ + cell_ewma = &(cdata->cell_ewma); + cell_ewma->cell_count += ewma_increment; + + /* + * Since we just sent on this circuit, it should be at the head of + * the queue. Pop the head, assert that it matches, then re-add. + */ + tmp = pop_first_cell_ewma(pol); + tor_assert(tmp == cell_ewma); + add_cell_ewma(pol, cell_ewma); +} + +/** + * Pick the preferred circuit to send from; this will be the one with + * the lowest EWMA value in the priority queue. This used to be done + * in channel_flush_from_first_active_circuit(). + */ + +static circuit_t * +ewma_pick_active_circuit(circuitmux_t *cmux, + circuitmux_policy_data_t *pol_data) +{ + ewma_policy_data_t *pol = NULL; + circuit_t *circ = NULL; + cell_ewma_t *cell_ewma = NULL; + + tor_assert(cmux); + tor_assert(pol_data); + + pol = TO_EWMA_POL_DATA(pol_data); + + if (smartlist_len(pol->active_circuit_pqueue) > 0) { + /* Get the head of the queue */ + cell_ewma = smartlist_get(pol->active_circuit_pqueue, 0); + circ = cell_ewma_to_circuit(cell_ewma); + } + + return circ; +} + +/** Helper for sorting cell_ewma_t values in their priority queue. */ +static int +compare_cell_ewma_counts(const void *p1, const void *p2) +{ + const cell_ewma_t *e1 = p1, *e2 = p2; + + if (e1->cell_count < e2->cell_count) + return -1; + else if (e1->cell_count > e2->cell_count) + return 1; + else + return 0; +} + +/** Given a cell_ewma_t, return a pointer to the circuit containing it. */ +static circuit_t * +cell_ewma_to_circuit(cell_ewma_t *ewma) +{ + ewma_policy_circ_data_t *cdata = NULL; + + tor_assert(ewma); + cdata = SUBTYPE_P(ewma, ewma_policy_circ_data_t, cell_ewma); + tor_assert(cdata); + + return cdata->circ; +} + +/* ==== Functions for scaling cell_ewma_t ==== + + When choosing which cells to relay first, we favor circuits that have been + quiet recently. This gives better latency on connections that aren't + pushing lots of data, and makes the network feel more interactive. + + Conceptually, we take an exponentially weighted mean average of the number + of cells a circuit has sent, and allow active circuits (those with cells to + relay) to send cells in reverse order of their exponentially-weighted mean + average (EWMA) cell count. [That is, a cell sent N seconds ago 'counts' + F^N times as much as a cell sent now, for 0<F<1.0, and we favor the + circuit that has sent the fewest cells] + + If 'double' had infinite precision, we could do this simply by counting a + cell sent at startup as having weight 1.0, and a cell sent N seconds later + as having weight F^-N. This way, we would never need to re-scale + any already-sent cells. + + To prevent double from overflowing, we could count a cell sent now as + having weight 1.0 and a cell sent N seconds ago as having weight F^N. + This, however, would mean we'd need to re-scale *ALL* old circuits every + time we wanted to send a cell. + + So as a compromise, we divide time into 'ticks' (currently, 10-second + increments) and say that a cell sent at the start of a current tick is + worth 1.0, a cell sent N seconds before the start of the current tick is + worth F^N, and a cell sent N seconds after the start of the current tick is + worth F^-N. This way we don't overflow, and we don't need to constantly + rescale. + */ + +/** Given a timeval <b>now</b>, compute the cell_ewma tick in which it occurs + * and the fraction of the tick that has elapsed between the start of the tick + * and <b>now</b>. Return the former and store the latter in + * *<b>remainder_out</b>. + * + * These tick values are not meant to be shared between Tor instances, or used + * for other purposes. */ + +static unsigned +cell_ewma_tick_from_timeval(const struct timeval *now, + double *remainder_out) +{ + unsigned res = (unsigned) (now->tv_sec / EWMA_TICK_LEN); + /* rem */ + double rem = (now->tv_sec % EWMA_TICK_LEN) + + ((double)(now->tv_usec)) / 1.0e6; + *remainder_out = rem / EWMA_TICK_LEN; + return res; +} + +/** Tell the caller whether ewma_enabled is set */ +int +cell_ewma_enabled(void) +{ + return ewma_enabled; +} + +/** Compute and return the current cell_ewma tick. */ +unsigned int +cell_ewma_get_tick(void) +{ + return ((unsigned)approx_time() / EWMA_TICK_LEN); +} + +/** Adjust the global cell scale factor based on <b>options</b> */ +void +cell_ewma_set_scale_factor(const or_options_t *options, + const networkstatus_t *consensus) +{ + int32_t halflife_ms; + double halflife; + const char *source; + if (options && options->CircuitPriorityHalflife >= -EPSILON) { + halflife = options->CircuitPriorityHalflife; + source = "CircuitPriorityHalflife in configuration"; + } else if (consensus && (halflife_ms = networkstatus_get_param( + consensus, "CircuitPriorityHalflifeMsec", + -1, -1, INT32_MAX)) >= 0) { + halflife = ((double)halflife_ms)/1000.0; + source = "CircuitPriorityHalflifeMsec in consensus"; + } else { + halflife = EWMA_DEFAULT_HALFLIFE; + source = "Default value"; + } + + if (halflife <= EPSILON) { + /* The cell EWMA algorithm is disabled. */ + ewma_scale_factor = 0.1; + ewma_enabled = 0; + log_info(LD_OR, + "Disabled cell_ewma algorithm because of value in %s", + source); + } else { + /* convert halflife into halflife-per-tick. */ + halflife /= EWMA_TICK_LEN; + /* compute per-tick scale factor. */ + ewma_scale_factor = exp( LOG_ONEHALF / halflife ); + ewma_enabled = 1; + log_info(LD_OR, + "Enabled cell_ewma algorithm because of value in %s; " + "scale factor is %f per %d seconds", + source, ewma_scale_factor, EWMA_TICK_LEN); + } +} + +/** Return the multiplier necessary to convert the value of a cell sent in + * 'from_tick' to one sent in 'to_tick'. */ +static INLINE double +get_scale_factor(unsigned from_tick, unsigned to_tick) +{ + /* This math can wrap around, but that's okay: unsigned overflow is + well-defined */ + int diff = (int)(to_tick - from_tick); + return pow(ewma_scale_factor, diff); +} + +/** Adjust the cell count of <b>ewma</b> so that it is scaled with respect to + * <b>cur_tick</b> */ +static void +scale_single_cell_ewma(cell_ewma_t *ewma, unsigned cur_tick) +{ + double factor = get_scale_factor(ewma->last_adjusted_tick, cur_tick); + ewma->cell_count *= factor; + ewma->last_adjusted_tick = cur_tick; +} + +/** Adjust the cell count of every active circuit on <b>chan</b> so + * that they are scaled with respect to <b>cur_tick</b> */ +static void +scale_active_circuits(ewma_policy_data_t *pol, unsigned cur_tick) +{ + double factor; + + tor_assert(pol); + tor_assert(pol->active_circuit_pqueue); + + factor = + get_scale_factor( + pol->active_circuit_pqueue_last_recalibrated, + cur_tick); + /** Ordinarily it isn't okay to change the value of an element in a heap, + * but it's okay here, since we are preserving the order. */ + SMARTLIST_FOREACH_BEGIN( + pol->active_circuit_pqueue, + cell_ewma_t *, e) { + tor_assert(e->last_adjusted_tick == + pol->active_circuit_pqueue_last_recalibrated); + e->cell_count *= factor; + e->last_adjusted_tick = cur_tick; + } SMARTLIST_FOREACH_END(e); + pol->active_circuit_pqueue_last_recalibrated = cur_tick; +} + +/** Rescale <b>ewma</b> to the same scale as <b>pol</b>, and add it to + * <b>pol</b>'s priority queue of active circuits */ +static void +add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) +{ + tor_assert(pol); + tor_assert(pol->active_circuit_pqueue); + tor_assert(ewma); + tor_assert(ewma->heap_index == -1); + + scale_single_cell_ewma( + ewma, + pol->active_circuit_pqueue_last_recalibrated); + + smartlist_pqueue_add(pol->active_circuit_pqueue, + compare_cell_ewma_counts, + STRUCT_OFFSET(cell_ewma_t, heap_index), + ewma); +} + +/** Remove <b>ewma</b> from <b>pol</b>'s priority queue of active circuits */ +static void +remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) +{ + tor_assert(pol); + tor_assert(pol->active_circuit_pqueue); + tor_assert(ewma); + tor_assert(ewma->heap_index != -1); + + smartlist_pqueue_remove(pol->active_circuit_pqueue, + compare_cell_ewma_counts, + STRUCT_OFFSET(cell_ewma_t, heap_index), + ewma); +} + +/** Remove and return the first cell_ewma_t from pol's priority queue of + * active circuits. Requires that the priority queue is nonempty. */ +static cell_ewma_t * +pop_first_cell_ewma(ewma_policy_data_t *pol) +{ + tor_assert(pol); + tor_assert(pol->active_circuit_pqueue); + + return smartlist_pqueue_pop(pol->active_circuit_pqueue, + compare_cell_ewma_counts, + STRUCT_OFFSET(cell_ewma_t, heap_index)); +} + diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h new file mode 100644 index 000000000..5621acc93 --- /dev/null +++ b/src/or/circuitmux_ewma.h @@ -0,0 +1,29 @@ +/* * Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitmux_ewma.h + * \brief Header file for circuitmux_ewma.c + **/ + +#ifndef TOR_CIRCUITMUX_EWMA_H +#define TOR_CIRCUITMUX_EWMA_H + +#include "or.h" +#include "circuitmux.h" + +/* Everything but circuitmux_ewma.c should see this extern */ +#ifndef TOR_CIRCUITMUX_EWMA_C_ + +extern circuitmux_policy_t ewma_policy; + +#endif /* !(TOR_CIRCUITMUX_EWMA_C_) */ + +/* Externally visible EWMA functions */ +int cell_ewma_enabled(void); +unsigned int cell_ewma_get_tick(void); +void cell_ewma_set_scale_factor(const or_options_t *options, + const networkstatus_t *consensus); + +#endif /* TOR_CIRCUITMUX_EWMA_H */ + diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c new file mode 100644 index 000000000..4f12a6fbc --- /dev/null +++ b/src/or/circuitstats.c @@ -0,0 +1,1569 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define CIRCUITSTATS_PRIVATE + +#include "or.h" +#include "circuitbuild.h" +#include "circuitstats.h" +#include "config.h" +#include "confparse.h" +#include "control.h" +#include "networkstatus.h" +#include "statefile.h" + +#undef log +#include <math.h> + +#define CBT_BIN_TO_MS(bin) ((bin)*CBT_BIN_WIDTH + (CBT_BIN_WIDTH/2)) + +/** Global list of circuit build times */ +// XXXX: Add this as a member for entry_guard_t instead of global? +// Then we could do per-guard statistics, as guards are likely to +// vary in their own latency. The downside of this is that guards +// can change frequently, so we'd be building a lot more circuits +// most likely. +/* XXXX024 Make this static; add accessor functions. */ +circuit_build_times_t circ_times; + +/** If set, we're running the unit tests: we should avoid clobbering + * our state file or accessing get_options() or get_or_state() */ +static int unit_tests = 0; + +/** + * This function decides if CBT learning should be disabled. It returns + * true if one or more of the following four conditions are met: + * + * 1. If the cbtdisabled consensus parameter is set. + * 2. If the torrc option LearnCircuitBuildTimeout is false. + * 3. If we are a directory authority + * 4. If we fail to write circuit build time history to our state file. + */ +int +circuit_build_times_disabled(void) +{ + if (unit_tests) { + return 0; + } else { + int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled", + 0, 0, 1); + int config_disabled = !get_options()->LearnCircuitBuildTimeout; + int dirauth_disabled = get_options()->AuthoritativeDir; + int state_disabled = did_last_state_file_write_fail() ? 1 : 0; + + if (consensus_disabled || config_disabled || dirauth_disabled || + state_disabled) { + log_debug(LD_CIRC, + "CircuitBuildTime learning is disabled. " + "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", + consensus_disabled, config_disabled, dirauth_disabled, + state_disabled); + return 1; + } else { + log_debug(LD_CIRC, + "CircuitBuildTime learning is not disabled. " + "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", + consensus_disabled, config_disabled, dirauth_disabled, + state_disabled); + return 0; + } + } +} + +/** + * Retrieve and bounds-check the cbtmaxtimeouts consensus paramter. + * + * Effect: When this many timeouts happen in the last 'cbtrecentcount' + * circuit attempts, the client should discard all of its history and + * begin learning a fresh timeout value. + */ +static int32_t +circuit_build_times_max_timeouts(void) +{ + int32_t cbt_maxtimeouts; + + cbt_maxtimeouts = networkstatus_get_param(NULL, "cbtmaxtimeouts", + CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT, + CBT_MIN_MAX_RECENT_TIMEOUT_COUNT, + CBT_MAX_MAX_RECENT_TIMEOUT_COUNT); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_max_timeouts() called, cbtmaxtimeouts is" + " %d", + cbt_maxtimeouts); + } + + return cbt_maxtimeouts; +} + +/** + * Retrieve and bounds-check the cbtnummodes consensus paramter. + * + * Effect: This value governs how many modes to use in the weighted + * average calculation of Pareto parameter Xm. A value of 3 introduces + * some bias (2-5% of CDF) under ideal conditions, but allows for better + * performance in the event that a client chooses guard nodes of radically + * different performance characteristics. + */ +static int32_t +circuit_build_times_default_num_xm_modes(void) +{ + int32_t num = networkstatus_get_param(NULL, "cbtnummodes", + CBT_DEFAULT_NUM_XM_MODES, + CBT_MIN_NUM_XM_MODES, + CBT_MAX_NUM_XM_MODES); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_default_num_xm_modes() called, cbtnummodes" + " is %d", + num); + } + + return num; +} + +/** + * Retrieve and bounds-check the cbtmincircs consensus paramter. + * + * Effect: This is the minimum number of circuits to build before + * computing a timeout. + */ +static int32_t +circuit_build_times_min_circs_to_observe(void) +{ + int32_t num = networkstatus_get_param(NULL, "cbtmincircs", + CBT_DEFAULT_MIN_CIRCUITS_TO_OBSERVE, + CBT_MIN_MIN_CIRCUITS_TO_OBSERVE, + CBT_MAX_MIN_CIRCUITS_TO_OBSERVE); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_min_circs_to_observe() called, cbtmincircs" + " is %d", + num); + } + + return num; +} + +/** Return true iff <b>cbt</b> has recorded enough build times that we + * want to start acting on the timeout it implies. */ +int +circuit_build_times_enough_to_compute(circuit_build_times_t *cbt) +{ + return cbt->total_build_times >= circuit_build_times_min_circs_to_observe(); +} + +/** + * Retrieve and bounds-check the cbtquantile consensus paramter. + * + * Effect: This is the position on the quantile curve to use to set the + * timeout value. It is a percent (10-99). + */ +double +circuit_build_times_quantile_cutoff(void) +{ + int32_t num = networkstatus_get_param(NULL, "cbtquantile", + CBT_DEFAULT_QUANTILE_CUTOFF, + CBT_MIN_QUANTILE_CUTOFF, + CBT_MAX_QUANTILE_CUTOFF); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_quantile_cutoff() called, cbtquantile" + " is %d", + num); + } + + return num/100.0; +} + +/* DOCDOC circuit_build_times_get_bw_scale */ +int +circuit_build_times_get_bw_scale(networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "bwweightscale", + BW_WEIGHT_SCALE, + BW_MIN_WEIGHT_SCALE, + BW_MAX_WEIGHT_SCALE); +} + +/** + * Retrieve and bounds-check the cbtclosequantile consensus paramter. + * + * Effect: This is the position on the quantile curve to use to set the + * timeout value to use to actually close circuits. It is a percent + * (0-99). + */ +static double +circuit_build_times_close_quantile(void) +{ + int32_t param; + /* Cast is safe - circuit_build_times_quantile_cutoff() is capped */ + int32_t min = (int)tor_lround(100*circuit_build_times_quantile_cutoff()); + param = networkstatus_get_param(NULL, "cbtclosequantile", + CBT_DEFAULT_CLOSE_QUANTILE, + CBT_MIN_CLOSE_QUANTILE, + CBT_MAX_CLOSE_QUANTILE); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_close_quantile() called, cbtclosequantile" + " is %d", param); + } + + if (param < min) { + log_warn(LD_DIR, "Consensus parameter cbtclosequantile is " + "too small, raising to %d", min); + param = min; + } + return param / 100.0; +} + +/** + * Retrieve and bounds-check the cbttestfreq consensus paramter. + * + * Effect: Describes how often in seconds to build a test circuit to + * gather timeout values. Only applies if less than 'cbtmincircs' + * have been recorded. + */ +static int32_t +circuit_build_times_test_frequency(void) +{ + int32_t num = networkstatus_get_param(NULL, "cbttestfreq", + CBT_DEFAULT_TEST_FREQUENCY, + CBT_MIN_TEST_FREQUENCY, + CBT_MAX_TEST_FREQUENCY); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_test_frequency() called, cbttestfreq is %d", + num); + } + + return num; +} + +/** + * Retrieve and bounds-check the cbtmintimeout consensus parameter. + * + * Effect: This is the minimum allowed timeout value in milliseconds. + * The minimum is to prevent rounding to 0 (we only check once + * per second). + */ +static int32_t +circuit_build_times_min_timeout(void) +{ + int32_t num = networkstatus_get_param(NULL, "cbtmintimeout", + CBT_DEFAULT_TIMEOUT_MIN_VALUE, + CBT_MIN_TIMEOUT_MIN_VALUE, + CBT_MAX_TIMEOUT_MIN_VALUE); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_min_timeout() called, cbtmintimeout is %d", + num); + } + + return num; +} + +/** + * Retrieve and bounds-check the cbtinitialtimeout consensus paramter. + * + * Effect: This is the timeout value to use before computing a timeout, + * in milliseconds. + */ +int32_t +circuit_build_times_initial_timeout(void) +{ + int32_t min = circuit_build_times_min_timeout(); + int32_t param = networkstatus_get_param(NULL, "cbtinitialtimeout", + CBT_DEFAULT_TIMEOUT_INITIAL_VALUE, + CBT_MIN_TIMEOUT_INITIAL_VALUE, + CBT_MAX_TIMEOUT_INITIAL_VALUE); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_initial_timeout() called, " + "cbtinitialtimeout is %d", + param); + } + + if (param < min) { + log_warn(LD_DIR, "Consensus parameter cbtinitialtimeout is too small, " + "raising to %d", min); + param = min; + } + return param; +} + +/** + * Retrieve and bounds-check the cbtrecentcount consensus paramter. + * + * Effect: This is the number of circuit build times to keep track of + * for deciding if we hit cbtmaxtimeouts and need to reset our state + * and learn a new timeout. + */ +static int32_t +circuit_build_times_recent_circuit_count(networkstatus_t *ns) +{ + int32_t num; + num = networkstatus_get_param(ns, "cbtrecentcount", + CBT_DEFAULT_RECENT_CIRCUITS, + CBT_MIN_RECENT_CIRCUITS, + CBT_MAX_RECENT_CIRCUITS); + + if (!(get_options()->LearnCircuitBuildTimeout)) { + log_debug(LD_BUG, + "circuit_build_times_recent_circuit_count() called, " + "cbtrecentcount is %d", + num); + } + + return num; +} + +/** + * This function is called when we get a consensus update. + * + * It checks to see if we have changed any consensus parameters + * that require reallocation or discard of previous stats. + */ +void +circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, + networkstatus_t *ns) +{ + int32_t num; + + /* + * First check if we're doing adaptive timeouts at all; nothing to + * update if we aren't. + */ + + if (!circuit_build_times_disabled()) { + num = circuit_build_times_recent_circuit_count(ns); + + if (num > 0) { + if (num != cbt->liveness.num_recent_circs) { + int8_t *recent_circs; + log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " + "circuits we must track to detect network failures from %d " + "to %d.", cbt->liveness.num_recent_circs, num); + + tor_assert(cbt->liveness.timeouts_after_firsthop || + cbt->liveness.num_recent_circs == 0); + + /* + * Technically this is a circular array that we are reallocating + * and memcopying. However, since it only consists of either 1s + * or 0s, and is only used in a statistical test to determine when + * we should discard our history after a sufficient number of 1's + * have been reached, it is fine if order is not preserved or + * elements are lost. + * + * cbtrecentcount should only be changing in cases of severe network + * distress anyway, so memory correctness here is paramount over + * doing acrobatics to preserve the array. + */ + recent_circs = tor_malloc_zero(sizeof(int8_t)*num); + if (cbt->liveness.timeouts_after_firsthop && + cbt->liveness.num_recent_circs > 0) { + memcpy(recent_circs, cbt->liveness.timeouts_after_firsthop, + sizeof(int8_t)*MIN(num, cbt->liveness.num_recent_circs)); + } + + // Adjust the index if it needs it. + if (num < cbt->liveness.num_recent_circs) { + cbt->liveness.after_firsthop_idx = MIN(num-1, + cbt->liveness.after_firsthop_idx); + } + + tor_free(cbt->liveness.timeouts_after_firsthop); + cbt->liveness.timeouts_after_firsthop = recent_circs; + cbt->liveness.num_recent_circs = num; + } + /* else no change, nothing to do */ + } else { /* num == 0 */ + /* + * Weird. This probably shouldn't happen, so log a warning, but try + * to do something sensible anyway. + */ + + log_warn(LD_CIRC, + "The cbtrecentcircs consensus parameter came back zero! " + "This disables adaptive timeouts since we can't keep track of " + "any recent circuits."); + + circuit_build_times_free_timeouts(cbt); + } + } else { + /* + * Adaptive timeouts are disabled; this might be because of the + * LearnCircuitBuildTimes config parameter, and hence permanent, or + * the cbtdisabled consensus parameter, so it may be a new condition. + * Treat it like getting num == 0 above and free the circuit history + * if we have any. + */ + + circuit_build_times_free_timeouts(cbt); + } +} + +/** + * Return the initial default or configured timeout in milliseconds + */ +static double +circuit_build_times_get_initial_timeout(void) +{ + double timeout; + + /* + * Check if we have LearnCircuitBuildTimeout, and if we don't, + * always use CircuitBuildTimeout, no questions asked. + */ + if (get_options()->LearnCircuitBuildTimeout) { + if (!unit_tests && get_options()->CircuitBuildTimeout) { + timeout = get_options()->CircuitBuildTimeout*1000; + if (timeout < circuit_build_times_min_timeout()) { + log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds", + circuit_build_times_min_timeout()/1000); + timeout = circuit_build_times_min_timeout(); + } + } else { + timeout = circuit_build_times_initial_timeout(); + } + } else { + timeout = get_options()->CircuitBuildTimeout*1000; + } + + return timeout; +} + +/** + * Reset the build time state. + * + * Leave estimated parameters, timeout and network liveness intact + * for future use. + */ +void +circuit_build_times_reset(circuit_build_times_t *cbt) +{ + memset(cbt->circuit_build_times, 0, sizeof(cbt->circuit_build_times)); + cbt->total_build_times = 0; + cbt->build_times_idx = 0; + cbt->have_computed_timeout = 0; +} + +/** + * Initialize the buildtimes structure for first use. + * + * Sets the initial timeout values based on either the config setting, + * the consensus param, or the default (CBT_DEFAULT_TIMEOUT_INITIAL_VALUE). + */ +void +circuit_build_times_init(circuit_build_times_t *cbt) +{ + memset(cbt, 0, sizeof(*cbt)); + /* + * Check if we really are using adaptive timeouts, and don't keep + * track of this stuff if not. + */ + if (!circuit_build_times_disabled()) { + cbt->liveness.num_recent_circs = + circuit_build_times_recent_circuit_count(NULL); + cbt->liveness.timeouts_after_firsthop = + tor_malloc_zero(sizeof(int8_t)*cbt->liveness.num_recent_circs); + } else { + cbt->liveness.num_recent_circs = 0; + cbt->liveness.timeouts_after_firsthop = NULL; + } + cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout(); + control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); +} + +/** + * Free the saved timeouts, if the cbtdisabled consensus parameter got turned + * on or something. + */ + +void +circuit_build_times_free_timeouts(circuit_build_times_t *cbt) +{ + if (!cbt) return; + + if (cbt->liveness.timeouts_after_firsthop) { + tor_free(cbt->liveness.timeouts_after_firsthop); + } + + cbt->liveness.num_recent_circs = 0; +} + +#if 0 +/** + * Rewind our build time history by n positions. + */ +static void +circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n) +{ + int i = 0; + + cbt->build_times_idx -= n; + cbt->build_times_idx %= CBT_NCIRCUITS_TO_OBSERVE; + + for (i = 0; i < n; i++) { + cbt->circuit_build_times[(i+cbt->build_times_idx) + %CBT_NCIRCUITS_TO_OBSERVE]=0; + } + + if (cbt->total_build_times > n) { + cbt->total_build_times -= n; + } else { + cbt->total_build_times = 0; + } + + log_info(LD_CIRC, + "Rewound history by %d places. Current index: %d. " + "Total: %d", n, cbt->build_times_idx, cbt->total_build_times); +} +#endif + +/** + * Add a new build time value <b>time</b> to the set of build times. Time + * units are milliseconds. + * + * circuit_build_times <b>cbt</b> is a circular array, so loop around when + * array is full. + */ +int +circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time) +{ + if (time <= 0 || time > CBT_BUILD_TIME_MAX) { + log_warn(LD_BUG, "Circuit build time is too large (%u)." + "This is probably a bug.", time); + tor_fragile_assert(); + return -1; + } + + log_debug(LD_CIRC, "Adding circuit build time %u", time); + + cbt->circuit_build_times[cbt->build_times_idx] = time; + cbt->build_times_idx = (cbt->build_times_idx + 1) % CBT_NCIRCUITS_TO_OBSERVE; + if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE) + cbt->total_build_times++; + + if ((cbt->total_build_times % CBT_SAVE_STATE_EVERY) == 0) { + /* Save state every n circuit builds */ + if (!unit_tests && !get_options()->AvoidDiskWrites) + or_state_mark_dirty(get_or_state(), 0); + } + + return 0; +} + +/** + * Return maximum circuit build time + */ +static build_time_t +circuit_build_times_max(circuit_build_times_t *cbt) +{ + int i = 0; + build_time_t max_build_time = 0; + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] > max_build_time + && cbt->circuit_build_times[i] != CBT_BUILD_ABANDONED) + max_build_time = cbt->circuit_build_times[i]; + } + return max_build_time; +} + +#if 0 +/** Return minimum circuit build time */ +build_time_t +circuit_build_times_min(circuit_build_times_t *cbt) +{ + int i = 0; + build_time_t min_build_time = CBT_BUILD_TIME_MAX; + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] && /* 0 <-> uninitialized */ + cbt->circuit_build_times[i] < min_build_time) + min_build_time = cbt->circuit_build_times[i]; + } + if (min_build_time == CBT_BUILD_TIME_MAX) { + log_warn(LD_CIRC, "No build times less than CBT_BUILD_TIME_MAX!"); + } + return min_build_time; +} +#endif + +/** + * Calculate and return a histogram for the set of build times. + * + * Returns an allocated array of histrogram bins representing + * the frequency of index*CBT_BIN_WIDTH millisecond + * build times. Also outputs the number of bins in nbins. + * + * The return value must be freed by the caller. + */ +static uint32_t * +circuit_build_times_create_histogram(circuit_build_times_t *cbt, + build_time_t *nbins) +{ + uint32_t *histogram; + build_time_t max_build_time = circuit_build_times_max(cbt); + int i, c; + + *nbins = 1 + (max_build_time / CBT_BIN_WIDTH); + histogram = tor_malloc_zero(*nbins * sizeof(build_time_t)); + + // calculate histogram + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] == 0 + || cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) + continue; /* 0 <-> uninitialized */ + + c = (cbt->circuit_build_times[i] / CBT_BIN_WIDTH); + histogram[c]++; + } + + return histogram; +} + +/** + * Return the Pareto start-of-curve parameter Xm. + * + * Because we are not a true Pareto curve, we compute this as the + * weighted average of the N most frequent build time bins. N is either + * 1 if we don't have enough circuit build time data collected, or + * determined by the consensus parameter cbtnummodes (default 3). + */ +static build_time_t +circuit_build_times_get_xm(circuit_build_times_t *cbt) +{ + build_time_t i, nbins; + build_time_t *nth_max_bin; + int32_t bin_counts=0; + build_time_t ret = 0; + uint32_t *histogram = circuit_build_times_create_histogram(cbt, &nbins); + int n=0; + int num_modes = circuit_build_times_default_num_xm_modes(); + + tor_assert(nbins > 0); + tor_assert(num_modes > 0); + + // Only use one mode if < 1000 buildtimes. Not enough data + // for multiple. + if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE) + num_modes = 1; + + nth_max_bin = (build_time_t*)tor_malloc_zero(num_modes*sizeof(build_time_t)); + + /* Determine the N most common build times */ + for (i = 0; i < nbins; i++) { + if (histogram[i] >= histogram[nth_max_bin[0]]) { + nth_max_bin[0] = i; + } + + for (n = 1; n < num_modes; n++) { + if (histogram[i] >= histogram[nth_max_bin[n]] && + (!histogram[nth_max_bin[n-1]] + || histogram[i] < histogram[nth_max_bin[n-1]])) { + nth_max_bin[n] = i; + } + } + } + + for (n = 0; n < num_modes; n++) { + bin_counts += histogram[nth_max_bin[n]]; + ret += CBT_BIN_TO_MS(nth_max_bin[n])*histogram[nth_max_bin[n]]; + log_info(LD_CIRC, "Xm mode #%d: %u %u", n, CBT_BIN_TO_MS(nth_max_bin[n]), + histogram[nth_max_bin[n]]); + } + + /* The following assert is safe, because we don't get called when we + * haven't observed at least CBT_MIN_MIN_CIRCUITS_TO_OBSERVE circuits. */ + tor_assert(bin_counts > 0); + + ret /= bin_counts; + tor_free(histogram); + tor_free(nth_max_bin); + + return ret; +} + +/** + * Output a histogram of current circuit build times to + * the or_state_t state structure. + */ +void +circuit_build_times_update_state(circuit_build_times_t *cbt, + or_state_t *state) +{ + uint32_t *histogram; + build_time_t i = 0; + build_time_t nbins = 0; + config_line_t **next, *line; + + histogram = circuit_build_times_create_histogram(cbt, &nbins); + // write to state + config_free_lines(state->BuildtimeHistogram); + next = &state->BuildtimeHistogram; + *next = NULL; + + state->TotalBuildTimes = cbt->total_build_times; + state->CircuitBuildAbandonedCount = 0; + + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) + state->CircuitBuildAbandonedCount++; + } + + for (i = 0; i < nbins; i++) { + // compress the histogram by skipping the blanks + if (histogram[i] == 0) continue; + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("CircuitBuildTimeBin"); + tor_asprintf(&line->value, "%d %d", + CBT_BIN_TO_MS(i), histogram[i]); + next = &(line->next); + } + + if (!unit_tests) { + if (!get_options()->AvoidDiskWrites) + or_state_mark_dirty(get_or_state(), 0); + } + + tor_free(histogram); +} + +/** + * Shuffle the build times array. + * + * Adapted from http://en.wikipedia.org/wiki/Fisher-Yates_shuffle + */ +static void +circuit_build_times_shuffle_and_store_array(circuit_build_times_t *cbt, + build_time_t *raw_times, + uint32_t num_times) +{ + uint32_t n = num_times; + if (num_times > CBT_NCIRCUITS_TO_OBSERVE) { + log_notice(LD_CIRC, "The number of circuit times that this Tor version " + "uses to calculate build times is less than the number stored " + "in your state file. Decreasing the circuit time history from " + "%lu to %d.", (unsigned long)num_times, + CBT_NCIRCUITS_TO_OBSERVE); + } + + if (n > INT_MAX-1) { + log_warn(LD_CIRC, "For some insane reasons, you had %lu circuit build " + "observations in your state file. That's far too many; probably " + "there's a bug here.", (unsigned long)n); + n = INT_MAX-1; + } + + /* This code can only be run on a compact array */ + while (n-- > 1) { + int k = crypto_rand_int(n + 1); /* 0 <= k <= n. */ + build_time_t tmp = raw_times[k]; + raw_times[k] = raw_times[n]; + raw_times[n] = tmp; + } + + /* Since the times are now shuffled, take a random CBT_NCIRCUITS_TO_OBSERVE + * subset (ie the first CBT_NCIRCUITS_TO_OBSERVE values) */ + for (n = 0; n < MIN(num_times, CBT_NCIRCUITS_TO_OBSERVE); n++) { + circuit_build_times_add_time(cbt, raw_times[n]); + } +} + +/** + * Filter old synthetic timeouts that were created before the + * new right-censored Pareto calculation was deployed. + * + * Once all clients before 0.2.1.13-alpha are gone, this code + * will be unused. + */ +static int +circuit_build_times_filter_timeouts(circuit_build_times_t *cbt) +{ + int num_filtered=0, i=0; + double timeout_rate = 0; + build_time_t max_timeout = 0; + + timeout_rate = circuit_build_times_timeout_rate(cbt); + max_timeout = (build_time_t)cbt->close_ms; + + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] > max_timeout) { + build_time_t replaced = cbt->circuit_build_times[i]; + num_filtered++; + cbt->circuit_build_times[i] = CBT_BUILD_ABANDONED; + + log_debug(LD_CIRC, "Replaced timeout %d with %d", replaced, + cbt->circuit_build_times[i]); + } + } + + log_info(LD_CIRC, + "We had %d timeouts out of %d build times, " + "and filtered %d above the max of %u", + (int)(cbt->total_build_times*timeout_rate), + cbt->total_build_times, num_filtered, max_timeout); + + return num_filtered; +} + +/** + * Load histogram from <b>state</b>, shuffling the resulting array + * after we do so. Use this result to estimate parameters and + * calculate the timeout. + * + * Return -1 on error. + */ +int +circuit_build_times_parse_state(circuit_build_times_t *cbt, + or_state_t *state) +{ + int tot_values = 0; + uint32_t loaded_cnt = 0, N = 0; + config_line_t *line; + unsigned int i; + build_time_t *loaded_times; + int err = 0; + circuit_build_times_init(cbt); + + if (circuit_build_times_disabled()) { + return 0; + } + + /* build_time_t 0 means uninitialized */ + loaded_times = tor_malloc_zero(sizeof(build_time_t)*state->TotalBuildTimes); + + for (line = state->BuildtimeHistogram; line; line = line->next) { + smartlist_t *args = smartlist_new(); + smartlist_split_string(args, line->value, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(args) < 2) { + log_warn(LD_GENERAL, "Unable to parse circuit build times: " + "Too few arguments to CircuitBuildTime"); + err = 1; + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + break; + } else { + const char *ms_str = smartlist_get(args,0); + const char *count_str = smartlist_get(args,1); + uint32_t count, k; + build_time_t ms; + int ok; + ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, + CBT_BUILD_TIME_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse circuit build times: " + "Unparsable bin number"); + err = 1; + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + break; + } + count = (uint32_t)tor_parse_ulong(count_str, 0, 0, + UINT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse circuit build times: " + "Unparsable bin count"); + err = 1; + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + break; + } + + if (loaded_cnt+count+state->CircuitBuildAbandonedCount + > state->TotalBuildTimes) { + log_warn(LD_CIRC, + "Too many build times in state file. " + "Stopping short before %d", + loaded_cnt+count); + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + break; + } + + for (k = 0; k < count; k++) { + loaded_times[loaded_cnt++] = ms; + } + N++; + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + } + } + + log_info(LD_CIRC, + "Adding %d timeouts.", state->CircuitBuildAbandonedCount); + for (i=0; i < state->CircuitBuildAbandonedCount; i++) { + loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED; + } + + if (loaded_cnt != state->TotalBuildTimes) { + log_warn(LD_CIRC, + "Corrupt state file? Build times count mismatch. " + "Read %d times, but file says %d", loaded_cnt, + state->TotalBuildTimes); + err = 1; + circuit_build_times_reset(cbt); + goto done; + } + + circuit_build_times_shuffle_and_store_array(cbt, loaded_times, loaded_cnt); + + /* Verify that we didn't overwrite any indexes */ + for (i=0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (!cbt->circuit_build_times[i]) + break; + tot_values++; + } + log_info(LD_CIRC, + "Loaded %d/%d values from %d lines in circuit time histogram", + tot_values, cbt->total_build_times, N); + + if (cbt->total_build_times != tot_values + || cbt->total_build_times > CBT_NCIRCUITS_TO_OBSERVE) { + log_warn(LD_CIRC, + "Corrupt state file? Shuffled build times mismatch. " + "Read %d times, but file says %d", tot_values, + state->TotalBuildTimes); + err = 1; + circuit_build_times_reset(cbt); + goto done; + } + + circuit_build_times_set_timeout(cbt); + + if (!state->CircuitBuildAbandonedCount && cbt->total_build_times) { + circuit_build_times_filter_timeouts(cbt); + } + + done: + tor_free(loaded_times); + return err ? -1 : 0; +} + +/** + * Estimates the Xm and Alpha parameters using + * http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation + * + * The notable difference is that we use mode instead of min to estimate Xm. + * This is because our distribution is frechet-like. We claim this is + * an acceptable approximation because we are only concerned with the + * accuracy of the CDF of the tail. + */ +int +circuit_build_times_update_alpha(circuit_build_times_t *cbt) +{ + build_time_t *x=cbt->circuit_build_times; + double a = 0; + int n=0,i=0,abandoned_count=0; + build_time_t max_time=0; + + /* http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation */ + /* We sort of cheat here and make our samples slightly more pareto-like + * and less frechet-like. */ + cbt->Xm = circuit_build_times_get_xm(cbt); + + tor_assert(cbt->Xm > 0); + + for (i=0; i< CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (!x[i]) { + continue; + } + + if (x[i] < cbt->Xm) { + a += tor_mathlog(cbt->Xm); + } else if (x[i] == CBT_BUILD_ABANDONED) { + abandoned_count++; + } else { + a += tor_mathlog(x[i]); + if (x[i] > max_time) + max_time = x[i]; + } + n++; + } + + /* + * We are erring and asserting here because this can only happen + * in codepaths other than startup. The startup state parsing code + * performs this same check, and resets state if it hits it. If we + * hit it at runtime, something serious has gone wrong. + */ + if (n!=cbt->total_build_times) { + log_err(LD_CIRC, "Discrepancy in build times count: %d vs %d", n, + cbt->total_build_times); + } + tor_assert(n==cbt->total_build_times); + + if (max_time <= 0) { + /* This can happen if Xm is actually the *maximum* value in the set. + * It can also happen if we've abandoned every single circuit somehow. + * In either case, tell the caller not to compute a new build timeout. */ + log_warn(LD_BUG, + "Could not determine largest build time (%d). " + "Xm is %dms and we've abandoned %d out of %d circuits.", max_time, + cbt->Xm, abandoned_count, n); + return 0; + } + + a += abandoned_count*tor_mathlog(max_time); + + a -= n*tor_mathlog(cbt->Xm); + // Estimator comes from Eq #4 in: + // "Bayesian estimation based on trimmed samples from Pareto populations" + // by Arturo J. Fernández. We are right-censored only. + a = (n-abandoned_count)/a; + + cbt->alpha = a; + + return 1; +} + +/** + * This is the Pareto Quantile Function. It calculates the point x + * in the distribution such that F(x) = quantile (ie quantile*100% + * of the mass of the density function is below x on the curve). + * + * We use it to calculate the timeout and also to generate synthetic + * values of time for circuits that timeout before completion. + * + * See http://en.wikipedia.org/wiki/Quantile_function, + * http://en.wikipedia.org/wiki/Inverse_transform_sampling and + * http://en.wikipedia.org/wiki/Pareto_distribution#Generating_a_ + * random_sample_from_Pareto_distribution + * That's right. I'll cite wikipedia all day long. + * + * Return value is in milliseconds. + */ +double +circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, + double quantile) +{ + double ret; + tor_assert(quantile >= 0); + tor_assert(1.0-quantile > 0); + tor_assert(cbt->Xm > 0); + + ret = cbt->Xm/pow(1.0-quantile,1.0/cbt->alpha); + if (ret > INT32_MAX) { + ret = INT32_MAX; + } + tor_assert(ret > 0); + return ret; +} + +/** Pareto CDF */ +double +circuit_build_times_cdf(circuit_build_times_t *cbt, double x) +{ + double ret; + tor_assert(cbt->Xm > 0); + ret = 1.0-pow(cbt->Xm/x,cbt->alpha); + tor_assert(0 <= ret && ret <= 1.0); + return ret; +} + +/** + * Generate a synthetic time using our distribution parameters. + * + * The return value will be within the [q_lo, q_hi) quantile points + * on the CDF. + */ +build_time_t +circuit_build_times_generate_sample(circuit_build_times_t *cbt, + double q_lo, double q_hi) +{ + double randval = crypto_rand_double(); + build_time_t ret; + double u; + + /* Generate between [q_lo, q_hi) */ + /*XXXX This is what nextafter is supposed to be for; we should use it on the + * platforms that support it. */ + q_hi -= 1.0/(INT32_MAX); + + tor_assert(q_lo >= 0); + tor_assert(q_hi < 1); + tor_assert(q_lo < q_hi); + + u = q_lo + (q_hi-q_lo)*randval; + + tor_assert(0 <= u && u < 1.0); + /* circuit_build_times_calculate_timeout returns <= INT32_MAX */ + ret = (build_time_t) + tor_lround(circuit_build_times_calculate_timeout(cbt, u)); + tor_assert(ret > 0); + return ret; +} + +/** + * Estimate an initial alpha parameter by solving the quantile + * function with a quantile point and a specific timeout value. + */ +void +circuit_build_times_initial_alpha(circuit_build_times_t *cbt, + double quantile, double timeout_ms) +{ + // Q(u) = Xm/((1-u)^(1/a)) + // Q(0.8) = Xm/((1-0.8))^(1/a)) = CircBuildTimeout + // CircBuildTimeout = Xm/((1-0.8))^(1/a)) + // CircBuildTimeout = Xm*((1-0.8))^(-1/a)) + // ln(CircBuildTimeout) = ln(Xm)+ln(((1-0.8)))*(-1/a) + // -ln(1-0.8)/(ln(CircBuildTimeout)-ln(Xm))=a + tor_assert(quantile >= 0); + tor_assert(cbt->Xm > 0); + cbt->alpha = tor_mathlog(1.0-quantile)/ + (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); + tor_assert(cbt->alpha > 0); +} + +/** + * Returns true if we need circuits to be built + */ +int +circuit_build_times_needs_circuits(circuit_build_times_t *cbt) +{ + /* Return true if < MIN_CIRCUITS_TO_OBSERVE */ + return !circuit_build_times_enough_to_compute(cbt); +} + +/** + * Returns true if we should build a timeout test circuit + * right now. + */ +int +circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt) +{ + return circuit_build_times_needs_circuits(cbt) && + approx_time()-cbt->last_circ_at > circuit_build_times_test_frequency(); +} + +/** + * Called to indicate that the network showed some signs of liveness, + * i.e. we received a cell. + * + * This is used by circuit_build_times_network_check_live() to decide + * if we should record the circuit build timeout or not. + * + * This function is called every time we receive a cell. Avoid + * syscalls, events, and other high-intensity work. + */ +void +circuit_build_times_network_is_live(circuit_build_times_t *cbt) +{ + time_t now = approx_time(); + if (cbt->liveness.nonlive_timeouts > 0) { + log_notice(LD_CIRC, + "Tor now sees network activity. Restoring circuit build " + "timeout recording. Network was down for %d seconds " + "during %d circuit attempts.", + (int)(now - cbt->liveness.network_last_live), + cbt->liveness.nonlive_timeouts); + } + cbt->liveness.network_last_live = now; + cbt->liveness.nonlive_timeouts = 0; +} + +/** + * Called to indicate that we completed a circuit. Because this circuit + * succeeded, it doesn't count as a timeout-after-the-first-hop. + * + * This is used by circuit_build_times_network_check_changed() to determine + * if we had too many recent timeouts and need to reset our learned timeout + * to something higher. + */ +void +circuit_build_times_network_circ_success(circuit_build_times_t *cbt) +{ + /* Check for NULLness because we might not be using adaptive timeouts */ + if (cbt->liveness.timeouts_after_firsthop && + cbt->liveness.num_recent_circs > 0) { + cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx] + = 0; + cbt->liveness.after_firsthop_idx++; + cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs; + } +} + +/** + * A circuit just timed out. If it failed after the first hop, record it + * in our history for later deciding if the network speed has changed. + * + * This is used by circuit_build_times_network_check_changed() to determine + * if we had too many recent timeouts and need to reset our learned timeout + * to something higher. + */ +static void +circuit_build_times_network_timeout(circuit_build_times_t *cbt, + int did_onehop) +{ + /* Check for NULLness because we might not be using adaptive timeouts */ + if (cbt->liveness.timeouts_after_firsthop && + cbt->liveness.num_recent_circs > 0) { + if (did_onehop) { + cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx] + = 1; + cbt->liveness.after_firsthop_idx++; + cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs; + } + } +} + +/** + * A circuit was just forcibly closed. If there has been no recent network + * activity at all, but this circuit was launched back when we thought the + * network was live, increment the number of "nonlive" circuit timeouts. + * + * This is used by circuit_build_times_network_check_live() to decide + * if we should record the circuit build timeout or not. + */ +static void +circuit_build_times_network_close(circuit_build_times_t *cbt, + int did_onehop, time_t start_time) +{ + time_t now = time(NULL); + /* + * Check if this is a timeout that was for a circuit that spent its + * entire existence during a time where we have had no network activity. + */ + if (cbt->liveness.network_last_live < start_time) { + if (did_onehop) { + char last_live_buf[ISO_TIME_LEN+1]; + char start_time_buf[ISO_TIME_LEN+1]; + char now_buf[ISO_TIME_LEN+1]; + format_local_iso_time(last_live_buf, cbt->liveness.network_last_live); + format_local_iso_time(start_time_buf, start_time); + format_local_iso_time(now_buf, now); + log_warn(LD_BUG, + "Circuit somehow completed a hop while the network was " + "not live. Network was last live at %s, but circuit launched " + "at %s. It's now %s.", last_live_buf, start_time_buf, + now_buf); + } + cbt->liveness.nonlive_timeouts++; + if (cbt->liveness.nonlive_timeouts == 1) { + log_notice(LD_CIRC, + "Tor has not observed any network activity for the past %d " + "seconds. Disabling circuit build timeout recording.", + (int)(now - cbt->liveness.network_last_live)); + } else { + log_info(LD_CIRC, + "Got non-live timeout. Current count is: %d", + cbt->liveness.nonlive_timeouts); + } + } +} + +/** + * When the network is not live, we do not record circuit build times. + * + * The network is considered not live if there has been at least one + * circuit build that began and ended (had its close_ms measurement + * period expire) since we last received a cell. + * + * Also has the side effect of rewinding the circuit time history + * in the case of recent liveness changes. + */ +int +circuit_build_times_network_check_live(circuit_build_times_t *cbt) +{ + if (cbt->liveness.nonlive_timeouts > 0) { + return 0; + } + + return 1; +} + +/** + * Returns true if we have seen more than MAX_RECENT_TIMEOUT_COUNT of + * the past RECENT_CIRCUITS time out after the first hop. Used to detect + * if the network connection has changed significantly, and if so, + * resets our circuit build timeout to the default. + * + * Also resets the entire timeout history in this case and causes us + * to restart the process of building test circuits and estimating a + * new timeout. + */ +int +circuit_build_times_network_check_changed(circuit_build_times_t *cbt) +{ + int total_build_times = cbt->total_build_times; + int timeout_count=0; + int i; + + if (cbt->liveness.timeouts_after_firsthop && + cbt->liveness.num_recent_circs > 0) { + /* how many of our recent circuits made it to the first hop but then + * timed out? */ + for (i = 0; i < cbt->liveness.num_recent_circs; i++) { + timeout_count += cbt->liveness.timeouts_after_firsthop[i]; + } + } + + /* If 80% of our recent circuits are timing out after the first hop, + * we need to re-estimate a new initial alpha and timeout. */ + if (timeout_count < circuit_build_times_max_timeouts()) { + return 0; + } + + circuit_build_times_reset(cbt); + if (cbt->liveness.timeouts_after_firsthop && + cbt->liveness.num_recent_circs > 0) { + memset(cbt->liveness.timeouts_after_firsthop, 0, + sizeof(*cbt->liveness.timeouts_after_firsthop)* + cbt->liveness.num_recent_circs); + } + cbt->liveness.after_firsthop_idx = 0; + + /* Check to see if this has happened before. If so, double the timeout + * to give people on abysmally bad network connections a shot at access */ + if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) { + if (cbt->timeout_ms > INT32_MAX/2 || cbt->close_ms > INT32_MAX/2) { + log_warn(LD_CIRC, "Insanely large circuit build timeout value. " + "(timeout = %fmsec, close = %fmsec)", + cbt->timeout_ms, cbt->close_ms); + } else { + cbt->timeout_ms *= 2; + cbt->close_ms *= 2; + } + } else { + cbt->close_ms = cbt->timeout_ms + = circuit_build_times_get_initial_timeout(); + } + + control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET); + + log_notice(LD_CIRC, + "Your network connection speed appears to have changed. Resetting " + "timeout to %lds after %d timeouts and %d buildtimes.", + tor_lround(cbt->timeout_ms/1000), timeout_count, + total_build_times); + + return 1; +} + +/** + * Count the number of timeouts in a set of cbt data. + */ +double +circuit_build_times_timeout_rate(const circuit_build_times_t *cbt) +{ + int i=0,timeouts=0; + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] >= cbt->timeout_ms) { + timeouts++; + } + } + + if (!cbt->total_build_times) + return 0; + + return ((double)timeouts)/cbt->total_build_times; +} + +/** + * Count the number of closed circuits in a set of cbt data. + */ +double +circuit_build_times_close_rate(const circuit_build_times_t *cbt) +{ + int i=0,closed=0; + for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) { + if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) { + closed++; + } + } + + if (!cbt->total_build_times) + return 0; + + return ((double)closed)/cbt->total_build_times; +} + +/** + * Store a timeout as a synthetic value. + * + * Returns true if the store was successful and we should possibly + * update our timeout estimate. + */ +int +circuit_build_times_count_close(circuit_build_times_t *cbt, + int did_onehop, + time_t start_time) +{ + if (circuit_build_times_disabled()) { + cbt->close_ms = cbt->timeout_ms + = circuit_build_times_get_initial_timeout(); + return 0; + } + + /* Record this force-close to help determine if the network is dead */ + circuit_build_times_network_close(cbt, did_onehop, start_time); + + /* Only count timeouts if network is live.. */ + if (!circuit_build_times_network_check_live(cbt)) { + return 0; + } + + circuit_build_times_add_time(cbt, CBT_BUILD_ABANDONED); + return 1; +} + +/** + * Update timeout counts to determine if we need to expire + * our build time history due to excessive timeouts. + * + * We do not record any actual time values at this stage; + * we are only interested in recording the fact that a timeout + * happened. We record the time values via + * circuit_build_times_count_close() and circuit_build_times_add_time(). + */ +void +circuit_build_times_count_timeout(circuit_build_times_t *cbt, + int did_onehop) +{ + if (circuit_build_times_disabled()) { + cbt->close_ms = cbt->timeout_ms + = circuit_build_times_get_initial_timeout(); + return; + } + + /* Register the fact that a timeout just occurred. */ + circuit_build_times_network_timeout(cbt, did_onehop); + + /* If there are a ton of timeouts, we should reset + * the circuit build timeout. */ + circuit_build_times_network_check_changed(cbt); +} + +/** + * Estimate a new timeout based on history and set our timeout + * variable accordingly. + */ +static int +circuit_build_times_set_timeout_worker(circuit_build_times_t *cbt) +{ + build_time_t max_time; + if (!circuit_build_times_enough_to_compute(cbt)) + return 0; + + if (!circuit_build_times_update_alpha(cbt)) + return 0; + + cbt->timeout_ms = circuit_build_times_calculate_timeout(cbt, + circuit_build_times_quantile_cutoff()); + + cbt->close_ms = circuit_build_times_calculate_timeout(cbt, + circuit_build_times_close_quantile()); + + max_time = circuit_build_times_max(cbt); + + if (cbt->timeout_ms > max_time) { + log_info(LD_CIRC, + "Circuit build timeout of %dms is beyond the maximum build " + "time we have ever observed. Capping it to %dms.", + (int)cbt->timeout_ms, max_time); + cbt->timeout_ms = max_time; + } + + if (max_time < INT32_MAX/2 && cbt->close_ms > 2*max_time) { + log_info(LD_CIRC, + "Circuit build measurement period of %dms is more than twice " + "the maximum build time we have ever observed. Capping it to " + "%dms.", (int)cbt->close_ms, 2*max_time); + cbt->close_ms = 2*max_time; + } + + /* Sometimes really fast guard nodes give us such a steep curve + * that this ends up being not that much greater than timeout_ms. + * Make it be at least 1 min to handle this case. */ + cbt->close_ms = MAX(cbt->close_ms, circuit_build_times_initial_timeout()); + + cbt->have_computed_timeout = 1; + return 1; +} + +/** + * Exposed function to compute a new timeout. Dispatches events and + * also filters out extremely high timeout values. + */ +void +circuit_build_times_set_timeout(circuit_build_times_t *cbt) +{ + long prev_timeout = tor_lround(cbt->timeout_ms/1000); + double timeout_rate; + + /* + * Just return if we aren't using adaptive timeouts + */ + if (circuit_build_times_disabled()) + return; + + if (!circuit_build_times_set_timeout_worker(cbt)) + return; + + if (cbt->timeout_ms < circuit_build_times_min_timeout()) { + log_info(LD_CIRC, "Set buildtimeout to low value %fms. Setting to %dms", + cbt->timeout_ms, circuit_build_times_min_timeout()); + cbt->timeout_ms = circuit_build_times_min_timeout(); + if (cbt->close_ms < cbt->timeout_ms) { + /* This shouldn't happen because of MAX() in timeout_worker above, + * but doing it just in case */ + cbt->close_ms = circuit_build_times_initial_timeout(); + } + } + + control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED); + + timeout_rate = circuit_build_times_timeout_rate(cbt); + + if (prev_timeout > tor_lround(cbt->timeout_ms/1000)) { + log_info(LD_CIRC, + "Based on %d circuit times, it looks like we don't need to " + "wait so long for circuits to finish. We will now assume a " + "circuit is too slow to use after waiting %ld seconds.", + cbt->total_build_times, + tor_lround(cbt->timeout_ms/1000)); + log_info(LD_CIRC, + "Circuit timeout data: %fms, %fms, Xm: %d, a: %f, r: %f", + cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, + timeout_rate); + } else if (prev_timeout < tor_lround(cbt->timeout_ms/1000)) { + log_info(LD_CIRC, + "Based on %d circuit times, it looks like we need to wait " + "longer for circuits to finish. We will now assume a " + "circuit is too slow to use after waiting %ld seconds.", + cbt->total_build_times, + tor_lround(cbt->timeout_ms/1000)); + log_info(LD_CIRC, + "Circuit timeout data: %fms, %fms, Xm: %d, a: %f, r: %f", + cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, + timeout_rate); + } else { + log_info(LD_CIRC, + "Set circuit build timeout to %lds (%fms, %fms, Xm: %d, a: %f," + " r: %f) based on %d circuit times", + tor_lround(cbt->timeout_ms/1000), + cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, timeout_rate, + cbt->total_build_times); + } +} +/** Make a note that we're running unit tests (rather than running Tor + * itself), so we avoid clobbering our state file. */ +void +circuitbuild_running_unit_tests(void) +{ + unit_tests = 1; +} + diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h new file mode 100644 index 000000000..efe2799b0 --- /dev/null +++ b/src/or/circuitstats.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file circuitstats.h + * \brief Header file for circuitstats.c + **/ + +#ifndef TOR_CIRCUITSTATS_H +#define TOR_CIRCUITSTATS_H + +extern circuit_build_times_t circ_times; + +int circuit_build_times_disabled(void); +int circuit_build_times_enough_to_compute(circuit_build_times_t *cbt); +void circuit_build_times_update_state(circuit_build_times_t *cbt, + or_state_t *state); +int circuit_build_times_parse_state(circuit_build_times_t *cbt, + or_state_t *state); +void circuit_build_times_count_timeout(circuit_build_times_t *cbt, + int did_onehop); +int circuit_build_times_count_close(circuit_build_times_t *cbt, + int did_onehop, time_t start_time); +void circuit_build_times_set_timeout(circuit_build_times_t *cbt); +int circuit_build_times_add_time(circuit_build_times_t *cbt, + build_time_t time); +int circuit_build_times_needs_circuits(circuit_build_times_t *cbt); + +int circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt); +void circuit_build_times_init(circuit_build_times_t *cbt); +void circuit_build_times_free_timeouts(circuit_build_times_t *cbt); +void circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, + networkstatus_t *ns); +double circuit_build_times_timeout_rate(const circuit_build_times_t *cbt); +double circuit_build_times_close_rate(const circuit_build_times_t *cbt); + +#ifdef CIRCUITSTATS_PRIVATE +double circuit_build_times_calculate_timeout(circuit_build_times_t *cbt, + double quantile); +build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, + double q_lo, double q_hi); +void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, + double quantile, double time_ms); +int circuit_build_times_update_alpha(circuit_build_times_t *cbt); +double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); +void circuitbuild_running_unit_tests(void); +void circuit_build_times_reset(circuit_build_times_t *cbt); + +/* Network liveness functions */ +int circuit_build_times_network_check_changed(circuit_build_times_t *cbt); +#endif + +/* Network liveness functions */ +void circuit_build_times_network_is_live(circuit_build_times_t *cbt); +int circuit_build_times_network_check_live(circuit_build_times_t *cbt); +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); + +#endif + diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 20f124eb4..bdaf7d811 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -10,13 +10,17 @@ **/ #include "or.h" +#include "addressmap.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuitstats.h" #include "circuituse.h" #include "config.h" #include "connection.h" #include "connection_edge.h" #include "control.h" +#include "entrynodes.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -53,7 +57,7 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, tor_assert(conn); tor_assert(conn->socks_request); - if (must_be_open && (circ->state != CIRCUIT_STATE_OPEN || !circ->n_conn)) + if (must_be_open && (circ->state != CIRCUIT_STATE_OPEN || !circ->n_chan)) return 0; /* ignore non-open circs */ if (circ->marked_for_close) return 0; @@ -172,6 +176,13 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob, const uint8_t purpose = ENTRY_TO_CONN(conn)->purpose; int a_bits, b_bits; + /* If one of the circuits was allowed to live due to relaxing its timeout, + * it is definitely worse (it's probably a much slower path). */ + if (oa->relaxed_timeout && !ob->relaxed_timeout) + return 0; /* ob is better. It's not relaxed. */ + if (!oa->relaxed_timeout && ob->relaxed_timeout) + return 1; /* oa is better. It's not relaxed. */ + switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: /* if it's used but less dirty it's best; @@ -183,7 +194,7 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob, return 1; } else { if (a->timestamp_dirty || - timercmp(&a->timestamp_created, &b->timestamp_created, >)) + timercmp(&a->timestamp_began, &b->timestamp_began, >)) return 1; if (ob->build_state->is_internal) /* XXX023 what the heck is this internal thing doing here. I @@ -269,17 +280,19 @@ circuit_get_best(const entry_connection_t *conn, if (!CIRCUIT_IS_ORIGIN(circ)) continue; origin_circ = TO_ORIGIN_CIRCUIT(circ); - if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, - need_uptime,need_internal,now.tv_sec)) - continue; + /* Log an info message if we're going to launch a new intro circ in + * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && circ->state != CIRCUIT_STATE_OPEN && - tv_mdiff(&now, &circ->timestamp_created) > circ_times.timeout_ms) { - intro_going_on_but_too_old = 1; - continue; + !must_be_open && origin_circ->hs_circ_has_timed_out) { + intro_going_on_but_too_old = 1; + continue; } + if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose, + need_uptime,need_internal,now.tv_sec)) + continue; + /* now this is an acceptable circ to hand back. but that doesn't * mean it's the *best* circ to hand back. try to decide. */ @@ -356,13 +369,38 @@ circuit_expire_building(void) * circuit_build_times_get_initial_timeout() if we haven't computed * custom timeouts yet */ struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff, - cannibalize_cutoff, close_cutoff, extremely_old_cutoff, - hs_extremely_old_cutoff; + close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff, + cannibalized_cutoff, c_intro_cutoff, s_intro_cutoff, stream_cutoff; const or_options_t *options = get_options(); struct timeval now; cpath_build_state_t *build_state; + int any_opened_circs = 0; tor_gettimeofday(&now); + + /* Check to see if we have any opened circuits. If we don't, + * we want to be more lenient with timeouts, in case the + * user has relocated and/or changed network connections. + * See bug #3443. */ + while (next_circ) { + if (!CIRCUIT_IS_ORIGIN(next_circ) || /* didn't originate here */ + next_circ->marked_for_close) { /* don't mess with marked circs */ + next_circ = next_circ->next; + continue; + } + + if (TO_ORIGIN_CIRCUIT(next_circ)->has_opened && + next_circ->state == CIRCUIT_STATE_OPEN && + TO_ORIGIN_CIRCUIT(next_circ)->build_state && + TO_ORIGIN_CIRCUIT(next_circ)->build_state->desired_path_len + == DEFAULT_ROUTE_LEN) { + any_opened_circs = 1; + break; + } + next_circ = next_circ->next; + } + next_circ = global_circuitlist; + #define SET_CUTOFF(target, msec) do { \ long ms = tor_lround(msec); \ struct timeval diff; \ @@ -371,10 +409,60 @@ circuit_expire_building(void) timersub(&now, &diff, &target); \ } while (0) + /** + * Because circuit build timeout is calculated only based on 3 hop + * general purpose circuit construction, we need to scale the timeout + * to make it properly apply to longer circuits, and circuits of + * certain usage types. The following diagram illustrates how we + * derive the scaling below. In short, we calculate the number + * of times our telescoping-based circuit construction causes cells + * to traverse each link for the circuit purpose types in question, + * and then assume each link is equivalent. + * + * OP --a--> A --b--> B --c--> C + * OP --a--> A --b--> B --c--> C --d--> D + * + * Let h = a = b = c = d + * + * Three hops (general_cutoff) + * RTTs = 3a + 2b + c + * RTTs = 6h + * Cannibalized: + * RTTs = a+b+c+d + * RTTs = 4h + * Four hops: + * RTTs = 4a + 3b + 2c + d + * RTTs = 10h + * Client INTRODUCE1+ACK: // XXX: correct? + * RTTs = 5a + 4b + 3c + 2d + * RTTs = 14h + * Server intro: + * RTTs = 4a + 3b + 2c + * RTTs = 9h + */ SET_CUTOFF(general_cutoff, circ_times.timeout_ms); SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms); - SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (4/3.0)); - SET_CUTOFF(cannibalize_cutoff, circ_times.timeout_ms / 2.0); + + /* > 3hop circs seem to have a 1.0 second delay on their cannibalized + * 4th hop. */ + SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000); + + /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell. + * Use the stream cutoff (more or less). */ + SET_CUTOFF(stream_cutoff, MAX(options->CircuitStreamTimeout,15)*1000 + 1000); + + /* Be lenient with cannibalized circs. They already survived the official + * CBT, and they're usually not perf-critical. */ + SET_CUTOFF(cannibalized_cutoff, + MAX(circ_times.close_ms*(4/6.0), + options->CircuitStreamTimeout * 1000) + 1000); + + // Intro circs have an extra round trip (and are also 4 hops long) + SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000); + + // Server intro circs have an extra round trip + SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000); + SET_CUTOFF(close_cutoff, circ_times.close_ms); SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000); @@ -387,28 +475,85 @@ circuit_expire_building(void) victim = next_circ; next_circ = next_circ->next; if (!CIRCUIT_IS_ORIGIN(victim) || /* didn't originate here */ - victim->marked_for_close) /* don't mess with marked circs */ + victim->marked_for_close) /* don't mess with marked circs */ + continue; + + /* If we haven't yet started the first hop, it means we don't have + * any orconns available, and thus have not started counting time yet + * for this circuit. See circuit_deliver_create_cell() and uses of + * timestamp_began. + * + * Continue to wait in this case. The ORConn should timeout + * independently and kill us then. + */ + if (TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_CLOSED) { continue; + } build_state = TO_ORIGIN_CIRCUIT(victim)->build_state; if (build_state && build_state->onehop_tunnel) cutoff = begindir_cutoff; - else if (build_state && build_state->desired_path_len == 4 - && !TO_ORIGIN_CIRCUIT(victim)->has_opened) - cutoff = fourhop_cutoff; - else if (TO_ORIGIN_CIRCUIT(victim)->has_opened) - cutoff = cannibalize_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; + else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || + victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + cutoff = c_intro_cutoff; + else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) + cutoff = s_intro_cutoff; + else if (victim->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND) + cutoff = stream_cutoff; + else if (victim->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING) + cutoff = close_cutoff; + else if (TO_ORIGIN_CIRCUIT(victim)->has_opened && + victim->state != CIRCUIT_STATE_OPEN) + cutoff = cannibalized_cutoff; + else if (build_state && build_state->desired_path_len >= 4) + cutoff = fourhop_cutoff; else cutoff = general_cutoff; if (TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) cutoff = hs_extremely_old_cutoff; - if (timercmp(&victim->timestamp_created, &cutoff, >)) + if (timercmp(&victim->timestamp_began, &cutoff, >)) continue; /* it's still young, leave it alone */ + if (!any_opened_circs) { + /* It's still young enough that we wouldn't close it, right? */ + if (timercmp(&victim->timestamp_began, &close_cutoff, >)) { + if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) { + int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath->state + == CPATH_STATE_OPEN; + log_info(LD_CIRC, + "No circuits are opened. Relaxing timeout for " + "a circuit with channel state %s. %d guards are live.", + channel_state_to_string(victim->n_chan->state), + num_live_entry_guards(0)); + + /* We count the timeout here for CBT, because technically this + * was a timeout, and the timeout value needs to reset if we + * see enough of them. Note this means we also need to avoid + * double-counting below, too. */ + circuit_build_times_count_timeout(&circ_times, first_hop_succeeded); + TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout = 1; + } + continue; + } else { + static ratelim_t relax_timeout_limit = RATELIM_INIT(3600); + char *m; + if ((m = rate_limit_log(&relax_timeout_limit, approx_time()))) { + log_notice(LD_CIRC, + "No circuits are opened. Relaxed timeout for " + "a circuit with channel state %s to %ldms. " + "However, it appears the circuit has timed out anyway. " + "%d guards are live. %s", + channel_state_to_string(victim->n_chan->state), + (long)circ_times.close_ms, num_live_entry_guards(0), m); + tor_free(m); + } + } + } + #if 0 /* some debug logs, to help track bugs */ if (victim->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING && @@ -436,8 +581,6 @@ circuit_expire_building(void) default: /* most open circuits can be left alone. */ continue; /* yes, continue inside a switch refers to the nearest * enclosing loop. C is smart. */ - case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - case CIRCUIT_PURPOSE_C_INTRODUCING: case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: break; /* too old, need to die */ case CIRCUIT_PURPOSE_C_REND_READY: @@ -449,6 +592,19 @@ circuit_expire_building(void) victim->timestamp_dirty > cutoff.tv_sec) continue; break; + case CIRCUIT_PURPOSE_PATH_BIAS_TESTING: + /* Open path bias testing circuits are given a long + * time to complete the test, but not forever */ + TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; + break; + case CIRCUIT_PURPOSE_C_INTRODUCING: + /* We keep old introducing circuits around for + * a while in parallel, and they can end up "opened". + * We decide below if we're going to mark them timed + * out and eventually close them. + */ + break; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: /* rend and intro circs become dirty each time they @@ -485,9 +641,12 @@ circuit_expire_building(void) /* Record this failure to check for too many timeouts * in a row. This function does not record a time value yet * (we do that later); it only counts the fact that we did - * have a timeout. */ - circuit_build_times_count_timeout(&circ_times, - first_hop_succeeded); + * have a timeout. We also want to avoid double-counting + * already "relaxed" circuits, which are counted above. */ + if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) { + circuit_build_times_count_timeout(&circ_times, + first_hop_succeeded); + } continue; } @@ -496,19 +655,31 @@ circuit_expire_building(void) * it off at, we probably had a suspend event along this codepath, * and we should discard the value. */ - if (timercmp(&victim->timestamp_created, &extremely_old_cutoff, <)) { + if (timercmp(&victim->timestamp_began, &extremely_old_cutoff, <)) { log_notice(LD_CIRC, "Extremely large value for circuit build timeout: %lds. " "Assuming clock jump. Purpose %d (%s)", - (long)(now.tv_sec - victim->timestamp_created.tv_sec), + (long)(now.tv_sec - victim->timestamp_began.tv_sec), victim->purpose, circuit_purpose_to_string(victim->purpose)); } else if (circuit_build_times_count_close(&circ_times, first_hop_succeeded, - victim->timestamp_created.tv_sec)) { + victim->timestamp_began.tv_sec)) { circuit_build_times_set_timeout(&circ_times); } } + + if (TO_ORIGIN_CIRCUIT(victim)->has_opened && + victim->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING) { + /* For path bias: we want to let these guys live for a while + * so we get a chance to test them. */ + log_info(LD_CIRC, + "Allowing cannibalized circuit %d time to finish building " + "as a pathbias testing circ.", + TO_ORIGIN_CIRCUIT(victim)->global_identifier); + circuit_change_purpose(victim, CIRCUIT_PURPOSE_PATH_BIAS_TESTING); + continue; /* It now should have a longer timeout next time */ + } } /* If this is a hidden service client circuit which is far enough @@ -534,6 +705,9 @@ circuit_expire_building(void) if (TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath == NULL) break; + /* fallthrough! */ + case CIRCUIT_PURPOSE_C_INTRODUCING: + /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ @@ -565,22 +739,32 @@ circuit_expire_building(void) continue; } - if (victim->n_conn) - log_info(LD_CIRC,"Abandoning circ %s:%d:%d (state %d:%s, purpose %d)", - victim->n_conn->_base.address, victim->n_conn->_base.port, + if (victim->n_chan) + log_info(LD_CIRC, + "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, " + "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier, + channel_get_canonical_remote_descr(victim->n_chan), victim->n_circ_id, + TO_ORIGIN_CIRCUIT(victim)->has_opened, victim->state, circuit_state_to_string(victim->state), - victim->purpose); + victim->purpose, + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); else - log_info(LD_CIRC,"Abandoning circ %d (state %d:%s, purpose %d)", - victim->n_circ_id, victim->state, - circuit_state_to_string(victim->state), victim->purpose); + log_info(LD_CIRC, + "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)", + TO_ORIGIN_CIRCUIT(victim)->global_identifier, + victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened, + victim->state, + circuit_state_to_string(victim->state), victim->purpose, + TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len); circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim)); if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) circuit_mark_for_close(victim, END_CIRC_REASON_MEASUREMENT_EXPIRED); else circuit_mark_for_close(victim, END_CIRC_REASON_TIMEOUT); + + pathbias_count_timeout(TO_ORIGIN_CIRCUIT(victim)); } } @@ -787,7 +971,7 @@ circuit_build_needed_circs(time_t now) circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL); if (get_options()->RunTesting && circ && - circ->timestamp_created.tv_sec + TESTING_CIRCUIT_INTERVAL < now) { + circ->timestamp_began.tv_sec + TESTING_CIRCUIT_INTERVAL < now) { log_fn(LOG_INFO,"Creating a new testing circuit."); circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } @@ -808,7 +992,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) tor_assert(circ); tor_assert(conn); - if (conn->_base.type == CONN_TYPE_AP) { + if (conn->base_.type == CONN_TYPE_AP) { entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); entry_conn->may_use_optimistic_data = 0; } @@ -907,7 +1091,7 @@ circuit_expire_old_circuits_clientside(void) circ->purpose); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!circ->timestamp_dirty && circ->state == CIRCUIT_STATE_OPEN) { - if (timercmp(&circ->timestamp_created, &cutoff, <)) { + if (timercmp(&circ->timestamp_began, &cutoff, <)) { if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL || circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT || circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -917,7 +1101,7 @@ circuit_expire_old_circuits_clientside(void) circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) { log_debug(LD_CIRC, "Closing circuit that has been unused for %ld msec.", - tv_mdiff(&circ->timestamp_created, &now)); + tv_mdiff(&circ->timestamp_began, &now)); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) { /* Server-side rend joined circuits can end up really old, because @@ -931,7 +1115,7 @@ circuit_expire_old_circuits_clientside(void) "Ancient non-dirty circuit %d is still around after " "%ld milliseconds. Purpose: %d (%s)", TO_ORIGIN_CIRCUIT(circ)->global_identifier, - tv_mdiff(&circ->timestamp_created, &now), + tv_mdiff(&circ->timestamp_began, &now), circ->purpose, circuit_purpose_to_string(circ->purpose)); TO_ORIGIN_CIRCUIT(circ)->is_ancient = 1; @@ -977,13 +1161,13 @@ circuit_expire_old_circuits_serverside(time_t now) /* If the circuit has been idle for too long, and there are no streams * on it, and it ends here, and it used a create_fast, mark it for close. */ - if (or_circ->is_first_hop && !circ->n_conn && + if (or_circ->is_first_hop && !circ->n_chan && !or_circ->n_streams && !or_circ->resolving_streams && - or_circ->p_conn && - or_circ->p_conn->timestamp_last_added_nonpadding <= cutoff) { + or_circ->p_chan && + channel_when_last_xmit(or_circ->p_chan) <= cutoff) { log_info(LD_CIRC, "Closing circ_id %d (empty %d secs ago)", or_circ->p_circ_id, - (int)(now - or_circ->p_conn->timestamp_last_added_nonpadding)); + (int)(now - channel_when_last_xmit(or_circ->p_chan))); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } } @@ -1125,7 +1309,7 @@ static int circuit_try_clearing_isolation_state(origin_circuit_t *circ) { if (/* The circuit may have become non-open if it was cannibalized.*/ - circ->_base.state == CIRCUIT_STATE_OPEN && + circ->base_.state == CIRCUIT_STATE_OPEN && /* If !isolation_values_set, there is nothing to clear. */ circ->isolation_values_set && /* It's not legal to clear a circuit's isolation info if it's ever had @@ -1163,6 +1347,7 @@ circuit_try_attaching_streams(origin_circuit_t *circ) void circuit_build_failed(origin_circuit_t *circ) { + channel_t *n_chan = NULL; /* we should examine circ and see if it failed because of * the last hop or an earlier hop. then use this info below. */ @@ -1179,11 +1364,12 @@ circuit_build_failed(origin_circuit_t *circ) /* We failed at the first hop. If there's an OR connection * to blame, blame it. Also, avoid this relay for a while, and * fail any one-hop directory fetches destined for it. */ - const char *n_conn_id = circ->cpath->extend_info->identity_digest; + const char *n_chan_id = circ->cpath->extend_info->identity_digest; int already_marked = 0; - if (circ->_base.n_conn) { - or_connection_t *n_conn = circ->_base.n_conn; - if (n_conn->is_bad_for_new_circs) { + if (circ->base_.n_chan) { + n_chan = circ->base_.n_chan; + + if (n_chan->is_bad_for_new_circs) { /* We only want to blame this router when a fresh healthy * connection fails. So don't mark this router as newly failed, * since maybe this was just an old circuit attempt that's @@ -1195,22 +1381,22 @@ circuit_build_failed(origin_circuit_t *circ) } log_info(LD_OR, "Our circuit failed to get a response from the first hop " - "(%s:%d). I'm going to try to rotate to a better connection.", - n_conn->_base.address, n_conn->_base.port); - n_conn->is_bad_for_new_circs = 1; + "(%s). I'm going to try to rotate to a better connection.", + channel_get_canonical_remote_descr(n_chan)); + n_chan->is_bad_for_new_circs = 1; } else { log_info(LD_OR, "Our circuit died before the first hop with no connection"); } - if (n_conn_id && !already_marked) { - entry_guard_register_connect_status(n_conn_id, 0, 1, time(NULL)); + if (n_chan_id && !already_marked) { + entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL)); /* if there are any one-hop streams waiting on this circuit, fail * them now so they can retry elsewhere. */ - connection_ap_fail_onehop(n_conn_id, circ->build_state); + connection_ap_fail_onehop(n_chan_id, circ->build_state); } } - switch (circ->_base.purpose) { + switch (circ->base_.purpose) { case CIRCUIT_PURPOSE_C_GENERAL: /* If we never built the circuit, note it as a failure. */ circuit_increment_failure_count(); @@ -1225,7 +1411,7 @@ circuit_build_failed(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at Bob, waiting for introductions */ - if (circ->_base.state != CIRCUIT_STATE_OPEN) { + if (circ->base_.state != CIRCUIT_STATE_OPEN) { circuit_increment_failure_count(); } /* no need to care here, because bob will rebuild intro @@ -1309,21 +1495,44 @@ circuit_launch_by_extend_info(uint8_t purpose, * internal circs rather than exit circs? -RD */ circ = circuit_find_to_cannibalize(purpose, extend_info, flags); if (circ) { - uint8_t old_purpose = circ->_base.purpose; - struct timeval old_timestamp_created; + uint8_t old_purpose = circ->base_.purpose; + struct timeval old_timestamp_began; log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)", build_state_get_exit_nickname(circ->build_state), purpose, circuit_purpose_to_string(purpose)); + if ((purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || + purpose == CIRCUIT_PURPOSE_C_INTRODUCING) && + circ->path_state == PATH_STATE_BUILD_SUCCEEDED) { + /* Path bias: Cannibalized rends pre-emptively count as a + * successfully used circ. We don't wait until the extend, + * because the rend point could be malicious. + * + * Same deal goes for client side introductions. Clients + * can be manipulated to connect repeatedly to them + * (especially web clients). + * + * If we decide to probe the initial portion of these circs, + * (up to the adversaries final hop), we need to remove this. + */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + /* This must be called before the purpose change */ + pathbias_check_close(circ, END_CIRC_REASON_FINISHED); + } + circuit_change_purpose(TO_CIRCUIT(circ), purpose); - /* reset the birth date of this circ, else expire_building + /* Reset the start date of this circ, else expire_building * will see it and think it's been trying to build since it - * began. */ - tor_gettimeofday(&circ->_base.timestamp_created); + * began. + * + * Technically, the code should reset this when the + * create cell is finally sent, but we're close enough + * here. */ + tor_gettimeofday(&circ->base_.timestamp_began); control_event_circuit_cannibalized(circ, old_purpose, - &old_timestamp_created); + &old_timestamp_began); switch (purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: @@ -1513,8 +1722,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if ((m = rate_limit_log(&delay_limit, approx_time()))) { log_notice(LD_APP, "We'd like to launch a circuit to handle a " "connection, but we already have %d general-purpose client " - "circuits pending. Waiting until some finish.", - n_pending); + "circuits pending. Waiting until some finish.%s", + n_pending, m); tor_free(m); } return 0; @@ -1570,9 +1779,9 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, escaped_safe_str_client(conn->socks_request->address)); return -1; } - extend_info = extend_info_alloc(conn->chosen_exit_name+1, - digest, NULL, &addr, - conn->socks_request->port); + extend_info = extend_info_new(conn->chosen_exit_name+1, + digest, NULL, NULL, &addr, + conn->socks_request->port); } else { /* We will need an onion key for the router, and we * don't have one. Refuse or relax requirements. */ @@ -1634,8 +1843,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (circ) { /* write the service_id into circ */ circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); - if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && - circ->_base.state == CIRCUIT_STATE_OPEN) + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && + circ->base_.state == CIRCUIT_STATE_OPEN) rend_client_rendcirc_has_opened(circ); } } @@ -1698,7 +1907,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, /* add it into the linked list of streams on this circuit */ log_debug(LD_APP|LD_CIRC, "attaching new conn to circ. n_circ_id %d.", - circ->_base.n_circ_id); + circ->base_.n_circ_id); /* reset it, so we can measure circ timeouts */ ENTRY_TO_CONN(apconn)->timestamp_lastread = time(NULL); ENTRY_TO_EDGE_CONN(apconn)->next_stream = circ->p_streams; @@ -1734,7 +1943,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, exitnode->rs) { /* Okay; we know what exit node this is. */ if (optimistic_data_enabled() && - circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL && + circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL && exitnode->rs->version_supports_optimistic_data) apconn->may_use_optimistic_data = 1; else @@ -1820,12 +2029,12 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, base_conn->state == AP_CONN_STATE_CONTROLLER_WAIT); tor_assert(conn->socks_request); tor_assert(circ); - tor_assert(circ->_base.state == CIRCUIT_STATE_OPEN); + tor_assert(circ->base_.state == CIRCUIT_STATE_OPEN); base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - if (!circ->_base.timestamp_dirty) - circ->_base.timestamp_dirty = time(NULL); + if (!circ->base_.timestamp_dirty) + circ->base_.timestamp_dirty = time(NULL); link_apconn_to_circ(conn, circ, cpath); tor_assert(conn->socks_request); @@ -1921,7 +2130,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) log_debug(LD_APP|LD_CIRC, "Attaching apconn to circ %d (stream %d sec old).", - circ->_base.n_circ_id, conn_age); + circ->base_.n_circ_id, conn_age); /* print the circ's path, so people can figure out which circs are * sucking. */ circuit_log_path(LOG_INFO,LD_APP|LD_CIRC,circ); @@ -1946,25 +2155,25 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) log_info(LD_REND, "rend joined circ %d already here. attaching. " "(stream %d sec old)", - rendcirc->_base.n_circ_id, conn_age); + rendcirc->base_.n_circ_id, conn_age); /* Mark rendezvous circuits as 'newly dirty' every time you use * them, since the process of rebuilding a rendezvous circ is so * expensive. There is a tradeoff between linkability and * feasibility, at this point. */ - rendcirc->_base.timestamp_dirty = time(NULL); + rendcirc->base_.timestamp_dirty = time(NULL); link_apconn_to_circ(conn, rendcirc, NULL); if (connection_ap_handshake_send_begin(conn) < 0) return 0; /* already marked, let them fade away */ return 1; } - if (rendcirc && (rendcirc->_base.purpose == + if (rendcirc && (rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) { log_info(LD_REND, "pending-join circ %d already here, with intro ack. " "Stalling. (stream %d sec old)", - rendcirc->_base.n_circ_id, conn_age); + rendcirc->base_.n_circ_id, conn_age); return 0; } @@ -1975,51 +2184,35 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ - circuit_t *c = NULL; tor_assert(introcirc); log_info(LD_REND, "Intro circ %d present and awaiting ack (rend %d). " "Stalling. (stream %d sec old)", - introcirc->_base.n_circ_id, - rendcirc ? rendcirc->_base.n_circ_id : 0, + introcirc->base_.n_circ_id, + rendcirc ? rendcirc->base_.n_circ_id : 0, conn_age); - /* abort parallel intro circs, if any */ - for (c = global_circuitlist; c; c = c->next) { - if (c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && - !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { - origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); - if (oc->rend_data && - !rend_cmp_service_ids( - ENTRY_TO_EDGE_CONN(conn)->rend_data->onion_address, - oc->rend_data->onion_address)) { - log_info(LD_REND|LD_CIRC, "Closing introduction circuit that we " - "built in parallel."); - circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT); - } - } - } return 0; } /* now rendcirc and introcirc are each either undefined or not finished */ if (rendcirc && introcirc && - rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY) { + rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY) { log_info(LD_REND, "ready rend circ %d already here (no intro-ack yet on " "intro %d). (stream %d sec old)", - rendcirc->_base.n_circ_id, - introcirc->_base.n_circ_id, conn_age); + rendcirc->base_.n_circ_id, + introcirc->base_.n_circ_id, conn_age); - tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); - if (introcirc->_base.state == CIRCUIT_STATE_OPEN) { + tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { log_info(LD_REND,"found open intro circ %d (rend %d); sending " "introduction. (stream %d sec old)", - introcirc->_base.n_circ_id, rendcirc->_base.n_circ_id, + introcirc->base_.n_circ_id, rendcirc->base_.n_circ_id, conn_age); switch (rend_client_send_introduction(introcirc, rendcirc)) { case 0: /* success */ - rendcirc->_base.timestamp_dirty = time(NULL); - introcirc->_base.timestamp_dirty = time(NULL); + rendcirc->base_.timestamp_dirty = time(NULL); + introcirc->base_.timestamp_dirty = time(NULL); assert_circuit_ok(TO_CIRCUIT(rendcirc)); assert_circuit_ok(TO_CIRCUIT(introcirc)); return 0; @@ -2036,8 +2229,8 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) log_info(LD_REND, "Intro (%d) and rend (%d) circs are not both ready. " "Stalling conn. (%d sec old)", - introcirc ? introcirc->_base.n_circ_id : 0, - rendcirc ? rendcirc->_base.n_circ_id : 0, conn_age); + introcirc ? introcirc->base_.n_circ_id : 0, + rendcirc ? rendcirc->base_.n_circ_id : 0, conn_age); return 0; } } diff --git a/src/or/circuituse.h b/src/or/circuituse.h index be2bd7ec5..e8760c22d 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -9,8 +9,8 @@ * \brief Header file for circuituse.c. **/ -#ifndef _TOR_CIRCUITUSE_H -#define _TOR_CIRCUITUSE_H +#ifndef TOR_CIRCUITUSE_H +#define TOR_CIRCUITUSE_H void circuit_expire_building(void); void circuit_remove_handled_ports(smartlist_t *needed_ports); diff --git a/src/or/command.c b/src/or/command.c index d935b5b18..7d1f53a87 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -12,10 +12,13 @@ /* In-points to command.c: * * - command_process_cell(), called from - * connection_or_process_cells_from_inbuf() in connection_or.c + * incoming cell handlers of channel_t instances; + * callbacks registered in command_setup_channel(), + * called when channels are created in circuitbuild.c */ #include "or.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" #include "command.h" @@ -31,8 +34,6 @@ #include "router.h" #include "routerlist.h" -/** How many CELL_PADDING cells have we received, ever? */ -uint64_t stats_n_padding_cells_processed = 0; /** How many CELL_CREATE cells have we received, ever? */ uint64_t stats_n_create_cells_processed = 0; /** How many CELL_CREATED cells have we received, ever? */ @@ -41,38 +42,16 @@ uint64_t stats_n_created_cells_processed = 0; uint64_t stats_n_relay_cells_processed = 0; /** How many CELL_DESTROY cells have we received, ever? */ uint64_t stats_n_destroy_cells_processed = 0; -/** How many CELL_VERSIONS cells have we received, ever? */ -uint64_t stats_n_versions_cells_processed = 0; -/** How many CELL_NETINFO cells have we received, ever? */ -uint64_t stats_n_netinfo_cells_processed = 0; -/** How many CELL_VPADDING cells have we received, ever? */ -uint64_t stats_n_vpadding_cells_processed = 0; -/** How many CELL_CERTS cells have we received, ever? */ -uint64_t stats_n_certs_cells_processed = 0; -/** How many CELL_AUTH_CHALLENGE cells have we received, ever? */ -uint64_t stats_n_auth_challenge_cells_processed = 0; -/** How many CELL_AUTHENTICATE cells have we received, ever? */ -uint64_t stats_n_authenticate_cells_processed = 0; -/** How many CELL_AUTHORIZE cells have we received, ever? */ -uint64_t stats_n_authorize_cells_processed = 0; +/* Handle an incoming channel */ +static void command_handle_incoming_channel(channel_listener_t *listener, + channel_t *chan); /* These are the main functions for processing cells */ -static void command_process_create_cell(cell_t *cell, or_connection_t *conn); -static void command_process_created_cell(cell_t *cell, or_connection_t *conn); -static void command_process_relay_cell(cell_t *cell, or_connection_t *conn); -static void command_process_destroy_cell(cell_t *cell, or_connection_t *conn); -static void command_process_versions_cell(var_cell_t *cell, - or_connection_t *conn); -static void command_process_netinfo_cell(cell_t *cell, or_connection_t *conn); -static void command_process_certs_cell(var_cell_t *cell, - or_connection_t *conn); -static void command_process_auth_challenge_cell(var_cell_t *cell, - or_connection_t *conn); -static void command_process_authenticate_cell(var_cell_t *cell, - or_connection_t *conn); -static int enter_v3_handshake_with_cell(var_cell_t *cell, - or_connection_t *conn); +static void command_process_create_cell(cell_t *cell, channel_t *chan); +static void command_process_created_cell(cell_t *cell, channel_t *chan); +static void command_process_relay_cell(cell_t *cell, channel_t *chan); +static void command_process_destroy_cell(cell_t *cell, channel_t *chan); #ifdef KEEP_TIMING_STATS /** This is a wrapper function around the actual function that processes the @@ -80,15 +59,15 @@ static int enter_v3_handshake_with_cell(var_cell_t *cell, * by the number of microseconds used by the call to <b>*func(cell, conn)</b>. */ static void -command_time_process_cell(cell_t *cell, or_connection_t *conn, int *time, - void (*func)(cell_t *, or_connection_t *)) +command_time_process_cell(cell_t *cell, channel_t *chan, int *time, + void (*func)(cell_t *, channel_t *)) { struct timeval start, end; long time_passed; tor_gettimeofday(&start); - (*func)(cell, conn); + (*func)(cell, chan); tor_gettimeofday(&end); time_passed = tv_udiff(&start, &end) ; @@ -104,15 +83,14 @@ command_time_process_cell(cell_t *cell, or_connection_t *conn, int *time, } #endif -/** Process a <b>cell</b> that was just received on <b>conn</b>. Keep internal +/** Process a <b>cell</b> that was just received on <b>chan</b>. Keep internal * statistics about how many of each cell we've processed so far * this second, and the total number of microseconds it took to * process each type of cell. */ void -command_process_cell(cell_t *cell, or_connection_t *conn) +command_process_cell(channel_t *chan, cell_t *cell) { - int handshaking = (conn->_base.state != OR_CONN_STATE_OPEN); #ifdef KEEP_TIMING_STATS /* how many of each cell have we seen so far this second? needs better * name. */ @@ -152,255 +130,118 @@ command_process_cell(cell_t *cell, or_connection_t *conn) #define PROCESS_CELL(tp, cl, cn) command_process_ ## tp ## _cell(cl, cn) #endif - if (conn->_base.marked_for_close) - return; - - /* Reject all but VERSIONS and NETINFO when handshaking. */ - /* (VERSIONS should actually be impossible; it's variable-length.) */ - if (handshaking && cell->command != CELL_VERSIONS && - cell->command != CELL_NETINFO) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received unexpected cell command %d in state %s; closing the " - "connection.", - (int)cell->command, - conn_state_to_string(CONN_TYPE_OR,conn->_base.state)); - connection_mark_for_close(TO_CONN(conn)); - return; - } - - if (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) - or_handshake_state_record_cell(conn->handshake_state, cell, 1); - switch (cell->command) { - case CELL_PADDING: - ++stats_n_padding_cells_processed; - /* do nothing */ - break; case CELL_CREATE: case CELL_CREATE_FAST: + case CELL_CREATE2: ++stats_n_create_cells_processed; - PROCESS_CELL(create, cell, conn); + PROCESS_CELL(create, cell, chan); break; case CELL_CREATED: case CELL_CREATED_FAST: + case CELL_CREATED2: ++stats_n_created_cells_processed; - PROCESS_CELL(created, cell, conn); + PROCESS_CELL(created, cell, chan); break; case CELL_RELAY: case CELL_RELAY_EARLY: ++stats_n_relay_cells_processed; - PROCESS_CELL(relay, cell, conn); + PROCESS_CELL(relay, cell, chan); break; case CELL_DESTROY: ++stats_n_destroy_cells_processed; - PROCESS_CELL(destroy, cell, conn); - break; - case CELL_VERSIONS: - tor_fragile_assert(); - break; - case CELL_NETINFO: - ++stats_n_netinfo_cells_processed; - PROCESS_CELL(netinfo, cell, conn); + PROCESS_CELL(destroy, cell, chan); break; default: log_fn(LOG_INFO, LD_PROTOCOL, - "Cell of unknown type (%d) received. Dropping.", cell->command); + "Cell of unknown or unexpected type (%d) received. " + "Dropping.", + cell->command); break; } } -/** Return true if <b>command</b> is a cell command that's allowed to start a - * V3 handshake. */ -static int -command_allowed_before_handshake(uint8_t command) -{ - switch (command) { - case CELL_VERSIONS: - case CELL_VPADDING: - case CELL_AUTHORIZE: - return 1; - default: - return 0; - } -} - -/** Process a <b>cell</b> that was just received on <b>conn</b>. Keep internal - * statistics about how many of each cell we've processed so far - * this second, and the total number of microseconds it took to - * process each type of cell. +/** Process an incoming var_cell from a channel; in the current protocol all + * the var_cells are handshake-related and handled below the channel layer, + * so this just logs a warning and drops the cell. */ + void -command_process_var_cell(var_cell_t *cell, or_connection_t *conn) +command_process_var_cell(channel_t *chan, var_cell_t *var_cell) { -#ifdef KEEP_TIMING_STATS - /* how many of each cell have we seen so far this second? needs better - * name. */ - static int num_versions=0, num_certs=0; - - time_t now = time(NULL); - - if (now > current_second) { /* the second has rolled over */ - /* print stats */ - log_info(LD_OR, - "At end of second: %d versions (%d ms), %d certs (%d ms)", - num_versions, versions_time/1000, - num_certs, certs_time/1000); - - num_versions = num_certs = 0; - versions_time = certs_time = 0; - - /* remember which second it is, for next time */ - current_second = now; - } -#endif - - if (conn->_base.marked_for_close) - return; - - switch (conn->_base.state) - { - case OR_CONN_STATE_OR_HANDSHAKING_V2: - if (cell->command != CELL_VERSIONS) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received a cell with command %d in state %s; " - "closing the connection.", - (int)cell->command, - conn_state_to_string(CONN_TYPE_OR,conn->_base.state)); - connection_mark_for_close(TO_CONN(conn)); - return; - } - break; - case OR_CONN_STATE_TLS_HANDSHAKING: - /* If we're using bufferevents, it's entirely possible for us to - * notice "hey, data arrived!" before we notice "hey, the handshake - * finished!" And we need to be accepting both at once to handle both - * the v2 and v3 handshakes. */ - - /* fall through */ - case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: - if (! command_allowed_before_handshake(cell->command)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received a cell with command %d in state %s; " - "closing the connection.", - (int)cell->command, - conn_state_to_string(CONN_TYPE_OR,conn->_base.state)); - connection_mark_for_close(TO_CONN(conn)); - return; - } else { - if (enter_v3_handshake_with_cell(cell, conn)<0) - return; - } - break; - case OR_CONN_STATE_OR_HANDSHAKING_V3: - if (cell->command != CELL_AUTHENTICATE) - or_handshake_state_record_var_cell(conn->handshake_state, cell, 1); - break; /* Everything is allowed */ - case OR_CONN_STATE_OPEN: - if (conn->link_proto < 3) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received a variable-length cell with command %d in state %s " - "with link protocol %d; ignoring it.", - (int)cell->command, - conn_state_to_string(CONN_TYPE_OR,conn->_base.state), - (int)conn->link_proto); - return; - } - break; - default: - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received var-length cell with command %d in unexpected state " - "%s [%d]; ignoring it.", - (int)cell->command, - conn_state_to_string(CONN_TYPE_OR,conn->_base.state), - (int)conn->_base.state); - return; - } + tor_assert(chan); + tor_assert(var_cell); - switch (cell->command) { - case CELL_VERSIONS: - ++stats_n_versions_cells_processed; - PROCESS_CELL(versions, cell, conn); - break; - case CELL_VPADDING: - ++stats_n_vpadding_cells_processed; - /* Do nothing */ - break; - case CELL_CERTS: - ++stats_n_certs_cells_processed; - PROCESS_CELL(certs, cell, conn); - break; - case CELL_AUTH_CHALLENGE: - ++stats_n_auth_challenge_cells_processed; - PROCESS_CELL(auth_challenge, cell, conn); - break; - case CELL_AUTHENTICATE: - ++stats_n_authenticate_cells_processed; - PROCESS_CELL(authenticate, cell, conn); - break; - case CELL_AUTHORIZE: - ++stats_n_authorize_cells_processed; - /* Ignored so far. */ - break; - default: - log_fn(LOG_INFO, LD_PROTOCOL, - "Variable-length cell of unknown type (%d) received.", - cell->command); - break; - } + log_info(LD_PROTOCOL, + "Received unexpected var_cell above the channel layer of type %d" + "; dropping it.", + var_cell->command); } -/** Process a 'create' <b>cell</b> that just arrived from <b>conn</b>. Make a +/** Process a 'create' <b>cell</b> that just arrived from <b>chan</b>. Make a * new circuit with the p_circ_id specified in cell. Put the circuit in state * onionskin_pending, and pass the onionskin to the cpuworker. Circ will get * picked up again when the cpuworker finishes decrypting it. */ static void -command_process_create_cell(cell_t *cell, or_connection_t *conn) +command_process_create_cell(cell_t *cell, channel_t *chan) { or_circuit_t *circ; const or_options_t *options = get_options(); int id_is_high; + create_cell_t *create_cell; + + tor_assert(cell); + tor_assert(chan); + + log_debug(LD_OR, + "Got a CREATE cell for circ_id %d on channel " U64_FORMAT + " (%p)", + cell->circ_id, + U64_PRINTF_ARG(chan->global_identifier), chan); if (we_are_hibernating()) { log_info(LD_OR, "Received create cell but we're shutting down. Sending back " "destroy."); - connection_or_send_destroy(cell->circ_id, conn, + channel_send_destroy(cell->circ_id, chan, END_CIRC_REASON_HIBERNATING); return; } if (!server_mode(options) || - (!public_server_mode(options) && conn->is_outgoing)) { + (!public_server_mode(options) && channel_is_outgoing(chan))) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received create cell (type %d) from %s:%d, but we're connected " + "Received create cell (type %d) from %s, but we're connected " "to it as a client. " "Sending back a destroy.", - (int)cell->command, conn->_base.address, conn->_base.port); - connection_or_send_destroy(cell->circ_id, conn, - END_CIRC_REASON_TORPROTOCOL); + (int)cell->command, channel_get_canonical_remote_descr(chan)); + channel_send_destroy(cell->circ_id, chan, + END_CIRC_REASON_TORPROTOCOL); return; } /* If the high bit of the circuit ID is not as expected, close the * circ. */ id_is_high = cell->circ_id & (1<<15); - if ((id_is_high && conn->circ_id_type == CIRC_ID_TYPE_HIGHER) || - (!id_is_high && conn->circ_id_type == CIRC_ID_TYPE_LOWER)) { + if ((id_is_high && + chan->circ_id_type == CIRC_ID_TYPE_HIGHER) || + (!id_is_high && + chan->circ_id_type == CIRC_ID_TYPE_LOWER)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Received create cell with unexpected circ_id %d. Closing.", cell->circ_id); - connection_or_send_destroy(cell->circ_id, conn, - END_CIRC_REASON_TORPROTOCOL); + channel_send_destroy(cell->circ_id, chan, + END_CIRC_REASON_TORPROTOCOL); return; } - if (circuit_id_in_use_on_orconn(cell->circ_id, conn)) { - const node_t *node = node_get_by_id(conn->identity_digest); + if (circuit_id_in_use_on_channel(cell->circ_id, chan)) { + const node_t *node = node_get_by_id(chan->identity_digest); log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Received CREATE cell (circID %d) for known circ. " "Dropping (age %d).", - cell->circ_id, (int)(time(NULL) - conn->_base.timestamp_created)); + cell->circ_id, (int)(time(NULL) - channel_when_created(chan))); if (node) { char *p = esc_for_log(node_get_platform(node)); log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, @@ -411,23 +252,22 @@ command_process_create_cell(cell_t *cell, or_connection_t *conn) return; } - circ = or_circuit_new(cell->circ_id, conn); - circ->_base.purpose = CIRCUIT_PURPOSE_OR; + circ = or_circuit_new(cell->circ_id, chan); + circ->base_.purpose = CIRCUIT_PURPOSE_OR; circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_ONIONSKIN_PENDING); - if (cell->command == CELL_CREATE) { - char *onionskin = tor_malloc(ONIONSKIN_CHALLENGE_LEN); - memcpy(onionskin, cell->payload, ONIONSKIN_CHALLENGE_LEN); + create_cell = tor_malloc_zero(sizeof(create_cell_t)); + if (create_cell_parse(create_cell, cell) < 0) { + tor_free(create_cell); + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Bogus/unrecognized create cell; closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return; + } + if (create_cell->handshake_type != ONION_HANDSHAKE_TYPE_FAST) { /* hand it off to the cpuworkers, and then return. */ - if (assign_onionskin_to_cpuworker(NULL, circ, onionskin) < 0) { -#define WARN_HANDOFF_FAILURE_INTERVAL (6*60*60) - static ratelim_t handoff_warning = - RATELIM_INIT(WARN_HANDOFF_FAILURE_INTERVAL); - char *m; - if ((m = rate_limit_log(&handoff_warning, approx_time()))) { - log_warn(LD_GENERAL,"Failed to hand off onionskin. Closing.%s",m); - tor_free(m); - } + if (assign_onionskin_to_cpuworker(NULL, circ, create_cell) < 0) { + log_debug(LD_GENERAL,"Failed to hand off onionskin. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_RESOURCELIMIT); return; } @@ -435,30 +275,44 @@ command_process_create_cell(cell_t *cell, or_connection_t *conn) } else { /* This is a CREATE_FAST cell; we can handle it immediately without using * a CPU worker. */ - char keys[CPATH_KEY_MATERIAL_LEN]; - char reply[DIGEST_LEN*2]; - - tor_assert(cell->command == CELL_CREATE_FAST); + uint8_t keys[CPATH_KEY_MATERIAL_LEN]; + uint8_t rend_circ_nonce[DIGEST_LEN]; + int len; + created_cell_t created_cell; /* Make sure we never try to use the OR connection on which we * received this cell to satisfy an EXTEND request, */ - conn->is_connection_with_client = 1; - - if (fast_server_handshake(cell->payload, (uint8_t*)reply, - (uint8_t*)keys, sizeof(keys))<0) { + channel_mark_client(chan); + + memset(&created_cell, 0, sizeof(created_cell)); + len = onion_skin_server_handshake(ONION_HANDSHAKE_TYPE_FAST, + create_cell->onionskin, + create_cell->handshake_len, + NULL, + created_cell.reply, + keys, CPATH_KEY_MATERIAL_LEN, + rend_circ_nonce); + tor_free(create_cell); + if (len < 0) { log_warn(LD_OR,"Failed to generate key material. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + tor_free(create_cell); return; } - if (onionskin_answer(circ, CELL_CREATED_FAST, reply, keys)<0) { + created_cell.cell_type = CELL_CREATED_FAST; + created_cell.handshake_len = len; + + if (onionskin_answer(circ, &created_cell, + (const char *)keys, rend_circ_nonce)<0) { log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return; } + memwipe(keys, 0, sizeof(keys)); } } -/** Process a 'created' <b>cell</b> that just arrived from <b>conn</b>. +/** Process a 'created' <b>cell</b> that just arrived from <b>chan</b>. * Find the circuit * that it's intended for. If we're not the origin of the circuit, package * the 'created' cell in an 'extended' relay cell and pass it back. If we @@ -467,11 +321,12 @@ command_process_create_cell(cell_t *cell, or_connection_t *conn) * extend to the next hop in the circuit if necessary. */ static void -command_process_created_cell(cell_t *cell, or_connection_t *conn) +command_process_created_cell(cell_t *cell, channel_t *chan) { circuit_t *circ; + extended_cell_t extended_cell; - circ = circuit_get_by_circid_orconn(cell->circ_id, conn); + circ = circuit_get_by_circid_channel(cell->circ_id, chan); if (!circ) { log_info(LD_OR, @@ -487,12 +342,18 @@ command_process_created_cell(cell_t *cell, or_connection_t *conn) return; } + if (created_cell_parse(&extended_cell.created_cell, cell) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, "Unparseable created cell."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + return; + } + if (CIRCUIT_IS_ORIGIN(circ)) { /* we're the OP. Handshake this. */ origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); int err_reason = 0; log_debug(LD_OR,"at OP. Finishing handshake."); - if ((err_reason = circuit_finish_handshake(origin_circ, cell->command, - cell->payload)) < 0) { + if ((err_reason = circuit_finish_handshake(origin_circ, + &extended_cell.created_cell)) < 0) { log_warn(LD_OR,"circuit_finish_handshake failed."); circuit_mark_for_close(circ, -err_reason); return; @@ -505,11 +366,24 @@ command_process_created_cell(cell_t *cell, or_connection_t *conn) return; } } else { /* pack it into an extended relay cell, and send it. */ + uint8_t command=0; + uint16_t len=0; + uint8_t payload[RELAY_PAYLOAD_SIZE]; log_debug(LD_OR, "Converting created cell to extended relay cell, sending."); - relay_send_command_from_edge(0, circ, RELAY_COMMAND_EXTENDED, - (char*)cell->payload, ONIONSKIN_REPLY_LEN, - NULL); + memset(payload, 0, sizeof(payload)); + if (extended_cell.created_cell.cell_type == CELL_CREATED2) + extended_cell.cell_type = RELAY_COMMAND_EXTENDED2; + else + extended_cell.cell_type = RELAY_COMMAND_EXTENDED; + if (extended_cell_format(&command, &len, payload, &extended_cell) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, "Can't format extended cell."); + circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); + return; + } + + relay_send_command_from_edge(0, circ, command, + (const char*)payload, len, NULL); } } @@ -518,17 +392,17 @@ command_process_created_cell(cell_t *cell, or_connection_t *conn) * circuit_receive_relay_cell() for actual processing. */ static void -command_process_relay_cell(cell_t *cell, or_connection_t *conn) +command_process_relay_cell(cell_t *cell, channel_t *chan) { circuit_t *circ; int reason, direction; - circ = circuit_get_by_circid_orconn(cell->circ_id, conn); + circ = circuit_get_by_circid_channel(cell->circ_id, chan); if (!circ) { log_debug(LD_OR, - "unknown circuit %d on connection from %s:%d. Dropping.", - cell->circ_id, conn->_base.address, conn->_base.port); + "unknown circuit %d on connection from %s. Dropping.", + cell->circ_id, channel_get_canonical_remote_descr(chan)); return; } @@ -541,7 +415,7 @@ command_process_relay_cell(cell_t *cell, or_connection_t *conn) if (CIRCUIT_IS_ORIGIN(circ)) { /* if we're a relay and treating connections with recent local * traffic better, then this is one of them. */ - conn->client_used = time(NULL); + channel_timestamp_client(chan); } if (!CIRCUIT_IS_ORIGIN(circ) && @@ -562,10 +436,10 @@ command_process_relay_cell(cell_t *cell, or_connection_t *conn) or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); if (or_circ->remaining_relay_early_cells == 0) { log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Received too many RELAY_EARLY cells on circ %d from %s:%d." + "Received too many RELAY_EARLY cells on circ %d from %s." " Closing circuit.", - cell->circ_id, safe_str(conn->_base.address), - conn->_base.port); + cell->circ_id, + safe_str(channel_get_canonical_remote_descr(chan))); circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL); return; } @@ -582,7 +456,7 @@ command_process_relay_cell(cell_t *cell, or_connection_t *conn) } /** Process a 'destroy' <b>cell</b> that just arrived from - * <b>conn</b>. Find the circ that it refers to (if any). + * <b>chan</b>. Find the circ that it refers to (if any). * * If the circ is in state * onionskin_pending, then call onion_pending_remove() to remove it @@ -595,15 +469,15 @@ command_process_relay_cell(cell_t *cell, or_connection_t *conn) * and passes the destroy cell onward if necessary). */ static void -command_process_destroy_cell(cell_t *cell, or_connection_t *conn) +command_process_destroy_cell(cell_t *cell, channel_t *chan) { circuit_t *circ; int reason; - circ = circuit_get_by_circid_orconn(cell->circ_id, conn); + circ = circuit_get_by_circid_channel(cell->circ_id, chan); if (!circ) { - log_info(LD_OR,"unknown circuit %d on connection from %s:%d. Dropping.", - cell->circ_id, conn->_base.address, conn->_base.port); + log_info(LD_OR,"unknown circuit %d on connection from %s. Dropping.", + cell->circ_id, channel_get_canonical_remote_descr(chan)); return; } log_debug(LD_OR,"Received for circID %d.",cell->circ_id); @@ -613,10 +487,10 @@ command_process_destroy_cell(cell_t *cell, or_connection_t *conn) if (!CIRCUIT_IS_ORIGIN(circ) && cell->circ_id == TO_OR_CIRCUIT(circ)->p_circ_id) { /* the destroy came from behind */ - circuit_set_p_circid_orconn(TO_OR_CIRCUIT(circ), 0, NULL); + circuit_set_p_circid_chan(TO_OR_CIRCUIT(circ), 0, NULL); circuit_mark_for_close(circ, reason|END_CIRC_REASON_FLAG_REMOTE); } else { /* the destroy came from ahead */ - circuit_set_n_circid_orconn(circ, 0, NULL); + circuit_set_n_circid_chan(circ, 0, NULL); if (CIRCUIT_IS_ORIGIN(circ)) { circuit_mark_for_close(circ, reason|END_CIRC_REASON_FLAG_REMOTE); } else { @@ -629,730 +503,43 @@ command_process_destroy_cell(cell_t *cell, or_connection_t *conn) } } -/** Called when we as a server receive an appropriate cell while waiting - * either for a cell or a TLS handshake. Set the connection's state to - * "handshaking_v3', initializes the or_handshake_state field as needed, - * and add the cell to the hash of incoming cells.) - * - * Return 0 on success; return -1 and mark the connection on failure. - */ -static int -enter_v3_handshake_with_cell(var_cell_t *cell, or_connection_t *conn) -{ - const int started_here = connection_or_nonopen_was_started_here(conn); - - tor_assert(conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING || - conn->_base.state == OR_CONN_STATE_TLS_SERVER_RENEGOTIATING); - - if (started_here) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Received a cell while TLS-handshaking, not in " - "OR_HANDSHAKING_V3, on a connection we originated."); - } - connection_or_block_renegotiation(conn); - conn->_base.state = OR_CONN_STATE_OR_HANDSHAKING_V3; - if (connection_init_or_handshake_state(conn, started_here) < 0) { - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - or_handshake_state_record_var_cell(conn->handshake_state, cell, 1); - return 0; -} - -/** Process a 'versions' cell. The current link protocol version must be 0 - * to indicate that no version has yet been negotiated. We compare the - * versions in the cell to the list of versions we support, pick the - * highest version we have in common, and continue the negotiation from - * there. +/** Callback to handle a new channel; call command_setup_channel() to give + * it the right cell handlers. */ -static void -command_process_versions_cell(var_cell_t *cell, or_connection_t *conn) -{ - int highest_supported_version = 0; - const uint8_t *cp, *end; - const int started_here = connection_or_nonopen_was_started_here(conn); - if (conn->link_proto != 0 || - (conn->handshake_state && conn->handshake_state->received_versions)) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Received a VERSIONS cell on a connection with its version " - "already set to %d; dropping", (int) conn->link_proto); - return; - } - switch (conn->_base.state) - { - case OR_CONN_STATE_OR_HANDSHAKING_V2: - case OR_CONN_STATE_OR_HANDSHAKING_V3: - break; - case OR_CONN_STATE_TLS_HANDSHAKING: - case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: - default: - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "VERSIONS cell while in unexpected state"); - return; - } - - tor_assert(conn->handshake_state); - end = cell->payload + cell->payload_len; - for (cp = cell->payload; cp+1 < end; ++cp) { - uint16_t v = ntohs(get_uint16(cp)); - if (is_or_protocol_version_known(v) && v > highest_supported_version) - highest_supported_version = v; - } - if (!highest_supported_version) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Couldn't find a version in common between my version list and the " - "list in the VERSIONS cell; closing connection."); - connection_mark_for_close(TO_CONN(conn)); - return; - } else if (highest_supported_version == 1) { - /* Negotiating version 1 makes no sense, since version 1 has no VERSIONS - * cells. */ - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Used version negotiation protocol to negotiate a v1 connection. " - "That's crazily non-compliant. Closing connection."); - connection_mark_for_close(TO_CONN(conn)); - return; - } else if (highest_supported_version < 3 && - conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Negotiated link protocol 2 or lower after doing a v3 TLS " - "handshake. Closing connection."); - connection_mark_for_close(TO_CONN(conn)); - return; - } else if (highest_supported_version != 2 && - conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V2) { - /* XXXX This should eventually be a log_protocol_warn */ - log_fn(LOG_WARN, LD_OR, - "Negotiated link with non-2 protocol after doing a v2 TLS " - "handshake with %s. Closing connection.", - fmt_addr(&conn->_base.addr)); - connection_mark_for_close(TO_CONN(conn)); - return; - } - - conn->link_proto = highest_supported_version; - conn->handshake_state->received_versions = 1; - - if (conn->link_proto == 2) { - log_info(LD_OR, "Negotiated version %d with %s:%d; sending NETINFO.", - highest_supported_version, - safe_str_client(conn->_base.address), - conn->_base.port); - - if (connection_or_send_netinfo(conn) < 0) { - connection_mark_for_close(TO_CONN(conn)); - return; - } - } else { - const int send_versions = !started_here; - /* If we want to authenticate, send a CERTS cell */ - const int send_certs = !started_here || public_server_mode(get_options()); - /* If we're a relay that got a connection, ask for authentication. */ - const int send_chall = !started_here && public_server_mode(get_options()); - /* If our certs cell will authenticate us, we can send a netinfo cell - * right now. */ - const int send_netinfo = !started_here; - const int send_any = - send_versions || send_certs || send_chall || send_netinfo; - tor_assert(conn->link_proto >= 3); - - log_info(LD_OR, "Negotiated version %d with %s:%d; %s%s%s%s%s", - highest_supported_version, - safe_str_client(conn->_base.address), - conn->_base.port, - send_any ? "Sending cells:" : "Waiting for CERTS cell", - send_versions ? " VERSIONS" : "", - send_certs ? " CERTS" : "", - send_chall ? " AUTH_CHALLENGE" : "", - send_netinfo ? " NETINFO" : ""); - -#ifdef DISABLE_V3_LINKPROTO_SERVERSIDE - if (1) { - connection_mark_for_close(TO_CONN(conn)); - return; - } -#endif - - if (send_versions) { - if (connection_or_send_versions(conn, 1) < 0) { - log_warn(LD_OR, "Couldn't send versions cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } - if (send_certs) { - if (connection_or_send_certs_cell(conn) < 0) { - log_warn(LD_OR, "Couldn't send certs cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } - if (send_chall) { - if (connection_or_send_auth_challenge_cell(conn) < 0) { - log_warn(LD_OR, "Couldn't send auth_challenge cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } - if (send_netinfo) { - if (connection_or_send_netinfo(conn) < 0) { - log_warn(LD_OR, "Couldn't send netinfo cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } - } -} -/** Process a 'netinfo' cell: read and act on its contents, and set the - * connection state to "open". */ static void -command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) +command_handle_incoming_channel(channel_listener_t *listener, channel_t *chan) { - time_t timestamp; - uint8_t my_addr_type; - uint8_t my_addr_len; - const uint8_t *my_addr_ptr; - const uint8_t *cp, *end; - uint8_t n_other_addrs; - time_t now = time(NULL); - - long apparent_skew = 0; - uint32_t my_apparent_addr = 0; - - if (conn->link_proto < 2) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Received a NETINFO cell on %s connection; dropping.", - conn->link_proto == 0 ? "non-versioned" : "a v1"); - return; - } - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V2 && - conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V3) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Received a NETINFO cell on non-handshaking connection; dropping."); - return; - } - tor_assert(conn->handshake_state && - conn->handshake_state->received_versions); - - if (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { - tor_assert(conn->link_proto >= 3); - if (conn->handshake_state->started_here) { - if (!conn->handshake_state->authenticated) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got a NETINFO cell from server, " - "but no authentication. Closing the connection."); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } else { - /* we're the server. If the client never authenticated, we have - some housekeeping to do.*/ - if (!conn->handshake_state->authenticated) { - tor_assert(tor_digest_is_zero( - (const char*)conn->handshake_state->authenticated_peer_id)); - connection_or_set_circid_type(conn, NULL); - - connection_or_init_conn_from_address(conn, - &conn->_base.addr, - conn->_base.port, - (const char*)conn->handshake_state->authenticated_peer_id, - 0); - } - } - } - - /* Decode the cell. */ - timestamp = ntohl(get_uint32(cell->payload)); - if (labs(now - conn->handshake_state->sent_versions_at) < 180) { - apparent_skew = now - timestamp; - } - - my_addr_type = (uint8_t) cell->payload[4]; - my_addr_len = (uint8_t) cell->payload[5]; - my_addr_ptr = (uint8_t*) cell->payload + 6; - end = cell->payload + CELL_PAYLOAD_SIZE; - cp = cell->payload + 6 + my_addr_len; - if (cp >= end) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Addresses too long in netinfo cell; closing connection."); - 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)); - } - - n_other_addrs = (uint8_t) *cp++; - while (n_other_addrs && cp < end-2) { - /* Consider all the other addresses; if any matches, this connection is - * "canonical." */ - tor_addr_t addr; - const uint8_t *next = - decode_address_from_payload(&addr, cp, (int)(end-cp)); - if (next == NULL) { - log_fn(LOG_PROTOCOL_WARN, LD_OR, - "Bad address in netinfo cell; closing connection."); - connection_mark_for_close(TO_CONN(conn)); - return; - } - if (tor_addr_eq(&addr, &conn->real_addr)) { - conn->is_canonical = 1; - break; - } - cp = next; - --n_other_addrs; - } - - /* Act on apparent skew. */ - /** Warn when we get a netinfo skew with at least this value. */ -#define NETINFO_NOTICE_SKEW 3600 - if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - router_get_by_id_digest(conn->identity_digest)) { - char dbuf[64]; - int severity; - /*XXXX be smarter about when everybody says we are skewed. */ - if (router_digest_is_trusted_dir(conn->identity_digest)) - severity = LOG_WARN; - else - severity = LOG_INFO; - format_time_interval(dbuf, sizeof(dbuf), apparent_skew); - log_fn(severity, LD_GENERAL, "Received NETINFO cell with skewed time from " - "server at %s:%d. It seems that our clock is %s by %s, or " - "that theirs is %s. Tor requires an accurate clock to work: " - "please check your time and date settings.", - conn->_base.address, (int)conn->_base.port, - apparent_skew>0 ? "ahead" : "behind", dbuf, - apparent_skew>0 ? "behind" : "ahead"); - if (severity == LOG_WARN) /* only tell the controller if an authority */ - control_event_general_status(LOG_WARN, - "CLOCK_SKEW SKEW=%ld SOURCE=OR:%s:%d", - apparent_skew, - conn->_base.address, conn->_base.port); - } + tor_assert(listener); + tor_assert(chan); - /* 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 " - "was unable to make the OR connection become open.", - safe_str_client(conn->_base.address), - conn->_base.port); - 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", - safe_str_client(conn->_base.address), - conn->_base.port, (int)conn->link_proto, - hex_str(conn->identity_digest, DIGEST_LEN)); - } - assert_connection_ok(TO_CONN(conn),time(NULL)); + command_setup_channel(chan); } -/** Process a CERTS cell from an OR connection. - * - * If the other side should not have sent us a CERTS cell, or the cell is - * malformed, or it is supposed to authenticate the TLS key but it doesn't, - * then mark the connection. - * - * If the cell has a good cert chain and we're doing a v3 handshake, then - * store the certificates in or_handshake_state. If this is the client side - * of the connection, we then authenticate the server or mark the connection. - * If it's the server side, wait for an AUTHENTICATE cell. +/** Given a channel, install the right handlers to process incoming + * cells on it. */ -static void -command_process_certs_cell(var_cell_t *cell, or_connection_t *conn) -{ -#define ERR(s) \ - do { \ - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ - "Received a bad CERTS cell from %s:%d: %s", \ - safe_str(conn->_base.address), conn->_base.port, (s)); \ - connection_mark_for_close(TO_CONN(conn)); \ - goto err; \ - } while (0) - - tor_cert_t *link_cert = NULL; - tor_cert_t *id_cert = NULL; - tor_cert_t *auth_cert = NULL; - - uint8_t *ptr; - int n_certs, i; - int send_netinfo = 0; - - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V3) - ERR("We're not doing a v3 handshake!"); - if (conn->link_proto < 3) - ERR("We're not using link protocol >= 3"); - if (conn->handshake_state->received_certs_cell) - ERR("We already got one"); - if (conn->handshake_state->authenticated) { - /* Should be unreachable, but let's make sure. */ - ERR("We're already authenticated!"); - } - if (cell->payload_len < 1) - ERR("It had no body"); - if (cell->circ_id) - ERR("It had a nonzero circuit ID"); - - n_certs = cell->payload[0]; - ptr = cell->payload + 1; - for (i = 0; i < n_certs; ++i) { - uint8_t cert_type; - uint16_t cert_len; - if (ptr + 3 > cell->payload + cell->payload_len) { - goto truncated; - } - cert_type = *ptr; - cert_len = ntohs(get_uint16(ptr+1)); - if (ptr + 3 + cert_len > cell->payload + cell->payload_len) { - goto truncated; - } - if (cert_type == OR_CERT_TYPE_TLS_LINK || - cert_type == OR_CERT_TYPE_ID_1024 || - cert_type == OR_CERT_TYPE_AUTH_1024) { - tor_cert_t *cert = tor_cert_decode(ptr + 3, cert_len); - if (!cert) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received undecodable certificate in CERTS cell from %s:%d", - safe_str(conn->_base.address), conn->_base.port); - } else { - if (cert_type == OR_CERT_TYPE_TLS_LINK) { - if (link_cert) { - tor_cert_free(cert); - ERR("Too many TLS_LINK certificates"); - } - link_cert = cert; - } else if (cert_type == OR_CERT_TYPE_ID_1024) { - if (id_cert) { - tor_cert_free(cert); - ERR("Too many ID_1024 certificates"); - } - id_cert = cert; - } else if (cert_type == OR_CERT_TYPE_AUTH_1024) { - if (auth_cert) { - tor_cert_free(cert); - ERR("Too many AUTH_1024 certificates"); - } - auth_cert = cert; - } else { - tor_cert_free(cert); - } - } - } - ptr += 3 + cert_len; - continue; - - truncated: - ERR("It ends in the middle of a certificate"); - } - - if (conn->handshake_state->started_here) { - int severity; - if (! (id_cert && link_cert)) - ERR("The certs we wanted were missing"); - /* Okay. We should be able to check the certificates now. */ - if (! tor_tls_cert_matches_key(conn->tls, link_cert)) { - ERR("The link certificate didn't match the TLS public key"); - } - /* Note that this warns more loudly about time and validity if we were - * _trying_ to connect to an authority, not necessarily if we _did_ connect - * to one. */ - if (router_digest_is_trusted_dir(conn->identity_digest)) - severity = LOG_WARN; - else - severity = LOG_PROTOCOL_WARN; - - if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, 0)) - ERR("The link certificate was not valid"); - if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, 1)) - ERR("The ID certificate was not valid"); - - conn->handshake_state->authenticated = 1; - { - const digests_t *id_digests = tor_cert_get_id_digests(id_cert); - crypto_pk_t *identity_rcvd; - if (!id_digests) - ERR("Couldn't compute digests for key in ID cert"); - - identity_rcvd = tor_tls_cert_get_key(id_cert); - if (!identity_rcvd) - ERR("Internal error: Couldn't get RSA key from ID cert."); - memcpy(conn->handshake_state->authenticated_peer_id, - id_digests->d[DIGEST_SHA1], DIGEST_LEN); - connection_or_set_circid_type(conn, identity_rcvd); - crypto_pk_free(identity_rcvd); - } - - if (connection_or_client_learned_peer_id(conn, - conn->handshake_state->authenticated_peer_id) < 0) - ERR("Problem setting or checking peer id"); - log_info(LD_OR, "Got some good certificates from %s:%d: Authenticated it.", - safe_str(conn->_base.address), conn->_base.port); - - conn->handshake_state->id_cert = id_cert; - id_cert = NULL; - - if (!public_server_mode(get_options())) { - /* If we initiated the connection and we are not a public server, we - * aren't planning to authenticate at all. At this point we know who we - * are talking to, so we can just send a netinfo now. */ - send_netinfo = 1; - } - } else { - if (! (id_cert && auth_cert)) - ERR("The certs we wanted were missing"); - - /* Remember these certificates so we can check an AUTHENTICATE cell */ - if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, 1)) - ERR("The authentication certificate was not valid"); - if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, 1)) - ERR("The ID certificate was not valid"); - - log_info(LD_OR, "Got some good certificates from %s:%d: " - "Waiting for AUTHENTICATE.", - safe_str(conn->_base.address), conn->_base.port); - /* XXXX check more stuff? */ - - conn->handshake_state->id_cert = id_cert; - conn->handshake_state->auth_cert = auth_cert; - id_cert = auth_cert = NULL; - } - - conn->handshake_state->received_certs_cell = 1; - - if (send_netinfo) { - if (connection_or_send_netinfo(conn) < 0) { - log_warn(LD_OR, "Couldn't send netinfo cell"); - connection_mark_for_close(TO_CONN(conn)); - goto err; - } - } - - err: - tor_cert_free(id_cert); - tor_cert_free(link_cert); - tor_cert_free(auth_cert); -#undef ERR -} - -/** Process an AUTH_CHALLENGE cell from an OR connection. - * - * If we weren't supposed to get one (for example, because we're not the - * originator of the connection), or it's ill-formed, or we aren't doing a v3 - * handshake, mark the connection. If the cell is well-formed but we don't - * want to authenticate, just drop it. If the cell is well-formed *and* we - * want to authenticate, send an AUTHENTICATE cell and then a NETINFO cell. */ -static void -command_process_auth_challenge_cell(var_cell_t *cell, or_connection_t *conn) +void +command_setup_channel(channel_t *chan) { - int n_types, i, use_type = -1; - uint8_t *cp; - -#define ERR(s) \ - do { \ - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ - "Received a bad AUTH_CHALLENGE cell from %s:%d: %s", \ - safe_str(conn->_base.address), conn->_base.port, (s)); \ - connection_mark_for_close(TO_CONN(conn)); \ - return; \ - } while (0) - - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V3) - ERR("We're not currently doing a v3 handshake"); - if (conn->link_proto < 3) - ERR("We're not using link protocol >= 3"); - if (! conn->handshake_state->started_here) - ERR("We didn't originate this connection"); - if (conn->handshake_state->received_auth_challenge) - ERR("We already received one"); - if (! conn->handshake_state->received_certs_cell) - ERR("We haven't gotten a CERTS cell yet"); - if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2) - ERR("It was too short"); - if (cell->circ_id) - ERR("It had a nonzero circuit ID"); - - n_types = ntohs(get_uint16(cell->payload + OR_AUTH_CHALLENGE_LEN)); - if (cell->payload_len < OR_AUTH_CHALLENGE_LEN + 2 + 2*n_types) - ERR("It looks truncated"); - - /* Now see if there is an authentication type we can use */ - cp=cell->payload+OR_AUTH_CHALLENGE_LEN+2; - for (i=0; i < n_types; ++i, cp += 2) { - uint16_t authtype = ntohs(get_uint16(cp)); - if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET) - use_type = authtype; - } - - conn->handshake_state->received_auth_challenge = 1; - - if (! public_server_mode(get_options())) { - /* If we're not a public server then we don't want to authenticate on a - connection we originated, and we already sent a NETINFO cell when we - got the CERTS cell. We have nothing more to do. */ - return; - } - - if (use_type >= 0) { - log_info(LD_OR, "Got an AUTH_CHALLENGE cell from %s:%d: Sending " - "authentication", - safe_str(conn->_base.address), conn->_base.port); - - if (connection_or_send_authenticate_cell(conn, use_type) < 0) { - log_warn(LD_OR, "Couldn't send authenticate cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } - } else { - log_info(LD_OR, "Got an AUTH_CHALLENGE cell from %s:%d, but we don't " - "know any of its authentication types. Not authenticating.", - safe_str(conn->_base.address), conn->_base.port); - } - - if (connection_or_send_netinfo(conn) < 0) { - log_warn(LD_OR, "Couldn't send netinfo cell"); - connection_mark_for_close(TO_CONN(conn)); - return; - } + tor_assert(chan); -#undef ERR + channel_set_cell_handlers(chan, + command_process_cell, + command_process_var_cell); } -/** Process an AUTHENTICATE cell from an OR connection. - * - * If it's ill-formed or we weren't supposed to get one or we're not doing a - * v3 handshake, then mark the connection. If it does not authenticate the - * other side of the connection successfully (because it isn't signed right, - * we didn't get a CERTS cell, etc) mark the connection. Otherwise, accept - * the identity of the router on the other side of the connection. +/** Given a listener, install the right handler to process incoming + * channels on it. */ -static void -command_process_authenticate_cell(var_cell_t *cell, or_connection_t *conn) -{ - uint8_t expected[V3_AUTH_FIXED_PART_LEN]; - const uint8_t *auth; - int authlen; - -#define ERR(s) \ - do { \ - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ - "Received a bad AUTHENTICATE cell from %s:%d: %s", \ - safe_str(conn->_base.address), conn->_base.port, (s)); \ - connection_mark_for_close(TO_CONN(conn)); \ - return; \ - } while (0) - - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING_V3) - ERR("We're not doing a v3 handshake"); - if (conn->link_proto < 3) - ERR("We're not using link protocol >= 3"); - if (conn->handshake_state->started_here) - ERR("We originated this connection"); - if (conn->handshake_state->received_authenticate) - ERR("We already got one!"); - if (conn->handshake_state->authenticated) { - /* Should be impossible given other checks */ - ERR("The peer is already authenticated"); - } - if (! conn->handshake_state->received_certs_cell) - ERR("We never got a certs cell"); - if (conn->handshake_state->auth_cert == NULL) - ERR("We never got an authentication certificate"); - if (conn->handshake_state->id_cert == NULL) - ERR("We never got an identity certificate"); - if (cell->payload_len < 4) - ERR("Cell was way too short"); - - auth = cell->payload; - { - uint16_t type = ntohs(get_uint16(auth)); - uint16_t len = ntohs(get_uint16(auth+2)); - if (4 + len > cell->payload_len) - ERR("Authenticator was truncated"); - - if (type != AUTHTYPE_RSA_SHA256_TLSSECRET) - ERR("Authenticator type was not recognized"); - - auth += 4; - authlen = len; - } - - if (authlen < V3_AUTH_BODY_LEN + 1) - ERR("Authenticator was too short"); - - if (connection_or_compute_authenticate_cell_body( - conn, expected, sizeof(expected), NULL, 1) < 0) - ERR("Couldn't compute expected AUTHENTICATE cell body"); - if (tor_memneq(expected, auth, sizeof(expected))) - ERR("Some field in the AUTHENTICATE cell body was not as expected"); - - { - crypto_pk_t *pk = tor_tls_cert_get_key( - conn->handshake_state->auth_cert); - char d[DIGEST256_LEN]; - char *signed_data; - size_t keysize; - int signed_len; - - if (!pk) - ERR("Internal error: couldn't get RSA key from AUTH cert."); - crypto_digest256(d, (char*)auth, V3_AUTH_BODY_LEN, DIGEST_SHA256); - - keysize = crypto_pk_keysize(pk); - signed_data = tor_malloc(keysize); - signed_len = crypto_pk_public_checksig(pk, signed_data, keysize, - (char*)auth + V3_AUTH_BODY_LEN, - authlen - V3_AUTH_BODY_LEN); - crypto_pk_free(pk); - if (signed_len < 0) { - tor_free(signed_data); - ERR("Signature wasn't valid"); - } - if (signed_len < DIGEST256_LEN) { - tor_free(signed_data); - ERR("Not enough data was signed"); - } - /* Note that we deliberately allow *more* than DIGEST256_LEN bytes here, - * in case they're later used to hold a SHA3 digest or something. */ - if (tor_memneq(signed_data, d, DIGEST256_LEN)) { - tor_free(signed_data); - ERR("Signature did not match data to be signed."); - } - tor_free(signed_data); - } - - /* Okay, we are authenticated. */ - conn->handshake_state->received_authenticate = 1; - conn->handshake_state->authenticated = 1; - conn->handshake_state->digest_received_data = 0; - { - crypto_pk_t *identity_rcvd = - tor_tls_cert_get_key(conn->handshake_state->id_cert); - const digests_t *id_digests = - tor_cert_get_id_digests(conn->handshake_state->id_cert); - - /* This must exist; we checked key type when reading the cert. */ - tor_assert(id_digests); - - memcpy(conn->handshake_state->authenticated_peer_id, - id_digests->d[DIGEST_SHA1], DIGEST_LEN); - - connection_or_set_circid_type(conn, identity_rcvd); - crypto_pk_free(identity_rcvd); - - connection_or_init_conn_from_address(conn, - &conn->_base.addr, - conn->_base.port, - (const char*)conn->handshake_state->authenticated_peer_id, - 0); - - log_info(LD_OR, "Got an AUTHENTICATE cell from %s:%d: Looks good.", - safe_str(conn->_base.address), conn->_base.port); - } +void +command_setup_listener(channel_listener_t *listener) +{ + tor_assert(listener); + tor_assert(listener->state == CHANNEL_LISTENER_STATE_LISTENING); -#undef ERR + channel_listener_set_listener_fn(listener, command_handle_incoming_channel); } diff --git a/src/or/command.h b/src/or/command.h index 078ccc9f5..375d70456 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -9,11 +9,15 @@ * \brief Header file for command.c. **/ -#ifndef _TOR_COMMAND_H -#define _TOR_COMMAND_H +#ifndef TOR_COMMAND_H +#define TOR_COMMAND_H -void command_process_cell(cell_t *cell, or_connection_t *conn); -void command_process_var_cell(var_cell_t *cell, or_connection_t *conn); +#include "channel.h" + +void command_process_cell(channel_t *chan, cell_t *cell); +void command_process_var_cell(channel_t *chan, var_cell_t *cell); +void command_setup_channel(channel_t *chan); +void command_setup_listener(channel_listener_t *chan_l); extern uint64_t stats_n_padding_cells_processed; extern uint64_t stats_n_create_cells_processed; diff --git a/src/or/config.c b/src/or/config.c index 90a5dfbda..9905a94fd 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -12,21 +12,28 @@ #define CONFIG_PRIVATE #include "or.h" +#include "addressmap.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuitmux.h" +#include "circuitmux_ewma.h" #include "config.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" #include "control.h" +#include "confparse.h" #include "cpuworker.h" #include "dirserv.h" #include "dirvote.h" #include "dns.h" +#include "entrynodes.h" #include "geoip.h" #include "hibernate.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "relay.h" #include "rendclient.h" @@ -35,6 +42,8 @@ #include "router.h" #include "util.h" #include "routerlist.h" +#include "routerset.h" +#include "statefile.h" #include "transports.h" #ifdef _WIN32 #include <shlobj.h> @@ -45,51 +54,9 @@ /* From main.c */ extern int quiet_level; -/** Enumeration of types which option values can take */ -typedef enum config_type_t { - CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */ - CONFIG_TYPE_FILENAME, /**< A filename: some prefixes get expanded. */ - CONFIG_TYPE_UINT, /**< A non-negative integer less than MAX_INT */ - CONFIG_TYPE_INT, /**< Any integer. */ - CONFIG_TYPE_PORT, /**< A port from 1...65535, 0 for "not set", or - * "auto". */ - CONFIG_TYPE_INTERVAL, /**< A number of seconds, with optional units*/ - CONFIG_TYPE_MSEC_INTERVAL,/**< A number of milliseconds, with optional - * units */ - CONFIG_TYPE_MEMUNIT, /**< A number of bytes, with optional units*/ - CONFIG_TYPE_DOUBLE, /**< A floating-point value */ - CONFIG_TYPE_BOOL, /**< A boolean value, expressed as 0 or 1. */ - CONFIG_TYPE_AUTOBOOL, /**< A boolean+auto value, expressed 0 for false, - * 1 for true, and -1 for auto */ - CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to GMT. */ - CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and - * optional whitespace. */ - CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */ - CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines, - * mixed with other keywords. */ - CONFIG_TYPE_LINELIST_V, /**< Catch-all "virtual" option to summarize - * context-sensitive config lines when fetching. - */ - CONFIG_TYPE_ROUTERSET, /**< A list of router names, addrs, and fps, - * parsed into a routerset_t. */ - CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */ -} config_type_t; - -/** An abbreviation for a configuration option allowed on the command line. */ -typedef struct config_abbrev_t { - const char *abbreviated; - const char *full; - int commandline_only; - int warn; -} config_abbrev_t; - -/* Handy macro for declaring "In the config file or on the command line, - * you can abbreviate <b>tok</b>s as <b>tok</b>". */ -#define PLURAL(tok) { #tok, #tok "s", 0, 0 } - /** A list of abbreviations and aliases to map command-line options, obsolete * option names, or alternative option names, to their current values. */ -static config_abbrev_t _option_abbrevs[] = { +static config_abbrev_t option_abbrevs_[] = { PLURAL(AuthDirBadDirCC), PLURAL(AuthDirBadExitCC), PLURAL(AuthDirInvalidCC), @@ -114,6 +81,7 @@ static config_abbrev_t _option_abbrevs[] = { { "BandwidthRateBytes", "BandwidthRate", 0, 0}, { "BandwidthBurstBytes", "BandwidthBurst", 0, 0}, { "DirFetchPostPeriod", "StatusFetchPeriod", 0, 0}, + { "DirServer", "DirAuthority", 0, 0}, /* XXXX024 later, make this warn? */ { "MaxConn", "ConnLimit", 0, 1}, { "ORBindAddress", "ORListenAddress", 0, 0}, { "DirBindAddress", "DirListenAddress", 0, 0}, @@ -130,31 +98,11 @@ static config_abbrev_t _option_abbrevs[] = { { "HashedControlPassword", "__HashedControlSessionPassword", 1, 0}, { "StrictEntryNodes", "StrictNodes", 0, 1}, { "StrictExitNodes", "StrictNodes", 0, 1}, + { "VirtualAddrNetwork", "VirtualAddrNetworkIPv4", 0, 0}, + { "_UseFilteringSSLBufferevents", "UseFilteringSSLBufferevents", 0, 1}, { NULL, NULL, 0, 0}, }; -/** A list of state-file "abbreviations," for compatibility. */ -static config_abbrev_t _state_abbrevs[] = { - { "AccountingBytesReadInterval", "AccountingBytesReadInInterval", 0, 0 }, - { "HelperNode", "EntryGuard", 0, 0 }, - { "HelperNodeDownSince", "EntryGuardDownSince", 0, 0 }, - { "HelperNodeUnlistedSince", "EntryGuardUnlistedSince", 0, 0 }, - { "EntryNode", "EntryGuard", 0, 0 }, - { "EntryNodeDownSince", "EntryGuardDownSince", 0, 0 }, - { "EntryNodeUnlistedSince", "EntryGuardUnlistedSince", 0, 0 }, - { NULL, NULL, 0, 0}, -}; -#undef PLURAL - -/** A variable allowed in the configuration file or on the command line. */ -typedef struct config_var_t { - const char *name; /**< The full keyword (case insensitive). */ - config_type_t type; /**< How to interpret the type and turn it into a - * value. */ - off_t var_offset; /**< Offset of the corresponding member of or_options_t. */ - const char *initvalue; /**< String (or null) describing initial value. */ -} config_var_t; - /** An entry for config_vars: "The option <b>name</b> has type * CONFIG_TYPE_<b>conftype</b>, and corresponds to * or_options_t.<b>member</b>" @@ -175,7 +123,7 @@ typedef struct config_var_t { * abbreviations, order is significant, since the first matching option will * be chosen first. */ -static config_var_t _option_vars[] = { +static config_var_t option_vars_[] = { OBSOLETE("AccountingMaxKB"), V(AccountingMax, MEMUNIT, "0 bytes"), V(AccountingStart, STRING, NULL), @@ -204,12 +152,13 @@ static config_var_t _option_vars[] = { V(AuthDirListBadExits, BOOL, "0"), V(AuthDirMaxServersPerAddr, UINT, "2"), V(AuthDirMaxServersPerAuthAddr,UINT, "5"), + V(AuthDirHasIPv6Connectivity, BOOL, "0"), VAR("AuthoritativeDirectory", BOOL, AuthoritativeDir, "0"), V(AutomapHostsOnResolve, BOOL, "0"), V(AutomapHostsSuffixes, CSV, ".onion,.exit"), V(AvoidDiskWrites, BOOL, "0"), - V(BandwidthBurst, MEMUNIT, "10 MB"), - V(BandwidthRate, MEMUNIT, "5 MB"), + V(BandwidthBurst, MEMUNIT, "1 GB"), + V(BandwidthRate, MEMUNIT, "1 GB"), V(BridgeAuthoritativeDir, BOOL, "0"), VAR("Bridge", LINELIST, Bridges, NULL), V(BridgePassword, STRING, NULL), @@ -223,8 +172,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"), @@ -257,7 +208,8 @@ static config_var_t _option_vars[] = { OBSOLETE("DirRecordUsageRetainIPs"), OBSOLETE("DirRecordUsageSaveInterval"), V(DirReqStatistics, BOOL, "1"), - VAR("DirServer", LINELIST, DirServers, NULL), + VAR("DirAuthority", LINELIST, DirAuthorities, NULL), + V(DirAuthorityFallbackRate, DOUBLE, "1.0"), V(DisableAllSwap, BOOL, "0"), V(DisableDebuggerAttachment, BOOL, "1"), V(DisableIOCP, BOOL, "1"), @@ -278,13 +230,9 @@ static config_var_t _option_vars[] = { V(ExitPortStatistics, BOOL, "0"), V(ExtendAllowPrivateAddresses, BOOL, "0"), V(ExtraInfoStatistics, BOOL, "1"), + V(FallbackDir, LINELIST, NULL), -#if defined (WINCE) - V(FallbackNetworkstatusFile, FILENAME, "fallback-consensus"), -#else - V(FallbackNetworkstatusFile, FILENAME, - SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "fallback-consensus"), -#endif + OBSOLETE("FallbackNetworkstatusFile"), V(FascistFirewall, BOOL, "0"), V(FirewallPorts, CSV, ""), V(FastFirstHopPK, BOOL, "1"), @@ -296,9 +244,12 @@ static config_var_t _option_vars[] = { V(FetchV2Networkstatus, BOOL, "0"), #ifdef _WIN32 V(GeoIPFile, FILENAME, "<default>"), + V(GeoIPv6File, FILENAME, "<default>"), #else V(GeoIPFile, FILENAME, SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"), + V(GeoIPv6File, FILENAME, + SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"), #endif OBSOLETE("GiveGuardFlagTo_CVE_2011_2768_VulnerableRelays"), OBSOLETE("Group"), @@ -324,7 +275,9 @@ static config_var_t _option_vars[] = { V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), V(HTTPSProxyAuthenticator, STRING, NULL), + V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), + V(ServerTransportListenAddr, LINELIST, NULL), V(Socks4Proxy, STRING, NULL), V(Socks5Proxy, STRING, NULL), V(Socks5ProxyUsername, STRING, NULL), @@ -355,16 +308,22 @@ static config_var_t _option_vars[] = { OBSOLETE("NoPublish"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), V(NumCPUs, UINT, "0"), + V(NumDirectoryGuards, UINT, "3"), V(NumEntryGuards, UINT, "3"), V(ORListenAddress, LINELIST, NULL), VPORT(ORPort, LINELIST, NULL), - V(OutboundBindAddress, STRING, NULL), + V(OutboundBindAddress, LINELIST, NULL), + OBSOLETE("PathBiasDisableRate"), V(PathBiasCircThreshold, INT, "-1"), V(PathBiasNoticeRate, DOUBLE, "-1"), - V(PathBiasDisableRate, DOUBLE, "-1"), + V(PathBiasWarnRate, DOUBLE, "-1"), + V(PathBiasExtremeRate, DOUBLE, "-1"), V(PathBiasScaleThreshold, INT, "-1"), V(PathBiasScaleFactor, INT, "-1"), + V(PathBiasMultFactor, INT, "-1"), + V(PathBiasDropGuards, AUTOBOOL, "0"), + V(PathBiasUseCloseCounts, AUTOBOOL, "1"), OBSOLETE("PathlenCoinWeight"), V(PerConnBWBurst, MEMUNIT, "0"), @@ -419,6 +378,7 @@ static config_var_t _option_vars[] = { OBSOLETE("TestVia"), V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"), V(Tor2webMode, BOOL, "0"), + V(TLSECGroup, STRING, NULL), V(TrackHostExits, CSV, NULL), V(TrackHostExitsExpire, INTERVAL, "30 minutes"), OBSOLETE("TrafficShaping"), @@ -428,7 +388,9 @@ static config_var_t _option_vars[] = { V(UpdateBridgesFromAuthority, BOOL, "0"), V(UseBridges, BOOL, "0"), V(UseEntryGuards, BOOL, "1"), + V(UseEntryGuardsAsDirGuards, BOOL, "1"), V(UseMicrodescriptors, AUTOBOOL, "auto"), + V(UseNTorHandshake, AUTOBOOL, "auto"), V(User, STRING, NULL), V(UserspaceIOCPBuffers, BOOL, "0"), VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), @@ -444,9 +406,10 @@ static config_var_t _option_vars[] = { V(V3AuthUseLegacyKey, BOOL, "0"), V(V3BandwidthsFile, FILENAME, NULL), VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"), - V(VirtualAddrNetwork, STRING, "127.192.0.0/10"), + V(VirtualAddrNetworkIPv4, STRING, "127.192.0.0/10"), + V(VirtualAddrNetworkIPv6, STRING, "[FE80::]/10"), V(WarnPlaintextPorts, CSV, "23,109,110,143"), - V(_UseFilteringSSLBufferevents, BOOL, "0"), + V(UseFilteringSSLBufferevents, BOOL, "0"), VAR("__ReloadTorrcOnSIGHUP", BOOL, ReloadTorrcOnSIGHUP, "1"), VAR("__AllDirActionsPrivate", BOOL, AllDirActionsPrivate, "0"), VAR("__DisablePredictedCircuits",BOOL,DisablePredictedCircuits, "0"), @@ -456,7 +419,7 @@ static config_var_t _option_vars[] = { VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL), V(MinUptimeHidServDirectoryV2, INTERVAL, "25 hours"), V(VoteOnHidServDirectoriesV2, BOOL, "1"), - V(_UsingTestNetworkDefaults, BOOL, "0"), + VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } }; @@ -484,127 +447,18 @@ static const config_var_t testing_tor_network_defaults[] = { V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), - V(_UsingTestNetworkDefaults, BOOL, "1"), + VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } }; -#undef VAR - -#define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \ - initvalue } - -/** Array of "state" variables saved to the ~/.tor/state file. */ -static config_var_t _state_vars[] = { - /* Remember to document these in state-contents.txt ! */ - - V(AccountingBytesReadInInterval, MEMUNIT, NULL), - V(AccountingBytesWrittenInInterval, MEMUNIT, NULL), - V(AccountingExpectedUsage, MEMUNIT, NULL), - V(AccountingIntervalStart, ISOTIME, NULL), - V(AccountingSecondsActive, INTERVAL, NULL), - V(AccountingSecondsToReachSoftLimit,INTERVAL, NULL), - V(AccountingSoftLimitHitAt, ISOTIME, NULL), - V(AccountingBytesAtSoftLimit, MEMUNIT, NULL), - - VAR("EntryGuard", LINELIST_S, EntryGuards, NULL), - VAR("EntryGuardDownSince", LINELIST_S, EntryGuards, NULL), - VAR("EntryGuardUnlistedSince", LINELIST_S, EntryGuards, NULL), - VAR("EntryGuardAddedBy", LINELIST_S, EntryGuards, NULL), - VAR("EntryGuardPathBias", LINELIST_S, EntryGuards, NULL), - V(EntryGuards, LINELIST_V, NULL), - - VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), - V(TransportProxies, LINELIST_V, NULL), - - V(BWHistoryReadEnds, ISOTIME, NULL), - V(BWHistoryReadInterval, UINT, "900"), - V(BWHistoryReadValues, CSV, ""), - V(BWHistoryReadMaxima, CSV, ""), - V(BWHistoryWriteEnds, ISOTIME, NULL), - V(BWHistoryWriteInterval, UINT, "900"), - V(BWHistoryWriteValues, CSV, ""), - V(BWHistoryWriteMaxima, CSV, ""), - V(BWHistoryDirReadEnds, ISOTIME, NULL), - V(BWHistoryDirReadInterval, UINT, "900"), - V(BWHistoryDirReadValues, CSV, ""), - V(BWHistoryDirReadMaxima, CSV, ""), - V(BWHistoryDirWriteEnds, ISOTIME, NULL), - V(BWHistoryDirWriteInterval, UINT, "900"), - V(BWHistoryDirWriteValues, CSV, ""), - V(BWHistoryDirWriteMaxima, CSV, ""), - - V(TorVersion, STRING, NULL), - - V(LastRotatedOnionKey, ISOTIME, NULL), - V(LastWritten, ISOTIME, NULL), - - V(TotalBuildTimes, UINT, NULL), - V(CircuitBuildAbandonedCount, UINT, "0"), - VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), - VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } -}; #undef VAR #undef V #undef OBSOLETE -/** Represents an English description of a configuration variable; used when - * generating configuration file comments. */ -typedef struct config_var_description_t { - const char *name; - const char *description; -} config_var_description_t; - -/** Type of a callback to validate whether a given configuration is - * well-formed and consistent. See options_trial_assign() for documentation - * of arguments. */ -typedef int (*validate_fn_t)(void*,void*,int,char**); - -/** Information on the keys, value types, key-to-struct-member mappings, - * variable descriptions, validation functions, and abbreviations for a - * configuration or storage format. */ -typedef struct { - size_t size; /**< Size of the struct that everything gets parsed into. */ - uint32_t magic; /**< Required 'magic value' to make sure we have a struct - * of the right type. */ - off_t magic_offset; /**< Offset of the magic value within the struct. */ - config_abbrev_t *abbrevs; /**< List of abbreviations that we expand when - * parsing this format. */ - config_var_t *vars; /**< List of variables we recognize, their default - * values, and where we stick them in the structure. */ - validate_fn_t validate_fn; /**< Function to validate config. */ - /** If present, extra is a LINELIST variable for unrecognized - * lines. Otherwise, unrecognized lines are an error. */ - config_var_t *extra; -} config_format_t; - -/** Macro: assert that <b>cfg</b> has the right magic field for format - * <b>fmt</b>. */ -#define CHECK(fmt, cfg) STMT_BEGIN \ - tor_assert(fmt && cfg); \ - tor_assert((fmt)->magic == \ - *(uint32_t*)STRUCT_VAR_P(cfg,fmt->magic_offset)); \ - STMT_END - #ifdef _WIN32 static char *get_windows_conf_root(void); #endif -static void config_line_append(config_line_t **lst, - const char *key, const char *val); -static void option_clear(const config_format_t *fmt, or_options_t *options, - const config_var_t *var); -static void option_reset(const config_format_t *fmt, or_options_t *options, - const config_var_t *var, int use_defaults); -static void config_free(const config_format_t *fmt, void *options); -static int config_lines_eq(config_line_t *a, config_line_t *b); -static int config_count_key(const config_line_t *a, const char *key); -static int option_is_same(const config_format_t *fmt, - const or_options_t *o1, const or_options_t *o2, - const char *name); -static or_options_t *options_dup(const config_format_t *fmt, - const or_options_t *old); static int options_validate(or_options_t *old_options, or_options_t *options, int from_setconf, char **msg); @@ -623,9 +477,13 @@ static int parse_bridge_line(const char *line, int validate_only); static int parse_client_transport_line(const char *line, int validate_only); static int parse_server_transport_line(const char *line, int validate_only); -static int parse_dir_server_line(const char *line, +static char *get_bindaddr_from_transport_listen_line(const char *line, + const char *transport); +static int parse_dir_authority_line(const char *line, dirinfo_type_t required_type, int validate_only); +static int parse_dir_fallback_line(const char *line, + int validate_only); static void port_cfg_free(port_cfg_t *port); static int parse_ports(or_options_t *options, int validate_only, char **msg_out, int *n_ports_out); @@ -635,20 +493,14 @@ static int check_server_ports(const smartlist_t *ports, static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); -static config_line_t *get_assigned_option(const config_format_t *fmt, - const void *options, const char *key, - int escape_val); -static void config_init(const config_format_t *fmt, void *options); -static int or_state_validate(or_state_t *old_options, or_state_t *options, - int from_setconf, char **msg); -static int or_state_load(void); static int options_init_logs(or_options_t *options, int validate_only); -static uint64_t config_parse_memunit(const char *s, int *ok); -static int config_parse_msec_interval(const char *s, int *ok); -static int config_parse_interval(const char *s, int *ok); static void init_libevent(const or_options_t *options); static int opt_streq(const char *s1, const char *s2); +static int parse_outbound_addresses(or_options_t *options, int validate_only, + char **msg); +static void config_maybe_load_geoip_files_(const or_options_t *options, + const or_options_t *old_options); /** Magic value for or_options_t. */ #define OR_OPTIONS_MAGIC 9090909 @@ -657,33 +509,13 @@ static int opt_streq(const char *s1, const char *s2); static config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, - STRUCT_OFFSET(or_options_t, _magic), - _option_abbrevs, - _option_vars, + STRUCT_OFFSET(or_options_t, magic_), + option_abbrevs_, + option_vars_, (validate_fn_t)options_validate, NULL }; -/** Magic value for or_state_t. */ -#define OR_STATE_MAGIC 0x57A73f57 - -/** "Extra" variable in the state that receives lines we can't parse. This - * lets us preserve options from versions of Tor newer than us. */ -static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL -}; - -/** Configuration format for or_state_t. */ -static const config_format_t state_format = { - sizeof(or_state_t), - OR_STATE_MAGIC, - STRUCT_OFFSET(or_state_t, _magic), - _state_abbrevs, - _state_vars, - (validate_fn_t)or_state_validate, - &state_extra_var, -}; - /* * Functions to read and write the global options pointer. */ @@ -697,8 +529,6 @@ static or_options_t *global_default_options = NULL; static char *torrc_fname = NULL; /** Name of the most recently read torrc-defaults file.*/ static char *torrc_defaults_fname; -/** Persistent serialized state. */ -static or_state_t *global_state = NULL; /** Configuration Options set by command line. */ static config_line_t *global_cmdline_options = NULL; /** Contents of most recently read DirPortFrontPage file. */ @@ -713,16 +543,6 @@ get_dirportfrontpage(void) return global_dirfrontpagecontents; } -/** Allocate an empty configuration object of a given format type. */ -static void * -config_alloc(const config_format_t *fmt) -{ - void *opts = tor_malloc_zero(fmt->size); - *(uint32_t*)STRUCT_VAR_P(opts, fmt->magic_offset) = fmt->magic; - CHECK(fmt, opts); - return opts; -} - /** Return the currently configured options. */ or_options_t * get_options_mutable(void) @@ -773,8 +593,9 @@ set_options(or_options_t *new_val, char **msg) var->type == CONFIG_TYPE_OBSOLETE) { continue; } - if (!option_is_same(&options_format, new_val, old_options, var_name)) { - line = get_assigned_option(&options_format, new_val, var_name, 1); + if (!config_is_same(&options_format, new_val, old_options, var_name)) { + line = config_get_assigned_option(&options_format, new_val, + var_name, 1); if (line) { for (; line; line = line->next) { @@ -843,13 +664,13 @@ or_options_free(or_options_t *options) if (!options) return; - routerset_free(options->_ExcludeExitNodesUnion); + routerset_free(options->ExcludeExitNodesUnion_); if (options->NodeFamilySets) { SMARTLIST_FOREACH(options->NodeFamilySets, routerset_t *, rs, routerset_free(rs)); smartlist_free(options->NodeFamilySets); } - tor_free(options->_BridgePassword_AuthDigest); + tor_free(options->BridgePassword_AuthDigest_); config_free(&options_format, options); } @@ -863,15 +684,12 @@ config_free_all(void) or_options_free(global_default_options); global_default_options = NULL; - config_free(&state_format, global_state); - global_state = NULL; - config_free_lines(global_cmdline_options); global_cmdline_options = NULL; if (configured_ports) { SMARTLIST_FOREACH(configured_ports, - port_cfg_t *, p, tor_free(p)); + port_cfg_t *, p, port_cfg_free(p)); smartlist_free(configured_ports); configured_ports = NULL; } @@ -880,6 +698,9 @@ config_free_all(void) tor_free(torrc_defaults_fname); tor_free(the_tor_version); tor_free(global_dirfrontpagecontents); + + tor_free(the_short_tor_version); + tor_free(the_tor_version); } /** Make <b>address</b> -- a piece of information related to our operation as @@ -892,7 +713,7 @@ const char * safe_str_client(const char *address) { tor_assert(address); - if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL) + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_ALL) return "[scrubbed]"; else return address; @@ -909,7 +730,7 @@ const char * safe_str(const char *address) { tor_assert(address); - if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE) + if (get_options()->SafeLogging_ != SAFELOG_SCRUB_NONE) return "[scrubbed]"; else return address; @@ -921,7 +742,7 @@ safe_str(const char *address) const char * escaped_safe_str_client(const char *address) { - if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL) + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_ALL) return "[scrubbed]"; else return escaped(address); @@ -933,7 +754,7 @@ escaped_safe_str_client(const char *address) const char * escaped_safe_str(const char *address) { - if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE) + if (get_options()->SafeLogging_ != SAFELOG_SCRUB_NONE) return "[scrubbed]"; else return escaped(address); @@ -945,7 +766,7 @@ static void add_default_trusted_dir_authorities(dirinfo_type_t type) { int i; - const char *dirservers[] = { + const char *authorities[] = { "moria1 orport=9101 no-v2 " "v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 " "128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31", @@ -974,10 +795,27 @@ add_default_trusted_dir_authorities(dirinfo_type_t type) "154.35.32.5:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC", NULL }; - for (i=0; dirservers[i]; i++) { - if (parse_dir_server_line(dirservers[i], type, 0)<0) { - log_err(LD_BUG, "Couldn't parse internal dirserver line %s", - dirservers[i]); + for (i=0; authorities[i]; i++) { + if (parse_dir_authority_line(authorities[i], type, 0)<0) { + log_err(LD_BUG, "Couldn't parse internal DirAuthority line %s", + authorities[i]); + } + } +} + +/** Add the default fallback directory servers into the fallback directory + * server list. */ +static void +add_default_fallback_dir_servers(void) +{ + int i; + const char *fallback[] = { + NULL + }; + for (i=0; fallback[i]; i++) { + if (parse_dir_fallback_line(fallback[i], 0)<0) { + log_err(LD_BUG, "Couldn't parse internal FallbackDir line %s", + fallback[i]); } } } @@ -987,28 +825,29 @@ add_default_trusted_dir_authorities(dirinfo_type_t type) * user if we changed any dangerous ones. */ static int -validate_dir_authorities(or_options_t *options, or_options_t *old_options) +validate_dir_servers(or_options_t *options, or_options_t *old_options) { config_line_t *cl; - if (options->DirServers && + if (options->DirAuthorities && (options->AlternateDirAuthority || options->AlternateBridgeAuthority || options->AlternateHSAuthority)) { log_warn(LD_CONFIG, - "You cannot set both DirServers and Alternate*Authority."); + "You cannot set both DirAuthority and Alternate*Authority."); return -1; } /* do we want to complain to the user about being partitionable? */ - if ((options->DirServers && + if ((options->DirAuthorities && (!old_options || - !config_lines_eq(options->DirServers, old_options->DirServers))) || + !config_lines_eq(options->DirAuthorities, + old_options->DirAuthorities))) || (options->AlternateDirAuthority && (!old_options || !config_lines_eq(options->AlternateDirAuthority, old_options->AlternateDirAuthority)))) { log_warn(LD_CONFIG, - "You have used DirServer or AlternateDirAuthority to " + "You have used DirAuthority or AlternateDirAuthority to " "specify alternate directory authorities in " "your configuration. This is potentially dangerous: it can " "make you look different from all other Tor users, and hurt " @@ -1019,17 +858,20 @@ validate_dir_authorities(or_options_t *options, or_options_t *old_options) /* Now go through the four ways you can configure an alternate * set of directory authorities, and make sure none are broken. */ - for (cl = options->DirServers; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 1)<0) + for (cl = options->DirAuthorities; cl; cl = cl->next) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) return -1; for (cl = options->AlternateBridgeAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 1)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) return -1; for (cl = options->AlternateDirAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 1)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) return -1; for (cl = options->AlternateHSAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 1)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0) + return -1; + for (cl = options->FallbackDir; cl; cl = cl->next) + if (parse_dir_fallback_line(cl->value, 1)<0) return -1; return 0; } @@ -1038,13 +880,15 @@ validate_dir_authorities(or_options_t *options, or_options_t *old_options) * as appropriate. */ static int -consider_adding_dir_authorities(const or_options_t *options, - const or_options_t *old_options) +consider_adding_dir_servers(const or_options_t *options, + const or_options_t *old_options) { config_line_t *cl; int need_to_update = - !smartlist_len(router_get_trusted_dir_servers()) || !old_options || - !config_lines_eq(options->DirServers, old_options->DirServers) || + !smartlist_len(router_get_trusted_dir_servers()) || + !smartlist_len(router_get_fallback_dir_servers()) || !old_options || + !config_lines_eq(options->DirAuthorities, old_options->DirAuthorities) || + !config_lines_eq(options->FallbackDir, old_options->FallbackDir) || !config_lines_eq(options->AlternateBridgeAuthority, old_options->AlternateBridgeAuthority) || !config_lines_eq(options->AlternateDirAuthority, @@ -1056,9 +900,9 @@ consider_adding_dir_authorities(const or_options_t *options, return 0; /* all done */ /* Start from a clean slate. */ - clear_trusted_dir_servers(); + clear_dir_servers(); - if (!options->DirServers) { + if (!options->DirAuthorities) { /* then we may want some of the defaults */ dirinfo_type_t type = NO_DIRINFO; if (!options->AlternateBridgeAuthority) @@ -1070,18 +914,23 @@ consider_adding_dir_authorities(const or_options_t *options, type |= HIDSERV_DIRINFO; add_default_trusted_dir_authorities(type); } + if (!options->FallbackDir) + add_default_fallback_dir_servers(); - for (cl = options->DirServers; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 0)<0) + for (cl = options->DirAuthorities; cl; cl = cl->next) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) return -1; for (cl = options->AlternateBridgeAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 0)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) return -1; for (cl = options->AlternateDirAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 0)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) return -1; for (cl = options->AlternateHSAuthority; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_DIRINFO, 0)<0) + if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0) + return -1; + for (cl = options->FallbackDir; cl; cl = cl->next) + if (parse_dir_fallback_line(cl->value, 0)<0) return -1; return 0; } @@ -1130,7 +979,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) int n_ports=0; /* We need to set the connection limit before we can open the listeners. */ if (set_max_file_descriptors((unsigned)options->ConnLimit, - &options->_ConnLimit) < 0) { + &options->ConnLimit_) < 0) { *msg = tor_strdup("Problem with ConnLimit value. See logs for details."); goto rollback; } @@ -1271,7 +1120,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) if (set_conn_limit && old_options) set_max_file_descriptors((unsigned)old_options->ConnLimit, - &options->_ConnLimit); + &options->ConnLimit_); SMARTLIST_FOREACH(new_listeners, connection_t *, conn, { @@ -1353,6 +1202,9 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options, return 1; } + if (!opt_streq(old_options->TLSECGroup, new_options->TLSECGroup)) + return 1; + return 0; } @@ -1371,9 +1223,10 @@ options_act(const or_options_t *old_options) config_line_t *cl; or_options_t *options = get_options_mutable(); int running_tor = options->command == CMD_RUN_TOR; - char *msg; + char *msg=NULL; const int transition_affects_workers = old_options && options_transition_affects_workers(old_options, options); + int old_ewma_enabled; /* disable ptrace and later, other basic debugging techniques */ { @@ -1404,7 +1257,7 @@ options_act(const or_options_t *old_options) return -1; } - if (consider_adding_dir_authorities(options, old_options) < 0) + if (consider_adding_dir_servers(options, old_options) < 0) return -1; #ifdef NON_ANONYMOUS_MODE_ENABLED @@ -1454,7 +1307,7 @@ options_act(const or_options_t *old_options) } /* Load state */ - if (! global_state && running_tor) { + if (! or_state_loaded() && running_tor) { if (or_state_load()) return -1; rep_hist_load_mtbf_data(time(NULL)); @@ -1540,7 +1393,8 @@ options_act(const or_options_t *old_options) /* Register addressmap directives */ config_register_addressmaps(options); - parse_virtual_addr_network(options->VirtualAddrNetwork, 0, &msg); + parse_virtual_addr_network(options->VirtualAddrNetworkIPv4, AF_INET,0,NULL); + parse_virtual_addr_network(options->VirtualAddrNetworkIPv6, AF_INET6,0,NULL); /* Update address policies. */ if (policies_parse_from_options(options) < 0) { @@ -1557,7 +1411,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; } @@ -1581,8 +1435,16 @@ options_act(const or_options_t *old_options) connection_bucket_init(); #endif + old_ewma_enabled = cell_ewma_enabled(); /* Change the cell EWMA settings */ cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus()); + /* If we just enabled ewma, set the cmux policy on all active channels */ + if (cell_ewma_enabled() && !old_ewma_enabled) { + channel_set_cmux_policy_everywhere(&ewma_policy); + } else if (!cell_ewma_enabled() && old_ewma_enabled) { + /* Turn it off everywhere */ + channel_set_cmux_policy_everywhere(NULL); + } /* Update the BridgePassword's hashed version as needed. We store this as a * digest so that we can do side-channel-proof comparisons on it. @@ -1595,13 +1457,19 @@ options_act(const or_options_t *old_options) "BridgePassword."); return -1; } - options->_BridgePassword_AuthDigest = tor_malloc(DIGEST256_LEN); - crypto_digest256(options->_BridgePassword_AuthDigest, + options->BridgePassword_AuthDigest_ = tor_malloc(DIGEST256_LEN); + crypto_digest256(options->BridgePassword_AuthDigest_, http_authenticator, strlen(http_authenticator), DIGEST_SHA256); tor_free(http_authenticator); } + if (parse_outbound_addresses(options, 0, &msg) < 0) { + log_warn(LD_BUG, "Failed parsing oubound bind addresses: %s", msg); + tor_free(msg); + return -1; + } + /* Check for transitions that need action. */ if (old_options) { int revise_trackexithosts = 0; @@ -1639,8 +1507,10 @@ options_act(const or_options_t *old_options) if (!smartlist_strings_eq(old_options->AutomapHostsSuffixes, options->AutomapHostsSuffixes)) revise_automap_entries = 1; - else if (!opt_streq(old_options->VirtualAddrNetwork, - options->VirtualAddrNetwork)) + else if (!opt_streq(old_options->VirtualAddrNetworkIPv4, + options->VirtualAddrNetworkIPv4) || + !opt_streq(old_options->VirtualAddrNetworkIPv6, + options->VirtualAddrNetworkIPv6)) revise_automap_entries = 1; } @@ -1695,24 +1565,7 @@ options_act(const or_options_t *old_options) connection_or_update_token_buckets(get_connection_array(), options); } - /* Maybe load geoip file */ - if (options->GeoIPFile && - ((!old_options || !opt_streq(old_options->GeoIPFile, options->GeoIPFile)) - || !geoip_is_loaded())) { - /* XXXX Don't use this "<default>" junk; make our filename options - * understand prefixes somehow. -NM */ - /* XXXX024 Reload GeoIPFile on SIGHUP. -NM */ - char *actual_fname = tor_strdup(options->GeoIPFile); -#ifdef _WIN32 - if (!strcmp(actual_fname, "<default>")) { - const char *conf_root = get_windows_conf_root(); - tor_free(actual_fname); - tor_asprintf(&actual_fname, "%s\\geoip", conf_root); - } -#endif - geoip_load_file(actual_fname, options); - tor_free(actual_fname); - } + config_maybe_load_geoip_files_(options, old_options); if (options->CellStatistics || options->DirReqStatistics || options->EntryStatistics || options->ExitPortStatistics || @@ -1736,7 +1589,7 @@ options_act(const or_options_t *old_options) } if ((!old_options || !old_options->DirReqStatistics) && options->DirReqStatistics) { - if (geoip_is_loaded()) { + if (geoip_is_loaded(AF_INET)) { geoip_dirreq_stats_init(now); print_notice = 1; } else { @@ -1751,7 +1604,7 @@ options_act(const or_options_t *old_options) } if ((!old_options || !old_options->EntryStatistics) && options->EntryStatistics && !should_record_bridge_info(options)) { - if (geoip_is_loaded()) { + if (geoip_is_loaded(AF_INET) || geoip_is_loaded(AF_INET6)) { geoip_entry_stats_init(now); print_notice = 1; } else { @@ -1847,42 +1700,6 @@ options_act(const or_options_t *old_options) return 0; } -/* - * Functions to parse config options - */ - -/** If <b>option</b> is an official abbreviation for a longer option, - * return the longer option. Otherwise return <b>option</b>. - * If <b>command_line</b> is set, apply all abbreviations. Otherwise, only - * apply abbreviations that work for the config file and the command line. - * If <b>warn_obsolete</b> is set, warn about deprecated names. */ -static const char * -expand_abbrev(const config_format_t *fmt, const char *option, int command_line, - int warn_obsolete) -{ - int i; - if (! fmt->abbrevs) - return option; - for (i=0; fmt->abbrevs[i].abbreviated; ++i) { - /* Abbreviations are case insensitive. */ - if (!strcasecmp(option,fmt->abbrevs[i].abbreviated) && - (command_line || !fmt->abbrevs[i].commandline_only)) { - if (warn_obsolete && fmt->abbrevs[i].warn) { - log_warn(LD_CONFIG, - "The configuration option '%s' is deprecated; " - "use '%s' instead.", - fmt->abbrevs[i].abbreviated, - fmt->abbrevs[i].full); - } - /* Keep going through the list in case we want to rewrite it more. - * (We could imagine recursing here, but I don't want to get the - * user into an infinite loop if we craft our list wrong.) */ - option = fmt->abbrevs[i].full; - } - } - return option; -} - /** Helper: Read a list of configuration options from the command line. * If successful, put them in *<b>result</b> and return 0, and return * -1 and leave *<b>result</b> alone. */ @@ -1942,7 +1759,7 @@ config_get_commandlines(int argc, char **argv, config_line_t **result) return -1; } - (*new)->key = tor_strdup(expand_abbrev(&options_format, s, 1, 1)); + (*new)->key = tor_strdup(config_expand_abbrev(&options_format, s, 1, 1)); (*new)->value = want_arg ? tor_strdup(argv[i+1]) : tor_strdup(""); (*new)->command = command; (*new)->next = NULL; @@ -1956,444 +1773,6 @@ config_get_commandlines(int argc, char **argv, config_line_t **result) return 0; } -/** Helper: allocate a new configuration option mapping 'key' to 'val', - * append it to *<b>lst</b>. */ -static void -config_line_append(config_line_t **lst, - const char *key, - const char *val) -{ - config_line_t *newline; - - newline = tor_malloc_zero(sizeof(config_line_t)); - newline->key = tor_strdup(key); - newline->value = tor_strdup(val); - newline->next = NULL; - while (*lst) - lst = &((*lst)->next); - - (*lst) = newline; -} - -/** Helper: parse the config string and strdup into key/value - * strings. Set *result to the list, or NULL if parsing the string - * failed. Return 0 on success, -1 on failure. Warn and ignore any - * misformatted lines. - * - * If <b>extended</b> is set, then treat keys beginning with / and with + as - * indicating "clear" and "append" respectively. */ -int -config_get_lines(const char *string, config_line_t **result, int extended) -{ - config_line_t *list = NULL, **next; - char *k, *v; - - next = &list; - do { - k = v = NULL; - string = parse_config_line_from_str(string, &k, &v); - if (!string) { - config_free_lines(list); - tor_free(k); - tor_free(v); - return -1; - } - if (k && v) { - unsigned command = CONFIG_LINE_NORMAL; - if (extended) { - if (k[0] == '+') { - char *k_new = tor_strdup(k+1); - tor_free(k); - k = k_new; - command = CONFIG_LINE_APPEND; - } else if (k[0] == '/') { - char *k_new = tor_strdup(k+1); - tor_free(k); - k = k_new; - tor_free(v); - v = tor_strdup(""); - command = CONFIG_LINE_CLEAR; - } - } - /* This list can get long, so we keep a pointer to the end of it - * rather than using config_line_append over and over and getting - * n^2 performance. */ - *next = tor_malloc_zero(sizeof(config_line_t)); - (*next)->key = k; - (*next)->value = v; - (*next)->next = NULL; - (*next)->command = command; - next = &((*next)->next); - } else { - tor_free(k); - tor_free(v); - } - } while (*string); - - *result = list; - return 0; -} - -/** - * Free all the configuration lines on the linked list <b>front</b>. - */ -void -config_free_lines(config_line_t *front) -{ - config_line_t *tmp; - - while (front) { - tmp = front; - front = tmp->next; - - tor_free(tmp->key); - tor_free(tmp->value); - tor_free(tmp); - } -} - -/** As config_find_option, but return a non-const pointer. */ -static config_var_t * -config_find_option_mutable(config_format_t *fmt, const char *key) -{ - int i; - size_t keylen = strlen(key); - if (!keylen) - return NULL; /* if they say "--" on the command line, it's not an option */ - /* First, check for an exact (case-insensitive) match */ - for (i=0; fmt->vars[i].name; ++i) { - if (!strcasecmp(key, fmt->vars[i].name)) { - return &fmt->vars[i]; - } - } - /* If none, check for an abbreviated match */ - for (i=0; fmt->vars[i].name; ++i) { - if (!strncasecmp(key, fmt->vars[i].name, keylen)) { - log_warn(LD_CONFIG, "The abbreviation '%s' is deprecated. " - "Please use '%s' instead", - key, fmt->vars[i].name); - return &fmt->vars[i]; - } - } - /* Okay, unrecognized option */ - return NULL; -} - -/** If <b>key</b> is a configuration option, return the corresponding const - * config_var_t. Otherwise, if <b>key</b> is a non-standard abbreviation, - * warn, and return the corresponding const config_var_t. Otherwise return - * NULL. - */ -static const config_var_t * -config_find_option(const config_format_t *fmt, const char *key) -{ - return config_find_option_mutable((config_format_t*)fmt, key); -} - -/** Return the number of option entries in <b>fmt</b>. */ -static int -config_count_options(const config_format_t *fmt) -{ - int i; - for (i=0; fmt->vars[i].name; ++i) - ; - return i; -} - -/* - * Functions to assign config options. - */ - -/** <b>c</b>-\>key is known to be a real key. Update <b>options</b> - * with <b>c</b>-\>value and return 0, or return -1 if bad value. - * - * Called from config_assign_line() and option_reset(). - */ -static int -config_assign_value(const config_format_t *fmt, or_options_t *options, - config_line_t *c, char **msg) -{ - int i, ok; - const config_var_t *var; - void *lvalue; - - CHECK(fmt, options); - - var = config_find_option(fmt, c->key); - tor_assert(var); - - lvalue = STRUCT_VAR_P(options, var->var_offset); - - switch (var->type) { - - case CONFIG_TYPE_PORT: - if (!strcasecmp(c->value, "auto")) { - *(int *)lvalue = CFG_AUTO_PORT; - break; - } - /* fall through */ - case CONFIG_TYPE_INT: - case CONFIG_TYPE_UINT: - i = (int)tor_parse_long(c->value, 10, - var->type==CONFIG_TYPE_INT ? INT_MIN : 0, - var->type==CONFIG_TYPE_PORT ? 65535 : INT_MAX, - &ok, NULL); - if (!ok) { - tor_asprintf(msg, - "Int keyword '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - - case CONFIG_TYPE_INTERVAL: { - i = config_parse_interval(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Interval '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - } - - case CONFIG_TYPE_MSEC_INTERVAL: { - i = config_parse_msec_interval(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Msec interval '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - } - - case CONFIG_TYPE_MEMUNIT: { - uint64_t u64 = config_parse_memunit(c->value, &ok); - if (!ok) { - tor_asprintf(msg, - "Value '%s %s' is malformed or out of bounds.", - c->key, c->value); - return -1; - } - *(uint64_t *)lvalue = u64; - break; - } - - case CONFIG_TYPE_BOOL: - i = (int)tor_parse_long(c->value, 10, 0, 1, &ok, NULL); - if (!ok) { - tor_asprintf(msg, - "Boolean '%s %s' expects 0 or 1.", - c->key, c->value); - return -1; - } - *(int *)lvalue = i; - break; - - case CONFIG_TYPE_AUTOBOOL: - if (!strcmp(c->value, "auto")) - *(int *)lvalue = -1; - else if (!strcmp(c->value, "0")) - *(int *)lvalue = 0; - else if (!strcmp(c->value, "1")) - *(int *)lvalue = 1; - else { - tor_asprintf(msg, "Boolean '%s %s' expects 0, 1, or 'auto'.", - c->key, c->value); - return -1; - } - break; - - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - tor_free(*(char **)lvalue); - *(char **)lvalue = tor_strdup(c->value); - break; - - case CONFIG_TYPE_DOUBLE: - *(double *)lvalue = atof(c->value); - break; - - case CONFIG_TYPE_ISOTIME: - if (parse_iso_time(c->value, (time_t *)lvalue)) { - tor_asprintf(msg, - "Invalid time '%s' for keyword '%s'", c->value, c->key); - return -1; - } - break; - - case CONFIG_TYPE_ROUTERSET: - if (*(routerset_t**)lvalue) { - routerset_free(*(routerset_t**)lvalue); - } - *(routerset_t**)lvalue = routerset_new(); - if (routerset_parse(*(routerset_t**)lvalue, c->value, c->key)<0) { - tor_asprintf(msg, "Invalid exit list '%s' for option '%s'", - c->value, c->key); - return -1; - } - break; - - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)lvalue) { - SMARTLIST_FOREACH(*(smartlist_t**)lvalue, char *, cp, tor_free(cp)); - smartlist_clear(*(smartlist_t**)lvalue); - } else { - *(smartlist_t**)lvalue = smartlist_new(); - } - - smartlist_split_string(*(smartlist_t**)lvalue, c->value, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - break; - - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_S: - { - config_line_t *lastval = *(config_line_t**)lvalue; - if (lastval && lastval->fragile) { - if (c->command != CONFIG_LINE_APPEND) { - config_free_lines(lastval); - *(config_line_t**)lvalue = NULL; - } else { - lastval->fragile = 0; - } - } - - config_line_append((config_line_t**)lvalue, c->key, c->value); - } - break; - case CONFIG_TYPE_OBSOLETE: - log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key); - break; - case CONFIG_TYPE_LINELIST_V: - tor_asprintf(msg, - "You may not provide a value for virtual option '%s'", c->key); - return -1; - default: - tor_assert(0); - break; - } - return 0; -} - -/** Mark every linelist in <b>options</b> "fragile", so that fresh assignments - * to it will replace old ones. */ -static void -config_mark_lists_fragile(const config_format_t *fmt, or_options_t *options) -{ - int i; - tor_assert(fmt); - tor_assert(options); - - for (i = 0; fmt->vars[i].name; ++i) { - const config_var_t *var = &fmt->vars[i]; - config_line_t *list; - if (var->type != CONFIG_TYPE_LINELIST && - var->type != CONFIG_TYPE_LINELIST_V) - continue; - - list = *(config_line_t **)STRUCT_VAR_P(options, var->var_offset); - if (list) - list->fragile = 1; - } -} - -/** If <b>c</b> is a syntactically valid configuration line, update - * <b>options</b> with its value and return 0. Otherwise return -1 for bad - * key, -2 for bad value. - * - * If <b>clear_first</b> is set, clear the value first. Then if - * <b>use_defaults</b> is set, set the value to the default. - * - * Called from config_assign(). - */ -static int -config_assign_line(const config_format_t *fmt, or_options_t *options, - config_line_t *c, int use_defaults, - int clear_first, bitarray_t *options_seen, char **msg) -{ - const config_var_t *var; - - CHECK(fmt, options); - - var = config_find_option(fmt, c->key); - if (!var) { - if (fmt->extra) { - void *lvalue = STRUCT_VAR_P(options, fmt->extra->var_offset); - log_info(LD_CONFIG, - "Found unrecognized option '%s'; saving it.", c->key); - config_line_append((config_line_t**)lvalue, c->key, c->value); - return 0; - } else { - tor_asprintf(msg, - "Unknown option '%s'. Failing.", c->key); - return -1; - } - } - - /* Put keyword into canonical case. */ - if (strcmp(var->name, c->key)) { - tor_free(c->key); - c->key = tor_strdup(var->name); - } - - if (!strlen(c->value)) { - /* reset or clear it, then return */ - if (!clear_first) { - if ((var->type == CONFIG_TYPE_LINELIST || - var->type == CONFIG_TYPE_LINELIST_S) && - c->command != CONFIG_LINE_CLEAR) { - /* We got an empty linelist from the torrc or command line. - As a special case, call this an error. Warn and ignore. */ - log_warn(LD_CONFIG, - "Linelist option '%s' has no value. Skipping.", c->key); - } else { /* not already cleared */ - option_reset(fmt, options, var, use_defaults); - } - } - return 0; - } else if (c->command == CONFIG_LINE_CLEAR && !clear_first) { - option_reset(fmt, options, var, use_defaults); - } - - if (options_seen && (var->type != CONFIG_TYPE_LINELIST && - var->type != CONFIG_TYPE_LINELIST_S)) { - /* We're tracking which options we've seen, and this option is not - * supposed to occur more than once. */ - int var_index = (int)(var - fmt->vars); - if (bitarray_is_set(options_seen, var_index)) { - log_warn(LD_CONFIG, "Option '%s' used more than once; all but the last " - "value will be ignored.", var->name); - } - bitarray_set(options_seen, var_index); - } - - if (config_assign_value(fmt, options, c, msg) < 0) - return -2; - return 0; -} - -/** Restore the option named <b>key</b> in options to its default value. - * Called from config_assign(). */ -static void -config_reset_line(const config_format_t *fmt, or_options_t *options, - const char *key, int use_defaults) -{ - const config_var_t *var; - - CHECK(fmt, options); - - var = config_find_option(fmt, key); - if (!var) - return; /* give error on next pass. */ - - option_reset(fmt, options, var, use_defaults); -} - /** Return true iff key is a valid configuration option. */ int option_is_recognized(const char *key) @@ -2416,287 +1795,7 @@ option_get_canonical_name(const char *key) config_line_t * option_get_assignment(const or_options_t *options, const char *key) { - return get_assigned_option(&options_format, options, key, 1); -} - -/** Return true iff value needs to be quoted and escaped to be used in - * a configuration file. */ -static int -config_value_needs_escape(const char *value) -{ - if (*value == '\"') - return 1; - while (*value) { - switch (*value) - { - case '\r': - case '\n': - case '#': - /* Note: quotes and backspaces need special handling when we are using - * quotes, not otherwise, so they don't trigger escaping on their - * own. */ - return 1; - default: - if (!TOR_ISPRINT(*value)) - return 1; - } - ++value; - } - return 0; -} - -/** Return a newly allocated deep copy of the lines in <b>inp</b>. */ -static config_line_t * -config_lines_dup(const config_line_t *inp) -{ - config_line_t *result = NULL; - config_line_t **next_out = &result; - while (inp) { - *next_out = tor_malloc_zero(sizeof(config_line_t)); - (*next_out)->key = tor_strdup(inp->key); - (*next_out)->value = tor_strdup(inp->value); - inp = inp->next; - next_out = &((*next_out)->next); - } - (*next_out) = NULL; - return result; -} - -/** Return newly allocated line or lines corresponding to <b>key</b> in the - * configuration <b>options</b>. If <b>escape_val</b> is true and a - * value needs to be quoted before it's put in a config file, quote and - * escape that value. Return NULL if no such key exists. */ -static config_line_t * -get_assigned_option(const config_format_t *fmt, const void *options, - const char *key, int escape_val) -{ - const config_var_t *var; - const void *value; - config_line_t *result; - tor_assert(options && key); - - CHECK(fmt, options); - - var = config_find_option(fmt, key); - if (!var) { - log_warn(LD_CONFIG, "Unknown option '%s'. Failing.", key); - return NULL; - } - value = STRUCT_VAR_P(options, var->var_offset); - - result = tor_malloc_zero(sizeof(config_line_t)); - result->key = tor_strdup(var->name); - switch (var->type) - { - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - if (*(char**)value) { - result->value = tor_strdup(*(char**)value); - } else { - tor_free(result->key); - tor_free(result); - return NULL; - } - break; - case CONFIG_TYPE_ISOTIME: - if (*(time_t*)value) { - result->value = tor_malloc(ISO_TIME_LEN+1); - format_iso_time(result->value, *(time_t*)value); - } else { - tor_free(result->key); - tor_free(result); - } - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_PORT: - if (*(int*)value == CFG_AUTO_PORT) { - result->value = tor_strdup("auto"); - escape_val = 0; - break; - } - /* fall through */ - case CONFIG_TYPE_INTERVAL: - case CONFIG_TYPE_MSEC_INTERVAL: - case CONFIG_TYPE_UINT: - case CONFIG_TYPE_INT: - /* This means every or_options_t uint or bool element - * needs to be an int. Not, say, a uint16_t or char. */ - tor_asprintf(&result->value, "%d", *(int*)value); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_MEMUNIT: - tor_asprintf(&result->value, U64_FORMAT, - U64_PRINTF_ARG(*(uint64_t*)value)); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_DOUBLE: - tor_asprintf(&result->value, "%f", *(double*)value); - escape_val = 0; /* Can't need escape. */ - break; - - case CONFIG_TYPE_AUTOBOOL: - if (*(int*)value == -1) { - result->value = tor_strdup("auto"); - escape_val = 0; - break; - } - /* fall through */ - case CONFIG_TYPE_BOOL: - result->value = tor_strdup(*(int*)value ? "1" : "0"); - escape_val = 0; /* Can't need escape. */ - break; - case CONFIG_TYPE_ROUTERSET: - result->value = routerset_to_string(*(routerset_t**)value); - break; - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)value) - result->value = - smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL); - else - result->value = tor_strdup(""); - break; - case CONFIG_TYPE_OBSOLETE: - log_fn(LOG_PROTOCOL_WARN, LD_CONFIG, - "You asked me for the value of an obsolete config option '%s'.", - key); - tor_free(result->key); - tor_free(result); - return NULL; - case CONFIG_TYPE_LINELIST_S: - log_warn(LD_CONFIG, - "Can't return context-sensitive '%s' on its own", key); - tor_free(result->key); - tor_free(result); - return NULL; - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_V: - tor_free(result->key); - tor_free(result); - result = config_lines_dup(*(const config_line_t**)value); - break; - default: - tor_free(result->key); - tor_free(result); - log_warn(LD_BUG,"Unknown type %d for known key '%s'", - var->type, key); - return NULL; - } - - if (escape_val) { - config_line_t *line; - for (line = result; line; line = line->next) { - if (line->value && config_value_needs_escape(line->value)) { - char *newval = esc_for_log(line->value); - tor_free(line->value); - line->value = newval; - } - } - } - - return result; -} - -/** Iterate through the linked list of requested options <b>list</b>. - * For each item, convert as appropriate and assign to <b>options</b>. - * If an item is unrecognized, set *msg and return -1 immediately, - * else return 0 for success. - * - * If <b>clear_first</b>, interpret config options as replacing (not - * extending) their previous values. If <b>clear_first</b> is set, - * then <b>use_defaults</b> to decide if you set to defaults after - * clearing, or make the value 0 or NULL. - * - * Here are the use cases: - * 1. A non-empty AllowInvalid line in your torrc. Appends to current - * if linelist, replaces current if csv. - * 2. An empty AllowInvalid line in your torrc. Should clear it. - * 3. "RESETCONF AllowInvalid" sets it to default. - * 4. "SETCONF AllowInvalid" makes it NULL. - * 5. "SETCONF AllowInvalid=foo" clears it and sets it to "foo". - * - * Use_defaults Clear_first - * 0 0 "append" - * 1 0 undefined, don't use - * 0 1 "set to null first" - * 1 1 "set to defaults first" - * Return 0 on success, -1 on bad key, -2 on bad value. - * - * As an additional special case, if a LINELIST config option has - * no value and clear_first is 0, then warn and ignore it. - */ - -/* -There are three call cases for config_assign() currently. - -Case one: Torrc entry -options_init_from_torrc() calls config_assign(0, 0) - calls config_assign_line(0, 0). - if value is empty, calls option_reset(0) and returns. - calls config_assign_value(), appends. - -Case two: setconf -options_trial_assign() calls config_assign(0, 1) - calls config_reset_line(0) - calls option_reset(0) - calls option_clear(). - calls config_assign_line(0, 1). - if value is empty, returns. - calls config_assign_value(), appends. - -Case three: resetconf -options_trial_assign() calls config_assign(1, 1) - calls config_reset_line(1) - calls option_reset(1) - calls option_clear(). - calls config_assign_value(default) - calls config_assign_line(1, 1). - returns. -*/ -static int -config_assign(const config_format_t *fmt, void *options, config_line_t *list, - int use_defaults, int clear_first, char **msg) -{ - config_line_t *p; - bitarray_t *options_seen; - const int n_options = config_count_options(fmt); - - CHECK(fmt, options); - - /* pass 1: normalize keys */ - for (p = list; p; p = p->next) { - const char *full = expand_abbrev(fmt, p->key, 0, 1); - if (strcmp(full,p->key)) { - tor_free(p->key); - p->key = tor_strdup(full); - } - } - - /* pass 2: if we're reading from a resetting source, clear all - * mentioned config options, and maybe set to their defaults. */ - if (clear_first) { - for (p = list; p; p = p->next) - config_reset_line(fmt, options, p->key, use_defaults); - } - - options_seen = bitarray_init_zero(n_options); - /* pass 3: assign. */ - while (list) { - int r; - if ((r=config_assign_line(fmt, options, list, use_defaults, - clear_first, options_seen, msg))) { - bitarray_free(options_seen); - return r; - } - list = list->next; - } - bitarray_free(options_seen); - - /** Now we're done assigning a group of options to the configuration. - * Subsequent group assignments should _replace_ linelists, not extend - * them. */ - config_mark_lists_fragile(fmt, options); - - return 0; + return config_get_assigned_option(&options_format, options, key, 1); } /** Try assigning <b>list</b> to the global options. You do this by duping @@ -2713,7 +1812,7 @@ options_trial_assign(config_line_t *list, int use_defaults, int clear_first, char **msg) { int r; - or_options_t *trial_options = options_dup(&options_format, get_options()); + or_options_t *trial_options = config_dup(&options_format, get_options()); if ((r=config_assign(&options_format, trial_options, list, use_defaults, clear_first, msg)) < 0) { @@ -2740,90 +1839,6 @@ options_trial_assign(config_line_t *list, int use_defaults, return SETOPT_OK; } -/** Reset config option <b>var</b> to 0, 0.0, NULL, or the equivalent. - * Called from option_reset() and config_free(). */ -static void -option_clear(const config_format_t *fmt, or_options_t *options, - const config_var_t *var) -{ - void *lvalue = STRUCT_VAR_P(options, var->var_offset); - (void)fmt; /* unused */ - switch (var->type) { - case CONFIG_TYPE_STRING: - case CONFIG_TYPE_FILENAME: - tor_free(*(char**)lvalue); - break; - case CONFIG_TYPE_DOUBLE: - *(double*)lvalue = 0.0; - break; - case CONFIG_TYPE_ISOTIME: - *(time_t*)lvalue = 0; - break; - case CONFIG_TYPE_INTERVAL: - case CONFIG_TYPE_MSEC_INTERVAL: - case CONFIG_TYPE_UINT: - case CONFIG_TYPE_INT: - case CONFIG_TYPE_PORT: - case CONFIG_TYPE_BOOL: - *(int*)lvalue = 0; - break; - case CONFIG_TYPE_AUTOBOOL: - *(int*)lvalue = -1; - break; - case CONFIG_TYPE_MEMUNIT: - *(uint64_t*)lvalue = 0; - break; - case CONFIG_TYPE_ROUTERSET: - if (*(routerset_t**)lvalue) { - routerset_free(*(routerset_t**)lvalue); - *(routerset_t**)lvalue = NULL; - } - break; - case CONFIG_TYPE_CSV: - if (*(smartlist_t**)lvalue) { - SMARTLIST_FOREACH(*(smartlist_t **)lvalue, char *, cp, tor_free(cp)); - smartlist_free(*(smartlist_t **)lvalue); - *(smartlist_t **)lvalue = NULL; - } - break; - case CONFIG_TYPE_LINELIST: - case CONFIG_TYPE_LINELIST_S: - config_free_lines(*(config_line_t **)lvalue); - *(config_line_t **)lvalue = NULL; - break; - case CONFIG_TYPE_LINELIST_V: - /* handled by linelist_s. */ - break; - case CONFIG_TYPE_OBSOLETE: - break; - } -} - -/** Clear the option indexed by <b>var</b> in <b>options</b>. Then if - * <b>use_defaults</b>, set it to its default value. - * Called by config_init() and option_reset_line() and option_assign_line(). */ -static void -option_reset(const config_format_t *fmt, or_options_t *options, - const config_var_t *var, int use_defaults) -{ - config_line_t *c; - char *msg = NULL; - CHECK(fmt, options); - option_clear(fmt, options, var); /* clear it first */ - if (!use_defaults) - return; /* all done */ - if (var->initvalue) { - c = tor_malloc_zero(sizeof(config_line_t)); - c->key = tor_strdup(var->name); - c->value = tor_strdup(var->initvalue); - if (config_assign_value(fmt, options, c, &msg) < 0) { - log_warn(LD_BUG, "Failed to assign default: %s", msg); - tor_free(msg); /* if this happens it's a bug */ - } - config_free_lines(c); - } -} - /** Print a usage message for tor. */ static void print_usage(void) @@ -2843,8 +1858,8 @@ list_torrc_options(void) { int i; smartlist_t *lines = smartlist_new(); - for (i = 0; _option_vars[i].name; ++i) { - const config_var_t *var = &_option_vars[i]; + for (i = 0; option_vars_[i].name; ++i) { + const config_var_t *var = &option_vars_[i]; if (var->type == CONFIG_TYPE_OBSOLETE || var->type == CONFIG_TYPE_LINELIST_V) continue; @@ -2953,18 +1968,18 @@ resolve_my_address(int warn_severity, const or_options_t *options, addr_string = tor_dup_ip(addr); if (is_internal_IP(addr, 0)) { /* make sure we're ok with publishing an internal IP */ - if (!options->DirServers && !options->AlternateDirAuthority) { - /* if they are using the default dirservers, disallow internal IPs + if (!options->DirAuthorities && !options->AlternateDirAuthority) { + /* if they are using the default authorities, disallow internal IPs * always. */ log_fn(warn_severity, LD_CONFIG, "Address '%s' resolves to private IP address '%s'. " - "Tor servers that use the default DirServers must have public " - "IP addresses.", hostname, addr_string); + "Tor servers that use the default DirAuthorities must have " + "public IP addresses.", hostname, addr_string); tor_free(addr_string); return -1; } if (!explicit_ip) { - /* even if they've set their own dirservers, require an explicit IP if + /* even if they've set their own authorities, require an explicit IP if * they're using an internal address. */ log_fn(warn_severity, LD_CONFIG, "Address '%s' resolves to private " "IP address '%s'. Please set the Address config option to be " @@ -3038,112 +2053,11 @@ is_local_addr(const tor_addr_t *addr) return 0; } -/** Release storage held by <b>options</b>. */ -static void -config_free(const config_format_t *fmt, void *options) -{ - int i; - - if (!options) - return; - - tor_assert(fmt); - - for (i=0; fmt->vars[i].name; ++i) - option_clear(fmt, options, &(fmt->vars[i])); - if (fmt->extra) { - config_line_t **linep = STRUCT_VAR_P(options, fmt->extra->var_offset); - config_free_lines(*linep); - *linep = NULL; - } - tor_free(options); -} - -/** Return true iff a and b contain identical keys and values in identical - * order. */ -static int -config_lines_eq(config_line_t *a, config_line_t *b) -{ - while (a && b) { - if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) - return 0; - a = a->next; - b = b->next; - } - if (a || b) - return 0; - return 1; -} - -/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */ -static int -config_count_key(const config_line_t *a, const char *key) -{ - int n = 0; - while (a) { - if (!strcasecmp(a->key, key)) { - ++n; - } - a = a->next; - } - return n; -} - -/** Return true iff the option <b>name</b> has the same value in <b>o1</b> - * and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options. - */ -static int -option_is_same(const config_format_t *fmt, - const or_options_t *o1, const or_options_t *o2, - const char *name) -{ - config_line_t *c1, *c2; - int r = 1; - CHECK(fmt, o1); - CHECK(fmt, o2); - - c1 = get_assigned_option(fmt, o1, name, 0); - c2 = get_assigned_option(fmt, o2, name, 0); - r = config_lines_eq(c1, c2); - config_free_lines(c1); - config_free_lines(c2); - return r; -} - -/** Copy storage held by <b>old</b> into a new or_options_t and return it. */ -static or_options_t * -options_dup(const config_format_t *fmt, const or_options_t *old) -{ - or_options_t *newopts; - int i; - config_line_t *line; - - newopts = config_alloc(fmt); - for (i=0; fmt->vars[i].name; ++i) { - if (fmt->vars[i].type == CONFIG_TYPE_LINELIST_S) - continue; - if (fmt->vars[i].type == CONFIG_TYPE_OBSOLETE) - continue; - line = get_assigned_option(fmt, old, fmt->vars[i].name, 0); - if (line) { - char *msg = NULL; - if (config_assign(fmt, newopts, line, 0, 0, &msg) < 0) { - log_err(LD_BUG, "Config_get_assigned_option() generated " - "something we couldn't config_assign(): %s", msg); - tor_free(msg); - tor_assert(0); - } - } - config_free_lines(line); - } - return newopts; -} - /** Return a new empty or_options_t. Used for testing. */ or_options_t * options_new(void) { - return config_alloc(&options_format); + return config_new(&options_format); } /** Set <b>options</b> to hold reasonable defaults for most options. @@ -3154,94 +2068,6 @@ options_init(or_options_t *options) config_init(&options_format, options); } -/** Set all vars in the configuration object <b>options</b> to their default - * values. */ -static void -config_init(const config_format_t *fmt, void *options) -{ - int i; - const config_var_t *var; - CHECK(fmt, options); - - for (i=0; fmt->vars[i].name; ++i) { - var = &fmt->vars[i]; - if (!var->initvalue) - continue; /* defaults to NULL or 0 */ - option_reset(fmt, options, var, 1); - } -} - -/** Allocate and return a new string holding the written-out values of the vars - * in 'options'. If 'minimal', do not write out any default-valued vars. - * Else, if comment_defaults, write default values as comments. - */ -static char * -config_dump(const config_format_t *fmt, const void *default_options, - const void *options, int minimal, - int comment_defaults) -{ - smartlist_t *elements; - const or_options_t *defaults = default_options; - void *defaults_tmp = NULL; - config_line_t *line, *assigned; - char *result; - int i; - char *msg = NULL; - - if (defaults == NULL) { - defaults = defaults_tmp = config_alloc(fmt); - config_init(fmt, defaults_tmp); - } - - /* XXX use a 1 here so we don't add a new log line while dumping */ - if (default_options == NULL) { - if (fmt->validate_fn(NULL, defaults_tmp, 1, &msg) < 0) { - log_err(LD_BUG, "Failed to validate default config."); - tor_free(msg); - tor_assert(0); - } - } - - elements = smartlist_new(); - for (i=0; fmt->vars[i].name; ++i) { - int comment_option = 0; - if (fmt->vars[i].type == CONFIG_TYPE_OBSOLETE || - fmt->vars[i].type == CONFIG_TYPE_LINELIST_S) - continue; - /* Don't save 'hidden' control variables. */ - if (!strcmpstart(fmt->vars[i].name, "__")) - continue; - if (minimal && option_is_same(fmt, options, defaults, fmt->vars[i].name)) - continue; - else if (comment_defaults && - option_is_same(fmt, options, defaults, fmt->vars[i].name)) - comment_option = 1; - - line = assigned = get_assigned_option(fmt, options, fmt->vars[i].name, 1); - - for (; line; line = line->next) { - smartlist_add_asprintf(elements, "%s%s %s\n", - comment_option ? "# " : "", - line->key, line->value); - } - config_free_lines(assigned); - } - - if (fmt->extra) { - line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->var_offset); - for (; line; line = line->next) { - smartlist_add_asprintf(elements, "%s %s\n", line->key, line->value); - } - } - - result = smartlist_join_strings(elements, "", 0, NULL); - SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); - smartlist_free(elements); - if (defaults_tmp) - config_free(fmt, defaults_tmp); - return result; -} - /** Return a string containing a possible configuration file that would give * the configuration in <b>options</b>. If <b>minimal</b> is true, do not * include options that are the same as Tor's defaults. @@ -3298,16 +2124,16 @@ ensure_bandwidth_cap(uint64_t *value, const char *desc, char **msg) } /** Parse an authority type from <b>options</b>-\>PublishServerDescriptor - * and write it to <b>options</b>-\>_PublishServerDescriptor. Treat "1" + * and write it to <b>options</b>-\>PublishServerDescriptor_. Treat "1" * as "v2,v3" unless BridgeRelay is 1, in which case treat it as "bridge". * Treat "0" as "". * Return 0 on success or -1 if not a recognized authority type (in which - * case the value of _PublishServerDescriptor is undefined). */ + * case the value of PublishServerDescriptor_ is undefined). */ static int compute_publishserverdescriptor(or_options_t *options) { smartlist_t *list = options->PublishServerDescriptor; - dirinfo_type_t *auth = &options->_PublishServerDescriptor; + dirinfo_type_t *auth = &options->PublishServerDescriptor_; *auth = NO_DIRINFO; if (!list) /* empty list, answer is none */ return 0; @@ -3403,6 +2229,9 @@ options_validate(or_options_t *old_options, or_options_t *options, if (parse_ports(options, 1, msg, &n_ports) < 0) return -1; + if (parse_outbound_addresses(options, 1, msg) < 0) + return -1; + if (validate_data_directory(options)<0) REJECT("Invalid DataDirectory"); @@ -3467,9 +2296,9 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->ExcludeExitNodes || options->ExcludeNodes) { - options->_ExcludeExitNodesUnion = routerset_new(); - routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeExitNodes); - routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes); + options->ExcludeExitNodesUnion_ = routerset_new(); + routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeExitNodes); + routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes); } if (options->NodeFamilies) { @@ -3484,6 +2313,12 @@ options_validate(or_options_t *old_options, or_options_t *options, } } + if (options->TLSECGroup && (strcasecmp(options->TLSECGroup, "P256") && + strcasecmp(options->TLSECGroup, "P224"))) { + COMPLAIN("Unrecognized TLSECGroup: Falling back to the default."); + tor_free(options->TLSECGroup); + } + if (options->ExcludeNodes && options->StrictNodes) { COMPLAIN("You have asked to exclude certain relays from all positions " "in your circuits. Expect hidden services and other Tor " @@ -3668,19 +2503,19 @@ options_validate(or_options_t *old_options, or_options_t *options, log_warn(LD_CONFIG, "EntryNodes is set, but UseEntryGuards is disabled. " "EntryNodes will be ignored."); - options->_AllowInvalid = 0; + options->AllowInvalid_ = 0; if (options->AllowInvalidNodes) { SMARTLIST_FOREACH_BEGIN(options->AllowInvalidNodes, const char *, cp) { if (!strcasecmp(cp, "entry")) - options->_AllowInvalid |= ALLOW_INVALID_ENTRY; + options->AllowInvalid_ |= ALLOW_INVALID_ENTRY; else if (!strcasecmp(cp, "exit")) - options->_AllowInvalid |= ALLOW_INVALID_EXIT; + options->AllowInvalid_ |= ALLOW_INVALID_EXIT; else if (!strcasecmp(cp, "middle")) - options->_AllowInvalid |= ALLOW_INVALID_MIDDLE; + options->AllowInvalid_ |= ALLOW_INVALID_MIDDLE; else if (!strcasecmp(cp, "introduction")) - options->_AllowInvalid |= ALLOW_INVALID_INTRODUCTION; + options->AllowInvalid_ |= ALLOW_INVALID_INTRODUCTION; else if (!strcasecmp(cp, "rendezvous")) - options->_AllowInvalid |= ALLOW_INVALID_RENDEZVOUS; + options->AllowInvalid_ |= ALLOW_INVALID_RENDEZVOUS; else { tor_asprintf(msg, "Unrecognized value '%s' in AllowInvalidNodes", cp); @@ -3691,11 +2526,11 @@ options_validate(or_options_t *old_options, or_options_t *options, if (!options->SafeLogging || !strcasecmp(options->SafeLogging, "0")) { - options->_SafeLogging = SAFELOG_SCRUB_NONE; + options->SafeLogging_ = SAFELOG_SCRUB_NONE; } else if (!strcasecmp(options->SafeLogging, "relay")) { - options->_SafeLogging = SAFELOG_SCRUB_RELAY; + options->SafeLogging_ = SAFELOG_SCRUB_RELAY; } else if (!strcasecmp(options->SafeLogging, "1")) { - options->_SafeLogging = SAFELOG_SCRUB_ALL; + options->SafeLogging_ = SAFELOG_SCRUB_ALL; } else { tor_asprintf(msg, "Unrecognized value '%s' in SafeLogging", @@ -3709,8 +2544,8 @@ options_validate(or_options_t *old_options, or_options_t *options, } if ((options->BridgeRelay - || options->_PublishServerDescriptor & BRIDGE_DIRINFO) - && (options->_PublishServerDescriptor + || options->PublishServerDescriptor_ & BRIDGE_DIRINFO) + && (options->PublishServerDescriptor_ & (V1_DIRINFO|V2_DIRINFO|V3_DIRINFO))) { REJECT("Bridges are not supposed to publish router descriptors to the " "directory authorities. Please correct your " @@ -3761,6 +2596,29 @@ options_validate(or_options_t *old_options, or_options_t *options, options->LearnCircuitBuildTimeout = 0; } + if (options->Tor2webMode && options->UseEntryGuards) { + /* tor2web mode clients do not (and should not) use entry guards + * in any meaningful way. Further, tor2web mode causes the hidden + * service client code to do things which break the path bias + * detector, and it's far easier to turn off entry guards (and + * thus the path bias detector with it) than to figure out how to + * make a piece of code which cannot possibly help tor2web mode + * users compatible with tor2web mode. + */ + log_notice(LD_CONFIG, + "Tor2WebMode is enabled; disabling UseEntryGuards."); + options->UseEntryGuards = 0; + } + + if (!(options->UseEntryGuards) && + (options->RendConfigLines != NULL)) { + log_warn(LD_CONFIG, + "UseEntryGuards is disabled, but you have configured one or more " + "hidden services on this Tor instance. Your hidden services " + "will be very easy to locate using a well-known attack -- see " + "http://freehaven.net/anonbib/#hs-attack06 for details."); + } + if (!(options->LearnCircuitBuildTimeout) && options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) { log_warn(LD_CONFIG, @@ -4035,8 +2893,9 @@ options_validate(or_options_t *old_options, or_options_t *options, if (validate_addr_policies(options, msg) < 0) return -1; - if (validate_dir_authorities(options, old_options) < 0) - REJECT("Directory authority line did not parse. See logs for details."); + if (validate_dir_servers(options, old_options) < 0) + REJECT("Directory authority/fallback line did not parse. See logs " + "for details."); if (options->UseBridges && !options->Bridges) REJECT("If you set UseBridges, you must specify at least one bridge."); @@ -4062,7 +2921,23 @@ 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)); + } + + for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { + /** If get_bindaddr_from_transport_listen_line() fails with + 'transport' being NULL, it means that something went wrong + while parsing the ServerTransportListenAddr line. */ + char *bindaddr = get_bindaddr_from_transport_listen_line(cl->value, NULL); + if (!bindaddr) + REJECT("ServerTransportListenAddr did not parse. See logs for details."); + tor_free(bindaddr); + } + + if (options->ServerTransportListenAddr && !options->ServerTransportPlugin) { + log_notice(LD_GENERAL, "You need at least a single managed-proxy to " + "specify a transport listen address. The " + "ServerTransportListenAddr line will be ignored."); } if (options->ConstrainedSockets) { @@ -4116,7 +2991,11 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to configure client authorization for hidden services. " "See logs for details."); - if (parse_virtual_addr_network(options->VirtualAddrNetwork, 1, NULL)<0) + if (parse_virtual_addr_network(options->VirtualAddrNetworkIPv4, + AF_INET, 1, msg)<0) + return -1; + if (parse_virtual_addr_network(options->VirtualAddrNetworkIPv6, + AF_INET6, 1, msg)<0) return -1; if (options->PreferTunneledDirConns && !options->TunnelDirConns) @@ -4138,7 +3017,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingTorNetwork && - !(options->DirServers || + !(options->DirAuthorities || (options->AlternateDirAuthority && options->AlternateBridgeAuthority))) { REJECT("TestingTorNetwork may only be configured in combination with " @@ -4146,7 +3025,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "and AlternateBridgeAuthority configured."); } - if (options->AllowSingleHopExits && !options->DirServers) { + if (options->AllowSingleHopExits && !options->DirAuthorities) { COMPLAIN("You have set AllowSingleHopExits; now your relay will allow " "others to make one-hop exits. However, since by default most " "clients avoid relays that set this option, most clients will " @@ -4158,7 +3037,7 @@ options_validate(or_options_t *old_options, or_options_t *options, /* Keep changes to hard-coded values synchronous to man page and default * values table. */ if (options->TestingV3AuthInitialVotingInterval != 30*60 && - !options->TestingTorNetwork && !options->_UsingTestNetworkDefaults) { + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { REJECT("TestingV3AuthInitialVotingInterval may only be changed in testing " "Tor networks!"); } else if (options->TestingV3AuthInitialVotingInterval < MIN_VOTE_INTERVAL) { @@ -4169,7 +3048,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingV3AuthInitialVoteDelay != 5*60 && - !options->TestingTorNetwork && !options->_UsingTestNetworkDefaults) { + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { REJECT("TestingV3AuthInitialVoteDelay may only be changed in testing " "Tor networks!"); @@ -4178,7 +3057,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingV3AuthInitialDistDelay != 5*60 && - !options->TestingTorNetwork && !options->_UsingTestNetworkDefaults) { + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { REJECT("TestingV3AuthInitialDistDelay may only be changed in testing " "Tor networks!"); } else if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS) { @@ -4193,7 +3072,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingAuthDirTimeToLearnReachability != 30*60 && - !options->TestingTorNetwork && !options->_UsingTestNetworkDefaults) { + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { REJECT("TestingAuthDirTimeToLearnReachability may only be changed in " "testing Tor networks!"); } else if (options->TestingAuthDirTimeToLearnReachability < 0) { @@ -4203,7 +3082,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingEstimatedDescriptorPropagationTime != 10*60 && - !options->TestingTorNetwork && !options->_UsingTestNetworkDefaults) { + !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) { REJECT("TestingEstimatedDescriptorPropagationTime may only be changed in " "testing Tor networks!"); } else if (options->TestingEstimatedDescriptorPropagationTime < 0) { @@ -4339,7 +3218,7 @@ options_transition_affects_workers(const or_options_t *old_options, !config_lines_eq(old_options->ORPort_lines, new_options->ORPort_lines) || old_options->ServerDNSSearchDomains != new_options->ServerDNSSearchDomains || - old_options->_SafeLogging != new_options->_SafeLogging || + old_options->SafeLogging_ != new_options->SafeLogging_ || old_options->ClientOnly != new_options->ClientOnly || public_server_mode(old_options) != public_server_mode(new_options) || !config_lines_eq(old_options->Logs, new_options->Logs) || @@ -4366,14 +3245,15 @@ options_transition_affects_descriptor(const or_options_t *old_options, !config_lines_eq(old_options->ExitPolicy,new_options->ExitPolicy) || old_options->ExitPolicyRejectPrivate != new_options->ExitPolicyRejectPrivate || + old_options->IPv6Exit != new_options->IPv6Exit || !config_lines_eq(old_options->ORPort_lines, new_options->ORPort_lines) || !config_lines_eq(old_options->DirPort_lines, new_options->DirPort_lines) || old_options->ClientOnly != new_options->ClientOnly || old_options->DisableNetwork != new_options->DisableNetwork || - old_options->_PublishServerDescriptor != - new_options->_PublishServerDescriptor || + old_options->PublishServerDescriptor_ != + new_options->PublishServerDescriptor_ || get_effective_bwrate(old_options) != get_effective_bwrate(new_options) || get_effective_bwburst(old_options) != get_effective_bwburst(new_options) || @@ -4730,7 +3610,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, this is the first time we run*/ newoptions = tor_malloc_zero(sizeof(or_options_t)); - newoptions->_magic = OR_OPTIONS_MAGIC; + newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; newoptions->command_arg = command_arg; @@ -4752,7 +3632,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, goto err; } if (i==0) - newdefaultoptions = options_dup(&options_format, newoptions); + newdefaultoptions = config_dup(&options_format, newoptions); } /* Go through command-line variables too */ @@ -4790,7 +3670,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, config_free(&options_format, newdefaultoptions); newdefaultoptions = NULL; newoptions = tor_malloc_zero(sizeof(or_options_t)); - newoptions->_magic = OR_OPTIONS_MAGIC; + newoptions->magic_ = OR_OPTIONS_MAGIC; options_init(newoptions); newoptions->command = command; newoptions->command_arg = command_arg; @@ -4813,7 +3693,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, goto err; } if (i==0) - newdefaultoptions = options_dup(&options_format, newoptions); + newdefaultoptions = config_dup(&options_format, newoptions); } /* Assign command-line variables a second time too */ retval = config_assign(&options_format, newoptions, @@ -5147,8 +4027,8 @@ parse_bridge_line(const char *line, int validate_only) } if (!validate_only) { - log_debug(LD_DIR, "Bridge at %s:%d (transport: %s) (%s)", - fmt_addr(&addr), (int)port, + log_debug(LD_DIR, "Bridge at %s (transport: %s) (%s)", + fmt_addrport(&addr, port), transport_name ? transport_name : "no transport", fingerprint ? fingerprint : "no key listed"); bridge_add_from_config(&addr, port, @@ -5281,8 +4161,8 @@ parse_client_transport_line(const char *line, int validate_only) transport_add_from_config(&addr, port, smartlist_get(transport_list, 0), socks_ver); - log_info(LD_DIR, "Transport '%s' found at %s:%d", - transports, fmt_addr(&addr), (int)port); + log_info(LD_DIR, "Transport '%s' found at %s", + transports, fmt_addrport(&addr, port)); } } @@ -5303,6 +4183,80 @@ parse_client_transport_line(const char *line, int validate_only) return r; } +/** Given a ServerTransportListenAddr <b>line</b>, return its + * <address:port> string. Return NULL if the line was not + * well-formed. + * + * If <b>transport</b> is set, return NULL if the line is not + * referring to <b>transport</b>. + * + * The returned string is allocated on the heap and it's the + * responsibility of the caller to free it. */ +static char * +get_bindaddr_from_transport_listen_line(const char *line,const char *transport) +{ + smartlist_t *items = NULL; + const char *parsed_transport = NULL; + char *addrport = NULL; + tor_addr_t addr; + uint16_t port = 0; + + items = smartlist_new(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + if (smartlist_len(items) < 2) { + log_warn(LD_CONFIG,"Too few arguments on ServerTransportListenAddr line."); + goto err; + } + + parsed_transport = smartlist_get(items, 0); + addrport = tor_strdup(smartlist_get(items, 1)); + + /* If 'transport' is given, check if it matches the one on the line */ + if (transport && strcmp(transport, parsed_transport)) + goto err; + + /* Validate addrport */ + if (tor_addr_port_parse(LOG_WARN, addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing ServerTransportListenAddr " + "address '%s'", addrport); + goto err; + } + + goto done; + + err: + tor_free(addrport); + addrport = NULL; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return addrport; +} + +/** Given the name of a pluggable transport in <b>transport</b>, check + * the configuration file to see if the user has explicitly asked for + * it to listen on a specific port. Return a <address:port> string if + * so, otherwise NULL. */ +char * +get_transport_bindaddr_from_config(const char *transport) +{ + config_line_t *cl; + const or_options_t *options = get_options(); + + for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) { + char *bindaddr = + get_bindaddr_from_transport_listen_line(cl->value, transport); + if (bindaddr) + return bindaddr; + } + + return NULL; +} + /** Read the contents of a ServerTransportPlugin line from * <b>line</b>. Return 0 if the line is well-formed, and -1 if it * isn't. @@ -5401,8 +4355,8 @@ parse_server_transport_line(const char *line, int validate_only) } if (!validate_only) { - log_info(LD_DIR, "Server transport '%s' at %s:%d.", - transports, fmt_addr(&addr), (int)port); + log_info(LD_DIR, "Server transport '%s' at %s.", + transports, fmt_addrport(&addr, port)); } } @@ -5423,15 +4377,15 @@ parse_server_transport_line(const char *line, int validate_only) return r; } -/** Read the contents of a DirServer line from <b>line</b>. If +/** Read the contents of a DirAuthority line from <b>line</b>. If * <b>validate_only</b> is 0, and the line is well-formed, and it * shares any bits with <b>required_type</b> or <b>required_type</b> * is 0, then add the dirserver described in the line (minus whatever * bits it's missing) as a valid authority. Return 0 on success, * or -1 if the line isn't well-formed or if we can't add it. */ static int -parse_dir_server_line(const char *line, dirinfo_type_t required_type, - int validate_only) +parse_dir_authority_line(const char *line, dirinfo_type_t required_type, + int validate_only) { smartlist_t *items = NULL; int r; @@ -5441,6 +4395,7 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, char v3_digest[DIGEST_LEN]; dirinfo_type_t type = V2_DIRINFO; int is_not_hidserv_authority = 0, is_not_v2_authority = 0; + double weight = 1.0; items = smartlist_new(); smartlist_split_string(items, line, NULL, @@ -5476,6 +4431,14 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, if (!ok) log_warn(LD_CONFIG, "Invalid orport '%s' on DirServer line.", portstring); + } else if (!strcmpstart(flag, "weight=")) { + int ok; + const char *wstring = flag + strlen("weight="); + weight = tor_parse_double(wstring, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, "Invalid weight '%s' on DirAuthority line.",flag); + weight=1.0; + } } else if (!strcasecmpstart(flag, "v3ident=")) { char *idstr = flag + strlen("v3ident="); if (strlen(idstr) != HEX_DIGEST_LEN || @@ -5514,8 +4477,8 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, fingerprint = smartlist_join_strings(items, "", 0, NULL); if (strlen(fingerprint) != HEX_DIGEST_LEN) { - log_warn(LD_CONFIG, "Key digest for DirServer is wrong length %d.", - (int)strlen(fingerprint)); + log_warn(LD_CONFIG, "Key digest '%s' for DirServer is wrong length %d.", + fingerprint, (int)strlen(fingerprint)); goto err; } if (!strcmp(fingerprint, "E623F7625FBE0C87820F11EC5F6D5377ED816294")) { @@ -5532,14 +4495,16 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, } if (!validate_only && (!required_type || required_type & type)) { + dir_server_t *ds; if (required_type) type &= required_type; /* pare down what we think of them as an * authority for. */ log_debug(LD_DIR, "Trusted %d dirserver at %s:%d (%s)", (int)type, address, (int)dir_port, (char*)smartlist_get(items,0)); - if (!add_trusted_dir_server(nickname, address, dir_port, or_port, - digest, v3_digest, type)) + if (!(ds = trusted_dir_server_new(nickname, address, dir_port, or_port, + digest, v3_digest, type, weight))) goto err; + dir_server_add(ds); } r = 0; @@ -5558,6 +4523,110 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, return r; } +/** Read the contents of a FallbackDir line from <b>line</b>. If + * <b>validate_only</b> is 0, and the line is well-formed, then add the + * dirserver described in the line as a fallback directory. Return 0 on + * success, or -1 if the line isn't well-formed or if we can't add it. */ +static int +parse_dir_fallback_line(const char *line, + int validate_only) +{ + int r = -1; + smartlist_t *items = smartlist_new(), *positional = smartlist_new(); + int orport = -1; + uint16_t dirport; + tor_addr_t addr; + int ok; + char id[DIGEST_LEN]; + char *address=NULL; + double weight=1.0; + + memset(id, 0, sizeof(id)); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + SMARTLIST_FOREACH_BEGIN(items, const char *, cp) { + const char *eq = strchr(cp, '='); + ok = 1; + if (! eq) { + smartlist_add(positional, (char*)cp); + continue; + } + if (!strcmpstart(cp, "orport=")) { + orport = (int)tor_parse_long(cp+strlen("orport="), 10, + 1, 65535, &ok, NULL); + } else if (!strcmpstart(cp, "id=")) { + ok = !base16_decode(id, DIGEST_LEN, + cp+strlen("id="), strlen(cp)-strlen("id=")); + } else if (!strcmpstart(cp, "weight=")) { + int ok; + const char *wstring = cp + strlen("weight="); + weight = tor_parse_double(wstring, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, "Invalid weight '%s' on FallbackDir line.", cp); + weight=1.0; + } + } + + if (!ok) { + log_warn(LD_CONFIG, "Bad FallbackDir option %s", escaped(cp)); + goto end; + } + } SMARTLIST_FOREACH_END(cp); + + if (smartlist_len(positional) != 1) { + log_warn(LD_CONFIG, "Couldn't parse FallbackDir line %s", escaped(line)); + goto end; + } + + if (tor_digest_is_zero(id)) { + log_warn(LD_CONFIG, "Missing identity on FallbackDir line"); + goto end; + } + + if (orport <= 0) { + log_warn(LD_CONFIG, "Missing orport on FallbackDir line"); + goto end; + } + + if (tor_addr_port_split(LOG_INFO, smartlist_get(positional, 0), + &address, &dirport) < 0 || + tor_addr_parse(&addr, address)<0) { + log_warn(LD_CONFIG, "Couldn't parse address:port %s on FallbackDir line", + (const char*)smartlist_get(positional, 0)); + goto end; + } + + if (!validate_only) { + dir_server_t *ds; + ds = fallback_dir_server_new(&addr, dirport, orport, id, weight); + if (!ds) { + log_warn(LD_CONFIG, "Couldn't create FallbackDir %s", escaped(line)); + goto end; + } + dir_server_add(ds); + } + + r = 0; + + end: + SMARTLIST_FOREACH(items, char *, cp, tor_free(cp)); + smartlist_free(items); + smartlist_free(positional); + tor_free(address); + return r; +} + +/** Allocate and return a new port_cfg_t with reasonable defaults. */ +static port_cfg_t * +port_cfg_new(void) +{ + port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + cfg->ipv4_traffic = 1; + cfg->cache_ipv4_answers = 1; + cfg->prefer_ipv6_virtaddr = 1; + return cfg; +} + /** Free all storage held in <b>port</b> */ static void port_cfg_free(port_cfg_t *port) @@ -5565,24 +4634,29 @@ port_cfg_free(port_cfg_t *port) tor_free(port); } -/** Warn for every port in <b>ports</b> that is on a publicly routable - * address. */ +/** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is + * on a publicly routable address. */ static void -warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname) +warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, + int listener_type) { SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + if (port->type != listener_type) + continue; if (port->is_unix_addr) { /* Unix sockets aren't accessible over a network. */ } else if (!tor_addr_is_internal(&port->addr, 1)) { - log_warn(LD_CONFIG, "You specified a public address for %sPort. " + log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. " "Other people on the Internet might find your computer and " "use it as an open proxy. Please don't allow this unless you " - "have a good reason.", portname); + "have a good reason.", + fmt_addrport(&port->addr, port->port), portname); } else if (!tor_addr_is_loopback(&port->addr)) { - log_notice(LD_CONFIG, "You configured a non-loopback address for " - "%sPort. This allows everybody on your local network to use " - "your machine as a proxy. Make sure this is what you wanted.", - portname); + log_notice(LD_CONFIG, "You configured a non-loopback address '%s' " + "for %sPort. This allows everybody on your local network to " + "use your machine as a proxy. Make sure this is what you " + "wanted.", + fmt_addrport(&port->addr, port->port), portname); } } SMARTLIST_FOREACH_END(port); } @@ -5634,6 +4708,7 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid) #define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2) #define CL_PORT_SERVER_OPTIONS (1u<<3) #define CL_PORT_FORBID_NONLOCAL (1u<<4) +#define CL_PORT_TAKES_HOSTNAMES (1u<<5) /** * Parse port configuration for a single port type. @@ -5666,6 +4741,9 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid) * isolation options in the FooPort entries; instead allow the * server-port option set. * + * If CL_PORT_TAKES_HOSTNAMES is set in <b>flags</b>, allow the options + * {No,}IPv{4,6}Traffic. + * * On success, if <b>out</b> is given, add a new port_cfg_t entry to * <b>out</b> for every port that the client should listen on. Return 0 * on success, -1 on failure. @@ -5689,6 +4767,7 @@ parse_port_config(smartlist_t *out, const unsigned forbid_nonlocal = flags & CL_PORT_FORBID_NONLOCAL; const unsigned allow_spurious_listenaddr = flags & CL_PORT_ALLOW_EXTRA_LISTENADDR; + const unsigned takes_hostnames = flags & CL_PORT_TAKES_HOSTNAMES; int got_zero_port=0, got_nonzero_port=0; /* FooListenAddress is deprecated; let's make it work like it used to work, @@ -5725,12 +4804,14 @@ parse_port_config(smartlist_t *out, if (use_server_options && out) { /* Add a no_listen port. */ - port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + port_cfg_t *cfg = port_cfg_new(); cfg->type = listener_type; cfg->port = mainport; tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */ cfg->no_listen = 1; - cfg->ipv4_only = 1; + cfg->bind_ipv4_only = 1; + cfg->ipv4_traffic = 1; + cfg->prefer_ipv6_virtaddr = 1; smartlist_add(out, cfg); } @@ -5743,7 +4824,7 @@ parse_port_config(smartlist_t *out, return -1; } if (out) { - port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + port_cfg_t *cfg = port_cfg_new(); cfg->type = listener_type; cfg->port = port ? port : mainport; tor_addr_copy(&cfg->addr, &addr); @@ -5758,7 +4839,7 @@ parse_port_config(smartlist_t *out, if (is_control) warn_nonlocal_controller_ports(out, forbid_nonlocal); else - warn_nonlocal_client_ports(out, portname); + warn_nonlocal_client_ports(out, portname, listener_type); } return 0; } /* end if (listenaddrs) */ @@ -5767,7 +4848,7 @@ parse_port_config(smartlist_t *out, * one. */ if (! ports) { if (defaultport && out) { - port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + port_cfg_t *cfg = port_cfg_new(); cfg->type = listener_type; cfg->port = defaultport; tor_addr_parse(&cfg->addr, defaultaddr); @@ -5792,7 +4873,11 @@ parse_port_config(smartlist_t *out, uint16_t ptmp=0; int ok; int no_listen = 0, no_advertise = 0, all_addrs = 0, - ipv4_only = 0, ipv6_only = 0; + bind_ipv4_only = 0, bind_ipv6_only = 0, + ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0, + cache_ipv4 = 1, use_cached_ipv4 = 0, + cache_ipv6 = 0, use_cached_ipv6 = 0, + prefer_ipv6_automap = 1; smartlist_split_string(elts, ports->value, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); @@ -5857,9 +4942,9 @@ parse_port_config(smartlist_t *out, all_addrs = 1; #endif } else if (!strcasecmp(elt, "IPv4Only")) { - ipv4_only = 1; + bind_ipv4_only = 1; } else if (!strcasecmp(elt, "IPv6Only")) { - ipv6_only = 1; + bind_ipv6_only = 1; } else { log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'", portname, escaped(elt)); @@ -5872,18 +4957,18 @@ parse_port_config(smartlist_t *out, portname, escaped(ports->value)); goto err; } - if (ipv4_only && ipv6_only) { + if (bind_ipv4_only && bind_ipv6_only) { log_warn(LD_CONFIG, "Tried to set both IPv4Only and IPv6Only " "on %sPort line '%s'", portname, escaped(ports->value)); goto err; } - if (ipv4_only && tor_addr_family(&addr) == AF_INET6) { + if (bind_ipv4_only && tor_addr_family(&addr) == AF_INET6) { log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv6", portname); goto err; } - if (ipv6_only && tor_addr_family(&addr) == AF_INET) { + if (bind_ipv6_only && tor_addr_family(&addr) == AF_INET) { log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv4", portname); goto err; @@ -5916,6 +5001,42 @@ parse_port_config(smartlist_t *out, no = 1; elt += 2; } + + if (takes_hostnames) { + if (!strcasecmp(elt, "IPv4Traffic")) { + ipv4_traffic = ! no; + continue; + } else if (!strcasecmp(elt, "IPv6Traffic")) { + ipv6_traffic = ! no; + continue; + } else if (!strcasecmp(elt, "PreferIPv6")) { + prefer_ipv6 = ! no; + continue; + } + } + if (!strcasecmp(elt, "CacheIPv4DNS")) { + cache_ipv4 = ! no; + continue; + } else if (!strcasecmp(elt, "CacheIPv6DNS")) { + cache_ipv6 = ! no; + continue; + } else if (!strcasecmp(elt, "CacheDNS")) { + cache_ipv4 = cache_ipv6 = ! no; + continue; + } else if (!strcasecmp(elt, "UseIPv4Cache")) { + use_cached_ipv4 = ! no; + continue; + } else if (!strcasecmp(elt, "UseIPv6Cache")) { + use_cached_ipv6 = ! no; + continue; + } else if (!strcasecmp(elt, "UseDNSCache")) { + use_cached_ipv4 = use_cached_ipv6 = ! no; + continue; + } else if (!strcasecmp(elt, "PreferIPv6Automap")) { + prefer_ipv6_automap = ! no; + continue; + } + if (!strcasecmpend(elt, "s")) elt[strlen(elt)-1] = '\0'; /* kill plurals. */ @@ -5947,8 +5068,14 @@ parse_port_config(smartlist_t *out, else got_zero_port = 1; + if (ipv4_traffic == 0 && ipv6_traffic == 0) { + log_warn(LD_CONFIG, "You have a %sPort entry with both IPv4 and " + "IPv6 disabled; that won't work.", portname); + goto err; + } + if (out && port) { - port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + port_cfg_t *cfg = port_cfg_new(); tor_addr_copy(&cfg->addr, &addr); cfg->port = port; cfg->type = listener_type; @@ -5957,8 +5084,16 @@ parse_port_config(smartlist_t *out, cfg->no_advertise = no_advertise; cfg->no_listen = no_listen; cfg->all_addrs = all_addrs; - cfg->ipv4_only = ipv4_only; - cfg->ipv6_only = ipv6_only; + cfg->bind_ipv4_only = bind_ipv4_only; + cfg->bind_ipv6_only = bind_ipv6_only; + cfg->ipv4_traffic = ipv4_traffic; + cfg->ipv6_traffic = ipv6_traffic; + cfg->prefer_ipv6 = prefer_ipv6; + cfg->cache_ipv4_answers = cache_ipv4; + cfg->cache_ipv6_answers = cache_ipv6; + cfg->use_cached_ipv4_answers = use_cached_ipv4; + cfg->use_cached_ipv6_answers = use_cached_ipv6; + cfg->prefer_ipv6_virtaddr = prefer_ipv6_automap; smartlist_add(out, cfg); } @@ -5970,7 +5105,7 @@ parse_port_config(smartlist_t *out, if (is_control) warn_nonlocal_controller_ports(out, forbid_nonlocal); else - warn_nonlocal_client_ports(out, portname); + warn_nonlocal_client_ports(out, portname, listener_type); } if (got_zero_port && got_nonzero_port) { @@ -6051,7 +5186,8 @@ parse_ports(or_options_t *options, int validate_only, options->SocksPort_lines, options->SocksListenAddress, "Socks", CONN_TYPE_AP_LISTENER, "127.0.0.1", 9050, - CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR) < 0) { + CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR| + CL_PORT_TAKES_HOSTNAMES) < 0) { *msg = tor_strdup("Invalid SocksPort/SocksListenAddress configuration"); goto err; } @@ -6059,7 +5195,7 @@ parse_ports(or_options_t *options, int validate_only, options->DNSPort_lines, options->DNSListenAddress, "DNS", CONN_TYPE_AP_DNS_LISTENER, "127.0.0.1", 0, - CL_PORT_WARN_NONLOCAL) < 0) { + CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES) < 0) { *msg = tor_strdup("Invalid DNSPort/DNSListenAddress configuration"); goto err; } @@ -6191,7 +5327,8 @@ check_server_ports(const smartlist_t *ports, if (! port->no_advertise) { ++n_orport_advertised; if (tor_addr_family(&port->addr) == AF_INET || - (tor_addr_family(&port->addr) == AF_UNSPEC && !port->ipv6_only)) + (tor_addr_family(&port->addr) == AF_UNSPEC && + !port->bind_ipv6_only)) ++n_orport_advertised_ipv4; } if (! port->no_listen) @@ -6200,7 +5337,7 @@ check_server_ports(const smartlist_t *ports, continue; } #ifndef _WIN32 - if (!port->no_advertise && port->port < 1024) + if (!port->no_listen && port->port < 1024) ++n_low_port; #endif } SMARTLIST_FOREACH_END(port); @@ -6321,8 +5458,8 @@ get_first_advertised_port_by_type_af(int listener_type, int address_family) (tor_addr_family(&cfg->addr) == address_family || tor_addr_family(&cfg->addr) == AF_UNSPEC)) { if (tor_addr_family(&cfg->addr) != AF_UNSPEC || - (address_family == AF_INET && !cfg->ipv6_only) || - (address_family == AF_INET6 && !cfg->ipv4_only)) { + (address_family == AF_INET && !cfg->bind_ipv6_only) || + (address_family == AF_INET6 && !cfg->bind_ipv4_only)) { return cfg->port; } } @@ -6483,180 +5620,6 @@ options_save_current(void) return write_configuration_file(get_torrc_fname(0), get_options()); } -/** Mapping from a unit name to a multiplier for converting that unit into a - * base unit. Used by config_parse_unit. */ -struct unit_table_t { - const char *unit; /**< The name of the unit */ - uint64_t multiplier; /**< How many of the base unit appear in this unit */ -}; - -/** Table to map the names of memory units to the number of bytes they - * contain. */ -static struct unit_table_t memory_units[] = { - { "", 1 }, - { "b", 1<< 0 }, - { "byte", 1<< 0 }, - { "bytes", 1<< 0 }, - { "kb", 1<<10 }, - { "kbyte", 1<<10 }, - { "kbytes", 1<<10 }, - { "kilobyte", 1<<10 }, - { "kilobytes", 1<<10 }, - { "m", 1<<20 }, - { "mb", 1<<20 }, - { "mbyte", 1<<20 }, - { "mbytes", 1<<20 }, - { "megabyte", 1<<20 }, - { "megabytes", 1<<20 }, - { "gb", 1<<30 }, - { "gbyte", 1<<30 }, - { "gbytes", 1<<30 }, - { "gigabyte", 1<<30 }, - { "gigabytes", 1<<30 }, - { "tb", U64_LITERAL(1)<<40 }, - { "terabyte", U64_LITERAL(1)<<40 }, - { "terabytes", U64_LITERAL(1)<<40 }, - { NULL, 0 }, -}; - -/** Table to map the names of time units to the number of seconds they - * contain. */ -static struct unit_table_t time_units[] = { - { "", 1 }, - { "second", 1 }, - { "seconds", 1 }, - { "minute", 60 }, - { "minutes", 60 }, - { "hour", 60*60 }, - { "hours", 60*60 }, - { "day", 24*60*60 }, - { "days", 24*60*60 }, - { "week", 7*24*60*60 }, - { "weeks", 7*24*60*60 }, - { NULL, 0 }, -}; - -/** Table to map the names of time units to the number of milliseconds - * they contain. */ -static struct unit_table_t time_msec_units[] = { - { "", 1 }, - { "msec", 1 }, - { "millisecond", 1 }, - { "milliseconds", 1 }, - { "second", 1000 }, - { "seconds", 1000 }, - { "minute", 60*1000 }, - { "minutes", 60*1000 }, - { "hour", 60*60*1000 }, - { "hours", 60*60*1000 }, - { "day", 24*60*60*1000 }, - { "days", 24*60*60*1000 }, - { "week", 7*24*60*60*1000 }, - { "weeks", 7*24*60*60*1000 }, - { NULL, 0 }, -}; - -/** Parse a string <b>val</b> containing a number, zero or more - * spaces, and an optional unit string. If the unit appears in the - * table <b>u</b>, then multiply the number by the unit multiplier. - * On success, set *<b>ok</b> to 1 and return this product. - * Otherwise, set *<b>ok</b> to 0. - */ -static uint64_t -config_parse_units(const char *val, struct unit_table_t *u, int *ok) -{ - uint64_t v = 0; - double d = 0; - int use_float = 0; - char *cp; - - tor_assert(ok); - - v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp); - if (!*ok || (cp && *cp == '.')) { - d = tor_parse_double(val, 0, UINT64_MAX, ok, &cp); - if (!*ok) - goto done; - use_float = 1; - } - - if (!cp) { - *ok = 1; - v = use_float ? DBL_TO_U64(d) : v; - goto done; - } - - cp = (char*) eat_whitespace(cp); - - for ( ;u->unit;++u) { - if (!strcasecmp(u->unit, cp)) { - if (use_float) - v = u->multiplier * d; - else - v *= u->multiplier; - *ok = 1; - goto done; - } - } - log_warn(LD_CONFIG, "Unknown unit '%s'.", cp); - *ok = 0; - done: - - if (*ok) - return v; - else - return 0; -} - -/** Parse a string in the format "number unit", where unit is a unit of - * information (byte, KB, M, etc). On success, set *<b>ok</b> to true - * and return the number of bytes specified. Otherwise, set - * *<b>ok</b> to false and return 0. */ -static uint64_t -config_parse_memunit(const char *s, int *ok) -{ - uint64_t u = config_parse_units(s, memory_units, ok); - return u; -} - -/** Parse a string in the format "number unit", where unit is a unit of - * time in milliseconds. On success, set *<b>ok</b> to true and return - * the number of milliseconds in the provided interval. Otherwise, set - * *<b>ok</b> to 0 and return -1. */ -static int -config_parse_msec_interval(const char *s, int *ok) -{ - uint64_t r; - r = config_parse_units(s, time_msec_units, ok); - if (!ok) - return -1; - if (r > INT_MAX) { - log_warn(LD_CONFIG, "Msec interval '%s' is too long", s); - *ok = 0; - return -1; - } - return (int)r; -} - -/** Parse a string in the format "number unit", where unit is a unit of time. - * On success, set *<b>ok</b> to true and return the number of seconds in - * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1. - */ -static int -config_parse_interval(const char *s, int *ok) -{ - uint64_t r; - r = config_parse_units(s, time_units, ok); - if (!ok) - return -1; - if (r > INT_MAX) { - log_warn(LD_CONFIG, "Interval '%s' is too long", s); - *ok = 0; - return -1; - } - return (int)r; -} - /** Return the number of cpus configured in <b>options</b>. If we are * told to auto-detect the number of cpus, return the auto-detected number. */ int @@ -6710,14 +5673,6 @@ init_libevent(const or_options_t *options) } } -/** Return the persistent state struct for this Tor. */ -or_state_t * -get_or_state(void) -{ - tor_assert(global_state); - return global_state; -} - /** Return a newly allocated string holding a filename relative to the data * directory. If <b>sub1</b> is present, it is the first path component after * the data directory. If <b>sub2</b> is also present, it is the second path @@ -6768,474 +5723,6 @@ options_get_datadir_fname2_suffix(const or_options_t *options, return fname; } -/** Return true if <b>line</b> is a valid state TransportProxy line. - * Return false otherwise. */ -static int -state_transport_line_is_valid(const char *line) -{ - smartlist_t *items = NULL; - char *addrport=NULL; - tor_addr_t addr; - uint16_t port = 0; - int r; - - items = smartlist_new(); - smartlist_split_string(items, line, NULL, - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); - - if (smartlist_len(items) != 2) { - log_warn(LD_CONFIG, "state: Not enough arguments in TransportProxy line."); - goto err; - } - - addrport = smartlist_get(items, 1); - if (tor_addr_port_lookup(addrport, &addr, &port) < 0) { - log_warn(LD_CONFIG, "state: Could not parse addrport."); - goto err; - } - - if (!port) { - log_warn(LD_CONFIG, "state: Transport line did not contain port."); - goto err; - } - - r = 1; - goto done; - - err: - r = 0; - - done: - SMARTLIST_FOREACH(items, char*, s, tor_free(s)); - smartlist_free(items); - return r; -} - -/** Return 0 if all TransportProxy lines in <b>state</b> are well - * formed. Otherwise, return -1. */ -static int -validate_transports_in_state(or_state_t *state) -{ - int broken = 0; - config_line_t *line; - - for (line = state->TransportProxies ; line ; line = line->next) { - tor_assert(!strcmp(line->key, "TransportProxy")); - if (!state_transport_line_is_valid(line->value)) - broken = 1; - } - - if (broken) - log_warn(LD_CONFIG, "state: State file seems to be broken."); - - return 0; -} - -/** Return 0 if every setting in <b>state</b> is reasonable, and a - * permissible transition from <b>old_state</b>. Else warn and return -1. - * Should have no side effects, except for normalizing the contents of - * <b>state</b>. - */ -/* XXX from_setconf is here because of bug 238 */ -static int -or_state_validate(or_state_t *old_state, or_state_t *state, - int from_setconf, char **msg) -{ - /* We don't use these; only options do. Still, we need to match that - * signature. */ - (void) from_setconf; - (void) old_state; - - if (entry_guards_parse_state(state, 0, msg)<0) - return -1; - - if (validate_transports_in_state(state)<0) - return -1; - - return 0; -} - -/** Replace the current persistent state with <b>new_state</b> */ -static int -or_state_set(or_state_t *new_state) -{ - char *err = NULL; - int ret = 0; - tor_assert(new_state); - config_free(&state_format, global_state); - global_state = new_state; - if (entry_guards_parse_state(global_state, 1, &err)<0) { - log_warn(LD_GENERAL,"%s",err); - tor_free(err); - ret = -1; - } - if (rep_hist_load_state(global_state, &err)<0) { - log_warn(LD_GENERAL,"Unparseable bandwidth history state: %s",err); - tor_free(err); - ret = -1; - } - if (circuit_build_times_parse_state(&circ_times, global_state) < 0) { - ret = -1; - } - return ret; -} - -/** - * Save a broken state file to a backup location. - */ -static void -or_state_save_broken(char *fname) -{ - int i; - file_status_t status; - char *fname2 = NULL; - for (i = 0; i < 100; ++i) { - tor_asprintf(&fname2, "%s.%d", fname, i); - status = file_status(fname2); - if (status == FN_NOENT) - break; - tor_free(fname2); - } - if (i == 100) { - log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad " - "state files to move aside. Discarding the old state file.", - fname); - unlink(fname); - } else { - log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside " - "to \"%s\". This could be a bug in Tor; please tell " - "the developers.", fname, fname2); - if (rename(fname, fname2) < 0) { - log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The " - "OS gave an error of %s", strerror(errno)); - } - } - tor_free(fname2); -} - -/** Reload the persistent state from disk, generating a new state as needed. - * Return 0 on success, less than 0 on failure. - */ -static int -or_state_load(void) -{ - or_state_t *new_state = NULL; - char *contents = NULL, *fname; - char *errmsg = NULL; - int r = -1, badstate = 0; - - fname = get_datadir_fname("state"); - switch (file_status(fname)) { - case FN_FILE: - if (!(contents = read_file_to_str(fname, 0, NULL))) { - log_warn(LD_FS, "Unable to read state file \"%s\"", fname); - goto done; - } - break; - case FN_NOENT: - break; - case FN_ERROR: - case FN_DIR: - default: - log_warn(LD_GENERAL,"State file \"%s\" is not a file? Failing.", fname); - goto done; - } - new_state = tor_malloc_zero(sizeof(or_state_t)); - new_state->_magic = OR_STATE_MAGIC; - config_init(&state_format, new_state); - if (contents) { - config_line_t *lines=NULL; - int assign_retval; - if (config_get_lines(contents, &lines, 0)<0) - goto done; - assign_retval = config_assign(&state_format, new_state, - lines, 0, 0, &errmsg); - config_free_lines(lines); - if (assign_retval<0) - badstate = 1; - if (errmsg) { - log_warn(LD_GENERAL, "%s", errmsg); - tor_free(errmsg); - } - } - - if (!badstate && or_state_validate(NULL, new_state, 1, &errmsg) < 0) - badstate = 1; - - if (errmsg) { - log_warn(LD_GENERAL, "%s", errmsg); - tor_free(errmsg); - } - - if (badstate && !contents) { - log_warn(LD_BUG, "Uh oh. We couldn't even validate our own default state." - " This is a bug in Tor."); - goto done; - } else if (badstate && contents) { - or_state_save_broken(fname); - - tor_free(contents); - config_free(&state_format, new_state); - - new_state = tor_malloc_zero(sizeof(or_state_t)); - new_state->_magic = OR_STATE_MAGIC; - config_init(&state_format, new_state); - } else if (contents) { - log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); - } else { - log_info(LD_GENERAL, "Initialized state"); - } - if (or_state_set(new_state) == -1) { - or_state_save_broken(fname); - } - new_state = NULL; - if (!contents) { - global_state->next_write = 0; - or_state_save(time(NULL)); - } - r = 0; - - done: - tor_free(fname); - tor_free(contents); - if (new_state) - config_free(&state_format, new_state); - - return r; -} - -/** Did the last time we tried to write the state file fail? If so, we - * should consider disabling such features as preemptive circuit generation - * to compute circuit-build-time. */ -static int last_state_file_write_failed = 0; - -/** Return whether the state file failed to write last time we tried. */ -int -did_last_state_file_write_fail(void) -{ - return last_state_file_write_failed; -} - -/** If writing the state to disk fails, try again after this many seconds. */ -#define STATE_WRITE_RETRY_INTERVAL 3600 - -/** If we're a relay, how often should we checkpoint our state file even - * if nothing else dirties it? This will checkpoint ongoing stats like - * bandwidth used, per-country user stats, etc. */ -#define STATE_RELAY_CHECKPOINT_INTERVAL (12*60*60) - -/** Write the persistent state to disk. Return 0 for success, <0 on failure. */ -int -or_state_save(time_t now) -{ - char *state, *contents; - char tbuf[ISO_TIME_LEN+1]; - char *fname; - - tor_assert(global_state); - - if (global_state->next_write > now) - return 0; - - /* Call everything else that might dirty the state even more, in order - * to avoid redundant writes. */ - entry_guards_update_state(global_state); - rep_hist_update_state(global_state); - circuit_build_times_update_state(&circ_times, global_state); - if (accounting_is_enabled(get_options())) - accounting_run_housekeeping(now); - - global_state->LastWritten = now; - - tor_free(global_state->TorVersion); - tor_asprintf(&global_state->TorVersion, "Tor %s", get_version()); - - state = config_dump(&state_format, NULL, global_state, 1, 0); - format_local_iso_time(tbuf, now); - tor_asprintf(&contents, - "# Tor state file last generated on %s local time\n" - "# Other times below are in GMT\n" - "# You *do not* need to edit this file.\n\n%s", - tbuf, state); - tor_free(state); - fname = get_datadir_fname("state"); - if (write_str_to_file(fname, contents, 0)<0) { - log_warn(LD_FS, "Unable to write state to file \"%s\"; " - "will try again later", fname); - last_state_file_write_failed = 1; - tor_free(fname); - tor_free(contents); - /* Try again after STATE_WRITE_RETRY_INTERVAL (or sooner, if the state - * changes sooner). */ - global_state->next_write = now + STATE_WRITE_RETRY_INTERVAL; - return -1; - } - - last_state_file_write_failed = 0; - log_info(LD_GENERAL, "Saved state to \"%s\"", fname); - tor_free(fname); - tor_free(contents); - - if (server_mode(get_options())) - global_state->next_write = now + STATE_RELAY_CHECKPOINT_INTERVAL; - else - global_state->next_write = TIME_MAX; - - return 0; -} - -/** Return the config line for transport <b>transport</b> in the current state. - * Return NULL if there is no config line for <b>transport</b>. */ -static config_line_t * -get_transport_in_state_by_name(const char *transport) -{ - or_state_t *or_state = get_or_state(); - config_line_t *line; - config_line_t *ret = NULL; - smartlist_t *items = NULL; - - for (line = or_state->TransportProxies ; line ; line = line->next) { - tor_assert(!strcmp(line->key, "TransportProxy")); - - items = smartlist_new(); - smartlist_split_string(items, line->value, NULL, - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); - if (smartlist_len(items) != 2) /* broken state */ - goto done; - - if (!strcmp(smartlist_get(items, 0), transport)) { - ret = line; - goto done; - } - - SMARTLIST_FOREACH(items, char*, s, tor_free(s)); - smartlist_free(items); - items = NULL; - } - - done: - if (items) { - SMARTLIST_FOREACH(items, char*, s, tor_free(s)); - smartlist_free(items); - } - return ret; -} - -/** Return string containing the address:port part of the - * TransportProxy <b>line</b> for transport <b>transport</b>. - * If the line is corrupted, return NULL. */ -static const char * -get_transport_bindaddr(const char *line, const char *transport) -{ - char *line_tmp = NULL; - - if (strlen(line) < strlen(transport) + 2) { - goto broken_state; - } else { - /* line should start with the name of the transport and a space. - (for example, "obfs2 127.0.0.1:47245") */ - tor_asprintf(&line_tmp, "%s ", transport); - if (strcmpstart(line, line_tmp)) - goto broken_state; - - tor_free(line_tmp); - return (line+strlen(transport)+1); - } - - broken_state: - tor_free(line_tmp); - return NULL; -} - -/** Return a string containing the address:port that a proxy transport - * should bind on. The string is stored on the heap and must be freed - * by the caller of this function. */ -char * -get_stored_bindaddr_for_server_transport(const char *transport) -{ - char *default_addrport = NULL; - const char *stored_bindaddr = NULL; - - config_line_t *line = get_transport_in_state_by_name(transport); - if (!line) /* Found no references in state for this transport. */ - goto no_bindaddr_found; - - stored_bindaddr = get_transport_bindaddr(line->value, transport); - if (stored_bindaddr) /* found stored bindaddr in state file. */ - return tor_strdup(stored_bindaddr); - - no_bindaddr_found: - /** If we didn't find references for this pluggable transport in the - state file, we should instruct the pluggable transport proxy to - listen on INADDR_ANY on a random ephemeral port. */ - tor_asprintf(&default_addrport, "%s:%s", fmt_addr32(INADDR_ANY), "0"); - return default_addrport; -} - -/** Save <b>transport</b> listening on <b>addr</b>:<b>port</b> to - state */ -void -save_transport_to_state(const char *transport, - const tor_addr_t *addr, uint16_t port) -{ - or_state_t *state = get_or_state(); - - char *transport_addrport=NULL; - - /** find where to write on the state */ - config_line_t **next, *line; - - /* see if this transport is already stored in state */ - config_line_t *transport_line = - get_transport_in_state_by_name(transport); - - if (transport_line) { /* if transport already exists in state... */ - const char *prev_bindaddr = /* get its addrport... */ - get_transport_bindaddr(transport_line->value, transport); - tor_asprintf(&transport_addrport, "%s:%d", fmt_addr(addr), (int)port); - - /* if transport in state has the same address as this one, life is good */ - if (!strcmp(prev_bindaddr, transport_addrport)) { - log_info(LD_CONFIG, "Transport seems to have spawned on its usual " - "address:port."); - goto done; - } else { /* if addrport in state is different than the one we got */ - log_info(LD_CONFIG, "Transport seems to have spawned on different " - "address:port. Let's update the state file with the new " - "address:port"); - tor_free(transport_line->value); /* free the old line */ - tor_asprintf(&transport_line->value, "%s %s:%d", transport, - fmt_addr(addr), - (int) port); /* replace old addrport line with new line */ - } - } else { /* never seen this one before; save it in state for next time */ - log_info(LD_CONFIG, "It's the first time we see this transport. " - "Let's save its address:port"); - next = &state->TransportProxies; - /* find the last TransportProxy line in the state and point 'next' - right after it */ - line = state->TransportProxies; - while (line) { - next = &(line->next); - line = line->next; - } - - /* allocate space for the new line and fill it in */ - *next = line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup("TransportProxy"); - tor_asprintf(&line->value, "%s %s:%d", transport, - fmt_addr(addr), (int) port); - - next = &(line->next); - } - - if (!get_options()->AvoidDiskWrites) - or_state_mark_dirty(state, 0); - - done: - tor_free(transport_addrport); -} - /** Given a file name check to see whether the file exists but has not been * modified for a very long time. If so, remove it. */ void @@ -7253,6 +5740,43 @@ remove_file_if_very_old(const char *fname, time_t now) } } +/** Return a smartlist of ports that must be forwarded by + * tor-fw-helper. The smartlist contains the ports in a string format + * that is understandable by tor-fw-helper. */ +smartlist_t * +get_list_of_ports_to_forward(void) +{ + smartlist_t *ports_to_forward = smartlist_new(); + int port = 0; + + /** XXX TODO tor-fw-helper does not support forwarding ports to + other hosts than the local one. If the user is binding to a + different IP address, tor-fw-helper won't work. */ + port = router_get_advertised_or_port(get_options()); /* Get ORPort */ + if (port) + smartlist_add_asprintf(ports_to_forward, "%d:%d", port, port); + + port = router_get_advertised_dir_port(get_options(), 0); /* Get DirPort */ + if (port) + smartlist_add_asprintf(ports_to_forward, "%d:%d", port, port); + + /* Get ports of transport proxies */ + { + smartlist_t *transport_ports = get_transport_proxy_ports(); + if (transport_ports) { + smartlist_add_all(ports_to_forward, transport_ports); + smartlist_free(transport_ports); + } + } + + if (!smartlist_len(ports_to_forward)) { + smartlist_free(ports_to_forward); + ports_to_forward = NULL; + } + + return ports_to_forward; +} + /** Helper to implement GETINFO functions about configuration variables (not * their values). Given a "config/names" question, set *<b>answer</b> to a * new string describing the supported configuration variables and their @@ -7267,9 +5791,12 @@ getinfo_helper_config(control_connection_t *conn, if (!strcmp(question, "config/names")) { smartlist_t *sl = smartlist_new(); int i; - for (i = 0; _option_vars[i].name; ++i) { - const config_var_t *var = &_option_vars[i]; + for (i = 0; option_vars_[i].name; ++i) { + const config_var_t *var = &option_vars_[i]; const char *type; + /* don't tell controller about triple-underscore options */ + if (!strncmp(option_vars_[i].name, "___", 3)) + continue; switch (var->type) { case CONFIG_TYPE_STRING: type = "String"; break; case CONFIG_TYPE_FILENAME: type = "Filename"; break; @@ -7299,7 +5826,120 @@ 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; +} + +/** Parse outbound bind address option lines. If <b>validate_only</b> + * is not 0 update OutboundBindAddressIPv4_ and + * OutboundBindAddressIPv6_ in <b>options</b>. On failure, set + * <b>msg</b> (if provided) to a newly allocated string containing a + * description of the problem and return -1. */ +static int +parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) +{ + const config_line_t *lines = options->OutboundBindAddress; + int found_v4 = 0, found_v6 = 0; + + if (!validate_only) { + memset(&options->OutboundBindAddressIPv4_, 0, + sizeof(options->OutboundBindAddressIPv4_)); + memset(&options->OutboundBindAddressIPv6_, 0, + sizeof(options->OutboundBindAddressIPv6_)); + } + while (lines) { + tor_addr_t addr, *dst_addr = NULL; + int af = tor_addr_parse(&addr, lines->value); + switch (af) { + case AF_INET: + if (found_v4) { + if (msg) + tor_asprintf(msg, "Multiple IPv4 outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + found_v4 = 1; + dst_addr = &options->OutboundBindAddressIPv4_; + break; + case AF_INET6: + if (found_v6) { + if (msg) + tor_asprintf(msg, "Multiple IPv6 outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + found_v6 = 1; + dst_addr = &options->OutboundBindAddressIPv6_; + break; + default: + if (msg) + tor_asprintf(msg, "Outbound bind address '%s' didn't parse.", + lines->value); + return -1; + } + if (!validate_only) + tor_addr_copy(dst_addr, &addr); + lines = lines->next; } return 0; } +/** Load one of the geoip files, <a>family</a> determining which + * one. <a>default_fname</a> is used if on Windows and + * <a>fname</a> equals "<default>". */ +static void +config_load_geoip_file_(sa_family_t family, + const char *fname, + const char *default_fname) +{ +#ifdef _WIN32 + char *free_fname = NULL; /* Used to hold any temporary-allocated value */ + /* XXXX Don't use this "<default>" junk; make our filename options + * understand prefixes somehow. -NM */ + if (!strcmp(fname, "<default>")) { + const char *conf_root = get_windows_conf_root(); + tor_asprintf(&free_fname, "%s\\%s", conf_root, default_fname); + fname = free_fname; + } + geoip_load_file(family, fname); + tor_free(free_fname); +#else + (void)default_fname; + geoip_load_file(family, fname); +#endif +} + +/** Load geoip files for IPv4 and IPv6 if <a>options</a> and + * <a>old_options</a> indicate we should. */ +static void +config_maybe_load_geoip_files_(const or_options_t *options, + const or_options_t *old_options) +{ + /* XXXX024 Reload GeoIPFile on SIGHUP. -NM */ + + if (options->GeoIPFile && + ((!old_options || !opt_streq(old_options->GeoIPFile, + options->GeoIPFile)) + || !geoip_is_loaded(AF_INET))) + config_load_geoip_file_(AF_INET, options->GeoIPFile, "geoip"); + if (options->GeoIPv6File && + ((!old_options || !opt_streq(old_options->GeoIPv6File, + options->GeoIPv6File)) + || !geoip_is_loaded(AF_INET6))) + config_load_geoip_file_(AF_INET6, options->GeoIPv6File, "geoip6"); +} + diff --git a/src/or/config.h b/src/or/config.h index dd76edcf1..336685d07 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -9,8 +9,8 @@ * \brief Header file for config.c. **/ -#ifndef _TOR_CONFIG_H -#define _TOR_CONFIG_H +#ifndef TOR_CONFIG_H +#define TOR_CONFIG_H const char *get_dirportfrontpage(void); const or_options_t *get_options(void); @@ -23,11 +23,9 @@ const char *escaped_safe_str_client(const char *address); const char *escaped_safe_str(const char *address); const char *get_version(void); const char *get_short_version(void); - -int config_get_lines(const char *string, config_line_t **result, int extended); -void config_free_lines(config_line_t *front); setopt_err_t options_trial_assign(config_line_t *list, int use_defaults, int clear_first, char **msg); + int resolve_my_address(int warn_severity, const or_options_t *options, uint32_t *addr, char **hostname_out); int is_local_addr(const tor_addr_t *addr); @@ -61,10 +59,6 @@ char *options_get_datadir_fname2_suffix(const or_options_t *options, int get_num_cpus(const or_options_t *options); -or_state_t *get_or_state(void); -int did_last_state_file_write_fail(void); -int or_state_save(time_t now); - const smartlist_t *get_configured_ports(void); int get_first_advertised_port_by_type_af(int listener_type, int address_family); @@ -78,9 +72,7 @@ char *get_first_listener_addrport_string(int listener_type); int options_need_geoip_info(const or_options_t *options, const char **reason_out); -void save_transport_to_state(const char *transport_name, - const tor_addr_t *addr, uint16_t port); -char *get_stored_bindaddr_for_server_transport(const char *transport); +smartlist_t *get_list_of_ports_to_forward(void); int getinfo_helper_config(control_connection_t *conn, const char *question, char **answer, @@ -90,6 +82,8 @@ const char *tor_get_digests(void); uint32_t get_effective_bwrate(const or_options_t *options); uint32_t get_effective_bwburst(const or_options_t *options); +char *get_transport_bindaddr_from_config(const char *transport); + #ifdef CONFIG_PRIVATE /* Used only by config.c and test.c */ or_options_t *options_new(void); diff --git a/src/or/confparse.c b/src/or/confparse.c new file mode 100644 index 000000000..67cf43fe8 --- /dev/null +++ b/src/or/confparse.c @@ -0,0 +1,1226 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "confparse.h" +#include "routerset.h" + +static uint64_t config_parse_memunit(const char *s, int *ok); +static int config_parse_msec_interval(const char *s, int *ok); +static int config_parse_interval(const char *s, int *ok); +static void config_reset(const config_format_t *fmt, void *options, + const config_var_t *var, int use_defaults); + +/** Allocate an empty configuration object of a given format type. */ +void * +config_new(const config_format_t *fmt) +{ + void *opts = tor_malloc_zero(fmt->size); + *(uint32_t*)STRUCT_VAR_P(opts, fmt->magic_offset) = fmt->magic; + CONFIG_CHECK(fmt, opts); + return opts; +} + +/* + * Functions to parse config options + */ + +/** If <b>option</b> is an official abbreviation for a longer option, + * return the longer option. Otherwise return <b>option</b>. + * If <b>command_line</b> is set, apply all abbreviations. Otherwise, only + * apply abbreviations that work for the config file and the command line. + * If <b>warn_obsolete</b> is set, warn about deprecated names. */ +const char * +config_expand_abbrev(const config_format_t *fmt, const char *option, + int command_line, int warn_obsolete) +{ + int i; + if (! fmt->abbrevs) + return option; + for (i=0; fmt->abbrevs[i].abbreviated; ++i) { + /* Abbreviations are case insensitive. */ + if (!strcasecmp(option,fmt->abbrevs[i].abbreviated) && + (command_line || !fmt->abbrevs[i].commandline_only)) { + if (warn_obsolete && fmt->abbrevs[i].warn) { + log_warn(LD_CONFIG, + "The configuration option '%s' is deprecated; " + "use '%s' instead.", + fmt->abbrevs[i].abbreviated, + fmt->abbrevs[i].full); + } + /* Keep going through the list in case we want to rewrite it more. + * (We could imagine recursing here, but I don't want to get the + * user into an infinite loop if we craft our list wrong.) */ + option = fmt->abbrevs[i].full; + } + } + return option; +} + +/** Helper: allocate a new configuration option mapping 'key' to 'val', + * append it to *<b>lst</b>. */ +void +config_line_append(config_line_t **lst, + const char *key, + const char *val) +{ + config_line_t *newline; + + newline = tor_malloc_zero(sizeof(config_line_t)); + newline->key = tor_strdup(key); + newline->value = tor_strdup(val); + newline->next = NULL; + while (*lst) + lst = &((*lst)->next); + + (*lst) = newline; +} + +/** Helper: parse the config string and strdup into key/value + * strings. Set *result to the list, or NULL if parsing the string + * failed. Return 0 on success, -1 on failure. Warn and ignore any + * misformatted lines. + * + * If <b>extended</b> is set, then treat keys beginning with / and with + as + * indicating "clear" and "append" respectively. */ +int +config_get_lines(const char *string, config_line_t **result, int extended) +{ + config_line_t *list = NULL, **next; + char *k, *v; + + next = &list; + do { + k = v = NULL; + string = parse_config_line_from_str(string, &k, &v); + if (!string) { + config_free_lines(list); + tor_free(k); + tor_free(v); + return -1; + } + if (k && v) { + unsigned command = CONFIG_LINE_NORMAL; + if (extended) { + if (k[0] == '+') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + command = CONFIG_LINE_APPEND; + } else if (k[0] == '/') { + char *k_new = tor_strdup(k+1); + tor_free(k); + k = k_new; + tor_free(v); + v = tor_strdup(""); + command = CONFIG_LINE_CLEAR; + } + } + /* This list can get long, so we keep a pointer to the end of it + * rather than using config_line_append over and over and getting + * n^2 performance. */ + *next = tor_malloc_zero(sizeof(config_line_t)); + (*next)->key = k; + (*next)->value = v; + (*next)->next = NULL; + (*next)->command = command; + next = &((*next)->next); + } else { + tor_free(k); + tor_free(v); + } + } while (*string); + + *result = list; + return 0; +} + +/** + * Free all the configuration lines on the linked list <b>front</b>. + */ +void +config_free_lines(config_line_t *front) +{ + config_line_t *tmp; + + while (front) { + tmp = front; + front = tmp->next; + + tor_free(tmp->key); + tor_free(tmp->value); + tor_free(tmp); + } +} + +/** As config_find_option, but return a non-const pointer. */ +config_var_t * +config_find_option_mutable(config_format_t *fmt, const char *key) +{ + int i; + size_t keylen = strlen(key); + if (!keylen) + return NULL; /* if they say "--" on the command line, it's not an option */ + /* First, check for an exact (case-insensitive) match */ + for (i=0; fmt->vars[i].name; ++i) { + if (!strcasecmp(key, fmt->vars[i].name)) { + return &fmt->vars[i]; + } + } + /* If none, check for an abbreviated match */ + for (i=0; fmt->vars[i].name; ++i) { + if (!strncasecmp(key, fmt->vars[i].name, keylen)) { + log_warn(LD_CONFIG, "The abbreviation '%s' is deprecated. " + "Please use '%s' instead", + key, fmt->vars[i].name); + return &fmt->vars[i]; + } + } + /* Okay, unrecognized option */ + return NULL; +} + +/** If <b>key</b> is a configuration option, return the corresponding const + * config_var_t. Otherwise, if <b>key</b> is a non-standard abbreviation, + * warn, and return the corresponding const config_var_t. Otherwise return + * NULL. + */ +const config_var_t * +config_find_option(const config_format_t *fmt, const char *key) +{ + return config_find_option_mutable((config_format_t*)fmt, key); +} + +/** Return the number of option entries in <b>fmt</b>. */ +static int +config_count_options(const config_format_t *fmt) +{ + int i; + for (i=0; fmt->vars[i].name; ++i) + ; + return i; +} + +/* + * Functions to assign config options. + */ + +/** <b>c</b>-\>key is known to be a real key. Update <b>options</b> + * with <b>c</b>-\>value and return 0, or return -1 if bad value. + * + * Called from config_assign_line() and option_reset(). + */ +static int +config_assign_value(const config_format_t *fmt, void *options, + config_line_t *c, char **msg) +{ + int i, ok; + const config_var_t *var; + void *lvalue; + + CONFIG_CHECK(fmt, options); + + var = config_find_option(fmt, c->key); + tor_assert(var); + + lvalue = STRUCT_VAR_P(options, var->var_offset); + + switch (var->type) { + + case CONFIG_TYPE_PORT: + if (!strcasecmp(c->value, "auto")) { + *(int *)lvalue = CFG_AUTO_PORT; + break; + } + /* fall through */ + case CONFIG_TYPE_INT: + case CONFIG_TYPE_UINT: + i = (int)tor_parse_long(c->value, 10, + var->type==CONFIG_TYPE_INT ? INT_MIN : 0, + var->type==CONFIG_TYPE_PORT ? 65535 : INT_MAX, + &ok, NULL); + if (!ok) { + tor_asprintf(msg, + "Int keyword '%s %s' is malformed or out of bounds.", + c->key, c->value); + return -1; + } + *(int *)lvalue = i; + break; + + case CONFIG_TYPE_INTERVAL: { + i = config_parse_interval(c->value, &ok); + if (!ok) { + tor_asprintf(msg, + "Interval '%s %s' is malformed or out of bounds.", + c->key, c->value); + return -1; + } + *(int *)lvalue = i; + break; + } + + case CONFIG_TYPE_MSEC_INTERVAL: { + i = config_parse_msec_interval(c->value, &ok); + if (!ok) { + tor_asprintf(msg, + "Msec interval '%s %s' is malformed or out of bounds.", + c->key, c->value); + return -1; + } + *(int *)lvalue = i; + break; + } + + case CONFIG_TYPE_MEMUNIT: { + uint64_t u64 = config_parse_memunit(c->value, &ok); + if (!ok) { + tor_asprintf(msg, + "Value '%s %s' is malformed or out of bounds.", + c->key, c->value); + return -1; + } + *(uint64_t *)lvalue = u64; + break; + } + + case CONFIG_TYPE_BOOL: + i = (int)tor_parse_long(c->value, 10, 0, 1, &ok, NULL); + if (!ok) { + tor_asprintf(msg, + "Boolean '%s %s' expects 0 or 1.", + c->key, c->value); + return -1; + } + *(int *)lvalue = i; + break; + + case CONFIG_TYPE_AUTOBOOL: + if (!strcmp(c->value, "auto")) + *(int *)lvalue = -1; + else if (!strcmp(c->value, "0")) + *(int *)lvalue = 0; + else if (!strcmp(c->value, "1")) + *(int *)lvalue = 1; + else { + tor_asprintf(msg, "Boolean '%s %s' expects 0, 1, or 'auto'.", + c->key, c->value); + return -1; + } + break; + + case CONFIG_TYPE_STRING: + case CONFIG_TYPE_FILENAME: + tor_free(*(char **)lvalue); + *(char **)lvalue = tor_strdup(c->value); + break; + + case CONFIG_TYPE_DOUBLE: + *(double *)lvalue = atof(c->value); + break; + + case CONFIG_TYPE_ISOTIME: + if (parse_iso_time(c->value, (time_t *)lvalue)) { + tor_asprintf(msg, + "Invalid time '%s' for keyword '%s'", c->value, c->key); + return -1; + } + break; + + case CONFIG_TYPE_ROUTERSET: + if (*(routerset_t**)lvalue) { + routerset_free(*(routerset_t**)lvalue); + } + *(routerset_t**)lvalue = routerset_new(); + if (routerset_parse(*(routerset_t**)lvalue, c->value, c->key)<0) { + tor_asprintf(msg, "Invalid exit list '%s' for option '%s'", + c->value, c->key); + return -1; + } + break; + + case CONFIG_TYPE_CSV: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t**)lvalue, char *, cp, tor_free(cp)); + smartlist_clear(*(smartlist_t**)lvalue); + } else { + *(smartlist_t**)lvalue = smartlist_new(); + } + + smartlist_split_string(*(smartlist_t**)lvalue, c->value, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + break; + + case CONFIG_TYPE_LINELIST: + case CONFIG_TYPE_LINELIST_S: + { + config_line_t *lastval = *(config_line_t**)lvalue; + if (lastval && lastval->fragile) { + if (c->command != CONFIG_LINE_APPEND) { + config_free_lines(lastval); + *(config_line_t**)lvalue = NULL; + } else { + lastval->fragile = 0; + } + } + + config_line_append((config_line_t**)lvalue, c->key, c->value); + } + break; + case CONFIG_TYPE_OBSOLETE: + log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key); + break; + case CONFIG_TYPE_LINELIST_V: + tor_asprintf(msg, + "You may not provide a value for virtual option '%s'", c->key); + return -1; + default: + tor_assert(0); + break; + } + return 0; +} + +/** Mark every linelist in <b>options</b> "fragile", so that fresh assignments + * to it will replace old ones. */ +static void +config_mark_lists_fragile(const config_format_t *fmt, void *options) +{ + int i; + tor_assert(fmt); + tor_assert(options); + + for (i = 0; fmt->vars[i].name; ++i) { + const config_var_t *var = &fmt->vars[i]; + config_line_t *list; + if (var->type != CONFIG_TYPE_LINELIST && + var->type != CONFIG_TYPE_LINELIST_V) + continue; + + list = *(config_line_t **)STRUCT_VAR_P(options, var->var_offset); + if (list) + list->fragile = 1; + } +} + +/** If <b>c</b> is a syntactically valid configuration line, update + * <b>options</b> with its value and return 0. Otherwise return -1 for bad + * key, -2 for bad value. + * + * If <b>clear_first</b> is set, clear the value first. Then if + * <b>use_defaults</b> is set, set the value to the default. + * + * Called from config_assign(). + */ +static int +config_assign_line(const config_format_t *fmt, void *options, + config_line_t *c, int use_defaults, + int clear_first, bitarray_t *options_seen, char **msg) +{ + const config_var_t *var; + + CONFIG_CHECK(fmt, options); + + var = config_find_option(fmt, c->key); + if (!var) { + if (fmt->extra) { + void *lvalue = STRUCT_VAR_P(options, fmt->extra->var_offset); + log_info(LD_CONFIG, + "Found unrecognized option '%s'; saving it.", c->key); + config_line_append((config_line_t**)lvalue, c->key, c->value); + return 0; + } else { + tor_asprintf(msg, + "Unknown option '%s'. Failing.", c->key); + return -1; + } + } + + /* Put keyword into canonical case. */ + if (strcmp(var->name, c->key)) { + tor_free(c->key); + c->key = tor_strdup(var->name); + } + + if (!strlen(c->value)) { + /* reset or clear it, then return */ + if (!clear_first) { + if ((var->type == CONFIG_TYPE_LINELIST || + var->type == CONFIG_TYPE_LINELIST_S) && + c->command != CONFIG_LINE_CLEAR) { + /* We got an empty linelist from the torrc or command line. + As a special case, call this an error. Warn and ignore. */ + log_warn(LD_CONFIG, + "Linelist option '%s' has no value. Skipping.", c->key); + } else { /* not already cleared */ + config_reset(fmt, options, var, use_defaults); + } + } + return 0; + } else if (c->command == CONFIG_LINE_CLEAR && !clear_first) { + config_reset(fmt, options, var, use_defaults); + } + + if (options_seen && (var->type != CONFIG_TYPE_LINELIST && + var->type != CONFIG_TYPE_LINELIST_S)) { + /* We're tracking which options we've seen, and this option is not + * supposed to occur more than once. */ + int var_index = (int)(var - fmt->vars); + if (bitarray_is_set(options_seen, var_index)) { + log_warn(LD_CONFIG, "Option '%s' used more than once; all but the last " + "value will be ignored.", var->name); + } + bitarray_set(options_seen, var_index); + } + + if (config_assign_value(fmt, options, c, msg) < 0) + return -2; + return 0; +} + +/** Restore the option named <b>key</b> in options to its default value. + * Called from config_assign(). */ +static void +config_reset_line(const config_format_t *fmt, void *options, + const char *key, int use_defaults) +{ + const config_var_t *var; + + CONFIG_CHECK(fmt, options); + + var = config_find_option(fmt, key); + if (!var) + return; /* give error on next pass. */ + + config_reset(fmt, options, var, use_defaults); +} + +/** Return true iff value needs to be quoted and escaped to be used in + * a configuration file. */ +static int +config_value_needs_escape(const char *value) +{ + if (*value == '\"') + return 1; + while (*value) { + switch (*value) + { + case '\r': + case '\n': + case '#': + /* Note: quotes and backspaces need special handling when we are using + * quotes, not otherwise, so they don't trigger escaping on their + * own. */ + return 1; + default: + if (!TOR_ISPRINT(*value)) + return 1; + } + ++value; + } + return 0; +} + +/** Return a newly allocated deep copy of the lines in <b>inp</b>. */ +config_line_t * +config_lines_dup(const config_line_t *inp) +{ + config_line_t *result = NULL; + config_line_t **next_out = &result; + while (inp) { + *next_out = tor_malloc_zero(sizeof(config_line_t)); + (*next_out)->key = tor_strdup(inp->key); + (*next_out)->value = tor_strdup(inp->value); + inp = inp->next; + next_out = &((*next_out)->next); + } + (*next_out) = NULL; + return result; +} + +/** Return newly allocated line or lines corresponding to <b>key</b> in the + * configuration <b>options</b>. If <b>escape_val</b> is true and a + * value needs to be quoted before it's put in a config file, quote and + * escape that value. Return NULL if no such key exists. */ +config_line_t * +config_get_assigned_option(const config_format_t *fmt, const void *options, + const char *key, int escape_val) +{ + const config_var_t *var; + const void *value; + config_line_t *result; + tor_assert(options && key); + + CONFIG_CHECK(fmt, options); + + var = config_find_option(fmt, key); + if (!var) { + log_warn(LD_CONFIG, "Unknown option '%s'. Failing.", key); + return NULL; + } + value = STRUCT_VAR_P(options, var->var_offset); + + result = tor_malloc_zero(sizeof(config_line_t)); + result->key = tor_strdup(var->name); + switch (var->type) + { + case CONFIG_TYPE_STRING: + case CONFIG_TYPE_FILENAME: + if (*(char**)value) { + result->value = tor_strdup(*(char**)value); + } else { + tor_free(result->key); + tor_free(result); + return NULL; + } + break; + case CONFIG_TYPE_ISOTIME: + if (*(time_t*)value) { + result->value = tor_malloc(ISO_TIME_LEN+1); + format_iso_time(result->value, *(time_t*)value); + } else { + tor_free(result->key); + tor_free(result); + } + escape_val = 0; /* Can't need escape. */ + break; + case CONFIG_TYPE_PORT: + if (*(int*)value == CFG_AUTO_PORT) { + result->value = tor_strdup("auto"); + escape_val = 0; + break; + } + /* fall through */ + case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: + case CONFIG_TYPE_UINT: + case CONFIG_TYPE_INT: + /* This means every or_options_t uint or bool element + * needs to be an int. Not, say, a uint16_t or char. */ + tor_asprintf(&result->value, "%d", *(int*)value); + escape_val = 0; /* Can't need escape. */ + break; + case CONFIG_TYPE_MEMUNIT: + tor_asprintf(&result->value, U64_FORMAT, + U64_PRINTF_ARG(*(uint64_t*)value)); + escape_val = 0; /* Can't need escape. */ + break; + case CONFIG_TYPE_DOUBLE: + tor_asprintf(&result->value, "%f", *(double*)value); + escape_val = 0; /* Can't need escape. */ + break; + + case CONFIG_TYPE_AUTOBOOL: + if (*(int*)value == -1) { + result->value = tor_strdup("auto"); + escape_val = 0; + break; + } + /* fall through */ + case CONFIG_TYPE_BOOL: + result->value = tor_strdup(*(int*)value ? "1" : "0"); + escape_val = 0; /* Can't need escape. */ + break; + case CONFIG_TYPE_ROUTERSET: + result->value = routerset_to_string(*(routerset_t**)value); + break; + case CONFIG_TYPE_CSV: + if (*(smartlist_t**)value) + result->value = + smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL); + else + result->value = tor_strdup(""); + break; + case CONFIG_TYPE_OBSOLETE: + log_fn(LOG_INFO, LD_CONFIG, + "You asked me for the value of an obsolete config option '%s'.", + key); + tor_free(result->key); + tor_free(result); + return NULL; + case CONFIG_TYPE_LINELIST_S: + log_warn(LD_CONFIG, + "Can't return context-sensitive '%s' on its own", key); + tor_free(result->key); + tor_free(result); + return NULL; + case CONFIG_TYPE_LINELIST: + case CONFIG_TYPE_LINELIST_V: + tor_free(result->key); + tor_free(result); + result = config_lines_dup(*(const config_line_t**)value); + break; + default: + tor_free(result->key); + tor_free(result); + log_warn(LD_BUG,"Unknown type %d for known key '%s'", + var->type, key); + return NULL; + } + + if (escape_val) { + config_line_t *line; + for (line = result; line; line = line->next) { + if (line->value && config_value_needs_escape(line->value)) { + char *newval = esc_for_log(line->value); + tor_free(line->value); + line->value = newval; + } + } + } + + return result; +} +/** Iterate through the linked list of requested options <b>list</b>. + * For each item, convert as appropriate and assign to <b>options</b>. + * If an item is unrecognized, set *msg and return -1 immediately, + * else return 0 for success. + * + * If <b>clear_first</b>, interpret config options as replacing (not + * extending) their previous values. If <b>clear_first</b> is set, + * then <b>use_defaults</b> to decide if you set to defaults after + * clearing, or make the value 0 or NULL. + * + * Here are the use cases: + * 1. A non-empty AllowInvalid line in your torrc. Appends to current + * if linelist, replaces current if csv. + * 2. An empty AllowInvalid line in your torrc. Should clear it. + * 3. "RESETCONF AllowInvalid" sets it to default. + * 4. "SETCONF AllowInvalid" makes it NULL. + * 5. "SETCONF AllowInvalid=foo" clears it and sets it to "foo". + * + * Use_defaults Clear_first + * 0 0 "append" + * 1 0 undefined, don't use + * 0 1 "set to null first" + * 1 1 "set to defaults first" + * Return 0 on success, -1 on bad key, -2 on bad value. + * + * As an additional special case, if a LINELIST config option has + * no value and clear_first is 0, then warn and ignore it. + */ + +/* +There are three call cases for config_assign() currently. + +Case one: Torrc entry +options_init_from_torrc() calls config_assign(0, 0) + calls config_assign_line(0, 0). + if value is empty, calls config_reset(0) and returns. + calls config_assign_value(), appends. + +Case two: setconf +options_trial_assign() calls config_assign(0, 1) + calls config_reset_line(0) + calls config_reset(0) + calls option_clear(). + calls config_assign_line(0, 1). + if value is empty, returns. + calls config_assign_value(), appends. + +Case three: resetconf +options_trial_assign() calls config_assign(1, 1) + calls config_reset_line(1) + calls config_reset(1) + calls option_clear(). + calls config_assign_value(default) + calls config_assign_line(1, 1). + returns. +*/ +int +config_assign(const config_format_t *fmt, void *options, config_line_t *list, + int use_defaults, int clear_first, char **msg) +{ + config_line_t *p; + bitarray_t *options_seen; + const int n_options = config_count_options(fmt); + + CONFIG_CHECK(fmt, options); + + /* pass 1: normalize keys */ + for (p = list; p; p = p->next) { + const char *full = config_expand_abbrev(fmt, p->key, 0, 1); + if (strcmp(full,p->key)) { + tor_free(p->key); + p->key = tor_strdup(full); + } + } + + /* pass 2: if we're reading from a resetting source, clear all + * mentioned config options, and maybe set to their defaults. */ + if (clear_first) { + for (p = list; p; p = p->next) + config_reset_line(fmt, options, p->key, use_defaults); + } + + options_seen = bitarray_init_zero(n_options); + /* pass 3: assign. */ + while (list) { + int r; + if ((r=config_assign_line(fmt, options, list, use_defaults, + clear_first, options_seen, msg))) { + bitarray_free(options_seen); + return r; + } + list = list->next; + } + bitarray_free(options_seen); + + /** Now we're done assigning a group of options to the configuration. + * Subsequent group assignments should _replace_ linelists, not extend + * them. */ + config_mark_lists_fragile(fmt, options); + + return 0; +} + +/** Reset config option <b>var</b> to 0, 0.0, NULL, or the equivalent. + * Called from config_reset() and config_free(). */ +static void +config_clear(const config_format_t *fmt, void *options, + const config_var_t *var) +{ + void *lvalue = STRUCT_VAR_P(options, var->var_offset); + (void)fmt; /* unused */ + switch (var->type) { + case CONFIG_TYPE_STRING: + case CONFIG_TYPE_FILENAME: + tor_free(*(char**)lvalue); + break; + case CONFIG_TYPE_DOUBLE: + *(double*)lvalue = 0.0; + break; + case CONFIG_TYPE_ISOTIME: + *(time_t*)lvalue = 0; + break; + case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: + case CONFIG_TYPE_UINT: + case CONFIG_TYPE_INT: + case CONFIG_TYPE_PORT: + case CONFIG_TYPE_BOOL: + *(int*)lvalue = 0; + break; + case CONFIG_TYPE_AUTOBOOL: + *(int*)lvalue = -1; + break; + case CONFIG_TYPE_MEMUNIT: + *(uint64_t*)lvalue = 0; + break; + case CONFIG_TYPE_ROUTERSET: + if (*(routerset_t**)lvalue) { + routerset_free(*(routerset_t**)lvalue); + *(routerset_t**)lvalue = NULL; + } + break; + case CONFIG_TYPE_CSV: + if (*(smartlist_t**)lvalue) { + SMARTLIST_FOREACH(*(smartlist_t **)lvalue, char *, cp, tor_free(cp)); + smartlist_free(*(smartlist_t **)lvalue); + *(smartlist_t **)lvalue = NULL; + } + break; + case CONFIG_TYPE_LINELIST: + case CONFIG_TYPE_LINELIST_S: + config_free_lines(*(config_line_t **)lvalue); + *(config_line_t **)lvalue = NULL; + break; + case CONFIG_TYPE_LINELIST_V: + /* handled by linelist_s. */ + break; + case CONFIG_TYPE_OBSOLETE: + break; + } +} + +/** Clear the option indexed by <b>var</b> in <b>options</b>. Then if + * <b>use_defaults</b>, set it to its default value. + * Called by config_init() and option_reset_line() and option_assign_line(). */ +static void +config_reset(const config_format_t *fmt, void *options, + const config_var_t *var, int use_defaults) +{ + config_line_t *c; + char *msg = NULL; + CONFIG_CHECK(fmt, options); + config_clear(fmt, options, var); /* clear it first */ + if (!use_defaults) + return; /* all done */ + if (var->initvalue) { + c = tor_malloc_zero(sizeof(config_line_t)); + c->key = tor_strdup(var->name); + c->value = tor_strdup(var->initvalue); + if (config_assign_value(fmt, options, c, &msg) < 0) { + log_warn(LD_BUG, "Failed to assign default: %s", msg); + tor_free(msg); /* if this happens it's a bug */ + } + config_free_lines(c); + } +} + +/** Release storage held by <b>options</b>. */ +void +config_free(const config_format_t *fmt, void *options) +{ + int i; + + if (!options) + return; + + tor_assert(fmt); + + for (i=0; fmt->vars[i].name; ++i) + config_clear(fmt, options, &(fmt->vars[i])); + if (fmt->extra) { + config_line_t **linep = STRUCT_VAR_P(options, fmt->extra->var_offset); + config_free_lines(*linep); + *linep = NULL; + } + tor_free(options); +} + +/** Return true iff a and b contain identical keys and values in identical + * order. */ +int +config_lines_eq(config_line_t *a, config_line_t *b) +{ + while (a && b) { + if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) + return 0; + a = a->next; + b = b->next; + } + if (a || b) + return 0; + return 1; +} + +/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */ +int +config_count_key(const config_line_t *a, const char *key) +{ + int n = 0; + while (a) { + if (!strcasecmp(a->key, key)) { + ++n; + } + a = a->next; + } + return n; +} + +/** Return true iff the option <b>name</b> has the same value in <b>o1</b> + * and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options. + */ +int +config_is_same(const config_format_t *fmt, + const void *o1, const void *o2, + const char *name) +{ + config_line_t *c1, *c2; + int r = 1; + CONFIG_CHECK(fmt, o1); + CONFIG_CHECK(fmt, o2); + + c1 = config_get_assigned_option(fmt, o1, name, 0); + c2 = config_get_assigned_option(fmt, o2, name, 0); + r = config_lines_eq(c1, c2); + config_free_lines(c1); + config_free_lines(c2); + return r; +} + +/** Copy storage held by <b>old</b> into a new or_options_t and return it. */ +void * +config_dup(const config_format_t *fmt, const void *old) +{ + void *newopts; + int i; + config_line_t *line; + + newopts = config_new(fmt); + for (i=0; fmt->vars[i].name; ++i) { + if (fmt->vars[i].type == CONFIG_TYPE_LINELIST_S) + continue; + if (fmt->vars[i].type == CONFIG_TYPE_OBSOLETE) + continue; + line = config_get_assigned_option(fmt, old, fmt->vars[i].name, 0); + if (line) { + char *msg = NULL; + if (config_assign(fmt, newopts, line, 0, 0, &msg) < 0) { + log_err(LD_BUG, "config_get_assigned_option() generated " + "something we couldn't config_assign(): %s", msg); + tor_free(msg); + tor_assert(0); + } + } + config_free_lines(line); + } + return newopts; +} +/** Set all vars in the configuration object <b>options</b> to their default + * values. */ +void +config_init(const config_format_t *fmt, void *options) +{ + int i; + const config_var_t *var; + CONFIG_CHECK(fmt, options); + + for (i=0; fmt->vars[i].name; ++i) { + var = &fmt->vars[i]; + if (!var->initvalue) + continue; /* defaults to NULL or 0 */ + config_reset(fmt, options, var, 1); + } +} + +/** Allocate and return a new string holding the written-out values of the vars + * in 'options'. If 'minimal', do not write out any default-valued vars. + * Else, if comment_defaults, write default values as comments. + */ +char * +config_dump(const config_format_t *fmt, const void *default_options, + const void *options, int minimal, + int comment_defaults) +{ + smartlist_t *elements; + const void *defaults = default_options; + void *defaults_tmp = NULL; + config_line_t *line, *assigned; + char *result; + int i; + char *msg = NULL; + + if (defaults == NULL) { + defaults = defaults_tmp = config_new(fmt); + config_init(fmt, defaults_tmp); + } + + /* XXX use a 1 here so we don't add a new log line while dumping */ + if (default_options == NULL) { + if (fmt->validate_fn(NULL, defaults_tmp, 1, &msg) < 0) { + log_err(LD_BUG, "Failed to validate default config."); + tor_free(msg); + tor_assert(0); + } + } + + elements = smartlist_new(); + for (i=0; fmt->vars[i].name; ++i) { + int comment_option = 0; + if (fmt->vars[i].type == CONFIG_TYPE_OBSOLETE || + fmt->vars[i].type == CONFIG_TYPE_LINELIST_S) + continue; + /* Don't save 'hidden' control variables. */ + if (!strcmpstart(fmt->vars[i].name, "__")) + continue; + if (minimal && config_is_same(fmt, options, defaults, fmt->vars[i].name)) + continue; + else if (comment_defaults && + config_is_same(fmt, options, defaults, fmt->vars[i].name)) + comment_option = 1; + + line = assigned = + config_get_assigned_option(fmt, options, fmt->vars[i].name, 1); + + for (; line; line = line->next) { + smartlist_add_asprintf(elements, "%s%s %s\n", + comment_option ? "# " : "", + line->key, line->value); + } + config_free_lines(assigned); + } + + if (fmt->extra) { + line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->var_offset); + for (; line; line = line->next) { + smartlist_add_asprintf(elements, "%s %s\n", line->key, line->value); + } + } + + result = smartlist_join_strings(elements, "", 0, NULL); + SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); + smartlist_free(elements); + if (defaults_tmp) + config_free(fmt, defaults_tmp); + return result; +} + +/** Mapping from a unit name to a multiplier for converting that unit into a + * base unit. Used by config_parse_unit. */ +struct unit_table_t { + const char *unit; /**< The name of the unit */ + uint64_t multiplier; /**< How many of the base unit appear in this unit */ +}; + +/** Table to map the names of memory units to the number of bytes they + * contain. */ +static struct unit_table_t memory_units[] = { + { "", 1 }, + { "b", 1<< 0 }, + { "byte", 1<< 0 }, + { "bytes", 1<< 0 }, + { "kb", 1<<10 }, + { "kbyte", 1<<10 }, + { "kbytes", 1<<10 }, + { "kilobyte", 1<<10 }, + { "kilobytes", 1<<10 }, + { "m", 1<<20 }, + { "mb", 1<<20 }, + { "mbyte", 1<<20 }, + { "mbytes", 1<<20 }, + { "megabyte", 1<<20 }, + { "megabytes", 1<<20 }, + { "gb", 1<<30 }, + { "gbyte", 1<<30 }, + { "gbytes", 1<<30 }, + { "gigabyte", 1<<30 }, + { "gigabytes", 1<<30 }, + { "tb", U64_LITERAL(1)<<40 }, + { "terabyte", U64_LITERAL(1)<<40 }, + { "terabytes", U64_LITERAL(1)<<40 }, + { NULL, 0 }, +}; + +/** Table to map the names of time units to the number of seconds they + * contain. */ +static struct unit_table_t time_units[] = { + { "", 1 }, + { "second", 1 }, + { "seconds", 1 }, + { "minute", 60 }, + { "minutes", 60 }, + { "hour", 60*60 }, + { "hours", 60*60 }, + { "day", 24*60*60 }, + { "days", 24*60*60 }, + { "week", 7*24*60*60 }, + { "weeks", 7*24*60*60 }, + { NULL, 0 }, +}; + +/** Table to map the names of time units to the number of milliseconds + * they contain. */ +static struct unit_table_t time_msec_units[] = { + { "", 1 }, + { "msec", 1 }, + { "millisecond", 1 }, + { "milliseconds", 1 }, + { "second", 1000 }, + { "seconds", 1000 }, + { "minute", 60*1000 }, + { "minutes", 60*1000 }, + { "hour", 60*60*1000 }, + { "hours", 60*60*1000 }, + { "day", 24*60*60*1000 }, + { "days", 24*60*60*1000 }, + { "week", 7*24*60*60*1000 }, + { "weeks", 7*24*60*60*1000 }, + { NULL, 0 }, +}; + +/** Parse a string <b>val</b> containing a number, zero or more + * spaces, and an optional unit string. If the unit appears in the + * table <b>u</b>, then multiply the number by the unit multiplier. + * On success, set *<b>ok</b> to 1 and return this product. + * Otherwise, set *<b>ok</b> to 0. + */ +static uint64_t +config_parse_units(const char *val, struct unit_table_t *u, int *ok) +{ + uint64_t v = 0; + double d = 0; + int use_float = 0; + char *cp; + + tor_assert(ok); + + v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp); + if (!*ok || (cp && *cp == '.')) { + d = tor_parse_double(val, 0, UINT64_MAX, ok, &cp); + if (!*ok) + goto done; + use_float = 1; + } + + if (!cp) { + *ok = 1; + v = use_float ? DBL_TO_U64(d) : v; + goto done; + } + + cp = (char*) eat_whitespace(cp); + + for ( ;u->unit;++u) { + if (!strcasecmp(u->unit, cp)) { + if (use_float) + v = u->multiplier * d; + else + v *= u->multiplier; + *ok = 1; + goto done; + } + } + log_warn(LD_CONFIG, "Unknown unit '%s'.", cp); + *ok = 0; + done: + + if (*ok) + return v; + else + return 0; +} + +/** Parse a string in the format "number unit", where unit is a unit of + * information (byte, KB, M, etc). On success, set *<b>ok</b> to true + * and return the number of bytes specified. Otherwise, set + * *<b>ok</b> to false and return 0. */ +static uint64_t +config_parse_memunit(const char *s, int *ok) +{ + uint64_t u = config_parse_units(s, memory_units, ok); + return u; +} + +/** Parse a string in the format "number unit", where unit is a unit of + * time in milliseconds. On success, set *<b>ok</b> to true and return + * the number of milliseconds in the provided interval. Otherwise, set + * *<b>ok</b> to 0 and return -1. */ +static int +config_parse_msec_interval(const char *s, int *ok) +{ + uint64_t r; + r = config_parse_units(s, time_msec_units, ok); + if (!ok) + return -1; + if (r > INT_MAX) { + log_warn(LD_CONFIG, "Msec interval '%s' is too long", s); + *ok = 0; + return -1; + } + return (int)r; +} + +/** Parse a string in the format "number unit", where unit is a unit of time. + * On success, set *<b>ok</b> to true and return the number of seconds in + * the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1. + */ +static int +config_parse_interval(const char *s, int *ok) +{ + uint64_t r; + r = config_parse_units(s, time_units, ok); + if (!ok) + return -1; + if (r > INT_MAX) { + log_warn(LD_CONFIG, "Interval '%s' is too long", s); + *ok = 0; + return -1; + } + return (int)r; +} + diff --git a/src/or/confparse.h b/src/or/confparse.h new file mode 100644 index 000000000..20616be92 --- /dev/null +++ b/src/or/confparse.h @@ -0,0 +1,132 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_CONFPARSE_H +#define TOR_CONFPARSE_H + +/** Enumeration of types which option values can take */ +typedef enum config_type_t { + CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */ + CONFIG_TYPE_FILENAME, /**< A filename: some prefixes get expanded. */ + CONFIG_TYPE_UINT, /**< A non-negative integer less than MAX_INT */ + CONFIG_TYPE_INT, /**< Any integer. */ + CONFIG_TYPE_PORT, /**< A port from 1...65535, 0 for "not set", or + * "auto". */ + CONFIG_TYPE_INTERVAL, /**< A number of seconds, with optional units*/ + CONFIG_TYPE_MSEC_INTERVAL,/**< A number of milliseconds, with optional + * units */ + CONFIG_TYPE_MEMUNIT, /**< A number of bytes, with optional units*/ + CONFIG_TYPE_DOUBLE, /**< A floating-point value */ + CONFIG_TYPE_BOOL, /**< A boolean value, expressed as 0 or 1. */ + CONFIG_TYPE_AUTOBOOL, /**< A boolean+auto value, expressed 0 for false, + * 1 for true, and -1 for auto */ + CONFIG_TYPE_ISOTIME, /**< An ISO-formatted time relative to UTC. */ + CONFIG_TYPE_CSV, /**< A list of strings, separated by commas and + * optional whitespace. */ + CONFIG_TYPE_LINELIST, /**< Uninterpreted config lines */ + CONFIG_TYPE_LINELIST_S, /**< Uninterpreted, context-sensitive config lines, + * mixed with other keywords. */ + CONFIG_TYPE_LINELIST_V, /**< Catch-all "virtual" option to summarize + * context-sensitive config lines when fetching. + */ + CONFIG_TYPE_ROUTERSET, /**< A list of router names, addrs, and fps, + * parsed into a routerset_t. */ + CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */ +} config_type_t; + +/** An abbreviation for a configuration option allowed on the command line. */ +typedef struct config_abbrev_t { + const char *abbreviated; + const char *full; + int commandline_only; + int warn; +} config_abbrev_t; + +/* Handy macro for declaring "In the config file or on the command line, + * you can abbreviate <b>tok</b>s as <b>tok</b>". */ +#define PLURAL(tok) { #tok, #tok "s", 0, 0 } + +/** A variable allowed in the configuration file or on the command line. */ +typedef struct config_var_t { + const char *name; /**< The full keyword (case insensitive). */ + config_type_t type; /**< How to interpret the type and turn it into a + * value. */ + off_t var_offset; /**< Offset of the corresponding member of or_options_t. */ + const char *initvalue; /**< String (or null) describing initial value. */ +} config_var_t; + +/** Represents an English description of a configuration variable; used when + * generating configuration file comments. */ +typedef struct config_var_description_t { + const char *name; + const char *description; +} config_var_description_t; + +/** Type of a callback to validate whether a given configuration is + * well-formed and consistent. See options_trial_assign() for documentation + * of arguments. */ +typedef int (*validate_fn_t)(void*,void*,int,char**); + +/** Information on the keys, value types, key-to-struct-member mappings, + * variable descriptions, validation functions, and abbreviations for a + * configuration or storage format. */ +typedef struct { + size_t size; /**< Size of the struct that everything gets parsed into. */ + uint32_t magic; /**< Required 'magic value' to make sure we have a struct + * of the right type. */ + off_t magic_offset; /**< Offset of the magic value within the struct. */ + config_abbrev_t *abbrevs; /**< List of abbreviations that we expand when + * parsing this format. */ + config_var_t *vars; /**< List of variables we recognize, their default + * values, and where we stick them in the structure. */ + validate_fn_t validate_fn; /**< Function to validate config. */ + /** If present, extra is a LINELIST variable for unrecognized + * lines. Otherwise, unrecognized lines are an error. */ + config_var_t *extra; +} config_format_t; + +/** Macro: assert that <b>cfg</b> has the right magic field for format + * <b>fmt</b>. */ +#define CONFIG_CHECK(fmt, cfg) STMT_BEGIN \ + tor_assert(fmt && cfg); \ + tor_assert((fmt)->magic == \ + *(uint32_t*)STRUCT_VAR_P(cfg,fmt->magic_offset)); \ + STMT_END + +void *config_new(const config_format_t *fmt); +void config_line_append(config_line_t **lst, + const char *key, const char *val); +config_line_t *config_lines_dup(const config_line_t *inp); +void config_free(const config_format_t *fmt, void *options); +int config_lines_eq(config_line_t *a, config_line_t *b); +int config_count_key(const config_line_t *a, const char *key); +config_line_t *config_get_assigned_option(const config_format_t *fmt, + const void *options, const char *key, + int escape_val); +int config_is_same(const config_format_t *fmt, + const void *o1, const void *o2, + const char *name); +void config_init(const config_format_t *fmt, void *options); +void *config_dup(const config_format_t *fmt, const void *old); +char *config_dump(const config_format_t *fmt, const void *default_options, + const void *options, int minimal, + int comment_defaults); +int config_assign(const config_format_t *fmt, void *options, + config_line_t *list, + int use_defaults, int clear_first, char **msg); +config_var_t *config_find_option_mutable(config_format_t *fmt, + const char *key); +const config_var_t *config_find_option(const config_format_t *fmt, + const char *key); + +int config_get_lines(const char *string, config_line_t **result, int extended); +void config_free_lines(config_line_t *front); +const char *config_expand_abbrev(const config_format_t *fmt, + const char *option, + int command_line, int warn_obsolete); + +#endif + diff --git a/src/or/connection.c b/src/or/connection.c index eac9c4f32..42ff7e1a0 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -12,6 +12,13 @@ #include "or.h" #include "buffers.h" +/* + * Define this so we get channel internal functions, since we're implementing + * part of a subclass (channel_tls_t). + */ +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -25,6 +32,7 @@ #include "dirserv.h" #include "dns.h" #include "dnsserv.h" +#include "entrynodes.h" #include "geoip.h" #include "main.h" #include "policies.h" @@ -34,6 +42,7 @@ #include "rendcommon.h" #include "rephist.h" #include "router.h" +#include "transports.h" #include "routerparse.h" #ifdef USE_BUFFEREVENTS @@ -238,7 +247,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) { @@ -247,16 +265,15 @@ or_connection_new(int socket_family) connection_init(now, TO_CONN(or_conn), CONN_TYPE_OR, socket_family); or_conn->timestamp_last_added_nonpadding = time(NULL); - or_conn->next_circ_id = crypto_rand_int(1<<15); - - or_conn->active_circuit_pqueue = smartlist_new(); - or_conn->active_circuit_pqueue_last_recalibrated = cell_ewma_get_tick(); return or_conn; } /** 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) { @@ -264,6 +281,13 @@ entry_connection_new(int type, int socket_family) tor_assert(type == CONN_TYPE_AP); connection_init(time(NULL), ENTRY_TO_CONN(entry_conn), type, socket_family); entry_conn->socks_request = socks_request_new(); + /* If this is coming from a listener, we'll set it up based on the listener + * in a little while. Otherwise, we're doing this as a linked connection + * of some kind, and we should set it up here based on the socket family */ + if (socket_family == AF_INET) + entry_conn->ipv4_traffic_ok = 1; + else if (socket_family == AF_INET6) + entry_conn->ipv6_traffic_ok = 1; return entry_conn; } @@ -338,14 +362,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 @@ -414,7 +435,7 @@ connection_link_connections(connection_t *conn_a, connection_t *conn_b) * if <b>conn</b> is an OR or OP connection. */ static void -_connection_free(connection_t *conn) +connection_free_(connection_t *conn) { void *mem; size_t memlen; @@ -492,7 +513,6 @@ _connection_free(connection_t *conn) or_conn->tls = NULL; or_handshake_state_free(or_conn->handshake_state); or_conn->handshake_state = NULL; - smartlist_free(or_conn->active_circuit_pqueue); tor_free(or_conn->nickname); } if (conn->type == CONN_TYPE_AP) { @@ -592,7 +612,7 @@ connection_free(connection_t *conn) connection_control_closed(TO_CONTROL_CONN(conn)); } connection_unregister_events(conn); - _connection_free(conn); + connection_free_(conn); } /** @@ -668,7 +688,42 @@ connection_close_immediate(connection_t *conn) /** Mark <b>conn</b> to be closed next time we loop through * conn_close_if_marked() in main.c. */ void -_connection_mark_for_close(connection_t *conn, int line, const char *file) +connection_mark_for_close_(connection_t *conn, int line, const char *file) +{ + assert_connection_ok(conn,0); + tor_assert(line); + tor_assert(line < 1<<16); /* marked_for_close can only fit a uint16_t. */ + tor_assert(file); + + if (conn->type == CONN_TYPE_OR) { + /* + * An or_connection should have been closed through one of the channel- + * aware functions in connection_or.c. We'll assume this is an error + * close and do that, and log a bug warning. + */ + log_warn(LD_CHANNEL | LD_BUG, + "Something tried to close an or_connection_t without going " + "through channels at %s:%d", + file, line); + connection_or_close_for_error(TO_OR_CONN(conn), 0); + } else { + /* Pass it down to the real function */ + connection_mark_for_close_internal_(conn, line, file); + } +} + +/** Mark <b>conn</b> to be closed next time we loop through + * conn_close_if_marked() in main.c; the _internal version bypasses the + * CONN_TYPE_OR checks; this should be called when you either are sure that + * if this is an or_connection_t the controlling channel has been notified + * (e.g. with connection_or_notify_error()), or you actually are the + * connection_or_close_for_error() or connection_or_close_normally function. + * For all other cases, use connection_mark_and_flush() instead, which + * checks for or_connection_t properly, instead. See below. + */ +void +connection_mark_for_close_internal_(connection_t *conn, + int line, const char *file) { assert_connection_ok(conn,0); tor_assert(line); @@ -683,6 +738,17 @@ _connection_mark_for_close(connection_t *conn, int line, const char *file) return; } + if (conn->type == CONN_TYPE_OR) { + /* + * Bad news if this happens without telling the controlling channel; do + * this so we can find things that call this wrongly when the asserts hit. + */ + log_debug(LD_CHANNEL, + "Calling connection_mark_for_close_internal_() on an OR conn " + "at %s:%d", + file, line); + } + conn->marked_for_close = line; conn->marked_for_close_file = file; add_connection_to_closeable_list(conn); @@ -881,7 +947,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, static int global_next_session_group = SESSION_GROUP_FIRST_AUTO; tor_addr_t addr; - if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { + if (get_n_open_sockets() >= get_options()->ConnLimit_-1) { warn_too_many_conns(); return NULL; } @@ -894,8 +960,8 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_addr_from_sockaddr(&addr, listensockaddr, &usePort); - log_notice(LD_NET, "Opening %s on %s:%d", - conn_type_to_string(type), fmt_addr(&addr), usePort); + log_notice(LD_NET, "Opening %s on %s", + conn_type_to_string(type), fmt_addrport(&addr, usePort)); s = tor_open_socket(tor_addr_family(&addr), is_tcp ? SOCK_STREAM : SOCK_DGRAM, @@ -1056,6 +1122,19 @@ connection_listener_new(const struct sockaddr *listensockaddr, lis_conn->session_group = global_next_session_group--; } } + if (type == CONN_TYPE_AP_LISTENER) { + lis_conn->socks_ipv4_traffic = port_cfg->ipv4_traffic; + lis_conn->socks_ipv6_traffic = port_cfg->ipv6_traffic; + lis_conn->socks_prefer_ipv6 = port_cfg->prefer_ipv6; + } else { + lis_conn->socks_ipv4_traffic = 1; + lis_conn->socks_ipv6_traffic = 1; + } + lis_conn->cache_ipv4_answers = port_cfg->cache_ipv4_answers; + lis_conn->cache_ipv6_answers = port_cfg->cache_ipv6_answers; + lis_conn->use_cached_ipv4_answers = port_cfg->use_cached_ipv4_answers; + lis_conn->use_cached_ipv6_answers = port_cfg->use_cached_ipv6_answers; + lis_conn->prefer_ipv6_virtaddr = port_cfg->prefer_ipv6_virtaddr; if (connection_add(conn) < 0) { /* no space, forget it */ log_warn(LD_NET,"connection_add for listener failed. Giving up."); @@ -1089,7 +1168,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; @@ -1166,6 +1245,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) if (!SOCKET_OK(news)) { /* accept() error */ int e = tor_socket_errno(conn->s); if (ERRNO_IS_ACCEPT_EAGAIN(e)) { + log_notice(LD_APP, "he hung up before we could accept(). that's fine."); return 0; /* he hung up before we could accept(). that's fine. */ } else if (ERRNO_IS_ACCEPT_RESOURCE_LIMIT(e)) { warn_too_many_conns(); @@ -1177,7 +1257,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) connection_mark_for_close(conn); return -1; } - log_debug(LD_NET, + log_notice(LD_NET, "Connection accepted on socket %d (child of fd %d).", (int)news,(int)conn->s); @@ -1202,11 +1282,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 */ @@ -1215,7 +1290,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) if (socks_policy_permits_address(&addr) == 0) { log_notice(LD_APP, "Denying socks connection from untrusted address %s.", - fmt_addr(&addr)); + fmt_and_decorate_addr(&addr)); tor_close_socket(news); return 0; } @@ -1224,7 +1299,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) /* check dirpolicy to see if we should accept it */ if (dir_policy_permits_address(&addr) == 0) { log_notice(LD_DIRSERV,"Denying dir connection from address %s.", - fmt_addr(&addr)); + fmt_and_decorate_addr(&addr)); tor_close_socket(news); return 0; } @@ -1276,17 +1351,36 @@ static int connection_init_accepted_conn(connection_t *conn, const listener_connection_t *listener) { + int rv; + connection_start_reading(conn); switch (conn->type) { case CONN_TYPE_OR: control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0); - return connection_tls_start_handshake(TO_OR_CONN(conn), 1); + rv = connection_tls_start_handshake(TO_OR_CONN(conn), 1); + if (rv < 0) { + connection_or_close_for_error(TO_OR_CONN(conn), 0); + } + return rv; + break; case CONN_TYPE_AP: TO_ENTRY_CONN(conn)->isolation_flags = listener->isolation_flags; TO_ENTRY_CONN(conn)->session_group = listener->session_group; TO_ENTRY_CONN(conn)->nym_epoch = get_signewnym_epoch(); - TO_ENTRY_CONN(conn)->socks_request->listener_type = listener->_base.type; + TO_ENTRY_CONN(conn)->socks_request->listener_type = listener->base_.type; + TO_ENTRY_CONN(conn)->ipv4_traffic_ok = listener->socks_ipv4_traffic; + TO_ENTRY_CONN(conn)->ipv6_traffic_ok = listener->socks_ipv6_traffic; + TO_ENTRY_CONN(conn)->prefer_ipv6_traffic = listener->socks_prefer_ipv6; + TO_ENTRY_CONN(conn)->cache_ipv4_answers = listener->cache_ipv4_answers; + TO_ENTRY_CONN(conn)->cache_ipv6_answers = listener->cache_ipv6_answers; + TO_ENTRY_CONN(conn)->use_cached_ipv4_answers = + listener->use_cached_ipv4_answers; + TO_ENTRY_CONN(conn)->use_cached_ipv6_answers = + listener->use_cached_ipv6_answers; + TO_ENTRY_CONN(conn)->prefer_ipv6_virtaddr = + listener->prefer_ipv6_virtaddr; + switch (TO_CONN(listener)->type) { case CONN_TYPE_AP_LISTENER: conn->state = AP_CONN_STATE_SOCKS_WAIT; @@ -1333,7 +1427,7 @@ connection_connect(connection_t *conn, const char *address, const or_options_t *options = get_options(); int protocol_family; - if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { + if (get_n_open_sockets() >= get_options()->ConnLimit_-1) { warn_too_many_conns(); *socket_error = SOCK_ERRNO(ENOBUFS); return -1; @@ -1372,23 +1466,34 @@ connection_connect(connection_t *conn, const char *address, make_socket_reuseable(s); - if (options->OutboundBindAddress && !tor_addr_is_loopback(addr)) { - struct sockaddr_in ext_addr; - - memset(&ext_addr, 0, sizeof(ext_addr)); - ext_addr.sin_family = AF_INET; - ext_addr.sin_port = 0; - if (!tor_inet_aton(options->OutboundBindAddress, &ext_addr.sin_addr)) { - log_warn(LD_CONFIG,"Outbound bind address '%s' didn't parse. Ignoring.", - options->OutboundBindAddress); - } else { - if (bind(s, (struct sockaddr*)&ext_addr, - (socklen_t)sizeof(ext_addr)) < 0) { - *socket_error = tor_socket_errno(s); - log_warn(LD_NET,"Error binding network socket: %s", - tor_socket_strerror(*socket_error)); - tor_close_socket(s); - return -1; + if (!tor_addr_is_loopback(addr)) { + const tor_addr_t *ext_addr = NULL; + if (protocol_family == AF_INET && + !tor_addr_is_null(&options->OutboundBindAddressIPv4_)) + ext_addr = &options->OutboundBindAddressIPv4_; + else if (protocol_family == AF_INET6 && + !tor_addr_is_null(&options->OutboundBindAddressIPv6_)) + ext_addr = &options->OutboundBindAddressIPv6_; + if (ext_addr) { + struct sockaddr_storage ext_addr_sa; + socklen_t ext_addr_len = 0; + memset(&ext_addr_sa, 0, sizeof(ext_addr_sa)); + ext_addr_len = tor_addr_to_sockaddr(ext_addr, 0, + (struct sockaddr *) &ext_addr_sa, + sizeof(ext_addr_sa)); + if (ext_addr_len == 0) { + log_warn(LD_NET, + "Error converting OutboundBindAddress %s into sockaddr. " + "Ignoring.", fmt_and_decorate_addr(ext_addr)); + } else { + if (bind(s, (struct sockaddr *) &ext_addr_sa, ext_addr_len) < 0) { + *socket_error = tor_socket_errno(s); + log_warn(LD_NET,"Error binding network socket to %s: %s", + fmt_and_decorate_addr(ext_addr), + tor_socket_strerror(*socket_error)); + tor_close_socket(s); + return -1; + } } } } @@ -1424,7 +1529,7 @@ connection_connect(connection_t *conn, const char *address, /* it succeeded. we're connected. */ log_fn(inprogress?LOG_DEBUG:LOG_INFO, LD_NET, - "Connection to %s:%u %s (sock %d).", + "Connection to %s:%u %s (sock "TOR_SOCKET_T_FORMAT").", escaped_safe_str_client(address), port, inprogress?"in progress":"established", s); conn->s = s; @@ -1495,17 +1600,17 @@ connection_proxy_connect(connection_t *conn, int type) } if (base64_authenticator) { - const char *addr = fmt_addr(&conn->addr); - tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n" - "Host: %s:%d\r\n" + const char *addrport = fmt_addrport(&conn->addr, conn->port); + tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n" "Proxy-Authorization: Basic %s\r\n\r\n", - addr, conn->port, - addr, conn->port, + addrport, + addrport, base64_authenticator); tor_free(base64_authenticator); } else { - tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n", - fmt_addr(&conn->addr), conn->port); + tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.0\r\n\r\n", + fmt_addrport(&conn->addr, conn->port)); } connection_write_to_buf(buf, strlen(buf), conn); @@ -2033,9 +2138,9 @@ connection_mark_all_noncontrol_connections(void) /** Return 1 if we should apply rate limiting to <b>conn</b>, and 0 * otherwise. * Right now this just checks if it's an internal IP address or an - * internal connection. We also check if the connection uses pluggable - * transports, since we should then limit it even if it comes from an - * internal IP address. */ + * internal connection. We also should, but don't, check if the connection + * uses pluggable transports, since we should then limit it even if it + * comes from an internal IP address. */ static int connection_is_rate_limited(connection_t *conn) { @@ -2075,7 +2180,8 @@ static int connection_counts_as_relayed_traffic(connection_t *conn, time_t now) { if (conn->type == CONN_TYPE_OR && - TO_OR_CONN(conn)->client_used + CLIENT_IDLE_TIME_FOR_PRIORITY < now) + connection_or_client_used(TO_OR_CONN(conn)) + + CLIENT_IDLE_TIME_FOR_PRIORITY < now) return 1; if (conn->type == CONN_TYPE_DIR && DIR_CONN_IS_SERVER(conn)) return 1; @@ -2534,7 +2640,7 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) { tor_assert(conn); - if (conn->_base.state != OR_CONN_STATE_OPEN) + if (conn->base_.state != OR_CONN_STATE_OPEN) return 0; /* only open connections play the rate limiting game */ if (bucket >= conn->bandwidthburst) return 0; @@ -2672,11 +2778,14 @@ connection_handle_read_impl(connection_t *conn) before = buf_datalen(conn->inbuf); if (connection_read_to_buf(conn, &max_to_read, &socket_error) < 0) { /* There's a read error; kill the connection.*/ - if (conn->type == CONN_TYPE_OR && - conn->state == OR_CONN_STATE_CONNECTING) { - connection_or_connect_failed(TO_OR_CONN(conn), - errno_to_orconn_end_reason(socket_error), - tor_socket_strerror(socket_error)); + if (conn->type == CONN_TYPE_OR) { + connection_or_notify_error(TO_OR_CONN(conn), + socket_error != 0 ? + errno_to_orconn_end_reason(socket_error) : + END_OR_CONN_REASON_CONNRESET, + socket_error != 0 ? + tor_socket_strerror(socket_error) : + "(unknown, errno was 0)"); } if (CONN_IS_EDGE(conn)) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); @@ -2687,7 +2796,11 @@ connection_handle_read_impl(connection_t *conn) } } connection_close_immediate(conn); /* Don't flush; connection is dead. */ - connection_mark_for_close(conn); + /* + * This can bypass normal channel checking since we did + * connection_or_notify_error() above. + */ + connection_mark_for_close_internal(conn); return -1; } n_read += buf_datalen(conn->inbuf) - before; @@ -3198,12 +3311,16 @@ connection_handle_write_impl(connection_t *conn, int force) if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); if (conn->type == CONN_TYPE_OR) - connection_or_connect_failed(TO_OR_CONN(conn), - errno_to_orconn_end_reason(e), - tor_socket_strerror(e)); + connection_or_notify_error(TO_OR_CONN(conn), + errno_to_orconn_end_reason(e), + tor_socket_strerror(e)); connection_close_immediate(conn); - connection_mark_for_close(conn); + /* + * This can bypass normal channel checking since we did + * connection_or_notify_error() above. + */ + connection_mark_for_close_internal(conn); return -1; } else { return 0; /* no change, see if next time is better */ @@ -3225,8 +3342,16 @@ connection_handle_write_impl(connection_t *conn, int force) connection_stop_writing(conn); if (connection_tls_continue_handshake(or_conn) < 0) { /* Don't flush; connection is dead. */ + connection_or_notify_error(or_conn, + END_OR_CONN_REASON_MISC, + "TLS error in connection_tls_" + "continue_handshake()"); connection_close_immediate(conn); - connection_mark_for_close(conn); + /* + * This can bypass normal channel checking since we did + * connection_or_notify_error() above. + */ + connection_mark_for_close_internal(conn); return -1; } return 0; @@ -3238,21 +3363,29 @@ connection_handle_write_impl(connection_t *conn, int force) result = flush_buf_tls(or_conn->tls, conn->outbuf, max_to_write, &conn->outbuf_flushlen); - /* If we just flushed the last bytes, check if this tunneled dir - * request is done. */ + /* If we just flushed the last bytes, tell the channel on the + * or_conn to check if it needs to geoip_change_dirreq_state() */ /* XXXX move this to flushed_some or finished_flushing -NM */ - if (buf_datalen(conn->outbuf) == 0 && conn->dirreq_id) - geoip_change_dirreq_state(conn->dirreq_id, DIRREQ_TUNNELED, - DIRREQ_OR_CONN_BUFFER_FLUSHED); + if (buf_datalen(conn->outbuf) == 0 && or_conn->chan) + channel_notify_flushed(TLS_CHAN_TO_BASE(or_conn->chan)); switch (result) { CASE_TOR_TLS_ERROR_ANY: case TOR_TLS_CLOSE: - log_info(LD_NET,result!=TOR_TLS_CLOSE? + log_info(LD_NET, result != TOR_TLS_CLOSE ? "tls error. breaking.":"TLS connection closed on flush"); /* Don't flush; connection is dead. */ + connection_or_notify_error(or_conn, + END_OR_CONN_REASON_MISC, + result != TOR_TLS_CLOSE ? + "TLS error in during flush" : + "TLS closed during flush"); connection_close_immediate(conn); - connection_mark_for_close(conn); + /* + * This can bypass normal channel checking since we did + * connection_or_notify_error() above. + */ + connection_mark_for_close_internal(conn); return -1; case TOR_TLS_WANTWRITE: log_debug(LD_NET,"wanted write."); @@ -3309,8 +3442,20 @@ connection_handle_write_impl(connection_t *conn, int force) if (result > 0) { /* If we wrote any bytes from our buffer, then call the appropriate * functions. */ - if (connection_flushed_some(conn) < 0) - connection_mark_for_close(conn); + if (connection_flushed_some(conn) < 0) { + if (connection_speaks_cells(conn)) { + connection_or_notify_error(TO_OR_CONN(conn), + END_OR_CONN_REASON_MISC, + "Got error back from " + "connection_flushed_some()"); + } + + /* + * This can bypass normal channel checking since we did + * connection_or_notify_error() above. + */ + connection_mark_for_close_internal(conn); + } } if (!connection_wants_to_flush(conn)) { /* it's done flushing */ @@ -3360,13 +3505,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,13 +3513,12 @@ 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, +connection_write_to_buf_impl_(const char *string, size_t len, connection_t *conn, int zlib) { /* XXXX This function really needs to return -1 on failure. */ @@ -3446,7 +3583,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 +3592,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 +3612,6 @@ _connection_write_to_buf_impl(const char *string, size_t len, } return; } - if (extra) { - conn->outbuf_flushlen += extra; - connection_start_writing(conn); - } } } @@ -3960,9 +4085,9 @@ connection_reached_eof(connection_t *conn) void connection_dump_buffer_mem_stats(int severity) { - uint64_t used_by_type[_CONN_TYPE_MAX+1]; - uint64_t alloc_by_type[_CONN_TYPE_MAX+1]; - int n_conns_by_type[_CONN_TYPE_MAX+1]; + uint64_t used_by_type[CONN_TYPE_MAX_+1]; + uint64_t alloc_by_type[CONN_TYPE_MAX_+1]; + int n_conns_by_type[CONN_TYPE_MAX_+1]; uint64_t total_alloc = 0; uint64_t total_used = 0; int i; @@ -3984,7 +4109,7 @@ connection_dump_buffer_mem_stats(int severity) alloc_by_type[tp] += buf_allocation(c->outbuf); } } SMARTLIST_FOREACH_END(c); - for (i=0; i <= _CONN_TYPE_MAX; ++i) { + for (i=0; i <= CONN_TYPE_MAX_; ++i) { total_used += used_by_type[i]; total_alloc += alloc_by_type[i]; } @@ -3993,7 +4118,7 @@ connection_dump_buffer_mem_stats(int severity) "In buffers for %d connections: "U64_FORMAT" used/"U64_FORMAT" allocated", smartlist_len(conns), U64_PRINTF_ARG(total_used), U64_PRINTF_ARG(total_alloc)); - for (i=_CONN_TYPE_MIN; i <= _CONN_TYPE_MAX; ++i) { + for (i=CONN_TYPE_MIN_; i <= CONN_TYPE_MAX_; ++i) { if (!n_conns_by_type[i]) continue; log(severity, LD_GENERAL, @@ -4011,8 +4136,8 @@ assert_connection_ok(connection_t *conn, time_t now) { (void) now; /* XXXX unused. */ tor_assert(conn); - tor_assert(conn->type >= _CONN_TYPE_MIN); - tor_assert(conn->type <= _CONN_TYPE_MAX); + tor_assert(conn->type >= CONN_TYPE_MIN_); + tor_assert(conn->type <= CONN_TYPE_MAX_); #ifdef USE_BUFFEREVENTS if (conn->bufev) { @@ -4127,34 +4252,33 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn->state == LISTENER_STATE_READY); break; case CONN_TYPE_OR: - tor_assert(conn->state >= _OR_CONN_STATE_MIN); - tor_assert(conn->state <= _OR_CONN_STATE_MAX); - tor_assert(TO_OR_CONN(conn)->n_circuits >= 0); + tor_assert(conn->state >= OR_CONN_STATE_MIN_); + tor_assert(conn->state <= OR_CONN_STATE_MAX_); break; case CONN_TYPE_EXIT: - tor_assert(conn->state >= _EXIT_CONN_STATE_MIN); - tor_assert(conn->state <= _EXIT_CONN_STATE_MAX); - tor_assert(conn->purpose >= _EXIT_PURPOSE_MIN); - tor_assert(conn->purpose <= _EXIT_PURPOSE_MAX); + tor_assert(conn->state >= EXIT_CONN_STATE_MIN_); + tor_assert(conn->state <= EXIT_CONN_STATE_MAX_); + tor_assert(conn->purpose >= EXIT_PURPOSE_MIN_); + tor_assert(conn->purpose <= EXIT_PURPOSE_MAX_); break; case CONN_TYPE_AP: - tor_assert(conn->state >= _AP_CONN_STATE_MIN); - tor_assert(conn->state <= _AP_CONN_STATE_MAX); + tor_assert(conn->state >= AP_CONN_STATE_MIN_); + tor_assert(conn->state <= AP_CONN_STATE_MAX_); tor_assert(TO_ENTRY_CONN(conn)->socks_request); break; case CONN_TYPE_DIR: - tor_assert(conn->state >= _DIR_CONN_STATE_MIN); - tor_assert(conn->state <= _DIR_CONN_STATE_MAX); - tor_assert(conn->purpose >= _DIR_PURPOSE_MIN); - tor_assert(conn->purpose <= _DIR_PURPOSE_MAX); + tor_assert(conn->state >= DIR_CONN_STATE_MIN_); + tor_assert(conn->state <= DIR_CONN_STATE_MAX_); + tor_assert(conn->purpose >= DIR_PURPOSE_MIN_); + tor_assert(conn->purpose <= DIR_PURPOSE_MAX_); break; case CONN_TYPE_CPUWORKER: - tor_assert(conn->state >= _CPUWORKER_STATE_MIN); - tor_assert(conn->state <= _CPUWORKER_STATE_MAX); + tor_assert(conn->state >= CPUWORKER_STATE_MIN_); + tor_assert(conn->state <= CPUWORKER_STATE_MAX_); break; case CONN_TYPE_CONTROL: - tor_assert(conn->state >= _CONTROL_CONN_STATE_MIN); - tor_assert(conn->state <= _CONTROL_CONN_STATE_MAX); + tor_assert(conn->state >= CONTROL_CONN_STATE_MIN_); + tor_assert(conn->state <= CONTROL_CONN_STATE_MAX_); break; default: tor_assert(0); @@ -4238,10 +4362,10 @@ log_failed_proxy_connection(connection_t *conn) return; /* if we have no proxy set up, leave this function. */ log_warn(LD_NET, - "The connection to the %s proxy server at %s:%u just failed. " + "The connection to the %s proxy server at %s just failed. " "Make sure that the proxy server is up and running.", - proxy_type_to_string(get_proxy_type()), fmt_addr(&proxy_addr), - proxy_port); + proxy_type_to_string(get_proxy_type()), + fmt_addrport(&proxy_addr, proxy_port)); } /** Return string representation of <b>proxy_type</b>. */ @@ -4259,7 +4383,7 @@ proxy_type_to_string(int proxy_type) return NULL; /*Unreached*/ } -/** Call _connection_free() on every connection in our array, and release all +/** Call connection_free_() on every connection in our array, and release all * storage held by connection.c. This is used by cpuworkers and dnsworkers * when they fork, so they don't keep resources held open (especially * sockets). @@ -4285,7 +4409,7 @@ connection_free_all(void) /* Clear out our list of broken connections */ clear_broken_connection_map(0); - SMARTLIST_FOREACH(conns, connection_t *, conn, _connection_free(conn)); + SMARTLIST_FOREACH(conns, connection_t *, conn, connection_free_(conn)); if (outgoing_addrs) { SMARTLIST_FOREACH(outgoing_addrs, tor_addr_t *, addr, tor_free(addr)); @@ -4293,6 +4417,9 @@ connection_free_all(void) outgoing_addrs = NULL; } + tor_free(last_interface_ipv4); + tor_free(last_interface_ipv6); + #ifdef USE_BUFFEREVENTS if (global_rate_limit) bufferevent_rate_limit_group_free(global_rate_limit); diff --git a/src/or/connection.h b/src/or/connection.h index 785625e44..b0c2a4283 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -9,8 +9,8 @@ * \brief Header file for connection.c. **/ -#ifndef _TOR_CONNECTION_H -#define _TOR_CONNECTION_H +#ifndef TOR_CONNECTION_H +#define TOR_CONNECTION_H /* XXXX For buf_datalen in inline function */ #include "buffers.h" @@ -31,25 +31,57 @@ void connection_free(connection_t *conn); void connection_free_all(void); void connection_about_to_close_connection(connection_t *conn); void connection_close_immediate(connection_t *conn); -void _connection_mark_for_close(connection_t *conn,int line, const char *file); +void connection_mark_for_close_(connection_t *conn, + int line, const char *file); +void connection_mark_for_close_internal_(connection_t *conn, + int line, const char *file); #define connection_mark_for_close(c) \ - _connection_mark_for_close((c), __LINE__, _SHORT_FILE_) + connection_mark_for_close_((c), __LINE__, SHORT_FILE__) +#define connection_mark_for_close_internal(c) \ + connection_mark_for_close_internal_((c), __LINE__, SHORT_FILE__) /** * Mark 'c' for close, but try to hold it open until all the data is written. + * Use the _internal versions of connection_mark_for_close; this should be + * called when you either are sure that if this is an or_connection_t the + * controlling channel has been notified (e.g. with + * connection_or_notify_error()), or you actually are the + * connection_or_close_for_error() or connection_or_close_normally function. + * For all other cases, use connection_mark_and_flush() instead, which + * checks for or_connection_t properly, instead. See below. */ -#define _connection_mark_and_flush(c,line,file) \ - do { \ - connection_t *tmp_conn_ = (c); \ - _connection_mark_for_close(tmp_conn_, (line), (file)); \ - tmp_conn_->hold_open_until_flushed = 1; \ - IF_HAS_BUFFEREVENT(tmp_conn_, \ - connection_start_writing(tmp_conn_)); \ +#define connection_mark_and_flush_internal_(c,line,file) \ + do { \ + connection_t *tmp_conn_ = (c); \ + connection_mark_for_close_internal_(tmp_conn_, (line), (file)); \ + tmp_conn_->hold_open_until_flushed = 1; \ + IF_HAS_BUFFEREVENT(tmp_conn_, \ + connection_start_writing(tmp_conn_)); \ + } while (0) + +#define connection_mark_and_flush_internal(c) \ + connection_mark_and_flush_internal_((c), __LINE__, SHORT_FILE__) + +/** + * Mark 'c' for close, but try to hold it open until all the data is written. + */ +#define connection_mark_and_flush_(c,line,file) \ + do { \ + connection_t *tmp_conn_ = (c); \ + if (tmp_conn_->type == CONN_TYPE_OR) { \ + log_warn(LD_CHANNEL | LD_BUG, \ + "Something tried to close (and flush) an or_connection_t" \ + " without going through channels at %s:%d", \ + file, line); \ + connection_or_close_for_error(TO_OR_CONN(tmp_conn_), 1); \ + } else { \ + connection_mark_and_flush_internal_(c, line, file); \ + } \ } while (0) #define connection_mark_and_flush(c) \ - _connection_mark_and_flush((c), __LINE__, _SHORT_FILE_) + connection_mark_and_flush_((c), __LINE__, SHORT_FILE__) void connection_expire_held_open(void); @@ -90,7 +122,7 @@ int connection_outbuf_too_full(connection_t *conn); int connection_handle_write(connection_t *conn, int force); int connection_flush(connection_t *conn); -void _connection_write_to_buf_impl(const char *string, size_t len, +void connection_write_to_buf_impl_(const char *string, size_t len, connection_t *conn, int zlib); /* DOCDOC connection_write_to_buf */ static void connection_write_to_buf(const char *string, size_t len, @@ -101,13 +133,13 @@ static void connection_write_to_buf_zlib(const char *string, size_t len, static INLINE void connection_write_to_buf(const char *string, size_t len, connection_t *conn) { - _connection_write_to_buf_impl(string, len, conn, 0); + connection_write_to_buf_impl_(string, len, conn, 0); } static INLINE void connection_write_to_buf_zlib(const char *string, size_t len, dir_connection_t *conn, int done) { - _connection_write_to_buf_impl(string, len, TO_CONN(conn), done ? -1 : 1); + connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); } /* DOCDOC connection_get_inbuf_len */ diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 9563ca622..272955f16 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -8,9 +8,12 @@ * \file connection_edge.c * \brief Handle edge streams. **/ +#define CONNECTION_EDGE_PRIVATE #include "or.h" +#include "addressmap.h" #include "buffers.h" +#include "channel.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" @@ -33,6 +36,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" +#include "routerset.h" #ifdef HAVE_LINUX_TYPES_H #include <linux/types.h> @@ -54,9 +58,7 @@ static int connection_ap_handshake_process_socks(entry_connection_t *conn); static int connection_ap_process_natd(entry_connection_t *conn); static int connection_exit_connect_dir(edge_connection_t *exitconn); -static int address_is_in_virtual_range(const char *addr); static int consider_plaintext_ports(entry_connection_t *conn, uint16_t port); -static void clear_trackexithost_mappings(const char *exitname); static int connection_ap_supports_optimistic_data(const entry_connection_t *); /** An AP stream has failed/finished. If it hasn't already sent back @@ -64,7 +66,7 @@ static int connection_ap_supports_optimistic_data(const entry_connection_t *); * has_sent_end to 1, and mark the conn. */ void -_connection_mark_unattached_ap(entry_connection_t *conn, int endreason, +connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, int line, const char *file) { connection_t *base_conn = ENTRY_TO_CONN(conn); @@ -87,7 +89,7 @@ _connection_mark_unattached_ap(entry_connection_t *conn, int endreason, if (base_conn->marked_for_close) { /* This call will warn as appropriate. */ - _connection_mark_for_close(base_conn, line, file); + connection_mark_for_close_(base_conn, line, file); return; } @@ -107,7 +109,7 @@ _connection_mark_unattached_ap(entry_connection_t *conn, int endreason, conn->socks_request->has_finished = 1; } - _connection_mark_and_flush(base_conn, line, file); + connection_mark_and_flush_(base_conn, line, file); ENTRY_TO_EDGE_CONN(conn)->end_reason = endreason; } @@ -122,12 +124,13 @@ connection_edge_reached_eof(edge_connection_t *conn) /* it still has stuff to process. don't let it die yet. */ return 0; } - log_info(LD_EDGE,"conn (fd %d) reached eof. Closing.", conn->_base.s); - if (!conn->_base.marked_for_close) { + log_info(LD_EDGE,"conn (fd "TOR_SOCKET_T_FORMAT") reached eof. Closing.", + conn->base_.s); + if (!conn->base_.marked_for_close) { /* only mark it if not already marked. it's possible to * get the 'end' right around when the client hangs up on us. */ connection_edge_end(conn, END_STREAM_REASON_DONE); - if (conn->_base.type == CONN_TYPE_AP) { + if (conn->base_.type == CONN_TYPE_AP) { /* eof, so don't send a socks reply back */ if (EDGE_TO_ENTRY_CONN(conn)->socks_request) EDGE_TO_ENTRY_CONN(conn)->socks_request->has_finished = 1; @@ -152,7 +155,7 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) { tor_assert(conn); - switch (conn->_base.state) { + switch (conn->base_.state) { case AP_CONN_STATE_SOCKS_WAIT: if (connection_ap_handshake_process_socks(EDGE_TO_ENTRY_CONN(conn)) <0) { /* already marked */ @@ -178,7 +181,7 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) log_info(LD_EDGE, "data from edge while in '%s' state. Sending it anyway. " "package_partial=%d, buflen=%ld", - conn_state_to_string(conn->_base.type, conn->_base.state), + conn_state_to_string(conn->base_.type, conn->base_.state), package_partial, (long)connection_get_inbuf_len(TO_CONN(conn))); if (connection_edge_package_raw_inbuf(conn, package_partial, NULL)<0) { @@ -197,10 +200,10 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) case AP_CONN_STATE_CONTROLLER_WAIT: log_info(LD_EDGE, "data from edge while in '%s' state. Leaving it on buffer.", - conn_state_to_string(conn->_base.type, conn->_base.state)); + conn_state_to_string(conn->base_.type, conn->base_.state)); return 0; } - log_warn(LD_BUG,"Got unexpected state %d. Closing.",conn->_base.state); + log_warn(LD_BUG,"Got unexpected state %d. Closing.",conn->base_.state); tor_fragile_assert(); connection_edge_end(conn, END_STREAM_REASON_INTERNAL); connection_mark_for_close(TO_CONN(conn)); @@ -213,10 +216,10 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) int connection_edge_destroy(circid_t circ_id, edge_connection_t *conn) { - if (!conn->_base.marked_for_close) { + if (!conn->base_.marked_for_close) { log_info(LD_EDGE, "CircID %d: At an edge. Marking connection for close.", circ_id); - if (conn->_base.type == CONN_TYPE_AP) { + if (conn->base_.type == CONN_TYPE_AP) { entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_DESTROY); control_event_stream_bandwidth(conn); @@ -280,10 +283,10 @@ connection_edge_end(edge_connection_t *conn, uint8_t reason) return -1; } - if (conn->_base.marked_for_close) { + if (conn->base_.marked_for_close) { log_warn(LD_BUG, "called on conn that's already marked for close at %s:%d.", - conn->_base.marked_for_close_file, conn->_base.marked_for_close); + conn->base_.marked_for_close_file, conn->base_.marked_for_close); return 0; } @@ -299,11 +302,11 @@ connection_edge_end(edge_connection_t *conn, uint8_t reason) if (reason == END_STREAM_REASON_EXITPOLICY && !connection_edge_is_rendezvous_stream(conn)) { int addrlen; - if (tor_addr_family(&conn->_base.addr) == AF_INET) { - set_uint32(payload+1, tor_addr_to_ipv4n(&conn->_base.addr)); + if (tor_addr_family(&conn->base_.addr) == AF_INET) { + set_uint32(payload+1, tor_addr_to_ipv4n(&conn->base_.addr)); addrlen = 4; } else { - memcpy(payload+1, tor_addr_to_in6_addr8(&conn->_base.addr), 16); + memcpy(payload+1, tor_addr_to_in6_addr8(&conn->base_.addr), 16); addrlen = 16; } set_uint32(payload+1+addrlen, htonl(dns_clip_ttl(conn->address_ttl))); @@ -311,12 +314,14 @@ connection_edge_end(edge_connection_t *conn, uint8_t reason) } if (circ && !circ->marked_for_close) { - log_debug(LD_EDGE,"Sending end on conn (fd %d).",conn->_base.s); + log_debug(LD_EDGE,"Sending end on conn (fd "TOR_SOCKET_T_FORMAT").", + conn->base_.s); connection_edge_send_command(conn, RELAY_COMMAND_END, payload, payload_len); } else { - log_debug(LD_EDGE,"No circ to send end on conn (fd %d).", - conn->_base.s); + log_debug(LD_EDGE,"No circ to send end on conn " + "(fd "TOR_SOCKET_T_FORMAT").", + conn->base_.s); } conn->edge_has_sent_end = 1; @@ -333,7 +338,7 @@ connection_edge_end_errno(edge_connection_t *conn) { uint8_t reason; tor_assert(conn); - reason = errno_to_stream_end_reason(tor_socket_errno(conn->_base.s)); + reason = errno_to_stream_end_reason(tor_socket_errno(conn->base_.s)); return connection_edge_end(conn, reason); } @@ -345,7 +350,7 @@ connection_edge_end_errno(edge_connection_t *conn) int connection_edge_flushed_some(edge_connection_t *conn) { - switch (conn->_base.state) { + switch (conn->base_.state) { case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: connection_edge_consider_sending_sendme(conn); @@ -369,7 +374,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) { tor_assert(conn); - switch (conn->_base.state) { + switch (conn->base_.state) { case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: connection_edge_consider_sending_sendme(conn); @@ -383,13 +388,55 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_RESOLVE_WAIT: return 0; default: - log_warn(LD_BUG, "Called in unexpected state %d.",conn->_base.state); + log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state); tor_fragile_assert(); return -1; } return 0; } +/** Longest size for the relay payload of a RELAY_CONNECTED cell that we're + * able to generate. */ +/* 4 zero bytes; 1 type byte; 16 byte IPv6 address; 4 byte TTL. */ +#define MAX_CONNECTED_CELL_PAYLOAD_LEN 25 + +/** Set the buffer at <b>payload_out</b> -- which must have at least + * MAX_CONNECTED_CELL_PAYLOAD_LEN bytes available -- to the body of a + * RELAY_CONNECTED cell indicating that we have connected to <b>addr</b>, and + * that the name resolution that led us to <b>addr</b> will be valid for + * <b>ttl</b> seconds. Return -1 on error, or the number of bytes used on + * success. */ +/* private */int +connected_cell_format_payload(uint8_t *payload_out, + const tor_addr_t *addr, + uint32_t ttl) +{ + const sa_family_t family = tor_addr_family(addr); + int connected_payload_len; + + /* should be needless */ + memset(payload_out, 0, MAX_CONNECTED_CELL_PAYLOAD_LEN); + + if (family == AF_INET) { + set_uint32(payload_out, tor_addr_to_ipv4n(addr)); + connected_payload_len = 4; + } else if (family == AF_INET6) { + set_uint32(payload_out, 0); + set_uint8(payload_out + 4, 6); + memcpy(payload_out + 5, tor_addr_to_in6_addr8(addr), 16); + connected_payload_len = 21; + } else { + return -1; + } + + set_uint32(payload_out + connected_payload_len, htonl(dns_clip_ttl(ttl))); + connected_payload_len += 4; + + tor_assert(connected_payload_len <= MAX_CONNECTED_CELL_PAYLOAD_LEN); + + return connected_payload_len; +} + /** Connected handler for exit connections: start writing pending * data, deliver 'CONNECTED' relay cells as appropriate, and check * any pending data that may have been received. */ @@ -399,13 +446,13 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) connection_t *conn; tor_assert(edge_conn); - tor_assert(edge_conn->_base.type == CONN_TYPE_EXIT); + tor_assert(edge_conn->base_.type == CONN_TYPE_EXIT); conn = TO_CONN(edge_conn); tor_assert(conn->state == EXIT_CONN_STATE_CONNECTING); log_info(LD_EXIT,"Exit connection to %s:%u (%s) established.", escaped_safe_str(conn->address), conn->port, - safe_str(fmt_addr(&conn->addr))); + safe_str(fmt_and_decorate_addr(&conn->addr))); rep_hist_note_exit_stream_opened(conn->port); @@ -421,22 +468,16 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) RELAY_COMMAND_CONNECTED, NULL, 0) < 0) return 0; /* circuit is closed, don't continue */ } else { - char connected_payload[20]; - int connected_payload_len; - if (tor_addr_family(&conn->addr) == AF_INET) { - set_uint32(connected_payload, tor_addr_to_ipv4n(&conn->addr)); - set_uint32(connected_payload+4, - htonl(dns_clip_ttl(edge_conn->address_ttl))); - connected_payload_len = 8; - } else { - memcpy(connected_payload, tor_addr_to_in6_addr8(&conn->addr), 16); - set_uint32(connected_payload+16, - htonl(dns_clip_ttl(edge_conn->address_ttl))); - connected_payload_len = 20; - } + uint8_t connected_payload[MAX_CONNECTED_CELL_PAYLOAD_LEN]; + int connected_payload_len = + connected_cell_format_payload(connected_payload, &conn->addr, + edge_conn->address_ttl); + if (connected_payload_len < 0) + return -1; + if (connection_edge_send_command(edge_conn, - RELAY_COMMAND_CONNECTED, - connected_payload, connected_payload_len) < 0) + RELAY_COMMAND_CONNECTED, + (char*)connected_payload, connected_payload_len) < 0) return 0; /* circuit is closed, don't continue */ } tor_assert(edge_conn->package_window > 0); @@ -626,7 +667,7 @@ connection_ap_expire_beginning(void) tor_assert(circ->timestamp_dirty); circ->timestamp_dirty -= options->MaxCircuitDirtiness; /* give our stream another 'cutoff' seconds to try */ - conn->_base.timestamp_lastread += cutoff; + conn->base_.timestamp_lastread += cutoff; if (entry_conn->num_socks_retries < 250) /* avoid overflow */ entry_conn->num_socks_retries++; /* move it back into 'pending' state, and try to attach. */ @@ -752,7 +793,7 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) /** The AP connection <b>conn</b> has just failed while attaching or * sending a BEGIN or resolving on <b>circ</b>, but another circuit * might work. Detach the circuit, and either reattach it, launch a - * new circuit, tell the controller, or give up as a appropriate. + * new circuit, tell the controller, or give up as appropriate. * * Returns -1 on err, 1 on success, 0 on not-yet-sure. */ @@ -782,948 +823,6 @@ connection_ap_detach_retriable(entry_connection_t *conn, } } -/** A client-side struct to remember requests to rewrite addresses - * to new addresses. These structs are stored in the hash table - * "addressmap" below. - * - * There are 5 ways to set an address mapping: - * - A MapAddress command from the controller [permanent] - * - An AddressMap directive in the torrc [permanent] - * - When a TrackHostExits torrc directive is triggered [temporary] - * - When a DNS resolve succeeds [temporary] - * - When a DNS resolve fails [temporary] - * - * When an addressmap request is made but one is already registered, - * the new one is replaced only if the currently registered one has - * no "new_address" (that is, it's in the process of DNS resolve), - * or if the new one is permanent (expires==0 or 1). - * - * (We overload the 'expires' field, using "0" for mappings set via - * the configuration file, "1" for mappings set from the control - * interface, and other values for DNS and TrackHostExit mappings that can - * expire.) - * - * A mapping may be 'wildcarded'. If "src_wildcard" is true, then - * any address that ends with a . followed by the key for this entry will - * get remapped by it. If "dst_wildcard" is also true, then only the - * matching suffix of such addresses will get replaced by new_address. - */ -typedef struct { - char *new_address; - time_t expires; - addressmap_entry_source_t source:3; - unsigned src_wildcard:1; - unsigned dst_wildcard:1; - short num_resolve_failures; -} addressmap_entry_t; - -/** Entry for mapping addresses to which virtual address we mapped them to. */ -typedef struct { - char *ipv4_address; - char *hostname_address; -} virtaddress_entry_t; - -/** A hash table to store client-side address rewrite instructions. */ -static strmap_t *addressmap=NULL; -/** - * Table mapping addresses to which virtual address, if any, we - * assigned them to. - * - * We maintain the following invariant: if [A,B] is in - * virtaddress_reversemap, then B must be a virtual address, and [A,B] - * must be in addressmap. We do not require that the converse hold: - * if it fails, then we could end up mapping two virtual addresses to - * the same address, which is no disaster. - **/ -static strmap_t *virtaddress_reversemap=NULL; - -/** Initialize addressmap. */ -void -addressmap_init(void) -{ - addressmap = strmap_new(); - virtaddress_reversemap = strmap_new(); -} - -/** Free the memory associated with the addressmap entry <b>_ent</b>. */ -static void -addressmap_ent_free(void *_ent) -{ - addressmap_entry_t *ent; - if (!_ent) - return; - - ent = _ent; - tor_free(ent->new_address); - tor_free(ent); -} - -/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ -static void -addressmap_virtaddress_ent_free(void *_ent) -{ - virtaddress_entry_t *ent; - if (!_ent) - return; - - ent = _ent; - tor_free(ent->ipv4_address); - tor_free(ent->hostname_address); - tor_free(ent); -} - -/** Free storage held by a virtaddress_entry_t* entry in <b>ent</b>. */ -static void -addressmap_virtaddress_remove(const char *address, addressmap_entry_t *ent) -{ - if (ent && ent->new_address && - address_is_in_virtual_range(ent->new_address)) { - virtaddress_entry_t *ve = - strmap_get(virtaddress_reversemap, ent->new_address); - /*log_fn(LOG_NOTICE,"remove reverse mapping for %s",ent->new_address);*/ - if (ve) { - if (!strcmp(address, ve->ipv4_address)) - tor_free(ve->ipv4_address); - if (!strcmp(address, ve->hostname_address)) - tor_free(ve->hostname_address); - if (!ve->ipv4_address && !ve->hostname_address) { - tor_free(ve); - strmap_remove(virtaddress_reversemap, ent->new_address); - } - } - } -} - -/** Remove <b>ent</b> (which must be mapped to by <b>address</b>) from the - * client address maps. */ -static void -addressmap_ent_remove(const char *address, addressmap_entry_t *ent) -{ - addressmap_virtaddress_remove(address, ent); - addressmap_ent_free(ent); -} - -/** Unregister all TrackHostExits mappings from any address to - * *.exitname.exit. */ -static void -clear_trackexithost_mappings(const char *exitname) -{ - char *suffix = NULL; - if (!addressmap || !exitname) - return; - tor_asprintf(&suffix, ".%s.exit", exitname); - tor_strlower(suffix); - - STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { - if (ent->source == ADDRMAPSRC_TRACKEXIT && - !strcmpend(ent->new_address, suffix)) { - addressmap_ent_remove(address, ent); - MAP_DEL_CURRENT(address); - } - } STRMAP_FOREACH_END; - - tor_free(suffix); -} - -/** Remove all TRACKEXIT mappings from the addressmap for which the target - * host is unknown or no longer allowed, or for which the source address - * is no longer in trackexithosts. */ -void -addressmap_clear_excluded_trackexithosts(const or_options_t *options) -{ - const routerset_t *allow_nodes = options->ExitNodes; - const routerset_t *exclude_nodes = options->_ExcludeExitNodesUnion; - - if (!addressmap) - return; - if (routerset_is_empty(allow_nodes)) - allow_nodes = NULL; - if (allow_nodes == NULL && routerset_is_empty(exclude_nodes)) - return; - - STRMAP_FOREACH_MODIFY(addressmap, address, addressmap_entry_t *, ent) { - size_t len; - const char *target = ent->new_address, *dot; - char *nodename; - const node_t *node; - - if (!target) { - /* DNS resolving in progress */ - continue; - } else if (strcmpend(target, ".exit")) { - /* Not a .exit mapping */ - continue; - } else if (ent->source != ADDRMAPSRC_TRACKEXIT) { - /* Not a trackexit mapping. */ - continue; - } - len = strlen(target); - if (len < 6) - continue; /* malformed. */ - dot = target + len - 6; /* dot now points to just before .exit */ - while (dot > target && *dot != '.') - dot--; - if (*dot == '.') dot++; - nodename = tor_strndup(dot, len-5-(dot-target));; - node = node_get_by_nickname(nodename, 0); - tor_free(nodename); - if (!node || - (allow_nodes && !routerset_contains_node(allow_nodes, node)) || - routerset_contains_node(exclude_nodes, node) || - !hostname_in_track_host_exits(options, address)) { - /* We don't know this one, or we want to be rid of it. */ - addressmap_ent_remove(address, ent); - MAP_DEL_CURRENT(address); - } - } STRMAP_FOREACH_END; -} - -/** Remove all AUTOMAP mappings from the addressmap for which the - * source address no longer matches AutomapHostsSuffixes, which is - * no longer allowed by AutomapHostsOnResolve, or for which the - * target address is no longer in the virtual network. */ -void -addressmap_clear_invalid_automaps(const or_options_t *options) -{ - int clear_all = !options->AutomapHostsOnResolve; - const smartlist_t *suffixes = options->AutomapHostsSuffixes; - - if (!addressmap) - return; - - if (!suffixes) - clear_all = 1; /* This should be impossible, but let's be sure. */ - - STRMAP_FOREACH_MODIFY(addressmap, src_address, addressmap_entry_t *, ent) { - int remove = clear_all; - if (ent->source != ADDRMAPSRC_AUTOMAP) - continue; /* not an automap mapping. */ - - if (!remove) { - int suffix_found = 0; - SMARTLIST_FOREACH(suffixes, const char *, suffix, { - if (!strcasecmpend(src_address, suffix)) { - suffix_found = 1; - break; - } - }); - if (!suffix_found) - remove = 1; - } - - if (!remove && ! address_is_in_virtual_range(ent->new_address)) - remove = 1; - - if (remove) { - addressmap_ent_remove(src_address, ent); - MAP_DEL_CURRENT(src_address); - } - } STRMAP_FOREACH_END; -} - -/** Remove all entries from the addressmap that were set via the - * configuration file or the command line. */ -void -addressmap_clear_configured(void) -{ - addressmap_get_mappings(NULL, 0, 0, 0); -} - -/** Remove all entries from the addressmap that are set to expire, ever. */ -void -addressmap_clear_transient(void) -{ - addressmap_get_mappings(NULL, 2, TIME_MAX, 0); -} - -/** Clean out entries from the addressmap cache that were - * added long enough ago that they are no longer valid. - */ -void -addressmap_clean(time_t now) -{ - addressmap_get_mappings(NULL, 2, now, 0); -} - -/** Free all the elements in the addressmap, and free the addressmap - * itself. */ -void -addressmap_free_all(void) -{ - strmap_free(addressmap, addressmap_ent_free); - addressmap = NULL; - - strmap_free(virtaddress_reversemap, addressmap_virtaddress_ent_free); - virtaddress_reversemap = NULL; -} - -/** Try to find a match for AddressMap expressions that use - * wildcard notation such as '*.c.d *.e.f' (so 'a.c.d' will map to 'a.e.f') or - * '*.c.d a.b.c' (so 'a.c.d' will map to a.b.c). - * Return the matching entry in AddressMap or NULL if no match is found. - * For expressions such as '*.c.d *.e.f', truncate <b>address</b> 'a.c.d' - * to 'a' before we return the matching AddressMap entry. - * - * This function does not handle the case where a pattern of the form "*.c.d" - * matches the address c.d -- that's done by the main addressmap_rewrite - * function. - */ -static addressmap_entry_t * -addressmap_match_superdomains(char *address) -{ - addressmap_entry_t *val; - char *cp; - - cp = address; - while ((cp = strchr(cp, '.'))) { - /* cp now points to a suffix of address that begins with a . */ - val = strmap_get_lc(addressmap, cp+1); - if (val && val->src_wildcard) { - if (val->dst_wildcard) - *cp = '\0'; - return val; - } - ++cp; - } - return NULL; -} - -/** Look at address, and rewrite it until it doesn't want any - * more rewrites; but don't get into an infinite loop. - * Don't write more than maxlen chars into address. Return true if the - * address changed; false otherwise. Set *<b>expires_out</b> to the - * expiry time of the result, or to <b>time_max</b> if the result does - * not expire. - * - * If <b>exit_source_out</b> is non-null, we set it as follows. If we the - * address starts out as a non-exit address, and we remap it to an .exit - * address at any point, then set *<b>exit_source_out</b> to the - * address_entry_source_t of the first such rule. Set *<b>exit_source_out</b> - * to ADDRMAPSRC_NONE if there is no such rewrite, or if the original address - * was a .exit. - */ -int -addressmap_rewrite(char *address, size_t maxlen, time_t *expires_out, - addressmap_entry_source_t *exit_source_out) -{ - addressmap_entry_t *ent; - int rewrites; - time_t expires = TIME_MAX; - addressmap_entry_source_t exit_source = ADDRMAPSRC_NONE; - char *addr_orig = tor_strdup(address); - char *log_addr_orig = NULL; - - for (rewrites = 0; rewrites < 16; rewrites++) { - int exact_match = 0; - log_addr_orig = tor_strdup(escaped_safe_str_client(address)); - - ent = strmap_get(addressmap, address); - - if (!ent || !ent->new_address) { - ent = addressmap_match_superdomains(address); - } else { - if (ent->src_wildcard && !ent->dst_wildcard && - !strcasecmp(address, ent->new_address)) { - /* This is a rule like *.example.com example.com, and we just got - * "example.com" */ - goto done; - } - - exact_match = 1; - } - - if (!ent || !ent->new_address) { - goto done; - } - - if (ent->dst_wildcard && !exact_match) { - strlcat(address, ".", maxlen); - strlcat(address, ent->new_address, maxlen); - } else { - strlcpy(address, ent->new_address, maxlen); - } - - if (!strcmpend(address, ".exit") && - strcmpend(addr_orig, ".exit") && - exit_source == ADDRMAPSRC_NONE) { - exit_source = ent->source; - } - - log_info(LD_APP, "Addressmap: rewriting %s to %s", - log_addr_orig, escaped_safe_str_client(address)); - if (ent->expires > 1 && ent->expires < expires) - expires = ent->expires; - - tor_free(log_addr_orig); - } - log_warn(LD_CONFIG, - "Loop detected: we've rewritten %s 16 times! Using it as-is.", - escaped_safe_str_client(address)); - /* it's fine to rewrite a rewrite, but don't loop forever */ - - done: - tor_free(addr_orig); - tor_free(log_addr_orig); - if (exit_source_out) - *exit_source_out = exit_source; - if (expires_out) - *expires_out = TIME_MAX; - return (rewrites > 0); -} - -/** If we have a cached reverse DNS entry for the address stored in the - * <b>maxlen</b>-byte buffer <b>address</b> (typically, a dotted quad) then - * rewrite to the cached value and return 1. Otherwise return 0. Set - * *<b>expires_out</b> to the expiry time of the result, or to <b>time_max</b> - * if the result does not expire. */ -static int -addressmap_rewrite_reverse(char *address, size_t maxlen, time_t *expires_out) -{ - char *s, *cp; - addressmap_entry_t *ent; - int r = 0; - tor_asprintf(&s, "REVERSE[%s]", address); - ent = strmap_get(addressmap, s); - if (ent) { - cp = tor_strdup(escaped_safe_str_client(ent->new_address)); - log_info(LD_APP, "Rewrote reverse lookup %s -> %s", - escaped_safe_str_client(s), cp); - tor_free(cp); - strlcpy(address, ent->new_address, maxlen); - r = 1; - } - - if (expires_out) - *expires_out = (ent && ent->expires > 1) ? ent->expires : TIME_MAX; - - tor_free(s); - return r; -} - -/** Return 1 if <b>address</b> is already registered, else return 0. If address - * is already registered, and <b>update_expires</b> is non-zero, then update - * the expiry time on the mapping with update_expires if it is a - * mapping created by TrackHostExits. */ -int -addressmap_have_mapping(const char *address, int update_expiry) -{ - addressmap_entry_t *ent; - if (!(ent=strmap_get_lc(addressmap, address))) - return 0; - if (update_expiry && ent->source==ADDRMAPSRC_TRACKEXIT) - ent->expires=time(NULL) + update_expiry; - return 1; -} - -/** Register a request to map <b>address</b> to <b>new_address</b>, - * which will expire on <b>expires</b> (or 0 if never expires from - * config file, 1 if never expires from controller, 2 if never expires - * (virtual address mapping) from the controller.) - * - * <b>new_address</b> should be a newly dup'ed string, which we'll use or - * free as appropriate. We will leave address alone. - * - * If <b>wildcard_addr</b> is true, then the mapping will match any address - * equal to <b>address</b>, or any address ending with a period followed by - * <b>address</b>. If <b>wildcard_addr</b> and <b>wildcard_new_addr</b> are - * both true, the mapping will rewrite addresses that end with - * ".<b>address</b>" into ones that end with ".<b>new_address</b>." - * - * If <b>new_address</b> is NULL, or <b>new_address</b> is equal to - * <b>address</b> and <b>wildcard_addr</b> is equal to - * <b>wildcard_new_addr</b>, remove any mappings that exist from - * <b>address</b>. - * - * - * It is an error to set <b>wildcard_new_addr</b> if <b>wildcard_addr</b> is - * not set. */ -void -addressmap_register(const char *address, char *new_address, time_t expires, - addressmap_entry_source_t source, - const int wildcard_addr, - const int wildcard_new_addr) -{ - addressmap_entry_t *ent; - - if (wildcard_new_addr) - tor_assert(wildcard_addr); - - ent = strmap_get(addressmap, address); - if (!new_address || (!strcasecmp(address,new_address) && - wildcard_addr == wildcard_new_addr)) { - /* Remove the mapping, if any. */ - tor_free(new_address); - if (ent) { - addressmap_ent_remove(address,ent); - strmap_remove(addressmap, address); - } - return; - } - if (!ent) { /* make a new one and register it */ - ent = tor_malloc_zero(sizeof(addressmap_entry_t)); - strmap_set(addressmap, address, ent); - } else if (ent->new_address) { /* we need to clean up the old mapping. */ - if (expires > 1) { - log_info(LD_APP,"Temporary addressmap ('%s' to '%s') not performed, " - "since it's already mapped to '%s'", - safe_str_client(address), - safe_str_client(new_address), - safe_str_client(ent->new_address)); - tor_free(new_address); - return; - } - if (address_is_in_virtual_range(ent->new_address) && - expires != 2) { - /* XXX This isn't the perfect test; we want to avoid removing - * mappings set from the control interface _as virtual mapping */ - addressmap_virtaddress_remove(address, ent); - } - tor_free(ent->new_address); - } /* else { we have an in-progress resolve with no mapping. } */ - - ent->new_address = new_address; - ent->expires = expires==2 ? 1 : expires; - ent->num_resolve_failures = 0; - ent->source = source; - ent->src_wildcard = wildcard_addr ? 1 : 0; - ent->dst_wildcard = wildcard_new_addr ? 1 : 0; - - log_info(LD_CONFIG, "Addressmap: (re)mapped '%s' to '%s'", - safe_str_client(address), - safe_str_client(ent->new_address)); - control_event_address_mapped(address, ent->new_address, expires, NULL); -} - -/** An attempt to resolve <b>address</b> failed at some OR. - * Increment the number of resolve failures we have on record - * for it, and then return that number. - */ -int -client_dns_incr_failures(const char *address) -{ - addressmap_entry_t *ent = strmap_get(addressmap, address); - if (!ent) { - ent = tor_malloc_zero(sizeof(addressmap_entry_t)); - ent->expires = time(NULL) + MAX_DNS_ENTRY_AGE; - strmap_set(addressmap,address,ent); - } - if (ent->num_resolve_failures < SHORT_MAX) - ++ent->num_resolve_failures; /* don't overflow */ - log_info(LD_APP, "Address %s now has %d resolve failures.", - safe_str_client(address), - ent->num_resolve_failures); - return ent->num_resolve_failures; -} - -/** If <b>address</b> is in the client DNS addressmap, reset - * the number of resolve failures we have on record for it. - * This is used when we fail a stream because it won't resolve: - * otherwise future attempts on that address will only try once. - */ -void -client_dns_clear_failures(const char *address) -{ - addressmap_entry_t *ent = strmap_get(addressmap, address); - if (ent) - ent->num_resolve_failures = 0; -} - -/** Record the fact that <b>address</b> resolved to <b>name</b>. - * We can now use this in subsequent streams via addressmap_rewrite() - * so we can more correctly choose an exit that will allow <b>address</b>. - * - * If <b>exitname</b> is defined, then append the addresses with - * ".exitname.exit" before registering the mapping. - * - * If <b>ttl</b> is nonnegative, the mapping will be valid for - * <b>ttl</b>seconds; otherwise, we use the default. - */ -static void -client_dns_set_addressmap_impl(const char *address, const char *name, - const char *exitname, - int ttl) -{ - /* <address>.<hex or nickname>.exit\0 or just <address>\0 */ - char extendedaddress[MAX_SOCKS_ADDR_LEN+MAX_VERBOSE_NICKNAME_LEN+10]; - /* 123.123.123.123.<hex or nickname>.exit\0 or just 123.123.123.123\0 */ - char extendedval[INET_NTOA_BUF_LEN+MAX_VERBOSE_NICKNAME_LEN+10]; - - tor_assert(address); - tor_assert(name); - - if (ttl<0) - ttl = DEFAULT_DNS_TTL; - else - ttl = dns_clip_ttl(ttl); - - if (exitname) { - /* XXXX fails to ever get attempts to get an exit address of - * google.com.digest[=~]nickname.exit; we need a syntax for this that - * won't make strict RFC952-compliant applications (like us) barf. */ - tor_snprintf(extendedaddress, sizeof(extendedaddress), - "%s.%s.exit", address, exitname); - tor_snprintf(extendedval, sizeof(extendedval), - "%s.%s.exit", name, exitname); - } else { - tor_snprintf(extendedaddress, sizeof(extendedaddress), - "%s", address); - tor_snprintf(extendedval, sizeof(extendedval), - "%s", name); - } - addressmap_register(extendedaddress, tor_strdup(extendedval), - time(NULL) + ttl, ADDRMAPSRC_DNS, 0, 0); -} - -/** Record the fact that <b>address</b> resolved to <b>val</b>. - * We can now use this in subsequent streams via addressmap_rewrite() - * so we can more correctly choose an exit that will allow <b>address</b>. - * - * If <b>exitname</b> is defined, then append the addresses with - * ".exitname.exit" before registering the mapping. - * - * If <b>ttl</b> is nonnegative, the mapping will be valid for - * <b>ttl</b>seconds; otherwise, we use the default. - */ -void -client_dns_set_addressmap(const char *address, uint32_t val, - const char *exitname, - int ttl) -{ - struct in_addr in; - char valbuf[INET_NTOA_BUF_LEN]; - - tor_assert(address); - - if (tor_inet_aton(address, &in)) - return; /* If address was an IP address already, don't add a mapping. */ - in.s_addr = htonl(val); - tor_inet_ntoa(&in,valbuf,sizeof(valbuf)); - - client_dns_set_addressmap_impl(address, valbuf, exitname, ttl); -} - -/** Add a cache entry noting that <b>address</b> (ordinarily a dotted quad) - * resolved via a RESOLVE_PTR request to the hostname <b>v</b>. - * - * If <b>exitname</b> is defined, then append the addresses with - * ".exitname.exit" before registering the mapping. - * - * If <b>ttl</b> is nonnegative, the mapping will be valid for - * <b>ttl</b>seconds; otherwise, we use the default. - */ -static void -client_dns_set_reverse_addressmap(const char *address, const char *v, - const char *exitname, - int ttl) -{ - char *s = NULL; - tor_asprintf(&s, "REVERSE[%s]", address); - client_dns_set_addressmap_impl(s, v, exitname, ttl); - tor_free(s); -} - -/* By default, we hand out 127.192.0.1 through 127.254.254.254. - * These addresses should map to localhost, so even if the - * application accidentally tried to connect to them directly (not - * via Tor), it wouldn't get too far astray. - * - * These options are configured by parse_virtual_addr_network(). - */ -/** Which network should we use for virtual IPv4 addresses? Only the first - * bits of this value are fixed. */ -static uint32_t virtual_addr_network = 0x7fc00000u; -/** How many bits of <b>virtual_addr_network</b> are fixed? */ -static maskbits_t virtual_addr_netmask_bits = 10; -/** What's the next virtual address we will hand out? */ -static uint32_t next_virtual_addr = 0x7fc00000u; - -/** Read a netmask of the form 127.192.0.0/10 from "val", and check whether - * it's a valid set of virtual addresses to hand out in response to MAPADDRESS - * requests. Return 0 on success; set *msg (if provided) to a newly allocated - * string and return -1 on failure. If validate_only is false, sets the - * actual virtual address range to the parsed value. */ -int -parse_virtual_addr_network(const char *val, int validate_only, - char **msg) -{ - uint32_t addr; - uint16_t port_min, port_max; - maskbits_t bits; - - if (parse_addr_and_port_range(val, &addr, &bits, &port_min, &port_max)) { - if (msg) *msg = tor_strdup("Error parsing VirtualAddressNetwork"); - return -1; - } - - if (port_min != 1 || port_max != 65535) { - if (msg) *msg = tor_strdup("Can't specify ports on VirtualAddressNetwork"); - return -1; - } - - if (bits > 16) { - if (msg) *msg = tor_strdup("VirtualAddressNetwork expects a /16 " - "network or larger"); - return -1; - } - - if (validate_only) - return 0; - - virtual_addr_network = (uint32_t)( addr & (0xfffffffful << (32-bits)) ); - virtual_addr_netmask_bits = bits; - - if (addr_mask_cmp_bits(next_virtual_addr, addr, bits)) - next_virtual_addr = addr; - - return 0; -} - -/** - * Return true iff <b>addr</b> is likely to have been returned by - * client_dns_get_unused_address. - **/ -static int -address_is_in_virtual_range(const char *address) -{ - struct in_addr in; - tor_assert(address); - if (!strcasecmpend(address, ".virtual")) { - return 1; - } else if (tor_inet_aton(address, &in)) { - uint32_t addr = ntohl(in.s_addr); - if (!addr_mask_cmp_bits(addr, virtual_addr_network, - virtual_addr_netmask_bits)) - return 1; - } - return 0; -} - -/** Increment the value of next_virtual_addr; reset it to the start of the - * virtual address range if it wraps around. - */ -static INLINE void -increment_virtual_addr(void) -{ - ++next_virtual_addr; - if (addr_mask_cmp_bits(next_virtual_addr, virtual_addr_network, - virtual_addr_netmask_bits)) - next_virtual_addr = virtual_addr_network; -} - -/** Return a newly allocated string holding an address of <b>type</b> - * (one of RESOLVED_TYPE_{IPV4|HOSTNAME}) that has not yet been mapped, - * and that is very unlikely to be the address of any real host. - * - * May return NULL if we have run out of virtual addresses. - */ -static char * -addressmap_get_virtual_address(int type) -{ - char buf[64]; - tor_assert(addressmap); - - if (type == RESOLVED_TYPE_HOSTNAME) { - char rand[10]; - do { - crypto_rand(rand, sizeof(rand)); - base32_encode(buf,sizeof(buf),rand,sizeof(rand)); - strlcat(buf, ".virtual", sizeof(buf)); - } while (strmap_get(addressmap, buf)); - return tor_strdup(buf); - } else if (type == RESOLVED_TYPE_IPV4) { - // This is an imperfect estimate of how many addresses are available, but - // that's ok. - struct in_addr in; - uint32_t available = 1u << (32-virtual_addr_netmask_bits); - while (available) { - /* Don't hand out any .0 or .255 address. */ - while ((next_virtual_addr & 0xff) == 0 || - (next_virtual_addr & 0xff) == 0xff) { - increment_virtual_addr(); - if (! --available) { - log_warn(LD_CONFIG, "Ran out of virtual addresses!"); - return NULL; - } - } - in.s_addr = htonl(next_virtual_addr); - tor_inet_ntoa(&in, buf, sizeof(buf)); - if (!strmap_get(addressmap, buf)) { - increment_virtual_addr(); - break; - } - - increment_virtual_addr(); - --available; - // log_info(LD_CONFIG, "%d addrs available", (int)available); - if (! available) { - log_warn(LD_CONFIG, "Ran out of virtual addresses!"); - return NULL; - } - } - return tor_strdup(buf); - } else { - log_warn(LD_BUG, "Called with unsupported address type (%d)", type); - return NULL; - } -} - -/** A controller has requested that we map some address of type - * <b>type</b> to the address <b>new_address</b>. Choose an address - * that is unlikely to be used, and map it, and return it in a newly - * allocated string. If another address of the same type is already - * mapped to <b>new_address</b>, try to return a copy of that address. - * - * The string in <b>new_address</b> may be freed or inserted into a map - * as appropriate. May return NULL if are out of virtual addresses. - **/ -const char * -addressmap_register_virtual_address(int type, char *new_address) -{ - char **addrp; - virtaddress_entry_t *vent; - int vent_needs_to_be_added = 0; - - tor_assert(new_address); - tor_assert(addressmap); - tor_assert(virtaddress_reversemap); - - vent = strmap_get(virtaddress_reversemap, new_address); - if (!vent) { - vent = tor_malloc_zero(sizeof(virtaddress_entry_t)); - vent_needs_to_be_added = 1; - } - - addrp = (type == RESOLVED_TYPE_IPV4) ? - &vent->ipv4_address : &vent->hostname_address; - if (*addrp) { - addressmap_entry_t *ent = strmap_get(addressmap, *addrp); - if (ent && ent->new_address && - !strcasecmp(new_address, ent->new_address)) { - tor_free(new_address); - tor_assert(!vent_needs_to_be_added); - return tor_strdup(*addrp); - } else - log_warn(LD_BUG, - "Internal confusion: I thought that '%s' was mapped to by " - "'%s', but '%s' really maps to '%s'. This is a harmless bug.", - safe_str_client(new_address), - safe_str_client(*addrp), - safe_str_client(*addrp), - ent?safe_str_client(ent->new_address):"(nothing)"); - } - - tor_free(*addrp); - *addrp = addressmap_get_virtual_address(type); - if (!*addrp) { - tor_free(vent); - tor_free(new_address); - return NULL; - } - log_info(LD_APP, "Registering map from %s to %s", *addrp, new_address); - if (vent_needs_to_be_added) - strmap_set(virtaddress_reversemap, new_address, vent); - addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0); - -#if 0 - { - /* Try to catch possible bugs */ - addressmap_entry_t *ent; - ent = strmap_get(addressmap, *addrp); - tor_assert(ent); - tor_assert(!strcasecmp(ent->new_address,new_address)); - vent = strmap_get(virtaddress_reversemap, new_address); - tor_assert(vent); - tor_assert(!strcasecmp(*addrp, - (type == RESOLVED_TYPE_IPV4) ? - vent->ipv4_address : vent->hostname_address)); - log_info(LD_APP, "Map from %s to %s okay.", - safe_str_client(*addrp), - safe_str_client(new_address)); - } -#endif - - return *addrp; -} - -/** Return 1 if <b>address</b> has funny characters in it like colons. Return - * 0 if it's fine, or if we're configured to allow it anyway. <b>client</b> - * should be true if we're using this address as a client; false if we're - * using it as a server. - */ -int -address_is_invalid_destination(const char *address, int client) -{ - if (client) { - if (get_options()->AllowNonRFC953Hostnames) - return 0; - } else { - if (get_options()->ServerDNSAllowNonRFC953Hostnames) - return 0; - } - - while (*address) { - if (TOR_ISALNUM(*address) || - *address == '-' || - *address == '.' || - *address == '_') /* Underscore is not allowed, but Windows does it - * sometimes, just to thumb its nose at the IETF. */ - ++address; - else - return 1; - } - return 0; -} - -/** Iterate over all address mappings which have expiry times between - * min_expires and max_expires, inclusive. If sl is provided, add an - * "old-addr new-addr expiry" string to sl for each mapping, omitting - * the expiry time if want_expiry is false. If sl is NULL, remove the - * mappings. - */ -void -addressmap_get_mappings(smartlist_t *sl, time_t min_expires, - time_t max_expires, int want_expiry) -{ - strmap_iter_t *iter; - const char *key; - void *_val; - addressmap_entry_t *val; - - if (!addressmap) - addressmap_init(); - - for (iter = strmap_iter_init(addressmap); !strmap_iter_done(iter); ) { - strmap_iter_get(iter, &key, &_val); - val = _val; - if (val->expires >= min_expires && val->expires <= max_expires) { - if (!sl) { - iter = strmap_iter_next_rmv(addressmap,iter); - addressmap_ent_remove(key, val); - continue; - } else if (val->new_address) { - const char *src_wc = val->src_wildcard ? "*." : ""; - const char *dst_wc = val->dst_wildcard ? "*." : ""; - if (want_expiry) { - if (val->expires < 3 || val->expires == TIME_MAX) - smartlist_add_asprintf(sl, "%s%s %s%s NEVER", - src_wc, key, dst_wc, val->new_address); - else { - char time[ISO_TIME_LEN+1]; - format_iso_time(time, val->expires); - smartlist_add_asprintf(sl, "%s%s %s%s \"%s\"", - src_wc, key, dst_wc, val->new_address, - time); - } - } else { - smartlist_add_asprintf(sl, "%s%s %s%s", - src_wc, key, dst_wc, val->new_address); - } - } - } - iter = strmap_iter_next(addressmap,iter); - } -} - /** Check if <b>conn</b> is using a dangerous port. Then warn and/or * reject depending on our config options. */ static int @@ -1798,7 +897,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, socks_request_t *socks = conn->socks_request; hostname_type_t addresstype; const or_options_t *options = get_options(); - struct in_addr addr_tmp; + tor_addr_t addr_tmp; /* We set this to true if this is an address we should automatically * remap to a local address in VirtualAddrNetwork */ int automap = 0; @@ -1828,17 +927,20 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, conn->original_dest_address = tor_strdup(conn->socks_request->address); if (socks->command == SOCKS_COMMAND_RESOLVE && - !tor_inet_aton(socks->address, &addr_tmp) && - options->AutomapHostsOnResolve && options->AutomapHostsSuffixes) { - SMARTLIST_FOREACH(options->AutomapHostsSuffixes, const char *, cp, - if (!strcasecmpend(socks->address, cp)) { - automap = 1; - break; - }); + tor_addr_parse(&addr_tmp, socks->address)<0 && + options->AutomapHostsOnResolve) { + automap = addressmap_address_should_automap(socks->address, options); if (automap) { const char *new_addr; + int addr_type = RESOLVED_TYPE_IPV4; + if (conn->socks_request->socks_version != 4) { + if (!conn->ipv4_traffic_ok || + (conn->ipv6_traffic_ok && conn->prefer_ipv6_traffic) || + conn->prefer_ipv6_virtaddr) + addr_type = RESOLVED_TYPE_IPV6; + } new_addr = addressmap_register_virtual_address( - RESOLVED_TYPE_IPV4, tor_strdup(socks->address)); + addr_type, tor_strdup(socks->address)); if (! new_addr) { log_warn(LD_APP, "Unable to automap address %s", escaped_safe_str(socks->address)); @@ -1853,8 +955,14 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) { + unsigned rewrite_flags = 0; + if (conn->use_cached_ipv4_answers) + rewrite_flags |= AMR_FLAG_USE_IPV4_DNS; + if (conn->use_cached_ipv6_answers) + rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; + if (addressmap_rewrite_reverse(socks->address, sizeof(socks->address), - &map_expires)) { + rewrite_flags, &map_expires)) { char *result = tor_strdup(socks->address); /* remember _what_ is supposed to have been resolved. */ tor_snprintf(socks->address, sizeof(socks->address), "REVERSE[%s]", @@ -1885,8 +993,13 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } else if (!automap) { /* For address map controls, remap the address. */ + unsigned rewrite_flags = 0; + if (conn->use_cached_ipv4_answers) + rewrite_flags |= AMR_FLAG_USE_IPV4_DNS; + if (conn->use_cached_ipv6_answers) + rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; if (addressmap_rewrite(socks->address, sizeof(socks->address), - &map_expires, &exit_source)) { + rewrite_flags, &map_expires, &exit_source)) { control_event_stream_status(conn, STREAM_EVENT_REMAP, REMAP_STREAM_SOURCE_CACHE); } @@ -1923,7 +1036,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */ routerset_t *excludeset = options->StrictNodes ? - options->_ExcludeExitNodesUnion : options->ExcludeExitNodes; + options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes; const node_t *node; if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { @@ -2087,6 +1200,37 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } + { + tor_addr_t addr; + /* XXX Duplicate call to tor_addr_parse. */ + if (tor_addr_parse(&addr, socks->address) >= 0) { + sa_family_t family = tor_addr_family(&addr); + if ((family == AF_INET && ! conn->ipv4_traffic_ok) || + (family == AF_INET6 && ! conn->ipv4_traffic_ok)) { + log_warn(LD_NET, "Rejecting SOCKS request for an IP address " + "family that this listener does not support."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } else if (family == AF_INET6 && socks->socks_version == 4) { + log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } else if (socks->socks_version == 4 && !conn->ipv4_traffic_ok) { + log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with " + "no IPv4 traffic supported."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } else if (family == AF_INET6) { + conn->ipv4_traffic_ok = 0; + } else if (family == AF_INET) { + conn->ipv6_traffic_ok = 0; + } + } + } + + if (socks->socks_version == 4) + conn->ipv6_traffic_ok = 0; + if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ const node_t *r = @@ -2256,7 +1400,7 @@ connection_ap_get_original_destination(entry_connection_t *conn, } tor_addr_from_sockaddr(&addr, (struct sockaddr*)&orig_dst, &req->port); - tor_addr_to_str(req->address, &addr, sizeof(req->address), 0); + tor_addr_to_str(req->address, &addr, sizeof(req->address), 1); return 0; #elif defined(TRANS_PF) @@ -2317,7 +1461,7 @@ connection_ap_get_original_destination(entry_connection_t *conn, return -1; } - tor_addr_to_str(req->address, &addr, sizeof(req->address), 0); + tor_addr_to_str(req->address, &addr, sizeof(req->address), 1); req->port = ntohs(pnl.rdport); return 0; @@ -2517,7 +1661,7 @@ connection_ap_process_natd(entry_connection_t *conn) /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ -static streamid_t +streamid_t get_unique_stream_id_by_circ(origin_circuit_t *circ) { edge_connection_t *tmpconn; @@ -2555,6 +1699,65 @@ connection_ap_supports_optimistic_data(const entry_connection_t *conn) return conn->may_use_optimistic_data; } +/** Return a bitmask of BEGIN_FLAG_* flags that we should transmit in the + * RELAY_BEGIN cell for <b>ap_conn</b>. */ +static uint32_t +connection_ap_get_begincell_flags(entry_connection_t *ap_conn) +{ + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + const node_t *exitnode = NULL; + const crypt_path_t *cpath_layer = edge_conn->cpath_layer; + uint32_t flags = 0; + + /* No flags for begindir */ + if (ap_conn->use_begindir) + return 0; + + /* No flags for hidden services. */ + if (edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL) + return 0; + + /* If only IPv4 is supported, no flags */ + if (ap_conn->ipv4_traffic_ok && !ap_conn->ipv6_traffic_ok) + return 0; + + if (! cpath_layer || + ! cpath_layer->extend_info) + return 0; + + if (!ap_conn->ipv4_traffic_ok) + flags |= BEGIN_FLAG_IPV4_NOT_OK; + + exitnode = node_get_by_id(cpath_layer->extend_info->identity_digest); + + if (ap_conn->ipv6_traffic_ok && exitnode) { + tor_addr_t a; + tor_addr_make_null(&a, AF_INET6); + if (compare_tor_addr_to_node_policy(&a, ap_conn->socks_request->port, + exitnode) + != ADDR_POLICY_REJECTED) { + /* Only say "IPv6 OK" if the exit node supports IPv6. Otherwise there's + * no point. */ + flags |= BEGIN_FLAG_IPV6_OK; + } + } + + if (flags == BEGIN_FLAG_IPV6_OK) { + /* When IPv4 and IPv6 are both allowed, consider whether to say we + * prefer IPv6. Otherwise there's no point in declaring a preference */ + if (ap_conn->prefer_ipv6_traffic) + flags |= BEGIN_FLAG_IPV6_PREFERRED; + } + + if (flags == BEGIN_FLAG_IPV4_NOT_OK) { + log_warn(LD_BUG, "Hey; I'm about to ask a node for a connection that I " + "am telling it to fulfil with neither IPv4 nor IPv6. That's " + "probably not going to work."); + } + + return flags; +} + /** Write a relay begin cell, using destaddr and destport from ap_conn's * socks_request field, and send it down circ. * @@ -2585,16 +1788,23 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) /* Mark this circuit "unusable for new streams". */ /* XXXX024 this is a kludgy way to do this. */ - tor_assert(circ->_base.timestamp_dirty); - circ->_base.timestamp_dirty -= get_options()->MaxCircuitDirtiness; + tor_assert(circ->base_.timestamp_dirty); + circ->base_.timestamp_dirty -= get_options()->MaxCircuitDirtiness; return -1; } + /* Set up begin cell flags. */ + edge_conn->begincell_flags = connection_ap_get_begincell_flags(ap_conn); + tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:%d", - (circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL) ? + (circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL) ? ap_conn->socks_request->address : "", ap_conn->socks_request->port); payload_len = (int)strlen(payload)+1; + if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) { + set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags)); + payload_len += 4; + } log_info(LD_APP, "Sending relay cell %d to begin stream %d.", @@ -2617,8 +1827,9 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) edge_conn->package_window = STREAMWINDOW_START; edge_conn->deliver_window = STREAMWINDOW_START; base_conn->state = AP_CONN_STATE_CONNECT_WAIT; - log_info(LD_APP,"Address/port sent, ap socket %d, n_circ_id %d", - base_conn->s, circ->_base.n_circ_id); + log_info(LD_APP,"Address/port sent, ap socket "TOR_SOCKET_T_FORMAT + ", n_circ_id %d", + base_conn->s, circ->base_.n_circ_id); control_event_stream_status(ap_conn, STREAM_EVENT_SENT_CONNECT, 0); /* If there's queued-up data, send it now */ @@ -2657,7 +1868,7 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) tor_assert(base_conn->type == CONN_TYPE_AP); tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT); tor_assert(ap_conn->socks_request); - tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL); command = ap_conn->socks_request->command; tor_assert(SOCKS_COMMAND_IS_RESOLVE(command)); @@ -2670,8 +1881,8 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) /* Mark this circuit "unusable for new streams". */ /* XXXX024 this is a kludgy way to do this. */ - tor_assert(circ->_base.timestamp_dirty); - circ->_base.timestamp_dirty -= get_options()->MaxCircuitDirtiness; + tor_assert(circ->base_.timestamp_dirty); + circ->base_.timestamp_dirty -= get_options()->MaxCircuitDirtiness; return -1; } @@ -2686,7 +1897,7 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) /* We're doing a reverse lookup. The input could be an IP address, or * could be an .in-addr.arpa or .ip6.arpa address */ - r = tor_addr_parse_PTR_name(&addr, a, AF_INET, 1); + r = tor_addr_parse_PTR_name(&addr, a, AF_UNSPEC, 1); if (r <= 0) { log_warn(LD_APP, "Rejecting ill-formed reverse lookup of %s", safe_str_client(a)); @@ -2718,8 +1929,9 @@ connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) tor_free(base_conn->address); /* Maybe already set by dnsserv. */ base_conn->address = tor_strdup("(Tor_internal)"); base_conn->state = AP_CONN_STATE_RESOLVE_WAIT; - log_info(LD_APP,"Address sent for resolve, ap socket %d, n_circ_id %d", - base_conn->s, circ->_base.n_circ_id); + log_info(LD_APP,"Address sent for resolve, ap socket "TOR_SOCKET_T_FORMAT + ", n_circ_id %d", + base_conn->s, circ->base_.n_circ_id); control_event_stream_status(ap_conn, STREAM_EVENT_NEW, 0); control_event_stream_status(ap_conn, STREAM_EVENT_SENT_RESOLVE, 0); return 0; @@ -2856,13 +2068,25 @@ connection_ap_handshake_socks_resolved(entry_connection_t *conn, if (ttl >= 0) { if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) { - uint32_t a = ntohl(get_uint32(answer)); - if (a) - client_dns_set_addressmap(conn->socks_request->address, a, + tor_addr_t a; + tor_addr_from_ipv4n(&a, get_uint32(answer)); + if (! tor_addr_is_null(&a)) { + client_dns_set_addressmap(conn, + conn->socks_request->address, &a, + conn->chosen_exit_name, ttl); + } + } else if (answer_type == RESOLVED_TYPE_IPV6 && answer_len == 16) { + tor_addr_t a; + tor_addr_from_ipv6_bytes(&a, (char*)answer); + if (! tor_addr_is_null(&a)) { + client_dns_set_addressmap(conn, + conn->socks_request->address, &a, conn->chosen_exit_name, ttl); + } } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) { char *cp = tor_strndup((char*)answer, answer_len); - client_dns_set_reverse_addressmap(conn->socks_request->address, + client_dns_set_reverse_addressmap(conn, + conn->socks_request->address, cp, conn->chosen_exit_name, ttl); tor_free(cp); @@ -2962,6 +2186,29 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, status==SOCKS5_SUCCEEDED ? STREAM_EVENT_SUCCEEDED : STREAM_EVENT_FAILED, endreason); + /* Flag this stream's circuit as having completed a stream successfully + * (for path bias) */ + if (status == SOCKS5_SUCCEEDED || + endreason == END_STREAM_REASON_RESOLVEFAILED || + endreason == END_STREAM_REASON_CONNECTREFUSED || + endreason == END_STREAM_REASON_CONNRESET || + endreason == END_STREAM_REASON_NOROUTE || + endreason == END_STREAM_REASON_RESOURCELIMIT) { + if (!conn->edge_.on_circuit || + !CIRCUIT_IS_ORIGIN(conn->edge_.on_circuit)) { + // DNS remaps can trigger this. So can failed hidden service + // lookups. + log_info(LD_BUG, + "No origin circuit for successful SOCKS stream "U64_FORMAT + ". Reason: %d", + U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), + endreason); + } else { + TO_ORIGIN_CIRCUIT(conn->edge_.on_circuit)->path_state + = PATH_STATE_USE_SUCCEEDED; + } + } + if (conn->socks_request->has_finished) { log_warn(LD_BUG, "(Harmless.) duplicate calls to " "connection_ap_handshake_socks_reply."); @@ -2992,6 +2239,70 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } +/** Read a RELAY_BEGIN or RELAY_BEGINDIR cell from <b>cell</b>, decode it, and + * place the result in <b>bcell</b>. On success return 0; on failure return + * <0 and set *<b>end_reason_out</b> to the end reason we should send back to + * the client. + * + * Return -1 in the case where want to send a RELAY_END cell, and < -1 when + * we don't. + **/ +/* static */ int +begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, + uint8_t *end_reason_out) +{ + relay_header_t rh; + const uint8_t *body, *nul; + + memset(bcell, 0, sizeof(*bcell)); + *end_reason_out = END_STREAM_REASON_MISC; + + relay_header_unpack(&rh, cell->payload); + if (rh.length > RELAY_PAYLOAD_SIZE) { + return -2; /*XXXX why not TORPROTOCOL? */ + } + + bcell->stream_id = rh.stream_id; + + if (rh.command == RELAY_COMMAND_BEGIN_DIR) { + bcell->is_begindir = 1; + return 0; + } else if (rh.command != RELAY_COMMAND_BEGIN) { + log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command); + *end_reason_out = END_STREAM_REASON_INTERNAL; + return -1; + } + + body = cell->payload + RELAY_HEADER_SIZE; + nul = memchr(body, 0, rh.length); + if (! nul) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Relay begin cell has no \\0. Closing."); + *end_reason_out = END_STREAM_REASON_TORPROTOCOL; + return -1; + } + + if (tor_addr_port_split(LOG_PROTOCOL_WARN, + (char*)(body), + &bcell->address,&bcell->port)<0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Unable to parse addr:port in relay begin cell. Closing."); + *end_reason_out = END_STREAM_REASON_TORPROTOCOL; + return -1; + } + if (bcell->port == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Missing port in relay begin cell. Closing."); + tor_free(bcell->address); + *end_reason_out = END_STREAM_REASON_TORPROTOCOL; + return -1; + } + if (body + rh.length >= nul + 4) + bcell->flags = ntohl(get_uint32(nul+1)); + + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3015,10 +2326,13 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) { edge_connection_t *n_stream; relay_header_t rh; - char *address=NULL; - uint16_t port; + char *address = NULL; + uint16_t port = 0; or_circuit_t *or_circ = NULL; const or_options_t *options = get_options(); + begin_cell_t bcell; + int r; + uint8_t end_reason=0; assert_circuit_ok(circ); if (!CIRCUIT_IS_ORIGIN(circ)) @@ -3042,52 +2356,43 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) return 0; } - if (rh.command == RELAY_COMMAND_BEGIN) { - if (!memchr(cell->payload+RELAY_HEADER_SIZE, 0, rh.length)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Relay begin cell has no \\0. Closing."); - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_TORPROTOCOL, NULL); - return 0; - } - if (tor_addr_port_split(LOG_PROTOCOL_WARN, - (char*)(cell->payload+RELAY_HEADER_SIZE), - &address,&port)<0) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Unable to parse addr:port in relay begin cell. Closing."); - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_TORPROTOCOL, NULL); - return 0; - } - if (port==0) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Missing port in relay begin cell. Closing."); - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_TORPROTOCOL, NULL); - tor_free(address); - return 0; - } - if (or_circ && or_circ->p_conn && !options->AllowSingleHopExits && - (or_circ->is_first_hop || - (!connection_or_digest_is_known_relay( - or_circ->p_conn->identity_digest) && + r = begin_cell_parse(cell, &bcell, &end_reason); + if (r < -1) { + return -1; + } else if (r == -1) { + tor_free(bcell.address); + relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, NULL); + return 0; + } + + if (! bcell.is_begindir) { + /* Steal reference */ + address = bcell.address; + port = bcell.port; + + if (or_circ && or_circ->p_chan) { + if (!options->AllowSingleHopExits && + (or_circ->is_first_hop || + (!connection_or_digest_is_known_relay( + or_circ->p_chan->identity_digest) && should_refuse_unknown_exits(options)))) { - /* Don't let clients use us as a single-hop proxy, unless the user - * has explicitly allowed that in the config. It attracts attackers - * and users who'd be better off with, well, single-hop proxies. - */ - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Attempt by %s to open a stream %s. Closing.", - safe_str(or_circ->p_conn->_base.address), - or_circ->is_first_hop ? "on first hop of circuit" : - "from unknown relay"); - relay_send_end_cell_from_edge(rh.stream_id, circ, - or_circ->is_first_hop ? - END_STREAM_REASON_TORPROTOCOL : - END_STREAM_REASON_MISC, - NULL); - tor_free(address); - return 0; + /* Don't let clients use us as a single-hop proxy, unless the user + * has explicitly allowed that in the config. It attracts attackers + * and users who'd be better off with, well, single-hop proxies. + */ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Attempt by %s to open a stream %s. Closing.", + safe_str(channel_get_canonical_remote_descr(or_circ->p_chan)), + or_circ->is_first_hop ? "on first hop of circuit" : + "from unknown relay"); + relay_send_end_cell_from_edge(rh.stream_id, circ, + or_circ->is_first_hop ? + END_STREAM_REASON_TORPROTOCOL : + END_STREAM_REASON_MISC, + NULL); + tor_free(address); + return 0; + } } } else if (rh.command == RELAY_COMMAND_BEGIN_DIR) { if (!directory_permits_begindir_requests(options) || @@ -3098,10 +2403,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) } /* Make sure to get the 'real' address of the previous hop: the * caller might want to know whether his IP address has changed, and - * we might already have corrected _base.addr[ess] for the relay's + * we might already have corrected base_.addr[ess] for the relay's * canonical IP address. */ - if (or_circ && or_circ->p_conn) - address = tor_dup_addr(&or_circ->p_conn->real_addr); + if (or_circ && or_circ->p_chan) + address = tor_strdup(channel_get_actual_remote_address(or_circ->p_chan)); else address = tor_strdup("127.0.0.1"); port = 1; /* XXXX This value is never actually used anywhere, and there @@ -3114,17 +2419,31 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) return 0; } + if (! options->IPv6Exit) { + /* I don't care if you prefer IPv6; I can't give you any. */ + bcell.flags &= ~BEGIN_FLAG_IPV6_PREFERRED; + /* If you don't want IPv4, I can't help. */ + if (bcell.flags & BEGIN_FLAG_IPV4_NOT_OK) { + tor_free(address); + relay_send_end_cell_from_edge(rh.stream_id, circ, + END_STREAM_REASON_EXITPOLICY, NULL); + return 0; + } + } + log_debug(LD_EXIT,"Creating new exit connection."); + /* The 'AF_INET' here is temporary; we might need to change it later in + * connection_exit_connect(). */ n_stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET); /* Remember the tunneled request ID in the new edge connection, so that * we can measure download times. */ - TO_CONN(n_stream)->dirreq_id = circ->dirreq_id; - - n_stream->_base.purpose = EXIT_PURPOSE_CONNECT; + n_stream->dirreq_id = circ->dirreq_id; + n_stream->base_.purpose = EXIT_PURPOSE_CONNECT; + n_stream->begincell_flags = bcell.flags; n_stream->stream_id = rh.stream_id; - n_stream->_base.port = port; + n_stream->base_.port = port; /* leave n_stream->s at -1, because it's not yet valid */ n_stream->package_window = STREAMWINDOW_START; n_stream->deliver_window = STREAMWINDOW_START; @@ -3132,14 +2451,14 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->_base.address = tor_strdup("(rendezvous)"); - n_stream->_base.state = EXIT_CONN_STATE_CONNECTING; + n_stream->base_.address = tor_strdup("(rendezvous)"); + n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; n_stream->rend_data = rend_data_dup(origin_circ->rend_data); tor_assert(connection_edge_is_rendezvous_stream(n_stream)); assert_circuit_ok(circ); if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) { log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->_base.port); + n_stream->base_.port); relay_send_end_cell_from_edge(rh.stream_id, circ, END_STREAM_REASON_EXITPOLICY, origin_circ->cpath->prev); @@ -3158,12 +2477,16 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) assert_circuit_ok(circ); connection_exit_connect(n_stream); + + /* For path bias: This circuit was used successfully */ + origin_circ->path_state = PATH_STATE_USE_SUCCEEDED; + tor_free(address); return 0; } tor_strlower(address); - n_stream->_base.address = address; - n_stream->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; + n_stream->base_.address = address; + n_stream->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; /* default to failed, change in dns_resolve if it turns out not to fail */ if (we_are_hibernating()) { @@ -3176,9 +2499,12 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->on_circuit = circ; if (rh.command == RELAY_COMMAND_BEGIN_DIR) { + tor_addr_t tmp_addr; tor_assert(or_circ); - if (or_circ->p_conn && !tor_addr_is_null(&or_circ->p_conn->real_addr)) - tor_addr_copy(&n_stream->_base.addr, &or_circ->p_conn->real_addr); + if (or_circ->p_chan && + channel_get_addr_if_possible(or_circ->p_chan, &tmp_addr)) { + tor_addr_copy(&n_stream->base_.addr, &tmp_addr); + } return connection_exit_connect_dir(n_stream); } @@ -3228,12 +2554,12 @@ connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ) */ dummy_conn = edge_connection_new(CONN_TYPE_EXIT, AF_INET); dummy_conn->stream_id = rh.stream_id; - dummy_conn->_base.address = tor_strndup( + dummy_conn->base_.address = tor_strndup( (char*)cell->payload+RELAY_HEADER_SIZE, rh.length); - dummy_conn->_base.port = 0; - dummy_conn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; - dummy_conn->_base.purpose = EXIT_PURPOSE_RESOLVE; + dummy_conn->base_.port = 0; + dummy_conn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + dummy_conn->base_.purpose = EXIT_PURPOSE_RESOLVE; dummy_conn->on_circuit = TO_CIRCUIT(circ); @@ -3243,7 +2569,7 @@ connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ) /* Connection freed; don't touch it. */ return 0; case 1: /* The result was cached; a resolved cell was sent. */ - if (!dummy_conn->_base.marked_for_close) + if (!dummy_conn->base_.marked_for_close) connection_free(TO_CONN(dummy_conn)); return 0; case 0: /* resolve added to pending list */ @@ -3268,8 +2594,11 @@ connection_exit_connect(edge_connection_t *edge_conn) connection_t *conn = TO_CONN(edge_conn); int socket_error = 0; - if (!connection_edge_is_rendezvous_stream(edge_conn) && - router_compare_to_my_exit_policy(edge_conn)) { + if ( (!connection_edge_is_rendezvous_stream(edge_conn) && + router_compare_to_my_exit_policy(&edge_conn->base_.addr, + edge_conn->base_.port)) || + (tor_addr_family(&conn->addr) == AF_INET6 && + ! get_options()->IPv6Exit)) { log_info(LD_EXIT,"%s:%d failed exit policy. Closing.", escaped_safe_str_client(conn->address), conn->port); connection_edge_end(edge_conn, END_STREAM_REASON_EXITPOLICY); @@ -3281,6 +2610,9 @@ connection_exit_connect(edge_connection_t *edge_conn) addr = &conn->addr; port = conn->port; + if (tor_addr_family(addr) == AF_INET6) + conn->socket_family = AF_INET6; + log_debug(LD_EXIT,"about to try connecting"); switch (connection_connect(conn, conn->address, addr, port, &socket_error)) { case -1: { @@ -3317,21 +2649,21 @@ connection_exit_connect(edge_connection_t *edge_conn) RELAY_COMMAND_CONNECTED, NULL, 0); } else { /* normal stream */ - char connected_payload[20]; - int connected_payload_len; - if (tor_addr_family(&conn->addr) == AF_INET) { - set_uint32(connected_payload, tor_addr_to_ipv4n(&conn->addr)); - connected_payload_len = 4; - } else { - memcpy(connected_payload, tor_addr_to_in6_addr8(&conn->addr), 16); - connected_payload_len = 16; + uint8_t connected_payload[MAX_CONNECTED_CELL_PAYLOAD_LEN]; + int connected_payload_len = + connected_cell_format_payload(connected_payload, &conn->addr, + edge_conn->address_ttl); + if (connected_payload_len < 0) { + connection_edge_end(edge_conn, END_STREAM_REASON_INTERNAL); + circuit_detach_stream(circuit_get_by_edge_conn(edge_conn), edge_conn); + connection_free(conn); + return; } - set_uint32(connected_payload+connected_payload_len, - htonl(dns_clip_ttl(edge_conn->address_ttl))); - connected_payload_len += 4; + connection_edge_send_command(edge_conn, RELAY_COMMAND_CONNECTED, - connected_payload, connected_payload_len); + (char*)connected_payload, + connected_payload_len); } } @@ -3350,20 +2682,20 @@ connection_exit_connect_dir(edge_connection_t *exitconn) log_info(LD_EXIT, "Opening local connection for anonymized directory exit"); - exitconn->_base.state = EXIT_CONN_STATE_OPEN; + exitconn->base_.state = EXIT_CONN_STATE_OPEN; - dirconn = dir_connection_new(tor_addr_family(&exitconn->_base.addr)); + dirconn = dir_connection_new(tor_addr_family(&exitconn->base_.addr)); - tor_addr_copy(&dirconn->_base.addr, &exitconn->_base.addr); - dirconn->_base.port = 0; - dirconn->_base.address = tor_strdup(exitconn->_base.address); - dirconn->_base.type = CONN_TYPE_DIR; - dirconn->_base.purpose = DIR_PURPOSE_SERVER; - dirconn->_base.state = DIR_CONN_STATE_SERVER_COMMAND_WAIT; + tor_addr_copy(&dirconn->base_.addr, &exitconn->base_.addr); + dirconn->base_.port = 0; + dirconn->base_.address = tor_strdup(exitconn->base_.address); + dirconn->base_.type = CONN_TYPE_DIR; + dirconn->base_.purpose = DIR_PURPOSE_SERVER; + dirconn->base_.state = DIR_CONN_STATE_SERVER_COMMAND_WAIT; /* Note that the new dir conn belongs to the same tunneled request as * the edge conn, so that we can measure download times. */ - TO_CONN(dirconn)->dirreq_id = TO_CONN(exitconn)->dirreq_id; + dirconn->dirreq_id = exitconn->dirreq_id; connection_link_connections(TO_CONN(dirconn), TO_CONN(exitconn)); @@ -3446,11 +2778,15 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit) } if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) { - struct in_addr in; tor_addr_t addr, *addrp = NULL; addr_policy_result_t r; - if (tor_inet_aton(conn->socks_request->address, &in)) { - tor_addr_from_in(&addr, &in); + if (0 == tor_addr_parse(&addr, conn->socks_request->address)) { + addrp = &addr; + } else if (!conn->ipv4_traffic_ok && conn->ipv6_traffic_ok) { + tor_addr_make_null(&addr, AF_INET6); + addrp = &addr; + } else if (conn->ipv4_traffic_ok && !conn->ipv6_traffic_ok) { + tor_addr_make_null(&addr, AF_INET); addrp = &addr; } r = compare_tor_addr_to_node_policy(addrp, conn->socks_request->port,exit); @@ -3465,7 +2801,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit) if (!conn->chosen_exit_name && node_exit_policy_rejects_all(exit)) return 0; } - if (routerset_contains_node(options->_ExcludeExitNodesUnion, exit)) { + if (routerset_contains_node(options->ExcludeExitNodesUnion_, exit)) { /* Not a suitable exit. Refuse it. */ return 0; } diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index c320d6ba4..82cd95bed 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -9,13 +9,13 @@ * \brief Header file for connection_edge.c. **/ -#ifndef _TOR_CONNECTION_EDGE_H -#define _TOR_CONNECTION_EDGE_H +#ifndef TOR_CONNECTION_EDGE_H +#define TOR_CONNECTION_EDGE_H #define connection_mark_unattached_ap(conn, endreason) \ - _connection_mark_unattached_ap((conn), (endreason), __LINE__, _SHORT_FILE_) + connection_mark_unattached_ap_((conn), (endreason), __LINE__, SHORT_FILE__) -void _connection_mark_unattached_ap(entry_connection_t *conn, int endreason, +void connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, int line, const char *file); int connection_edge_reached_eof(edge_connection_t *conn); int connection_edge_process_inbuf(edge_connection_t *conn, @@ -67,30 +67,6 @@ int connection_ap_process_transparent(entry_connection_t *conn); int address_is_invalid_destination(const char *address, int client); -void addressmap_init(void); -void addressmap_clear_excluded_trackexithosts(const or_options_t *options); -void addressmap_clear_invalid_automaps(const or_options_t *options); -void addressmap_clean(time_t now); -void addressmap_clear_configured(void); -void addressmap_clear_transient(void); -void addressmap_free_all(void); -int addressmap_rewrite(char *address, size_t maxlen, time_t *expires_out, - addressmap_entry_source_t *exit_source_out); -int addressmap_have_mapping(const char *address, int update_timeout); - -void addressmap_register(const char *address, char *new_address, - time_t expires, addressmap_entry_source_t source, - const int address_wildcard, - const int new_address_wildcard); -int parse_virtual_addr_network(const char *val, int validate_only, - char **msg); -int client_dns_incr_failures(const char *address); -void client_dns_clear_failures(const char *address); -void client_dns_set_addressmap(const char *address, uint32_t val, - const char *exitname, int ttl); -const char *addressmap_register_virtual_address(int type, char *new_address); -void addressmap_get_mappings(smartlist_t *sl, time_t min_expires, - time_t max_expires, int want_expiry); int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); @@ -114,6 +90,52 @@ int connection_edge_update_circuit_isolation(const entry_connection_t *conn, origin_circuit_t *circ, int dry_run); void circuit_clear_isolation(origin_circuit_t *circ); +streamid_t get_unique_stream_id_by_circ(origin_circuit_t *circ); + +/** @name Begin-cell flags + * + * These flags are used in RELAY_BEGIN cells to change the default behavior + * of the cell. + * + * @{ + **/ +/** When this flag is set, the client is willing to get connected to IPv6 + * addresses */ +#define BEGIN_FLAG_IPV6_OK (1u<<0) +/** When this flag is set, the client DOES NOT support connecting to IPv4 + * addresses. (The sense of this flag is inverted from IPV6_OK, so that the + * old default behavior of Tor is equivalent to having all flags set to 0.) + **/ +#define BEGIN_FLAG_IPV4_NOT_OK (1u<<1) +/** When this flag is set, if we find both an IPv4 and an IPv6 address, + * we use the IPv6 address. Otherwise we use the IPv4 address. */ +#define BEGIN_FLAG_IPV6_PREFERRED (1u<<2) +/**@}*/ + +#ifdef CONNECTION_EDGE_PRIVATE + +/** A parsed BEGIN or BEGIN_DIR cell */ +typedef struct begin_cell_t { + /** The address the client has asked us to connect to, or NULL if this is + * a BEGIN_DIR cell*/ + char *address; + /** The flags specified in the BEGIN cell's body. One or more of + * BEGIN_FLAG_*. */ + uint32_t flags; + /** The client's requested port. */ + uint16_t port; + /** The client's requested Stream ID */ + uint16_t stream_id; + /** True iff this is a BEGIN_DIR cell. */ + unsigned is_begindir : 1; +} begin_cell_t; + +int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, + uint8_t *end_reason_out); +int connected_cell_format_payload(uint8_t *payload_out, + const tor_addr_t *addr, + uint32_t ttl); +#endif #endif diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 5eecee074..c2e4375db 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -12,14 +12,23 @@ #include "or.h" #include "buffers.h" +/* + * Define this so we get channel internal functions, since we're implementing + * part of a subclass (channel_tls_t). + */ +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuitstats.h" #include "command.h" #include "config.h" #include "connection.h" #include "connection_or.h" #include "control.h" #include "dirserv.h" +#include "entrynodes.h" #include "geoip.h" #include "main.h" #include "networkstatus.h" @@ -43,6 +52,17 @@ static int connection_or_check_valid_tls_handshake(or_connection_t *conn, static void connection_or_tls_renegotiated_cb(tor_tls_t *tls, void *_conn); +static unsigned int +connection_or_is_bad_for_new_circs(or_connection_t *or_conn); +static void connection_or_mark_bad_for_new_circs(or_connection_t *or_conn); + +/* + * Call this when changing connection state, so notifications to the owning + * channel can be handled. + */ + +static void connection_or_change_state(or_connection_t *conn, uint8_t state); + #ifdef USE_BUFFEREVENTS static void connection_or_handle_event_cb(struct bufferevent *bufev, short event, void *arg); @@ -127,8 +147,11 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) return; /* If the identity was set previously, remove the old mapping. */ - if (! tor_digest_is_zero(conn->identity_digest)) + if (! tor_digest_is_zero(conn->identity_digest)) { connection_or_remove_from_identity_map(conn); + if (conn->chan) + channel_clear_identity_digest(TLS_CHAN_TO_BASE(conn->chan)); + } memcpy(conn->identity_digest, digest, DIGEST_LEN); @@ -139,6 +162,10 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) tmp = digestmap_set(orconn_identity_map, digest, conn); conn->next_with_same_id = tmp; + /* Deal with channels */ + if (conn->chan) + channel_set_identity_digest(TLS_CHAN_TO_BASE(conn->chan), digest); + #if 1 /* Testing code to check for bugs in representation. */ for (; tmp; tmp = tmp->next_with_same_id) { @@ -282,6 +309,39 @@ connection_or_report_broken_states(int severity, int domain) smartlist_free(items); } +/** Call this to change or_connection_t states, so the owning channel_tls_t can + * be notified. + */ + +static void +connection_or_change_state(or_connection_t *conn, uint8_t state) +{ + uint8_t old_state; + + tor_assert(conn); + + old_state = conn->base_.state; + conn->base_.state = state; + + if (conn->chan) + channel_tls_handle_state_change_on_orconn(conn->chan, conn, + old_state, state); +} + +/** Return the number of circuits using an or_connection_t; this used to + * be an or_connection_t field, but it got moved to channel_t and we + * shouldn't maintain two copies. */ + +int +connection_or_get_num_circuits(or_connection_t *conn) +{ + tor_assert(conn); + + if (conn->chan) { + return channel_num_circuits(TLS_CHAN_TO_BASE(conn->chan)); + } else return 0; +} + /**************************************************************/ /** Pack the cell_t host-order structure <b>src</b> into network-order @@ -296,7 +356,7 @@ cell_pack(packed_cell_t *dst, const cell_t *src) { char *dest = dst->body; set_uint16(dest, htons(src->circ_id)); - *(uint8_t*)(dest+2) = src->command; + set_uint8(dest+2, src->command); memcpy(dest+3, src->payload, CELL_PAYLOAD_SIZE); } @@ -307,7 +367,7 @@ static void cell_unpack(cell_t *dest, const char *src) { dest->circ_id = ntohs(get_uint16(src)); - dest->command = *(uint8_t*)(src+2); + dest->command = get_uint8(src+2); memcpy(dest->payload, src+3, CELL_PAYLOAD_SIZE); } @@ -327,7 +387,7 @@ var_cell_t * var_cell_new(uint16_t payload_len) { size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len; - var_cell_t *cell = tor_malloc(size); + var_cell_t *cell = tor_malloc_zero(size); cell->payload_len = payload_len; cell->command = 0; cell->circ_id = 0; @@ -345,8 +405,11 @@ var_cell_free(var_cell_t *cell) int connection_or_reached_eof(or_connection_t *conn) { + tor_assert(conn); + log_info(LD_OR,"OR connection reached EOF. Closing."); - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_normally(conn, 1); + return 0; } @@ -366,7 +429,7 @@ connection_or_process_inbuf(or_connection_t *conn) int ret = 0; tor_assert(conn); - switch (conn->_base.state) { + switch (conn->base_.state) { case OR_CONN_STATE_PROXY_HANDSHAKING: ret = connection_read_proxy_handshake(TO_CONN(conn)); @@ -375,9 +438,12 @@ connection_or_process_inbuf(or_connection_t *conn) tor_assert(TO_CONN(conn)->proxy_state == PROXY_CONNECTED); if (connection_tls_start_handshake(conn, 0) < 0) ret = -1; + /* Touch the channel's active timestamp if there is one */ + if (conn->chan) + channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); } if (ret < 0) { - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); } return ret; @@ -385,7 +451,7 @@ connection_or_process_inbuf(or_connection_t *conn) #ifdef USE_BUFFEREVENTS if (tor_tls_server_got_renegotiate(conn->tls)) connection_or_tls_renegotiated_cb(conn->tls, conn); - if (conn->_base.marked_for_close) + if (conn->base_.marked_for_close) return 0; /* fall through. */ #endif @@ -403,14 +469,14 @@ connection_or_process_inbuf(or_connection_t *conn) * * XXX024 Remove this check once we verify that the above paragraph is * 100% true. */ - if (buf_datalen(conn->_base.inbuf) > MAX_OR_INBUF_WHEN_NONOPEN) { + if (buf_datalen(conn->base_.inbuf) > MAX_OR_INBUF_WHEN_NONOPEN) { log_fn(LOG_PROTOCOL_WARN, LD_NET, "Accumulated too much data (%d bytes) " "on nonopen OR connection %s %s:%u in state %s; closing.", - (int)buf_datalen(conn->_base.inbuf), + (int)buf_datalen(conn->base_.inbuf), connection_or_nonopen_was_started_here(conn) ? "to" : "from", - conn->_base.address, conn->_base.port, - conn_state_to_string(conn->_base.type, conn->_base.state)); - connection_mark_for_close(TO_CONN(conn)); + conn->base_.address, conn->base_.port, + conn_state_to_string(conn->base_.type, conn->base_.state)); + connection_or_close_for_error(conn, 0); ret = -1; } @@ -430,18 +496,31 @@ connection_or_process_inbuf(or_connection_t *conn) int connection_or_flushed_some(or_connection_t *conn) { - size_t datalen = connection_get_outbuf_len(TO_CONN(conn)); + size_t datalen, temp; + ssize_t n, flushed; + /* If we're under the low water mark, add cells until we're just over the * high water mark. */ + datalen = connection_get_outbuf_len(TO_CONN(conn)); if (datalen < OR_CONN_LOWWATER) { - ssize_t n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, CELL_NETWORK_SIZE); - time_t now = approx_time(); - while (conn->active_circuits && n > 0) { - int flushed; - flushed = connection_or_flush_from_first_active_circuit(conn, 1, now); - n -= flushed; + while ((conn->chan) && channel_tls_more_to_flush(conn->chan)) { + /* Compute how many more cells we want at most */ + n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, CELL_NETWORK_SIZE); + /* Bail out if we don't want any more */ + if (n <= 0) break; + /* We're still here; try to flush some more cells */ + flushed = channel_tls_flush_some_cells(conn->chan, n); + /* Bail out if it says it didn't flush anything */ + if (flushed <= 0) break; + /* How much in the outbuf now? */ + temp = connection_get_outbuf_len(TO_CONN(conn)); + /* Bail out if we didn't actually increase the outbuf size */ + if (temp <= datalen) break; + /* Update datalen for the next iteration */ + datalen = temp; } } + return 0; } @@ -459,14 +538,14 @@ connection_or_finished_flushing(or_connection_t *conn) tor_assert(conn); assert_connection_ok(TO_CONN(conn),0); - switch (conn->_base.state) { + switch (conn->base_.state) { case OR_CONN_STATE_PROXY_HANDSHAKING: case OR_CONN_STATE_OPEN: case OR_CONN_STATE_OR_HANDSHAKING_V2: case OR_CONN_STATE_OR_HANDSHAKING_V3: break; default: - log_err(LD_BUG,"Called in unexpected state %d.", conn->_base.state); + log_err(LD_BUG,"Called in unexpected state %d.", conn->base_.state); tor_fragile_assert(); return -1; } @@ -480,6 +559,7 @@ connection_or_finished_connecting(or_connection_t *or_conn) { const int proxy_type = or_conn->proxy_type; connection_t *conn; + tor_assert(or_conn); conn = TO_CONN(or_conn); tor_assert(conn->state == OR_CONN_STATE_CONNECTING); @@ -491,18 +571,18 @@ connection_or_finished_connecting(or_connection_t *or_conn) if (proxy_type != PROXY_NONE) { /* start proxy handshake */ if (connection_proxy_connect(conn, proxy_type) < 0) { - connection_mark_for_close(conn); + connection_or_close_for_error(or_conn, 0); return -1; } connection_start_reading(conn); - conn->state = OR_CONN_STATE_PROXY_HANDSHAKING; + connection_or_change_state(or_conn, OR_CONN_STATE_PROXY_HANDSHAKING); return 0; } if (connection_tls_start_handshake(or_conn, 0) < 0) { /* TLS handshaking error of some kind. */ - connection_mark_for_close(conn); + connection_or_close_for_error(or_conn, 0); return -1; } return 0; @@ -516,11 +596,14 @@ connection_or_about_to_close(or_connection_t *or_conn) time_t now = time(NULL); connection_t *conn = TO_CONN(or_conn); + /* Tell the controlling channel we're closed */ + if (or_conn->chan) { + channel_closed(TLS_CHAN_TO_BASE(or_conn->chan)); + or_conn->chan = NULL; + } + /* Remember why we're closing this connection. */ if (conn->state != OR_CONN_STATE_OPEN) { - /* Inform any pending (not attached) circs that they should - * give up. */ - circuit_n_conn_done(TO_OR_CONN(conn), 0); /* now mark things down as needed */ if (connection_or_nonopen_was_started_here(or_conn)) { const or_options_t *options = get_options(); @@ -548,9 +631,6 @@ connection_or_about_to_close(or_connection_t *or_conn) control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED, tls_error_to_orconn_end_reason(or_conn->tls_error)); } - /* Now close all the attached circuits on it. */ - circuit_unlink_all_from_or_conn(TO_OR_CONN(conn), - END_CIRC_REASON_OR_CONN_CLOSED); } /** Return 1 if identity digest <b>id_digest</b> is known to be a @@ -613,8 +693,8 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, cfg = ev_token_bucket_cfg_new(rate_per_tick, burst, rate_per_tick, burst, tick); old_cfg = conn->bucket_cfg; - if (conn->_base.bufev) - tor_set_bufferevent_rate_limit(conn->_base.bufev, cfg); + if (conn->base_.bufev) + tor_set_bufferevent_rate_limit(conn->base_.bufev, cfg); if (old_cfg) ev_token_bucket_cfg_free(old_cfg); conn->bucket_cfg = cfg; @@ -663,15 +743,15 @@ connection_or_init_conn_from_address(or_connection_t *conn, connection_or_set_identity_digest(conn, id_digest); connection_or_update_token_buckets_helper(conn, 1, get_options()); - conn->_base.port = port; - tor_addr_copy(&conn->_base.addr, addr); + conn->base_.port = port; + tor_addr_copy(&conn->base_.addr, addr); tor_addr_copy(&conn->real_addr, addr); if (r) { tor_addr_port_t node_ap; node_get_pref_orport(r, &node_ap); /* XXXX proposal 186 is making this more complex. For now, a conn is canonical when it uses the _preferred_ address. */ - if (tor_addr_eq(&conn->_base.addr, &node_ap.addr)) + if (tor_addr_eq(&conn->base_.addr, &node_ap.addr)) conn->is_canonical = 1; if (!started_here) { /* Override the addr/port, so our log messages will make sense. @@ -684,12 +764,12 @@ connection_or_init_conn_from_address(or_connection_t *conn, * right IP address and port 56244, that wouldn't be as helpful. now we * log the "right" port too, so we know if it's moria1 or moria2. */ - tor_addr_copy(&conn->_base.addr, &node_ap.addr); - conn->_base.port = node_ap.port; + tor_addr_copy(&conn->base_.addr, &node_ap.addr); + conn->base_.port = node_ap.port; } conn->nickname = tor_strdup(node_get_nickname(r)); - tor_free(conn->_base.address); - conn->_base.address = tor_dup_addr(&node_ap.addr); + tor_free(conn->base_.address); + conn->base_.address = tor_dup_addr(&node_ap.addr); } else { const char *n; /* If we're an authoritative directory server, we may know a @@ -703,157 +783,31 @@ connection_or_init_conn_from_address(or_connection_t *conn, base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1, conn->identity_digest, DIGEST_LEN); } - tor_free(conn->_base.address); - conn->_base.address = tor_dup_addr(addr); + tor_free(conn->base_.address); + conn->base_.address = tor_dup_addr(addr); } } -/** Return true iff <b>a</b> is "better" than <b>b</b> for new circuits. - * - * A more canonical connection is always better than a less canonical - * connection. That aside, a connection is better if it has circuits and the - * other does not, or if it was created more recently. - * - * Requires that both input connections are open; not is_bad_for_new_circs, - * and not impossibly non-canonical. - * - * If <b>forgive_new_connections</b> is true, then we do not call - * <b>a</b>better than <b>b</b> simply because b has no circuits, - * unless b is also relatively old. - */ -static int -connection_or_is_better(time_t now, - const or_connection_t *a, - const or_connection_t *b, - int forgive_new_connections) -{ - int newer; -/** Do not definitively deprecate a new connection with no circuits on it - * until this much time has passed. */ -#define NEW_CONN_GRACE_PERIOD (15*60) - - if (b->is_canonical && !a->is_canonical) - return 0; /* A canonical connection is better than a non-canonical - * one, no matter how new it is or which has circuits. */ - - newer = b->_base.timestamp_created < a->_base.timestamp_created; - - if ( - /* We prefer canonical connections regardless of newness. */ - (!b->is_canonical && a->is_canonical) || - /* If both have circuits we prefer the newer: */ - (b->n_circuits && a->n_circuits && newer) || - /* If neither has circuits we prefer the newer: */ - (!b->n_circuits && !a->n_circuits && newer)) - return 1; +/** These just pass all the is_bad_for_new_circs manipulation on to + * channel_t */ - /* If one has no circuits and the other does... */ - if (!b->n_circuits && a->n_circuits) { - /* Then it's bad, unless it's in its grace period and we're forgiving. */ - if (forgive_new_connections && - now < b->_base.timestamp_created + NEW_CONN_GRACE_PERIOD) - return 0; - else - return 1; - } +static unsigned int +connection_or_is_bad_for_new_circs(or_connection_t *or_conn) +{ + tor_assert(or_conn); - return 0; + if (or_conn->chan) + return channel_is_bad_for_new_circs(TLS_CHAN_TO_BASE(or_conn->chan)); + else return 0; } -/** Return the OR connection we should use to extend a circuit to the router - * whose identity is <b>digest</b>, and whose address we believe (or have been - * told in an extend cell) is <b>target_addr</b>. If there is no good - * connection, set *<b>msg_out</b> to a message describing the connection's - * state and our next action, and set <b>launch_out</b> to a boolean for - * whether we should launch a new connection or not. - */ -or_connection_t * -connection_or_get_for_extend(const char *digest, - const tor_addr_t *target_addr, - const char **msg_out, - int *launch_out) +static void +connection_or_mark_bad_for_new_circs(or_connection_t *or_conn) { - or_connection_t *conn, *best=NULL; - int n_inprogress_goodaddr = 0, n_old = 0, n_noncanonical = 0, n_possible = 0; - time_t now = approx_time(); - - tor_assert(msg_out); - tor_assert(launch_out); - - if (!orconn_identity_map) { - *msg_out = "Router not connected (nothing is). Connecting."; - *launch_out = 1; - return NULL; - } - - conn = digestmap_get(orconn_identity_map, digest); - - for (; conn; conn = conn->next_with_same_id) { - tor_assert(conn->_base.magic == OR_CONNECTION_MAGIC); - tor_assert(conn->_base.type == CONN_TYPE_OR); - tor_assert(tor_memeq(conn->identity_digest, digest, DIGEST_LEN)); - if (conn->_base.marked_for_close) - continue; - /* Never return a connection on which the other end appears to be - * a client. */ - if (conn->is_connection_with_client) { - continue; - } - /* Never return a non-open connection. */ - if (conn->_base.state != OR_CONN_STATE_OPEN) { - /* If the address matches, don't launch a new connection for this - * circuit. */ - if (!tor_addr_compare(&conn->real_addr, target_addr, CMP_EXACT)) - ++n_inprogress_goodaddr; - continue; - } - /* Never return a connection that shouldn't be used for circs. */ - if (conn->is_bad_for_new_circs) { - ++n_old; - continue; - } - /* Never return a non-canonical connection using a recent link protocol - * if the address is not what we wanted. - * - * (For old link protocols, we can't rely on is_canonical getting - * set properly if we're talking to the right address, since we might - * have an out-of-date descriptor, and we will get no NETINFO cell to - * tell us about the right address.) */ - if (!conn->is_canonical && conn->link_proto >= 2 && - tor_addr_compare(&conn->real_addr, target_addr, CMP_EXACT)) { - ++n_noncanonical; - continue; - } - - ++n_possible; - - if (!best) { - best = conn; /* If we have no 'best' so far, this one is good enough. */ - continue; - } - - if (connection_or_is_better(now, conn, best, 0)) - best = conn; - } + tor_assert(or_conn); - if (best) { - *msg_out = "Connection is fine; using it."; - *launch_out = 0; - return best; - } else if (n_inprogress_goodaddr) { - *msg_out = "Connection in progress; waiting."; - *launch_out = 0; - return NULL; - } else if (n_old || n_noncanonical) { - *msg_out = "Connections all too old, or too non-canonical. " - " Launching a new one."; - *launch_out = 1; - return NULL; - } else { - *msg_out = "Not connected. Connecting."; - *launch_out = 1; - return NULL; - } + if (or_conn->chan) + channel_mark_bad_for_new_circs(TLS_CHAN_TO_BASE(or_conn->chan)); } /** How old do we let a connection to an OR get before deciding it's @@ -874,8 +828,8 @@ connection_or_get_for_extend(const char *digest, * - all open non-canonical connections for which a 'better' non-canonical * connection exists to the same router at the same address. * - * See connection_or_is_better() for our idea of what makes one OR connection - * better than another. + * See channel_is_better() in channel.c for our idea of what makes one OR + * connection better than another. */ static void connection_or_group_set_badness(or_connection_t *head, int force) @@ -887,23 +841,23 @@ connection_or_group_set_badness(or_connection_t *head, int force) /* Pass 1: expire everything that's old, and see what the status of * everything else is. */ for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) { - if (or_conn->_base.marked_for_close || - or_conn->is_bad_for_new_circs) + if (or_conn->base_.marked_for_close || + connection_or_is_bad_for_new_circs(or_conn)) continue; if (force || - or_conn->_base.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD + or_conn->base_.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD < now) { log_info(LD_OR, "Marking OR conn to %s:%d as too old for new circuits " - "(fd %d, %d secs old).", - or_conn->_base.address, or_conn->_base.port, or_conn->_base.s, - (int)(now - or_conn->_base.timestamp_created)); - or_conn->is_bad_for_new_circs = 1; + "(fd "TOR_SOCKET_T_FORMAT", %d secs old).", + or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, + (int)(now - or_conn->base_.timestamp_created)); + connection_or_mark_bad_for_new_circs(or_conn); } - if (or_conn->is_bad_for_new_circs) { + if (connection_or_is_bad_for_new_circs(or_conn)) { ++n_old; - } else if (or_conn->_base.state != OR_CONN_STATE_OPEN) { + } else if (or_conn->base_.state != OR_CONN_STATE_OPEN) { ++n_inprogress; } else if (or_conn->is_canonical) { ++n_canonical; @@ -915,10 +869,10 @@ connection_or_group_set_badness(or_connection_t *head, int force) /* Pass 2: We know how about how good the best connection is. * expire everything that's worse, and find the very best if we can. */ for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) { - if (or_conn->_base.marked_for_close || - or_conn->is_bad_for_new_circs) + if (or_conn->base_.marked_for_close || + connection_or_is_bad_for_new_circs(or_conn)) continue; /* This one doesn't need to be marked bad. */ - if (or_conn->_base.state != OR_CONN_STATE_OPEN) + if (or_conn->base_.state != OR_CONN_STATE_OPEN) continue; /* Don't mark anything bad until we have seen what happens * when the connection finishes. */ if (n_canonical && !or_conn->is_canonical) { @@ -926,16 +880,21 @@ connection_or_group_set_badness(or_connection_t *head, int force) * and this one is open but not canonical. Mark it bad. */ log_info(LD_OR, "Marking OR conn to %s:%d as unsuitable for new circuits: " - "(fd %d, %d secs old). It is not canonical, and we have " - "another connection to that OR that is.", - or_conn->_base.address, or_conn->_base.port, or_conn->_base.s, - (int)(now - or_conn->_base.timestamp_created)); - or_conn->is_bad_for_new_circs = 1; + "(fd "TOR_SOCKET_T_FORMAT", %d secs old). It is not " + "canonical, and we have another connection to that OR that is.", + or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, + (int)(now - or_conn->base_.timestamp_created)); + connection_or_mark_bad_for_new_circs(or_conn); continue; } - if (!best || connection_or_is_better(now, or_conn, best, 0)) + if (!best || + channel_is_better(now, + TLS_CHAN_TO_BASE(or_conn->chan), + TLS_CHAN_TO_BASE(best->chan), + 0)) { best = or_conn; + } } if (!best) @@ -956,32 +915,37 @@ connection_or_group_set_badness(or_connection_t *head, int force) * "mostly harmless", so a fix can wait until somebody is bored. */ for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) { - if (or_conn->_base.marked_for_close || - or_conn->is_bad_for_new_circs || - or_conn->_base.state != OR_CONN_STATE_OPEN) + if (or_conn->base_.marked_for_close || + connection_or_is_bad_for_new_circs(or_conn) || + or_conn->base_.state != OR_CONN_STATE_OPEN) continue; - if (or_conn != best && connection_or_is_better(now, best, or_conn, 1)) { + if (or_conn != best && + channel_is_better(now, + TLS_CHAN_TO_BASE(best->chan), + TLS_CHAN_TO_BASE(or_conn->chan), 1)) { /* This isn't the best conn, _and_ the best conn is better than it, even when we're being forgiving. */ if (best->is_canonical) { log_info(LD_OR, "Marking OR conn to %s:%d as unsuitable for new circuits: " - "(fd %d, %d secs old). We have a better canonical one " - "(fd %d; %d secs old).", - or_conn->_base.address, or_conn->_base.port, or_conn->_base.s, - (int)(now - or_conn->_base.timestamp_created), - best->_base.s, (int)(now - best->_base.timestamp_created)); - or_conn->is_bad_for_new_circs = 1; + "(fd "TOR_SOCKET_T_FORMAT", %d secs old). " + "We have a better canonical one " + "(fd "TOR_SOCKET_T_FORMAT"; %d secs old).", + or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, + (int)(now - or_conn->base_.timestamp_created), + best->base_.s, (int)(now - best->base_.timestamp_created)); + connection_or_mark_bad_for_new_circs(or_conn); } else if (!tor_addr_compare(&or_conn->real_addr, &best->real_addr, CMP_EXACT)) { log_info(LD_OR, "Marking OR conn to %s:%d as unsuitable for new circuits: " - "(fd %d, %d secs old). We have a better one with the " - "same address (fd %d; %d secs old).", - or_conn->_base.address, or_conn->_base.port, or_conn->_base.s, - (int)(now - or_conn->_base.timestamp_created), - best->_base.s, (int)(now - best->_base.timestamp_created)); - or_conn->is_bad_for_new_circs = 1; + "(fd "TOR_SOCKET_T_FORMAT", %d secs old). We have a better " + "one with the " + "same address (fd "TOR_SOCKET_T_FORMAT"; %d secs old).", + or_conn->base_.address, or_conn->base_.port, or_conn->base_.s, + (int)(now - or_conn->base_.timestamp_created), + best->base_.s, (int)(now - best->base_.timestamp_created)); + connection_or_mark_bad_for_new_circs(or_conn); } } } @@ -1019,8 +983,41 @@ connection_or_connect_failed(or_connection_t *conn, control_event_bootstrap_problem(msg, reason); } +/** <b>conn</b> got an error in connection_handle_read_impl() or + * connection_handle_write_impl() and is going to die soon. + * + * <b>reason</b> specifies the or_conn_end_reason for the failure; + * <b>msg</b> specifies the strerror-style error message. + */ +void +connection_or_notify_error(or_connection_t *conn, + int reason, const char *msg) +{ + channel_t *chan; + + tor_assert(conn); + + /* If we're connecting, call connect_failed() too */ + if (TO_CONN(conn)->state == OR_CONN_STATE_CONNECTING) + connection_or_connect_failed(conn, reason, msg); + + /* Tell the controlling channel if we have one */ + if (conn->chan) { + chan = TLS_CHAN_TO_BASE(conn->chan); + /* Don't transition if we're already in closing, closed or error */ + if (!(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)) { + channel_close_for_error(chan); + } + } + + /* No need to mark for error because connection.c is about to do that */ +} + /** Launch a new OR connection to <b>addr</b>:<b>port</b> and expect to - * handshake with an OR with identity digest <b>id_digest</b>. + * handshake with an OR with identity digest <b>id_digest</b>. Optionally, + * pass in a pointer to a channel using this connection. * * If <b>id_digest</b> is me, do nothing. If we're already connected to it, * return that connection. If the connect() is in progress, set the @@ -1035,7 +1032,8 @@ connection_or_connect_failed(or_connection_t *conn, */ or_connection_t * connection_or_connect(const tor_addr_t *_addr, uint16_t port, - const char *id_digest) + const char *id_digest, + channel_tls_t *chan) { or_connection_t *conn; const or_options_t *options = get_options(); @@ -1058,9 +1056,17 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, conn = or_connection_new(tor_addr_family(&addr)); - /* set up conn so it's got all the data we need to remember */ + /* + * Set up conn so it's got all the data we need to remember for channels + * + * This stuff needs to happen before connection_or_init_conn_from_address() + * so connection_or_set_identity_digest() and such know where to look to + * keep the channel up to date. + */ + conn->chan = chan; + chan->conn = conn; connection_or_init_conn_from_address(conn, &addr, port, id_digest, 1); - conn->_base.state = OR_CONN_STATE_CONNECTING; + connection_or_change_state(conn, OR_CONN_STATE_CONNECTING); control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0); conn->is_outgoing = 1; @@ -1072,7 +1078,7 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, if (proxy_type != PROXY_NONE) { tor_addr_copy(&addr, &proxy_addr); port = proxy_port; - conn->_base.proxy_state = PROXY_INFANT; + conn->base_.proxy_state = PROXY_INFANT; } } else { /* get_proxy_addrport() might fail if we have a Bridge line that @@ -1084,29 +1090,29 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, TO_CONN(conn)->port); if (transport_name) { - log_warn(LD_GENERAL, "We were supposed to connect to bridge '%s:%u' " + log_warn(LD_GENERAL, "We were supposed to connect to bridge '%s' " "using pluggable transport '%s', but we can't find a pluggable " "transport proxy supporting '%s'. This can happen if you " "haven't provided a ClientTransportPlugin line, or if " "your pluggable transport proxy stopped running.", - fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port, + fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port), transport_name, transport_name); } else { - log_warn(LD_GENERAL, "Tried to connect to '%s:%u' through a proxy, but " + log_warn(LD_GENERAL, "Tried to connect to '%s' through a proxy, but " "the proxy address could not be found.", - fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); + fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port)); } connection_free(TO_CONN(conn)); return NULL; } - switch (connection_connect(TO_CONN(conn), conn->_base.address, + switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr, port, &socket_error)) { case -1: /* If the connection failed immediately, and we're using * a proxy, our proxy is down. Don't blame the Tor server. */ - if (conn->_base.proxy_state == PROXY_INFANT) + if (conn->base_.proxy_state == PROXY_INFANT) entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); connection_or_connect_failed(conn, @@ -1129,6 +1135,52 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, return conn; } +/** Mark orconn for close and transition the associated channel, if any, to + * the closing state. + */ + +void +connection_or_close_normally(or_connection_t *orconn, int flush) +{ + channel_t *chan = NULL; + + tor_assert(orconn); + if (flush) connection_mark_and_flush_internal(TO_CONN(orconn)); + else connection_mark_for_close_internal(TO_CONN(orconn)); + if (orconn->chan) { + chan = TLS_CHAN_TO_BASE(orconn->chan); + /* Don't transition if we're already in closing, closed or error */ + if (!(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)) { + channel_close_from_lower_layer(chan); + } + } +} + +/** Mark orconn for close and transition the associated channel, if any, to + * the error state. + */ + +void +connection_or_close_for_error(or_connection_t *orconn, int flush) +{ + channel_t *chan = NULL; + + tor_assert(orconn); + if (flush) connection_mark_and_flush_internal(TO_CONN(orconn)); + else connection_mark_for_close_internal(TO_CONN(orconn)); + if (orconn->chan) { + chan = TLS_CHAN_TO_BASE(orconn->chan); + /* Don't transition if we're already in closing, closed or error */ + if (!(chan->state == CHANNEL_STATE_CLOSING || + chan->state == CHANNEL_STATE_CLOSED || + chan->state == CHANNEL_STATE_ERROR)) { + channel_close_for_error(chan); + } + } +} + /** Begin the tls handshake with <b>conn</b>. <b>receiving</b> is 0 if * we initiated the connection, else it's 1. * @@ -1140,29 +1192,46 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, int connection_tls_start_handshake(or_connection_t *conn, int receiving) { - conn->_base.state = OR_CONN_STATE_TLS_HANDSHAKING; + channel_listener_t *chan_listener; + channel_t *chan; + + /* Incoming connections will need a new channel passed to the + * channel_tls_listener */ + if (receiving) { + /* It shouldn't already be set */ + tor_assert(!(conn->chan)); + chan_listener = channel_tls_get_listener(); + if (!chan_listener) { + chan_listener = channel_tls_start_listener(); + command_setup_listener(chan_listener); + } + chan = channel_tls_handle_incoming(conn); + channel_listener_queue_incoming(chan_listener, chan); + } + + connection_or_change_state(conn, OR_CONN_STATE_TLS_HANDSHAKING); tor_assert(!conn->tls); - conn->tls = tor_tls_new(conn->_base.s, receiving); + conn->tls = tor_tls_new(conn->base_.s, receiving); if (!conn->tls) { log_warn(LD_BUG,"tor_tls_new failed. Closing."); return -1; } tor_tls_set_logged_address(conn->tls, // XXX client and relay? - escaped_safe_str(conn->_base.address)); + escaped_safe_str(conn->base_.address)); #ifdef USE_BUFFEREVENTS if (connection_type_uses_bufferevent(TO_CONN(conn))) { - const int filtering = get_options()->_UseFilteringSSLBufferevents; + const int filtering = get_options()->UseFilteringSSLBufferevents; struct bufferevent *b = - tor_tls_init_bufferevent(conn->tls, conn->_base.bufev, conn->_base.s, + tor_tls_init_bufferevent(conn->tls, conn->base_.bufev, conn->base_.s, receiving, filtering); if (!b) { log_warn(LD_BUG,"tor_tls_init_bufferevent failed. Closing."); return -1; } - conn->_base.bufev = b; + conn->base_.bufev = b; if (conn->bucket_cfg) - tor_set_bufferevent_rate_limit(conn->_base.bufev, conn->bucket_cfg); + tor_set_bufferevent_rate_limit(conn->base_.bufev, conn->bucket_cfg); connection_enable_rate_limiting(TO_CONN(conn)); connection_configure_bufferevent_callbacks(TO_CONN(conn)); @@ -1174,7 +1243,8 @@ connection_tls_start_handshake(or_connection_t *conn, int receiving) } #endif connection_start_reading(TO_CONN(conn)); - log_debug(LD_HANDSHAKE,"starting TLS handshake on fd %d", conn->_base.s); + log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT, + conn->base_.s); note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); IF_HAS_BUFFEREVENT(TO_CONN(conn), { @@ -1211,7 +1281,7 @@ connection_or_tls_renegotiated_cb(tor_tls_t *tls, void *_conn) if (connection_tls_finish_handshake(conn) < 0) { /* XXXX_TLS double-check that it's ok to do this from inside read. */ /* XXXX_TLS double-check that this verifies certificates. */ - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); } } @@ -1226,12 +1296,12 @@ connection_tls_continue_handshake(or_connection_t *conn) int result; check_no_tls_errors(); again: - if (conn->_base.state == OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING) { + if (conn->base_.state == OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING) { // log_notice(LD_OR, "Renegotiate with %p", conn->tls); result = tor_tls_renegotiate(conn->tls); // log_notice(LD_OR, "Result: %d", result); } else { - tor_assert(conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING); + tor_assert(conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING); // log_notice(LD_OR, "Continue handshake with %p", conn->tls); result = tor_tls_handshake(conn->tls); // log_notice(LD_OR, "Result: %d", result); @@ -1244,19 +1314,21 @@ connection_tls_continue_handshake(or_connection_t *conn) case TOR_TLS_DONE: if (! tor_tls_used_v1_handshake(conn->tls)) { if (!tor_tls_is_server(conn->tls)) { - if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) { if (tor_tls_received_v3_certificate(conn->tls)) { log_info(LD_OR, "Client got a v3 cert! Moving on to v3 " - "handshake."); + "handshake with ciphersuite %s", + tor_tls_get_ciphersuite_name(conn->tls)); return connection_or_launch_v3_or_handshake(conn); } else { log_debug(LD_OR, "Done with initial SSL handshake (client-side)." " Requesting renegotiation."); - conn->_base.state = OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING; + connection_or_change_state(conn, + OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING); goto again; } } - // log_notice(LD_OR,"Done. state was %d.", conn->_base.state); + // log_notice(LD_OR,"Done. state was %d.", conn->base_.state); } else { /* v2/v3 handshake, but not a client. */ log_debug(LD_OR, "Done with initial SSL handshake (server-side). " @@ -1264,7 +1336,8 @@ connection_tls_continue_handshake(or_connection_t *conn) tor_tls_set_renegotiate_callback(conn->tls, connection_or_tls_renegotiated_cb, conn); - conn->_base.state = OR_CONN_STATE_TLS_SERVER_RENEGOTIATING; + connection_or_change_state(conn, + OR_CONN_STATE_TLS_SERVER_RENEGOTIATING); connection_stop_writing(TO_CONN(conn)); connection_start_reading(TO_CONN(conn)); return 0; @@ -1294,28 +1367,29 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, /* XXXX cut-and-paste code; should become a function. */ if (event & BEV_EVENT_CONNECTED) { - if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) { if (tor_tls_finish_handshake(conn->tls) < 0) { log_warn(LD_OR, "Problem finishing handshake"); - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); return; } } if (! tor_tls_used_v1_handshake(conn->tls)) { if (!tor_tls_is_server(conn->tls)) { - if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) { if (tor_tls_received_v3_certificate(conn->tls)) { log_info(LD_OR, "Client got a v3 cert!"); if (connection_or_launch_v3_or_handshake(conn) < 0) - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); return; } else { - conn->_base.state = OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING; + connection_or_change_state(conn, + OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING); tor_tls_unblock_renegotiation(conn->tls); - if (bufferevent_ssl_renegotiate(conn->_base.bufev)<0) { + if (bufferevent_ssl_renegotiate(conn->base_.bufev)<0) { log_warn(LD_OR, "Start_renegotiating went badly."); - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); } tor_tls_unblock_renegotiation(conn->tls); return; /* ???? */ @@ -1330,7 +1404,8 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, tor_tls_set_renegotiate_callback(conn->tls, connection_or_tls_renegotiated_cb, conn); - conn->_base.state = OR_CONN_STATE_TLS_SERVER_RENEGOTIATING; + connection_or_change_state(conn, + OR_CONN_STATE_TLS_SERVER_RENEGOTIATING); } else if (handshakes == 2) { /* v2 handshake, as a server. Two handshakes happened already, * so we treat renegotiation as done. @@ -1339,18 +1414,18 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, } else if (handshakes > 2) { log_warn(LD_OR, "More than two handshakes done on connection. " "Closing."); - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); } else { log_warn(LD_BUG, "We were unexpectedly told that a connection " "got %d handshakes. Closing.", handshakes); - connection_mark_for_close(TO_CONN(conn)); + connection_or_close_for_error(conn, 0); } return; } } connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); if (connection_tls_finish_handshake(conn) < 0) - connection_mark_for_close(TO_CONN(conn)); /* ???? */ + connection_or_close_for_error(conn, 0); /* ???? */ return; } @@ -1372,7 +1447,7 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, int connection_or_nonopen_was_started_here(or_connection_t *conn) { - tor_assert(conn->_base.type == CONN_TYPE_OR); + tor_assert(conn->base_.type == CONN_TYPE_OR); if (!conn->tls) return 1; /* it's still in proxy states or something */ if (conn->handshake_state) @@ -1380,29 +1455,6 @@ connection_or_nonopen_was_started_here(or_connection_t *conn) return !tor_tls_is_server(conn->tls); } -/** Set the circid_type field of <b>conn</b> (which determines which part of - * the circuit ID space we're willing to use) based on comparing our ID to - * <b>identity_rcvd</b> */ -void -connection_or_set_circid_type(or_connection_t *conn, - crypto_pk_t *identity_rcvd) -{ - const int started_here = connection_or_nonopen_was_started_here(conn); - crypto_pk_t *our_identity = - started_here ? get_tlsclient_identity_key() : - get_server_identity_key(); - - if (identity_rcvd) { - if (crypto_pk_cmp_keys(our_identity, identity_rcvd)<0) { - conn->circ_id_type = CIRC_ID_TYPE_LOWER; - } else { - conn->circ_id_type = CIRC_ID_TYPE_HIGHER; - } - } else { - conn->circ_id_type = CIRC_ID_TYPE_NEITHER; - } -} - /** <b>Conn</b> just completed its handshake. Return 0 if all is well, and * return -1 if he is lying, broken, or otherwise something is wrong. * @@ -1437,8 +1489,8 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, const or_options_t *options = get_options(); int severity = server_mode(options) ? LOG_PROTOCOL_WARN : LOG_WARN; const char *safe_address = - started_here ? conn->_base.address : - safe_str_client(conn->_base.address); + started_here ? conn->base_.address : + safe_str_client(conn->base_.address); const char *conn_type = started_here ? "outgoing" : "incoming"; int has_cert = 0; @@ -1447,7 +1499,7 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, if (started_here && !has_cert) { log_info(LD_HANDSHAKE,"Tried connecting to router at %s:%d, but it didn't " "send a cert! Closing.", - safe_address, conn->_base.port); + safe_address, conn->base_.port); return -1; } else if (!has_cert) { log_debug(LD_HANDSHAKE,"Got incoming connection with no certificate. " @@ -1461,7 +1513,7 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, if (started_here && v<0) { log_fn(severity,LD_HANDSHAKE,"Tried connecting to router at %s:%d: It" " has a cert but it's invalid. Closing.", - safe_address, conn->_base.port); + safe_address, conn->base_.port); return -1; } else if (v<0) { log_info(LD_HANDSHAKE,"Incoming connection gave us an invalid cert " @@ -1469,7 +1521,7 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, } else { log_debug(LD_HANDSHAKE, "The certificate seems to be valid on %s connection " - "with %s:%d", conn_type, safe_address, conn->_base.port); + "with %s:%d", conn_type, safe_address, conn->base_.port); } check_no_tls_errors(); } @@ -1480,7 +1532,8 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, memset(digest_rcvd_out, 0, DIGEST_LEN); } - connection_or_set_circid_type(conn, identity_rcvd); + tor_assert(conn->chan); + channel_set_circid_type(TLS_CHAN_TO_BASE(conn->chan), identity_rcvd); crypto_pk_free(identity_rcvd); if (started_here) @@ -1521,10 +1574,10 @@ connection_or_client_learned_peer_id(or_connection_t *conn, conn->identity_digest, DIGEST_LEN); log_info(LD_HANDSHAKE, "Connected to router %s at %s:%d without knowing " "its key. Hoping for the best.", - conn->nickname, conn->_base.address, conn->_base.port); + conn->nickname, conn->base_.address, conn->base_.port); /* if it's a bridge and we didn't know its identity fingerprint, now * we do -- remember it for future attempts. */ - learned_router_identity(&conn->_base.addr, conn->_base.port, + learned_router_identity(&conn->base_.addr, conn->base_.port, (const char*)peer_id); } @@ -1538,7 +1591,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, log_fn(severity, LD_HANDSHAKE, "Tried connecting to router at %s:%d, but identity key was not " "as expected: wanted %s but got %s.", - conn->_base.address, conn->_base.port, expected, seen); + conn->base_.address, conn->base_.port, expected, seen); entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, @@ -1550,13 +1603,26 @@ 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); } return 0; } +/** Return when a client used this, for connection.c, since client_used + * is now one of the timestamps of channel_t */ + +time_t +connection_or_client_used(or_connection_t *conn) +{ + tor_assert(conn); + + if (conn->chan) { + return channel_when_last_client(TLS_CHAN_TO_BASE(conn->chan)); + } else return 0; +} + /** The v1/v2 TLS handshake is finished. * * Make sure we are happy with the person we just handshaked with. @@ -1576,10 +1642,12 @@ connection_tls_finish_handshake(or_connection_t *conn) char digest_rcvd[DIGEST_LEN]; int started_here = connection_or_nonopen_was_started_here(conn); - log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done. verifying.", + log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done, using " + "ciphersuite %s. verifying.", started_here?"outgoing":"incoming", conn, - safe_str_client(conn->_base.address)); + safe_str_client(conn->base_.address), + tor_tls_get_ciphersuite_name(conn->tls)); directory_set_dirty(); @@ -1592,18 +1660,18 @@ connection_tls_finish_handshake(or_connection_t *conn) if (tor_tls_used_v1_handshake(conn->tls)) { conn->link_proto = 1; if (!started_here) { - connection_or_init_conn_from_address(conn, &conn->_base.addr, - conn->_base.port, digest_rcvd, 0); + connection_or_init_conn_from_address(conn, &conn->base_.addr, + conn->base_.port, digest_rcvd, 0); } tor_tls_block_renegotiation(conn->tls); return connection_or_set_state_open(conn); } else { - conn->_base.state = OR_CONN_STATE_OR_HANDSHAKING_V2; + connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V2); if (connection_init_or_handshake_state(conn, started_here) < 0) return -1; if (!started_here) { - connection_or_init_conn_from_address(conn, &conn->_base.addr, - conn->_base.port, digest_rcvd, 0); + connection_or_init_conn_from_address(conn, &conn->base_.addr, + conn->base_.port, digest_rcvd, 0); } return connection_or_send_versions(conn, 0); } @@ -1623,7 +1691,7 @@ connection_or_launch_v3_or_handshake(or_connection_t *conn) circuit_build_times_network_is_live(&circ_times); - conn->_base.state = OR_CONN_STATE_OR_HANDSHAKING_V3; + connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V3); if (connection_init_or_handshake_state(conn, 1) < 0) return -1; @@ -1742,35 +1810,9 @@ or_handshake_state_record_var_cell(or_handshake_state_t *state, int connection_or_set_state_open(or_connection_t *conn) { - int started_here = connection_or_nonopen_was_started_here(conn); - time_t now = time(NULL); - conn->_base.state = OR_CONN_STATE_OPEN; + connection_or_change_state(conn, OR_CONN_STATE_OPEN); control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED, 0); - if (started_here) { - circuit_build_times_network_is_live(&circ_times); - rep_hist_note_connect_succeeded(conn->identity_digest, now); - if (entry_guard_register_connect_status(conn->identity_digest, - 1, 0, now) < 0) { - /* Close any circuits pending on this conn. We leave it in state - * 'open' though, because it didn't actually *fail* -- we just - * chose not to use it. (Otherwise - * connection_about_to_close_connection() will call a big pile of - * functions to indicate we shouldn't try it again.) */ - log_debug(LD_OR, "New entry guard was reachable, but closing this " - "connection so we can retry the earlier entry guards."); - circuit_n_conn_done(conn, 0); - return -1; - } - router_set_status(conn->identity_digest, 1); - } else { - /* only report it to the geoip module if it's not a known router */ - if (!router_get_by_id_digest(conn->identity_digest)) { - geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &TO_CONN(conn)->addr, - now); - } - } - or_handshake_state_free(conn->handshake_state); conn->handshake_state = NULL; IF_HAS_BUFFEREVENT(TO_CONN(conn), { @@ -1779,8 +1821,6 @@ connection_or_set_state_open(or_connection_t *conn) connection_start_reading(TO_CONN(conn)); } - circuit_n_conn_done(conn, 1); /* send the pending creates, if any. */ - return 0; } @@ -1800,7 +1840,11 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) connection_write_to_buf(networkcell.body, CELL_NETWORK_SIZE, TO_CONN(conn)); - if (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) + /* Touch the channel's active timestamp if there is one */ + if (conn->chan) + channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); + + if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_cell(conn->handshake_state, cell, 0); if (cell->command != CELL_PADDING) @@ -1822,10 +1866,14 @@ connection_or_write_var_cell_to_buf(const var_cell_t *cell, connection_write_to_buf(hdr, sizeof(hdr), TO_CONN(conn)); connection_write_to_buf((char*)cell->payload, cell->payload_len, TO_CONN(conn)); - if (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3) + if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_var_cell(conn->handshake_state, cell, 0); if (cell->command != CELL_PADDING) conn->timestamp_last_added_nonpadding = approx_time(); + + /* Touch the channel's active timestamp if there is one */ + if (conn->chan) + channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); } /** See whether there's a variable-length cell waiting on <b>or_conn</b>'s @@ -1856,14 +1904,20 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) while (1) { log_debug(LD_OR, - "%d: starting, inbuf_datalen %d (%d pending in tls object).", - conn->_base.s,(int)connection_get_inbuf_len(TO_CONN(conn)), + TOR_SOCKET_T_FORMAT": starting, inbuf_datalen %d " + "(%d pending in tls object).", + conn->base_.s,(int)connection_get_inbuf_len(TO_CONN(conn)), tor_tls_get_pending_bytes(conn->tls)); if (connection_fetch_var_cell_from_buf(conn, &var_cell)) { if (!var_cell) return 0; /* not yet. */ + + /* Touch the channel's active timestamp if there is one */ + if (conn->chan) + channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); + circuit_build_times_network_is_live(&circ_times); - command_process_var_cell(var_cell, conn); + channel_tls_handle_var_cell(var_cell, conn); var_cell_free(var_cell); } else { char buf[CELL_NETWORK_SIZE]; @@ -1872,6 +1926,10 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) < CELL_NETWORK_SIZE) /* whole response available? */ return 0; /* not yet */ + /* Touch the channel's active timestamp if there is one */ + if (conn->chan) + channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); + circuit_build_times_network_is_live(&circ_times); connection_fetch_from_buf(buf, CELL_NETWORK_SIZE, TO_CONN(conn)); @@ -1879,34 +1937,11 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) * network-order string) */ cell_unpack(&cell, buf); - command_process_cell(&cell, conn); + channel_tls_handle_cell(&cell, conn); } } } -/** Write a destroy cell with circ ID <b>circ_id</b> and reason <b>reason</b> - * onto OR connection <b>conn</b>. Don't perform range-checking on reason: - * we may want to propagate reasons from other cells. - * - * Return 0. - */ -int -connection_or_send_destroy(circid_t circ_id, or_connection_t *conn, int reason) -{ - cell_t cell; - - tor_assert(conn); - - memset(&cell, 0, sizeof(cell_t)); - cell.circ_id = circ_id; - cell.command = CELL_DESTROY; - cell.payload[0] = (uint8_t) reason; - log_debug(LD_OR,"Sending destroy (circID %d).", circ_id); - - connection_or_write_cell_to_buf(&cell, conn); - return 0; -} - /** Array of recognized link protocol versions. */ static const uint16_t or_protocol_versions[] = { 1, 2, 3 }; /** Number of versions in <b>or_protocol_versions</b>. */ @@ -1984,10 +2019,10 @@ connection_or_send_netinfo(or_connection_t *conn) /* Their address. */ out = cell.payload + 4; /* We use &conn->real_addr below, unless it hasn't yet been set. If it - * hasn't yet been set, we know that _base.addr hasn't been tampered with + * hasn't yet been set, we know that base_.addr hasn't been tampered with * yet either. */ len = append_address_to_payload(out, !tor_addr_is_null(&conn->real_addr) - ? &conn->real_addr : &conn->_base.addr); + ? &conn->real_addr : &conn->base_.addr); if (len<0) return -1; out += len; @@ -1998,12 +2033,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; } @@ -2027,7 +2069,7 @@ connection_or_send_certs_cell(or_connection_t *conn) ssize_t pos; int server_mode; - tor_assert(conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3); + tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3); if (! conn->handshake_state) return -1; @@ -2074,7 +2116,7 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn) var_cell_t *cell; uint8_t *cp; uint8_t challenge[OR_AUTH_CHALLENGE_LEN]; - tor_assert(conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3); + tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3); if (! conn->handshake_state) return -1; diff --git a/src/or/connection_or.h b/src/or/connection_or.h index b78c44492..727de211b 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -9,8 +9,8 @@ * \brief Header file for connection_or.c. **/ -#ifndef _TOR_CONNECTION_OR_H -#define _TOR_CONNECTION_OR_H +#ifndef TOR_CONNECTION_OR_H +#define TOR_CONNECTION_OR_H void connection_or_remove_from_identity_map(or_connection_t *conn); void connection_or_clear_identity_map(void); @@ -34,8 +34,14 @@ void connection_or_update_token_buckets(smartlist_t *conns, void connection_or_connect_failed(or_connection_t *conn, int reason, const char *msg); +void connection_or_notify_error(or_connection_t *conn, + int reason, const char *msg); or_connection_t *connection_or_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest); + const char *id_digest, + channel_tls_t *chan); + +void connection_or_close_normally(or_connection_t *orconn, int flush); +void connection_or_close_for_error(or_connection_t *orconn, int flush); void connection_or_report_broken_states(int severity, int domain); @@ -51,8 +57,8 @@ void connection_or_init_conn_from_address(or_connection_t *conn, int started_here); int connection_or_client_learned_peer_id(or_connection_t *conn, const uint8_t *peer_id); -void connection_or_set_circid_type(or_connection_t *conn, - crypto_pk_t *identity_rcvd); +time_t connection_or_client_used(or_connection_t *conn); +int connection_or_get_num_circuits(or_connection_t *conn); void or_handshake_state_free(or_handshake_state_t *state); void or_handshake_state_record_cell(or_handshake_state_t *state, const cell_t *cell, @@ -66,8 +72,6 @@ void connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn); void connection_or_write_var_cell_to_buf(const var_cell_t *cell, or_connection_t *conn); -int connection_or_send_destroy(circid_t circ_id, or_connection_t *conn, - int reason); int connection_or_send_versions(or_connection_t *conn, int v3_plus); int connection_or_send_netinfo(or_connection_t *conn); int connection_or_send_certs_cell(or_connection_t *conn); diff --git a/src/or/control.c b/src/or/control.c index 913d18a7f..65689c46d 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -11,11 +11,16 @@ #define CONTROL_PRIVATE #include "or.h" +#include "addressmap.h" #include "buffers.h" +#include "channel.h" +#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuitstats.h" #include "circuituse.h" #include "config.h" +#include "confparse.h" #include "connection.h" #include "connection_edge.h" #include "connection_or.h" @@ -23,6 +28,7 @@ #include "directory.h" #include "dirserv.h" #include "dnsserv.h" +#include "entrynodes.h" #include "geoip.h" #include "hibernate.h" #include "main.h" @@ -50,7 +56,7 @@ * because it is used both as a list of v0 event types, and as indices * into the bitfield to determine which controllers want which events. */ -#define _EVENT_MIN 0x0001 +#define EVENT_MIN_ 0x0001 #define EVENT_CIRCUIT_STATUS 0x0001 #define EVENT_STREAM_STATUS 0x0002 #define EVENT_OR_CONN_STATUS 0x0003 @@ -76,8 +82,8 @@ #define EVENT_BUILDTIMEOUT_SET 0x0017 #define EVENT_SIGNAL 0x0018 #define EVENT_CONF_CHANGED 0x0019 -#define _EVENT_MAX 0x0019 -/* If _EVENT_MAX ever hits 0x0020, we need to make the mask wider. */ +#define EVENT_MAX_ 0x0019 +/* If EVENT_MAX_ ever hits 0x0020, we need to make the mask wider. */ /** Bitfield: The bit 1<<e is set if <b>any</b> open control * connection is interested in events of type <b>e</b>. We use this @@ -592,7 +598,7 @@ send_control_event_string(uint16_t event, event_format_t which, { smartlist_t *conns = get_connection_array(); (void)which; - tor_assert(event >= _EVENT_MIN && event <= _EVENT_MAX); + tor_assert(event >= EVENT_MIN_ && event <= EVENT_MAX_); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type == CONN_TYPE_CONTROL && @@ -1215,9 +1221,10 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, connection_mark_for_close(TO_CONN(conn)); return 0; ok: - log_info(LD_CONTROL, "Authenticated control connection (%d)", conn->_base.s); + log_info(LD_CONTROL, "Authenticated control connection ("TOR_SOCKET_T_FORMAT + ")", conn->base_.s); send_control_done(conn); - conn->_base.state = CONTROL_CONN_STATE_OPEN; + conn->base_.state = CONTROL_CONN_STATE_OPEN; tor_free(password); if (sl) { /* clean up */ SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); @@ -1243,6 +1250,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 +1278,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 +1288,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); @@ -1306,7 +1327,7 @@ handle_control_takeownership(control_connection_t *conn, uint32_t len, log_info(LD_CONTROL, "Control connection %d has taken ownership of this " "Tor instance.", - (int)(conn->_base.s)); + (int)(conn->base_.s)); send_control_done(conn); return 0; @@ -1352,10 +1373,13 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, "512-syntax error: invalid address '%s'", to); log_warn(LD_CONTROL, "Skipping invalid argument '%s' in MapAddress msg", to); - } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0")) { + } else if (!strcmp(from, ".") || !strcmp(from, "0.0.0.0") || + !strcmp(from, "::")) { + const char type = + !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : + (!strcmp(from, "0.0.0.0") ? RESOLVED_TYPE_IPV4 : RESOLVED_TYPE_IPV6); const char *address = addressmap_register_virtual_address( - !strcmp(from,".") ? RESOLVED_TYPE_HOSTNAME : RESOLVED_TYPE_IPV4, - tor_strdup(to)); + type, tor_strdup(to)); if (!address) { smartlist_add_asprintf(reply, "451-resource exhausted: skipping '%s'", line); @@ -1440,6 +1464,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")) { @@ -1614,10 +1648,13 @@ getinfo_helper_dir(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { - const routerinfo_t *ri; + const node_t *node; + const routerinfo_t *ri = NULL; (void) control_conn; if (!strcmpstart(question, "desc/id/")) { - ri = router_get_by_hexdigest(question+strlen("desc/id/")); + node = node_get_by_hex_id(question+strlen("desc/id/")); + if (node) + ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1626,7 +1663,9 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "desc/name/")) { /* XXX023 Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ - ri = router_get_by_nickname(question+strlen("desc/name/"),1); + node = node_get_by_nickname(question+strlen("desc/name/"), 1); + if (node) + ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1688,8 +1727,9 @@ getinfo_helper_dir(control_connection_t *control_conn, *answer = tor_strndup(md->body, md->bodylen); } } else if (!strcmpstart(question, "desc-annotations/id/")) { - ri = router_get_by_hexdigest(question+ - strlen("desc-annotations/id/")); + node = node_get_by_hex_id(question+strlen("desc-annotations/id/")); + if (node) + ri = node->ri; if (ri) { const char *annotations = signed_descriptor_get_annotations(&ri->cache_info); @@ -1847,11 +1887,11 @@ circuit_describe_status_for_controller(origin_circuit_t *circ) } smartlist_add_asprintf(descparts, "PURPOSE=%s", - circuit_purpose_to_controller_string(circ->_base.purpose)); + circuit_purpose_to_controller_string(circ->base_.purpose)); { const char *hs_state = - circuit_purpose_to_controller_hs_state_string(circ->_base.purpose); + circuit_purpose_to_controller_hs_state_string(circ->base_.purpose); if (hs_state != NULL) { smartlist_add_asprintf(descparts, "HS_STATE=%s", hs_state); @@ -1865,7 +1905,7 @@ circuit_describe_status_for_controller(origin_circuit_t *circ) { char tbuf[ISO_TIME_USEC_LEN+1]; - format_iso_time_nospace_usec(tbuf, &circ->_base.timestamp_created); + format_iso_time_nospace_usec(tbuf, &circ->base_.timestamp_created); smartlist_add_asprintf(descparts, "TIME_CREATED=%s", tbuf); } @@ -1889,7 +1929,7 @@ getinfo_helper_events(control_connection_t *control_conn, if (!strcmp(question, "circuit-status")) { circuit_t *circ_; smartlist_t *status = smartlist_new(); - for (circ_ = _circuit_get_global_list(); circ_; circ_ = circ_->next) { + for (circ_ = circuit_get_global_list_(); circ_; circ_ = circ_->next) { origin_circuit_t *circ; char *circdesc; const char *state; @@ -1897,7 +1937,7 @@ getinfo_helper_events(control_connection_t *control_conn, continue; circ = TO_ORIGIN_CIRCUIT(circ_); - if (circ->_base.state == CIRCUIT_STATE_OPEN) + if (circ->base_.state == CIRCUIT_STATE_OPEN) state = "BUILT"; else if (circ->cpath) state = "EXTENDED"; @@ -1974,7 +2014,7 @@ getinfo_helper_events(control_connection_t *control_conn, if (base_conn->type != CONN_TYPE_OR || base_conn->marked_for_close) continue; conn = TO_OR_CONN(base_conn); - if (conn->_base.state == OR_CONN_STATE_OPEN) + if (conn->base_.state == OR_CONN_STATE_OPEN) state = "CONNECTED"; else if (conn->nickname) state = "LAUNCHED"; @@ -2130,10 +2170,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."), @@ -2497,7 +2541,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, goto done; } } else { - if (circ->_base.state == CIRCUIT_STATE_OPEN) { + if (circ->base_.state == CIRCUIT_STATE_OPEN) { int err_reason = 0; circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) { @@ -2630,7 +2674,7 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; } - if (circ && (circ->_base.state != CIRCUIT_STATE_OPEN)) { + if (circ && (circ->base_.state != CIRCUIT_STATE_OPEN)) { connection_write_str_to_buf( "551 Can't attach stream to non-open origin circuit\r\n", conn); @@ -2903,7 +2947,7 @@ handle_control_resolve(control_connection_t *conn, uint32_t len, send_control_done(conn); SMARTLIST_FOREACH(failed, const char *, arg, { control_event_address_mapped(arg, arg, time(NULL), - "Unable to launch resolve request"); + "internal"); }); SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); @@ -3198,7 +3242,7 @@ connection_control_closed(control_connection_t *conn) static int is_valid_initial_command(control_connection_t *conn, const char *cmd) { - if (conn->_base.state == CONTROL_CONN_STATE_OPEN) + if (conn->base_.state == CONTROL_CONN_STATE_OPEN) return 1; if (!strcasecmp(cmd, "PROTOCOLINFO")) return (!conn->have_sent_protocolinfo && @@ -3243,8 +3287,8 @@ connection_control_process_inbuf(control_connection_t *conn) char *args; tor_assert(conn); - tor_assert(conn->_base.state == CONTROL_CONN_STATE_OPEN || - conn->_base.state == CONTROL_CONN_STATE_NEEDAUTH); + tor_assert(conn->base_.state == CONTROL_CONN_STATE_OPEN || + conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH); if (!conn->incoming_cmd) { conn->incoming_cmd = tor_malloc(1024); @@ -3252,7 +3296,7 @@ connection_control_process_inbuf(control_connection_t *conn) conn->incoming_cmd_cur_len = 0; } - if (conn->_base.state == CONTROL_CONN_STATE_NEEDAUTH && + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && peek_connection_has_control0_command(TO_CONN(conn))) { /* Detect v0 commands and send a "no more v0" message. */ size_t body_len; @@ -3351,7 +3395,7 @@ connection_control_process_inbuf(control_connection_t *conn) return 0; } - if (conn->_base.state == CONTROL_CONN_STATE_NEEDAUTH && + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && !is_valid_initial_command(conn, conn->incoming_cmd)) { connection_write_str_to_buf("514 Authentication required.\r\n", conn); connection_mark_for_close(TO_CONN(conn)); @@ -3538,9 +3582,9 @@ control_event_circuit_status_minor(origin_circuit_t *circ, /* event_tail can currently be up to 130 chars long */ const char *hs_state_str = circuit_purpose_to_controller_hs_state_string(purpose); - const struct timeval *old_timestamp_created = tv; + const struct timeval *old_timestamp_began = tv; char tbuf[ISO_TIME_USEC_LEN+1]; - format_iso_time_nospace_usec(tbuf, old_timestamp_created); + format_iso_time_nospace_usec(tbuf, old_timestamp_began); tor_snprintf(event_tail, sizeof(event_tail), " OLD_PURPOSE=%s%s%s OLD_TIME_CREATED=%s", @@ -3755,7 +3799,7 @@ orconn_target_get_name(char *name, size_t len, or_connection_t *conn) DIGEST_LEN); } else { tor_snprintf(name, len, "%s:%d", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); } } @@ -3787,8 +3831,12 @@ control_event_or_conn_status(or_connection_t *conn, or_conn_status_event_t tp, log_warn(LD_BUG, "Unrecognized status code %d", (int)tp); return 0; } - ncircs = circuit_count_pending_on_or_conn(conn); - ncircs += conn->n_circuits; + if (conn->chan) { + ncircs = circuit_count_pending_on_channel(TLS_CHAN_TO_BASE(conn->chan)); + } else { + ncircs = 0; + } + ncircs += connection_or_get_num_circuits(conn); if (ncircs && (tp == OR_CONN_EVENT_FAILED || tp == OR_CONN_EVENT_CLOSED)) { tor_snprintf(ncircs_buf, sizeof(ncircs_buf), "%sNCIRCS=%d", reason ? " " : "", ncircs); @@ -3817,7 +3865,7 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_FORMATS, "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", - U64_PRINTF_ARG(edge_conn->_base.global_identifier), + U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); @@ -3846,7 +3894,7 @@ control_event_stream_bandwidth_used(void) send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_FORMATS, "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", - U64_PRINTF_ARG(edge_conn->_base.global_identifier), + U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, (unsigned long)edge_conn->n_written); diff --git a/src/or/control.h b/src/or/control.h index f301ce91b..eea3af724 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -9,8 +9,8 @@ * \brief Header file for control.c. **/ -#ifndef _TOR_CONTROL_H -#define _TOR_CONTROL_H +#ifndef TOR_CONTROL_H +#define TOR_CONTROL_H void control_update_global_event_mask(void); void control_adjust_event_log_severity(void); diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index 0255227e7..3f8fc947b 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -14,6 +14,8 @@ #include "or.h" #include "buffers.h" +#include "channel.h" +#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" #include "config.h" @@ -30,9 +32,6 @@ /** The tag specifies which circuit this onionskin was from. */ #define TAG_LEN 10 -/** How many bytes are sent from the cpuworker back to tor? */ -#define LEN_ONION_RESPONSE \ - (1+TAG_LEN+ONIONSKIN_REPLY_LEN+CPATH_KEY_MATERIAL_LEN) /** How many cpuworkers we have running right now. */ static int num_cpuworkers=0; @@ -68,22 +67,64 @@ connection_cpu_finished_flushing(connection_t *conn) /** Pack global_id and circ_id; set *tag to the result. (See note on * cpuworker_main for wire format.) */ static void -tag_pack(char *tag, uint64_t conn_id, circid_t circ_id) +tag_pack(uint8_t *tag, uint64_t chan_id, circid_t circ_id) { /*XXXX RETHINK THIS WHOLE MESS !!!! !NM NM NM NM*/ - set_uint64(tag, conn_id); + /*XXXX DOUBLEPLUSTHIS!!!! AS AS AS AS*/ + set_uint64(tag, chan_id); set_uint16(tag+8, circ_id); } /** Unpack <b>tag</b> into addr, port, and circ_id. */ static void -tag_unpack(const char *tag, uint64_t *conn_id, circid_t *circ_id) +tag_unpack(const uint8_t *tag, uint64_t *chan_id, circid_t *circ_id) { - *conn_id = get_uint64(tag); + *chan_id = get_uint64(tag); *circ_id = get_uint16(tag+8); } +/** Magic numbers to make sure our cpuworker_requests don't grow any + * mis-framing bugs. */ +#define CPUWORKER_REQUEST_MAGIC 0xda4afeed +#define CPUWORKER_REPLY_MAGIC 0x5eedf00d + +/** A request sent to a cpuworker. */ +typedef struct cpuworker_request_t { + /** Magic number; must be CPUWORKER_REQUEST_MAGIC. */ + uint32_t magic; + /** Opaque tag to identify the job */ + uint8_t tag[TAG_LEN]; + /** Task code. Must be one of CPUWORKER_TASK_* */ + uint8_t task; + + /** A create cell for the cpuworker to process. */ + create_cell_t create_cell; + + /* Turn the above into a tagged union if needed. */ +} cpuworker_request_t; + +/** A reply sent by a cpuworker. */ +typedef struct cpuworker_reply_t { + /** Magic number; must be CPUWORKER_REPLY_MAGIC. */ + uint32_t magic; + /** Opaque tag to identify the job; matches the request's tag.*/ + uint8_t tag[TAG_LEN]; + /** True iff we got a successful request. */ + uint8_t success; + + /** Output of processing a create cell + * + * @{ + */ + /** The created cell to send back. */ + created_cell_t created_cell; + /** The keys to use on this circuit. */ + uint8_t keys[CPATH_KEY_MATERIAL_LEN]; + /** Input to use for authenticating introduce1 cells. */ + uint8_t rend_auth_material[DIGEST_LEN]; +} cpuworker_reply_t; + /** Called when the onion key has changed and we need to spawn new * cpuworkers. Close all currently idle cpuworkers, and mark the last * rotation time as now. @@ -129,12 +170,9 @@ connection_cpu_reached_eof(connection_t *conn) int connection_cpu_process_inbuf(connection_t *conn) { - char success; - char buf[LEN_ONION_RESPONSE]; - uint64_t conn_id; + uint64_t chan_id; circid_t circ_id; - connection_t *tmp_conn; - or_connection_t *p_conn = NULL; + channel_t *p_chan = NULL; circuit_t *circ; tor_assert(conn); @@ -144,25 +182,27 @@ connection_cpu_process_inbuf(connection_t *conn) return 0; if (conn->state == CPUWORKER_STATE_BUSY_ONION) { - if (connection_get_inbuf_len(conn) < LEN_ONION_RESPONSE) + cpuworker_reply_t rpl; + if (connection_get_inbuf_len(conn) < sizeof(cpuworker_reply_t)) return 0; /* not yet */ - tor_assert(connection_get_inbuf_len(conn) == LEN_ONION_RESPONSE); + tor_assert(connection_get_inbuf_len(conn) == sizeof(cpuworker_reply_t)); - connection_fetch_from_buf(&success,1,conn); - connection_fetch_from_buf(buf,LEN_ONION_RESPONSE-1,conn); + connection_fetch_from_buf((void*)&rpl,sizeof(cpuworker_reply_t),conn); + tor_assert(rpl.magic == CPUWORKER_REPLY_MAGIC); /* parse out the circ it was talking about */ - tag_unpack(buf, &conn_id, &circ_id); + tag_unpack(rpl.tag, &chan_id, &circ_id); circ = NULL; - tmp_conn = connection_get_by_global_id(conn_id); - if (tmp_conn && !tmp_conn->marked_for_close && - tmp_conn->type == CONN_TYPE_OR) - p_conn = TO_OR_CONN(tmp_conn); + log_debug(LD_OR, + "Unpacking cpuworker reply, chan_id is " U64_FORMAT + ", circ_id is %d", + U64_PRINTF_ARG(chan_id), circ_id); + p_chan = channel_find_by_global_id(chan_id); - if (p_conn) - circ = circuit_get_by_circid_orconn(circ_id, p_conn); + if (p_chan) + circ = circuit_get_by_circid_channel(circ_id, p_chan); - if (success == 0) { + if (rpl.success == 0) { log_debug(LD_OR, "decoding onionskin failed. " "(Old key or bad software.) Closing."); @@ -180,8 +220,10 @@ connection_cpu_process_inbuf(connection_t *conn) goto done_processing; } tor_assert(! CIRCUIT_IS_ORIGIN(circ)); - if (onionskin_answer(TO_OR_CIRCUIT(circ), CELL_CREATED, buf+TAG_LEN, - buf+TAG_LEN+ONIONSKIN_REPLY_LEN) < 0) { + if (onionskin_answer(TO_OR_CIRCUIT(circ), + &rpl.created_cell, + (const char*)rpl.keys, + rpl.rend_auth_material) < 0) { log_warn(LD_OR,"onionskin_answer failed. Closing."); circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL); goto done_processing; @@ -208,32 +250,21 @@ connection_cpu_process_inbuf(connection_t *conn) * Read and writes from fdarray[1]. Reads requests, writes answers. * * Request format: - * Task type [1 byte, always CPUWORKER_TASK_ONION] - * Opaque tag TAG_LEN - * Onionskin challenge ONIONSKIN_CHALLENGE_LEN + * cpuworker_request_t. * Response format: - * Success/failure [1 byte, boolean.] - * Opaque tag TAG_LEN - * Onionskin challenge ONIONSKIN_REPLY_LEN - * Negotiated keys KEY_LEN*2+DIGEST_LEN*2 - * - * (Note: this _should_ be by addr/port, since we're concerned with specific - * connections, not with routers (where we'd use identity).) + * cpuworker_reply_t */ static void cpuworker_main(void *data) { - char question[ONIONSKIN_CHALLENGE_LEN]; - uint8_t question_type; + /* For talking to the parent thread/process */ tor_socket_t *fdarray = data; tor_socket_t fd; /* variables for onion processing */ - char keys[CPATH_KEY_MATERIAL_LEN]; - char reply_to_proxy[ONIONSKIN_REPLY_LEN]; - char buf[LEN_ONION_RESPONSE]; - char tag[TAG_LEN]; - crypto_pk_t *onion_key = NULL, *last_onion_key = NULL; + server_onion_keys_t onion_keys; + cpuworker_request_t req; + cpuworker_reply_t rpl; fd = fdarray[1]; /* this side is ours */ #ifndef TOR_IS_MULTITHREADED @@ -244,68 +275,68 @@ cpuworker_main(void *data) #endif tor_free(data); - dup_onion_keys(&onion_key, &last_onion_key); + setup_server_onion_keys(&onion_keys); for (;;) { - ssize_t r; - - if ((r = recv(fd, (void *)&question_type, 1, 0)) != 1) { -// log_fn(LOG_ERR,"read type failed. Exiting."); - if (r == 0) { - log_info(LD_OR, - "CPU worker exiting because Tor process closed connection " - "(either rotated keys or died)."); - } else { - log_info(LD_OR, - "CPU worker exiting because of error on connection to Tor " - "process."); - log_info(LD_OR,"(Error on %d was %s)", - fd, tor_socket_strerror(tor_socket_errno(fd))); - } - goto end; - } - tor_assert(question_type == CPUWORKER_TASK_ONION); - - if (read_all(fd, tag, TAG_LEN, 1) != TAG_LEN) { - log_err(LD_BUG,"read tag failed. Exiting."); + if (read_all(fd, (void *)&req, sizeof(req), 1) != sizeof(req)) { + log_info(LD_OR, "read request failed. Exiting."); goto end; } - - if (read_all(fd, question, ONIONSKIN_CHALLENGE_LEN, 1) != - ONIONSKIN_CHALLENGE_LEN) { - log_err(LD_BUG,"read question failed. Exiting."); - goto end; - } - - if (question_type == CPUWORKER_TASK_ONION) { - if (onion_skin_server_handshake(question, onion_key, last_onion_key, - reply_to_proxy, keys, CPATH_KEY_MATERIAL_LEN) < 0) { + tor_assert(req.magic == CPUWORKER_REQUEST_MAGIC); + + memset(&rpl, 0, sizeof(rpl)); + + if (req.task == CPUWORKER_TASK_ONION) { + const create_cell_t *cc = &req.create_cell; + created_cell_t *cell_out = &rpl.created_cell; + int n; + n = onion_skin_server_handshake(cc->handshake_type, + cc->onionskin, cc->handshake_len, + &onion_keys, + cell_out->reply, + rpl.keys, CPATH_KEY_MATERIAL_LEN, + rpl.rend_auth_material); + if (n < 0) { /* failure */ log_debug(LD_OR,"onion_skin_server_handshake failed."); - *buf = 0; /* indicate failure in first byte */ - memcpy(buf+1,tag,TAG_LEN); - /* send all zeros as answer */ - memset(buf+1+TAG_LEN, 0, LEN_ONION_RESPONSE-(1+TAG_LEN)); + memset(&rpl, 0, sizeof(rpl)); + memcpy(rpl.tag, req.tag, TAG_LEN); + rpl.success = 0; } else { /* success */ log_debug(LD_OR,"onion_skin_server_handshake succeeded."); - buf[0] = 1; /* 1 means success */ - memcpy(buf+1,tag,TAG_LEN); - memcpy(buf+1+TAG_LEN,reply_to_proxy,ONIONSKIN_REPLY_LEN); - memcpy(buf+1+TAG_LEN+ONIONSKIN_REPLY_LEN,keys,CPATH_KEY_MATERIAL_LEN); + memcpy(rpl.tag, req.tag, TAG_LEN); + cell_out->handshake_len = n; + switch (cc->cell_type) { + case CELL_CREATE: + cell_out->cell_type = CELL_CREATED; break; + case CELL_CREATE2: + cell_out->cell_type = CELL_CREATED2; break; + case CELL_CREATE_FAST: + cell_out->cell_type = CELL_CREATED_FAST; break; + default: + tor_assert(0); + goto end; + } + rpl.success = 1; } - if (write_all(fd, buf, LEN_ONION_RESPONSE, 1) != LEN_ONION_RESPONSE) { + rpl.magic = CPUWORKER_REPLY_MAGIC; + if (write_all(fd, (void*)&rpl, sizeof(rpl), 1) != sizeof(rpl)) { log_err(LD_BUG,"writing response buf failed. Exiting."); goto end; } log_debug(LD_OR,"finished writing response."); + } else if (req.task == CPUWORKER_TASK_SHUTDOWN) { + log_info(LD_OR,"Clean shutdown: exiting"); + goto end; } + memwipe(&req, 0, sizeof(req)); + memwipe(&rpl, 0, sizeof(req)); } end: - if (onion_key) - crypto_pk_free(onion_key); - if (last_onion_key) - crypto_pk_free(last_onion_key); + memwipe(&req, 0, sizeof(req)); + memwipe(&rpl, 0, sizeof(req)); + release_server_onion_keys(&onion_keys); tor_close_socket(fd); crypto_thread_cleanup(); spawn_exit(); @@ -388,7 +419,7 @@ static void process_pending_task(connection_t *cpuworker) { or_circuit_t *circ; - char *onionskin = NULL; + create_cell_t *onionskin = NULL; tor_assert(cpuworker); @@ -441,10 +472,10 @@ cull_wedged_cpuworkers(void) */ int assign_onionskin_to_cpuworker(connection_t *cpuworker, - or_circuit_t *circ, char *onionskin) + or_circuit_t *circ, + create_cell_t *onionskin) { - char qbuf[1]; - char tag[TAG_LEN]; + cpuworker_request_t req; time_t now = approx_time(); static time_t last_culled_cpuworkers = 0; @@ -475,12 +506,15 @@ assign_onionskin_to_cpuworker(connection_t *cpuworker, tor_assert(cpuworker); - if (!circ->p_conn) { - log_info(LD_OR,"circ->p_conn gone. Failing circ."); + if (!circ->p_chan) { + log_info(LD_OR,"circ->p_chan gone. Failing circ."); tor_free(onionskin); return -1; } - tag_pack(tag, circ->p_conn->_base.global_identifier, + + memset(&req, 0, sizeof(req)); + req.magic = CPUWORKER_REQUEST_MAGIC; + tag_pack(req.tag, circ->p_chan->global_identifier, circ->p_circ_id); cpuworker->state = CPUWORKER_STATE_BUSY_ONION; @@ -490,11 +524,13 @@ assign_onionskin_to_cpuworker(connection_t *cpuworker, cpuworker->timestamp_lastwritten = time(NULL); num_cpuworkers_busy++; - qbuf[0] = CPUWORKER_TASK_ONION; - connection_write_to_buf(qbuf, 1, cpuworker); - connection_write_to_buf(tag, sizeof(tag), cpuworker); - connection_write_to_buf(onionskin, ONIONSKIN_CHALLENGE_LEN, cpuworker); + req.task = CPUWORKER_TASK_ONION; + memcpy(&req.create_cell, onionskin, sizeof(create_cell_t)); + tor_free(onionskin); + + connection_write_to_buf((void*)&req, sizeof(req), cpuworker); + memwipe(&req, 0, sizeof(req)); } return 0; } diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h index 91172caa5..f607e7d48 100644 --- a/src/or/cpuworker.h +++ b/src/or/cpuworker.h @@ -9,17 +9,18 @@ * \brief Header file for cpuworker.c. **/ -#ifndef _TOR_CPUWORKER_H -#define _TOR_CPUWORKER_H +#ifndef TOR_CPUWORKER_H +#define TOR_CPUWORKER_H void cpu_init(void); void cpuworkers_rotate(void); int connection_cpu_finished_flushing(connection_t *conn); int connection_cpu_reached_eof(connection_t *conn); int connection_cpu_process_inbuf(connection_t *conn); +struct create_cell_t; int assign_onionskin_to_cpuworker(connection_t *cpuworker, or_circuit_t *circ, - char *onionskin); + struct create_cell_t *onionskin); #endif diff --git a/src/or/directory.c b/src/or/directory.c index f235bf3b4..042b2dcf1 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -13,6 +13,7 @@ #include "directory.h" #include "dirserv.h" #include "dirvote.h" +#include "entrynodes.h" #include "geoip.h" #include "main.h" #include "microdesc.h" @@ -25,6 +26,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" #if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO) #ifndef OPENBSD @@ -58,7 +60,6 @@ static void directory_send_command(dir_connection_t *conn, int purpose, int direct, const char *resource, const char *payload, size_t payload_len, - int supports_conditional_consensus, time_t if_modified_since); static int directory_handle_command(dir_connection_t *conn); static int body_is_plausible(const char *body, size_t body_len, int purpose); @@ -89,12 +90,10 @@ static void directory_initiate_command_rend(const char *address, const tor_addr_t *addr, uint16_t or_port, uint16_t dir_port, - int supports_conditional_consensus, - int supports_begindir, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, @@ -227,16 +226,9 @@ router_supports_extrainfo(const char *identity_digest, int is_authority) if (node && node->ri) { if (node->ri->caches_extra_info) return 1; - if (is_authority && node->ri->platform && - tor_version_as_new_as(node->ri->platform, - "Tor 0.2.0.0-alpha-dev (r10070)")) - return 1; } if (is_authority) { - const routerstatus_t *rs = - router_get_consensus_status_by_id(identity_digest); - if (rs && rs->version_supports_extrainfo_upload) - return 1; + return 1; } return 0; } @@ -252,10 +244,10 @@ router_supports_extrainfo(const char *identity_digest, int is_authority) int directories_have_accepted_server_descriptor(void) { - smartlist_t *servers = router_get_trusted_dir_servers(); + const smartlist_t *servers = router_get_trusted_dir_servers(); const or_options_t *options = get_options(); - SMARTLIST_FOREACH(servers, trusted_dir_server_t *, d, { - if ((d->type & options->_PublishServerDescriptor) && + SMARTLIST_FOREACH(servers, dir_server_t *, d, { + if ((d->type & options->PublishServerDescriptor_) && d->has_accepted_serverdesc) { return 1; } @@ -288,7 +280,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, { const or_options_t *options = get_options(); int post_via_tor; - smartlist_t *dirservers = router_get_trusted_dir_servers(); + const smartlist_t *dirservers = router_get_trusted_dir_servers(); int found = 0; const int exclude_self = (dir_purpose == DIR_PURPOSE_UPLOAD_VOTE || dir_purpose == DIR_PURPOSE_UPLOAD_SIGNATURES); @@ -296,7 +288,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, /* This tries dirservers which we believe to be down, but ultimately, that's * harmless, and we may as well err on the side of getting things uploaded. */ - SMARTLIST_FOREACH_BEGIN(dirservers, trusted_dir_server_t *, ds) { + SMARTLIST_FOREACH_BEGIN(dirservers, dir_server_t *, ds) { routerstatus_t *rs = &(ds->fake_status); size_t upload_len = payload_len; tor_addr_t ds_addr; @@ -342,6 +334,60 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, } } +/** Return true iff, according to the values in <b>options</b>, we should be + * using directory guards for direct downloads of directory information. */ +static int +should_use_directory_guards(const or_options_t *options) +{ + /* Public (non-bridge) servers never use directory guards. */ + if (public_server_mode(options)) + return 0; + /* If guards are disabled, or directory guards are disabled, we can't + * use directory guards. + */ + if (!options->UseEntryGuards || !options->UseEntryGuardsAsDirGuards) + return 0; + /* If we're configured to fetch directory info aggressively or of a + * nonstandard type, don't use directory guards. */ + if (options->DownloadExtraInfo || options->FetchDirInfoEarly || + options->FetchDirInfoExtraEarly || options->FetchUselessDescriptors || + options->FetchV2Networkstatus) + return 0; + if (! options->PreferTunneledDirConns) + return 0; + return 1; +} + +/** Pick an unconsetrained directory server from among our guards, the latest + * networkstatus, or the fallback dirservers, for use in downloading + * information of type <b>type</b>, and return its routerstatus. */ +static const routerstatus_t * +directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, + uint8_t dir_purpose) +{ + const routerstatus_t *rs = NULL; + const or_options_t *options = get_options(); + + if (options->UseBridges) + log_warn(LD_BUG, "Called when we have UseBridges set."); + + if (should_use_directory_guards(options)) { + const node_t *node = choose_random_dirguard(type); + if (node) + rs = node->rs; + } else { + /* anybody with a non-zero dirport will do */ + rs = router_pick_directory_server(type, pds_flags); + } + if (!rs) { + log_info(LD_DIR, "No router found for %s; falling back to " + "dirserver list.", dir_conn_purpose_to_string(dir_purpose)); + rs = router_pick_fallback_dirserver(type, pds_flags); + } + + return rs; +} + /** Start a connection to a random running directory server, using * connection purpose <b>dir_purpose</b>, intending to fetch descriptors * of purpose <b>router_purpose</b>, and requesting <b>resource</b>. @@ -431,8 +477,6 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, * the behavior supported by our oldest bridge; see for example * any_bridges_dont_support_microdescriptors(). */ - /* XXX024 Not all bridges handle conditional consensus downloading, - * so, for now, never assume the server supports that. -PP */ const node_t *node = choose_random_entry(NULL); if (node && node->ri) { /* every bridge has a routerinfo. */ @@ -440,12 +484,12 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, routerinfo_t *ri = node->ri; node_get_addr(node, &addr); directory_initiate_command(ri->address, &addr, - ri->or_port, 0, - 0, /* don't use conditional consensus url */ - 1, ri->cache_info.identity_digest, + ri->or_port, 0/*no dirport*/, + ri->cache_info.identity_digest, dir_purpose, router_purpose, - 0, resource, NULL, 0, if_modified_since); + DIRIND_ONEHOP, + resource, NULL, 0, if_modified_since); } else log_notice(LD_DIR, "Ignoring directory request, since no bridge " "nodes are available yet."); @@ -479,14 +523,13 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, } } if (!rs && type != BRIDGE_DIRINFO) { - /* anybody with a non-zero dirport will do */ - rs = router_pick_directory_server(type, pds_flags); + /* */ + rs = directory_pick_generic_dirserver(type, pds_flags, + dir_purpose); if (!rs) { - log_info(LD_DIR, "No router found for %s; falling back to " - "dirserver list.", dir_conn_purpose_to_string(dir_purpose)); - rs = router_pick_trusteddirserver(type, pds_flags); - if (!rs) - get_via_tor = 1; /* last resort: try routing it via Tor */ + /*XXXX024 I'm pretty sure this can never do any good, since + * rs isn't set. */ + get_via_tor = 1; /* last resort: try routing it via Tor */ } } } @@ -506,13 +549,15 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, } } - if (rs) + if (rs) { + const dir_indirection_t indirection = + get_via_tor ? DIRIND_ANONYMOUS : DIRIND_ONEHOP; directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, - get_via_tor, + indirection, resource, NULL, 0, if_modified_since); - else { + } else { log_notice(LD_DIR, "While fetching directory info, " "no running dirservers known. Will try again later. " @@ -536,7 +581,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose, dir_purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES); SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds) { + dir_server_t *, ds) { routerstatus_t *rs; if (router_digest_is_me(ds->digest)) continue; @@ -544,17 +589,25 @@ directory_get_from_all_authorities(uint8_t dir_purpose, continue; rs = &ds->fake_status; directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, - 0, resource, NULL, 0, 0); + DIRIND_ONEHOP, resource, NULL, + 0, 0); } SMARTLIST_FOREACH_END(ds); } +/** Return true iff <b>ind</b> requires a multihop circuit. */ +static int +dirind_is_anon(dir_indirection_t ind) +{ + return ind == DIRIND_ANON_DIRPORT || ind == DIRIND_ANONYMOUS; +} + /** Same as directory_initiate_command_routerstatus(), but accepts * rendezvous data to fetch a hidden service descriptor. */ void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, @@ -567,6 +620,7 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, struct in_addr in; const char *address; tor_addr_t addr; + const int anonymized_connection = dirind_is_anon(indirection); node = node_get_by_id(status->identity_digest); if (!node && anonymized_connection) { @@ -596,11 +650,9 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, directory_initiate_command_rend(address, &addr, status->or_port, status->dir_port, - status->version_supports_conditional_consensus, - status->version_supports_begindir, status->identity_digest, dir_purpose, router_purpose, - anonymized_connection, resource, + indirection, resource, payload, payload_len, if_modified_since, rend_query); } @@ -623,7 +675,7 @@ void directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, @@ -631,7 +683,7 @@ directory_initiate_command_routerstatus(const routerstatus_t *status, { directory_initiate_command_routerstatus_rend(status, dir_purpose, router_purpose, - anonymized_connection, resource, + indirection, resource, payload, payload_len, if_modified_since, NULL); } @@ -647,8 +699,8 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn) const routerinfo_t *me = router_get_my_routerinfo(); if (me && router_digest_is_me(conn->identity_digest) && - tor_addr_eq_ipv4h(&conn->_base.addr, me->addr) && /*XXXX prop 118*/ - me->dir_port == conn->_base.port) + tor_addr_eq_ipv4h(&conn->base_.addr, me->addr) && /*XXXX prop 118*/ + me->dir_port == conn->base_.port) return 1; } return 0; @@ -666,35 +718,35 @@ connection_dir_request_failed(dir_connection_t *conn) } if (!entry_list_is_constrained(get_options())) router_set_status(conn->identity_digest, 0); /* don't try him again */ - if (conn->_base.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", - conn->_base.address); + conn->base_.address); connection_dir_download_v2_networkstatus_failed(conn, -1); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { log_info(LD_DIR, "Giving up on serverdesc/extrainfo fetch from " "directory server at '%s'; retrying", - conn->_base.address); + conn->base_.address); if (conn->router_purpose == ROUTER_PURPOSE_BRIDGE) connection_dir_bridge_routerdesc_failed(conn); connection_dir_download_routerdesc_failed(conn); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { if (conn->requested_resource) networkstatus_consensus_download_failed(0, conn->requested_resource); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { log_info(LD_DIR, "Giving up on certificate fetch from directory server " "at '%s'; retrying", - conn->_base.address); + conn->base_.address); connection_dir_download_cert_failed(conn, 0); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { log_info(LD_DIR, "Giving up downloading detached signatures from '%s'", - conn->_base.address); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { + conn->base_.address); + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { log_info(LD_DIR, "Giving up downloading votes from '%s'", - conn->_base.address); - } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC) { + conn->base_.address); + } else if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) { log_info(LD_DIR, "Giving up on downloading microdescriptors from " - " directory server at '%s'; will retry", conn->_base.address); + " directory server at '%s'; will retry", conn->base_.address); connection_dir_download_routerdesc_failed(conn); } } @@ -717,10 +769,10 @@ connection_dir_download_v2_networkstatus_failed(dir_connection_t *conn, /* We're a non-authoritative directory cache; try again. Ignore status * code, since we don't want to keep trying forever in a tight loop * if all the authorities are shutting us out. */ - smartlist_t *trusted_dirs = router_get_trusted_dir_servers(); - SMARTLIST_FOREACH(trusted_dirs, trusted_dir_server_t *, ds, + const smartlist_t *trusted_dirs = router_get_trusted_dir_servers(); + SMARTLIST_FOREACH(trusted_dirs, dir_server_t *, ds, download_status_failed(&ds->v2_ns_dl_status, 0)); - directory_get_from_dirserver(conn->_base.purpose, conn->router_purpose, + directory_get_from_dirserver(conn->base_.purpose, conn->router_purpose, "all.z", 0 /* don't retry_if_no_servers */); } else if (!strcmpstart(conn->requested_resource, "fp/")) { /* We were trying to download by fingerprint; mark them all as having @@ -765,9 +817,9 @@ connection_dir_download_routerdesc_failed(dir_connection_t *conn) /* No need to relaunch descriptor downloads here: we already do it * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ - tor_assert(conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || - conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC); (void) conn; } @@ -791,7 +843,7 @@ connection_dir_bridge_routerdesc_failed(dir_connection_t *conn) + strlen("fp/"), which, NULL, 0); - tor_assert(conn->_base.purpose != DIR_PURPOSE_FETCH_EXTRAINFO); + tor_assert(conn->base_.purpose != DIR_PURPOSE_FETCH_EXTRAINFO); if (smartlist_len(which)) { connection_dir_retry_bridges(which); SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); @@ -804,7 +856,7 @@ static void connection_dir_download_cert_failed(dir_connection_t *conn, int status) { smartlist_t *failed; - tor_assert(conn->_base.purpose == DIR_PURPOSE_FETCH_CERTIFICATE); + tor_assert(conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE); if (!conn->requested_resource) return; @@ -833,11 +885,13 @@ static int directory_command_should_use_begindir(const or_options_t *options, const tor_addr_t *addr, int or_port, uint8_t router_purpose, - int anonymized_connection) + dir_indirection_t indirection) { if (!or_port) return 0; /* We don't know an ORPort -- no chance. */ - if (!anonymized_connection) + if (indirection == DIRIND_DIRECT_CONN || indirection == DIRIND_ANON_DIRPORT) + return 0; + if (indirection == DIRIND_ONEHOP) if (!fascist_firewall_allows_address_or(addr, or_port) || directory_fetches_from_authorities(options)) return 0; /* We're firewalled or are acting like a relay -- also no. */ @@ -855,17 +909,15 @@ directory_command_should_use_begindir(const or_options_t *options, void directory_initiate_command(const char *address, const tor_addr_t *_addr, uint16_t or_port, uint16_t dir_port, - int supports_conditional_consensus, - int supports_begindir, const char *digest, + const char *digest, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, const char *resource, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since) { directory_initiate_command_rend(address, _addr, or_port, dir_port, - supports_conditional_consensus, - supports_begindir, digest, dir_purpose, - router_purpose, anonymized_connection, + digest, dir_purpose, + router_purpose, indirection, resource, payload, payload_len, if_modified_since, NULL); } @@ -889,10 +941,9 @@ is_sensitive_dir_purpose(uint8_t dir_purpose) static void directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, uint16_t or_port, uint16_t dir_port, - int supports_conditional_consensus, - int supports_begindir, const char *digest, + const char *digest, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since, @@ -901,9 +952,9 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, dir_connection_t *conn; const or_options_t *options = get_options(); int socket_error = 0; - int use_begindir = supports_begindir && - directory_command_should_use_begindir(options, _addr, - or_port, router_purpose, anonymized_connection); + int use_begindir = directory_command_should_use_begindir(options, _addr, + or_port, router_purpose, indirection); + const int anonymized_connection = dirind_is_anon(indirection); tor_addr_t addr; tor_assert(address); @@ -937,18 +988,19 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, conn = dir_connection_new(tor_addr_family(&addr)); /* set up conn so it's got all the data we need to remember */ - tor_addr_copy(&conn->_base.addr, &addr); - conn->_base.port = use_begindir ? or_port : dir_port; - conn->_base.address = tor_strdup(address); + tor_addr_copy(&conn->base_.addr, &addr); + conn->base_.port = use_begindir ? or_port : dir_port; + conn->base_.address = tor_strdup(address); memcpy(conn->identity_digest, digest, DIGEST_LEN); - conn->_base.purpose = dir_purpose; + conn->base_.purpose = dir_purpose; conn->router_purpose = router_purpose; /* give it an initial state */ - conn->_base.state = DIR_CONN_STATE_CONNECTING; + conn->base_.state = DIR_CONN_STATE_CONNECTING; /* decide whether we can learn our IP address from this conn */ + /* XXXX This is a bad name for this field now. */ conn->dirconn_direct = !anonymized_connection; /* copy rendezvous data, if any */ @@ -963,7 +1015,7 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, dir_port = options->HTTPProxyPort; } - switch (connection_connect(TO_CONN(conn), conn->_base.address, &addr, + switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr, dir_port, &socket_error)) { case -1: connection_dir_request_failed(conn); /* retry if we want */ @@ -973,13 +1025,12 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, return; case 1: /* start flushing conn */ - conn->_base.state = DIR_CONN_STATE_CLIENT_SENDING; + conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* fall through */ case 0: /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 1, resource, payload, payload_len, - supports_conditional_consensus, if_modified_since); connection_watch_events(TO_CONN(conn), READ_EVENT | WRITE_EVENT); /* writable indicates finish, readable indicates broken link, @@ -997,7 +1048,7 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, if (anonymized_connection && use_begindir) rep_hist_note_used_internal(time(NULL), 0, 1); else if (anonymized_connection && !use_begindir) - rep_hist_note_used_port(time(NULL), conn->_base.port); + rep_hist_note_used_port(time(NULL), conn->base_.port); /* make an AP connection * populate it and add it at the right state @@ -1005,7 +1056,7 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, */ linked_conn = connection_ap_make_link(TO_CONN(conn), - conn->_base.address, conn->_base.port, + conn->base_.address, conn->base_.port, digest, SESSION_GROUP_DIRCONN, iso_flags, use_begindir, conn->dirconn_direct); @@ -1020,11 +1071,10 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, connection_mark_for_close(TO_CONN(conn)); return; } - conn->_base.state = DIR_CONN_STATE_CLIENT_SENDING; + conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 0, resource, payload, payload_len, - supports_conditional_consensus, if_modified_since); connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); @@ -1054,7 +1104,7 @@ connection_dir_is_encrypted(dir_connection_t *conn) * sort strings alphabetically */ static int -_compare_strs(const void **a, const void **b) +compare_strs_(const void **a, const void **b) { const char *s1 = *a, *s2 = *b; return strcmp(s1, s2); @@ -1074,8 +1124,7 @@ _compare_strs(const void **a, const void **b) * If 'resource' is provided, it is the name of a consensus flavor to request. */ static char * -directory_get_consensus_url(int supports_conditional_consensus, - const char *resource) +directory_get_consensus_url(const char *resource) { char *url = NULL; const char *hyphen, *flavor; @@ -1087,12 +1136,12 @@ directory_get_consensus_url(int supports_conditional_consensus, hyphen = "-"; } - if (supports_conditional_consensus) { + { char *authority_id_list; smartlist_t *authority_digests = smartlist_new(); SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds) { + dir_server_t *, ds) { char *hex; if (!(ds->type & V3_DIRINFO)) continue; @@ -1102,7 +1151,7 @@ directory_get_consensus_url(int supports_conditional_consensus, ds->v3_identity_digest, CONDITIONAL_CONSENSUS_FPR_LEN); smartlist_add(authority_digests, hex); } SMARTLIST_FOREACH_END(ds); - smartlist_sort(authority_digests, _compare_strs); + smartlist_sort(authority_digests, compare_strs_); authority_id_list = smartlist_join_strings(authority_digests, "+", 0, NULL); @@ -1112,9 +1161,6 @@ directory_get_consensus_url(int supports_conditional_consensus, SMARTLIST_FOREACH(authority_digests, char *, cp, tor_free(cp)); smartlist_free(authority_digests); tor_free(authority_id_list); - } else { - tor_asprintf(&url, "/tor/status-vote/current/consensus%s%s.z", - hyphen, flavor); } return url; } @@ -1126,7 +1172,6 @@ static void directory_send_command(dir_connection_t *conn, int purpose, int direct, const char *resource, const char *payload, size_t payload_len, - int supports_conditional_consensus, time_t if_modified_since) { char proxystring[256]; @@ -1137,18 +1182,18 @@ directory_send_command(dir_connection_t *conn, const char *httpcommand = NULL; tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_DIR); + tor_assert(conn->base_.type == CONN_TYPE_DIR); tor_free(conn->requested_resource); if (resource) conn->requested_resource = tor_strdup(resource); /* come up with a string for which Host: we want */ - if (conn->_base.port == 80) { - strlcpy(hoststring, conn->_base.address, sizeof(hoststring)); + if (conn->base_.port == 80) { + strlcpy(hoststring, conn->base_.address, sizeof(hoststring)); } else { tor_snprintf(hoststring, sizeof(hoststring),"%s:%d", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); } /* Format if-modified-since */ @@ -1189,8 +1234,7 @@ directory_send_command(dir_connection_t *conn, /* resource is optional. If present, it's a flavor name */ tor_assert(!payload); httpcommand = "GET"; - url = directory_get_consensus_url(supports_conditional_consensus, - resource); + url = directory_get_consensus_url(resource); log_info(LD_DIR, "Downloading consensus from %s using %s", hoststring, url); break; @@ -1584,9 +1628,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn) compress_method_t compression; int plausible; int skewed=0; - int allow_partial = (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || - conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); + int allow_partial = (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC); int was_compressed=0; time_t now = time(NULL); @@ -1597,7 +1641,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case -1: /* overflow */ log_warn(LD_PROTOCOL, "'fetch' response too large (server '%s:%d'). Closing.", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); return -1; case 0: log_info(LD_HTTP, @@ -1610,7 +1654,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (parse_http_response(headers, &status_code, &date_header, &compression, &reason) < 0) { log_warn(LD_HTTP,"Unparseable headers (server '%s:%d'). Closing.", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); tor_free(body); tor_free(headers); return -1; } @@ -1619,9 +1663,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_debug(LD_DIR, "Received response from directory server '%s:%d': %d %s " "(purpose: %d)", - conn->_base.address, conn->_base.port, status_code, + conn->base_.address, conn->base_.port, status_code, escaped(reason), - conn->_base.purpose); + conn->base_.purpose); /* now check if it's got any hints for us about our IP address. */ if (conn->dirconn_direct) { @@ -1638,7 +1682,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * and the date header. (We used to check now-date_header, but that's * inaccurate if we spend a lot of time downloading.) */ - delta = conn->_base.timestamp_lastwritten - date_header; + delta = conn->base_.timestamp_lastwritten - date_header; if (labs(delta)>ALLOW_DIRECTORY_TIME_SKEW) { char dbuf[64]; int trusted = router_digest_is_trusted_dir(conn->identity_digest); @@ -1649,14 +1693,14 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "It seems that our clock is %s by %s, or that theirs is %s. " "Tor requires an accurate clock to work: please check your time, " "timezone, and date settings.", - conn->_base.address, conn->_base.port, + conn->base_.address, conn->base_.port, delta>0 ? "ahead" : "behind", dbuf, delta>0 ? "behind" : "ahead"); skewed = 1; /* don't check the recommended-versions line */ if (trusted) control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=DIRSERV:%s:%d", - delta, conn->_base.address, conn->_base.port); + delta, conn->base_.address, conn->base_.port); } else { log_debug(LD_HTTP, "Time on received directory is within tolerance; " "we are %ld seconds skewed. (That's okay.)", delta); @@ -1666,22 +1710,22 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (status_code == 503) { routerstatus_t *rs; - trusted_dir_server_t *ds; + dir_server_t *ds; const char *id_digest = conn->identity_digest; log_info(LD_DIR,"Received http status code %d (%s) from server " "'%s:%d'. I'll try again soon.", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); if ((rs = router_get_mutable_consensus_status_by_id(id_digest))) rs->last_dir_503_at = now; - if ((ds = router_get_trusteddirserver_by_digest(id_digest))) + if ((ds = router_get_fallback_dirserver_by_digest(id_digest))) ds->fake_status.last_dir_503_at = now; tor_free(body); tor_free(headers); tor_free(reason); return -1; } - plausible = body_is_plausible(body, body_len, conn->_base.purpose); + plausible = body_is_plausible(body, body_len, conn->base_.purpose); if (compression != NO_METHOD || !plausible) { char *new_body = NULL; size_t new_len = 0; @@ -1708,7 +1752,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_HTTP, "HTTP body from server '%s:%d' was labeled %s, " "but it seems to be %s.%s", - conn->_base.address, conn->_base.port, description1, + conn->base_.address, conn->base_.port, description1, description2, (compression>0 && guessed>0)?" Trying both.":""); } @@ -1728,7 +1772,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (!plausible && !new_body) { log_fn(LOG_PROTOCOL_WARN, LD_HTTP, "Unable to decompress HTTP body (server '%s:%d').", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); tor_free(body); tor_free(headers); tor_free(reason); return -1; } @@ -1740,12 +1784,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) { smartlist_t *which = NULL; v2_networkstatus_source_t source; char *cp; log_info(LD_DIR,"Received networkstatus objects (size %d) from server " - "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port); + "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); if (status_code != 200) { static ratelim_t warning_limit = RATELIM_INIT(3600); char *m; @@ -1754,8 +1798,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "Received http status code %d (%s) from server " "'%s:%d' while fetching \"/tor/status/%s\". " "I'll try again soon.%s", - status_code, escaped(reason), conn->_base.address, - conn->_base.port, conn->requested_resource, m); + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource, m); tor_free(m); } tor_free(body); tor_free(headers); tor_free(reason); @@ -1773,7 +1817,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) source = NS_FROM_DIR_ALL; which = smartlist_new(); SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds, + dir_server_t *, ds, { char *hex = tor_malloc(HEX_DIGEST_LEN+1); base16_encode(hex, HEX_DIGEST_LEN+1, ds->digest, DIGEST_LEN); @@ -1811,7 +1855,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { int r; const char *flavname = conn->requested_resource; if (status_code != 200) { @@ -1819,19 +1863,19 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log(severity, LD_DIR, "Received http status code %d (%s) from server " "'%s:%d' while fetching consensus directory.", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); tor_free(body); tor_free(headers); tor_free(reason); networkstatus_consensus_download_failed(status_code, flavname); return -1; } log_info(LD_DIR,"Received consensus directory (size %d) from server " - "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port); + "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); if ((r=networkstatus_set_current_consensus(body, flavname, 0))<0) { log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, "Unable to load %s consensus directory downloaded from " "server '%s:%d'. I'll try again soon.", - flavname, conn->_base.address, conn->_base.port); + flavname, conn->base_.address, conn->base_.port); tor_free(body); tor_free(headers); tor_free(reason); networkstatus_consensus_download_failed(0, flavname); return -1; @@ -1844,19 +1888,19 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_DIR, "Successfully loaded consensus."); } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { if (status_code != 200) { log_warn(LD_DIR, "Received http status code %d (%s) from server " "'%s:%d' while fetching \"/tor/keys/%s\".", - status_code, escaped(reason), conn->_base.address, - conn->_base.port, conn->requested_resource); + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource); connection_dir_download_cert_failed(conn, status_code); tor_free(body); tor_free(headers); tor_free(reason); return -1; } log_info(LD_DIR,"Received authority certificates (size %d) from server " - "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port); + "'%s:%d'", (int)body_len, conn->base_.address, conn->base_.port); if (trusted_dirs_load_certs_from_string(body, 0, 1)<0) { log_warn(LD_DIR, "Unable to parse fetched certificates"); /* if we fetched more than one and only some failed, the successful @@ -1867,17 +1911,17 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_DIR, "Successfully loaded certificates from fetch."); } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_STATUS_VOTE) { const char *msg; int st; log_info(LD_DIR,"Got votes (size %d) from server %s:%d", - (int)body_len, conn->_base.address, conn->_base.port); + (int)body_len, conn->base_.address, conn->base_.port); if (status_code != 200) { log_warn(LD_DIR, "Received http status code %d (%s) from server " "'%s:%d' while fetching \"/tor/status-vote/next/%s.z\".", - status_code, escaped(reason), conn->_base.address, - conn->_base.port, conn->requested_resource); + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource); tor_free(body); tor_free(headers); tor_free(reason); return -1; } @@ -1888,35 +1932,35 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_DIR, "Added vote(s) successfully [msg: %s]", msg); } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { const char *msg = NULL; log_info(LD_DIR,"Got detached signatures (size %d) from server %s:%d", - (int)body_len, conn->_base.address, conn->_base.port); + (int)body_len, conn->base_.address, conn->base_.port); if (status_code != 200) { log_warn(LD_DIR, "Received http status code %d (%s) from server '%s:%d' while fetching " "\"/tor/status-vote/next/consensus-signatures.z\".", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); tor_free(body); tor_free(headers); tor_free(reason); return -1; } - if (dirvote_add_signatures(body, conn->_base.address, &msg)<0) { + if (dirvote_add_signatures(body, conn->base_.address, &msg)<0) { log_warn(LD_DIR, "Problem adding detached signatures from %s:%d: %s", - conn->_base.address, conn->_base.port, msg?msg:"???"); + conn->base_.address, conn->base_.port, msg?msg:"???"); } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || - conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { - int was_ei = conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO; + if (conn->base_.purpose == DIR_PURPOSE_FETCH_SERVERDESC || + conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { + int was_ei = conn->base_.purpose == DIR_PURPOSE_FETCH_EXTRAINFO; smartlist_t *which = NULL; int n_asked_for = 0; int descriptor_digests = conn->requested_resource && !strcmpstart(conn->requested_resource,"d/"); log_info(LD_DIR,"Received %s (size %d) from server '%s:%d'", was_ei ? "extra server info" : "server info", - (int)body_len, conn->_base.address, conn->_base.port); + (int)body_len, conn->base_.address, conn->base_.port); if (conn->requested_resource && (!strcmpstart(conn->requested_resource,"d/") || !strcmpstart(conn->requested_resource,"fp/"))) { @@ -1934,8 +1978,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_fn(dir_okay ? LOG_INFO : LOG_WARN, LD_DIR, "Received http status code %d (%s) from server '%s:%d' " "while fetching \"/tor/server/%s\". I'll try again soon.", - status_code, escaped(reason), conn->_base.address, - conn->_base.port, conn->requested_resource); + status_code, escaped(reason), conn->base_.address, + conn->base_.port, conn->requested_resource); if (!which) { connection_dir_download_routerdesc_failed(conn); } else { @@ -1969,7 +2013,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) // descriptor_digests, conn->router_purpose); if (load_downloaded_routers(body, which, descriptor_digests, conn->router_purpose, - conn->_base.address)) + conn->base_.address)) directory_info_has_arrived(now, 0); } } @@ -1977,7 +2021,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_DIR, "Received %d/%d %s requested from %s:%d", n_asked_for-smartlist_len(which), n_asked_for, was_ei ? "extra-info documents" : "router descriptors", - conn->_base.address, (int)conn->_base.port); + conn->base_.address, (int)conn->base_.port); if (smartlist_len(which)) { dir_routerdesc_download_failed(which, status_code, conn->router_purpose, @@ -1989,12 +2033,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (directory_conn_is_self_reachability_test(conn)) router_dirport_found_reachable(); } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_MICRODESC) { smartlist_t *which = NULL; log_info(LD_DIR,"Received answer to microdescriptor request (status %d, " "size %d) from server '%s:%d'", - status_code, (int)body_len, conn->_base.address, - conn->_base.port); + status_code, (int)body_len, conn->base_.address, + conn->base_.port); tor_assert(conn->requested_resource && !strcmpstart(conn->requested_resource, "d/")); which = smartlist_new(); @@ -2005,8 +2049,8 @@ connection_dir_client_reached_eof(dir_connection_t *conn) log_info(LD_DIR, "Received status code %d (%s) from server " "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again " "soon.", - status_code, escaped(reason), conn->_base.address, - (int)conn->_base.port, conn->requested_resource); + status_code, escaped(reason), conn->base_.address, + (int)conn->base_.port, conn->requested_resource); dir_microdesc_download_failed(which, status_code); SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); smartlist_free(which); @@ -2027,10 +2071,10 @@ connection_dir_client_reached_eof(dir_connection_t *conn) } } - if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_DIR) { + if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_DIR) { switch (status_code) { case 200: { - trusted_dir_server_t *ds = + dir_server_t *ds = router_get_trusteddirserver_by_digest(conn->identity_digest); char *rejected_hdr = http_get_header(headers, "X-Descriptor-Not-New: "); @@ -2053,7 +2097,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "descriptor: finished."); control_event_server_status( LOG_NOTICE, "ACCEPTED_SERVER_DESCRIPTOR DIRAUTH=%s:%d", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); ds->has_accepted_serverdesc = 1; if (directories_have_accepted_server_descriptor()) @@ -2063,72 +2107,72 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case 400: log_warn(LD_GENERAL,"http status 400 (%s) response from " "dirserver '%s:%d'. Please correct.", - escaped(reason), conn->_base.address, conn->_base.port); + escaped(reason), conn->base_.address, conn->base_.port); control_event_server_status(LOG_WARN, "BAD_SERVER_DESCRIPTOR DIRAUTH=%s:%d REASON=\"%s\"", - conn->_base.address, conn->_base.port, escaped(reason)); + conn->base_.address, conn->base_.port, escaped(reason)); break; default: log_warn(LD_GENERAL, "http status %d (%s) reason unexpected while uploading " "descriptor to server '%s:%d').", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } /* return 0 in all cases, since we don't want to mark any * dirservers down just because they don't like us. */ } - if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_VOTE) { + if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_VOTE) { switch (status_code) { case 200: { log_notice(LD_DIR,"Uploaded a vote to dirserver %s:%d", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); } break; case 400: log_warn(LD_DIR,"http status 400 (%s) response after uploading " "vote to dirserver '%s:%d'. Please correct.", - escaped(reason), conn->_base.address, conn->_base.port); + escaped(reason), conn->base_.address, conn->base_.port); break; default: log_warn(LD_GENERAL, "http status %d (%s) reason unexpected while uploading " "vote to server '%s:%d').", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } /* return 0 in all cases, since we don't want to mark any * dirservers down just because they don't like us. */ } - if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES) { + if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_SIGNATURES) { switch (status_code) { case 200: { log_notice(LD_DIR,"Uploaded signature(s) to dirserver %s:%d", - conn->_base.address, conn->_base.port); + conn->base_.address, conn->base_.port); } break; case 400: log_warn(LD_DIR,"http status 400 (%s) response after uploading " "signatures to dirserver '%s:%d'. Please correct.", - escaped(reason), conn->_base.address, conn->_base.port); + escaped(reason), conn->base_.address, conn->base_.port); break; default: log_warn(LD_GENERAL, "http status %d (%s) reason unexpected while uploading " "signatures to server '%s:%d').", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } /* return 0 in all cases, since we don't want to mark any * dirservers down just because they don't like us. */ } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC) { tor_assert(conn->rend_data); log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " "(%s))", @@ -2146,7 +2190,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) } else { /* Success, or at least there's a v2 descriptor already * present. Notify pending connections about this. */ - conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; + conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; rend_client_desc_trynow(conn->rend_data->onion_address); } break; @@ -2162,13 +2206,13 @@ connection_dir_client_reached_eof(dir_connection_t *conn) default: log_warn(LD_REND,"http status %d (%s) response unexpected while " "fetching hidden service descriptor (server '%s:%d').", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } } - if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) { + if (conn->base_.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) { tor_assert(conn->rend_data); log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " "(%s))", @@ -2187,13 +2231,13 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * and _not_ performing another request. */ log_info(LD_REND, "Successfully fetched v2 rendezvous " "descriptor, but we already have a v0 descriptor."); - conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; + conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; break; default: /* success. notify pending connections about this. */ log_info(LD_REND, "Successfully fetched v2 rendezvous " "descriptor."); - conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; + conn->base_.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC; rend_client_desc_trynow(conn->rend_data->onion_address); break; } @@ -2215,14 +2259,14 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "http status %d (%s) response unexpected while " "fetching v2 hidden service descriptor (server '%s:%d'). " "Retrying at another directory.", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } } - if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_RENDDESC || - conn->_base.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) { + if (conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC || + conn->base_.purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) { log_info(LD_REND,"Uploaded rendezvous descriptor (status %d " "(%s))", status_code, escaped(reason)); @@ -2235,17 +2279,17 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case 400: log_warn(LD_REND,"http status 400 (%s) response from dirserver " "'%s:%d'. Malformed rendezvous descriptor?", - escaped(reason), conn->_base.address, conn->_base.port); + escaped(reason), conn->base_.address, conn->base_.port); break; default: log_warn(LD_REND,"http status %d (%s) response unexpected (server " "'%s:%d').", - status_code, escaped(reason), conn->_base.address, - conn->_base.port); + status_code, escaped(reason), conn->base_.address, + conn->base_.port); break; } } - note_client_request(conn->_base.purpose, was_compressed, orig_len); + note_client_request(conn->base_.purpose, was_compressed, orig_len); tor_free(body); tor_free(headers); tor_free(reason); return 0; } @@ -2255,9 +2299,9 @@ int connection_dir_reached_eof(dir_connection_t *conn) { int retval; - if (conn->_base.state != DIR_CONN_STATE_CLIENT_READING) { + if (conn->base_.state != DIR_CONN_STATE_CLIENT_READING) { log_info(LD_HTTP,"conn reached eof, not reading. [state=%d] Closing.", - conn->_base.state); + conn->base_.state); connection_close_immediate(TO_CONN(conn)); /* error: give up on flushing */ connection_mark_for_close(TO_CONN(conn)); return -1; @@ -2265,7 +2309,7 @@ connection_dir_reached_eof(dir_connection_t *conn) retval = connection_dir_client_reached_eof(conn); if (retval == 0) /* success */ - conn->_base.state = DIR_CONN_STATE_CLIENT_FINISHED; + conn->base_.state = DIR_CONN_STATE_CLIENT_FINISHED; connection_mark_for_close(TO_CONN(conn)); return retval; } @@ -2283,7 +2327,7 @@ int connection_dir_process_inbuf(dir_connection_t *conn) { tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_DIR); + tor_assert(conn->base_.type == CONN_TYPE_DIR); /* Directory clients write, then read data until they receive EOF; * directory servers read data until they get an HTTP command, then @@ -2292,7 +2336,7 @@ connection_dir_process_inbuf(dir_connection_t *conn) */ /* If we're on the dirserver side, look for a command. */ - if (conn->_base.state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) { + if (conn->base_.state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) { if (directory_handle_command(conn) < 0) { connection_mark_for_close(TO_CONN(conn)); return -1; @@ -2307,7 +2351,7 @@ connection_dir_process_inbuf(dir_connection_t *conn) return -1; } - if (!conn->_base.inbuf_reached_eof) + if (!conn->base_.inbuf_reached_eof) log_debug(LD_HTTP,"Got data, not eof. Leaving on inbuf."); return 0; } @@ -2380,12 +2424,12 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, tor_snprintf(cp, sizeof(tmp)-(cp-tmp), "Content-Type: %s\r\n", type); cp += strlen(cp); } - if (!is_local_addr(&conn->_base.addr)) { + if (!is_local_addr(&conn->base_.addr)) { /* Don't report the source address for a nearby/private connection. * Otherwise we tend to mis-report in cases where incoming ports are * being forwarded to a Tor server running behind the firewall. */ tor_snprintf(cp, sizeof(tmp)-(cp-tmp), - X_ADDRESS_HEADER "%s\r\n", conn->_base.address); + X_ADDRESS_HEADER "%s\r\n", conn->base_.address); cp += strlen(cp); } if (encoding) { @@ -2637,7 +2681,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, log_debug(LD_DIRSERV,"Received GET command."); - conn->_base.state = DIR_CONN_STATE_SERVER_WRITING; + conn->base_.state = DIR_CONN_STATE_SERVER_WRITING; if (parse_http_url(headers, &url) < 0) { write_http_status_line(conn, 400, "Bad request"); @@ -2865,8 +2909,8 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, geoip_note_ns_response(act, GEOIP_SUCCESS); /* Note that a request for a network status has started, so that we * can measure the download time later on. */ - if (TO_CONN(conn)->dirreq_id) - geoip_start_dirreq(TO_CONN(conn)->dirreq_id, dlen, act, + if (conn->dirreq_id) + geoip_start_dirreq(conn->dirreq_id, dlen, act, DIRREQ_TUNNELED); else geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen, act, @@ -3231,7 +3275,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, } if (options->BridgeAuthoritativeDir && - options->_BridgePassword_AuthDigest && + options->BridgePassword_AuthDigest_ && connection_dir_is_encrypted(conn) && !strcmp(url,"/tor/networkstatus-bridges")) { char *status; @@ -3244,7 +3288,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, /* now make sure the password is there and right */ if (!header || tor_memneq(digest, - options->_BridgePassword_AuthDigest, DIGEST256_LEN)) { + options->BridgePassword_AuthDigest_, DIGEST256_LEN)) { write_http_status_line(conn, 404, "Not found"); tor_free(header); goto done; @@ -3300,7 +3344,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, }while(0); if (!strcmp(url,"/tor/mallinfo.txt") && - (tor_addr_eq_ipv4h(&conn->_base.addr, 0x7f000001ul))) { + (tor_addr_eq_ipv4h(&conn->base_.addr, 0x7f000001ul))) { char *result; size_t len; struct mallinfo mi; @@ -3355,7 +3399,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, log_debug(LD_DIRSERV,"Received POST command."); - conn->_base.state = DIR_CONN_STATE_SERVER_WRITING; + conn->base_.state = DIR_CONN_STATE_SERVER_WRITING; if (parse_http_url(headers, &url) < 0) { write_http_status_line(conn, 400, "Bad request"); @@ -3371,13 +3415,13 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, case -2: log_info(LD_REND, "Rejected v2 rend descriptor (length %d) from %s " "since we're not currently a hidden service directory.", - (int)body_len, conn->_base.address); + (int)body_len, conn->base_.address); write_http_status_line(conn, 503, "Currently not acting as v2 " "hidden service directory"); break; case -1: log_warn(LD_REND, "Rejected v2 rend descriptor (length %d) from %s.", - (int)body_len, conn->_base.address); + (int)body_len, conn->base_.address); write_http_status_line(conn, 400, "Invalid v2 service descriptor rejected"); break; @@ -3402,7 +3446,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, uint8_t purpose = authdir_mode_bridge(options) ? ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL; was_router_added_t r = dirserv_add_multiple_descriptors(body, purpose, - conn->_base.address, &msg); + conn->base_.address, &msg); tor_assert(msg); if (WRA_WAS_ADDED(r)) dirserv_get_directory(); /* rebuild and write to disk */ @@ -3412,7 +3456,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, log_info(LD_DIRSERV, "Problematic router descriptor or extra-info from %s " "(\"%s\").", - conn->_base.address, msg); + conn->base_.address, msg); write_http_status_line(conn, 400, msg); } else if (r == ROUTER_ADDED_SUCCESSFULLY) { write_http_status_line(conn, 200, msg); @@ -3423,7 +3467,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, log_info(LD_DIRSERV, "Rejected router descriptor or extra-info from %s " "(\"%s\").", - conn->_base.address, msg); + conn->base_.address, msg); write_http_status_line(conn, 400, msg); } goto done; @@ -3436,7 +3480,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, if (rend_cache_store(body, body_len, 1, NULL) < 0) { log_fn(LOG_PROTOCOL_WARN, LD_DIRSERV, "Rejected rend descriptor (length %d) from %s.", - (int)body_len, conn->_base.address); + (int)body_len, conn->base_.address); write_http_status_line(conn, 400, "Invalid v0 service descriptor rejected"); } else { @@ -3454,7 +3498,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, } else { tor_assert(msg); log_warn(LD_DIRSERV, "Rejected vote from %s (\"%s\").", - conn->_base.address, msg); + conn->base_.address, msg); write_http_status_line(conn, status, msg); } goto done; @@ -3463,11 +3507,11 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, if (authdir_mode_v3(options) && !strcmp(url,"/tor/post/consensus-signature")) { /* sigs on consensus. */ const char *msg = NULL; - if (dirvote_add_signatures(body, conn->_base.address, &msg)>=0) { + if (dirvote_add_signatures(body, conn->base_.address, &msg)>=0) { write_http_status_line(conn, 200, msg?msg:"Signatures stored"); } else { log_warn(LD_DIR, "Unable to store signatures posted by %s: %s", - conn->_base.address, msg?msg:"???"); + conn->base_.address, msg?msg:"???"); write_http_status_line(conn, 400, msg?msg:"Unable to store signatures"); } goto done; @@ -3494,7 +3538,7 @@ directory_handle_command(dir_connection_t *conn) int r; tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_DIR); + tor_assert(conn->base_.type == CONN_TYPE_DIR); switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, @@ -3502,7 +3546,7 @@ directory_handle_command(dir_connection_t *conn) case -1: /* overflow */ log_warn(LD_DIRSERV, "Request too large from address '%s' to DirPort. Closing.", - safe_str(conn->_base.address)); + safe_str(conn->base_.address)); return -1; case 0: log_debug(LD_DIRSERV,"command not all here yet."); @@ -3536,23 +3580,23 @@ int connection_dir_finished_flushing(dir_connection_t *conn) { tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_DIR); + tor_assert(conn->base_.type == CONN_TYPE_DIR); /* Note that we have finished writing the directory response. For direct * connections this means we're done, for tunneled connections its only * an intermediate step. */ - if (TO_CONN(conn)->dirreq_id) - geoip_change_dirreq_state(TO_CONN(conn)->dirreq_id, DIRREQ_TUNNELED, + if (conn->dirreq_id) + geoip_change_dirreq_state(conn->dirreq_id, DIRREQ_TUNNELED, DIRREQ_FLUSHING_DIR_CONN_FINISHED); else geoip_change_dirreq_state(TO_CONN(conn)->global_identifier, DIRREQ_DIRECT, DIRREQ_FLUSHING_DIR_CONN_FINISHED); - switch (conn->_base.state) { + switch (conn->base_.state) { case DIR_CONN_STATE_CONNECTING: case DIR_CONN_STATE_CLIENT_SENDING: log_debug(LD_DIR,"client finished sending command."); - conn->_base.state = DIR_CONN_STATE_CLIENT_READING; + conn->base_.state = DIR_CONN_STATE_CLIENT_READING; return 0; case DIR_CONN_STATE_SERVER_WRITING: if (conn->dir_spool_src != DIR_SPOOL_NONE) { @@ -3573,7 +3617,7 @@ connection_dir_finished_flushing(dir_connection_t *conn) return 0; default: log_warn(LD_BUG,"called in unexpected state %d.", - conn->_base.state); + conn->base_.state); tor_fragile_assert(); return -1; } @@ -3586,13 +3630,13 @@ int connection_dir_finished_connecting(dir_connection_t *conn) { tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_DIR); - tor_assert(conn->_base.state == DIR_CONN_STATE_CONNECTING); + tor_assert(conn->base_.type == CONN_TYPE_DIR); + tor_assert(conn->base_.state == DIR_CONN_STATE_CONNECTING); log_debug(LD_HTTP,"Dir connection to router %s:%u established.", - conn->_base.address,conn->_base.port); + conn->base_.address,conn->base_.port); - conn->_base.state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ + conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ return 0; } @@ -3606,13 +3650,13 @@ dir_networkstatus_download_failed(smartlist_t *failed, int status_code) return; SMARTLIST_FOREACH_BEGIN(failed, const char *, fp) { char digest[DIGEST_LEN]; - trusted_dir_server_t *dir; + dir_server_t *dir; if (base16_decode(digest, DIGEST_LEN, fp, strlen(fp))<0) { log_warn(LD_BUG, "Called with bad fingerprint in list: %s", escaped(fp)); continue; } - dir = router_get_trusteddirserver_by_digest(digest); + dir = router_get_fallback_dirserver_by_digest(digest); if (dir) download_status_failed(&dir->v2_ns_dl_status, status_code); @@ -3829,7 +3873,7 @@ dir_microdesc_download_failed(smartlist_t *failed, /** Helper. Compare two fp_pair_t objects, and return negative, 0, or * positive as appropriate. */ static int -_compare_pairs(const void **a, const void **b) +compare_pairs_(const void **a, const void **b) { const fp_pair_t *fp1 = *a, *fp2 = *b; int r; @@ -3880,8 +3924,8 @@ dir_split_resource_into_fingerprint_pairs(const char *res, smartlist_free(pairs_tmp); /* Uniq-and-sort */ - smartlist_sort(pairs_result, _compare_pairs); - smartlist_uniq(pairs_result, _compare_pairs, _tor_free); + smartlist_sort(pairs_result, compare_pairs_); + smartlist_uniq(pairs_result, compare_pairs_, tor_free_); smartlist_add_all(pairs_out, pairs_result); smartlist_free(pairs_result); diff --git a/src/or/directory.h b/src/or/directory.h index 1ca1c5a6e..9ff78d12c 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -9,8 +9,8 @@ * \brief Header file for directory.c. **/ -#ifndef _TOR_DIRECTORY_H -#define _TOR_DIRECTORY_H +#ifndef TOR_DIRECTORY_H +#define TOR_DIRECTORY_H int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, @@ -22,10 +22,24 @@ void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, void directory_get_from_all_authorities(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); + +/** Enumeration of ways to connect to a directory server */ +typedef enum { + /** Default: connect over a one-hop Tor circuit but fall back to direct + * connection */ + DIRIND_ONEHOP=0, + /** Connect over a multi-hop anonymizing Tor circuit */ + DIRIND_ANONYMOUS=1, + /** Conncet to the DirPort directly */ + DIRIND_DIRECT_CONN, + /** Connect over a multi-hop anonymizing Tor circuit to our dirport */ + DIRIND_ANON_DIRPORT, +} dir_indirection_t; + void directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, @@ -33,7 +47,7 @@ void directory_initiate_command_routerstatus(const routerstatus_t *status, void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, @@ -51,10 +65,9 @@ int connection_dir_finished_connecting(dir_connection_t *conn); void connection_dir_about_to_close(dir_connection_t *dir_conn); void directory_initiate_command(const char *address, const tor_addr_t *addr, uint16_t or_port, uint16_t dir_port, - int supports_conditional_consensus, - int supports_begindir, const char *digest, + const char *digest, uint8_t dir_purpose, uint8_t router_purpose, - int anonymized_connection, + dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since); diff --git a/src/or/dirserv.c b/src/or/dirserv.c index f1c9c6232..7ffd75352 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -7,6 +7,10 @@ #include "or.h" #include "buffers.h" #include "config.h" +#include "confparse.h" +#include "channel.h" +#include "channeltls.h" +#include "command.h" #include "connection.h" #include "connection_or.h" #include "control.h" @@ -62,6 +66,19 @@ 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, MIN_METHOD_FOR_P6_LINES - 1}, + {MIN_METHOD_FOR_P6_LINES, MIN_METHOD_FOR_NTOR_KEY - 1}, + {MIN_METHOD_FOR_NTOR_KEY, 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); @@ -79,7 +96,8 @@ static const signed_descriptor_t *get_signed_descriptor_by_fp( const char *fp, int extrainfo, time_t publish_cutoff); -static int dirserv_add_extrainfo(extrainfo_t *ei, const char **msg); +static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei, + const char **msg); /************** Measured Bandwidth parsing code ******/ #define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */ @@ -388,18 +406,18 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, strmap_size(fingerprint_list->fp_by_name), digestmap_size(fingerprint_list->status_by_digest)); - /* Versions before Tor 0.2.1.30 have known security issues that + /* Versions before Tor 0.2.2.35 have known security issues that * make them unsuitable for the current network. */ - if (platform && !tor_version_as_new_as(platform,"0.2.1.30")) { + if (platform && !tor_version_as_new_as(platform,"0.2.2.35")) { if (msg) - *msg = "Tor version is insecure. Please upgrade!"; + *msg = "Tor version is insecure or unsupported. Please upgrade!"; return FP_REJECT; - } else if (platform && tor_version_as_new_as(platform,"0.2.2.1-alpha")) { - /* Versions from 0.2.2.1-alpha...0.2.2.20-alpha have known security + } else if (platform && tor_version_as_new_as(platform,"0.2.3.0-alpha")) { + /* Versions from 0.2.3-alpha...0.2.3.9-alpha have known security * issues that make them unusable for the current network */ - if (!tor_version_as_new_as(platform, "0.2.2.21-alpha")) { + if (!tor_version_as_new_as(platform, "0.2.3.10-alpha")) { if (msg) - *msg = "Tor version is insecure. Please upgrade!"; + *msg = "Tor version is insecure or unsupported. Please upgrade!"; return FP_REJECT; } } @@ -501,8 +519,8 @@ dirserv_free_fingerprint_list(void) if (!fingerprint_list) return; - strmap_free(fingerprint_list->fp_by_name, _tor_free); - digestmap_free(fingerprint_list->status_by_digest, _tor_free); + strmap_free(fingerprint_list->fp_by_name, tor_free_); + digestmap_free(fingerprint_list->status_by_digest, tor_free_); tor_free(fingerprint_list); } @@ -717,7 +735,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) "MAX_DESCRIPTOR_UPLOAD_SIZE (%d) constant is too low.", ri->nickname, source, (int)ri->cache_info.signed_descriptor_len, MAX_DESCRIPTOR_UPLOAD_SIZE); - *msg = "Router descriptor was too large"; + *msg = "Router descriptor was too large."; control_event_or_authdir_new_descriptor("REJECTED", ri->cache_info.signed_descriptor_body, ri->cache_info.signed_descriptor_len, *msg); @@ -980,6 +998,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 +1007,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 != 1 || + tor_addr_is_null(&router->ipv6_addr) || + now < node->last_reachable6 + REACHABLE_TIMEOUT)); } if (!answer && running_long_enough_to_decide_unreachable()) { @@ -1008,11 +1037,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); } @@ -1117,6 +1148,8 @@ int dirserv_dump_directory_to_string(char **dir_out, crypto_pk_t *private_key) { + /* XXXX 024 Get rid of this function if we can confirm that nobody's + * fetching these any longer */ char *cp; char *identity_pkey; /* Identity key, DER64-encoded. */ char *recommended_versions; @@ -1399,7 +1432,7 @@ clear_cached_dir(cached_dir_t *d) /** Free all storage held by the cached_dir_t in <b>d</b>. */ static void -_free_cached_dir(void *_d) +free_cached_dir_(void *_d) { cached_dir_t *d; if (!_d) @@ -1417,21 +1450,12 @@ _free_cached_dir(void *_d) * If <b>is_running_routers</b>, this is really a v1 running_routers * document rather than a v1 directory. */ -void -dirserv_set_cached_directory(const char *directory, time_t published, - int is_running_routers) +static void +dirserv_set_cached_directory(const char *directory, time_t published) { - time_t now = time(NULL); - if (is_running_routers) { - if (published >= now - MAX_V1_RR_AGE) - set_cached_dir(&cached_runningrouters, tor_strdup(directory), published); - } else { - if (published >= now - MAX_V1_DIRECTORY_AGE) { - cached_dir_decref(cached_directory); - cached_directory = new_cached_dir(tor_strdup(directory), published); - } - } + cached_dir_decref(cached_directory); + cached_directory = new_cached_dir(tor_strdup(directory), published); } /** If <b>networkstatus</b> is non-NULL, we've just received a v2 @@ -1448,7 +1472,6 @@ dirserv_set_cached_networkstatus_v2(const char *networkstatus, time_t published) { cached_dir_t *d, *old_d; - smartlist_t *trusted_dirs; if (!cached_v2_networkstatus) cached_v2_networkstatus = digestmap_new(); @@ -1471,9 +1494,9 @@ dirserv_set_cached_networkstatus_v2(const char *networkstatus, } /* Now purge old entries. */ - trusted_dirs = router_get_trusted_dir_servers(); + if (digestmap_size(cached_v2_networkstatus) > - smartlist_len(trusted_dirs) + MAX_UNTRUSTED_NETWORKSTATUSES) { + get_n_authorities(V2_DIRINFO) + MAX_UNTRUSTED_NETWORKSTATUSES) { /* We need to remove the oldest untrusted networkstatus. */ const char *oldest = NULL; time_t oldest_published = TIME_MAX; @@ -1613,6 +1636,8 @@ dirserv_get_directory(void) static cached_dir_t * dirserv_regenerate_directory(void) { + /* XXXX 024 Get rid of this function if we can confirm that nobody's + * fetching these any longer */ char *new_directory=NULL; if (dirserv_dump_directory_to_string(&new_directory, @@ -1632,7 +1657,7 @@ dirserv_regenerate_directory(void) /* Save the directory to disk so we re-load it quickly on startup. */ - dirserv_set_cached_directory(the_directory->dir, time(NULL), 0); + dirserv_set_cached_directory(the_directory->dir, time(NULL)); return the_directory; } @@ -2040,7 +2065,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 +2104,32 @@ 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)) { + r = tor_snprintf(cp, buf_len - (cp-buf), + "a %s\n", + fmt_addrport(&rs->ipv6_addr, 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; } @@ -2192,7 +2234,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, } if (desc) { - summary = policy_summarize(desc->exit_policy); + summary = policy_summarize(desc->exit_policy, AF_INET); r = tor_snprintf(cp, buf_len - (cp-buf), "p %s\n", summary); if (r<0) { log_warn(LD_BUG, "Not enough space in buffer."); @@ -2213,7 +2255,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, * and a router with more bandwidth is more useful than one with less.) **/ static int -_compare_routerinfo_by_ip_and_bw(const void **a, const void **b) +compare_routerinfo_by_ip_and_bw_(const void **a, const void **b) { routerinfo_t *first = *(routerinfo_t **)a, *second = *(routerinfo_t **)b; int first_is_auth, second_is_auth; @@ -2228,7 +2270,7 @@ _compare_routerinfo_by_ip_and_bw(const void **a, const void **b) else if (first->addr > second->addr) return 1; - /* Potentially, this next bit could cause k n lg n memcmp calls. But in + /* Potentially, this next bit could cause k n lg n memeq calls. But in * reality, we will almost never get here, since addresses will usually be * different. */ @@ -2288,7 +2330,7 @@ get_possible_sybil_list(const smartlist_t *routers) max_with_same_addr_on_authority = INT_MAX; smartlist_add_all(routers_by_ip, routers); - smartlist_sort(routers_by_ip, _compare_routerinfo_by_ip_and_bw); + smartlist_sort(routers_by_ip, compare_routerinfo_by_ip_and_bw_); omit_as_sybil = digestmap_new(); last_addr = 0; @@ -2393,8 +2435,6 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, int listbaddirs, int vote_on_hsdirs) { const or_options_t *options = get_options(); - int unstable_version = - !tor_version_as_new_as(ri->platform,"0.1.1.16-rc-cvs"); uint32_t routerbw = router_get_advertised_bandwidth(ri); memset(rs, 0, sizeof(routerstatus_t)); @@ -2406,8 +2446,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, rs->is_exit = node->is_exit; rs->is_stable = node->is_stable = router_is_active(ri, node, now) && - !dirserv_thinks_router_is_unreliable(now, ri, 1, 0) && - !unstable_version; + !dirserv_thinks_router_is_unreliable(now, ri, 1, 0); rs->is_fast = node->is_fast = router_is_active(ri, node, now) && !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); @@ -2453,6 +2492,14 @@ 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->AuthDirHasIPv6Connectivity == 1 && + !tor_addr_is_null(&ri->ipv6_addr) && + node->last_reachable6 >= now - REACHABLE_TIMEOUT) { + /* We're configured as having IPv6 connectivity. There's an IPv6 + OR port and it's reachable 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 +2762,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,17 +2784,22 @@ 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; - md->last_listed = now; - smartlist_add(microdescriptors, md); + 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); + } } smartlist_add(routerstatuses, vrs); @@ -3074,7 +3127,7 @@ dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, } } else { SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds, + dir_server_t *, ds, if (ds->type & V2_DIRINFO) smartlist_add(result, tor_memdup(ds->digest, DIGEST_LEN))); } @@ -3273,36 +3326,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 +3384,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 +3397,35 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri, void dirserv_single_reachability_test(time_t now, routerinfo_t *router) { + channel_t *chan = NULL; + node_t *node = NULL; tor_addr_t router_addr; + (void) now; + + 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; tor_addr_from_ipv4h(&router_addr, router->addr); - connection_or_connect(&router_addr, router->or_port, - router->cache_info.identity_digest); + chan = channel_tls_connect(&router_addr, router->or_port, + router->cache_info.identity_digest); + if (chan) command_setup_channel(chan); + + /* Possible IPv6. */ + if (get_options()->AuthDirHasIPv6Connectivity == 1 && + !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); + chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport, + router->cache_info.identity_digest); + if (chan) command_setup_channel(chan); + } } /** Auth dir server only: load balance such that we only @@ -3767,7 +3846,7 @@ connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn) int connection_dirserv_flushed_some(dir_connection_t *conn) { - tor_assert(conn->_base.state == DIR_CONN_STATE_SERVER_WRITING); + tor_assert(conn->base_.state == DIR_CONN_STATE_SERVER_WRITING); if (connection_get_outbuf_len(TO_CONN(conn)) >= DIRSERV_BUFFER_MIN) return 0; @@ -3802,9 +3881,9 @@ dirserv_free_all(void) cached_dir_decref(cached_directory); clear_cached_dir(&cached_runningrouters); - digestmap_free(cached_v2_networkstatus, _free_cached_dir); + digestmap_free(cached_v2_networkstatus, free_cached_dir_); cached_v2_networkstatus = NULL; - strmap_free(cached_consensuses, _free_cached_dir); + strmap_free(cached_consensuses, free_cached_dir_); cached_consensuses = NULL; } diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 22269b200..0140cfb4d 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -9,8 +9,8 @@ * \brief Header file for dirserv.c. **/ -#ifndef _TOR_DIRSERV_H -#define _TOR_DIRSERV_H +#ifndef TOR_DIRSERV_H +#define TOR_DIRSERV_H /** What fraction (1 over this number) of the relay ID space do we * (as a directory authority) launch connections to at each reachability @@ -87,8 +87,6 @@ void directory_set_dirty(void); cached_dir_t *dirserv_get_directory(void); cached_dir_t *dirserv_get_runningrouters(void); cached_dir_t *dirserv_get_consensus(const char *flavor_name); -void dirserv_set_cached_directory(const char *directory, time_t when, - int is_running_routers); void dirserv_set_cached_networkstatus_v2(const char *directory, const char *identity, time_t published); @@ -107,7 +105,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 144859ae0..ef1e506f4 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 * =====*/ @@ -346,7 +323,7 @@ typedef struct dir_src_ent_t { /** Helper for sorting networkstatus_t votes (not consensuses) by the * hash of their voters' identity digests. */ static int -_compare_votes_by_authority_id(const void **_a, const void **_b) +compare_votes_by_authority_id_(const void **_a, const void **_b) { const networkstatus_t *a = *_a, *b = *_b; return fast_memcmp(get_voter(a)->identity_digest, @@ -357,7 +334,7 @@ _compare_votes_by_authority_id(const void **_a, const void **_b) * their identity digests, and return -1, 0, or 1 depending on their * ordering */ static int -_compare_dir_src_ents_by_authority_id(const void **_a, const void **_b) +compare_dir_src_ents_by_authority_id_(const void **_a, const void **_b) { const dir_src_ent_t *a = *_a, *b = *_b; const networkstatus_voter_info_t *a_v = get_voter(a->v), @@ -424,12 +401,27 @@ compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b) /** Helper for sorting routerlists based on compare_vote_rs. */ static int -_compare_vote_rs(const void **_a, const void **_b) +compare_vote_rs_(const void **_a, const void **_b) { const vote_routerstatus_t *a = *_a, *b = *_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,17 +429,18 @@ _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; time_t most_published = 0; - /* _compare_vote_rs() sorts the items by identity digest (all the same), + /* compare_vote_rs_() sorts the items by identity digest (all the same), * then by SD digest. That way, if we have a tie that the published_on * date cannot tie, we use the descriptor with the smaller digest. */ - smartlist_sort(votes, _compare_vote_rs); + smartlist_sort(votes, compare_vote_rs_); SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) { if (cur && !compare_vote_rs(cur, rs)) { ++cur_n; @@ -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", + most->status.nickname, + fmt_addrport(&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(); @@ -518,7 +543,7 @@ hash_list_members(char *digest_out, size_t len_out, * positive integers. (Non-integers are treated as prior to all integers, and * compared lexically.) */ static int -_cmp_int_strings(const void **_a, const void **_b) +cmp_int_strings_(const void **_a, const void **_b) { const char *a = *_a, *b = *_b; int ai = (int)tor_parse_long(a, 10, 1, INT_MAX, NULL, NULL); @@ -549,13 +574,13 @@ compute_consensus_method(smartlist_t *votes) { tor_assert(vote->supported_methods); smartlist_add_all(tmp, vote->supported_methods); - smartlist_sort(tmp, _cmp_int_strings); - smartlist_uniq(tmp, _cmp_int_strings, NULL); + smartlist_sort(tmp, cmp_int_strings_); + smartlist_uniq(tmp, cmp_int_strings_, NULL); smartlist_add_all(all_methods, tmp); smartlist_clear(tmp); }); - smartlist_sort(all_methods, _cmp_int_strings); + smartlist_sort(all_methods, cmp_int_strings_); get_frequent_members(acceptable_methods, all_methods, min); n_ok = smartlist_len(acceptable_methods); if (n_ok) { @@ -1504,7 +1529,7 @@ networkstatus_compute_consensus(smartlist_t *votes, } /* Sort the votes. */ - smartlist_sort(votes, _compare_votes_by_authority_id); + smartlist_sort(votes, compare_votes_by_authority_id_); /* Add the authority sections. */ { smartlist_t *dir_sources = smartlist_new(); @@ -1523,7 +1548,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add(dir_sources, e_legacy); } } SMARTLIST_FOREACH_END(v); - smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id); + smartlist_sort(dir_sources, compare_dir_src_ents_by_authority_id_); SMARTLIST_FOREACH_BEGIN(dir_sources, const dir_src_ent_t *, e) { char fingerprint[HEX_DIGEST_LEN+1]; @@ -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; @@ -1862,7 +1892,7 @@ networkstatus_compute_consensus(smartlist_t *votes, * listed that descriptor will have the same summary. If not then * something is fishy and we'll use the most common one (breaking * ties in favor of lexicographically larger one (only because it - * lets me reuse more existing code. + * lets me reuse more existing code)). * * The other case that can happen is that no authority that voted * for that descriptor has an exit policy summary. That's @@ -2193,7 +2223,7 @@ networkstatus_add_detached_signatures(networkstatus_t *target, { digests_t *digests = strmap_get(sigs->digests, flavor); int n_matches = 0; - digest_algorithm_t alg; + int alg; if (!digests) { *msg_out = "No digests for given consensus flavor"; return -1; @@ -2457,7 +2487,7 @@ ns_detached_signatures_free(ns_detached_signatures_t *s) smartlist_free(sigs); } STRMAP_FOREACH_END; strmap_free(s->signatures, NULL); - strmap_free(s->digests, _tor_free); + strmap_free(s->digests, tor_free_); } tor_free(s); @@ -2757,7 +2787,7 @@ dirvote_fetch_missing_votes(void) char *resource; SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds) { + dir_server_t *, ds) { if (!(ds->type & V3_DIRINFO)) continue; if (!dirvote_get_vote(ds->v3_identity_digest, @@ -2875,7 +2905,7 @@ list_v3_auth_ids(void) smartlist_t *known_v3_keys = smartlist_new(); char *keys; SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds, + dir_server_t *, ds, if ((ds->type & V3_DIRINFO) && !tor_digest_is_zero(ds->v3_identity_digest)) smartlist_add(known_v3_keys, @@ -2896,7 +2926,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) { networkstatus_t *vote; networkstatus_voter_info_t *vi; - trusted_dir_server_t *ds; + dir_server_t *ds; pending_vote_t *pending_vote = NULL; const char *end_of_vote = NULL; int any_failed = 0; @@ -3441,7 +3471,7 @@ dirvote_free_all(void) const char * dirvote_get_pending_consensus(consensus_flavor_t flav) { - tor_assert(((int)flav) >= 0 && flav < N_CONSENSUS_FLAVORS); + tor_assert(((int)flav) >= 0 && (int)flav < N_CONSENSUS_FLAVORS); return pending_consensuses[flav].body; } @@ -3504,15 +3534,11 @@ dirvote_get_vote(const char *fp, int flags) return NULL; } -/** Construct and return a new microdescriptor from a routerinfo <b>ri</b>. - * - * XXX Right now, there is only one way to generate microdescriptors from - * router descriptors. This may change in future consensus methods. If so, - * we'll need an internal way to remember which method we used, and ask for a - * particular method. +/** Construct and return a new microdescriptor from a routerinfo <b>ri</b> + * according to <b>consensus_method</b>. **/ 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; @@ -3522,18 +3548,43 @@ dirvote_create_microdescriptor(const routerinfo_t *ri) if (crypto_pk_write_public_key_to_string(ri->onion_pkey, &key, &keylen)<0) goto done; - summary = policy_summarize(ri->exit_policy); + summary = policy_summarize(ri->exit_policy, AF_INET); if (ri->declared_family) family = smartlist_join_strings(ri->declared_family, " ", 0, NULL); smartlist_add_asprintf(chunks, "onion-key\n%s", key); + if (consensus_method >= MIN_METHOD_FOR_NTOR_KEY && + ri->onion_curve25519_pkey) { + char kbuf[128]; + base64_encode(kbuf, sizeof(kbuf), + (const char*)ri->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN); + smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf); + } + + if (consensus_method >= MIN_METHOD_FOR_A_LINES && + !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport) + smartlist_add_asprintf(chunks, "a %s\n", + fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); + if (family) smartlist_add_asprintf(chunks, "family %s\n", family); if (summary && strcmp(summary, "reject 1-65535")) smartlist_add_asprintf(chunks, "p %s\n", summary); + if (consensus_method >= MIN_METHOD_FOR_P6_LINES && + ri->ipv6_exit_policy) { + /* XXXX024 This doesn't match proposal 208, which says these should + * be taken unchanged from the routerinfo. That's bogosity, IMO: + * the proposal should have said to do this instead.*/ + char *p6 = write_short_policy(ri->ipv6_exit_policy); + if (p6 && strcmp(p6, "reject 1-65535")) + smartlist_add_asprintf(chunks, "p6 %s\n", p6); + tor_free(p6); + } + output = smartlist_join_strings(chunks, "", 0, NULL); { @@ -3561,33 +3612,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) { + ssize_t 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 e6f970061..19444c370 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -9,8 +9,8 @@ * \brief Header file for dirvote.c. **/ -#ifndef _TOR_DIRVOTE_H -#define _TOR_DIRVOTE_H +#ifndef TOR_DIRVOTE_H +#define TOR_DIRVOTE_H /** Lowest allowable value for VoteSeconds. */ #define MIN_VOTE_SECONDS 20 @@ -19,6 +19,39 @@ /** Smallest allowable voting interval. */ #define MIN_VOTE_INTERVAL 300 +/** The highest consensus method that we currently support. */ +#define MAX_SUPPORTED_CONSENSUS_METHOD 16 + +/** 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 + +/** Lowest consensus method where microdescs may include a "p6" line. */ +#define MIN_METHOD_FOR_P6_LINES 15 + +/** Lowest consensus method where microdescs may include an onion-key-ntor + * line */ +#define MIN_METHOD_FOR_NTOR_KEY 16 + void dirvote_free_all(void); /* vote manipulation */ @@ -70,10 +103,12 @@ 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/dns.c b/src/or/dns.c index 78893bfbe..c81948a60 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -61,6 +61,9 @@ struct evdns_request; #define evdns_base_resolve_ipv4(base, addr, options, cb, ptr) \ ((evdns_resolve_ipv4((addr), (options), (cb), (ptr))!=0) \ ? NULL : ((void*)1)) +#define evdns_base_resolve_ipv6(base, addr, options, cb, ptr) \ + ((evdns_resolve_ipv6((addr), (options), (cb), (ptr))!=0) \ + ? NULL : ((void*)1)) #define evdns_base_resolve_reverse(base, addr, options, cb, ptr) \ ((evdns_resolve_reverse((addr), (options), (cb), (ptr))!=0) \ ? NULL : ((void*)1)) @@ -84,12 +87,6 @@ struct evdns_request; * that the resolver is wedged? */ #define RESOLVE_MAX_TIMEOUT 300 -/** Possible outcomes from hostname lookup: permanent failure, - * transient (retryable) failure, and success. */ -#define DNS_RESOLVE_FAILED_TRANSIENT 1 -#define DNS_RESOLVE_FAILED_PERMANENT 2 -#define DNS_RESOLVE_SUCCEEDED 3 - /** Our evdns_base; this structure handles all our name lookups. */ static struct evdns_base *the_evdns_base = NULL; @@ -117,7 +114,7 @@ typedef struct pending_connection_t { /* Possible states for a cached resolve_t */ /** We are waiting for the resolver system to tell us an answer here. * When we get one, or when we time out, the state of this cached_resolve_t - * will become "DONE" and we'll possibly add a CACHED_VALID or a CACHED_FAILED + * will become "DONE" and we'll possibly add a CACHED * entry. This cached_resolve_t will be in the hash table so that we will * know not to launch more requests for this addr, but rather to add more * connections to the pending list for the addr. */ @@ -128,10 +125,18 @@ typedef struct pending_connection_t { #define CACHE_STATE_DONE 1 /** We are caching an answer for this address. This should have no pending * connections, and should appear in the hash table. */ -#define CACHE_STATE_CACHED_VALID 2 -/** We are caching a failure for this address. This should have no pending - * connections, and should appear in the hash table */ -#define CACHE_STATE_CACHED_FAILED 3 +#define CACHE_STATE_CACHED 2 + +/** @name status values for a single DNS request. + * + * @{ */ +/** The DNS request is in progress. */ +#define RES_STATUS_INFLIGHT 1 +/** The DNS request finished and gave an answer */ +#define RES_STATUS_DONE_OK 2 +/** The DNS request finished and gave an error */ +#define RES_STATUS_DONE_ERR 3 +/**@}*/ /** A DNS request: possibly completed, possibly pending; cached_resolve * structs are stored at the OR side in a hash table, and as a linked @@ -139,19 +144,39 @@ typedef struct pending_connection_t { */ typedef struct cached_resolve_t { HT_ENTRY(cached_resolve_t) node; - uint32_t magic; + uint32_t magic; /**< Must be CACHED_RESOLVE_MAGIC */ char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */ + + union { + uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>, if successful. + * (In host order.) */ + int err_ipv4; /**< One of DNS_ERR_*, if IPv4 lookup failed. */ + } result_ipv4; /**< Outcome of IPv4 lookup */ + union { + struct in6_addr addr_ipv6; /**< IPv6 addr for <b>address</b>, if + * successful */ + int err_ipv6; /**< One of DNS_ERR_*, if IPv6 lookup failed. */ + } result_ipv6; /**< Outcome of IPv6 lookup, if any */ union { - struct { - struct in6_addr addr6; /**< IPv6 addr for <b>address</b>. */ - uint32_t addr; /**< IPv4 addr for <b>address</b>. */ - } a; - char *hostname; /**< Hostname for <b>address</b> (if a reverse lookup) */ - } result; - uint8_t state; /**< Is this cached entry pending/done/valid/failed? */ - uint8_t is_reverse; /**< Is this a reverse (addr-to-hostname) lookup? */ + char *hostname; /** A hostname, if PTR lookup happened successfully*/ + int err_hostname; /** One of DNS_ERR_*, if PTR lookup failed. */ + } result_ptr; + /** @name Status fields + * + * These take one of the RES_STATUS_* values, depending on the state + * of the corresponding lookup. + * + * @{ */ + unsigned int res_status_ipv4 : 2; + unsigned int res_status_ipv6 : 2; + unsigned int res_status_hostname : 2; + /**@}*/ + uint8_t state; /**< Is this cached entry pending/done/informative? */ + time_t expire; /**< Remove items from cache after this time. */ - uint32_t ttl; /**< What TTL did the nameserver tell us? */ + uint32_t ttl_ipv4; /**< What TTL did the nameserver tell us? */ + uint32_t ttl_ipv6; /**< What TTL did the nameserver tell us? */ + uint32_t ttl_hostname; /**< What TTL did the nameserver tell us? */ /** Connections that want to know when we get an answer for this resolve. */ pending_connection_t *pending_connections; /** Position of this element in the heap*/ @@ -159,20 +184,31 @@ typedef struct cached_resolve_t { } cached_resolve_t; static void purge_expired_resolves(time_t now); -static void dns_found_answer(const char *address, uint8_t is_reverse, - uint32_t addr, const char *hostname, char outcome, +static void dns_found_answer(const char *address, uint8_t query_type, + int dns_answer, + const tor_addr_t *addr, + const char *hostname, uint32_t ttl); -static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type); -static int launch_resolve(edge_connection_t *exitconn); +static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type, + const cached_resolve_t *resolve); +static int launch_resolve(cached_resolve_t *resolve); static void add_wildcarded_test_address(const char *address); static int configure_nameservers(int force); static int answer_is_wildcarded(const char *ip); static int dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, or_circuit_t *oncirc, char **resolved_to_hostname, - int *made_connection_pending_out); + int *made_connection_pending_out, + cached_resolve_t **resolve_out); +static int set_exitconn_info_from_resolve(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out); +static int evdns_err_is_transient(int err); +static void inform_pending_connections(cached_resolve_t *resolve); +static void make_pending_resolve_cached(cached_resolve_t *cached); + #ifdef DEBUG_DNS_CACHE -static void _assert_cache_ok(void); -#define assert_cache_ok() _assert_cache_ok() +static void assert_cache_ok_(void); +#define assert_cache_ok() assert_cache_ok_() #else #define assert_cache_ok() STMT_NIL #endif @@ -181,6 +217,13 @@ static void assert_resolve_ok(cached_resolve_t *resolve); /** Hash table of cached_resolve objects. */ static HT_HEAD(cache_map, cached_resolve_t) cache_root; +/** Global: how many IPv6 requests have we made in all? */ +static uint64_t n_ipv6_requests_made = 0; +/** Global: how many IPv6 requests have timed out? */ +static uint64_t n_ipv6_timeouts = 0; +/** Global: Do we think that IPv6 DNS is broken? */ +static int dns_is_broken_for_ipv6 = 0; + /** Function to compare hashed resolves on their addresses; used to * implement hash tables. */ static INLINE int @@ -254,7 +297,7 @@ evdns_log_cb(int warn, const char *msg) /** Helper: passed to eventdns.c as a callback so it can generate random * numbers for transaction IDs and 0x20-hack coding. */ static void -_dns_randfn(char *b, size_t n) +dns_randfn_(char *b, size_t n) { crypto_rand(b,n); } @@ -264,7 +307,7 @@ int dns_init(void) { init_cache_map(); - evdns_set_random_bytes_fn(_dns_randfn); + evdns_set_random_bytes_fn(dns_randfn_); if (server_mode(get_options())) { int r = configure_nameservers(1); return r; @@ -336,7 +379,7 @@ dns_get_expiry_ttl(uint32_t ttl) /** Helper: free storage held by an entry in the DNS cache. */ static void -_free_cached_resolve(cached_resolve_t *r) +free_cached_resolve_(cached_resolve_t *r) { if (!r) return; @@ -345,8 +388,8 @@ _free_cached_resolve(cached_resolve_t *r) r->pending_connections = victim->next; tor_free(victim); } - if (r->is_reverse) - tor_free(r->result.hostname); + if (r->res_status_hostname == RES_STATUS_DONE_OK) + tor_free(r->result_ptr.hostname); r->magic = 0xFF00FF00; tor_free(r); } @@ -355,7 +398,7 @@ _free_cached_resolve(cached_resolve_t *r) * less-than-zero, zero, or greater-than-zero as appropriate. Used for * the priority queue implementation. */ static int -_compare_cached_resolves_by_expiry(const void *_a, const void *_b) +compare_cached_resolves_by_expiry_(const void *_a, const void *_b) { const cached_resolve_t *a = _a, *b = _b; if (a->expire < b->expire) @@ -370,6 +413,65 @@ _compare_cached_resolves_by_expiry(const void *_a, const void *_b) * will expire. */ static smartlist_t *cached_resolve_pqueue = NULL; +static void +cached_resolve_add_answer(cached_resolve_t *resolve, + int query_type, + int dns_result, + const tor_addr_t *answer_addr, + const char *answer_hostname, + uint32_t ttl) +{ + if (query_type == DNS_PTR) { + if (resolve->res_status_hostname != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_hostname) { + resolve->result_ptr.hostname = tor_strdup(answer_hostname); + resolve->res_status_hostname = RES_STATUS_DONE_OK; + } else { + resolve->result_ptr.err_hostname = dns_result; + resolve->res_status_hostname = RES_STATUS_DONE_ERR; + } + resolve->ttl_hostname = ttl; + } else if (query_type == DNS_IPv4_A) { + if (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_addr) { + tor_assert(tor_addr_family(answer_addr) == AF_INET); + resolve->result_ipv4.addr_ipv4 = tor_addr_to_ipv4h(answer_addr); + resolve->res_status_ipv4 = RES_STATUS_DONE_OK; + } else { + resolve->result_ipv4.err_ipv4 = dns_result; + resolve->res_status_ipv4 = RES_STATUS_DONE_ERR; + } + + } else if (query_type == DNS_IPv6_AAAA) { + if (resolve->res_status_ipv6 != RES_STATUS_INFLIGHT) + return; + + if (dns_result == DNS_ERR_NONE && answer_addr) { + tor_assert(tor_addr_family(answer_addr) == AF_INET6); + memcpy(&resolve->result_ipv6.addr_ipv6, + tor_addr_to_in6(answer_addr), + sizeof(struct in6_addr)); + resolve->res_status_ipv6 = RES_STATUS_DONE_OK; + } else { + resolve->result_ipv6.err_ipv6 = dns_result; + resolve->res_status_ipv6 = RES_STATUS_DONE_ERR; + } + } +} + +/** Return true iff there are no in-flight requests for <b>resolve</b>. */ +static int +cached_resolve_have_all_answers(const cached_resolve_t *resolve) +{ + return (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT && + resolve->res_status_ipv6 != RES_STATUS_INFLIGHT && + resolve->res_status_hostname != RES_STATUS_INFLIGHT); +} + /** Set an expiry time for a cached_resolve_t, and add it to the expiry * priority queue */ static void @@ -380,7 +482,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires) cached_resolve_pqueue = smartlist_new(); resolve->expire = expires; smartlist_pqueue_add(cached_resolve_pqueue, - _compare_cached_resolves_by_expiry, + compare_cached_resolves_by_expiry_, STRUCT_OFFSET(cached_resolve_t, minheap_idx), resolve); } @@ -395,13 +497,13 @@ dns_free_all(void) SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, { if (res->state == CACHE_STATE_DONE) - _free_cached_resolve(res); + free_cached_resolve_(res); }); } for (ptr = HT_START(cache_map, &cache_root); ptr != NULL; ptr = next) { item = *ptr; next = HT_NEXT_RMV(cache_map, &cache_root, ptr); - _free_cached_resolve(item); + free_cached_resolve_(item); } HT_CLEAR(cache_map, &cache_root); smartlist_free(cached_resolve_pqueue); @@ -427,7 +529,7 @@ purge_expired_resolves(time_t now) if (resolve->expire > now) break; smartlist_pqueue_pop(cached_resolve_pqueue, - _compare_cached_resolves_by_expiry, + compare_cached_resolves_by_expiry_, STRUCT_OFFSET(cached_resolve_t, minheap_idx)); if (resolve->state == CACHE_STATE_PENDING) { @@ -435,8 +537,7 @@ purge_expired_resolves(time_t now) "Expiring a dns resolve %s that's still pending. Forgot to " "cull it? DNS resolve didn't tell us about the timeout?", escaped_safe_str(resolve->address)); - } else if (resolve->state == CACHE_STATE_CACHED_VALID || - resolve->state == CACHE_STATE_CACHED_FAILED) { + } else if (resolve->state == CACHE_STATE_CACHED) { log_debug(LD_EXIT, "Forgetting old cached resolve (address %s, expires %lu)", escaped_safe_str(resolve->address), @@ -454,9 +555,9 @@ purge_expired_resolves(time_t now) pend = resolve->pending_connections; resolve->pending_connections = pend->next; /* Connections should only be pending if they have no socket. */ - tor_assert(!SOCKET_OK(pend->conn->_base.s)); + tor_assert(!SOCKET_OK(pend->conn->base_.s)); pendconn = pend->conn; - if (!pendconn->_base.marked_for_close) { + if (!pendconn->base_.marked_for_close) { connection_edge_end(pendconn, END_STREAM_REASON_TIMEOUT); circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); connection_free(TO_CONN(pendconn)); @@ -465,8 +566,7 @@ purge_expired_resolves(time_t now) } } - if (resolve->state == CACHE_STATE_CACHED_VALID || - resolve->state == CACHE_STATE_CACHED_FAILED || + if (resolve->state == CACHE_STATE_CACHED || resolve->state == CACHE_STATE_PENDING) { removed = HT_REMOVE(cache_map, &cache_root, resolve); if (removed != resolve) { @@ -481,8 +581,8 @@ purge_expired_resolves(time_t now) cached_resolve_t *tmp = HT_FIND(cache_map, &cache_root, resolve); tor_assert(tmp != resolve); } - if (resolve->is_reverse) - tor_free(resolve->result.hostname); + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) + tor_free(resolve->result_ptr.hostname); resolve->magic = 0xF0BBF0BB; tor_free(resolve); } @@ -490,19 +590,24 @@ purge_expired_resolves(time_t now) assert_cache_ok(); } +/* argument for send_resolved_cell only, meaning "let the answer type be ipv4 + * or ipv6 depending on the connection's address". */ +#define RESOLVED_TYPE_AUTO 0xff + /** Send a response to the RESOLVE request of a connection. * <b>answer_type</b> must be one of - * RESOLVED_TYPE_(IPV4|ERROR|ERROR_TRANSIENT). + * RESOLVED_TYPE_(AUTO|ERROR|ERROR_TRANSIENT|). * * If <b>circ</b> is provided, and we have a cached answer, send the * answer back along circ; otherwise, send the answer back along * <b>conn</b>'s attached circuit. */ static void -send_resolved_cell(edge_connection_t *conn, uint8_t answer_type) +send_resolved_cell(edge_connection_t *conn, uint8_t answer_type, + const cached_resolve_t *resolved) { - char buf[RELAY_PAYLOAD_SIZE]; - size_t buflen; + char buf[RELAY_PAYLOAD_SIZE], *cp = buf; + size_t buflen = 0; uint32_t ttl; buf[0] = answer_type; @@ -510,19 +615,36 @@ send_resolved_cell(edge_connection_t *conn, uint8_t answer_type) switch (answer_type) { - case RESOLVED_TYPE_IPV4: - buf[1] = 4; - set_uint32(buf+2, tor_addr_to_ipv4n(&conn->_base.addr)); - set_uint32(buf+6, htonl(ttl)); - buflen = 10; - break; - /*XXXX IP6 need ipv6 implementation */ + case RESOLVED_TYPE_AUTO: + if (resolved && resolved->res_status_ipv4 == RES_STATUS_DONE_OK) { + cp[0] = RESOLVED_TYPE_IPV4; + cp[1] = 4; + set_uint32(cp+2, htonl(resolved->result_ipv4.addr_ipv4)); + set_uint32(cp+6, htonl(ttl)); + cp += 10; + } + if (resolved && resolved->res_status_ipv6 == RES_STATUS_DONE_OK) { + const uint8_t *bytes = resolved->result_ipv6.addr_ipv6.s6_addr; + cp[0] = RESOLVED_TYPE_IPV6; + cp[1] = 16; + memcpy(cp+2, bytes, 16); + set_uint32(cp+18, htonl(ttl)); + cp += 22; + } + if (cp != buf) { + buflen = cp - buf; + break; + } else { + answer_type = RESOLVED_TYPE_ERROR; + /* fall through. */ + } case RESOLVED_TYPE_ERROR_TRANSIENT: case RESOLVED_TYPE_ERROR: { const char *errmsg = "Error resolving hostname"; size_t msglen = strlen(errmsg); + buf[0] = answer_type; buf[1] = msglen; strlcpy(buf+2, errmsg, sizeof(buf)-2); set_uint32(buf+2+msglen, htonl(ttl)); @@ -600,10 +722,11 @@ dns_resolve(edge_connection_t *exitconn) int is_resolve, r; int made_connection_pending = 0; char *hostname = NULL; - is_resolve = exitconn->_base.purpose == EXIT_PURPOSE_RESOLVE; + cached_resolve_t *resolve = NULL; + is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE; r = dns_resolve_impl(exitconn, is_resolve, oncirc, &hostname, - &made_connection_pending); + &made_connection_pending, &resolve); switch (r) { case 1: @@ -614,7 +737,7 @@ dns_resolve(edge_connection_t *exitconn) if (hostname) send_resolved_hostname_cell(exitconn, hostname); else - send_resolved_cell(exitconn, RESOLVED_TYPE_IPV4); + send_resolved_cell(exitconn, RESOLVED_TYPE_AUTO, resolve); exitconn->on_circuit = NULL; } else { /* Add to the n_streams list; the calling function will send back a @@ -626,7 +749,7 @@ dns_resolve(edge_connection_t *exitconn) case 0: /* The request is pending: add the connection into the linked list of * resolving_streams on this circuit. */ - exitconn->_base.state = EXIT_CONN_STATE_RESOLVING; + exitconn->base_.state = EXIT_CONN_STATE_RESOLVING; exitconn->next_stream = oncirc->resolving_streams; oncirc->resolving_streams = exitconn; break; @@ -636,14 +759,15 @@ dns_resolve(edge_connection_t *exitconn) * and stop everybody waiting for the same connection. */ if (is_resolve) { send_resolved_cell(exitconn, - (r == -1) ? RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT); + (r == -1) ? RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT, + NULL); } exitconn->on_circuit = NULL; - dns_cancel_pending_resolve(exitconn->_base.address); + dns_cancel_pending_resolve(exitconn->base_.address); - if (!made_connection_pending && !exitconn->_base.marked_for_close) { + if (!made_connection_pending && !exitconn->base_.marked_for_close) { /* If we made the connection pending, then we freed it already in * dns_cancel_pending_resolve(). If we marked it for close, it'll * get freed from the main loop. Otherwise, can free it now. */ @@ -670,48 +794,50 @@ dns_resolve(edge_connection_t *exitconn) * Set *<b>made_connection_pending_out</b> to true if we have placed * <b>exitconn</b> on the list of pending connections for some resolve; set it * to false otherwise. + * + * Set *<b>resolve_out</b> to a cached resolve, if we found one. */ static int dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, or_circuit_t *oncirc, char **hostname_out, - int *made_connection_pending_out) + int *made_connection_pending_out, + cached_resolve_t **resolve_out) { cached_resolve_t *resolve; cached_resolve_t search; pending_connection_t *pending_connection; - const routerinfo_t *me; + int is_reverse = 0; tor_addr_t addr; time_t now = time(NULL); - uint8_t is_reverse = 0; int r; assert_connection_ok(TO_CONN(exitconn), 0); - tor_assert(!SOCKET_OK(exitconn->_base.s)); + tor_assert(!SOCKET_OK(exitconn->base_.s)); assert_cache_ok(); tor_assert(oncirc); *made_connection_pending_out = 0; - /* first check if exitconn->_base.address is an IP. If so, we already + /* first check if exitconn->base_.address is an IP. If so, we already * know the answer. */ - if (tor_addr_parse(&addr, exitconn->_base.address) >= 0) { - if (tor_addr_family(&addr) == AF_INET) { - tor_addr_copy(&exitconn->_base.addr, &addr); + if (tor_addr_parse(&addr, exitconn->base_.address) >= 0) { + if (tor_addr_family(&addr) == AF_INET || + tor_addr_family(&addr) == AF_INET6) { + tor_addr_copy(&exitconn->base_.addr, &addr); exitconn->address_ttl = DEFAULT_DNS_TTL; return 1; } else { - /* XXXX IPv6 */ + /* XXXX unspec? Bogus? */ return -1; } } /* If we're a non-exit, don't even do DNS lookups. */ - if (!(me = router_get_my_routerinfo()) || - policy_is_reject_star(me->exit_policy)) { + if (router_my_exit_policy_is_reject_star()) return -1; - } - if (address_is_invalid_destination(exitconn->_base.address, 0)) { + + if (address_is_invalid_destination(exitconn->base_.address, 0)) { log(LOG_PROTOCOL_WARN, LD_EXIT, "Rejecting invalid destination address %s", - escaped_safe_str(exitconn->_base.address)); + escaped_safe_str(exitconn->base_.address)); return -1; } @@ -719,14 +845,14 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, * resolves in the hash table. */ purge_expired_resolves(now); - /* lower-case exitconn->_base.address, so it's in canonical form */ - tor_strlower(exitconn->_base.address); + /* lower-case exitconn->base_.address, so it's in canonical form */ + tor_strlower(exitconn->base_.address); /* Check whether this is a reverse lookup. If it's malformed, or it's a * .in-addr.arpa address but this isn't a resolve request, kill the * connection. */ - if ((r = tor_addr_parse_PTR_name(&addr, exitconn->_base.address, + if ((r = tor_addr_parse_PTR_name(&addr, exitconn->base_.address, AF_UNSPEC, 0)) != 0) { if (r == 1) { is_reverse = 1; @@ -737,21 +863,22 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, if (!is_reverse || !is_resolve) { if (!is_reverse) log_info(LD_EXIT, "Bad .in-addr.arpa address \"%s\"; sending error.", - escaped_safe_str(exitconn->_base.address)); + escaped_safe_str(exitconn->base_.address)); else if (!is_resolve) log_info(LD_EXIT, "Attempt to connect to a .in-addr.arpa address \"%s\"; " "sending error.", - escaped_safe_str(exitconn->_base.address)); + escaped_safe_str(exitconn->base_.address)); return -1; } //log_notice(LD_EXIT, "Looks like an address %s", - //exitconn->_base.address); + //exitconn->base_.address); } + exitconn->is_reverse_dns_lookup = is_reverse; /* now check the hash table to see if 'address' is already there. */ - strlcpy(search.address, exitconn->_base.address, sizeof(search.address)); + strlcpy(search.address, exitconn->base_.address, sizeof(search.address)); resolve = HT_FIND(cache_map, &cache_root, &search); if (resolve && resolve->expire > now) { /* already there */ switch (resolve->state) { @@ -763,27 +890,19 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, pending_connection->next = resolve->pending_connections; resolve->pending_connections = pending_connection; *made_connection_pending_out = 1; - log_debug(LD_EXIT,"Connection (fd %d) waiting for pending DNS " - "resolve of %s", exitconn->_base.s, - escaped_safe_str(exitconn->_base.address)); + log_debug(LD_EXIT,"Connection (fd "TOR_SOCKET_T_FORMAT") waiting " + "for pending DNS resolve of %s", exitconn->base_.s, + escaped_safe_str(exitconn->base_.address)); return 0; - case CACHE_STATE_CACHED_VALID: - log_debug(LD_EXIT,"Connection (fd %d) found cached answer for %s", - exitconn->_base.s, + case CACHE_STATE_CACHED: + log_debug(LD_EXIT,"Connection (fd "TOR_SOCKET_T_FORMAT") found " + "cached answer for %s", + exitconn->base_.s, escaped_safe_str(resolve->address)); - exitconn->address_ttl = resolve->ttl; - if (resolve->is_reverse) { - tor_assert(is_resolve); - *hostname_out = tor_strdup(resolve->result.hostname); - } else { - tor_addr_from_ipv4h(&exitconn->_base.addr, resolve->result.a.addr); - } - return 1; - case CACHE_STATE_CACHED_FAILED: - log_debug(LD_EXIT,"Connection (fd %d) found cached error for %s", - exitconn->_base.s, - escaped_safe_str(exitconn->_base.address)); - return -1; + + *resolve_out = resolve; + + return set_exitconn_info_from_resolve(exitconn, resolve, hostname_out); case CACHE_STATE_DONE: log_err(LD_BUG, "Found a 'DONE' dns resolve still in the cache."); tor_fragile_assert(); @@ -796,8 +915,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, resolve->magic = CACHED_RESOLVE_MAGIC; resolve->state = CACHE_STATE_PENDING; resolve->minheap_idx = -1; - resolve->is_reverse = is_reverse; - strlcpy(resolve->address, exitconn->_base.address, sizeof(resolve->address)); + strlcpy(resolve->address, exitconn->base_.address, sizeof(resolve->address)); /* add this connection to the pending list */ pending_connection = tor_malloc_zero(sizeof(pending_connection_t)); @@ -810,10 +928,115 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, set_expiry(resolve, now + RESOLVE_MAX_TIMEOUT); log_debug(LD_EXIT,"Launching %s.", - escaped_safe_str(exitconn->_base.address)); + escaped_safe_str(exitconn->base_.address)); assert_cache_ok(); - return launch_resolve(exitconn); + return launch_resolve(resolve); +} + +/** Given an exit connection <b>exitconn</b>, and a cached_resolve_t + * <b>resolve</b> whose DNS lookups have all succeeded or failed, update the + * appropriate fields (address_ttl and addr) of <b>exitconn</b>. + * + * If this is a reverse lookup, set *<b>hostname_out</b> to a newly allocated + * copy of the name resulting hostname. + * + * Return -2 on a transient error, -1 on a permenent error, and 1 on + * a successful lookup. + */ +static int +set_exitconn_info_from_resolve(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out) +{ + int ipv4_ok, ipv6_ok, answer_with_ipv4, r; + uint32_t begincell_flags; + const int is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE; + tor_assert(exitconn); + tor_assert(resolve); + + if (exitconn->is_reverse_dns_lookup) { + exitconn->address_ttl = resolve->ttl_hostname; + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) { + *hostname_out = tor_strdup(resolve->result_ptr.hostname); + return 1; + } else { + return -1; + } + } + + /* If we're here then the connection wants one or either of ipv4, ipv6, and + * we can give it one or both. */ + if (is_resolve) { + begincell_flags = BEGIN_FLAG_IPV6_OK; + } else { + begincell_flags = exitconn->begincell_flags; + } + + ipv4_ok = (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) && + ! (begincell_flags & BEGIN_FLAG_IPV4_NOT_OK); + ipv6_ok = (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) && + (begincell_flags & BEGIN_FLAG_IPV6_OK) && + get_options()->IPv6Exit; + + /* Now decide which one to actually give. */ + if (ipv4_ok && ipv6_ok && is_resolve) { + answer_with_ipv4 = 1; + } else if (ipv4_ok && ipv6_ok) { + /* If we have both, see if our exit policy has an opinion. */ + const uint16_t port = exitconn->base_.port; + int ipv4_allowed, ipv6_allowed; + tor_addr_t a4, a6; + tor_addr_from_ipv4h(&a4, resolve->result_ipv4.addr_ipv4); + tor_addr_from_in6(&a6, &resolve->result_ipv6.addr_ipv6); + ipv4_allowed = !router_compare_to_my_exit_policy(&a4, port); + ipv6_allowed = !router_compare_to_my_exit_policy(&a6, port); + if (ipv4_allowed && !ipv6_allowed) { + answer_with_ipv4 = 1; + } else if (ipv6_allowed && !ipv4_allowed) { + answer_with_ipv4 = 0; + } else { + /* Our exit policy would permit both. Answer with whichever the user + * prefers */ + answer_with_ipv4 = !(begincell_flags & + BEGIN_FLAG_IPV6_PREFERRED); + } + } else { + /* Otherwise if one is okay, send it back. */ + if (ipv4_ok) { + answer_with_ipv4 = 1; + } else if (ipv6_ok) { + answer_with_ipv4 = 0; + } else { + /* Neither one was okay. Choose based on user preference. */ + answer_with_ipv4 = !(begincell_flags & + BEGIN_FLAG_IPV6_PREFERRED); + } + } + + /* Finally, we write the answer back. */ + r = 1; + if (answer_with_ipv4) { + if (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) { + tor_addr_from_ipv4h(&exitconn->base_.addr, + resolve->result_ipv4.addr_ipv4); + } else { + r = evdns_err_is_transient(resolve->result_ipv4.err_ipv4) ? -2 : -1; + } + + exitconn->address_ttl = resolve->ttl_ipv4; + } else { + if (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) { + tor_addr_from_in6(&exitconn->base_.addr, + &resolve->result_ipv6.addr_ipv6); + } else { + r = evdns_err_is_transient(resolve->result_ipv6.err_ipv6) ? -2 : -1; + } + + exitconn->address_ttl = resolve->ttl_ipv6; + } + + return r; } /** Log an error and abort if conn is waiting for a DNS resolve. @@ -826,7 +1049,7 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn) #if 1 cached_resolve_t *resolve; - strlcpy(search.address, conn->_base.address, sizeof(search.address)); + strlcpy(search.address, conn->base_.address, sizeof(search.address)); resolve = HT_FIND(cache_map, &cache_root, &search); if (!resolve) return; @@ -856,7 +1079,7 @@ assert_all_pending_dns_resolves_ok(void) pend; pend = pend->next) { assert_connection_ok(TO_CONN(pend->conn), 0); - tor_assert(!SOCKET_OK(pend->conn->_base.s)); + tor_assert(!SOCKET_OK(pend->conn->base_.s)); tor_assert(!connection_in_array(TO_CONN(pend->conn))); } } @@ -871,15 +1094,15 @@ connection_dns_remove(edge_connection_t *conn) cached_resolve_t search; cached_resolve_t *resolve; - tor_assert(conn->_base.type == CONN_TYPE_EXIT); - tor_assert(conn->_base.state == EXIT_CONN_STATE_RESOLVING); + tor_assert(conn->base_.type == CONN_TYPE_EXIT); + tor_assert(conn->base_.state == EXIT_CONN_STATE_RESOLVING); - strlcpy(search.address, conn->_base.address, sizeof(search.address)); + strlcpy(search.address, conn->base_.address, sizeof(search.address)); resolve = HT_FIND(cache_map, &cache_root, &search); if (!resolve) { log_notice(LD_BUG, "Address %s is not pending. Dropping.", - escaped_safe_str(conn->_base.address)); + escaped_safe_str(conn->base_.address)); return; } @@ -891,10 +1114,10 @@ connection_dns_remove(edge_connection_t *conn) if (pend->conn == conn) { resolve->pending_connections = pend->next; tor_free(pend); - log_debug(LD_EXIT, "First connection (fd %d) no longer waiting " - "for resolve of %s", - conn->_base.s, - escaped_safe_str(conn->_base.address)); + log_debug(LD_EXIT, "First connection (fd "TOR_SOCKET_T_FORMAT") no " + "longer waiting for resolve of %s", + conn->base_.s, + escaped_safe_str(conn->base_.address)); return; } else { for ( ; pend->next; pend = pend->next) { @@ -903,8 +1126,9 @@ connection_dns_remove(edge_connection_t *conn) pend->next = victim->next; tor_free(victim); log_debug(LD_EXIT, - "Connection (fd %d) no longer waiting for resolve of %s", - conn->_base.s, escaped_safe_str(conn->_base.address)); + "Connection (fd "TOR_SOCKET_T_FORMAT") no longer waiting " + "for resolve of %s", + conn->base_.s, escaped_safe_str(conn->base_.address)); return; /* more are pending */ } } @@ -959,17 +1183,17 @@ dns_cancel_pending_resolve(const char *address) escaped_safe_str(address)); while (resolve->pending_connections) { pend = resolve->pending_connections; - pend->conn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; + pend->conn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; pendconn = pend->conn; assert_connection_ok(TO_CONN(pendconn), 0); - tor_assert(!SOCKET_OK(pendconn->_base.s)); - if (!pendconn->_base.marked_for_close) { + tor_assert(!SOCKET_OK(pendconn->base_.s)); + if (!pendconn->base_.marked_for_close) { connection_edge_end(pendconn, END_STREAM_REASON_RESOLVEFAILED); } circ = circuit_get_by_edge_conn(pendconn); if (circ) circuit_detach_stream(circ, pendconn); - if (!pendconn->_base.marked_for_close) + if (!pendconn->base_.marked_for_close) connection_free(TO_CONN(pendconn)); resolve->pending_connections = pend->next; tor_free(pend); @@ -987,47 +1211,6 @@ dns_cancel_pending_resolve(const char *address) resolve->state = CACHE_STATE_DONE; } -/** Helper: adds an entry to the DNS cache mapping <b>address</b> to the ipv4 - * address <b>addr</b> (if is_reverse is 0) or the hostname <b>hostname</b> (if - * is_reverse is 1). <b>ttl</b> is a cache ttl; <b>outcome</b> is one of - * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. - **/ -static void -add_answer_to_cache(const char *address, uint8_t is_reverse, uint32_t addr, - const char *hostname, char outcome, uint32_t ttl) -{ - cached_resolve_t *resolve; - if (outcome == DNS_RESOLVE_FAILED_TRANSIENT) - return; - - //log_notice(LD_EXIT, "Adding to cache: %s -> %s (%lx, %s), %d", - // address, is_reverse?"(reverse)":"", (unsigned long)addr, - // hostname?hostname:"NULL",(int)outcome); - - resolve = tor_malloc_zero(sizeof(cached_resolve_t)); - resolve->magic = CACHED_RESOLVE_MAGIC; - resolve->state = (outcome == DNS_RESOLVE_SUCCEEDED) ? - CACHE_STATE_CACHED_VALID : CACHE_STATE_CACHED_FAILED; - strlcpy(resolve->address, address, sizeof(resolve->address)); - resolve->is_reverse = is_reverse; - if (is_reverse) { - if (outcome == DNS_RESOLVE_SUCCEEDED) { - tor_assert(hostname); - resolve->result.hostname = tor_strdup(hostname); - } else { - tor_assert(! hostname); - resolve->result.hostname = NULL; - } - } else { - tor_assert(!hostname); - resolve->result.a.addr = addr; - } - resolve->ttl = ttl; - assert_resolve_ok(resolve); - HT_INSERT(cache_map, &cache_root, resolve); - set_expiry(resolve, time(NULL) + dns_get_expiry_ttl(ttl)); -} - /** Return true iff <b>address</b> is one of the addresses we use to verify * that well-known sites aren't being hijacked by our DNS servers. */ static INLINE int @@ -1038,22 +1221,23 @@ is_test_address(const char *address) smartlist_string_isin_case(options->ServerDNSTestAddresses, address); } -/** Called on the OR side when a DNS worker or the eventdns library tells us - * the outcome of a DNS resolve: tell all pending connections about the result - * of the lookup, and cache the value. (<b>address</b> is a NUL-terminated - * string containing the address to look up; <b>addr</b> is an IPv4 address in - * host order; <b>outcome</b> is one of - * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. +/** Called on the OR side when the eventdns library tells us the outcome of a + * single DNS resolve: remember the answer, and tell all pending connections + * about the result of the lookup if the lookup is now done. (<b>address</b> + * is a NUL-terminated string containing the address to look up; + * <b>query_type</b> is one of DNS_{IPv4_A,IPv6_AAAA,PTR}; <b>dns_answer</b> + * is DNS_OK or one of DNS_ERR_*, <b>addr</b> is an IPv4 or IPv6 address if we + * got one; <b>hostname</b> is a hostname fora PTR request if we got one, and + * <b>ttl</b> is the time-to-live of this answer, in seconds.) */ static void -dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, - const char *hostname, char outcome, uint32_t ttl) +dns_found_answer(const char *address, uint8_t query_type, + int dns_answer, + const tor_addr_t *addr, + const char *hostname, uint32_t ttl) { - pending_connection_t *pend; cached_resolve_t search; - cached_resolve_t *resolve, *removed; - edge_connection_t *pendconn; - circuit_t *circ; + cached_resolve_t *resolve; assert_cache_ok(); @@ -1063,9 +1247,8 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, if (!resolve) { int is_test_addr = is_test_address(address); if (!is_test_addr) - log_info(LD_EXIT,"Resolved unasked address %s; caching anyway.", + log_info(LD_EXIT,"Resolved unasked address %s; ignoring.", escaped_safe_str(address)); - add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl); return; } assert_resolve_ok(resolve); @@ -1081,46 +1264,66 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, tor_assert(resolve->pending_connections == NULL); return; } - /* Removed this assertion: in fact, we'll sometimes get a double answer - * to the same question. This can happen when we ask one worker to resolve - * X.Y.Z., then we cancel the request, and then we ask another worker to - * resolve X.Y.Z. */ - /* tor_assert(resolve->state == CACHE_STATE_PENDING); */ + + cached_resolve_add_answer(resolve, query_type, dns_answer, + addr, hostname, ttl); + + if (cached_resolve_have_all_answers(resolve)) { + inform_pending_connections(resolve); + + make_pending_resolve_cached(resolve); + } +} + +/** Given a pending cached_resolve_t that we just finished resolving, + * inform every connection that was waiting for the outcome of that + * resolution. */ +static void +inform_pending_connections(cached_resolve_t *resolve) +{ + pending_connection_t *pend; + edge_connection_t *pendconn; + int r; while (resolve->pending_connections) { + char *hostname = NULL; pend = resolve->pending_connections; pendconn = pend->conn; /* don't pass complex things to the connection_mark_for_close macro */ assert_connection_ok(TO_CONN(pendconn),time(NULL)); - if (pendconn->_base.marked_for_close) { + + if (pendconn->base_.marked_for_close) { /* prevent double-remove. */ - pendconn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; resolve->pending_connections = pend->next; tor_free(pend); continue; } - tor_addr_from_ipv4h(&pendconn->_base.addr, addr); - pendconn->address_ttl = ttl; - if (outcome != DNS_RESOLVE_SUCCEEDED) { + r = set_exitconn_info_from_resolve(pendconn, + resolve, + &hostname); + + if (r < 0) { /* prevent double-remove. */ - pendconn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; - if (pendconn->_base.purpose == EXIT_PURPOSE_CONNECT) { + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { connection_edge_end(pendconn, END_STREAM_REASON_RESOLVEFAILED); /* This detach must happen after we send the end cell. */ circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); } else { - send_resolved_cell(pendconn, outcome == DNS_RESOLVE_FAILED_PERMANENT ? - RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT); + send_resolved_cell(pendconn, r == -1 ? + RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT, + NULL); /* This detach must happen after we send the resolved cell. */ circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); } connection_free(TO_CONN(pendconn)); } else { - if (pendconn->_base.purpose == EXIT_PURPOSE_CONNECT) { - tor_assert(!is_reverse); + circuit_t *circ; + if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { /* prevent double-remove. */ - pend->conn->_base.state = EXIT_CONN_STATE_CONNECTING; + pend->conn->base_.state = EXIT_CONN_STATE_CONNECTING; circ = circuit_get_by_edge_conn(pend->conn); tor_assert(circ); @@ -1136,11 +1339,11 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, } else { /* prevent double-remove. This isn't really an accurate state, * but it does the right thing. */ - pendconn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; - if (is_reverse) + pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; + if (pendconn->is_reverse_dns_lookup) send_resolved_hostname_cell(pendconn, hostname); else - send_resolved_cell(pendconn, RESOLVED_TYPE_IPV4); + send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO, resolve); circ = circuit_get_by_edge_conn(pendconn); tor_assert(circ); circuit_detach_stream(circ, pendconn); @@ -1150,9 +1353,21 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, resolve->pending_connections = pend->next; tor_free(pend); } +} + +/** Remove a pending cached_resolve_t from the hashtable, and add a + * corresponding cached cached_resolve_t. + * + * This function is only necessary because of the perversity of our + * cache timeout code; see inline comment for ideas on eliminating it. + **/ +static void +make_pending_resolve_cached(cached_resolve_t *resolve) +{ + cached_resolve_t *removed; resolve->state = CACHE_STATE_DONE; - removed = HT_REMOVE(cache_map, &cache_root, &search); + removed = HT_REMOVE(cache_map, &cache_root, resolve); if (removed != resolve) { log_err(LD_BUG, "The pending resolve we found wasn't removable from" " the cache. Tried to purge %s (%p); instead got %s (%p).", @@ -1161,8 +1376,42 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, } assert_resolve_ok(resolve); assert_cache_ok(); + /* The resolve will eventually just hit the time-out in the expiry queue and + * expire. See fd0bafb0dedc7e2 for a brief explanation of how this got that + * way. XXXXX we could do better!*/ + + { + cached_resolve_t *new_resolve = tor_memdup(resolve, + sizeof(cached_resolve_t)); + uint32_t ttl = UINT32_MAX; + new_resolve->expire = 0; /* So that set_expiry won't croak. */ + if (resolve->res_status_hostname == RES_STATUS_DONE_OK) + new_resolve->result_ptr.hostname = + tor_strdup(resolve->result_ptr.hostname); + + new_resolve->state = CACHE_STATE_CACHED; + + assert_resolve_ok(new_resolve); + HT_INSERT(cache_map, &cache_root, new_resolve); + + if ((resolve->res_status_ipv4 == RES_STATUS_DONE_OK || + resolve->res_status_ipv4 == RES_STATUS_DONE_ERR) && + resolve->ttl_ipv4 < ttl) + ttl = resolve->ttl_ipv4; + + if ((resolve->res_status_ipv6 == RES_STATUS_DONE_OK || + resolve->res_status_ipv6 == RES_STATUS_DONE_ERR) && + resolve->ttl_ipv6 < ttl) + ttl = resolve->ttl_ipv6; + + if ((resolve->res_status_hostname == RES_STATUS_DONE_OK || + resolve->res_status_hostname == RES_STATUS_DONE_ERR) && + resolve->ttl_hostname < ttl) + ttl = resolve->ttl_hostname; + + set_expiry(new_resolve, time(NULL) + dns_get_expiry_ttl(ttl)); + } - add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl); assert_cache_ok(); } @@ -1210,24 +1459,18 @@ configure_nameservers(int force) } #ifdef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS - if (options->OutboundBindAddress) { - tor_addr_t addr; - if (tor_addr_parse(&addr, options->OutboundBindAddress) < 0) { - log_warn(LD_CONFIG,"Outbound bind address '%s' didn't parse. Ignoring.", - options->OutboundBindAddress); + if (! tor_addr_is_null(&options->OutboundBindAddressIPv4_)) { + int socklen; + struct sockaddr_storage ss; + socklen = tor_addr_to_sockaddr(&options->OutboundBindAddressIPv4_, 0, + (struct sockaddr *)&ss, sizeof(ss)); + if (socklen <= 0) { + log_warn(LD_BUG, "Couldn't convert outbound bind address to sockaddr." + " Ignoring."); } else { - int socklen; - struct sockaddr_storage ss; - socklen = tor_addr_to_sockaddr(&addr, 0, - (struct sockaddr *)&ss, sizeof(ss)); - if (socklen <= 0) { - log_warn(LD_BUG, "Couldn't convert outbound bind address to sockaddr." - " Ignoring."); - } else { - evdns_base_set_default_outgoing_bind_address(the_evdns_base, - (struct sockaddr *)&ss, - socklen); - } + evdns_base_set_default_outgoing_bind_address(the_evdns_base, + (struct sockaddr *)&ss, + socklen); } } #endif @@ -1331,23 +1574,40 @@ static void evdns_callback(int result, char type, int count, int ttl, void *addresses, void *arg) { - char *string_address = arg; - uint8_t is_reverse = 0; - int status = DNS_RESOLVE_FAILED_PERMANENT; - uint32_t addr = 0; + char *arg_ = arg; + uint8_t orig_query_type = arg_[0]; + char *string_address = arg_ + 1; + tor_addr_t addr; const char *hostname = NULL; int was_wildcarded = 0; + tor_addr_make_unspec(&addr); + + /* Keep track of whether IPv6 is working */ + if (type == DNS_IPv6_AAAA) { + if (result == DNS_ERR_TIMEOUT) { + ++n_ipv6_timeouts; + } + + if (n_ipv6_timeouts > 10 && + n_ipv6_timeouts > n_ipv6_requests_made / 2) { + if (! dns_is_broken_for_ipv6) { + log_notice(LD_EXIT, "More than half of our IPv6 requests seem to " + "have timed out. I'm going to assume I can't get AAAA " + "responses."); + dns_is_broken_for_ipv6 = 1; + } + } + } + if (result == DNS_ERR_NONE) { if (type == DNS_IPv4_A && count) { char answer_buf[INET_NTOA_BUF_LEN+1]; - struct in_addr in; char *escaped_address; uint32_t *addrs = addresses; - in.s_addr = addrs[0]; - addr = ntohl(addrs[0]); - status = DNS_RESOLVE_SUCCEEDED; - tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); + tor_addr_from_ipv4n(&addr, addrs[0]); + + tor_addr_to_str(answer_buf, &addr, sizeof(answer_buf), 0); escaped_address = esc_for_log(string_address); if (answer_is_wildcarded(answer_buf)) { @@ -1356,8 +1616,30 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, safe_str(escaped_address), escaped_safe_str(answer_buf)); was_wildcarded = 1; - addr = 0; - status = DNS_RESOLVE_FAILED_PERMANENT; + tor_addr_make_unspec(&addr); + result = DNS_ERR_NOTEXIST; + } else { + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + } + tor_free(escaped_address); + } else if (type == DNS_IPv6_AAAA && count) { + char answer_buf[TOR_ADDR_BUF_LEN]; + char *escaped_address; + struct in6_addr *addrs = addresses; + tor_addr_from_in6(&addr, &addrs[0]); + tor_inet_ntop(AF_INET6, &addrs[0], answer_buf, sizeof(answer_buf)); + escaped_address = esc_for_log(string_address); + + if (answer_is_wildcarded(answer_buf)) { + log_debug(LD_EXIT, "eventdns said that %s resolves to ISP-hijacked " + "address %s; treating as a failure.", + safe_str(escaped_address), + escaped_safe_str(answer_buf)); + was_wildcarded = 1; + tor_addr_make_unspec(&addr); + result = DNS_ERR_NOTEXIST; } else { log_debug(LD_EXIT, "eventdns said that %s resolves to %s", safe_str(escaped_address), @@ -1366,9 +1648,7 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, tor_free(escaped_address); } else if (type == DNS_PTR && count) { char *escaped_address; - is_reverse = 1; hostname = ((char**)addresses)[0]; - status = DNS_RESOLVE_SUCCEEDED; escaped_address = esc_for_log(string_address); log_debug(LD_EXIT, "eventdns said that %s resolves to %s", safe_str(escaped_address), @@ -1381,9 +1661,6 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, log_warn(LD_BUG, "eventdns returned no addresses or error for %s!", escaped_safe_str(string_address)); } - } else { - if (evdns_err_is_transient(result)) - status = DNS_RESOLVE_FAILED_TRANSIENT; } if (was_wildcarded) { if (is_test_address(string_address)) { @@ -1392,23 +1669,78 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, add_wildcarded_test_address(string_address); } } + + if (orig_query_type && type && orig_query_type != type) { + log_warn(LD_BUG, "Weird; orig_query_type == %d but type == %d", + (int)orig_query_type, (int)type); + } if (result != DNS_ERR_SHUTDOWN) - dns_found_answer(string_address, is_reverse, addr, hostname, status, ttl); - tor_free(string_address); + dns_found_answer(string_address, orig_query_type, + result, &addr, hostname, ttl); + + tor_free(arg_); +} + +/** Start a single DNS resolve for <b>address</b> (if <b>query_type</b> is + * DNS_IPv4_A or DNS_IPv6_AAAA) <b>ptr_address</b> (if <b>query_type</b> is + * DNS_PTR). Return 0 if we launched the request, -1 otherwise. */ +static int +launch_one_resolve(const char *address, uint8_t query_type, + const tor_addr_t *ptr_address) +{ + const int options = get_options()->ServerDNSSearchDomains ? 0 + : DNS_QUERY_NO_SEARCH; + const size_t addr_len = strlen(address); + struct evdns_request *req = 0; + char *addr = tor_malloc(addr_len + 2); + addr[0] = (char) query_type; + memcpy(addr+1, address, addr_len + 1); + + switch (query_type) { + case DNS_IPv4_A: + req = evdns_base_resolve_ipv4(the_evdns_base, + address, options, evdns_callback, addr); + break; + case DNS_IPv6_AAAA: + req = evdns_base_resolve_ipv6(the_evdns_base, + address, options, evdns_callback, addr); + ++n_ipv6_requests_made; + break; + case DNS_PTR: + if (tor_addr_family(ptr_address) == AF_INET) + req = evdns_base_resolve_reverse(the_evdns_base, + tor_addr_to_in(ptr_address), + DNS_QUERY_NO_SEARCH, + evdns_callback, addr); + else if (tor_addr_family(ptr_address) == AF_INET6) + req = evdns_base_resolve_reverse_ipv6(the_evdns_base, + tor_addr_to_in6(ptr_address), + DNS_QUERY_NO_SEARCH, + evdns_callback, addr); + else + log_warn(LD_BUG, "Called with PTR query and unexpected address family"); + break; + default: + log_warn(LD_BUG, "Called with unexpectd query type %d", (int)query_type); + break; + } + + if (req) { + return 0; + } else { + tor_free(addr); + return -1; + } } /** For eventdns: start resolving as necessary to find the target for * <b>exitconn</b>. Returns -1 on error, -2 on transient error, * 0 on "resolve launched." */ static int -launch_resolve(edge_connection_t *exitconn) +launch_resolve(cached_resolve_t *resolve) { - char *addr; - struct evdns_request *req = NULL; tor_addr_t a; int r; - int options = get_options()->ServerDNSSearchDomains ? 0 - : DNS_QUERY_NO_SEARCH; if (get_options()->DisableNetwork) return -1; @@ -1422,40 +1754,45 @@ launch_resolve(edge_connection_t *exitconn) } } - addr = tor_strdup(exitconn->_base.address); - r = tor_addr_parse_PTR_name( - &a, exitconn->_base.address, AF_UNSPEC, 0); + &a, resolve->address, AF_UNSPEC, 0); tor_assert(the_evdns_base); if (r == 0) { log_info(LD_EXIT, "Launching eventdns request for %s", - escaped_safe_str(exitconn->_base.address)); - req = evdns_base_resolve_ipv4(the_evdns_base, - exitconn->_base.address, options, - evdns_callback, addr); + escaped_safe_str(resolve->address)); + resolve->res_status_ipv4 = RES_STATUS_INFLIGHT; + if (get_options()->IPv6Exit) + resolve->res_status_ipv6 = RES_STATUS_INFLIGHT; + + if (launch_one_resolve(resolve->address, DNS_IPv4_A, NULL) < 0) { + resolve->res_status_ipv4 = 0; + r = -1; + } + + if (r==0 && get_options()->IPv6Exit) { + /* We ask for an IPv6 address for *everything*. */ + if (launch_one_resolve(resolve->address, DNS_IPv6_AAAA, NULL) < 0) { + resolve->res_status_ipv6 = 0; + r = -1; + } + } } else if (r == 1) { + r = 0; log_info(LD_EXIT, "Launching eventdns reverse request for %s", - escaped_safe_str(exitconn->_base.address)); - if (tor_addr_family(&a) == AF_INET) - req = evdns_base_resolve_reverse(the_evdns_base, - tor_addr_to_in(&a), DNS_QUERY_NO_SEARCH, - evdns_callback, addr); - else - req = evdns_base_resolve_reverse_ipv6(the_evdns_base, - tor_addr_to_in6(&a), DNS_QUERY_NO_SEARCH, - evdns_callback, addr); + escaped_safe_str(resolve->address)); + resolve->res_status_hostname = RES_STATUS_INFLIGHT; + if (launch_one_resolve(resolve->address, DNS_PTR, &a) < 0) { + resolve->res_status_hostname = 0; + r = -1; + } } else if (r == -1) { log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here."); } - r = 0; - if (!req) { + if (r < 0) { log_fn(LOG_PROTOCOL_WARN, LD_EXIT, "eventdns rejected address %s.", - escaped_safe_str(addr)); - r = -1; - tor_free(addr); /* There is no evdns request in progress; stop - * addr from getting leaked. */ + escaped_safe_str(resolve->address)); } return r; } @@ -1488,8 +1825,8 @@ static int dns_wildcarded_test_address_notice_given = 0; /** True iff all addresses seem to be getting wildcarded. */ static int dns_is_completely_invalid = 0; -/** Called when we see <b>id</b> (a dotted quad) in response to a request for - * a hopefully bogus address. */ +/** Called when we see <b>id</b> (a dotted quad or IPv6 address) in response + * to a request for a hopefully bogus address. */ static void wildcard_increment_answer(const char *id) { @@ -1561,17 +1898,27 @@ evdns_wildcard_check_callback(int result, char type, int count, int ttl, { (void)ttl; ++n_wildcard_requests; - if (result == DNS_ERR_NONE && type == DNS_IPv4_A && count) { - uint32_t *addrs = addresses; - int i; + if (result == DNS_ERR_NONE && count) { char *string_address = arg; - for (i = 0; i < count; ++i) { - char answer_buf[INET_NTOA_BUF_LEN+1]; - struct in_addr in; - in.s_addr = addrs[i]; - tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); - wildcard_increment_answer(answer_buf); + int i; + if (type == DNS_IPv4_A) { + const uint32_t *addrs = addresses; + for (i = 0; i < count; ++i) { + char answer_buf[INET_NTOA_BUF_LEN+1]; + struct in_addr in; + in.s_addr = addrs[i]; + tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); + wildcard_increment_answer(answer_buf); + } + } else if (type == DNS_IPv6_AAAA) { + const struct in6_addr *addrs = addresses; + for (i = 0; i < count; ++i) { + char answer_buf[TOR_ADDR_BUF_LEN+1]; + tor_inet_ntop(AF_INET6, &addrs[i], answer_buf, sizeof(answer_buf)); + wildcard_increment_answer(answer_buf); + } } + log(dns_wildcard_one_notice_given ? LOG_INFO : LOG_NOTICE, LD_EXIT, "Your DNS provider gave an answer for \"%s\", which " "is not supposed to exist. Apparently they are hijacking " @@ -1588,7 +1935,8 @@ evdns_wildcard_check_callback(int result, char type, int count, int ttl, * <b>min_len</b> and <b>max_len</b> random (plausible) characters followed by * <b>suffix</b> */ static void -launch_wildcard_check(int min_len, int max_len, const char *suffix) +launch_wildcard_check(int min_len, int max_len, int is_ipv6, + const char *suffix) { char *addr; struct evdns_request *req; @@ -1598,7 +1946,15 @@ launch_wildcard_check(int min_len, int max_len, const char *suffix) "domains with request for bogus hostname \"%s\"", addr); tor_assert(the_evdns_base); - req = evdns_base_resolve_ipv4( + if (is_ipv6) + req = evdns_base_resolve_ipv6( + the_evdns_base, + /* This "addr" tells us which address to resolve */ + addr, + DNS_QUERY_NO_SEARCH, evdns_wildcard_check_callback, + /* This "addr" is an argument to the callback*/ addr); + else + req = evdns_base_resolve_ipv4( the_evdns_base, /* This "addr" tells us which address to resolve */ addr, @@ -1613,10 +1969,9 @@ launch_wildcard_check(int min_len, int max_len, const char *suffix) /** Launch attempts to resolve a bunch of known-good addresses (configured in * ServerDNSTestAddresses). [Callback for a libevent timer] */ static void -launch_test_addresses(int fd, short event, void *args) +launch_test_addresses(evutil_socket_t fd, short event, void *args) { const or_options_t *options = get_options(); - struct evdns_request *req; (void)fd; (void)event; (void)args; @@ -1629,21 +1984,22 @@ launch_test_addresses(int fd, short event, void *args) /* This situation is worse than the failure-hijacking situation. When this * happens, we're no good for DNS requests at all, and we shouldn't really * be an exit server.*/ - if (!options->ServerDNSTestAddresses) - return; - tor_assert(the_evdns_base); - SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses, - const char *, address) { - char *a = tor_strdup(address); - req = evdns_base_resolve_ipv4(the_evdns_base, - address, DNS_QUERY_NO_SEARCH, evdns_callback, a); + if (options->ServerDNSTestAddresses) { - if (!req) { - log_info(LD_EXIT, "eventdns rejected test address %s", - escaped_safe_str(address)); - tor_free(a); - } - } SMARTLIST_FOREACH_END(address); + tor_assert(the_evdns_base); + SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses, + const char *, address) { + if (launch_one_resolve(address, DNS_IPv4_A, NULL) < 0) { + log_info(LD_EXIT, "eventdns rejected test address %s", + escaped_safe_str(address)); + } + + if (launch_one_resolve(address, DNS_IPv6_AAAA, NULL) < 0) { + log_info(LD_EXIT, "eventdns rejected test address %s", + escaped_safe_str(address)); + } + } SMARTLIST_FOREACH_END(address); + } } #define N_WILDCARD_CHECKS 2 @@ -1655,27 +2011,29 @@ launch_test_addresses(int fd, short event, void *args) static void dns_launch_wildcard_checks(void) { - int i; + int i, ipv6; log_info(LD_EXIT, "Launching checks to see whether our nameservers like " "to hijack DNS failures."); - for (i = 0; i < N_WILDCARD_CHECKS; ++i) { - /* RFC2606 reserves these. Sadly, some DNS hijackers, in a silly attempt - * to 'comply' with rfc2606, refrain from giving A records for these. - * This is the standards-compliance equivalent of making sure that your - * crackhouse's elevator inspection certificate is up to date. - */ - launch_wildcard_check(2, 16, ".invalid"); - launch_wildcard_check(2, 16, ".test"); - - /* These will break specs if there are ever any number of - * 8+-character top-level domains. */ - launch_wildcard_check(8, 16, ""); - - /* Try some random .com/org/net domains. This will work fine so long as - * not too many resolve to the same place. */ - launch_wildcard_check(8, 16, ".com"); - launch_wildcard_check(8, 16, ".org"); - launch_wildcard_check(8, 16, ".net"); + for (ipv6 = 0; ipv6 <= 1; ++ipv6) { + for (i = 0; i < N_WILDCARD_CHECKS; ++i) { + /* RFC2606 reserves these. Sadly, some DNS hijackers, in a silly + * attempt to 'comply' with rfc2606, refrain from giving A records for + * these. This is the standards-compliance equivalent of making sure + * that your crackhouse's elevator inspection certificate is up to date. + */ + launch_wildcard_check(2, 16, ipv6, ".invalid"); + launch_wildcard_check(2, 16, ipv6, ".test"); + + /* These will break specs if there are ever any number of + * 8+-character top-level domains. */ + launch_wildcard_check(8, 16, ipv6, ""); + + /* Try some random .com/org/net domains. This will work fine so long as + * not too many resolve to the same place. */ + launch_wildcard_check(8, 16, ipv6, ".com"); + launch_wildcard_check(8, 16, ipv6, ".org"); + launch_wildcard_check(8, 16, ipv6, ".net"); + } } } @@ -1709,15 +2067,24 @@ dns_seems_to_be_broken(void) return dns_is_completely_invalid; } +/** Return true iff we think that IPv6 hostname lookup is broken */ +int +dns_seems_to_be_broken_for_ipv6(void) +{ + return dns_is_broken_for_ipv6; +} + /** Forget what we've previously learned about our DNS servers' correctness. */ void dns_reset_correctness_checks(void) { - strmap_free(dns_wildcard_response_count, _tor_free); + strmap_free(dns_wildcard_response_count, tor_free_); dns_wildcard_response_count = NULL; n_wildcard_requests = 0; + n_ipv6_requests_made = n_ipv6_timeouts = 0; + if (dns_wildcard_list) { SMARTLIST_FOREACH(dns_wildcard_list, char *, cp, tor_free(cp)); smartlist_clear(dns_wildcard_list); @@ -1728,7 +2095,8 @@ dns_reset_correctness_checks(void) smartlist_clear(dns_wildcarded_test_address_list); } dns_wildcard_one_notice_given = dns_wildcard_notice_given = - dns_wildcarded_test_address_notice_given = dns_is_completely_invalid = 0; + dns_wildcarded_test_address_notice_given = dns_is_completely_invalid = + dns_is_broken_for_ipv6 = 0; } /** Return true iff we have noticed that the dotted-quad <b>ip</b> has been @@ -1752,11 +2120,14 @@ assert_resolve_ok(cached_resolve_t *resolve) } if (resolve->state == CACHE_STATE_PENDING || resolve->state == CACHE_STATE_DONE) { +#if 0 tor_assert(!resolve->ttl); if (resolve->is_reverse) - tor_assert(!resolve->result.hostname); + tor_assert(!resolve->hostname); else - tor_assert(!resolve->result.a.addr); + tor_assert(!resolve->result_ipv4.addr_ipv4); +#endif + /*XXXXX ADD MORE */ } } @@ -1787,7 +2158,7 @@ dump_dns_mem_usage(int severity) #ifdef DEBUG_DNS_CACHE /** Exit with an assertion if the DNS cache is corrupt. */ static void -_assert_cache_ok(void) +assert_cache_ok_(void) { cached_resolve_t **resolve; int bad_rep = _cache_map_HT_REP_IS_BAD(&cache_root); @@ -1804,7 +2175,7 @@ _assert_cache_ok(void) return; smartlist_pqueue_assert_ok(cached_resolve_pqueue, - _compare_cached_resolves_by_expiry, + compare_cached_resolves_by_expiry_, STRUCT_OFFSET(cached_resolve_t, minheap_idx)); SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, diff --git a/src/or/dns.h b/src/or/dns.h index 8c8b476ac..d2f6614e6 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -9,8 +9,8 @@ * \brief Header file for dns.c. **/ -#ifndef _TOR_DNS_H -#define _TOR_DNS_H +#ifndef TOR_DNS_H +#define TOR_DNS_H int dns_init(void); int has_dns_init_failed(void); @@ -24,6 +24,7 @@ void dns_cancel_pending_resolve(const char *question); int dns_resolve(edge_connection_t *exitconn); void dns_launch_correctness_checks(void); int dns_seems_to_be_broken(void); +int dns_seems_to_be_broken_for_ipv6(void); void dns_reset_correctness_checks(void); void dump_dns_mem_usage(int severity); diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 69adcef9e..a21189907 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -89,6 +89,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) continue; switch (req->questions[i]->type) { case EVDNS_TYPE_A: + case EVDNS_TYPE_AAAA: case EVDNS_TYPE_PTR: q = req->questions[i]; default: @@ -101,7 +102,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) evdns_server_request_respond(req, DNS_ERR_NOTIMPL); return; } - if (q->type != EVDNS_TYPE_A) { + if (q->type != EVDNS_TYPE_A && q->type != EVDNS_TYPE_AAAA) { tor_assert(q->type == EVDNS_TYPE_PTR); } @@ -125,7 +126,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) TO_CONN(conn)->port = port; TO_CONN(conn)->address = tor_dup_addr(&tor_addr); - if (q->type == EVDNS_TYPE_A) + if (q->type == EVDNS_TYPE_A || q->type == EVDNS_TYPE_AAAA) entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE; else entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; @@ -133,7 +134,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) strlcpy(entry_conn->socks_request->address, q->name, sizeof(entry_conn->socks_request->address)); - entry_conn->socks_request->listener_type = listener->_base.type; + entry_conn->socks_request->listener_type = listener->base_.type; entry_conn->dns_server_request = req; entry_conn->isolation_flags = listener->isolation_flags; entry_conn->session_group = listener->session_group; @@ -178,7 +179,7 @@ dnsserv_launch_request(const char *name, int reverse) /* Make a new dummy AP connection, and attach the request to it. */ entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); conn = ENTRY_TO_EDGE_CONN(entry_conn); - conn->_base.state = AP_CONN_STATE_RESOLVE_WAIT; + conn->base_.state = AP_CONN_STATE_RESOLVE_WAIT; if (reverse) entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; @@ -289,8 +290,9 @@ dnsserv_resolved(entry_connection_t *conn, * or more of the questions in the request); then, call * evdns_server_request_respond. */ if (answer_type == RESOLVED_TYPE_IPV6) { - log_info(LD_APP, "Got an IPv6 answer; that's not implemented."); - err = DNS_ERR_NOTIMPL; + evdns_server_request_add_aaaa_reply(req, + name, + 1, answer, ttl); } else if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4 && conn->socks_request->command == SOCKS_COMMAND_RESOLVE) { evdns_server_request_add_a_reply(req, diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index 3aaa038d2..39bd1d33b 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -9,8 +9,8 @@ * \brief Header file for dnsserv.c. **/ -#ifndef _TOR_DNSSERV_H -#define _TOR_DNSSERV_H +#ifndef TOR_DNSSERV_H +#define TOR_DNSSERV_H void dnsserv_configure_listener(connection_t *conn); void dnsserv_close_listener(connection_t *conn); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c new file mode 100644 index 000000000..e583b881e --- /dev/null +++ b/src/or/entrynodes.c @@ -0,0 +1,2057 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file entrynodes.c + * \brief Code to manage our fixed first nodes for various functions. + * + * Entry nodes can be guards (for general use) or bridges (for censorship + * circumvention). + **/ + +#include "or.h" +#include "circuitbuild.h" +#include "circuitstats.h" +#include "config.h" +#include "confparse.h" +#include "connection.h" +#include "connection_or.h" +#include "control.h" +#include "directory.h" +#include "entrynodes.h" +#include "main.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerlist.h" +#include "routerparse.h" +#include "routerset.h" +#include "transports.h" +#include "statefile.h" + +/** Information about a configured bridge. Currently this just matches the + * ones in the torrc file, but one day we may be able to learn about new + * bridges on our own, and remember them in the state file. */ +typedef struct { + /** Address of the bridge. */ + tor_addr_t addr; + /** TLS port for the bridge. */ + uint16_t port; + /** Boolean: We are re-parsing our bridge list, and we are going to remove + * this one if we don't find it in the list of configured bridges. */ + unsigned marked_for_removal : 1; + /** Expected identity digest, or all zero bytes if we don't know what the + * digest should be. */ + char identity[DIGEST_LEN]; + + /** Name of pluggable transport protocol taken from its config line. */ + char *transport_name; + + /** When should we next try to fetch a descriptor for this bridge? */ + download_status_t fetch_status; +} bridge_info_t; + +/** A list of our chosen entry guards. */ +static smartlist_t *entry_guards = NULL; +/** A value of 1 means that the entry_guards list has changed + * and those changes need to be flushed to disk. */ +static int entry_guards_dirty = 0; + +static void bridge_free(bridge_info_t *bridge); +static const node_t *choose_random_entry_impl(cpath_build_state_t *state, + int for_directory, + dirinfo_type_t dirtype); + +/** Return the list of entry guards, creating it if necessary. */ +const smartlist_t * +get_entry_guards(void) +{ + if (! entry_guards) + entry_guards = smartlist_new(); + return entry_guards; +} + +/** Check whether the entry guard <b>e</b> is usable, given the directory + * authorities' opinion about the router (stored in <b>ri</b>) and the user's + * configuration (in <b>options</b>). Set <b>e</b>->bad_since + * accordingly. Return true iff the entry guard's status changes. + * + * If it's not usable, set *<b>reason</b> to a static string explaining why. + */ +static int +entry_guard_set_status(entry_guard_t *e, const node_t *node, + time_t now, const or_options_t *options, + const char **reason) +{ + char buf[HEX_DIGEST_LEN+1]; + int changed = 0; + + *reason = NULL; + + /* Do we want to mark this guard as bad? */ + if (!node) + *reason = "unlisted"; + else if (!node->is_running) + *reason = "down"; + else if (options->UseBridges && (!node->ri || + node->ri->purpose != ROUTER_PURPOSE_BRIDGE)) + *reason = "not a bridge"; + else if (options->UseBridges && !node_is_a_configured_bridge(node)) + *reason = "not a configured bridge"; + else if (!options->UseBridges && !node->is_possible_guard && + !routerset_contains_node(options->EntryNodes,node)) + *reason = "not recommended as a guard"; + else if (routerset_contains_node(options->ExcludeNodes, node)) + *reason = "excluded"; + else if (e->path_bias_disabled) + *reason = "path-biased"; + + if (*reason && ! e->bad_since) { + /* Router is newly bad. */ + base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN); + log_info(LD_CIRC, "Entry guard %s (%s) is %s: marking as unusable.", + e->nickname, buf, *reason); + + e->bad_since = now; + control_event_guard(e->nickname, e->identity, "BAD"); + changed = 1; + } else if (!*reason && e->bad_since) { + /* There's nothing wrong with the router any more. */ + base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN); + log_info(LD_CIRC, "Entry guard %s (%s) is no longer unusable: " + "marking as ok.", e->nickname, buf); + + e->bad_since = 0; + control_event_guard(e->nickname, e->identity, "GOOD"); + changed = 1; + } + + if (node) { + int is_dir = node_is_dir(node) && node->rs && + node->rs->version_supports_microdesc_cache; + if (e->is_dir_cache != is_dir) { + e->is_dir_cache = is_dir; + changed = 1; + } + } + + return changed; +} + +/** Return true iff enough time has passed since we last tried to connect + * to the unreachable guard <b>e</b> that we're willing to try again. */ +static int +entry_is_time_to_retry(entry_guard_t *e, time_t now) +{ + long diff; + if (e->last_attempted < e->unreachable_since) + return 1; + diff = now - e->unreachable_since; + if (diff < 6*60*60) + return now > (e->last_attempted + 60*60); + else if (diff < 3*24*60*60) + return now > (e->last_attempted + 4*60*60); + else if (diff < 7*24*60*60) + return now > (e->last_attempted + 18*60*60); + else + return now > (e->last_attempted + 36*60*60); +} + +/** Return the node corresponding to <b>e</b>, if <b>e</b> is + * working well enough that we are willing to use it as an entry + * right now. (Else return NULL.) In particular, it must be + * - Listed as either up or never yet contacted; + * - Present in the routerlist; + * - Listed as 'stable' or 'fast' by the current dirserver consensus, + * if demanded by <b>need_uptime</b> or <b>need_capacity</b> + * (unless it's a configured EntryNode); + * - Allowed by our current ReachableORAddresses config option; and + * - Currently thought to be reachable by us (unless <b>assume_reachable</b> + * is true). + * + * If the answer is no, set *<b>msg</b> to an explanation of why. + * + * If need_descriptor is true, only return the node if we currently have + * a descriptor (routerinfo or microdesc) for it. + */ +static INLINE const node_t * +entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, + int assume_reachable, int need_descriptor, const char **msg) +{ + const node_t *node; + const or_options_t *options = get_options(); + tor_assert(msg); + + if (e->path_bias_disabled) { + *msg = "path-biased"; + return NULL; + } + if (e->bad_since) { + *msg = "bad"; + return NULL; + } + /* no good if it's unreachable, unless assume_unreachable or can_retry. */ + if (!assume_reachable && !e->can_retry && + e->unreachable_since && !entry_is_time_to_retry(e, time(NULL))) { + *msg = "unreachable"; + return NULL; + } + node = node_get_by_id(e->identity); + if (!node) { + *msg = "no node info"; + return NULL; + } + if (need_descriptor && !node_has_descriptor(node)) { + *msg = "no descriptor"; + return NULL; + } + if (get_options()->UseBridges) { + if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) { + *msg = "not a bridge"; + return NULL; + } + if (!node_is_a_configured_bridge(node)) { + *msg = "not a configured bridge"; + return NULL; + } + } else { /* !get_options()->UseBridges */ + if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) { + *msg = "not general-purpose"; + return NULL; + } + } + if (routerset_contains_node(options->EntryNodes, node)) { + /* they asked for it, they get it */ + need_uptime = need_capacity = 0; + } + if (node_is_unreliable(node, need_uptime, need_capacity, 0)) { + *msg = "not fast/stable"; + return NULL; + } + if (!fascist_firewall_allows_node(node)) { + *msg = "unreachable by config"; + return NULL; + } + return node; +} + +/** Return the number of entry guards that we think are usable. */ +int +num_live_entry_guards(int for_directory) +{ + int n = 0; + const char *msg; + if (! entry_guards) + return 0; + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + if (for_directory && !entry->is_dir_cache) + continue; + if (entry_is_live(entry, 0, 1, 0, !for_directory, &msg)) + ++n; + } SMARTLIST_FOREACH_END(entry); + return n; +} + +/** If <b>digest</b> matches the identity of any node in the + * entry_guards list, return that node. Else return NULL. */ +entry_guard_t * +entry_guard_get_by_id_digest(const char *digest) +{ + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, + if (tor_memeq(digest, entry->identity, DIGEST_LEN)) + return entry; + ); + return NULL; +} + +/** Dump a description of our list of entry guards to the log at level + * <b>severity</b>. */ +static void +log_entry_guards(int severity) +{ + smartlist_t *elements = smartlist_new(); + char *s; + + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) + { + const char *msg = NULL; + if (entry_is_live(e, 0, 1, 0, 0, &msg)) + smartlist_add_asprintf(elements, "%s [%s] (up %s)", + e->nickname, + hex_str(e->identity, DIGEST_LEN), + e->made_contact ? "made-contact" : "never-contacted"); + else + smartlist_add_asprintf(elements, "%s [%s] (%s, %s)", + e->nickname, + hex_str(e->identity, DIGEST_LEN), + msg, + e->made_contact ? "made-contact" : "never-contacted"); + } + SMARTLIST_FOREACH_END(e); + + s = smartlist_join_strings(elements, ",", 0, NULL); + SMARTLIST_FOREACH(elements, char*, cp, tor_free(cp)); + smartlist_free(elements); + log_fn(severity,LD_CIRC,"%s",s); + tor_free(s); +} + +/** Called when one or more guards that we would previously have used for some + * purpose are no longer in use because a higher-priority guard has become + * usable again. */ +static void +control_event_guard_deferred(void) +{ + /* XXXX We don't actually have a good way to figure out _how many_ entries + * are live for some purpose. We need an entry_is_even_slightly_live() + * function for this to work right. NumEntryGuards isn't reliable: if we + * need guards with weird properties, we can have more than that number + * live. + **/ +#if 0 + int n = 0; + const char *msg; + const or_options_t *options = get_options(); + if (!entry_guards) + return; + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, + { + if (entry_is_live(entry, 0, 1, 0, &msg)) { + if (n++ == options->NumEntryGuards) { + control_event_guard(entry->nickname, entry->identity, "DEFERRED"); + return; + } + } + }); +#endif +} + +/** Add a new (preferably stable and fast) router to our + * entry_guards list. Return a pointer to the router if we succeed, + * or NULL if we can't find any more suitable entries. + * + * If <b>chosen</b> is defined, use that one, and if it's not + * already in our entry_guards list, put it at the *beginning*. + * Else, put the one we pick at the end of the list. */ +static const node_t * +add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, + int for_directory) +{ + const node_t *node; + entry_guard_t *entry; + + if (chosen) { + node = chosen; + entry = entry_guard_get_by_id_digest(node->identity); + if (entry) { + if (reset_status) { + entry->bad_since = 0; + entry->can_retry = 1; + } + entry->is_dir_cache = node->rs && + node->rs->version_supports_microdesc_cache; + return NULL; + } + } else if (!for_directory) { + node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); + if (!node) + return NULL; + } else { + const routerstatus_t *rs; + rs = router_pick_directory_server(MICRODESC_DIRINFO|V3_DIRINFO, + PDS_PREFER_TUNNELED_DIR_CONNS_); + if (!rs) + return NULL; + node = node_get_by_id(rs->identity_digest); + if (!node) + return NULL; + } + entry = tor_malloc_zero(sizeof(entry_guard_t)); + log_info(LD_CIRC, "Chose %s as new entry guard.", + node_describe(node)); + strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); + memcpy(entry->identity, node->identity, DIGEST_LEN); + entry->is_dir_cache = node_is_dir(node) && + node->rs && node->rs->version_supports_microdesc_cache; + + /* Choose expiry time smudged over the past month. The goal here + * is to a) spread out when Tor clients rotate their guards, so they + * don't all select them on the same day, and b) avoid leaving a + * precise timestamp in the state file about when we first picked + * this guard. For details, see the Jan 2010 or-dev thread. */ + entry->chosen_on_date = time(NULL) - crypto_rand_int(3600*24*30); + entry->chosen_by_version = tor_strdup(VERSION); + if (prepend) + smartlist_insert(entry_guards, 0, entry); + else + smartlist_add(entry_guards, entry); + control_event_guard(entry->nickname, entry->identity, "NEW"); + control_event_guard_deferred(); + log_entry_guards(LOG_INFO); + return node; +} + +/** If the use of entry guards is configured, choose more entry guards + * until we have enough in the list. */ +static void +pick_entry_guards(const or_options_t *options, int for_directory) +{ + int changed = 0; + const int num_needed = for_directory ? options->NumDirectoryGuards : + options->NumEntryGuards; + + tor_assert(entry_guards); + + while (num_live_entry_guards(for_directory) < num_needed) { + if (!add_an_entry_guard(NULL, 0, 0, for_directory)) + break; + changed = 1; + } + if (changed) + entry_guards_changed(); +} + +/** How long (in seconds) do we allow an entry guard to be nonfunctional, + * unlisted, excluded, or otherwise nonusable before we give up on it? */ +#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60) + +/** Release all storage held by <b>e</b>. */ +static void +entry_guard_free(entry_guard_t *e) +{ + if (!e) + return; + tor_free(e->chosen_by_version); + tor_free(e); +} + +/** Remove any entry guard which was selected by an unknown version of Tor, + * or which was selected by a version of Tor that's known to select + * entry guards badly, or which was selected more 2 months ago. */ +/* XXXX The "obsolete guards" and "chosen long ago guards" things should + * probably be different functions. */ +static int +remove_obsolete_entry_guards(time_t now) +{ + int changed = 0, i; + + for (i = 0; i < smartlist_len(entry_guards); ++i) { + entry_guard_t *entry = smartlist_get(entry_guards, i); + const char *ver = entry->chosen_by_version; + const char *msg = NULL; + tor_version_t v; + int version_is_bad = 0, date_is_bad = 0; + if (!ver) { + msg = "does not say what version of Tor it was selected by"; + version_is_bad = 1; + } else if (tor_version_parse(ver, &v)) { + msg = "does not seem to be from any recognized version of Tor"; + version_is_bad = 1; + } else { + char *tor_ver = NULL; + tor_asprintf(&tor_ver, "Tor %s", ver); + if ((tor_version_as_new_as(tor_ver, "0.1.0.10-alpha") && + !tor_version_as_new_as(tor_ver, "0.1.2.16-dev")) || + (tor_version_as_new_as(tor_ver, "0.2.0.0-alpha") && + !tor_version_as_new_as(tor_ver, "0.2.0.6-alpha")) || + /* above are bug 440; below are bug 1217 */ + (tor_version_as_new_as(tor_ver, "0.2.1.3-alpha") && + !tor_version_as_new_as(tor_ver, "0.2.1.23")) || + (tor_version_as_new_as(tor_ver, "0.2.2.0-alpha") && + !tor_version_as_new_as(tor_ver, "0.2.2.7-alpha"))) { + msg = "was selected without regard for guard bandwidth"; + version_is_bad = 1; + } + tor_free(tor_ver); + } + if (!version_is_bad && entry->chosen_on_date + 3600*24*60 < now) { + /* It's been 2 months since the date listed in our state file. */ + msg = "was selected several months ago"; + date_is_bad = 1; + } + + if (version_is_bad || date_is_bad) { /* we need to drop it */ + char dbuf[HEX_DIGEST_LEN+1]; + tor_assert(msg); + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC, + "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.", + entry->nickname, dbuf, msg, ver?escaped(ver):"none"); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(entry_guards, i--); + log_entry_guards(LOG_INFO); + changed = 1; + } + } + + return changed ? 1 : 0; +} + +/** Remove all entry guards that have been down or unlisted for so + * long that we don't think they'll come up again. Return 1 if we + * removed any, or 0 if we did nothing. */ +static int +remove_dead_entry_guards(time_t now) +{ + char dbuf[HEX_DIGEST_LEN+1]; + char tbuf[ISO_TIME_LEN+1]; + int i; + int changed = 0; + + for (i = 0; i < smartlist_len(entry_guards); ) { + entry_guard_t *entry = smartlist_get(entry_guards, i); + if (entry->bad_since && + ! entry->path_bias_disabled && + entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { + + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + format_local_iso_time(tbuf, entry->bad_since); + log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted " + "since %s local time; removing.", + entry->nickname, dbuf, tbuf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(entry_guards, i); + log_entry_guards(LOG_INFO); + changed = 1; + } else + ++i; + } + return changed ? 1 : 0; +} + +/** A new directory or router-status has arrived; update the down/listed + * status of the entry guards. + * + * An entry is 'down' if the directory lists it as nonrunning. + * An entry is 'unlisted' if the directory doesn't include it. + * + * Don't call this on startup; only on a fresh download. Otherwise we'll + * think that things are unlisted. + */ +void +entry_guards_compute_status(const or_options_t *options, time_t now) +{ + int changed = 0; + digestmap_t *reasons; + + if (! entry_guards) + return; + + if (options->EntryNodes) /* reshuffle the entry guard list if needed */ + entry_nodes_should_be_added(); + + reasons = digestmap_new(); + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) + { + const node_t *r = node_get_by_id(entry->identity); + const char *reason = NULL; + if (entry_guard_set_status(entry, r, now, options, &reason)) + changed = 1; + + if (entry->bad_since) + tor_assert(reason); + if (reason) + digestmap_set(reasons, entry->identity, (char*)reason); + } + SMARTLIST_FOREACH_END(entry); + + if (remove_dead_entry_guards(now)) + changed = 1; + if (remove_obsolete_entry_guards(now)) + changed = 1; + + if (changed) { + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + const char *reason = digestmap_get(reasons, entry->identity); + const char *live_msg = ""; + const node_t *r = entry_is_live(entry, 0, 1, 0, 0, &live_msg); + log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.", + entry->nickname, + hex_str(entry->identity, DIGEST_LEN), + entry->unreachable_since ? "unreachable" : "reachable", + entry->bad_since ? "unusable" : "usable", + reason ? ", ": "", + reason ? reason : "", + r ? "live" : "not live / ", + r ? "" : live_msg); + } SMARTLIST_FOREACH_END(entry); + log_info(LD_CIRC, " (%d/%d entry guards are usable/new)", + num_live_entry_guards(0), smartlist_len(entry_guards)); + log_entry_guards(LOG_INFO); + entry_guards_changed(); + } + + digestmap_free(reasons, NULL); +} + +/** Called when a connection to an OR with the identity digest <b>digest</b> + * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0). + * If the OR is an entry, change that entry's up/down status. + * Return 0 normally, or -1 if we want to tear down the new connection. + * + * If <b>mark_relay_status</b>, also call router_set_status() on this + * relay. + * + * XXX024 change succeeded and mark_relay_status into 'int flags'. + */ +int +entry_guard_register_connect_status(const char *digest, int succeeded, + int mark_relay_status, time_t now) +{ + int changed = 0; + int refuse_conn = 0; + int first_contact = 0; + entry_guard_t *entry = NULL; + int idx = -1; + char buf[HEX_DIGEST_LEN+1]; + + if (! entry_guards) + return 0; + + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + tor_assert(e); + if (tor_memeq(e->identity, digest, DIGEST_LEN)) { + entry = e; + idx = e_sl_idx; + break; + } + } SMARTLIST_FOREACH_END(e); + + if (!entry) + return 0; + + base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN); + + if (succeeded) { + if (entry->unreachable_since) { + log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again. Good.", + entry->nickname, buf); + entry->can_retry = 0; + entry->unreachable_since = 0; + entry->last_attempted = now; + control_event_guard(entry->nickname, entry->identity, "UP"); + changed = 1; + } + if (!entry->made_contact) { + entry->made_contact = 1; + first_contact = changed = 1; + } + } else { /* ! succeeded */ + if (!entry->made_contact) { + /* We've never connected to this one. */ + log_info(LD_CIRC, + "Connection to never-contacted entry guard '%s' (%s) failed. " + "Removing from the list. %d/%d entry guards usable/new.", + entry->nickname, buf, + num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del_keeporder(entry_guards, idx); + log_entry_guards(LOG_INFO); + changed = 1; + } else if (!entry->unreachable_since) { + log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). " + "Marking as unreachable.", entry->nickname, buf); + entry->unreachable_since = entry->last_attempted = now; + control_event_guard(entry->nickname, entry->identity, "DOWN"); + changed = 1; + entry->can_retry = 0; /* We gave it an early chance; no good. */ + } else { + char tbuf[ISO_TIME_LEN+1]; + format_iso_time(tbuf, entry->unreachable_since); + log_debug(LD_CIRC, "Failed to connect to unreachable entry guard " + "'%s' (%s). It has been unreachable since %s.", + entry->nickname, buf, tbuf); + entry->last_attempted = now; + entry->can_retry = 0; /* We gave it an early chance; no good. */ + } + } + + /* if the caller asked us to, also update the is_running flags for this + * relay */ + if (mark_relay_status) + router_set_status(digest, succeeded); + + if (first_contact) { + /* We've just added a new long-term entry guard. Perhaps the network just + * came back? We should give our earlier entries another try too, + * and close this connection so we don't use it before we've given + * the others a shot. */ + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + if (e == entry) + break; + if (e->made_contact) { + const char *msg; + const node_t *r = entry_is_live(e, 0, 1, 1, 0, &msg); + if (r && e->unreachable_since) { + refuse_conn = 1; + e->can_retry = 1; + } + } + } SMARTLIST_FOREACH_END(e); + if (refuse_conn) { + log_info(LD_CIRC, + "Connected to new entry guard '%s' (%s). Marking earlier " + "entry guards up. %d/%d entry guards usable/new.", + entry->nickname, buf, + num_live_entry_guards(0), smartlist_len(entry_guards)); + log_entry_guards(LOG_INFO); + changed = 1; + } + } + + if (changed) + entry_guards_changed(); + return refuse_conn ? -1 : 0; +} + +/** When we try to choose an entry guard, should we parse and add + * config's EntryNodes first? */ +static int should_add_entry_nodes = 0; + +/** Called when the value of EntryNodes changes in our configuration. */ +void +entry_nodes_should_be_added(void) +{ + log_info(LD_CIRC, "EntryNodes config option set. Putting configured " + "relays at the front of the entry guard list."); + should_add_entry_nodes = 1; +} + +/** Adjust the entry guards list so that it only contains entries from + * EntryNodes, adding new entries from EntryNodes to the list as needed. */ +static void +entry_guards_set_from_config(const or_options_t *options) +{ + smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps; + smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list; + tor_assert(entry_guards); + + should_add_entry_nodes = 0; + + if (!options->EntryNodes) { + /* It's possible that a controller set EntryNodes, thus making + * should_add_entry_nodes set, then cleared it again, all before the + * call to choose_random_entry() that triggered us. If so, just return. + */ + return; + } + + { + char *string = routerset_to_string(options->EntryNodes); + log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string); + tor_free(string); + } + + entry_nodes = smartlist_new(); + worse_entry_nodes = smartlist_new(); + entry_fps = smartlist_new(); + old_entry_guards_on_list = smartlist_new(); + old_entry_guards_not_on_list = smartlist_new(); + + /* Split entry guards into those on the list and those not. */ + + routerset_get_all_nodes(entry_nodes, options->EntryNodes, + options->ExcludeNodes, 0); + SMARTLIST_FOREACH(entry_nodes, const node_t *,node, + smartlist_add(entry_fps, (void*)node->identity)); + + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { + if (smartlist_digest_isin(entry_fps, e->identity)) + smartlist_add(old_entry_guards_on_list, e); + else + smartlist_add(old_entry_guards_not_on_list, e); + }); + + /* Remove all currently configured guard nodes, excluded nodes, unreachable + * nodes, or non-Guard nodes from entry_nodes. */ + SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { + if (entry_guard_get_by_id_digest(node->identity)) { + SMARTLIST_DEL_CURRENT(entry_nodes, node); + continue; + } else if (routerset_contains_node(options->ExcludeNodes, node)) { + SMARTLIST_DEL_CURRENT(entry_nodes, node); + continue; + } else if (!fascist_firewall_allows_node(node)) { + SMARTLIST_DEL_CURRENT(entry_nodes, node); + continue; + } else if (! node->is_possible_guard) { + smartlist_add(worse_entry_nodes, (node_t*)node); + SMARTLIST_DEL_CURRENT(entry_nodes, node); + } + } SMARTLIST_FOREACH_END(node); + + /* Now build the new entry_guards list. */ + smartlist_clear(entry_guards); + /* First, the previously configured guards that are in EntryNodes. */ + smartlist_add_all(entry_guards, old_entry_guards_on_list); + /* Next, scramble the rest of EntryNodes, putting the guards first. */ + smartlist_shuffle(entry_nodes); + smartlist_shuffle(worse_entry_nodes); + smartlist_add_all(entry_nodes, worse_entry_nodes); + + /* Next, the rest of EntryNodes */ + SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { + add_an_entry_guard(node, 0, 0, 0); + if (smartlist_len(entry_guards) > options->NumEntryGuards * 10) + break; + } SMARTLIST_FOREACH_END(node); + log_notice(LD_GENERAL, "%d entries in guards", smartlist_len(entry_guards)); + /* Finally, free the remaining previously configured guards that are not in + * EntryNodes. */ + SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, + entry_guard_free(e)); + + smartlist_free(entry_nodes); + smartlist_free(worse_entry_nodes); + smartlist_free(entry_fps); + smartlist_free(old_entry_guards_on_list); + smartlist_free(old_entry_guards_not_on_list); + entry_guards_changed(); +} + +/** Return 0 if we're fine adding arbitrary routers out of the + * directory to our entry guard list, or return 1 if we have a + * list already and we must stick to it. + */ +int +entry_list_is_constrained(const or_options_t *options) +{ + if (options->EntryNodes) + return 1; + if (options->UseBridges) + return 1; + return 0; +} + +/** Pick a live (up and listed) entry guard from entry_guards. If + * <b>state</b> is non-NULL, this is for a specific circuit -- + * make sure not to pick this circuit's exit or any node in the + * exit's family. If <b>state</b> is NULL, we're looking for a random + * guard (likely a bridge). */ +const node_t * +choose_random_entry(cpath_build_state_t *state) +{ + return choose_random_entry_impl(state, 0, 0); +} + +/** Pick a live (up and listed) directory guard from entry_guards for + * downloading information of type <b>type</b>. */ +const node_t * +choose_random_dirguard(dirinfo_type_t type) +{ + return choose_random_entry_impl(NULL, 1, type); +} + +/** Helper for choose_random{entry,dirguard}. */ +static const node_t * +choose_random_entry_impl(cpath_build_state_t *state, int for_directory, + dirinfo_type_t dirinfo_type) +{ + const or_options_t *options = get_options(); + smartlist_t *live_entry_guards = smartlist_new(); + smartlist_t *exit_family = smartlist_new(); + const node_t *chosen_exit = + state?build_state_get_exit_node(state) : NULL; + const node_t *node = NULL; + int need_uptime = state ? state->need_uptime : 0; + int need_capacity = state ? state->need_capacity : 0; + int preferred_min, consider_exit_family = 0; + int need_descriptor = !for_directory; + const int num_needed = for_directory ? options->NumDirectoryGuards : + options->NumEntryGuards; + + /* Checking dirinfo_type isn't required yet, since we only choose directory + guards that can support microdescs, routerinfos, and networkstatuses, AND + we don't use directory guards if we're configured to do direct downloads + of anything else. */ + (void) dirinfo_type; + + if (chosen_exit) { + nodelist_add_node_and_family(exit_family, chosen_exit); + consider_exit_family = 1; + } + + if (!entry_guards) + entry_guards = smartlist_new(); + + if (should_add_entry_nodes) + entry_guards_set_from_config(options); + + if (!entry_list_is_constrained(options) && + smartlist_len(entry_guards) < num_needed) + pick_entry_guards(options, for_directory); + + retry: + smartlist_clear(live_entry_guards); + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + const char *msg; + node = entry_is_live(entry, need_uptime, need_capacity, 0, + need_descriptor, &msg); + if (!node) + continue; /* down, no point */ + if (for_directory) { + if (!entry->is_dir_cache) + continue; /* We need a directory and didn't get one. */ + } + if (node == chosen_exit) + continue; /* don't pick the same node for entry and exit */ + if (consider_exit_family && smartlist_isin(exit_family, node)) + continue; /* avoid relays that are family members of our exit */ +#if 0 /* since EntryNodes is always strict now, this clause is moot */ + if (options->EntryNodes && + !routerset_contains_node(options->EntryNodes, node)) { + /* We've come to the end of our preferred entry nodes. */ + if (smartlist_len(live_entry_guards)) + goto choose_and_finish; /* only choose from the ones we like */ + if (options->StrictNodes) { + /* in theory this case should never happen, since + * entry_guards_set_from_config() drops unwanted relays */ + tor_fragile_assert(); + } else { + log_info(LD_CIRC, + "No relays from EntryNodes available. Using others."); + } + } +#endif + smartlist_add(live_entry_guards, (void*)node); + if (!entry->made_contact) { + /* Always start with the first not-yet-contacted entry + * guard. Otherwise we might add several new ones, pick + * the second new one, and now we've expanded our entry + * guard list without needing to. */ + goto choose_and_finish; + } + if (smartlist_len(live_entry_guards) >= num_needed) + goto choose_and_finish; /* we have enough */ + } SMARTLIST_FOREACH_END(entry); + + if (entry_list_is_constrained(options)) { + /* If we prefer the entry nodes we've got, and we have at least + * one choice, that's great. Use it. */ + preferred_min = 1; + } else { + /* Try to have at least 2 choices available. This way we don't + * get stuck with a single live-but-crummy entry and just keep + * using him. + * (We might get 2 live-but-crummy entry guards, but so be it.) */ + preferred_min = 2; + } + + if (smartlist_len(live_entry_guards) < preferred_min) { + if (!entry_list_is_constrained(options)) { + /* still no? try adding a new entry then */ + /* XXX if guard doesn't imply fast and stable, then we need + * to tell add_an_entry_guard below what we want, or it might + * be a long time til we get it. -RD */ + node = add_an_entry_guard(NULL, 0, 0, for_directory); + if (node) { + entry_guards_changed(); + /* XXX we start over here in case the new node we added shares + * a family with our exit node. There's a chance that we'll just + * load up on entry guards here, if the network we're using is + * one big family. Perhaps we should teach add_an_entry_guard() + * to understand nodes-to-avoid-if-possible? -RD */ + goto retry; + } + } + if (!node && need_uptime) { + need_uptime = 0; /* try without that requirement */ + goto retry; + } + if (!node && need_capacity) { + /* still no? last attempt, try without requiring capacity */ + need_capacity = 0; + goto retry; + } +#if 0 + /* Removing this retry logic: if we only allow one exit, and it is in the + same family as all our entries, then we are just plain not going to win + here. */ + if (!node && entry_list_is_constrained(options) && consider_exit_family) { + /* still no? if we're using bridges or have strictentrynodes + * set, and our chosen exit is in the same family as all our + * bridges/entry guards, then be flexible about families. */ + consider_exit_family = 0; + goto retry; + } +#endif + /* live_entry_guards may be empty below. Oh well, we tried. */ + } + + choose_and_finish: + if (entry_list_is_constrained(options)) { + /* We need to weight by bandwidth, because our bridges or entryguards + * were not already selected proportional to their bandwidth. */ + node = node_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD); + } else { + /* We choose uniformly at random here, because choose_good_entry_server() + * already weights its choices by bandwidth, so we don't want to + * *double*-weight our guard selection. */ + node = smartlist_choose(live_entry_guards); + } + smartlist_free(live_entry_guards); + smartlist_free(exit_family); + return node; +} + +/** Parse <b>state</b> and learn about the entry guards it describes. + * If <b>set</b> is true, and there are no errors, replace the global + * entry_list with what we find. + * On success, return 0. On failure, alloc into *<b>msg</b> a string + * describing the error, and return -1. + */ +int +entry_guards_parse_state(or_state_t *state, int set, char **msg) +{ + entry_guard_t *node = NULL; + smartlist_t *new_entry_guards = smartlist_new(); + config_line_t *line; + time_t now = time(NULL); + const char *state_version = state->TorVersion; + digestmap_t *added_by = digestmap_new(); + + *msg = NULL; + for (line = state->EntryGuards; line; line = line->next) { + if (!strcasecmp(line->key, "EntryGuard")) { + smartlist_t *args = smartlist_new(); + node = tor_malloc_zero(sizeof(entry_guard_t)); + /* all entry guards on disk have been contacted */ + node->made_contact = 1; + smartlist_add(new_entry_guards, node); + smartlist_split_string(args, line->value, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(args)<2) { + *msg = tor_strdup("Unable to parse entry nodes: " + "Too few arguments to EntryGuard"); + } else if (!is_legal_nickname(smartlist_get(args,0))) { + *msg = tor_strdup("Unable to parse entry nodes: " + "Bad nickname for EntryGuard"); + } else { + strlcpy(node->nickname, smartlist_get(args,0), MAX_NICKNAME_LEN+1); + if (base16_decode(node->identity, DIGEST_LEN, smartlist_get(args,1), + strlen(smartlist_get(args,1)))<0) { + *msg = tor_strdup("Unable to parse entry nodes: " + "Bad hex digest for EntryGuard"); + } + } + if (smartlist_len(args) >= 3) { + const char *is_cache = smartlist_get(args, 2); + if (!strcasecmp(is_cache, "DirCache")) { + node->is_dir_cache = 1; + } else if (!strcasecmp(is_cache, "NoDirCache")) { + node->is_dir_cache = 0; + } else { + log_warn(LD_CONFIG, "Bogus third argument to EntryGuard line: %s", + escaped(is_cache)); + } + } + SMARTLIST_FOREACH(args, char*, cp, tor_free(cp)); + smartlist_free(args); + if (*msg) + break; + } else if (!strcasecmp(line->key, "EntryGuardDownSince") || + !strcasecmp(line->key, "EntryGuardUnlistedSince")) { + time_t when; + time_t last_try = 0; + if (!node) { + *msg = tor_strdup("Unable to parse entry nodes: " + "EntryGuardDownSince/UnlistedSince without EntryGuard"); + break; + } + if (parse_iso_time(line->value, &when)<0) { + *msg = tor_strdup("Unable to parse entry nodes: " + "Bad time in EntryGuardDownSince/UnlistedSince"); + break; + } + if (when > now) { + /* It's a bad idea to believe info in the future: you can wind + * up with timeouts that aren't allowed to happen for years. */ + continue; + } + if (strlen(line->value) >= ISO_TIME_LEN+ISO_TIME_LEN+1) { + /* ignore failure */ + (void) parse_iso_time(line->value+ISO_TIME_LEN+1, &last_try); + } + if (!strcasecmp(line->key, "EntryGuardDownSince")) { + node->unreachable_since = when; + node->last_attempted = last_try; + } else { + node->bad_since = when; + } + } else if (!strcasecmp(line->key, "EntryGuardAddedBy")) { + char d[DIGEST_LEN]; + /* format is digest version date */ + if (strlen(line->value) < HEX_DIGEST_LEN+1+1+1+ISO_TIME_LEN) { + log_warn(LD_BUG, "EntryGuardAddedBy line is not long enough."); + continue; + } + if (base16_decode(d, sizeof(d), line->value, HEX_DIGEST_LEN)<0 || + line->value[HEX_DIGEST_LEN] != ' ') { + log_warn(LD_BUG, "EntryGuardAddedBy line %s does not begin with " + "hex digest", escaped(line->value)); + continue; + } + digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1)); + } else if (!strcasecmp(line->key, "EntryGuardPathBias")) { + const or_options_t *options = get_options(); + double hop_cnt, success_cnt, timeouts, collapsed, successful_closed, + unusable; + + if (!node) { + *msg = tor_strdup("Unable to parse entry nodes: " + "EntryGuardPathBias without EntryGuard"); + break; + } + + /* First try 3 params, then 2. */ + /* In the long run: circuit_success ~= successful_circuit_close + + * collapsed_circuits + + * unusable_circuits */ + if (tor_sscanf(line->value, "%lf %lf %lf %lf %lf %lf", + &hop_cnt, &success_cnt, &successful_closed, + &collapsed, &unusable, &timeouts) != 6) { + int old_success, old_hops; + if (tor_sscanf(line->value, "%u %u", &old_success, &old_hops) != 2) { + continue; + } + log_info(LD_GENERAL, "Reading old-style EntryGuardPathBias %s", + escaped(line->value)); + + success_cnt = old_success; + successful_closed = old_success; + hop_cnt = old_hops; + timeouts = 0; + collapsed = 0; + unusable = 0; + } + + node->circ_attempts = hop_cnt; + node->circ_successes = success_cnt; + + node->successful_circuits_closed = successful_closed; + node->timeouts = timeouts; + node->collapsed_circuits = collapsed; + node->unusable_circuits = unusable; + + log_info(LD_GENERAL, "Read %f/%f path bias for node %s", + node->circ_successes, node->circ_attempts, node->nickname); + /* Note: We rely on the < comparison here to allow us to set a 0 + * rate and disable the feature entirely. If refactoring, don't + * change to <= */ + if (pathbias_get_success_count(node)/node->circ_attempts + < pathbias_get_extreme_rate(options) && + pathbias_get_dropguards(options)) { + node->path_bias_disabled = 1; + log_info(LD_GENERAL, + "Path bias is too high (%f/%f); disabling node %s", + node->circ_successes, node->circ_attempts, node->nickname); + } + + } else { + log_warn(LD_BUG, "Unexpected key %s", line->key); + } + } + + SMARTLIST_FOREACH_BEGIN(new_entry_guards, entry_guard_t *, e) { + char *sp; + char *val = digestmap_get(added_by, e->identity); + if (val && (sp = strchr(val, ' '))) { + time_t when; + *sp++ = '\0'; + if (parse_iso_time(sp, &when)<0) { + log_warn(LD_BUG, "Can't read time %s in EntryGuardAddedBy", sp); + } else { + e->chosen_by_version = tor_strdup(val); + e->chosen_on_date = when; + } + } else { + if (state_version) { + e->chosen_by_version = tor_strdup(state_version); + e->chosen_on_date = time(NULL) - crypto_rand_int(3600*24*30); + } + } + if (e->path_bias_disabled && !e->bad_since) + e->bad_since = time(NULL); + } + SMARTLIST_FOREACH_END(e); + + if (*msg || !set) { + SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(new_entry_guards); + } else { /* !err && set */ + if (entry_guards) { + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(entry_guards); + } + entry_guards = new_entry_guards; + entry_guards_dirty = 0; + /* XXX024 hand new_entry_guards to this func, and move it up a + * few lines, so we don't have to re-dirty it */ + if (remove_obsolete_entry_guards(now)) + entry_guards_dirty = 1; + } + digestmap_free(added_by, tor_free_); + return *msg ? -1 : 0; +} + +/** Our list of entry guards has changed, or some element of one + * of our entry guards has changed. Write the changes to disk within + * the next few minutes. + */ +void +entry_guards_changed(void) +{ + time_t when; + entry_guards_dirty = 1; + + /* or_state_save() will call entry_guards_update_state(). */ + when = get_options()->AvoidDiskWrites ? time(NULL) + 3600 : time(NULL)+600; + or_state_mark_dirty(get_or_state(), when); +} + +/** If the entry guard info has not changed, do nothing and return. + * Otherwise, free the EntryGuards piece of <b>state</b> and create + * a new one out of the global entry_guards list, and then mark + * <b>state</b> dirty so it will get saved to disk. + */ +void +entry_guards_update_state(or_state_t *state) +{ + config_line_t **next, *line; + if (! entry_guards_dirty) + return; + + config_free_lines(state->EntryGuards); + next = &state->EntryGuards; + *next = NULL; + if (!entry_guards) + entry_guards = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + char dbuf[HEX_DIGEST_LEN+1]; + if (!e->made_contact) + continue; /* don't write this one to disk */ + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuard"); + base16_encode(dbuf, sizeof(dbuf), e->identity, DIGEST_LEN); + tor_asprintf(&line->value, "%s %s %sDirCache", e->nickname, dbuf, + e->is_dir_cache ? "" : "No"); + next = &(line->next); + if (e->unreachable_since) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuardDownSince"); + line->value = tor_malloc(ISO_TIME_LEN+1+ISO_TIME_LEN+1); + format_iso_time(line->value, e->unreachable_since); + if (e->last_attempted) { + line->value[ISO_TIME_LEN] = ' '; + format_iso_time(line->value+ISO_TIME_LEN+1, e->last_attempted); + } + next = &(line->next); + } + if (e->bad_since) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuardUnlistedSince"); + line->value = tor_malloc(ISO_TIME_LEN+1); + format_iso_time(line->value, e->bad_since); + next = &(line->next); + } + if (e->chosen_on_date && e->chosen_by_version && + !strchr(e->chosen_by_version, ' ')) { + char d[HEX_DIGEST_LEN+1]; + char t[ISO_TIME_LEN+1]; + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuardAddedBy"); + base16_encode(d, sizeof(d), e->identity, DIGEST_LEN); + format_iso_time(t, e->chosen_on_date); + tor_asprintf(&line->value, "%s %s %s", + d, e->chosen_by_version, t); + next = &(line->next); + } + if (e->circ_attempts > 0) { + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("EntryGuardPathBias"); + /* In the long run: circuit_success ~= successful_circuit_close + + * collapsed_circuits + + * unusable_circuits */ + tor_asprintf(&line->value, "%f %f %f %f %f %f", + e->circ_attempts, e->circ_successes, + pathbias_get_closed_count(e), e->collapsed_circuits, + e->unusable_circuits, e->timeouts); + next = &(line->next); + } + + } SMARTLIST_FOREACH_END(e); + if (!get_options()->AvoidDiskWrites) + or_state_mark_dirty(get_or_state(), 0); + entry_guards_dirty = 0; +} + +/** If <b>question</b> is the string "entry-guards", then dump + * to *<b>answer</b> a newly allocated string describing all of + * the nodes in the global entry_guards list. See control-spec.txt + * for details. + * For backward compatibility, we also handle the string "helper-nodes". + * */ +int +getinfo_helper_entry_guards(control_connection_t *conn, + const char *question, char **answer, + const char **errmsg) +{ + (void) conn; + (void) errmsg; + + if (!strcmp(question,"entry-guards") || + !strcmp(question,"helper-nodes")) { + smartlist_t *sl = smartlist_new(); + char tbuf[ISO_TIME_LEN+1]; + char nbuf[MAX_VERBOSE_NICKNAME_LEN+1]; + if (!entry_guards) + entry_guards = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + const char *status = NULL; + time_t when = 0; + const node_t *node; + + if (!e->made_contact) { + status = "never-connected"; + } else if (e->bad_since) { + when = e->bad_since; + status = "unusable"; + } else { + status = "up"; + } + + node = node_get_by_id(e->identity); + if (node) { + node_get_verbose_nickname(node, nbuf); + } else { + nbuf[0] = '$'; + base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN); + /* e->nickname field is not very reliable if we don't know about + * this router any longer; don't include it. */ + } + + if (when) { + format_iso_time(tbuf, when); + smartlist_add_asprintf(sl, "%s %s %s\n", nbuf, status, tbuf); + } else { + smartlist_add_asprintf(sl, "%s %s\n", nbuf, status); + } + } SMARTLIST_FOREACH_END(e); + *answer = smartlist_join_strings(sl, "", 0, NULL); + SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); + smartlist_free(sl); + } + return 0; +} + +/** A list of configured bridges. Whenever we actually get a descriptor + * for one, we add it as an entry guard. Note that the order of bridges + * in this list does not necessarily correspond to the order of bridges + * in the torrc. */ +static smartlist_t *bridge_list = NULL; + +/** Mark every entry of the bridge list to be removed on our next call to + * sweep_bridge_list unless it has first been un-marked. */ +void +mark_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, + b->marked_for_removal = 1); +} + +/** Remove every entry of the bridge list that was marked with + * mark_bridge_list if it has not subsequently been un-marked. */ +void +sweep_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { + if (b->marked_for_removal) { + SMARTLIST_DEL_CURRENT(bridge_list, b); + bridge_free(b); + } + } SMARTLIST_FOREACH_END(b); +} + +/** Initialize the bridge list to empty, creating it if needed. */ +static void +clear_bridge_list(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b)); + smartlist_clear(bridge_list); +} + +/** Free the bridge <b>bridge</b>. */ +static void +bridge_free(bridge_info_t *bridge) +{ + if (!bridge) + return; + + tor_free(bridge->transport_name); + tor_free(bridge); +} + +/** If we have a bridge configured whose digest matches <b>digest</b>, or a + * bridge with no known digest whose address matches any of the + * tor_addr_port_t's in <b>orports</b>, return that bridge. Else return + * NULL. */ +static bridge_info_t * +get_configured_bridge_by_orports_digest(const char *digest, + const smartlist_t *orports) +{ + if (!bridge_list) + return NULL; + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if (tor_digest_is_zero(bridge->identity)) { + SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap) + { + if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 && + bridge->port == ap->port) + return bridge; + } + SMARTLIST_FOREACH_END(ap); + } + if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + } + SMARTLIST_FOREACH_END(bridge); + return NULL; +} + +/** If we have a bridge configured whose digest matches <b>digest</b>, or a + * bridge with no known digest whose address matches <b>addr</b>:<b>/port</b>, + * return that bridge. Else return NULL. */ +static bridge_info_t * +get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, + uint16_t port, + const char *digest) +{ + if (!bridge_list) + return NULL; + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if (tor_digest_is_zero(bridge->identity) && + !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && + bridge->port == port) + return bridge; + if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + } + SMARTLIST_FOREACH_END(bridge); + return NULL; +} + +/** Wrapper around get_configured_bridge_by_addr_port_digest() to look + * it up via router descriptor <b>ri</b>. */ +static bridge_info_t * +get_configured_bridge_by_routerinfo(const routerinfo_t *ri) +{ + bridge_info_t *bi = NULL; + smartlist_t *orports = router_get_all_orports(ri); + bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest, + orports); + SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); + smartlist_free(orports); + return bi; +} + +/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */ +int +routerinfo_is_a_configured_bridge(const routerinfo_t *ri) +{ + return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; +} + +/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */ +int +node_is_a_configured_bridge(const node_t *node) +{ + int retval = 0; + smartlist_t *orports = node_get_all_orports(node); + retval = get_configured_bridge_by_orports_digest(node->identity, + orports) != NULL; + SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); + smartlist_free(orports); + return retval; +} + +/** We made a connection to a router at <b>addr</b>:<b>port</b> + * without knowing its digest. Its digest turned out to be <b>digest</b>. + * If it was a bridge, and we still don't know its digest, record it. + */ +void +learned_router_identity(const tor_addr_t *addr, uint16_t port, + const char *digest) +{ + bridge_info_t *bridge = + get_configured_bridge_by_addr_port_digest(addr, port, digest); + if (bridge && tor_digest_is_zero(bridge->identity)) { + memcpy(bridge->identity, digest, DIGEST_LEN); + log_notice(LD_DIR, "Learned fingerprint %s for bridge %s", + hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port)); + } +} + +/** Return true if <b>bridge</b> has the same identity digest as + * <b>digest</b>. If <b>digest</b> is NULL, it matches + * bridges with unspecified identity digests. */ +static int +bridge_has_digest(const bridge_info_t *bridge, const char *digest) +{ + if (digest) + return tor_memeq(digest, bridge->identity, DIGEST_LEN); + else + return tor_digest_is_zero(bridge->identity); +} + +/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional + * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously + * existing bridge with the same address and port, and warn the user as + * appropriate. + */ +static void +bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, + const char *digest, const char *transport_name) +{ + /* Iterate the already-registered bridge list: + + If you find a bridge with the same adress and port, mark it for + removal. It doesn't make sense to have two active bridges with + the same IP:PORT. If the bridge in question has a different + digest or transport than <b>digest</b>/<b>transport_name</b>, + it's probably a misconfiguration and we should warn the user. + */ + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { + if (bridge->marked_for_removal) + continue; + + if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { + + bridge->marked_for_removal = 1; + + if (!bridge_has_digest(bridge, digest) || + strcmp_opt(bridge->transport_name, transport_name)) { + /* warn the user */ + char *bridge_description_new, *bridge_description_old; + tor_asprintf(&bridge_description_new, "%s:%s:%s", + fmt_addrport(addr, port), + digest ? hex_str(digest, DIGEST_LEN) : "", + transport_name ? transport_name : ""); + tor_asprintf(&bridge_description_old, "%s:%s:%s", + fmt_addrport(&bridge->addr, bridge->port), + tor_digest_is_zero(bridge->identity) ? + "" : hex_str(bridge->identity,DIGEST_LEN), + bridge->transport_name ? bridge->transport_name : ""); + + log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict" + " with the already registered bridge '%s'. We will discard" + " the old bridge and keep '%s'. If this is not what you" + " wanted, please change your configuration file accordingly.", + bridge_description_new, bridge_description_old, + bridge_description_new); + + tor_free(bridge_description_new); + tor_free(bridge_description_old); + } + } + } SMARTLIST_FOREACH_END(bridge); +} + +/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b> + * is set, it tells us the identity key too. If we already had the + * bridge in our list, unmark it, and don't actually add anything new. + * If <b>transport_name</b> is non-NULL - the bridge is associated with a + * pluggable transport - we assign the transport to the bridge. */ +void +bridge_add_from_config(const tor_addr_t *addr, uint16_t port, + const char *digest, const char *transport_name) +{ + bridge_info_t *b; + + bridge_resolve_conflicts(addr, port, digest, transport_name); + + b = tor_malloc_zero(sizeof(bridge_info_t)); + tor_addr_copy(&b->addr, addr); + b->port = port; + if (digest) + memcpy(b->identity, digest, DIGEST_LEN); + if (transport_name) + b->transport_name = tor_strdup(transport_name); + b->fetch_status.schedule = DL_SCHED_BRIDGE; + if (!bridge_list) + bridge_list = smartlist_new(); + + smartlist_add(bridge_list, b); +} + +/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */ +static int +routerset_contains_bridge(const routerset_t *routerset, + const bridge_info_t *bridge) +{ + int result; + extend_info_t *extinfo; + tor_assert(bridge); + if (!routerset) + return 0; + + extinfo = extend_info_new( + NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port); + result = routerset_contains_extendinfo(routerset, extinfo); + extend_info_free(extinfo); + return result; +} + +/** If <b>digest</b> is one of our known bridges, return it. */ +static bridge_info_t * +find_bridge_by_digest(const char *digest) +{ + SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, + { + if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) + return bridge; + }); + return NULL; +} + +/* DOCDOC find_transport_name_by_bridge_addrport */ +const char * +find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) +{ + if (!bridge_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (tor_addr_eq(&bridge->addr, addr) && + (bridge->port == port)) + return bridge->transport_name; + } SMARTLIST_FOREACH_END(bridge); + + return NULL; +} + +/** If <b>addr</b> and <b>port</b> match the address and port of a + * bridge of ours that uses pluggable transports, place its transport + * in <b>transport</b>. + * + * Return 0 on success (found a transport, or found a bridge with no + * transport, or found no bridge); return -1 if we should be using a + * transport, but the transport could not be found. + */ +int +find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, + const transport_t **transport) +{ + *transport = NULL; + if (!bridge_list) + return 0; + + SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { + if (tor_addr_eq(&bridge->addr, addr) && + (bridge->port == port)) { /* bridge matched */ + if (bridge->transport_name) { /* it also uses pluggable transports */ + *transport = transport_get_by_name(bridge->transport_name); + if (*transport == NULL) { /* it uses pluggable transports, but + the transport could not be found! */ + return -1; + } + return 0; + } else { /* bridge matched, but it doesn't use transports. */ + break; + } + } + } SMARTLIST_FOREACH_END(bridge); + + *transport = NULL; + return 0; +} + +/** We need to ask <b>bridge</b> for its server descriptor. */ +static void +launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) +{ + char *address; + const or_options_t *options = get_options(); + + if (connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &bridge->addr, bridge->port, + DIR_PURPOSE_FETCH_SERVERDESC)) + return; /* it's already on the way */ + + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_and_decorate_addr(&bridge->addr))); + return; + } + + address = tor_dup_addr(&bridge->addr); + + directory_initiate_command(address, &bridge->addr, + bridge->port, 0/*no dirport*/, + bridge->identity, + DIR_PURPOSE_FETCH_SERVERDESC, + ROUTER_PURPOSE_BRIDGE, + DIRIND_ONEHOP, "authority.z", NULL, 0, 0); + tor_free(address); +} + +/** Fetching the bridge descriptor from the bridge authority returned a + * "not found". Fall back to trying a direct fetch. */ +void +retry_bridge_descriptor_fetch_directly(const char *digest) +{ + bridge_info_t *bridge = find_bridge_by_digest(digest); + if (!bridge) + return; /* not found? oh well. */ + + launch_direct_bridge_descriptor_fetch(bridge); +} + +/** For each bridge in our list for which we don't currently have a + * descriptor, fetch a new copy of its descriptor -- either directly + * from the bridge or via a bridge authority. */ +void +fetch_bridge_descriptors(const or_options_t *options, time_t now) +{ + int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO); + int ask_bridge_directly; + int can_use_bridge_authority; + + if (!bridge_list) + return; + + /* If we still have unconfigured managed proxies, don't go and + connect to a bridge. */ + if (pt_proxies_configuration_pending()) + return; + + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) + { + if (!download_status_is_ready(&bridge->fetch_status, now, + IMPOSSIBLE_TO_DOWNLOAD)) + continue; /* don't bother, no need to retry yet */ + if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { + download_status_mark_impossible(&bridge->fetch_status); + log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", + safe_str_client(fmt_and_decorate_addr(&bridge->addr))); + continue; + } + + /* schedule another fetch as if this one will fail, in case it does */ + download_status_failed(&bridge->fetch_status, 0); + + can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && + num_bridge_auths; + ask_bridge_directly = !can_use_bridge_authority || + !options->UpdateBridgesFromAuthority; + log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)", + ask_bridge_directly, tor_digest_is_zero(bridge->identity), + !options->UpdateBridgesFromAuthority, !num_bridge_auths); + + if (ask_bridge_directly && + !fascist_firewall_allows_address_or(&bridge->addr, bridge->port)) { + log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our " + "firewall policy. %s.", + fmt_addrport(&bridge->addr, bridge->port), + can_use_bridge_authority ? + "Asking bridge authority instead" : "Skipping"); + if (can_use_bridge_authority) + ask_bridge_directly = 0; + else + continue; + } + + if (ask_bridge_directly) { + /* we need to ask the bridge itself for its descriptor. */ + launch_direct_bridge_descriptor_fetch(bridge); + } else { + /* We have a digest and we want to ask an authority. We could + * combine all the requests into one, but that may give more + * hints to the bridge authority than we want to give. */ + char resource[10 + HEX_DIGEST_LEN]; + memcpy(resource, "fp/", 3); + base16_encode(resource+3, HEX_DIGEST_LEN+1, + bridge->identity, DIGEST_LEN); + memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3); + log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", + resource); + directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, + ROUTER_PURPOSE_BRIDGE, resource, 0); + } + } + SMARTLIST_FOREACH_END(bridge); +} + +/** If our <b>bridge</b> is configured to be a different address than + * the bridge gives in <b>node</b>, rewrite the routerinfo + * we received to use the address we meant to use. Now we handle + * multihomed bridges better. + */ +static void +rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) +{ + /* XXXX move this function. */ + /* XXXX overridden addresses should really live in the node_t, so that the + * routerinfo_t and the microdesc_t can be immutable. But we can only + * do that safely if we know that no function that connects to an OR + * does so through an address from any source other than node_get_addr(). + */ + tor_addr_t addr; + + if (node->ri) { + routerinfo_t *ri = node->ri; + tor_addr_from_ipv4h(&addr, ri->addr); + + if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && + bridge->port == ri->or_port) || + (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) && + bridge->port == ri->ipv6_orport)) { + /* they match, so no need to do anything */ + } else { + if (tor_addr_family(&bridge->addr) == AF_INET) { + ri->addr = tor_addr_to_ipv4h(&bridge->addr); + tor_free(ri->address); + ri->address = tor_dup_ip(ri->addr); + ri->or_port = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerinfo for '%s' to match configured " + "address %s:%d.", + ri->nickname, ri->address, ri->or_port); + } else if (tor_addr_family(&bridge->addr) == AF_INET6) { + tor_addr_copy(&ri->ipv6_addr, &bridge->addr); + ri->ipv6_orport = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerinfo for '%s' to match configured " + "address %s.", + ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); + } else { + log_err(LD_BUG, "Address family not supported: %d.", + tor_addr_family(&bridge->addr)); + return; + } + } + + /* Mark which address to use based on which bridge_t we got. */ + node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && + !tor_addr_is_null(&node->ri->ipv6_addr)); + + /* XXXipv6 we lack support for falling back to another address for + the same relay, warn the user */ + if (!tor_addr_is_null(&ri->ipv6_addr)) { + tor_addr_port_t ap; + node_get_pref_orport(node, &ap); + log_notice(LD_CONFIG, + "Bridge '%s' has both an IPv4 and an IPv6 address. " + "Will prefer using its %s address (%s).", + ri->nickname, + tor_addr_family(&ap.addr) == AF_INET6 ? "IPv6" : "IPv4", + fmt_addrport(&ap.addr, ap.port)); + } + } + if (node->rs) { + routerstatus_t *rs = node->rs; + tor_addr_from_ipv4h(&addr, rs->addr); + + if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && + bridge->port == rs->or_port) { + /* they match, so no need to do anything */ + } else { + rs->addr = tor_addr_to_ipv4h(&bridge->addr); + rs->or_port = bridge->port; + log_info(LD_DIR, + "Adjusted bridge routerstatus for '%s' to match " + "configured address %s.", + rs->nickname, fmt_addrport(&bridge->addr, rs->or_port)); + } + } +} + +/** We just learned a descriptor for a bridge. See if that + * digest is in our entry guard list, and add it if not. */ +void +learned_bridge_descriptor(routerinfo_t *ri, int from_cache) +{ + tor_assert(ri); + tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); + if (get_options()->UseBridges) { + int first = !any_bridge_descriptors_known(); + bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); + time_t now = time(NULL); + router_set_status(ri->cache_info.identity_digest, 1); + + if (bridge) { /* if we actually want to use this one */ + node_t *node; + /* it's here; schedule its re-fetch for a long time from now. */ + if (!from_cache) + download_status_reset(&bridge->fetch_status); + + node = node_get_mutable_by_id(ri->cache_info.identity_digest); + tor_assert(node); + rewrite_node_address_for_bridge(bridge, node); + add_an_entry_guard(node, 1, 1, 0); + + log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, + from_cache ? "cached" : "fresh", router_describe(ri)); + /* set entry->made_contact so if it goes down we don't drop it from + * our entry node list */ + entry_guard_register_connect_status(ri->cache_info.identity_digest, + 1, 0, now); + if (first) + routerlist_retry_directory_downloads(now); + } + } +} + +/** Return 1 if any of our entry guards have descriptors that + * are marked with purpose 'bridge' and are running. Else return 0. + * + * We use this function to decide if we're ready to start building + * circuits through our bridges, or if we need to wait until the + * directory "server/authority" requests finish. */ +int +any_bridge_descriptors_known(void) +{ + tor_assert(get_options()->UseBridges); + return choose_random_entry(NULL)!=NULL ? 1 : 0; +} + +/** Return 1 if there are any directory conns fetching bridge descriptors + * that aren't marked for close. We use this to guess if we should tell + * the controller that we have a problem. */ +int +any_pending_bridge_descriptor_fetches(void) +{ + smartlist_t *conns = get_connection_array(); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + if (conn->type == CONN_TYPE_DIR && + conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC && + TO_DIR_CONN(conn)->router_purpose == ROUTER_PURPOSE_BRIDGE && + !conn->marked_for_close && + conn->linked && + conn->linked_conn && !conn->linked_conn->marked_for_close) { + log_debug(LD_DIR, "found one: %s", conn->address); + return 1; + } + } SMARTLIST_FOREACH_END(conn); + return 0; +} + +/** Return 1 if we have at least one descriptor for an entry guard + * (bridge or member of EntryNodes) and all descriptors we know are + * down. Else return 0. If <b>act</b> is 1, then mark the down guards + * up; else just observe and report. */ +static int +entries_retry_helper(const or_options_t *options, int act) +{ + const node_t *node; + int any_known = 0; + int any_running = 0; + int need_bridges = options->UseBridges != 0; + if (!entry_guards) + entry_guards = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + node = node_get_by_id(e->identity); + if (node && node_has_descriptor(node) && + node_is_bridge(node) == need_bridges) { + any_known = 1; + if (node->is_running) + any_running = 1; /* some entry is both known and running */ + else if (act) { + /* Mark all current connections to this OR as unhealthy, since + * otherwise there could be one that started 30 seconds + * ago, and in 30 seconds it will time out, causing us to mark + * the node down and undermine the retry attempt. We mark even + * the established conns, since if the network just came back + * we'll want to attach circuits to fresh conns. */ + connection_or_set_bad_connections(node->identity, 1); + + /* mark this entry node for retry */ + router_set_status(node->identity, 1); + e->can_retry = 1; + e->bad_since = 0; + } + } + } SMARTLIST_FOREACH_END(e); + log_debug(LD_DIR, "%d: any_known %d, any_running %d", + act, any_known, any_running); + return any_known && !any_running; +} + +/** Do we know any descriptors for our bridges / entrynodes, and are + * all the ones we have descriptors for down? */ +int +entries_known_but_down(const or_options_t *options) +{ + tor_assert(entry_list_is_constrained(options)); + return entries_retry_helper(options, 0); +} + +/** Mark all down known bridges / entrynodes up. */ +void +entries_retry_all(const or_options_t *options) +{ + tor_assert(entry_list_is_constrained(options)); + entries_retry_helper(options, 1); +} + +/** Return true if we've ever had a bridge running a Tor version that can't + * provide microdescriptors to us. In that case fall back to asking for + * full descriptors. Eventually all bridges will support microdescriptors + * and we can take this check out; see bug 4013. */ +int +any_bridges_dont_support_microdescriptors(void) +{ + const node_t *node; + static int ever_answered_yes = 0; + if (!get_options()->UseBridges || !entry_guards) + return 0; + if (ever_answered_yes) + return 1; /* if we ever answer 'yes', always answer 'yes' */ + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + node = node_get_by_id(e->identity); + if (node && node->ri && + node_is_bridge(node) && node_is_a_configured_bridge(node) && + !tor_version_supports_microdescriptors(node->ri->platform)) { + /* This is one of our current bridges, and we know enough about + * it to know that it won't be able to answer our microdescriptor + * questions. */ + ever_answered_yes = 1; + return 1; + } + } SMARTLIST_FOREACH_END(e); + return 0; +} + +/** Release all storage held by the list of entry guards and related + * memory structs. */ +void +entry_guards_free_all(void) +{ + if (entry_guards) { + SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(entry_guards); + entry_guards = NULL; + } + clear_bridge_list(); + smartlist_free(bridge_list); + bridge_list = NULL; + circuit_build_times_free_timeouts(&circ_times); +} + diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h new file mode 100644 index 000000000..2a9a99cfb --- /dev/null +++ b/src/or/entrynodes.h @@ -0,0 +1,120 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file guardnodes.h + * \brief Header file for circuitbuild.c. + **/ + +#ifndef TOR_ENTRYNODES_H +#define TOR_ENTRYNODES_H + +#if 1 +/* XXXX NM I would prefer that all of this stuff be private to + * entrynodes.c. */ + +/** An entry_guard_t represents our information about a chosen long-term + * first hop, known as a "helper" node in the literature. We can't just + * use a node_t, since we want to remember these even when we + * don't have any directory info. */ +typedef struct entry_guard_t { + char nickname[MAX_NICKNAME_LEN+1]; + char identity[DIGEST_LEN]; + time_t chosen_on_date; /**< Approximately when was this guard added? + * "0" if we don't know. */ + char *chosen_by_version; /**< What tor version added this guard? NULL + * if we don't know. */ + unsigned int made_contact : 1; /**< 0 if we have never connected to this + * router, 1 if we have. */ + unsigned int can_retry : 1; /**< Should we retry connecting to this entry, + * in spite of having it marked as unreachable?*/ + unsigned int path_bias_noticed : 1; /**< Did we alert the user about path + * bias for this node already? */ + unsigned int path_bias_warned : 1; /**< Did we alert the user about path bias + * for this node already? */ + unsigned int path_bias_extreme : 1; /**< Did we alert the user about path + * bias for this node already? */ + unsigned int path_bias_disabled : 1; /**< Have we disabled this node because + * of path bias issues? */ + unsigned int is_dir_cache : 1; /**< Is this node a directory cache? */ + time_t bad_since; /**< 0 if this guard is currently usable, or the time at + * which it was observed to become (according to the + * directory or the user configuration) unusable. */ + time_t unreachable_since; /**< 0 if we can connect to this guard, or the + * time at which we first noticed we couldn't + * connect to it. */ + time_t last_attempted; /**< 0 if we can connect to this guard, or the time + * at which we last failed to connect to it. */ + + double circ_attempts; /**< Number of circuits this guard has "attempted" */ + double circ_successes; /**< Number of successfully built circuits using + * this guard as first hop. */ + double successful_circuits_closed; /**< Number of circuits that carried + * streams successfully. */ + double collapsed_circuits; /**< Number of fully built circuits that were + * remotely closed before any streams were + * attempted. */ + double unusable_circuits; /**< Number of circuits for which streams were + * attempted, but none succeeded. */ + double timeouts; /**< Number of 'right-censored' circuit timeouts for this + * guard. */ +} entry_guard_t; + +entry_guard_t *entry_guard_get_by_id_digest(const char *digest); +void entry_guards_changed(void); +const smartlist_t *get_entry_guards(void); +int num_live_entry_guards(int for_directory); + +#endif + +void entry_guards_compute_status(const or_options_t *options, time_t now); +int entry_guard_register_connect_status(const char *digest, int succeeded, + int mark_relay_status, time_t now); +void entry_nodes_should_be_added(void); +int entry_list_is_constrained(const or_options_t *options); +const node_t *choose_random_entry(cpath_build_state_t *state); +const node_t *choose_random_dirguard(dirinfo_type_t t); +int entry_guards_parse_state(or_state_t *state, int set, char **msg); +void entry_guards_update_state(or_state_t *state); +int getinfo_helper_entry_guards(control_connection_t *conn, + const char *question, char **answer, + const char **errmsg); + +void mark_bridge_list(void); +void sweep_bridge_list(void); + +int routerinfo_is_a_configured_bridge(const routerinfo_t *ri); +int node_is_a_configured_bridge(const node_t *node); +void learned_router_identity(const tor_addr_t *addr, uint16_t port, + const char *digest); +void bridge_add_from_config(const tor_addr_t *addr, uint16_t port, + const char *digest, + const char *transport_name); +void retry_bridge_descriptor_fetch_directly(const char *digest); +void fetch_bridge_descriptors(const or_options_t *options, time_t now); +void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); +int any_bridge_descriptors_known(void); +int any_pending_bridge_descriptor_fetches(void); +int entries_known_but_down(const or_options_t *options); +void entries_retry_all(const or_options_t *options); + +int any_bridges_dont_support_microdescriptors(void); + +void entry_guards_free_all(void); + +const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr, + uint16_t port); +struct transport_t; +int find_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, + const struct transport_t **transport); + +int validate_pluggable_transports_config(void); + +double pathbias_get_closed_count(entry_guard_t *gaurd); +double pathbias_get_success_count(entry_guard_t *guard); + +#endif + diff --git a/src/or/eventdns.c b/src/or/eventdns.c deleted file mode 100644 index 768693aba..000000000 --- a/src/or/eventdns.c +++ /dev/null @@ -1,3505 +0,0 @@ -/* READ THIS COMMENT BEFORE HACKING THIS FILE. - * - * This eventdns.c copy has diverged a bit from Libevent's version, and it's - * no longer easy to resynchronize them. Once Tor requires Libevent 2.0, we - * will just dump this file and use Libevent's evdns code. - * - * Therefore, you probably shouldn't make any change here without making it to - * Libevent as well: it's not good for the implementation to diverge even - * more. Also, we can't shouldn't wantonly the API here (since Libevent APIs - * can't change in ways that break user behavior). Also, we shouldn't bother - * with cosmetic changes: the whole module is slated for demolition, so - * there's no point dusting the linebreaks or re-painting the parser. - * - * (We can't just drop the Libevent 2.0 evdns implementation in here instead, - * since it depends pretty heavily on parts of Libevent 2.0.) - */ - -/* Async DNS Library - * Adam Langley <agl@imperialviolet.org> - * Public Domain code - * - * This software is Public Domain. To view a copy of the public domain dedication, - * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to - * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. - * - * I ask and expect, but do not require, that all derivative works contain an - * attribution similar to: - * Parts developed by Adam Langley <agl@imperialviolet.org> - * - * You may wish to replace the word "Parts" with something else depending on - * the amount of original code. - * - * (Derivative works does not include programs which link against, run or include - * the source verbatim in their source distributions) - * - * Version: 0.1b - */ - -#include "eventdns_tor.h" -#include "../common/util.h" -#include <sys/types.h> -/* #define NDEBUG */ - -#ifndef DNS_USE_CPU_CLOCK_FOR_ID -#ifndef DNS_USE_GETTIMEOFDAY_FOR_ID -#ifndef DNS_USE_OPENSSL_FOR_ID -#error Must configure at least one id generation method. -#error Please see the documentation. -#endif -#endif -#endif - -/* #define _POSIX_C_SOURCE 200507 */ -#define _GNU_SOURCE - -#ifdef DNS_USE_CPU_CLOCK_FOR_ID -#ifdef DNS_USE_OPENSSL_FOR_ID -#error Multiple id options selected -#endif -#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID -#error Multiple id options selected -#endif -#include <time.h> -#endif - -#ifdef DNS_USE_OPENSSL_FOR_ID -#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID -#error Multiple id options selected -#endif -#include <openssl/rand.h> -#endif - -#include <string.h> -#ifdef HAVE_FCNTL_H -#include <fcntl.h> -#endif -#ifdef HAVE_SYS_TIME_H -#include <sys/time.h> -#endif -#ifdef HAVE_STDINT_H -#include <stdint.h> -#endif -#include <stdlib.h> -#include <errno.h> -#include <assert.h> -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif -#ifdef HAVE_LIMITS_H -#include <limits.h> -#endif -#include <sys/stat.h> -#include <ctype.h> -#include <stdio.h> -#include <stdarg.h> - -#include "eventdns.h" - -#ifdef _WIN32 -#include <windows.h> -#include <winsock2.h> -#include <iphlpapi.h> -#else -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#endif - -#ifdef HAVE_NETINET_IN6_H -#include <netinet/in6.h> -#endif - -#ifdef _WIN32 -typedef int socklen_t; -#endif - -#define EVDNS_LOG_DEBUG 0 -#define EVDNS_LOG_WARN 1 - -#ifndef HOST_NAME_MAX -#define HOST_NAME_MAX 255 -#endif - -#ifndef NDEBUG -#include <stdio.h> -#endif - -/* for debugging possible memory leaks. */ -#define mm_malloc(x) tor_malloc(x) -#define mm_realloc(x,y) tor_realloc((x),(y)) -#define mm_free(x) tor_free(x) -#define mm_strdup(x) tor_strdup(x) -#define _mm_free(x) _tor_free(x) - -#undef MIN -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#if 0 -#ifdef __USE_ISOC99B -/* libevent doesn't work without this */ -typedef uint8_t u_char; -typedef unsigned int uint; -#endif -#endif -#include <event.h> - -#define u64 uint64_t -#define u32 uint32_t -#define u16 uint16_t -#define u8 uint8_t - -#define MAX_ADDRS 4 /* maximum number of addresses from a single packet */ -/* which we bother recording */ - -#define TYPE_A EVDNS_TYPE_A -#define TYPE_CNAME 5 -#define TYPE_PTR EVDNS_TYPE_PTR -#define TYPE_AAAA EVDNS_TYPE_AAAA - -#define CLASS_INET EVDNS_CLASS_INET - -#define CLEAR(x) do { memset((x), 0xF0, sizeof(*(x))); } while(0) - -struct evdns_request { - u8 *request; /* the dns packet data */ - unsigned int request_len; - int reissue_count; - int tx_count; /* the number of times that this packet has been sent */ - unsigned int request_type; /* TYPE_PTR or TYPE_A */ - void *user_pointer; /* the pointer given to us for this request */ - evdns_callback_type user_callback; - struct nameserver *ns; /* the server which we last sent it */ - - /* elements used by the searching code */ - int search_index; - struct search_state *search_state; - char *search_origname; /* needs to be mm_free()ed */ - int search_flags; - - /* these objects are kept in a circular list */ - struct evdns_request *next, *prev; - - struct event timeout_event; - - u16 trans_id; /* the transaction id */ - char request_appended; /* true if the request pointer is data which follows this struct */ - char transmit_me; /* needs to be transmitted */ -}; - -#ifndef HAVE_STRUCT_IN6_ADDR -struct in6_addr { - u8 s6_addr[16]; -}; -#endif - -struct reply { - unsigned int type; - unsigned int have_answer; - union { - struct { - u32 addrcount; - u32 addresses[MAX_ADDRS]; - } a; - struct { - u32 addrcount; - struct in6_addr addresses[MAX_ADDRS]; - } aaaa; - struct { - char name[HOST_NAME_MAX]; - } ptr; - } data; -}; - -struct nameserver { - int socket; /* a connected UDP socket */ - struct sockaddr_storage address; - int failed_times; /* number of times which we have given this server a chance */ - int timedout; /* number of times in a row a request has timed out */ - struct event event; - /* these objects are kept in a circular list */ - struct nameserver *next, *prev; - struct event timeout_event; /* used to keep the timeout for */ - /* when we next probe this server. */ - /* Valid if state == 0 */ - char state; /* zero if we think that this server is down */ - char choked; /* true if we have an EAGAIN from this server's socket */ - char write_waiting; /* true if we are waiting for EV_WRITE events */ -}; - -static struct evdns_request *req_head = NULL, *req_waiting_head = NULL; -static struct nameserver *server_head = NULL; - -/* Represents a local port where we're listening for DNS requests. Right now, */ -/* only UDP is supported. */ -struct evdns_server_port { - int socket; /* socket we use to read queries and write replies. */ - int refcnt; /* reference count. */ - char choked; /* Are we currently blocked from writing? */ - char closing; /* Are we trying to close this port, pending writes? */ - evdns_request_callback_fn_type user_callback; /* Fn to handle requests */ - void *user_data; /* Opaque pointer passed to user_callback */ - struct event event; /* Read/write event */ - /* circular list of replies that we want to write. */ - struct server_request *pending_replies; -}; - -/* Represents part of a reply being built. (That is, a single RR.) */ -struct server_reply_item { - struct server_reply_item *next; /* next item in sequence. */ - char *name; /* name part of the RR */ - u16 type : 16; /* The RR type */ - u16 class : 16; /* The RR class (usually CLASS_INET) */ - u32 ttl; /* The RR TTL */ - char is_name; /* True iff data is a label */ - u16 datalen; /* Length of data; -1 if data is a label */ - void *data; /* The contents of the RR */ -}; - -/* Represents a request that we've received as a DNS server, and holds */ -/* the components of the reply as we're constructing it. */ -struct server_request { - /* Pointers to the next and previous entries on the list of replies */ - /* that we're waiting to write. Only set if we have tried to respond */ - /* and gotten EAGAIN. */ - struct server_request *next_pending; - struct server_request *prev_pending; - - u16 trans_id; /* Transaction id. */ - struct evdns_server_port *port; /* Which port received this request on? */ - struct sockaddr_storage addr; /* Where to send the response */ - socklen_t addrlen; /* length of addr */ - - int n_answer; /* how many answer RRs have been set? */ - int n_authority; /* how many authority RRs have been set? */ - int n_additional; /* how many additional RRs have been set? */ - - struct server_reply_item *answer; /* linked list of answer RRs */ - struct server_reply_item *authority; /* linked list of authority RRs */ - struct server_reply_item *additional; /* linked list of additional RRs */ - - /* Constructed response. Only set once we're ready to send a reply. */ - /* Once this is set, the RR fields are cleared, and no more should be set. */ - char *response; - size_t response_len; - - /* Caller-visible fields: flags, questions. */ - struct evdns_server_request base; -}; - -/* helper macro */ -#define OFFSET_OF(st, member) ((off_t) (((char*)&((st*)0)->member)-(char*)0)) - -/* Given a pointer to an evdns_server_request, get the corresponding */ -/* server_request. */ -#define TO_SERVER_REQUEST(base_ptr) \ - ((struct server_request*) \ - (((char*)(base_ptr) - OFFSET_OF(struct server_request, base)))) - -/* The number of good nameservers that we have */ -static int global_good_nameservers = 0; - -/* inflight requests are contained in the req_head list */ -/* and are actually going out across the network */ -static int global_requests_inflight = 0; -/* requests which aren't inflight are in the waiting list */ -/* and are counted here */ -static int global_requests_waiting = 0; - -static int global_max_requests_inflight = 64; - -static struct timeval global_timeout = {5, 0}; /* 5 seconds */ -static int global_max_reissues = 1; /* a reissue occurs when we get some errors from the server */ -static int global_max_retransmits = 3; /* number of times we'll retransmit a request which timed out */ -/* number of timeouts in a row before we consider this server to be down */ -static int global_max_nameserver_timeout = 3; - -/* true iff we should use the 0x20 hack. */ -static int global_randomize_case = 1; - -/* These are the timeout values for nameservers. If we find a nameserver is down */ -/* we try to probe it at intervals as given below. Values are in seconds. */ -static const struct timeval global_nameserver_timeouts[] = {{10, 0}, {60, 0}, {300, 0}, {900, 0}, {3600, 0}}; -static const int global_nameserver_timeouts_length = (int)(sizeof(global_nameserver_timeouts)/sizeof(struct timeval)); - -static struct nameserver *nameserver_pick(void); -static void evdns_request_insert(struct evdns_request *req, struct evdns_request **head); -static void nameserver_ready_callback(int fd, short events, void *arg); -static int evdns_transmit(void); -static int evdns_request_transmit(struct evdns_request *req); -static void nameserver_send_probe(struct nameserver *const ns); -static void search_request_finished(struct evdns_request *const); -static int search_try_next(struct evdns_request *const req); -static int search_request_new(int type, const char *const name, int flags, evdns_callback_type user_callback, void *user_arg); -static void evdns_requests_pump_waiting_queue(void); -static u16 transaction_id_pick(void); -static struct evdns_request *request_new(int type, const char *name, int flags, evdns_callback_type callback, void *ptr); -static void request_submit(struct evdns_request *req); - -static int server_request_free(struct server_request *req); -static void server_request_free_answers(struct server_request *req); -static void server_port_free(struct evdns_server_port *port); -static void server_port_ready_callback(int fd, short events, void *arg); - -static int strtoint(const char *const str); - -#ifdef _WIN32 -static int -last_error(int sock) -{ - int optval, optvallen=sizeof(optval); - int err = WSAGetLastError(); - if (err == WSAEWOULDBLOCK && sock >= 0) { - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)&optval, - &optvallen)) - return err; - if (optval) - return optval; - } - return err; - -} -static int -error_is_eagain(int err) -{ - return err == EAGAIN || err == WSAEWOULDBLOCK; -} -#define inet_aton(c, addr) tor_inet_aton((c), (addr)) -#define CLOSE_SOCKET(x) closesocket(x) -#else -#define last_error(sock) (errno) -#define error_is_eagain(err) ((err) == EAGAIN) -#define CLOSE_SOCKET(x) close(x) -#endif - -#define ISSPACE(c) TOR_ISSPACE(c) -#define ISDIGIT(c) TOR_ISDIGIT(c) -#define ISALPHA(c) TOR_ISALPHA(c) -#define TOLOWER(c) TOR_TOLOWER(c) -#define TOUPPER(c) TOR_TOUPPER(c) - -#ifndef NDEBUG -static const char * -debug_ntoa(u32 address) -{ - static char buf[32]; - u32 a = ntohl(address); - snprintf(buf, sizeof(buf), "%d.%d.%d.%d", - (int)(u8)((a>>24)&0xff), - (int)(u8)((a>>16)&0xff), - (int)(u8)((a>>8 )&0xff), - (int)(u8)((a )&0xff)); - return buf; -} -static const char * -debug_ntop(const struct sockaddr *sa) -{ - if (sa->sa_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *) sa; - return debug_ntoa(sin->sin_addr.s_addr); - } - if (sa->sa_family == AF_INET6) { - /* Tor-specific. In libevent, add more check code. */ - static char buf[128]; - struct sockaddr_in6 *sin = (struct sockaddr_in6 *) sa; - tor_inet_ntop(AF_INET6, &sin->sin6_addr, buf, sizeof(buf)); - return buf; - } - return "<unknown>"; -} -#endif - -static evdns_debug_log_fn_type evdns_log_fn = NULL; - -void -evdns_set_log_fn(evdns_debug_log_fn_type fn) -{ - evdns_log_fn = fn; -} - -#ifdef __GNUC__ -#define EVDNS_LOG_CHECK __attribute__ ((format(printf, 2, 3))) -#else -#define EVDNS_LOG_CHECK -#endif - -static void _evdns_log(int warn, const char *fmt, ...) EVDNS_LOG_CHECK; -static void -_evdns_log(int warn, const char *fmt, ...) -{ - va_list args; - static char buf[512]; - if (!evdns_log_fn) - return; - va_start(args,fmt); -#ifdef _WIN32 - _vsnprintf(buf, sizeof(buf), fmt, args); -#else - vsnprintf(buf, sizeof(buf), fmt, args); -#endif - buf[sizeof(buf)-1] = '\0'; - evdns_log_fn(warn, buf); - va_end(args); -} - -#define log _evdns_log - -static int -sockaddr_eq(const struct sockaddr *sa1, const struct sockaddr *sa2, - int include_port) -{ - if (sa1->sa_family != sa2->sa_family) - return 0; - if (sa1->sa_family == AF_INET) { - const struct sockaddr_in *sin1, *sin2; - sin1 = (const struct sockaddr_in *)sa1; - sin2 = (const struct sockaddr_in *)sa2; - if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr) - return 0; - else if (include_port && sin1->sin_port != sin2->sin_port) - return 0; - else - return 1; - } -#ifdef AF_INET6 - if (sa1->sa_family == AF_INET6) { - const struct sockaddr_in6 *sin1, *sin2; - sin1 = (const struct sockaddr_in6 *)sa1; - sin2 = (const struct sockaddr_in6 *)sa2; - if (tor_memneq(sin1->sin6_addr.s6_addr, sin2->sin6_addr.s6_addr, 16)) - return 0; - else if (include_port && sin1->sin6_port != sin2->sin6_port) - return 0; - else - return 1; - } -#endif - return 1; -} - -#define add_timeout_event(s, to) \ - (event_add(&(s)->timeout_event, (to))) -#define del_timeout_event(s) \ - (event_del(&(s)->timeout_event)) - -/* This walks the list of inflight requests to find the */ -/* one with a matching transaction id. Returns NULL on */ -/* failure */ -static struct evdns_request * -request_find_from_trans_id(u16 trans_id) { - struct evdns_request *req = req_head, *const started_at = req_head; - - if (req) { - do { - if (req->trans_id == trans_id) return req; - req = req->next; - } while (req != started_at); - } - - return NULL; -} - -/* a libevent callback function which is called when a nameserver */ -/* has gone down and we want to test if it has came back to life yet */ -static void -nameserver_prod_callback(int fd, short events, void *arg) { - struct nameserver *const ns = (struct nameserver *) arg; - (void)fd; - (void)events; - - nameserver_send_probe(ns); -} - -/* a libevent callback which is called when a nameserver probe (to see if */ -/* it has come back to life) times out. We increment the count of failed_times */ -/* and wait longer to send the next probe packet. */ -static void -nameserver_probe_failed(struct nameserver *const ns) { - const struct timeval * timeout; - del_timeout_event(ns); - - if (ns->state == 1) { - /* This can happen if the nameserver acts in a way which makes us mark */ - /* it as bad and then starts sending good replies. */ - return; - } - - timeout = - &global_nameserver_timeouts[MIN(ns->failed_times, - global_nameserver_timeouts_length - 1)]; - ns->failed_times++; - - if (add_timeout_event(ns, (struct timeval *) timeout) < 0) { - log(EVDNS_LOG_WARN, - "Error from libevent when adding timer event for %s", - debug_ntop((struct sockaddr *)&ns->address)); - /* ???? Do more? */ - } -} - -/* called when a nameserver has been deemed to have failed. For example, too */ -/* many packets have timed out etc */ -static void -nameserver_failed(struct nameserver *const ns, const char *msg) { - struct evdns_request *req, *started_at; - /* if this nameserver has already been marked as failed */ - /* then don't do anything */ - if (!ns->state) return; - - log(EVDNS_LOG_WARN, "Nameserver %s has failed: %s", - debug_ntop((struct sockaddr *)&ns->address), msg); - global_good_nameservers--; - assert(global_good_nameservers >= 0); - if (global_good_nameservers == 0) { - log(EVDNS_LOG_WARN, "All nameservers have failed"); - } - - ns->state = 0; - ns->failed_times = 1; - - if (add_timeout_event(ns, (struct timeval *) &global_nameserver_timeouts[0]) < 0) { - log(EVDNS_LOG_WARN, - "Error from libevent when adding timer event for %s", - debug_ntop((struct sockaddr *)&ns->address)); - /* ???? Do more? */ - } - - /* walk the list of inflight requests to see if any can be reassigned to */ - /* a different server. Requests in the waiting queue don't have a */ - /* nameserver assigned yet */ - - /* if we don't have *any* good nameservers then there's no point */ - /* trying to reassign requests to one */ - if (!global_good_nameservers) return; - - req = req_head; - started_at = req_head; - if (req) { - do { - if (req->tx_count == 0 && req->ns == ns) { - /* still waiting to go out, can be moved */ - /* to another server */ - req->ns = nameserver_pick(); - } - req = req->next; - } while (req != started_at); - } -} - -static void -nameserver_up(struct nameserver *const ns) { - if (ns->state) return; - log(EVDNS_LOG_WARN, "Nameserver %s is back up", - debug_ntop((struct sockaddr *)&ns->address)); - del_timeout_event(ns); - ns->state = 1; - ns->failed_times = 0; - ns->timedout = 0; - global_good_nameservers++; -} - -static void -request_trans_id_set(struct evdns_request *const req, const u16 trans_id) { - req->trans_id = trans_id; - *((u16 *) req->request) = htons(trans_id); -} - -/* Called to remove a request from a list and dealloc it. */ -/* head is a pointer to the head of the list it should be */ -/* removed from or NULL if the request isn't in a list. */ -static void -request_finished(struct evdns_request *const req, struct evdns_request **head) { - if (head) { - if (req->next == req) { - /* only item in the list */ - *head = NULL; - } else { - req->next->prev = req->prev; - req->prev->next = req->next; - if (*head == req) *head = req->next; - } - } - - log(EVDNS_LOG_DEBUG, "Removing timeout for request %lx", - (unsigned long) req); - del_timeout_event(req); - - search_request_finished(req); - global_requests_inflight--; - - if (!req->request_appended) { - /* need to free the request data on it's own */ - mm_free(req->request); - } else { - /* the request data is appended onto the header */ - /* so everything gets mm_free()ed when we: */ - } - - CLEAR(req); - _mm_free(req); - - evdns_requests_pump_waiting_queue(); -} - -/* This is called when a server returns a funny error code. */ -/* We try the request again with another server. */ -/* */ -/* return: */ -/* 0 ok */ -/* 1 failed/reissue is pointless */ -static int -request_reissue(struct evdns_request *req) { - const struct nameserver *const last_ns = req->ns; - /* the last nameserver should have been marked as failing */ - /* by the caller of this function, therefore pick will try */ - /* not to return it */ - req->ns = nameserver_pick(); - if (req->ns == last_ns) { - /* ... but pick did return it */ - /* not a lot of point in trying again with the */ - /* same server */ - return 1; - } - - req->reissue_count++; - req->tx_count = 0; - req->transmit_me = 1; - - return 0; -} - -/* this function looks for space on the inflight queue and promotes */ -/* requests from the waiting queue if it can. */ -static void -evdns_requests_pump_waiting_queue(void) { - while (global_requests_inflight < global_max_requests_inflight && - global_requests_waiting) { - struct evdns_request *req; - /* move a request from the waiting queue to the inflight queue */ - assert(req_waiting_head); - if (req_waiting_head->next == req_waiting_head) { - /* only one item in the queue */ - req = req_waiting_head; - req_waiting_head = NULL; - } else { - req = req_waiting_head; - req->next->prev = req->prev; - req->prev->next = req->next; - req_waiting_head = req->next; - } - - global_requests_waiting--; - global_requests_inflight++; - - req->ns = nameserver_pick(); - request_trans_id_set(req, transaction_id_pick()); - - evdns_request_insert(req, &req_head); - evdns_request_transmit(req); - evdns_transmit(); - } -} - -static void -reply_callback(struct evdns_request *const req, u32 ttl, u32 err, struct reply *reply) { - switch (req->request_type) { - case TYPE_A: - if (reply) - req->user_callback(DNS_ERR_NONE, DNS_IPv4_A, - reply->data.a.addrcount, ttl, - reply->data.a.addresses, - req->user_pointer); - else - req->user_callback(err, 0, 0, 0, NULL, req->user_pointer); - return; - case TYPE_PTR: - if (reply) { - char *name = reply->data.ptr.name; - req->user_callback(DNS_ERR_NONE, DNS_PTR, 1, ttl, - &name, req->user_pointer); - } else { - req->user_callback(err, 0, 0, 0, NULL, - req->user_pointer); - } - return; - case TYPE_AAAA: - if (reply) - req->user_callback(DNS_ERR_NONE, DNS_IPv6_AAAA, - reply->data.aaaa.addrcount, ttl, - reply->data.aaaa.addresses, - req->user_pointer); - else - req->user_callback(err, 0, 0, 0, NULL, req->user_pointer); - return; - } - assert(0); -} - -/* this processes a parsed reply packet */ -static void -reply_handle(struct evdns_request *const req, u16 flags, u32 ttl, struct reply *reply) { - int error; - static const int error_codes[] = {DNS_ERR_FORMAT, DNS_ERR_SERVERFAILED, DNS_ERR_NOTEXIST, DNS_ERR_NOTIMPL, DNS_ERR_REFUSED}; - - if (flags & 0x020f || !reply || !reply->have_answer) { - /* there was an error */ - if (flags & 0x0200) { - error = DNS_ERR_TRUNCATED; - } else { - u16 error_code = (flags & 0x000f) - 1; - if (error_code > 4) { - error = DNS_ERR_UNKNOWN; - } else { - error = error_codes[error_code]; - } - } - - switch(error) { - case DNS_ERR_NOTIMPL: - case DNS_ERR_REFUSED: - /* we regard these errors as marking a bad nameserver */ - if (req->reissue_count < global_max_reissues) { - char msg[64]; - snprintf(msg, sizeof(msg), "Bad response %d (%s)", - error, evdns_err_to_string(error)); - nameserver_failed(req->ns, msg); - if (!request_reissue(req)) return; - } - break; - case DNS_ERR_SERVERFAILED: - /* rcode 2 (servfailed) sometimes means "we are broken" and - * sometimes (with some binds) means "that request was very - * confusing." Treat this as a timeout, not a failure. - */ - /*XXXX refactor the parts of */ - log(EVDNS_LOG_DEBUG, "Got a SERVERFAILED from nameserver %s; " - "will allow the request to time out.", - debug_ntop((struct sockaddr *)&req->ns->address)); - break; - default: - /* we got a good reply from the nameserver */ - nameserver_up(req->ns); - } - - if (req->search_state && req->request_type != TYPE_PTR) { - /* if we have a list of domains to search in, try the next one */ - if (!search_try_next(req)) { - /* a new request was issued so this request is finished and */ - /* the user callback will be made when that request (or a */ - /* child of it) finishes. */ - request_finished(req, &req_head); - return; - } - } - - /* all else failed. Pass the failure up */ - reply_callback(req, 0, error, NULL); - request_finished(req, &req_head); - } else { - /* all ok, tell the user */ - reply_callback(req, ttl, 0, reply); - nameserver_up(req->ns); - request_finished(req, &req_head); - } -} - -static INLINE int -name_parse(u8 *packet, int length, int *idx, char *name_out, size_t name_out_len) { - int name_end = -1; - int j = *idx; - int ptr_count = 0; -#define GET32(x) do { if (j + 4 > length) goto err; memcpy(&_t32, packet + j, 4); j += 4; x = ntohl(_t32); } while(0) -#define GET16(x) do { if (j + 2 > length) goto err; memcpy(&_t, packet + j, 2); j += 2; x = ntohs(_t); } while(0) -#define GET8(x) do { if (j >= length) goto err; x = packet[j++]; } while(0) - - char *cp = name_out; - const char *const end = name_out + name_out_len; - - /* Normally, names are a series of length prefixed strings terminated */ - /* with a length of 0 (the lengths are u8's < 63). */ - /* However, the length can start with a pair of 1 bits and that */ - /* means that the next 14 bits are a pointer within the current */ - /* packet. */ - - for(;;) { - u8 label_len; - if (j >= length) return -1; - GET8(label_len); - if (!label_len) break; - if (label_len & 0xc0) { - u8 ptr_low; - GET8(ptr_low); - if (name_end < 0) name_end = j; - j = (((int)label_len & 0x3f) << 8) + ptr_low; - /* Make sure that the target offset is in-bounds. */ - if (j < 0 || j >= length) return -1; - /* If we've jumped more times than there are characters in the - * message, we must have a loop. */ - if (++ptr_count > length) return -1; - continue; - } - if (label_len > 63) return -1; - if (cp != name_out) { - if (cp + 1 >= end) return -1; - *cp++ = '.'; - } - if (cp + label_len >= end) return -1; - memcpy(cp, packet + j, label_len); - cp += label_len; - j += label_len; - } - if (cp >= end) return -1; - *cp = '\0'; - if (name_end < 0) - *idx = j; - else - *idx = name_end; - return 0; - err: - return -1; -} - -/* parses a raw reply from a nameserver. */ -static int -reply_parse(u8 *packet, int length) { - int j = 0; /* index into packet */ - int k; - u16 _t; /* used by the macros */ - u32 _t32; /* used by the macros */ - char tmp_name[256], cmp_name[256]; /* used by the macros */ - - u16 trans_id, questions, answers, authority, additional, datalength; - u16 flags = 0; - u32 ttl, ttl_r = 0xffffffff; - struct reply reply; - struct evdns_request *req = NULL; - unsigned int i; - int name_matches = 0; - - GET16(trans_id); - GET16(flags); - GET16(questions); - GET16(answers); - GET16(authority); - GET16(additional); - (void) authority; /* suppress "unused variable" warnings. */ - (void) additional; /* suppress "unused variable" warnings. */ - - req = request_find_from_trans_id(trans_id); - /* if no request, can't do anything. */ - if (!req) return -1; - - memset(&reply, 0, sizeof(reply)); - - /* If it's not an answer, it doesn't go with any of our requests. */ - if (!(flags & 0x8000)) return -1; /* must be an answer */ - if (flags & 0x020f) { - /* there was an error */ - goto err; - } - /* if (!answers) return; */ /* must have an answer of some form */ - - /* This macro skips a name in the DNS reply. */ -#define GET_NAME \ - do { tmp_name[0] = '\0'; \ - if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\ - goto err; \ - } while(0) -#define TEST_NAME \ - do { tmp_name[0] = '\0'; \ - cmp_name[0] = '\0'; \ - k = j; \ - if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\ - goto err; \ - if (name_parse(req->request, req->request_len, &k, cmp_name, sizeof(cmp_name))<0) \ - goto err; \ - if (global_randomize_case) { \ - if (strcmp(tmp_name, cmp_name) == 0) \ - name_matches = 1; /* we ignore mismatching names */ \ - } else { \ - if (strcasecmp(tmp_name, cmp_name) == 0) \ - name_matches = 1; \ - } \ - } while(0) - - reply.type = req->request_type; - - /* skip over each question in the reply */ - for (i = 0; i < questions; ++i) { - /* the question looks like - * <label:name><u16:type><u16:class> - */ - TEST_NAME; - j += 4; - if (j >= length) goto err; - } - - if (!name_matches) - goto err; - - /* now we have the answer section which looks like - * <label:name><u16:type><u16:class><u32:ttl><u16:len><data...> - */ - - for (i = 0; i < answers; ++i) { - u16 type, class; - - GET_NAME; - GET16(type); - GET16(class); - GET32(ttl); - GET16(datalength); - - if (type == TYPE_A && class == CLASS_INET) { - int addrcount, addrtocopy; - if (req->request_type != TYPE_A) { - j += datalength; continue; - } - if ((datalength & 3) != 0) /* not an even number of As. */ - goto err; - addrcount = datalength >> 2; - addrtocopy = MIN(MAX_ADDRS - reply.data.a.addrcount, (unsigned)addrcount); - - ttl_r = MIN(ttl_r, ttl); - /* we only bother with the first four addresses. */ - if (j + 4*addrtocopy > length) goto err; - memcpy(&reply.data.a.addresses[reply.data.a.addrcount], - packet + j, 4*addrtocopy); - reply.data.a.addrcount += addrtocopy; - reply.have_answer = 1; - if (reply.data.a.addrcount == MAX_ADDRS) break; - j += 4*addrtocopy; - } else if (type == TYPE_PTR && class == CLASS_INET) { - if (req->request_type != TYPE_PTR) { - j += datalength; continue; - } - GET_NAME; - strlcpy(reply.data.ptr.name, tmp_name, - sizeof(reply.data.ptr.name)); - ttl_r = MIN(ttl_r, ttl); - reply.have_answer = 1; - break; - } else if (type == TYPE_AAAA && class == CLASS_INET) { - int addrcount, addrtocopy; - if (req->request_type != TYPE_AAAA) { - j += datalength; continue; - } - if ((datalength & 15) != 0) /* not an even number of AAAAs. */ - goto err; - addrcount = datalength >> 4; /* each address is 16 bytes long */ - addrtocopy = MIN(MAX_ADDRS - reply.data.aaaa.addrcount, (unsigned)addrcount); - ttl_r = MIN(ttl_r, ttl); - - /* we only bother with the first four addresses. */ - if (j + 16*addrtocopy > length) goto err; - memcpy(&reply.data.aaaa.addresses[reply.data.aaaa.addrcount], - packet + j, 16*addrtocopy); - reply.data.aaaa.addrcount += addrtocopy; - reply.have_answer = 1; - if (reply.data.aaaa.addrcount == MAX_ADDRS) break; - j += 16*addrtocopy; - } else { - /* skip over any other type of resource */ - j += datalength; - } - } - - reply_handle(req, flags, ttl_r, &reply); - return 0; - err: - if (req) - reply_handle(req, flags, 0, NULL); - return -1; -} - -/* Parse a raw request (packet,length) sent to a nameserver port (port) from */ -/* a DNS client (addr,addrlen), and if it's well-formed, call the corresponding */ -/* callback. */ -static int -request_parse(u8 *packet, ssize_t length, struct evdns_server_port *port, struct sockaddr *addr, socklen_t addrlen) -{ - int j = 0; /* index into packet */ - u16 _t; /* used by the macros */ - char tmp_name[256]; /* used by the macros */ - - int i; - u16 trans_id, flags, questions, answers, authority, additional; - struct server_request *server_req = NULL; - - /* Get the header fields */ - GET16(trans_id); - GET16(flags); - GET16(questions); - GET16(answers); - GET16(authority); - GET16(additional); - (void)additional; - (void)authority; - (void)answers; - - if (flags & 0x8000) return -1; /* Must not be an answer. */ - flags &= 0x0110; /* Only RD and CD get preserved. */ - - if (length > INT_MAX) - return -1; - - server_req = mm_malloc(sizeof(struct server_request)); - if (server_req == NULL) return -1; - memset(server_req, 0, sizeof(struct server_request)); - - server_req->trans_id = trans_id; - memcpy(&server_req->addr, addr, addrlen); - server_req->addrlen = addrlen; - - server_req->base.flags = flags; - server_req->base.nquestions = 0; - server_req->base.questions = mm_malloc(sizeof(struct evdns_server_question *) * questions); - if (server_req->base.questions == NULL) - goto err; - - for (i = 0; i < questions; ++i) { - u16 type, class; - struct evdns_server_question *q; - size_t namelen; - if (name_parse(packet, (int)length, &j, tmp_name, sizeof(tmp_name))<0) - goto err; - GET16(type); - GET16(class); - namelen = strlen(tmp_name); - q = mm_malloc(sizeof(struct evdns_server_question) + namelen); - if (!q) - goto err; - q->type = type; - q->dns_question_class = class; - memcpy(q->name, tmp_name, namelen+1); - server_req->base.questions[server_req->base.nquestions++] = q; - } - - /* Ignore answers, authority, and additional. */ - - server_req->port = port; - port->refcnt++; - - /* Only standard queries are supported. */ - if (flags & 0x7800) { - evdns_server_request_respond(&(server_req->base), DNS_ERR_NOTIMPL); - return -1; - } - - port->user_callback(&(server_req->base), port->user_data); - - return 0; -err: - if (server_req) { - if (server_req->base.questions) { - for (i = 0; i < server_req->base.nquestions; ++i) - mm_free(server_req->base.questions[i]); - mm_free(server_req->base.questions); - } - CLEAR(server_req); - mm_free(server_req); - } - return -1; - -#undef SKIP_NAME -#undef GET32 -#undef GET16 -#undef GET8 -} - -static uint16_t -default_transaction_id_fn(void) -{ - u16 trans_id; -#ifdef DNS_USE_CPU_CLOCK_FOR_ID - struct timespec ts; -#ifdef CLOCK_MONOTONIC - if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) -#else - if (clock_gettime(CLOCK_REALTIME, &ts) == -1) -#endif - event_err(1, "clock_gettime"); - trans_id = ts.tv_nsec & 0xffff; -#endif - -#ifdef DNS_USE_GETTIMEOFDAY_FOR_ID - struct timeval tv; - gettimeofday(&tv, NULL); - trans_id = tv.tv_usec & 0xffff; -#endif - -#ifdef DNS_USE_OPENSSL_FOR_ID - if (RAND_pseudo_bytes((u8 *) &trans_id, 2) == -1) { - /* in the case that the RAND call fails we back */ - /* down to using gettimeofday. */ - /* - struct timeval tv; - gettimeofday(&tv, NULL); - trans_id = tv.tv_usec & 0xffff; - */ - abort(); - } -#endif - return (unsigned short) trans_id; -} - -static uint16_t (*trans_id_function)(void) = default_transaction_id_fn; - -static void -default_random_bytes_fn(char *buf, size_t n) -{ - unsigned i; - for (i = 0; i < n; i += 2) { - u16 tid = trans_id_function(); - buf[i] = (tid >> 8) & 0xff; - if (i+1<n) - buf[i+1] = tid & 0xff; - } -} - -static void (*rand_bytes_function)(char *buf, size_t n) = - default_random_bytes_fn; - -static u16 -trans_id_from_random_bytes_fn(void) -{ - u16 tid; - rand_bytes_function((char*) &tid, sizeof(tid)); - return tid; -} - -void -evdns_set_transaction_id_fn(uint16_t (*fn)(void)) -{ - if (fn) - trans_id_function = fn; - else - trans_id_function = default_transaction_id_fn; - rand_bytes_function = default_random_bytes_fn; -} - -void -evdns_set_random_bytes_fn(void (*fn)(char *, size_t)) -{ - rand_bytes_function = fn; - trans_id_function = trans_id_from_random_bytes_fn; -} - -/* Try to choose a strong transaction id which isn't already in flight */ -static u16 -transaction_id_pick(void) { - for (;;) { - const struct evdns_request *req = req_head, *started_at; - u16 trans_id = trans_id_function(); - - if (trans_id == 0xffff) continue; - /* now check to see if that id is already inflight */ - req = started_at = req_head; - if (req) { - do { - if (req->trans_id == trans_id) break; - req = req->next; - } while (req != started_at); - } - /* we didn't find it, so this is a good id */ - if (req == started_at) return trans_id; - } -} - -/* choose a namesever to use. This function will try to ignore */ -/* nameservers which we think are down and load balance across the rest */ -/* by updating the server_head global each time. */ -static struct nameserver * -nameserver_pick(void) { - struct nameserver *started_at = server_head, *picked; - if (!server_head) return NULL; - - /* if we don't have any good nameservers then there's no */ - /* point in trying to find one. */ - if (!global_good_nameservers) { - server_head = server_head->next; - return server_head; - } - - /* remember that nameservers are in a circular list */ - for (;;) { - if (server_head->state) { - /* we think this server is currently good */ - picked = server_head; - server_head = server_head->next; - return picked; - } - - server_head = server_head->next; - if (server_head == started_at) { - /* all the nameservers seem to be down */ - /* so we just return this one and hope for the */ - /* best */ - assert(global_good_nameservers == 0); - picked = server_head; - server_head = server_head->next; - return picked; - } - } -} - -/* this is called when a namesever socket is ready for reading */ -static void -nameserver_read(struct nameserver *ns) { - struct sockaddr_storage ss; - struct sockaddr *sa = (struct sockaddr *) &ss; - socklen_t addrlen = sizeof(ss); - u8 packet[1500]; - - for (;;) { - const int r = - (int)recvfrom(ns->socket, (void*)packet, - (socklen_t)sizeof(packet), 0, - sa, &addrlen); - if (r < 0) { - int err = last_error(ns->socket); - if (error_is_eagain(err)) return; - nameserver_failed(ns, tor_socket_strerror(err)); - return; - } - /* XXX Match port too? */ - if (!sockaddr_eq(sa, (struct sockaddr*)&ns->address, 0)) { - log(EVDNS_LOG_WARN, - "Address mismatch on received DNS packet. Address was %s", - debug_ntop(sa)); - return; - } - ns->timedout = 0; - reply_parse(packet, r); - } -} - -/* Read a packet from a DNS client on a server port s, parse it, and */ -/* act accordingly. */ -static void -server_port_read(struct evdns_server_port *s) { - u8 packet[1500]; - struct sockaddr_storage addr; - socklen_t addrlen; - ssize_t r; - - for (;;) { - addrlen = (socklen_t)sizeof(struct sockaddr_storage); - r = recvfrom(s->socket, (void*)packet, sizeof(packet), 0, - (struct sockaddr*) &addr, &addrlen); - if (r < 0) { - int err = last_error(s->socket); - if (error_is_eagain(err)) return; - log(EVDNS_LOG_WARN, "Error %s (%d) while reading request.", - tor_socket_strerror(err), err); - return; - } - request_parse(packet, r, s, (struct sockaddr*) &addr, addrlen); - } -} - -/* Try to write all pending replies on a given DNS server port. */ -static void -server_port_flush(struct evdns_server_port *port) -{ - struct server_request *req = port->pending_replies; - while (req) { - ssize_t r = sendto(port->socket, req->response, req->response_len, 0, - (struct sockaddr*) &req->addr, (socklen_t)req->addrlen); - if (r < 0) { - int err = last_error(port->socket); - if (error_is_eagain(err)) - return; - log(EVDNS_LOG_WARN, "Error %s (%d) while writing response to port; dropping", tor_socket_strerror(err), err); - } - if (server_request_free(req)) { - /* we released the last reference to req->port. */ - return; - } else { - assert(port->pending_replies != req); - req = port->pending_replies; - } - } - - /* We have no more pending requests; stop listening for 'writeable' events. */ - (void) event_del(&port->event); - CLEAR(&port->event); - event_set(&port->event, port->socket, EV_READ | EV_PERSIST, - server_port_ready_callback, port); - if (event_add(&port->event, NULL) < 0) { - log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server."); - /* ???? Do more? */ - } -} - -/* set if we are waiting for the ability to write to this server. */ -/* if waiting is true then we ask libevent for EV_WRITE events, otherwise */ -/* we stop these events. */ -static void -nameserver_write_waiting(struct nameserver *ns, char waiting) { - if (ns->write_waiting == waiting) return; - - ns->write_waiting = waiting; - (void) event_del(&ns->event); - CLEAR(&ns->event); - event_set(&ns->event, ns->socket, EV_READ | (waiting ? EV_WRITE : 0) | EV_PERSIST, - nameserver_ready_callback, ns); - if (event_add(&ns->event, NULL) < 0) { - log(EVDNS_LOG_WARN, "Error from libevent when adding event for %s", - debug_ntop((struct sockaddr *)&ns->address)); - /* ???? Do more? */ - } -} - -/* a callback function. Called by libevent when the kernel says that */ -/* a nameserver socket is ready for writing or reading */ -static void -nameserver_ready_callback(int fd, short events, void *arg) { - struct nameserver *ns = (struct nameserver *) arg; - (void)fd; - - if (events & EV_WRITE) { - ns->choked = 0; - if (!evdns_transmit()) { - nameserver_write_waiting(ns, 0); - } - } - if (events & EV_READ) { - nameserver_read(ns); - } -} - -/* a callback function. Called by libevent when the kernel says that */ -/* a server socket is ready for writing or reading. */ -static void -server_port_ready_callback(int fd, short events, void *arg) { - struct evdns_server_port *port = (struct evdns_server_port *) arg; - (void) fd; - - if (events & EV_WRITE) { - port->choked = 0; - server_port_flush(port); - } - if (events & EV_READ) { - server_port_read(port); - } -} - -/* This is an inefficient representation; only use it via the dnslabel_table_* - * functions, so that is can be safely replaced with something smarter later. */ -#define MAX_LABELS 128 -/* Structures used to implement name compression */ -struct dnslabel_entry { char *v; off_t pos; }; -struct dnslabel_table { - int n_labels; /* number of current entries */ - /* map from name to position in message */ - struct dnslabel_entry labels[MAX_LABELS]; -}; - -/* Initialize dnslabel_table. */ -static void -dnslabel_table_init(struct dnslabel_table *table) -{ - table->n_labels = 0; -} - -/* Free all storage held by table, but not the table itself. */ -static void -dnslabel_clear(struct dnslabel_table *table) -{ - int i; - for (i = 0; i < table->n_labels; ++i) - mm_free(table->labels[i].v); - table->n_labels = 0; -} - -/* return the position of the label in the current message, or -1 if the label */ -/* hasn't been used yet. */ -static int -dnslabel_table_get_pos(const struct dnslabel_table *table, const char *label) -{ - int i; - for (i = 0; i < table->n_labels; ++i) { - if (!strcmp(label, table->labels[i].v)) { - off_t pos = table->labels[i].pos; - if (pos > 65535) - return -1; - return (int)pos; - } - } - return -1; -} - -/* remember that we've used the label at position pos */ -static int -dnslabel_table_add(struct dnslabel_table *table, const char *label, off_t pos) -{ - char *v; - int p; - if (table->n_labels == MAX_LABELS) - return (-1); - v = mm_strdup(label); - if (v == NULL) - return (-1); - p = table->n_labels++; - table->labels[p].v = v; - table->labels[p].pos = pos; - - return (0); -} - -/* Converts a string to a length-prefixed set of DNS labels, starting */ -/* at buf[j]. name and buf must not overlap. name_len should be the length */ -/* of name. table is optional, and is used for compression. */ -/* */ -/* Input: abc.def */ -/* Output: <3>abc<3>def<0> */ -/* */ -/* Returns the first index after the encoded name, or negative on error. */ -/* -1 label was > 63 bytes */ -/* -2 name too long to fit in buffer. */ -/* */ -static off_t -dnsname_to_labels(u8 *const buf, size_t buf_len, off_t j, - const char *name, const size_t name_len, - struct dnslabel_table *table) { - const char *end = name + name_len; - int ref = 0; - u16 _t; - -#define APPEND16(x) do { \ - if (j + 2 > (off_t)buf_len) \ - goto overflow; \ - _t = htons(x); \ - memcpy(buf + j, &_t, 2); \ - j += 2; \ - } while (0) -#define APPEND32(x) do { \ - if (j + 4 > (off_t)buf_len) \ - goto overflow; \ - _t32 = htonl(x); \ - memcpy(buf + j, &_t32, 4); \ - j += 4; \ - } while (0) - - if (name_len > 255) return -2; - - for (;;) { - const char *const start = name; - if (table && (ref = dnslabel_table_get_pos(table, name)) >= 0) { - APPEND16(ref | 0xc000); - return j; - } - name = strchr(name, '.'); - if (!name) { - const size_t label_len = end - start; - if (label_len > 63) return -1; - if ((size_t)(j+label_len+1) > buf_len) return -2; - if (table) dnslabel_table_add(table, start, j); - buf[j++] = (uint8_t)label_len; - - memcpy(buf + j, start, label_len); - j += end - start; - break; - } else { - /* append length of the label. */ - const size_t label_len = name - start; - if (label_len > 63) return -1; - if ((size_t)(j+label_len+1) > buf_len) return -2; - if (table) dnslabel_table_add(table, start, j); - buf[j++] = (uint8_t)label_len; - - memcpy(buf + j, start, name - start); - j += name - start; - /* hop over the '.' */ - name++; - } - } - - /* the labels must be terminated by a 0. */ - /* It's possible that the name ended in a . */ - /* in which case the zero is already there */ - if (!j || buf[j-1]) buf[j++] = 0; - return j; - overflow: - return (-2); -} - -/* Finds the length of a dns request for a DNS name of the given */ -/* length. The actual request may be smaller than the value returned */ -/* here */ -static size_t -evdns_request_len(const size_t name_len) { - return 96 + /* length of the DNS standard header */ - name_len + 2 + - 4; /* space for the resource type */ -} - -/* build a dns request packet into buf. buf should be at least as long */ -/* as evdns_request_len told you it should be. */ -/* */ -/* Returns the amount of space used. Negative on error. */ -static int -evdns_request_data_build(const char *const name, const size_t name_len, - const u16 trans_id, const u16 type, const u16 class, - u8 *const buf, size_t buf_len) { - off_t j = 0; /* current offset into buf */ - u16 _t; /* used by the macros */ - - APPEND16(trans_id); - APPEND16(0x0100); /* standard query, recusion needed */ - APPEND16(1); /* one question */ - APPEND16(0); /* no answers */ - APPEND16(0); /* no authority */ - APPEND16(0); /* no additional */ - - j = dnsname_to_labels(buf, buf_len, j, name, name_len, NULL); - if (j < 0) { - return (int)j; - } - - APPEND16(type); - APPEND16(class); - - return (int)j; - overflow: - return (-1); -} - -/* exported function */ -struct evdns_server_port * -evdns_add_server_port(tor_socket_t socket, int is_tcp, evdns_request_callback_fn_type cb, void *user_data) -{ - struct evdns_server_port *port; - if (!(port = mm_malloc(sizeof(struct evdns_server_port)))) - return NULL; - memset(port, 0, sizeof(struct evdns_server_port)); - - assert(!is_tcp); /* TCP sockets not yet implemented */ - port->socket = socket; - port->refcnt = 1; - port->choked = 0; - port->closing = 0; - port->user_callback = cb; - port->user_data = user_data; - port->pending_replies = NULL; - - event_set(&port->event, port->socket, EV_READ | EV_PERSIST, - server_port_ready_callback, port); - if (event_add(&port->event, NULL)<0) { - mm_free(port); - return NULL; - } - return port; -} - -/* exported function */ -void -evdns_close_server_port(struct evdns_server_port *port) -{ - port->closing = 1; - if (--port->refcnt == 0) - server_port_free(port); -} - -/* exported function */ -int -evdns_server_request_add_reply(struct evdns_server_request *_req, int section, const char *name, int type, int class, int ttl, int datalen, int is_name, const char *data) -{ - struct server_request *req = TO_SERVER_REQUEST(_req); - struct server_reply_item **itemp, *item; - int *countp; - - if (req->response) /* have we already answered? */ - return (-1); - - switch (section) { - case EVDNS_ANSWER_SECTION: - itemp = &req->answer; - countp = &req->n_answer; - break; - case EVDNS_AUTHORITY_SECTION: - itemp = &req->authority; - countp = &req->n_authority; - break; - case EVDNS_ADDITIONAL_SECTION: - itemp = &req->additional; - countp = &req->n_additional; - break; - default: - return (-1); - } - while (*itemp) { - itemp = &((*itemp)->next); - } - item = mm_malloc(sizeof(struct server_reply_item)); - if (!item) - return -1; - CLEAR(item); - item->next = NULL; - if (!(item->name = mm_strdup(name))) { - CLEAR(item); - mm_free(item); - return -1; - } - item->type = type; - item->class = class; - item->ttl = ttl; - item->is_name = is_name != 0; - item->datalen = 0; - item->data = NULL; - if (data) { - if (item->is_name) { - if (!(item->data = mm_strdup(data))) { - mm_free(item->name); - CLEAR(item); - mm_free(item); - return -1; - } - item->datalen = (u16)-1; - } else { - if (!(item->data = mm_malloc(datalen))) { - mm_free(item->name); - CLEAR(item); - mm_free(item); - return -1; - } - item->datalen = datalen; - memcpy(item->data, data, datalen); - } - } - - *itemp = item; - ++(*countp); - return 0; -} - -/* exported function */ -int -evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl) -{ - return evdns_server_request_add_reply( - req, EVDNS_ANSWER_SECTION, name, TYPE_A, CLASS_INET, - ttl, n*4, 0, addrs); -} - -/* exported function */ -int -evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl) -{ - return evdns_server_request_add_reply( - req, EVDNS_ANSWER_SECTION, name, TYPE_AAAA, CLASS_INET, - ttl, n*16, 0, addrs); -} - -/* exported function */ -int -evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, const char *inaddr_name, const char *hostname, int ttl) -{ - u32 a; - char buf[32]; - assert(in || inaddr_name); - assert(!(in && inaddr_name)); - if (in) { - a = ntohl(in->s_addr); - snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa", - (int)(u8)((a )&0xff), - (int)(u8)((a>>8 )&0xff), - (int)(u8)((a>>16)&0xff), - (int)(u8)((a>>24)&0xff)); - inaddr_name = buf; - } - return evdns_server_request_add_reply( - req, EVDNS_ANSWER_SECTION, inaddr_name, TYPE_PTR, CLASS_INET, - ttl, -1, 1, hostname); -} - -/* exported function */ -int -evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, const char *cname, int ttl) -{ - return evdns_server_request_add_reply( - req, EVDNS_ANSWER_SECTION, name, TYPE_CNAME, CLASS_INET, - ttl, -1, 1, cname); -} - - -static int -evdns_server_request_format_response(struct server_request *req, int err) -{ - unsigned char buf[1500]; - size_t buf_len = sizeof(buf); - off_t j = 0, r; - u16 _t; - u32 _t32; - int i; - u16 flags; - struct dnslabel_table table; - - if (err < 0 || err > 15) return -1; - - /* Set response bit and error code; copy OPCODE and RD fields from - * question; copy RA and AA if set by caller. */ - flags = req->base.flags; - flags |= (0x8000 | err); - - dnslabel_table_init(&table); - APPEND16(req->trans_id); - APPEND16(flags); - APPEND16(req->base.nquestions); - APPEND16(req->n_answer); - APPEND16(req->n_authority); - APPEND16(req->n_additional); - - /* Add questions. */ - for (i=0; i < req->base.nquestions; ++i) { - const char *s = req->base.questions[i]->name; - j = dnsname_to_labels(buf, buf_len, j, s, strlen(s), &table); - if (j < 0) { - dnslabel_clear(&table); - return (int) j; - } - APPEND16(req->base.questions[i]->type); - APPEND16(req->base.questions[i]->dns_question_class); - } - - /* Add answer, authority, and additional sections. */ - for (i=0; i<3; ++i) { - struct server_reply_item *item; - if (i==0) - item = req->answer; - else if (i==1) - item = req->authority; - else - item = req->additional; - while (item) { - r = dnsname_to_labels(buf, buf_len, j, item->name, strlen(item->name), &table); - if (r < 0) - goto overflow; - j = r; - - APPEND16(item->type); - APPEND16(item->class); - APPEND32(item->ttl); - if (item->is_name) { - off_t len_idx = j, name_start; - j += 2; - name_start = j; - r = dnsname_to_labels(buf, buf_len, j, item->data, strlen(item->data), &table); - if (r < 0) - goto overflow; - j = r; - _t = htons( (j-name_start) ); - memcpy(buf+len_idx, &_t, 2); - } else { - APPEND16(item->datalen); - if (j+item->datalen > (off_t)buf_len) - goto overflow; - memcpy(buf+j, item->data, item->datalen); - j += item->datalen; - } - item = item->next; - } - } - - if (j > 512) { -overflow: - j = 512; - buf[2] |= 0x02; /* set the truncated bit. */ - } - - req->response_len = (size_t)j; - - if (!(req->response = mm_malloc(req->response_len))) { - server_request_free_answers(req); - dnslabel_clear(&table); - return (-1); - } - memcpy(req->response, buf, req->response_len); - server_request_free_answers(req); - dnslabel_clear(&table); - return (0); -} - -/* exported function */ -int -evdns_server_request_respond(struct evdns_server_request *_req, int err) -{ - struct server_request *req = TO_SERVER_REQUEST(_req); - struct evdns_server_port *port = req->port; - ssize_t r; - if (!req->response) { - if ((r = evdns_server_request_format_response(req, err))<0) - return (int)r; - } - - r = sendto(port->socket, req->response, req->response_len, 0, - (struct sockaddr*) &req->addr, req->addrlen); - if (r<0) { - int error = last_error(port->socket); - if (! error_is_eagain(error)) - return -1; - - if (port->pending_replies) { - req->prev_pending = port->pending_replies->prev_pending; - req->next_pending = port->pending_replies; - req->prev_pending->next_pending = - req->next_pending->prev_pending = req; - } else { - req->prev_pending = req->next_pending = req; - port->pending_replies = req; - port->choked = 1; - - (void) event_del(&port->event); - CLEAR(&port->event); - event_set(&port->event, port->socket, (port->closing?0:EV_READ) | EV_WRITE | EV_PERSIST, server_port_ready_callback, port); - - if (event_add(&port->event, NULL) < 0) { - log(EVDNS_LOG_WARN, "Error from libevent when adding event for DNS server"); - } - - } - - return 1; - } - if (server_request_free(req)) - return 0; - - if (port->pending_replies) - server_port_flush(port); - - return 0; -} - -/* Free all storage held by RRs in req. */ -static void -server_request_free_answers(struct server_request *req) -{ - struct server_reply_item *victim, *next, **list; - int i; - for (i = 0; i < 3; ++i) { - if (i==0) - list = &req->answer; - else if (i==1) - list = &req->authority; - else - list = &req->additional; - - victim = *list; - while (victim) { - next = victim->next; - mm_free(victim->name); - if (victim->data) - mm_free(victim->data); - mm_free(victim); - victim = next; - } - *list = NULL; - } -} - -/* Free all storage held by req, and remove links to it. */ -/* return true iff we just wound up freeing the server_port. */ -static int -server_request_free(struct server_request *req) -{ - int i, rc=1; - if (req->base.questions) { - for (i = 0; i < req->base.nquestions; ++i) - mm_free(req->base.questions[i]); - mm_free(req->base.questions); - } - - if (req->port) { - if (req->port->pending_replies == req) { - if (req->next_pending && req->next_pending != req) - req->port->pending_replies = req->next_pending; - else - req->port->pending_replies = NULL; - } - rc = --req->port->refcnt; - } - - if (req->response) { - mm_free(req->response); - } - - server_request_free_answers(req); - - if (req->next_pending && req->next_pending != req) { - req->next_pending->prev_pending = req->prev_pending; - req->prev_pending->next_pending = req->next_pending; - } - - if (rc == 0) { - server_port_free(req->port); - CLEAR(req); - mm_free(req); - return (1); - } - CLEAR(req); - mm_free(req); - return (0); -} - -/* Free all storage held by an evdns_server_port. Only called when the - * reference count is down to 0. */ -static void -server_port_free(struct evdns_server_port *port) -{ - assert(port); - assert(!port->refcnt); - assert(!port->pending_replies); - if (port->socket > 0) { - CLOSE_SOCKET(port->socket); - port->socket = -1; - } - (void) event_del(&port->event); - CLEAR(&port->event); - CLEAR(port); - mm_free(port); -} - -/* exported function */ -int -evdns_server_request_drop(struct evdns_server_request *_req) -{ - struct server_request *req = TO_SERVER_REQUEST(_req); - server_request_free(req); - return 0; -} - -/* exported function */ -int -evdns_server_request_get_requesting_addr(struct evdns_server_request *_req, struct sockaddr *sa, int addr_len) -{ - struct server_request *req = TO_SERVER_REQUEST(_req); - if (addr_len < (int)req->addrlen) - return -1; - memcpy(sa, &(req->addr), req->addrlen); - return req->addrlen; -} - -#undef APPEND16 -#undef APPEND32 - -/* this is a libevent callback function which is called when a request */ -/* has timed out. */ -static void -evdns_request_timeout_callback(int fd, short events, void *arg) { - struct evdns_request *const req = (struct evdns_request *) arg; - (void) fd; - (void) events; - - log(EVDNS_LOG_DEBUG, "Request %lx timed out", (unsigned long) arg); - - req->ns->timedout++; - if (req->ns->timedout > global_max_nameserver_timeout) { - req->ns->timedout = 0; - nameserver_failed(req->ns, "request timed out."); - } - - if (req->tx_count >= global_max_retransmits) { - /* this request has failed */ - reply_callback(req, 0, DNS_ERR_TIMEOUT, NULL); - request_finished(req, &req_head); - } else { - /* retransmit it */ - /* Stop waiting for the timeout. No need to do this in - * request_finished; that one already deletes the timeout event. - * XXXX023 port this change to libevent. */ - del_timeout_event(req); - evdns_request_transmit(req); - } -} - -/* try to send a request to a given server. */ -/* */ -/* return: */ -/* 0 ok */ -/* 1 temporary failure */ -/* 2 other failure */ -static int -evdns_request_transmit_to(struct evdns_request *req, struct nameserver *server) { - const ssize_t r = send(server->socket, (void*)req->request, - req->request_len, 0); - if (r < 0) { - int err = last_error(server->socket); - if (error_is_eagain(err)) return 1; - nameserver_failed(req->ns, tor_socket_strerror(err)); - return 2; - } else if (r != (ssize_t)req->request_len) { - return 1; /* short write */ - } else { - return 0; - } -} - -/* try to send a request, updating the fields of the request */ -/* as needed */ -/* */ -/* return: */ -/* 0 ok */ -/* 1 failed */ -static int -evdns_request_transmit(struct evdns_request *req) { - int retcode = 0, r; - - /* if we fail to send this packet then this flag marks it */ - /* for evdns_transmit */ - req->transmit_me = 1; - if (req->trans_id == 0xffff) abort(); - - if (req->ns->choked) { - /* don't bother trying to write to a socket */ - /* which we have had EAGAIN from */ - return 1; - } - - r = evdns_request_transmit_to(req, req->ns); - switch (r) { - case 1: - /* temp failure */ - req->ns->choked = 1; - nameserver_write_waiting(req->ns, 1); - return 1; - case 2: - /* failed to transmit the request entirely. */ - retcode = 1; - /* fall through: we'll set a timeout, which will time out, - * and make us retransmit the request anyway. */ - default: - /* transmitted; we need to check for timeout. */ - log(EVDNS_LOG_DEBUG, - "Setting timeout for request %lx", (unsigned long) req); - - if (add_timeout_event(req, &global_timeout) < 0) { - log(EVDNS_LOG_WARN, - "Error from libevent when adding timer for request %lx", - (unsigned long) req); - /* ???? Do more? */ - } - req->tx_count++; - req->transmit_me = 0; - return retcode; - } -} - -static void -nameserver_probe_callback(int result, char type, int count, int ttl, void *addresses, void *arg) { - struct sockaddr *addr = arg; - struct nameserver *server; - (void) type; - (void) count; - (void) ttl; - (void) addresses; - - for (server = server_head; server; server = server->next) { - if (sockaddr_eq(addr, (struct sockaddr*) &server->address, 1)) { - if (result == DNS_ERR_NONE || result == DNS_ERR_NOTEXIST) { - /* this is a good reply */ - nameserver_up(server); - } else { - nameserver_probe_failed(server); - } - } - if (server->next == server_head) - break; - } - - mm_free(addr); -} - -static void -nameserver_send_probe(struct nameserver *const ns) { - struct evdns_request *req; - struct sockaddr_storage *addr; - /* here we need to send a probe to a given nameserver */ - /* in the hope that it is up now. */ - - /* We identify the nameserver by its address, in case it is removed before - * our probe comes back. */ - addr = mm_malloc(sizeof(struct sockaddr_storage)); - memcpy(addr, &ns->address, sizeof(struct sockaddr_storage)); - - log(EVDNS_LOG_DEBUG, "Sending probe to %s", debug_ntop((struct sockaddr *)&ns->address)); - - req = request_new(TYPE_A, "www.google.com", DNS_QUERY_NO_SEARCH, nameserver_probe_callback, addr); - if (!req) { - mm_free(addr); - return; - } - /* we force this into the inflight queue no matter what */ - request_trans_id_set(req, transaction_id_pick()); - req->ns = ns; - request_submit(req); -} - -/* returns: */ -/* 0 didn't try to transmit anything */ -/* 1 tried to transmit something */ -static int -evdns_transmit(void) { - char did_try_to_transmit = 0; - - if (req_head) { - struct evdns_request *const started_at = req_head, *req = req_head; - /* first transmit all the requests which are currently waiting */ - do { - if (req->transmit_me) { - did_try_to_transmit = 1; - evdns_request_transmit(req); - } - - req = req->next; - } while (req != started_at); - } - - return did_try_to_transmit; -} - -/* exported function */ -int -evdns_count_nameservers(void) -{ - const struct nameserver *server = server_head; - int n = 0; - if (!server) - return 0; - do { - ++n; - server = server->next; - } while (server != server_head); - return n; -} - -/* exported function */ -int -evdns_clear_nameservers_and_suspend(void) -{ - struct nameserver *server = server_head, *started_at = server_head; - struct evdns_request *req = req_head, *req_started_at = req_head; - - if (!server) - return 0; - while (1) { - struct nameserver *next = server->next; - (void) event_del(&server->event); - CLEAR(&server->event); - del_timeout_event(server); - if (server->socket >= 0) - CLOSE_SOCKET(server->socket); - CLEAR(server); - mm_free(server); - if (next == started_at) - break; - server = next; - } - server_head = NULL; - global_good_nameservers = 0; - - while (req) { - struct evdns_request *next = req->next; - req->tx_count = req->reissue_count = 0; - req->ns = NULL; - /* ???? What to do about searches? */ - del_timeout_event(req); - req->trans_id = 0; - req->transmit_me = 0; - - global_requests_waiting++; - evdns_request_insert(req, &req_waiting_head); - /* We want to insert these suspended elements at the front of - * the waiting queue, since they were pending before any of - * the waiting entries were added. This is a circular list, - * so we can just shift the start back by one.*/ - req_waiting_head = req_waiting_head->prev; - - if (next == req_started_at) - break; - req = next; - } - req_head = NULL; - global_requests_inflight = 0; - - return 0; -} - -static struct sockaddr_storage global_bind_address; -static socklen_t global_bind_addrlen = 0; -static int global_bind_addr_is_set = 0; -void -evdns_set_default_outgoing_bind_address(const struct sockaddr *addr, - socklen_t addrlen) -{ - memset(&global_bind_address, 0, sizeof(global_bind_address)); - if (addr) { - assert(addrlen <= (socklen_t)sizeof(global_bind_address)); - memcpy(&global_bind_address, addr, addrlen); - global_bind_addrlen = addrlen; - global_bind_addr_is_set = 1; - } else { - global_bind_addr_is_set = 0; - } -} - -/* exported function */ -int -evdns_resume(void) -{ - evdns_requests_pump_waiting_queue(); - return 0; -} - -static int -sockaddr_is_loopback(const struct sockaddr *addr) -{ - static const char LOOPBACK_S6[16] = - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1"; - if (addr->sa_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *)addr; - return (ntohl(sin->sin_addr.s_addr) & 0xff000000) == 0x7f000000; - } else if (addr->sa_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; - return fast_memeq(sin6->sin6_addr.s6_addr, LOOPBACK_S6, 16); - } - return 0; -} - -static int -_evdns_nameserver_add_impl(const struct sockaddr *address, - socklen_t addrlen) { - /* first check to see if we already have this nameserver */ - - const struct nameserver *server = server_head, *const started_at = server_head; - struct nameserver *ns; - - int err = 0; - if (server) { - do { - if (sockaddr_eq(address, (struct sockaddr *)&server->address, 1)) { - log(EVDNS_LOG_DEBUG, "Duplicate nameserver."); - return 3; - } - server = server->next; - } while (server != started_at); - } - if (addrlen > (int)sizeof(ns->address)) { - log(EVDNS_LOG_DEBUG, "Addrlen %d too long.", (int)addrlen); - return 2; - } - - ns = (struct nameserver *) mm_malloc(sizeof(struct nameserver)); - if (!ns) return -1; - - memset(ns, 0, sizeof(struct nameserver)); - - evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns); - - ns->socket = tor_open_socket(address->sa_family, SOCK_DGRAM, 0); - if (ns->socket < 0) { err = 1; goto out1; } -#ifdef _WIN32 - { - u_long nonblocking = 1; - ioctlsocket(ns->socket, FIONBIO, &nonblocking); - } -#else - fcntl(ns->socket, F_SETFL, O_NONBLOCK); -#endif - - if (global_bind_addr_is_set && - !sockaddr_is_loopback((struct sockaddr*)&global_bind_address)) { - if (bind(ns->socket, (struct sockaddr *)&global_bind_address, - global_bind_addrlen) < 0) { - log(EVDNS_LOG_DEBUG, "Couldn't bind to outgoing address."); - err = 2; - goto out2; - } - } - - if (connect(ns->socket, address, addrlen) != 0) { - log(EVDNS_LOG_DEBUG, "Couldn't open socket to nameserver."); - err = 2; - goto out2; - } - - memcpy(&ns->address, address, addrlen); - ns->state = 1; - event_set(&ns->event, ns->socket, EV_READ | EV_PERSIST, nameserver_ready_callback, ns); - if (event_add(&ns->event, NULL) < 0) { - log(EVDNS_LOG_DEBUG, "Couldn't add event for nameserver."); - err = 2; - goto out2; - } - - log(EVDNS_LOG_DEBUG, "Added nameserver %s", debug_ntop(address)); - - /* insert this nameserver into the list of them */ - if (!server_head) { - ns->next = ns->prev = ns; - server_head = ns; - } else { - ns->next = server_head->next; - ns->prev = server_head; - server_head->next = ns; - if (server_head->prev == server_head) { - server_head->prev = ns; - } - } - - global_good_nameservers++; - - return 0; - -out2: - CLOSE_SOCKET(ns->socket); -out1: - CLEAR(ns); - mm_free(ns); - log(EVDNS_LOG_WARN, "Unable to add nameserver %s: error %d", debug_ntop(address), err); - return err; -} - -/* exported function */ -int -evdns_nameserver_add(uint32_t address) { - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sin.sin_len = sizeof(sin); -#endif - sin.sin_addr.s_addr = htonl(address); - sin.sin_port = 53; - return _evdns_nameserver_add_impl((struct sockaddr*) &sin, sizeof(sin)); -} - -/* exported function */ -int -evdns_nameserver_ip_add(const char *ip_as_string) { - int port; - char buf[128]; - const char *cp, *addr_part, *port_part; - int is_ipv6; - /* recognized formats are: - * [ipv6]:port - * ipv6 - * [ipv6] - * ipv4:port - * ipv4 - */ - - log(EVDNS_LOG_DEBUG, "Trying to add nameserver <%s>", ip_as_string); - - cp = strchr(ip_as_string, ':'); - if (*ip_as_string == '[') { - size_t len; - if (!(cp = strchr(ip_as_string, ']'))) { - log(EVDNS_LOG_DEBUG, "Nameserver missing closing ]"); - return 4; - } - len = cp-(ip_as_string + 1); - if (len > sizeof(buf)-1) { - log(EVDNS_LOG_DEBUG, "[Nameserver] does not fit in buffer."); - return 4; - } - memcpy(buf, ip_as_string+1, len); - buf[len] = '\0'; - addr_part = buf; - if (cp[1] == ':') - port_part = cp+2; - else - port_part = NULL; - is_ipv6 = 1; - } else if (cp && strchr(cp+1, ':')) { - is_ipv6 = 1; - addr_part = ip_as_string; - port_part = NULL; - } else if (cp) { - is_ipv6 = 0; - if (cp - ip_as_string > (int)sizeof(buf)-1) { - log(EVDNS_LOG_DEBUG, "Nameserver does not fit in buffer."); - return 4; - } - memcpy(buf, ip_as_string, cp-ip_as_string); - buf[cp-ip_as_string] = '\0'; - addr_part = buf; - port_part = cp+1; - } else { - addr_part = ip_as_string; - port_part = NULL; - is_ipv6 = 0; - } - - if (port_part == NULL) { - port = 53; - } else { - port = strtoint(port_part); - if (port <= 0 || port > 65535) { - log(EVDNS_LOG_DEBUG, "Nameserver port <%s> out of range", - port_part); - return 4; - } - } - - /* Tor-only. needs a more general fix. */ - assert(addr_part); - if (is_ipv6) { - struct sockaddr_in6 sin6; - memset(&sin6, 0, sizeof(sin6)); -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6.sin6_len = sizeof(sin6); -#endif - sin6.sin6_family = AF_INET6; - sin6.sin6_port = htons(port); - if (1 != tor_inet_pton(AF_INET6, addr_part, &sin6.sin6_addr)) { - log(EVDNS_LOG_DEBUG, "inet_pton(%s) failed", addr_part); - return 4; - } - return _evdns_nameserver_add_impl((struct sockaddr*)&sin6, - sizeof(sin6)); - } else { - struct sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sin.sin_len = sizeof(sin); -#endif - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - if (!inet_aton(addr_part, &sin.sin_addr)) { - log(EVDNS_LOG_DEBUG, "inet_pton(%s) failed", addr_part); - return 4; - } - return _evdns_nameserver_add_impl((struct sockaddr*)&sin, - sizeof(sin)); - } -} - -int -evdns_nameserver_sockaddr_add(const struct sockaddr *sa, socklen_t len) -{ - return _evdns_nameserver_add_impl(sa, len); -} - -/* insert into the tail of the queue */ -static void -evdns_request_insert(struct evdns_request *req, struct evdns_request **head) { - if (!*head) { - *head = req; - req->next = req->prev = req; - return; - } - - req->prev = (*head)->prev; - req->prev->next = req; - req->next = *head; - (*head)->prev = req; -} - -static int -string_num_dots(const char *s) { - int count = 0; - while ((s = strchr(s, '.'))) { - s++; - count++; - } - return count; -} - -static struct evdns_request * -request_new(int type, const char *name, int flags, - evdns_callback_type callback, void *user_ptr) { - const char issuing_now = - (global_requests_inflight < global_max_requests_inflight) ? 1 : 0; - - const size_t name_len = strlen(name); - const size_t request_max_len = evdns_request_len(name_len); - const u16 trans_id = issuing_now ? transaction_id_pick() : 0xffff; - /* the request data is alloced in a single block with the header */ - struct evdns_request *const req = - (struct evdns_request *) mm_malloc(sizeof(struct evdns_request) + request_max_len); - char namebuf[256]; - int rlen; - (void) flags; - - if (!req) return NULL; - - if (name_len >= sizeof(namebuf)) { - _mm_free(req); - return NULL; - } - - memset(req, 0, sizeof(struct evdns_request)); - - evtimer_set(&req->timeout_event, evdns_request_timeout_callback, req); - - if (global_randomize_case) { - unsigned i; - char randbits[32]; - strlcpy(namebuf, name, sizeof(namebuf)); - rand_bytes_function(randbits, (name_len+7)/8); - for (i = 0; i < name_len; ++i) { - if (ISALPHA(namebuf[i])) { - if ((randbits[i >> 3] & (1<<(i%7)))) - namebuf[i] = TOLOWER(namebuf[i]); - else - namebuf[i] = TOUPPER(namebuf[i]); - } - } - name = namebuf; - } - - /* request data lives just after the header */ - req->request = ((u8 *) req) + sizeof(struct evdns_request); - /* denotes that the request data shouldn't be mm_free()ed */ - req->request_appended = 1; - rlen = evdns_request_data_build(name, name_len, trans_id, - type, CLASS_INET, req->request, request_max_len); - if (rlen < 0) - goto err1; - req->request_len = rlen; - req->trans_id = trans_id; - req->tx_count = 0; - req->request_type = type; - req->user_pointer = user_ptr; - req->user_callback = callback; - req->ns = issuing_now ? nameserver_pick() : NULL; - req->next = req->prev = NULL; - - return req; -err1: - CLEAR(req); - _mm_free(req); - return NULL; -} - -static void -request_submit(struct evdns_request *const req) { - if (req->ns) { - /* if it has a nameserver assigned then this is going */ - /* straight into the inflight queue */ - evdns_request_insert(req, &req_head); - global_requests_inflight++; - evdns_request_transmit(req); - } else { - evdns_request_insert(req, &req_waiting_head); - global_requests_waiting++; - } -} - -/* exported function */ -int evdns_resolve_ipv4(const char *name, int flags, - evdns_callback_type callback, void *ptr) { - log(EVDNS_LOG_DEBUG, "Resolve requested for %s", name); - if (flags & DNS_QUERY_NO_SEARCH) { - struct evdns_request *const req = - request_new(TYPE_A, name, flags, callback, ptr); - if (req == NULL) - return (1); - request_submit(req); - return (0); - } else { - return (search_request_new(TYPE_A, name, flags, callback, ptr)); - } -} - -/* exported function */ -int evdns_resolve_ipv6(const char *name, int flags, - evdns_callback_type callback, void *ptr) { - log(EVDNS_LOG_DEBUG, "Resolve requested for %s", name); - if (flags & DNS_QUERY_NO_SEARCH) { - struct evdns_request *const req = - request_new(TYPE_AAAA, name, flags, callback, ptr); - if (req == NULL) - return (1); - request_submit(req); - return (0); - } else { - return (search_request_new(TYPE_AAAA, name, flags, callback, ptr)); - } -} - -int evdns_resolve_reverse(const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr) { - char buf[32]; - struct evdns_request *req; - u32 a; - assert(in); - a = ntohl(in->s_addr); - snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa", - (int)(u8)((a )&0xff), - (int)(u8)((a>>8 )&0xff), - (int)(u8)((a>>16)&0xff), - (int)(u8)((a>>24)&0xff)); - log(EVDNS_LOG_DEBUG, "Resolve requested for %s (reverse)", buf); - req = request_new(TYPE_PTR, buf, flags, callback, ptr); - if (!req) return 1; - request_submit(req); - return 0; -} - -int evdns_resolve_reverse_ipv6(const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr) { - /* 32 nybbles, 32 periods, "ip6.arpa", NUL. */ - char buf[73]; - char *cp; - struct evdns_request *req; - int i; - assert(in); - cp = buf; - for (i=15; i >= 0; --i) { - u8 byte = in->s6_addr[i]; - *cp++ = "0123456789abcdef"[byte & 0x0f]; - *cp++ = '.'; - *cp++ = "0123456789abcdef"[byte >> 4]; - *cp++ = '.'; - } - assert(cp + strlen("ip6.arpa") < buf+sizeof(buf)); - memcpy(cp, "ip6.arpa", strlen("ip6.arpa")+1); - log(EVDNS_LOG_DEBUG, "Resolve requested for %s (reverse)", buf); - req = request_new(TYPE_PTR, buf, flags, callback, ptr); - if (!req) return 1; - request_submit(req); - return 0; -} - -/*/////////////////////////////////////////////////////////////////// */ -/* Search support */ -/* */ -/* the libc resolver has support for searching a number of domains */ -/* to find a name. If nothing else then it takes the single domain */ -/* from the gethostname() call. */ -/* */ -/* It can also be configured via the domain and search options in a */ -/* resolv.conf. */ -/* */ -/* The ndots option controls how many dots it takes for the resolver */ -/* to decide that a name is non-local and so try a raw lookup first. */ - -struct search_domain { - size_t len; - struct search_domain *next; - /* the text string is appended to this structure */ -}; - -struct search_state { - int refcount; - int ndots; - int num_domains; - struct search_domain *head; -}; - -static struct search_state *global_search_state = NULL; - -static void -search_state_decref(struct search_state *const state) { - if (!state) return; - state->refcount--; - if (!state->refcount) { - struct search_domain *next, *dom; - for (dom = state->head; dom; dom = next) { - next = dom->next; - CLEAR(dom); - _mm_free(dom); - } - CLEAR(state); - _mm_free(state); - } -} - -static struct search_state * -search_state_new(void) { - struct search_state *state = (struct search_state *) mm_malloc(sizeof(struct search_state)); - if (!state) return NULL; - memset(state, 0, sizeof(struct search_state)); - state->refcount = 1; - state->ndots = 1; - - return state; -} - -static void -search_postfix_clear(void) { - search_state_decref(global_search_state); - - global_search_state = search_state_new(); -} - -/* exported function */ -void -evdns_search_clear(void) { - search_postfix_clear(); -} - -static void -search_postfix_add(const char *domain) { - size_t domain_len; - struct search_domain *sdomain; - while (domain[0] == '.') domain++; - domain_len = strlen(domain); - - if (!global_search_state) global_search_state = search_state_new(); - if (!global_search_state) return; - global_search_state->num_domains++; - - sdomain = (struct search_domain *) mm_malloc(sizeof(struct search_domain) + domain_len); - if (!sdomain) return; - memcpy( ((u8 *) sdomain) + sizeof(struct search_domain), domain, domain_len); - sdomain->next = global_search_state->head; - sdomain->len = domain_len; - - global_search_state->head = sdomain; -} - -/* reverse the order of members in the postfix list. This is needed because, */ -/* when parsing resolv.conf we push elements in the wrong order */ -static void -search_reverse(void) { - struct search_domain *cur, *prev = NULL, *next; - cur = global_search_state->head; - while (cur) { - next = cur->next; - cur->next = prev; - prev = cur; - cur = next; - } - - global_search_state->head = prev; -} - -/* exported function */ -void -evdns_search_add(const char *domain) { - search_postfix_add(domain); -} - -/* exported function */ -void -evdns_search_ndots_set(const int ndots) { - if (!global_search_state) global_search_state = search_state_new(); - if (!global_search_state) return; - global_search_state->ndots = ndots; -} - -static void -search_set_from_hostname(void) { - char hostname[HOST_NAME_MAX + 1], *domainname; - - search_postfix_clear(); - if (gethostname(hostname, sizeof(hostname))) return; - domainname = strchr(hostname, '.'); - if (!domainname) return; - search_postfix_add(domainname); -} - -/* warning: returns malloced string */ -static char * -search_make_new(const struct search_state *const state, int n, const char *const base_name) { - const size_t base_len = strlen(base_name); - const char need_to_append_dot = base_name[base_len - 1] == '.' ? 0 : 1; - struct search_domain *dom; - - for (dom = state->head; dom; dom = dom->next) { - if (!n--) { - /* this is the postfix we want */ - /* the actual postfix string is kept at the end of the structure */ - const u8 *const postfix = ((u8 *) dom) + sizeof(struct search_domain); - const size_t postfix_len = dom->len; - char *const newname = (char *) mm_malloc(base_len + need_to_append_dot + postfix_len + 1); - if (!newname) return NULL; - memcpy(newname, base_name, base_len); - if (need_to_append_dot) newname[base_len] = '.'; - memcpy(newname + base_len + need_to_append_dot, postfix, postfix_len); - newname[base_len + need_to_append_dot + postfix_len] = 0; - return newname; - } - } - - /* we ran off the end of the list and still didn't find the requested string */ - abort(); - return NULL; /* unreachable; stops warnings in some compilers. */ -} - -static int -search_request_new(int type, const char *const name, int flags, evdns_callback_type user_callback, void *user_arg) { - assert(type == TYPE_A || type == TYPE_AAAA); - if ( ((flags & DNS_QUERY_NO_SEARCH) == 0) && - global_search_state && - global_search_state->num_domains) { - /* we have some domains to search */ - struct evdns_request *req; - if (string_num_dots(name) >= global_search_state->ndots) { - req = request_new(type, name, flags, user_callback, user_arg); - if (!req) return 1; - req->search_index = -1; - } else { - char *const new_name = search_make_new(global_search_state, 0, name); - if (!new_name) return 1; - req = request_new(type, new_name, flags, user_callback, user_arg); - _mm_free(new_name); - if (!req) return 1; - req->search_index = 0; - } - req->search_origname = mm_strdup(name); - req->search_state = global_search_state; - req->search_flags = flags; - global_search_state->refcount++; - request_submit(req); - return 0; - } else { - struct evdns_request *const req = request_new(type, name, flags, user_callback, user_arg); - if (!req) return 1; - request_submit(req); - return 0; - } -} - -/* this is called when a request has failed to find a name. We need to check */ -/* if it is part of a search and, if so, try the next name in the list */ -/* returns: */ -/* 0 another request has been submitted */ -/* 1 no more requests needed */ -static int -search_try_next(struct evdns_request *const req) { - if (req->search_state) { - /* it is part of a search */ - char *new_name; - struct evdns_request *newreq; - req->search_index++; - if (req->search_index >= req->search_state->num_domains) { - /* no more postfixes to try, however we may need to try */ - /* this name without a postfix */ - if (string_num_dots(req->search_origname) < req->search_state->ndots) { - /* yep, we need to try it raw */ - struct evdns_request *const newreq = request_new(req->request_type, req->search_origname, req->search_flags, req->user_callback, req->user_pointer); - log(EVDNS_LOG_DEBUG, "Search: trying raw query %s", req->search_origname); - if (newreq) { - request_submit(newreq); - return 0; - } - } - return 1; - } - - new_name = search_make_new(req->search_state, req->search_index, req->search_origname); - if (!new_name) return 1; - log(EVDNS_LOG_DEBUG, "Search: now trying %s (%d)", new_name, req->search_index); - newreq = request_new(req->request_type, new_name, req->search_flags, req->user_callback, req->user_pointer); - mm_free(new_name); - if (!newreq) return 1; - newreq->search_origname = req->search_origname; - req->search_origname = NULL; - newreq->search_state = req->search_state; - newreq->search_flags = req->search_flags; - newreq->search_index = req->search_index; - newreq->search_state->refcount++; - request_submit(newreq); - return 0; - } - return 1; -} - -static void -search_request_finished(struct evdns_request *const req) { - if (req->search_state) { - search_state_decref(req->search_state); - req->search_state = NULL; - } - if (req->search_origname) { - mm_free(req->search_origname); - req->search_origname = NULL; - } -} - -/*/////////////////////////////////////////////////////////////////// */ -/* Parsing resolv.conf files */ - -static void -evdns_resolv_set_defaults(int flags) { - /* if the file isn't found then we assume a local resolver */ - if (flags & DNS_OPTION_SEARCH) search_set_from_hostname(); - if (flags & DNS_OPTION_NAMESERVERS) evdns_nameserver_ip_add("127.0.0.1"); -} - -/* helper version of atoi which returns -1 on error */ -static int -strtoint(const char *const str) { - char *endptr; - const long r = strtol(str, &endptr, 10); - if (*endptr || r > INT_MAX) return -1; - return (int)r; -} - -/* helper version of atoi that returns -1 on error and clips to bounds. */ -static int -strtoint_clipped(const char *const str, int min, int max) -{ - int r = strtoint(str); - if (r == -1) - return r; - else if (r<min) - return min; - else if (r>max) - return max; - else - return r; -} - -/* exported function */ -int -evdns_set_option(const char *option, const char *val, int flags) -{ - if (!strncmp(option, "ndots:", 6)) { - const int ndots = strtoint(val); - if (ndots == -1) return -1; - if (!(flags & DNS_OPTION_SEARCH)) return 0; - log(EVDNS_LOG_DEBUG, "Setting ndots to %d", ndots); - if (!global_search_state) global_search_state = search_state_new(); - if (!global_search_state) return -1; - global_search_state->ndots = ndots; - } else if (!strncmp(option, "timeout:", 8)) { - const int timeout = strtoint(val); - if (timeout == -1) return -1; - if (!(flags & DNS_OPTION_MISC)) return 0; - log(EVDNS_LOG_DEBUG, "Setting timeout to %d", timeout); - global_timeout.tv_sec = timeout; - } else if (!strncmp(option, "max-timeouts:", 12)) { - const int maxtimeout = strtoint_clipped(val, 1, 255); - if (maxtimeout == -1) return -1; - if (!(flags & DNS_OPTION_MISC)) return 0; - log(EVDNS_LOG_DEBUG, "Setting maximum allowed timeouts to %d", - maxtimeout); - global_max_nameserver_timeout = maxtimeout; - } else if (!strncmp(option, "max-inflight:", 13)) { - const int maxinflight = strtoint_clipped(val, 1, 65000); - if (maxinflight == -1) return -1; - if (!(flags & DNS_OPTION_MISC)) return 0; - log(EVDNS_LOG_DEBUG, "Setting maximum inflight requests to %d", - maxinflight); - global_max_requests_inflight = maxinflight; - } else if (!strncmp(option, "attempts:", 9)) { - int retries = strtoint(val); - if (retries == -1) return -1; - if (retries > 255) retries = 255; - if (!(flags & DNS_OPTION_MISC)) return 0; - log(EVDNS_LOG_DEBUG, "Setting retries to %d", retries); - global_max_retransmits = retries; - } else if (!strncmp(option, "randomize-case:", 15)) { - int randcase = strtoint(val); - if (!(flags & DNS_OPTION_MISC)) return 0; - log(EVDNS_LOG_DEBUG, "Setting randomize_case to %d", randcase); - global_randomize_case = randcase; - } - return 0; -} - -static void -resolv_conf_parse_line(char *const start, int flags) { - char *strtok_state; - static const char *const delims = " \t"; -#define NEXT_TOKEN tor_strtok_r(NULL, delims, &strtok_state) - - char *const first_token = tor_strtok_r(start, delims, &strtok_state); - if (!first_token) return; - - if (!strcmp(first_token, "nameserver") && (flags & DNS_OPTION_NAMESERVERS)) { - const char *const nameserver = NEXT_TOKEN; - evdns_nameserver_ip_add(nameserver); - } else if (!strcmp(first_token, "domain") && (flags & DNS_OPTION_SEARCH)) { - const char *const domain = NEXT_TOKEN; - if (domain) { - search_postfix_clear(); - search_postfix_add(domain); - } - } else if (!strcmp(first_token, "search") && (flags & DNS_OPTION_SEARCH)) { - const char *domain; - search_postfix_clear(); - - while ((domain = NEXT_TOKEN)) { - search_postfix_add(domain); - } - search_reverse(); - } else if (!strcmp(first_token, "options")) { - const char *option; - while ((option = NEXT_TOKEN)) { - const char *val = strchr(option, ':'); - evdns_set_option(option, val ? val+1 : "", flags); - } - } -#undef NEXT_TOKEN -} - -/* exported function */ -/* returns: */ -/* 0 no errors */ -/* 1 failed to open file */ -/* 2 failed to stat file */ -/* 3 file too large */ -/* 4 out of memory */ -/* 5 short read from file */ -int -evdns_resolv_conf_parse(int flags, const char *const filename) { - struct stat st; - int fd, n, r; - u8 *resolv; - char *start; - int err = 0; - - log(EVDNS_LOG_DEBUG, "Parsing resolv.conf file %s", filename); - - fd = tor_open_cloexec(filename, O_RDONLY, 0); - if (fd < 0) { - evdns_resolv_set_defaults(flags); - return 1; - } - - if (fstat(fd, &st)) { err = 2; goto out1; } - if (!st.st_size) { - evdns_resolv_set_defaults(flags); - err = (flags & DNS_OPTION_NAMESERVERS) ? 6 : 0; - goto out1; - } - if (st.st_size > 65535) { err = 3; goto out1; } /* no resolv.conf should be any bigger */ - - resolv = (u8 *) mm_malloc((size_t)st.st_size + 1); - if (!resolv) { err = 4; goto out1; } - - n = 0; - while ((r = (int)read(fd, resolv+n, (size_t)st.st_size-n)) > 0) { - n += r; - if (n == st.st_size) - break; - assert(n < st.st_size); - } - if (r < 0) { err = 5; goto out2; } - resolv[n] = 0; /* we malloced an extra byte; this should be fine. */ - - start = (char *) resolv; - for (;;) { - char *const newline = strchr(start, '\n'); - if (!newline) { - resolv_conf_parse_line(start, flags); - break; - } else { - *newline = 0; - resolv_conf_parse_line(start, flags); - start = newline + 1; - } - } - - if (!server_head && (flags & DNS_OPTION_NAMESERVERS)) { - /* no nameservers were configured. */ - evdns_nameserver_ip_add("127.0.0.1"); - err = 6; - } - if (flags & DNS_OPTION_SEARCH && (!global_search_state || global_search_state->num_domains == 0)) { - search_set_from_hostname(); - } - -out2: - mm_free(resolv); -out1: - close(fd); - return err; -} - -#ifdef _WIN32 -/* Add multiple nameservers from a space-or-comma-separated list. */ -static int -evdns_nameserver_ip_add_line(const char *ips) { - const char *addr; - char *buf; - int r; - while (*ips) { - while (ISSPACE(*ips) || *ips == ',' || *ips == '\t') - ++ips; - addr = ips; - while (ISDIGIT(*ips) || *ips == '.' || *ips == ':' || *ips == '[' || *ips == ']') - ++ips; - buf = mm_malloc(ips-addr+1); - if (!buf) return 4; - memcpy(buf, addr, ips-addr); - buf[ips-addr] = '\0'; - r = evdns_nameserver_ip_add(buf); - mm_free(buf); - if (r) return r; - } - return 0; -} - -typedef DWORD(WINAPI *GetNetworkParams_fn_t)(FIXED_INFO *, DWORD*); - -/* Use the windows GetNetworkParams interface in iphlpapi.dll to */ -/* figure out what our nameservers are. */ -static int -load_nameservers_with_getnetworkparams(void) -{ - /* Based on MSDN examples and inspection of c-ares code. */ - FIXED_INFO *fixed; - HMODULE handle = 0; - ULONG size = sizeof(FIXED_INFO); - void *buf = NULL; - int status = 0, r, added_any; - IP_ADDR_STRING *ns; - GetNetworkParams_fn_t fn; - - if (!(handle = load_windows_system_library(TEXT("iphlpapi.dll")))) { - log(EVDNS_LOG_WARN, "Could not open iphlpapi.dll"); - /* right now status = 0, doesn't that mean "good" - mikec */ - status = -1; - goto done; - } - if (!(fn = (GetNetworkParams_fn_t) GetProcAddress(handle, TEXT("GetNetworkParams")))) { - log(EVDNS_LOG_WARN, "Could not get address of function."); - /* same as above */ - status = -1; - goto done; - } - - buf = mm_malloc(size); - if (!buf) { status = 4; goto done; } - fixed = buf; - r = fn(fixed, &size); - if (r != ERROR_SUCCESS && r != ERROR_BUFFER_OVERFLOW) { - status = -1; - goto done; - } - if (r != ERROR_SUCCESS) { - mm_free(buf); - buf = mm_malloc(size); - if (!buf) { status = 4; goto done; } - fixed = buf; - r = fn(fixed, &size); - if (r != ERROR_SUCCESS) { - log(EVDNS_LOG_DEBUG, "fn() failed."); - status = -1; - goto done; - } - } - - assert(fixed); - added_any = 0; - ns = &(fixed->DnsServerList); - while (ns) { - r = evdns_nameserver_ip_add_line(ns->IpAddress.String); - if (r) { - log(EVDNS_LOG_DEBUG,"Could not add nameserver %s to list, " - "error: %d; status: %d", - (ns->IpAddress.String),(int)GetLastError(), r); - status = r; - } else { - log(EVDNS_LOG_DEBUG,"Successfully added %s as nameserver",ns->IpAddress.String); - added_any++; - } - - ns = ns->Next; - } - - if (!added_any) { - log(EVDNS_LOG_DEBUG, "No nameservers added."); - if (status == 0) - status = -1; - } else { - status = 0; - } - - done: - if (buf) - mm_free(buf); - if (handle) - FreeLibrary(handle); - return status; -} - -static int -config_nameserver_from_reg_key(HKEY key, const TCHAR *subkey) -{ - char *buf; - char ansibuf[MAX_PATH] = {0}; - DWORD bufsz = 0, type = 0; - int status = 0; - - if (RegQueryValueEx(key, subkey, 0, &type, NULL, &bufsz) - != ERROR_MORE_DATA) - return -1; - if (!(buf = mm_malloc(bufsz))) - return -1; - - if (RegQueryValueEx(key, subkey, 0, &type, (LPBYTE)buf, &bufsz) - == ERROR_SUCCESS && bufsz > 1) { - wcstombs(ansibuf,(wchar_t*)buf,MAX_PATH);/*XXXX UNICODE */ - abuf[MAX_PATH-1] = '\0'; - status = evdns_nameserver_ip_add_line(ansibuf); - } - - mm_free(buf); - return status; -} - -#define SERVICES_KEY TEXT("System\\CurrentControlSet\\Services\\") -#define WIN_NS_9X_KEY SERVICES_KEY TEXT("VxD\\MSTCP") -#define WIN_NS_NT_KEY SERVICES_KEY TEXT("Tcpip\\Parameters") - -static int -load_nameservers_from_registry(void) -{ - int found = 0; - int r; - OSVERSIONINFO info; - memset(&info, 0, sizeof(info)); - info.dwOSVersionInfoSize = sizeof (info); - GetVersionEx(&info); - -#define TRY(k, name) \ - if (!found && config_nameserver_from_reg_key(k,TEXT(name)) == 0) { \ - log(EVDNS_LOG_DEBUG,"Found nameservers in %s/%s",#k,name); \ - found = 1; \ - } else if (!found) { \ - log(EVDNS_LOG_DEBUG,"Didn't find nameservers in %s/%s", \ - #k,#name); \ - } - - if (info.dwMajorVersion >= 5) { /* NT */ - HKEY nt_key = 0, interfaces_key = 0; - - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, - KEY_READ, &nt_key) != ERROR_SUCCESS) { - log(EVDNS_LOG_DEBUG,"Couldn't open nt key, %d",(int)GetLastError()); - return -1; - } - r = RegOpenKeyEx(nt_key, TEXT("Interfaces"), 0, - KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, - &interfaces_key); - if (r != ERROR_SUCCESS) { - log(EVDNS_LOG_DEBUG,"Couldn't open interfaces key, %d",(int)GetLastError()); - return -1; - } - TRY(nt_key, "NameServer"); - TRY(nt_key, "DhcpNameServer"); - TRY(interfaces_key, "NameServer"); - TRY(interfaces_key, "DhcpNameServer"); - RegCloseKey(interfaces_key); - RegCloseKey(nt_key); - } else { - HKEY win_key = 0; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_NS_9X_KEY, 0, - KEY_READ, &win_key) != ERROR_SUCCESS) { - log(EVDNS_LOG_DEBUG, "Couldn't open registry key, %d", (int)GetLastError()); - return -1; - } - TRY(win_key, "NameServer"); - RegCloseKey(win_key); - } - - if (found == 0) { - log(EVDNS_LOG_WARN,"Didn't find any nameservers."); - } - - return found ? 0 : -1; -#undef TRY -} - -int -evdns_config_windows_nameservers(void) -{ - if (load_nameservers_with_getnetworkparams() == 0) - return 0; - return load_nameservers_from_registry(); -} -#endif - -int -evdns_init(void) -{ - int res = 0; -#ifdef _WIN32 - evdns_config_windows_nameservers(); -#else - res = evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf"); -#endif - - return (res); -} - -const char * -evdns_err_to_string(int err) -{ - switch (err) { - case DNS_ERR_NONE: return "no error"; - case DNS_ERR_FORMAT: return "misformatted query"; - case DNS_ERR_SERVERFAILED: return "server failed"; - case DNS_ERR_NOTEXIST: return "name does not exist"; - case DNS_ERR_NOTIMPL: return "query not implemented"; - case DNS_ERR_REFUSED: return "refused"; - - case DNS_ERR_TRUNCATED: return "reply truncated or ill-formed"; - case DNS_ERR_UNKNOWN: return "unknown"; - case DNS_ERR_TIMEOUT: return "request timed out"; - case DNS_ERR_SHUTDOWN: return "dns subsystem shut down"; - default: return "[Unknown error code]"; - } -} - -void -evdns_shutdown(int fail_requests) -{ - struct nameserver *server, *server_next; - struct search_domain *dom, *dom_next; - - while (req_head) { - if (fail_requests) - reply_callback(req_head, 0, DNS_ERR_SHUTDOWN, NULL); - request_finished(req_head, &req_head); - } - while (req_waiting_head) { - if (fail_requests) - reply_callback(req_waiting_head, 0, DNS_ERR_SHUTDOWN, NULL); - request_finished(req_waiting_head, &req_waiting_head); - } - global_requests_inflight = global_requests_waiting = 0; - - for (server = server_head; server; server = server_next) { - server_next = server->next; - if (server->socket >= 0) - CLOSE_SOCKET(server->socket); - (void) event_del(&server->event); - del_timeout_event(server); - CLEAR(server); - mm_free(server); - if (server_next == server_head) - break; - } - server_head = NULL; - global_good_nameservers = 0; - - if (global_search_state) { - for (dom = global_search_state->head; dom; dom = dom_next) { - dom_next = dom->next; - CLEAR(dom); - mm_free(dom); - } - CLEAR(global_search_state); - mm_free(global_search_state); - global_search_state = NULL; - } - evdns_log_fn = NULL; -} - -#ifdef EVDNS_MAIN -void -main_callback(int result, char type, int count, int ttl, - void *addrs, void *orig) { - char *n = (char*)orig; - int i; - for (i = 0; i < count; ++i) { - if (type == DNS_IPv4_A) { - printf("%s: %s\n", n, debug_ntoa(((u32*)addrs)[i])); - } else if (type == DNS_PTR) { - printf("%s: %s\n", n, ((char**)addrs)[i]); - } - } - if (!count) { - printf("%s: No answer (%d)\n", n, result); - } - fflush(stdout); -} -void -evdns_server_callback(struct evdns_server_request *req, void *data) -{ - int i, r; - (void)data; - /* dummy; give 192.168.11.11 as an answer for all A questions, - * give foo.bar.example.com as an answer for all PTR questions. */ - for (i = 0; i < req->nquestions; ++i) { - u32 ans = htonl(0xc0a80b0bUL); - if (req->questions[i]->type == EVDNS_TYPE_A && - req->questions[i]->dns_question_class == EVDNS_CLASS_INET) { - printf(" -- replying for %s (A)\n", req->questions[i]->name); - r = evdns_server_request_add_a_reply(req, req->questions[i]->name, - 1, &ans, 10); - if (r<0) - printf("eeep, didn't work.\n"); - } else if (req->questions[i]->type == EVDNS_TYPE_PTR && - req->questions[i]->dns_question_class == EVDNS_CLASS_INET) { - printf(" -- replying for %s (PTR)\n", req->questions[i]->name); - r = evdns_server_request_add_ptr_reply(req, NULL, req->questions[i]->name, - "foo.bar.example.com", 10); - } else { - printf(" -- skipping %s [%d %d]\n", req->questions[i]->name, - req->questions[i]->type, req->questions[i]->dns_question_class); - } - } - - r = evdns_server_request_respond(req, 0); - if (r<0) - printf("eeek, couldn't send reply.\n"); -} - -void -logfn(int is_warn, const char *msg) { - (void) is_warn; - fprintf(stderr, "%s\n", msg); -} -int -main(int c, char **v) { - int idx; - int reverse = 0, verbose = 1, servertest = 0; - if (c<2) { - fprintf(stderr, "syntax: %s [-x] [-v] hostname\n", v[0]); - fprintf(stderr, "syntax: %s [-servertest]\n", v[0]); - return 1; - } - idx = 1; - while (idx < c && v[idx][0] == '-') { - if (!strcmp(v[idx], "-x")) - reverse = 1; - else if (!strcmp(v[idx], "-v")) - verbose = 1; - else if (!strcmp(v[idx], "-servertest")) - servertest = 1; - else - fprintf(stderr, "Unknown option %s\n", v[idx]); - ++idx; - } - event_init(); - if (verbose) - evdns_set_log_fn(logfn); - evdns_resolv_conf_parse(DNS_OPTION_NAMESERVERS, "/etc/resolv.conf"); - if (servertest) { - int sock; - struct sockaddr_in my_addr; - sock = tor_open_socket(PF_INET, SOCK_DGRAM, 0); - fcntl(sock, F_SETFL, O_NONBLOCK); - my_addr.sin_family = AF_INET; - my_addr.sin_port = htons(10053); - my_addr.sin_addr.s_addr = INADDR_ANY; - if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr))<0) { - perror("bind"); - exit(1); - } - evdns_add_server_port(sock, 0, evdns_server_callback, NULL); - } - for (; idx < c; ++idx) { - if (reverse) { - struct in_addr addr; - if (!inet_aton(v[idx], &addr)) { - fprintf(stderr, "Skipping non-IP %s\n", v[idx]); - continue; - } - fprintf(stderr, "resolving %s...\n",v[idx]); - evdns_resolve_reverse(&addr, 0, main_callback, v[idx]); - } else { - fprintf(stderr, "resolving (fwd) %s...\n",v[idx]); - evdns_resolve_ipv4(v[idx], 0, main_callback, v[idx]); - } - } - fflush(stdout); - event_dispatch(); - return 0; -} -#endif - -/* Local Variables: */ -/* tab-width: 4 */ -/* c-basic-offset: 4 */ -/* indent-tabs-mode: t */ -/* End: */ - diff --git a/src/or/eventdns.h b/src/or/eventdns.h deleted file mode 100644 index 1c130b2a1..000000000 --- a/src/or/eventdns.h +++ /dev/null @@ -1,337 +0,0 @@ - -/* - * The original DNS code is due to Adam Langley with heavy - * modifications by Nick Mathewson. Adam put his DNS software in the - * public domain. You can find his original copyright below. Please, - * aware that the code as part of libevent is governed by the 3-clause - * BSD license above. - * - * This software is Public Domain. To view a copy of the public domain dedication, - * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to - * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. - * - * I ask and expect, but do not require, that all derivative works contain an - * attribution similar to: - * Parts developed by Adam Langley <agl@imperialviolet.org> - * - * You may wish to replace the word "Parts" with something else depending on - * the amount of original code. - * - * (Derivative works does not include programs which link against, run or include - * the source verbatim in their source distributions) - */ - -/* - * Welcome, gentle reader - * - * Async DNS lookups are really a whole lot harder than they should be, - * mostly stemming from the fact that the libc resolver has never been - * very good at them. Before you use this library you should see if libc - * can do the job for you with the modern async call getaddrinfo_a - * (see http://www.imperialviolet.org/page25.html#e498). Otherwise, - * please continue. - * - * This code is based on libevent and you must call event_init before - * any of the APIs in this file. You must also seed the OpenSSL random - * source if you are using OpenSSL for ids (see below). - * - * This library is designed to be included and shipped with your source - * code. You statically link with it. You should also test for the - * existence of strtok_r and define HAVE_STRTOK_R if you have it. - * - * The DNS protocol requires a good source of id numbers and these - * numbers should be unpredictable for spoofing reasons. There are - * three methods for generating them here and you must define exactly - * one of them. In increasing order of preference: - * - * DNS_USE_GETTIMEOFDAY_FOR_ID: - * Using the bottom 16 bits of the usec result from gettimeofday. This - * is a pretty poor solution but should work anywhere. - * DNS_USE_CPU_CLOCK_FOR_ID: - * Using the bottom 16 bits of the nsec result from the CPU's time - * counter. This is better, but may not work everywhere. Requires - * POSIX realtime support and you'll need to link against -lrt on - * glibc systems at least. - * DNS_USE_OPENSSL_FOR_ID: - * Uses the OpenSSL RAND_bytes call to generate the data. You must - * have seeded the pool before making any calls to this library. - * - * The library keeps track of the state of nameservers and will avoid - * them when they go down. Otherwise it will round robin between them. - * - * Quick start guide: - * #include "evdns.h" - * void callback(int result, char type, int count, int ttl, - * void *addresses, void *arg); - * evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf"); - * evdns_resolve("www.hostname.com", 0, callback, NULL); - * - * When the lookup is complete the callback function is called. The - * first argument will be one of the DNS_ERR_* defines in evdns.h. - * Hopefully it will be DNS_ERR_NONE, in which case type will be - * DNS_IPv4_A, count will be the number of IP addresses, ttl is the time - * which the data can be cached for (in seconds), addresses will point - * to an array of uint32_t's and arg will be whatever you passed to - * evdns_resolve. - * - * Searching: - * - * In order for this library to be a good replacement for glibc's resolver it - * supports searching. This involves setting a list of default domains, in - * which names will be queried for. The number of dots in the query name - * determines the order in which this list is used. - * - * Searching appears to be a single lookup from the point of view of the API, - * although many DNS queries may be generated from a single call to - * evdns_resolve. Searching can also drastically slow down the resolution - * of names. - * - * To disable searching: - * 1. Never set it up. If you never call evdns_resolv_conf_parse or - * evdns_search_add then no searching will occur. - * - * 2. If you do call evdns_resolv_conf_parse then don't pass - * DNS_OPTION_SEARCH (or DNS_OPTIONS_ALL, which implies it). - * - * 3. When calling evdns_resolve, pass the DNS_QUERY_NO_SEARCH flag. - * - * The order of searches depends on the number of dots in the name. If the - * number is greater than the ndots setting then the names is first tried - * globally. Otherwise each search domain is appended in turn. - * - * The ndots setting can either be set from a resolv.conf, or by calling - * evdns_search_ndots_set. - * - * For example, with ndots set to 1 (the default) and a search domain list of - * ["myhome.net"]: - * Query: www - * Order: www.myhome.net, www. - * - * Query: www.abc - * Order: www.abc., www.abc.myhome.net - * - * API reference: - * - * int evdns_nameserver_add(uint32_t address) - * Add a nameserver. The address should be an IP address in - * network byte order. The type of address is chosen so that - * it matches in_addr.s_addr. - * Returns non-zero on error. - * - * int evdns_nameserver_ip_add(const char *ip_as_string) - * This wraps the above function by parsing a string as an IP - * address and adds it as a nameserver. - * Returns non-zero on error - * - * int evdns_resolve(const char *name, int flags, - * evdns_callback_type callback, - * void *ptr) - * Resolve a name. The name parameter should be a DNS name. - * The flags parameter should be 0, or DNS_QUERY_NO_SEARCH - * which disables searching for this query. (see defn of - * searching above). - * - * The callback argument is a function which is called when - * this query completes and ptr is an argument which is passed - * to that callback function. - * - * Returns non-zero on error - * - * void evdns_search_clear() - * Clears the list of search domains - * - * void evdns_search_add(const char *domain) - * Add a domain to the list of search domains - * - * void evdns_search_ndots_set(int ndots) - * Set the number of dots which, when found in a name, causes - * the first query to be without any search domain. - * - * int evdns_count_nameservers(void) - * Return the number of configured nameservers (not necessarily the - * number of running nameservers). This is useful for double-checking - * whether our calls to the various nameserver configuration functions - * have been successful. - * - * int evdns_clear_nameservers_and_suspend(void) - * Remove all currently configured nameservers, and suspend all pending - * resolves. Resolves will not necessarily be re-attempted until - * evdns_resume() is called. - * - * int evdns_resume(void) - * Re-attempt resolves left in limbo after an earlier call to - * evdns_clear_nameservers_and_suspend(). - * - * int evdns_config_windows_nameservers(void) - * Attempt to configure a set of nameservers based on platform settings on - * a win32 host. Preferentially tries to use GetNetworkParams; if that fails, - * looks in the registry. Returns 0 on success, nonzero on failure. - * - * int evdns_resolv_conf_parse(int flags, const char *filename) - * Parse a resolv.conf like file from the given filename. - * - * See the man page for resolv.conf for the format of this file. - * The flags argument determines what information is parsed from - * this file: - * DNS_OPTION_SEARCH - domain, search and ndots options - * DNS_OPTION_NAMESERVERS - nameserver lines - * DNS_OPTION_MISC - timeout and attempts options - * DNS_OPTIONS_ALL - all of the above - * The following directives are not parsed from the file: - * sortlist, rotate, no-check-names, inet6, debug - * - * Returns non-zero on error: - * 0 no errors - * 1 failed to open file - * 2 failed to stat file - * 3 file too large - * 4 out of memory - * 5 short read from file - * 6 no nameservers in file - * - * Internals: - * - * Requests are kept in two queues. The first is the inflight queue. In - * this queue requests have an allocated transaction id and nameserver. - * They will soon be transmitted if they haven't already been. - * - * The second is the waiting queue. The size of the inflight ring is - * limited and all other requests wait in waiting queue for space. This - * bounds the number of concurrent requests so that we don't flood the - * nameserver. Several algorithms require a full walk of the inflight - * queue and so bounding its size keeps thing going nicely under huge - * (many thousands of requests) loads. - * - * If a nameserver loses too many requests it is considered down and we - * try not to use it. After a while we send a probe to that nameserver - * (a lookup for google.com) and, if it replies, we consider it working - * again. If the nameserver fails a probe we wait longer to try again - * with the next probe. - */ - -#ifndef _TOR_EVENTDNS_H -#define _TOR_EVENTDNS_H - -/* Error codes 0-5 are as described in RFC 1035. */ -#define DNS_ERR_NONE 0 -/* The name server was unable to interpret the query */ -#define DNS_ERR_FORMAT 1 -/* The name server was unable to process this query due to a problem with the - * name server */ -#define DNS_ERR_SERVERFAILED 2 -/* The domain name does not exist */ -#define DNS_ERR_NOTEXIST 3 -/* The name server does not support the requested kind of query */ -#define DNS_ERR_NOTIMPL 4 -/* The name server refuses to reform the specified operation for policy - * reasons */ -#define DNS_ERR_REFUSED 5 -/* The reply was truncated or ill-formated */ -#define DNS_ERR_TRUNCATED 65 -/* An unknown error occurred */ -#define DNS_ERR_UNKNOWN 66 -/* Communication with the server timed out */ -#define DNS_ERR_TIMEOUT 67 -/* The request was canceled because the DNS subsystem was shut down. */ -#define DNS_ERR_SHUTDOWN 68 - -#define DNS_IPv4_A 1 -#define DNS_PTR 2 -#define DNS_IPv6_AAAA 3 - -#define DNS_QUERY_NO_SEARCH 1 - -#define DNS_OPTION_SEARCH 1 -#define DNS_OPTION_NAMESERVERS 2 -#define DNS_OPTION_MISC 4 -#define DNS_OPTIONS_ALL 7 - -/* - * The callback that contains the results from a lookup. - * - type is either DNS_IPv4_A or DNS_IPv6_AAAA or DNS_PTR - * - count contains the number of addresses of form type - * - ttl is the number of seconds the resolution may be cached for. - * - addresses needs to be cast according to type - */ -typedef void (*evdns_callback_type) (int result, char type, int count, int ttl, void *addresses, void *arg); - -int evdns_init(void); -void evdns_shutdown(int fail_requests); -const char *evdns_err_to_string(int err); -int evdns_nameserver_add(uint32_t address); -int evdns_count_nameservers(void); -int evdns_clear_nameservers_and_suspend(void); -int evdns_resume(void); -int evdns_nameserver_ip_add(const char *ip_as_string); -int evdns_nameserver_sockaddr_add(const struct sockaddr *sa, socklen_t len); -void evdns_set_default_outgoing_bind_address(const struct sockaddr *addr, socklen_t addrlen); -int evdns_resolve_ipv4(const char *name, int flags, evdns_callback_type callback, void *ptr); -int evdns_resolve_ipv6(const char *name, int flags, evdns_callback_type callback, void *ptr); -struct in_addr; -struct in6_addr; -int evdns_resolve_reverse(const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr); -int evdns_resolve_reverse_ipv6(const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr); -int evdns_set_option(const char *option, const char *val, int flags); -int evdns_resolv_conf_parse(int flags, const char *); -#ifdef _WIN32 -int evdns_config_windows_nameservers(void); -#endif -void evdns_search_clear(void); -void evdns_search_add(const char *domain); -void evdns_search_ndots_set(const int ndots); - -typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg); -void evdns_set_log_fn(evdns_debug_log_fn_type fn); - -void evdns_set_transaction_id_fn(uint16_t (*fn)(void)); -void evdns_set_random_bytes_fn(void (*fn)(char *, size_t)); - -#define DNS_NO_SEARCH 1 - -/* Structures and functions used to implement a DNS server. */ - -struct evdns_server_request { - int flags; - int nquestions; - struct evdns_server_question **questions; -}; -struct evdns_server_question { - int type; - int dns_question_class; - char name[1]; -}; -typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, void *); -#define EVDNS_ANSWER_SECTION 0 -#define EVDNS_AUTHORITY_SECTION 1 -#define EVDNS_ADDITIONAL_SECTION 2 - -#define EVDNS_TYPE_A 1 -#define EVDNS_TYPE_NS 2 -#define EVDNS_TYPE_CNAME 5 -#define EVDNS_TYPE_SOA 6 -#define EVDNS_TYPE_PTR 12 -#define EVDNS_TYPE_MX 15 -#define EVDNS_TYPE_TXT 16 -#define EVDNS_TYPE_AAAA 28 - -#define EVDNS_QTYPE_AXFR 252 -#define EVDNS_QTYPE_ALL 255 - -#define EVDNS_CLASS_INET 1 - -struct evdns_server_port *evdns_add_server_port(tor_socket_t socket, int is_tcp, evdns_request_callback_fn_type callback, void *user_data); -void evdns_close_server_port(struct evdns_server_port *port); - -int evdns_server_request_add_reply(struct evdns_server_request *req, int section, const char *name, int type, int class, int ttl, int datalen, int is_name, const char *data); -int evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl); -int evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl); -int evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, const char *inaddr_name, const char *hostname, int ttl); -int evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, const char *cname, int ttl); - -struct sockaddr; -int evdns_server_request_get_requesting_addr(struct evdns_server_request *req, struct sockaddr *sa, int addr_len); - -int evdns_server_request_respond(struct evdns_server_request *req, int err); -int evdns_server_request_drop(struct evdns_server_request *req); - -#endif // !EVENTDNS_H diff --git a/src/or/eventdns_tor.h b/src/or/eventdns_tor.h index 4c40b3524..0775643b5 100644 --- a/src/or/eventdns_tor.h +++ b/src/or/eventdns_tor.h @@ -1,6 +1,9 @@ /* Copyright (c) 2007-2012, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#ifndef TOR_EVENTDNS_TOR_H +#define TOR_EVENTDNS_TOR_H + #include "orconfig.h" #define DNS_USE_OPENSSL_FOR_ID #ifndef HAVE_UINT @@ -18,3 +21,5 @@ typedef unsigned char u_char; #include "util.h" #include "compat.h" +#endif + diff --git a/src/or/geoip.c b/src/or/geoip.c index 6b7cc82b8..72a1983cb 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -21,12 +21,19 @@ static void clear_geoip_db(void); static void init_geoip_countries(void); -/** An entry from the GeoIP file: maps an IP range to a country. */ -typedef struct geoip_entry_t { +/** An entry from the GeoIP IPv4 file: maps an IPv4 range to a country. */ +typedef struct geoip_ipv4_entry_t { uint32_t ip_low; /**< The lowest IP in the range, in host order */ uint32_t ip_high; /**< The highest IP in the range, in host order */ intptr_t country; /**< An index into geoip_countries */ -} geoip_entry_t; +} geoip_ipv4_entry_t; + +/** An entry from the GeoIP IPv6 file: maps an IPv6 range to a country. */ +typedef struct geoip_ipv6_entry_t { + struct in6_addr ip_low; /**< The lowest IP in the range, in host order */ + struct in6_addr ip_high; /**< The highest IP in the range, in host order */ + intptr_t country; /**< An index into geoip_countries */ +} geoip_ipv6_entry_t; /** A per-country record for GeoIP request history. */ typedef struct geoip_country_t { @@ -41,45 +48,48 @@ static smartlist_t *geoip_countries = NULL; * The index is encoded in the pointer, and 1 is added so that NULL can mean * not found. */ static strmap_t *country_idxplus1_by_lc_code = NULL; -/** A list of all known geoip_entry_t, sorted by ip_low. */ -static smartlist_t *geoip_entries = NULL; +/** Lists of all known geoip_ipv4_entry_t and geoip_ipv6_entry_t, sorted + * by their respective ip_low. */ +static smartlist_t *geoip_ipv4_entries = NULL, *geoip_ipv6_entries = NULL; -/** SHA1 digest of the GeoIP file to include in extra-info descriptors. */ +/** SHA1 digest of the GeoIP files to include in extra-info descriptors. */ static char geoip_digest[DIGEST_LEN]; +static char geoip6_digest[DIGEST_LEN]; -/** Return the index of the <b>country</b>'s entry in the GeoIP DB - * if it is a valid 2-letter country code, otherwise return -1. - */ +/** Return the index of the <b>country</b>'s entry in the GeoIP + * country list if it is a valid 2-letter country code, otherwise + * return -1. */ country_t geoip_get_country(const char *country) { - void *_idxplus1; + void *idxplus1_; intptr_t idx; - _idxplus1 = strmap_get_lc(country_idxplus1_by_lc_code, country); - if (!_idxplus1) + idxplus1_ = strmap_get_lc(country_idxplus1_by_lc_code, country); + if (!idxplus1_) return -1; - idx = ((uintptr_t)_idxplus1)-1; + idx = ((uintptr_t)idxplus1_)-1; return (country_t)idx; } -/** Add an entry to the GeoIP table, mapping all IPs between <b>low</b> and - * <b>high</b>, inclusive, to the 2-letter country code <b>country</b>. - */ +/** Add an entry to a GeoIP table, mapping all IP addresses between <b>low</b> + * and <b>high</b>, inclusive, to the 2-letter country code <b>country</b>. */ static void -geoip_add_entry(uint32_t low, uint32_t high, const char *country) +geoip_add_entry(const tor_addr_t *low, const tor_addr_t *high, + const char *country) { intptr_t idx; - geoip_entry_t *ent; - void *_idxplus1; + void *idxplus1_; - if (high < low) + if (tor_addr_family(low) != tor_addr_family(high)) + return; + if (tor_addr_compare(high, low, CMP_EXACT) < 0) return; - _idxplus1 = strmap_get_lc(country_idxplus1_by_lc_code, country); + idxplus1_ = strmap_get_lc(country_idxplus1_by_lc_code, country); - if (!_idxplus1) { + if (!idxplus1_) { geoip_country_t *c = tor_malloc_zero(sizeof(geoip_country_t)); strlcpy(c->countrycode, country, sizeof(c->countrycode)); tor_strlower(c->countrycode); @@ -87,54 +97,103 @@ geoip_add_entry(uint32_t low, uint32_t high, const char *country) idx = smartlist_len(geoip_countries) - 1; strmap_set_lc(country_idxplus1_by_lc_code, country, (void*)(idx+1)); } else { - idx = ((uintptr_t)_idxplus1)-1; + idx = ((uintptr_t)idxplus1_)-1; } { geoip_country_t *c = smartlist_get(geoip_countries, idx); tor_assert(!strcasecmp(c->countrycode, country)); } - ent = tor_malloc_zero(sizeof(geoip_entry_t)); - ent->ip_low = low; - ent->ip_high = high; - ent->country = idx; - smartlist_add(geoip_entries, ent); + + if (tor_addr_family(low) == AF_INET) { + geoip_ipv4_entry_t *ent = tor_malloc_zero(sizeof(geoip_ipv4_entry_t)); + ent->ip_low = tor_addr_to_ipv4h(low); + ent->ip_high = tor_addr_to_ipv4h(high); + ent->country = idx; + smartlist_add(geoip_ipv4_entries, ent); + } else if (tor_addr_family(low) == AF_INET6) { + geoip_ipv6_entry_t *ent = tor_malloc_zero(sizeof(geoip_ipv6_entry_t)); + ent->ip_low = *tor_addr_to_in6(low); + ent->ip_high = *tor_addr_to_in6(high); + ent->country = idx; + smartlist_add(geoip_ipv6_entries, ent); + } } -/** Add an entry to the GeoIP table, parsing it from <b>line</b>. The - * format is as for geoip_load_file(). */ +/** Add an entry to the GeoIP table indicated by <b>family</b>, + * parsing it from <b>line</b>. The format is as for geoip_load_file(). */ /*private*/ int -geoip_parse_entry(const char *line) +geoip_parse_entry(const char *line, sa_family_t family) { - unsigned int low, high; - char b[3]; + tor_addr_t low_addr, high_addr; + char c[3]; + char *country = NULL; + if (!geoip_countries) init_geoip_countries(); - if (!geoip_entries) - geoip_entries = smartlist_new(); + if (family == AF_INET) { + if (!geoip_ipv4_entries) + geoip_ipv4_entries = smartlist_new(); + } else if (family == AF_INET6) { + if (!geoip_ipv6_entries) + geoip_ipv6_entries = smartlist_new(); + } else { + log_warn(LD_GENERAL, "Unsupported family: %d", family); + return -1; + } while (TOR_ISSPACE(*line)) ++line; if (*line == '#') return 0; - if (tor_sscanf(line,"%u,%u,%2s", &low, &high, b) == 3) { - geoip_add_entry(low, high, b); - return 0; - } else if (tor_sscanf(line,"\"%u\",\"%u\",\"%2s\",", &low, &high, b) == 3) { - geoip_add_entry(low, high, b); - return 0; - } else { - log_warn(LD_GENERAL, "Unable to parse line from GEOIP file: %s", - escaped(line)); - return -1; + + if (family == AF_INET) { + unsigned int low, high; + if (tor_sscanf(line,"%u,%u,%2s", &low, &high, c) == 3 || + tor_sscanf(line,"\"%u\",\"%u\",\"%2s\",", &low, &high, c) == 3) { + tor_addr_from_ipv4h(&low_addr, low); + tor_addr_from_ipv4h(&high_addr, high); + } else + goto fail; + country = c; + } else { /* AF_INET6 */ + char buf[512]; + char *low_str, *high_str; + struct in6_addr low, high; + char *strtok_state; + strlcpy(buf, line, sizeof(buf)); + low_str = tor_strtok_r(buf, ",", &strtok_state); + if (!low_str) + goto fail; + high_str = tor_strtok_r(NULL, ",", &strtok_state); + if (!high_str) + goto fail; + country = tor_strtok_r(NULL, "\n", &strtok_state); + if (!country) + goto fail; + if (strlen(country) != 2) + goto fail; + if (tor_inet_pton(AF_INET6, low_str, &low) <= 0) + goto fail; + tor_addr_from_in6(&low_addr, &low); + if (tor_inet_pton(AF_INET6, high_str, &high) <= 0) + goto fail; + tor_addr_from_in6(&high_addr, &high); } + geoip_add_entry(&low_addr, &high_addr, country); + return 0; + + fail: + log_warn(LD_GENERAL, "Unable to parse line from GEOIP %s file: %s", + family == AF_INET ? "IPv4" : "IPv6", escaped(line)); + return -1; } /** Sorting helper: return -1, 1, or 0 based on comparison of two - * geoip_entry_t */ + * geoip_ipv4_entry_t */ static int -_geoip_compare_entries(const void **_a, const void **_b) +geoip_ipv4_compare_entries_(const void **_a, const void **_b) { - const geoip_entry_t *a = *_a, *b = *_b; + const geoip_ipv4_entry_t *a = *_a, *b = *_b; if (a->ip_low < b->ip_low) return -1; else if (a->ip_low > b->ip_low) @@ -144,13 +203,13 @@ _geoip_compare_entries(const void **_a, const void **_b) } /** bsearch helper: return -1, 1, or 0 based on comparison of an IP (a pointer - * to a uint32_t in host order) to a geoip_entry_t */ + * to a uint32_t in host order) to a geoip_ipv4_entry_t */ static int -_geoip_compare_key_to_entry(const void *_key, const void **_member) +geoip_ipv4_compare_key_to_entry_(const void *_key, const void **_member) { /* No alignment issue here, since _key really is a pointer to uint32_t */ const uint32_t addr = *(uint32_t *)_key; - const geoip_entry_t *entry = *_member; + const geoip_ipv4_entry_t *entry = *_member; if (addr < entry->ip_low) return -1; else if (addr > entry->ip_high) @@ -159,6 +218,34 @@ _geoip_compare_key_to_entry(const void *_key, const void **_member) return 0; } +/** Sorting helper: return -1, 1, or 0 based on comparison of two + * geoip_ipv6_entry_t */ +static int +geoip_ipv6_compare_entries_(const void **_a, const void **_b) +{ + const geoip_ipv6_entry_t *a = *_a, *b = *_b; + return fast_memcmp(a->ip_low.s6_addr, b->ip_low.s6_addr, + sizeof(struct in6_addr)); +} + +/** bsearch helper: return -1, 1, or 0 based on comparison of an IPv6 + * (a pointer to a in6_addr) to a geoip_ipv6_entry_t */ +static int +geoip_ipv6_compare_key_to_entry_(const void *_key, const void **_member) +{ + const struct in6_addr *addr = (struct in6_addr *)_key; + const geoip_ipv6_entry_t *entry = *_member; + + if (fast_memcmp(addr->s6_addr, entry->ip_low.s6_addr, + sizeof(struct in6_addr)) < 0) + return -1; + else if (fast_memcmp(addr->s6_addr, entry->ip_high.s6_addr, + sizeof(struct in6_addr)) > 0) + return 1; + else + return 0; +} + /** Return 1 if we should collect geoip stats on bridge users, and * include them in our extrainfo descriptor. Else return 0. */ int @@ -185,27 +272,35 @@ init_geoip_countries(void) strmap_set_lc(country_idxplus1_by_lc_code, "??", (void*)(1)); } -/** Clear the GeoIP database and reload it from the file - * <b>filename</b>. Return 0 on success, -1 on failure. +/** Clear appropriate GeoIP database, based on <b>family</b>, and + * reload it from the file <b>filename</b>. Return 0 on success, -1 on + * failure. * - * Recognized line formats are: + * Recognized line formats for IPv4 are: * INTIPLOW,INTIPHIGH,CC * and * "INTIPLOW","INTIPHIGH","CC","CC3","COUNTRY NAME" * where INTIPLOW and INTIPHIGH are IPv4 addresses encoded as 4-byte unsigned * integers, and CC is a country code. * + * Recognized line format for IPv6 is: + * IPV6LOW,IPV6HIGH,CC + * where IPV6LOW and IPV6HIGH are IPv6 addresses and CC is a country code. + * * It also recognizes, and skips over, blank lines and lines that start * with '#' (comments). */ int -geoip_load_file(const char *filename, const or_options_t *options) +geoip_load_file(sa_family_t family, const char *filename) { FILE *f; const char *msg = ""; + const or_options_t *options = get_options(); int severity = options_need_geoip_info(options, &msg) ? LOG_WARN : LOG_INFO; crypto_digest_t *geoip_digest_env = NULL; - clear_geoip_db(); + + tor_assert(family == AF_INET || family == AF_INET6); + if (!(f = tor_fopen_cloexec(filename, "r"))) { log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s. %s", filename, msg); @@ -213,33 +308,51 @@ geoip_load_file(const char *filename, const or_options_t *options) } if (!geoip_countries) init_geoip_countries(); - if (geoip_entries) { - SMARTLIST_FOREACH(geoip_entries, geoip_entry_t *, e, tor_free(e)); - smartlist_free(geoip_entries); + + if (family == AF_INET) { + if (geoip_ipv4_entries) { + SMARTLIST_FOREACH(geoip_ipv4_entries, geoip_ipv4_entry_t *, e, + tor_free(e)); + smartlist_free(geoip_ipv4_entries); + } + geoip_ipv4_entries = smartlist_new(); + } else { /* AF_INET6 */ + if (geoip_ipv6_entries) { + SMARTLIST_FOREACH(geoip_ipv6_entries, geoip_ipv6_entry_t *, e, + tor_free(e)); + smartlist_free(geoip_ipv6_entries); + } + geoip_ipv6_entries = smartlist_new(); } - geoip_entries = smartlist_new(); geoip_digest_env = crypto_digest_new(); - log_notice(LD_GENERAL, "Parsing GEOIP file %s.", filename); + + log_notice(LD_GENERAL, "Parsing GEOIP %s file %s.", + (family == AF_INET) ? "IPv4" : "IPv6", filename); while (!feof(f)) { char buf[512]; if (fgets(buf, (int)sizeof(buf), f) == NULL) break; crypto_digest_add_bytes(geoip_digest_env, buf, strlen(buf)); /* FFFF track full country name. */ - geoip_parse_entry(buf); + geoip_parse_entry(buf, family); } /*XXXX abort and return -1 if no entries/illformed?*/ fclose(f); - smartlist_sort(geoip_entries, _geoip_compare_entries); - - /* Okay, now we need to maybe change our mind about what is in which - * country. */ - refresh_all_country_info(); - - /* Remember file digest so that we can include it in our extra-info - * descriptors. */ - crypto_digest_get_digest(geoip_digest_env, geoip_digest, DIGEST_LEN); + /* Sort list and remember file digests so that we can include it in + * our extra-info descriptors. */ + if (family == AF_INET) { + smartlist_sort(geoip_ipv4_entries, geoip_ipv4_compare_entries_); + /* Okay, now we need to maybe change our mind about what is in + * which country. We do this for IPv4 only since that's what we + * store in node->country. */ + refresh_all_country_info(); + crypto_digest_get_digest(geoip_digest_env, geoip_digest, DIGEST_LEN); + } else { + /* AF_INET6 */ + smartlist_sort(geoip_ipv6_entries, geoip_ipv6_compare_entries_); + crypto_digest_get_digest(geoip_digest_env, geoip6_digest, DIGEST_LEN); + } crypto_digest_free(geoip_digest_env); return 0; @@ -252,12 +365,30 @@ geoip_load_file(const char *filename, const or_options_t *options) * geoip_get_country_name(). */ int -geoip_get_country_by_ip(uint32_t ipaddr) +geoip_get_country_by_ipv4(uint32_t ipaddr) +{ + geoip_ipv4_entry_t *ent; + if (!geoip_ipv4_entries) + return -1; + ent = smartlist_bsearch(geoip_ipv4_entries, &ipaddr, + geoip_ipv4_compare_key_to_entry_); + return ent ? (int)ent->country : 0; +} + +/** Given an IPv6 address, return a number representing the country to + * which that address belongs, -1 for "No geoip information available", or + * 0 for the 'unknown country'. The return value will always be less than + * geoip_get_n_countries(). To decode it, call geoip_get_country_name(). + */ +int +geoip_get_country_by_ipv6(const struct in6_addr *addr) { - geoip_entry_t *ent; - if (!geoip_entries) + geoip_ipv6_entry_t *ent; + + if (!geoip_ipv6_entries) return -1; - ent = smartlist_bsearch(geoip_entries, &ipaddr, _geoip_compare_key_to_entry); + ent = smartlist_bsearch(geoip_ipv6_entries, addr, + geoip_ipv6_compare_key_to_entry_); return ent ? (int)ent->country : 0; } @@ -269,14 +400,16 @@ geoip_get_country_by_ip(uint32_t ipaddr) int geoip_get_country_by_addr(const tor_addr_t *addr) { - if (tor_addr_family(addr) != AF_INET) { - /*XXXX IP6 support ipv6 geoip.*/ + if (tor_addr_family(addr) == AF_INET) { + return geoip_get_country_by_ipv4(tor_addr_to_ipv4h(addr)); + } else if (tor_addr_family(addr) == AF_INET6) { + return geoip_get_country_by_ipv6(tor_addr_to_in6(addr)); + } else { return -1; } - return geoip_get_country_by_ip(tor_addr_to_ipv4h(addr)); } -/** Return the number of countries recognized by the GeoIP database. */ +/** Return the number of countries recognized by the GeoIP country list. */ int geoip_get_n_countries(void) { @@ -299,18 +432,28 @@ geoip_get_country_name(country_t num) /** Return true iff we have loaded a GeoIP database.*/ int -geoip_is_loaded(void) +geoip_is_loaded(sa_family_t family) { - return geoip_countries != NULL && geoip_entries != NULL; + tor_assert(family == AF_INET || family == AF_INET6); + if (geoip_countries == NULL) + return 0; + if (family == AF_INET) + return geoip_ipv4_entries != NULL; + else /* AF_INET6 */ + return geoip_ipv6_entries != NULL; } /** Return the hex-encoded SHA1 digest of the loaded GeoIP file. The * result does not need to be deallocated, but will be overwritten by the * next call of hex_str(). */ const char * -geoip_db_digest(void) +geoip_db_digest(sa_family_t family) { - return hex_str(geoip_digest, DIGEST_LEN); + tor_assert(family == AF_INET || family == AF_INET6); + if (family == AF_INET) + return hex_str(geoip_digest, DIGEST_LEN); + else /* AF_INET6 */ + return hex_str(geoip6_digest, DIGEST_LEN); } /** Entry in a map from IP address to the last time we've seen an incoming @@ -489,7 +632,7 @@ geoip_note_client_seen(geoip_client_action_t action, /** HT_FOREACH helper: remove a clientmap_entry_t from the hashtable if it's * older than a certain time. */ static int -_remove_old_client_helper(struct clientmap_entry_t *ent, void *_cutoff) +remove_old_client_helper_(struct clientmap_entry_t *ent, void *_cutoff) { time_t cutoff = *(time_t*)_cutoff / 60; if (ent->last_seen_in_minutes < cutoff) { @@ -505,7 +648,7 @@ void geoip_remove_old_clients(time_t cutoff) { clientmap_HT_FOREACH_FN(&client_history, - _remove_old_client_helper, + remove_old_client_helper_, &cutoff); } @@ -559,10 +702,10 @@ typedef struct c_hist_t { } c_hist_t; /** Sorting helper: return -1, 1, or 0 based on comparison of two - * geoip_entry_t. Sort in descending order of total, and then by country + * geoip_ipv4_entry_t. Sort in descending order of total, and then by country * code. */ static int -_c_hist_compare(const void **_a, const void **_b) +c_hist_compare_(const void **_a, const void **_b) { const c_hist_t *a = *_a, *b = *_b; if (a->total > b->total) @@ -578,7 +721,7 @@ _c_hist_compare(const void **_a, const void **_b) * failed, the others as still running. */ #define DIRREQ_TIMEOUT (10*60) -/** Entry in a map from either conn->global_identifier for direct requests +/** Entry in a map from either chan->global_identifier for direct requests * or a unique circuit identifier for tunneled requests to request time, * response size, and completion time of a network status request. Used to * measure download times of requests to derive average client @@ -586,7 +729,7 @@ _c_hist_compare(const void **_a, const void **_b) typedef struct dirreq_map_entry_t { HT_ENTRY(dirreq_map_entry_t) node; /** Unique identifier for this network status request; this is either the - * conn->global_identifier of the dir conn (direct request) or a new + * chan->global_identifier of the dir channel (direct request) or a new * locally unique identifier of a circuit (tunneled request). This ID is * only unique among other direct or tunneled requests, respectively. */ uint64_t dirreq_id; @@ -631,7 +774,7 @@ HT_GENERATE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, * <b>type</b> and <b>dirreq_id</b> as key parts. If there is * already an entry for that key, print out a BUG warning and return. */ static void -_dirreq_map_put(dirreq_map_entry_t *entry, dirreq_type_t type, +dirreq_map_put_(dirreq_map_entry_t *entry, dirreq_type_t type, uint64_t dirreq_id) { dirreq_map_entry_t *old_ent; @@ -653,7 +796,7 @@ _dirreq_map_put(dirreq_map_entry_t *entry, dirreq_type_t type, * using <b>type</b> and <b>dirreq_id</b> as key parts. If there * is no such entry, return NULL. */ static dirreq_map_entry_t * -_dirreq_map_get(dirreq_type_t type, uint64_t dirreq_id) +dirreq_map_get_(dirreq_type_t type, uint64_t dirreq_id) { dirreq_map_entry_t lookup; lookup.type = type; @@ -678,7 +821,7 @@ geoip_start_dirreq(uint64_t dirreq_id, size_t response_size, ent->response_size = response_size; ent->action = action; ent->type = type; - _dirreq_map_put(ent, type, dirreq_id); + dirreq_map_put_(ent, type, dirreq_id); } /** Change the state of the either direct or tunneled (see <b>type</b>) @@ -694,7 +837,7 @@ geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type, dirreq_map_entry_t *ent; if (!get_options()->DirReqStatistics) return; - ent = _dirreq_map_get(type, dirreq_id); + ent = dirreq_map_get_(type, dirreq_id); if (!ent) return; if (new_state == DIRREQ_IS_FOR_NETWORK_STATUS) @@ -705,7 +848,7 @@ geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type, if ((type == DIRREQ_DIRECT && new_state == DIRREQ_FLUSHING_DIR_CONN_FINISHED) || (type == DIRREQ_TUNNELED && - new_state == DIRREQ_OR_CONN_BUFFER_FLUSHED)) { + new_state == DIRREQ_CHANNEL_BUFFER_FLUSHED)) { tor_gettimeofday(&ent->completion_time); ent->completed = 1; } @@ -813,27 +956,35 @@ geoip_get_dirreq_history(geoip_client_action_t action, return result; } -/** Return a newly allocated comma-separated string containing entries for - * all the countries from which we've seen enough clients connect as a - * bridge, directory server, or entry guard. The entry format is cc=num - * where num is the number of IPs we've seen connecting from that country, - * and cc is a lowercased country code. Returns NULL if we don't want - * to export geoip data yet. */ -char * -geoip_get_client_history(geoip_client_action_t action) +/** Store a newly allocated comma-separated string in + * *<a>country_str</a> containing entries for all the countries from + * which we've seen enough clients connect as a bridge, directory + * server, or entry guard. The entry format is cc=num where num is the + * number of IPs we've seen connecting from that country, and cc is a + * lowercased country code. *<a>country_str</a> is set to NULL if + * we're not ready to export per country data yet. + * + * Store a newly allocated comma-separated string in <a>ipver_str</a> + * containing entries for clients connecting over IPv4 and IPv6. The + * format is family=num where num is the nubmer of IPs we've seen + * connecting over that protocol family, and family is 'v4' or 'v6'. + * + * Return 0 on success and -1 if we're missing geoip data. */ +int +geoip_get_client_history(geoip_client_action_t action, + char **country_str, char **ipver_str) { - char *result = NULL; unsigned granularity = IP_GRANULARITY; - smartlist_t *chunks = NULL; smartlist_t *entries = NULL; int n_countries = geoip_get_n_countries(); int i; clientmap_entry_t **ent; unsigned *counts = NULL; unsigned total = 0; + unsigned ipv4_count = 0, ipv6_count = 0; - if (!geoip_is_loaded()) - return NULL; + if (!geoip_is_loaded(AF_INET) && !geoip_is_loaded(AF_INET6)) + return -1; counts = tor_malloc_zero(sizeof(unsigned)*n_countries); HT_FOREACH(ent, clientmap, &client_history) { @@ -846,10 +997,34 @@ geoip_get_client_history(geoip_client_action_t action) tor_assert(0 <= country && country < n_countries); ++counts[country]; ++total; + switch (tor_addr_family(&(*ent)->addr)) { + case AF_INET: + ipv4_count++; + break; + case AF_INET6: + ipv6_count++; + break; + } } - /* Don't record anything if we haven't seen enough IPs. */ - if (total < MIN_IPS_TO_NOTE_ANYTHING) - goto done; + if (ipver_str) { + smartlist_t *chunks = smartlist_new(); + smartlist_add_asprintf(chunks, "v4=%u", + round_to_next_multiple_of(ipv4_count, granularity)); + smartlist_add_asprintf(chunks, "v6=%u", + round_to_next_multiple_of(ipv6_count, granularity)); + *ipver_str = smartlist_join_strings(chunks, ",", 0, NULL); + SMARTLIST_FOREACH(chunks, char *, c, tor_free(c)); + smartlist_free(chunks); + } + + /* Don't record per country data if we haven't seen enough IPs. */ + if (total < MIN_IPS_TO_NOTE_ANYTHING) { + tor_free(counts); + if (country_str) + *country_str = NULL; + return 0; + } + /* Make a list of c_hist_t */ entries = smartlist_new(); for (i = 0; i < n_countries; ++i) { @@ -868,25 +1043,23 @@ geoip_get_client_history(geoip_client_action_t action) } /* Sort entries. Note that we must do this _AFTER_ rounding, or else * the sort order could leak info. */ - smartlist_sort(entries, _c_hist_compare); - - /* Build the result. */ - chunks = smartlist_new(); - SMARTLIST_FOREACH(entries, c_hist_t *, ch, { - smartlist_add_asprintf(chunks, "%s=%u", ch->country, ch->total); - }); - result = smartlist_join_strings(chunks, ",", 0, NULL); - done: - tor_free(counts); - if (chunks) { + smartlist_sort(entries, c_hist_compare_); + + if (country_str) { + smartlist_t *chunks = smartlist_new(); + SMARTLIST_FOREACH(entries, c_hist_t *, ch, { + smartlist_add_asprintf(chunks, "%s=%u", ch->country, ch->total); + }); + *country_str = smartlist_join_strings(chunks, ",", 0, NULL); SMARTLIST_FOREACH(chunks, char *, c, tor_free(c)); smartlist_free(chunks); } - if (entries) { - SMARTLIST_FOREACH(entries, c_hist_t *, c, tor_free(c)); - smartlist_free(entries); - } - return result; + + SMARTLIST_FOREACH(entries, c_hist_t *, c, tor_free(c)); + smartlist_free(entries); + tor_free(counts); + + return 0; } /** Return a newly allocated string holding the per-country request history @@ -918,7 +1091,7 @@ geoip_get_request_history(geoip_client_action_t action) ent->total = round_to_next_multiple_of(tot, granularity); smartlist_add(entries, ent); } SMARTLIST_FOREACH_END(c); - smartlist_sort(entries, _c_hist_compare); + smartlist_sort(entries, c_hist_compare_); strings = smartlist_new(); SMARTLIST_FOREACH(entries, c_hist_t *, ent, { @@ -1009,8 +1182,9 @@ geoip_format_dirreq_stats(time_t now) tor_assert(now >= start_of_dirreq_stats_interval); format_iso_time(t, now); - v2_ips_string = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS_V2); - v3_ips_string = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS); + geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS_V2, &v2_ips_string, + NULL); + geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS, &v3_ips_string, NULL); v2_reqs_string = geoip_get_request_history( GEOIP_CLIENT_NETWORKSTATUS_V2); v3_reqs_string = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS); @@ -1216,7 +1390,7 @@ static char *bridge_stats_extrainfo = NULL; char * geoip_format_bridge_stats(time_t now) { - char *out = NULL, *data = NULL; + char *out = NULL, *country_data = NULL, *ipver_data = NULL; long duration = now - start_of_bridge_stats_interval; char written[ISO_TIME_LEN+1]; @@ -1226,14 +1400,17 @@ geoip_format_bridge_stats(time_t now) return NULL; /* Not initialized. */ format_iso_time(written, now); - data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); + geoip_get_client_history(GEOIP_CLIENT_CONNECT, &country_data, &ipver_data); tor_asprintf(&out, "bridge-stats-end %s (%ld s)\n" - "bridge-ips %s\n", + "bridge-ips %s\n" + "bridge-ip-versions %s\n", written, duration, - data ? data : ""); - tor_free(data); + country_data ? country_data : "", + ipver_data ? ipver_data : ""); + tor_free(country_data); + tor_free(ipver_data); return out; } @@ -1244,17 +1421,20 @@ geoip_format_bridge_stats(time_t now) static char * format_bridge_stats_controller(time_t now) { - char *out = NULL, *data = NULL; + char *out = NULL, *country_data = NULL, *ipver_data = NULL; char started[ISO_TIME_LEN+1]; (void) now; format_iso_time(started, start_of_bridge_stats_interval); - data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); + geoip_get_client_history(GEOIP_CLIENT_CONNECT, &country_data, &ipver_data); tor_asprintf(&out, - "TimeStarted=\"%s\" CountrySummary=%s", - started, data ? data : ""); - tor_free(data); + "TimeStarted=\"%s\" CountrySummary=%s IPVersions=%s", + started, + country_data ? country_data : "", + ipver_data ? ipver_data : ""); + tor_free(country_data); + tor_free(ipver_data); return out; } @@ -1381,11 +1561,13 @@ geoip_format_entry_stats(time_t now) tor_assert(now >= start_of_entry_stats_interval); - data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); + geoip_get_client_history(GEOIP_CLIENT_CONNECT, &data, NULL); format_iso_time(t, now); - tor_asprintf(&result, "entry-stats-end %s (%u s)\nentry-ips %s\n", - t, (unsigned) (now - start_of_entry_stats_interval), - data ? data : ""); + tor_asprintf(&result, + "entry-stats-end %s (%u s)\n" + "entry-ips %s\n", + t, (unsigned) (now - start_of_entry_stats_interval), + data ? data : ""); tor_free(data); return result; } @@ -1437,25 +1619,30 @@ getinfo_helper_geoip(control_connection_t *control_conn, const char **errmsg) { (void)control_conn; - if (!geoip_is_loaded()) { - *errmsg = "GeoIP data not loaded"; - return -1; - } if (!strcmpstart(question, "ip-to-country/")) { int c; - uint32_t ip; - struct in_addr in; + sa_family_t family; + tor_addr_t addr; question += strlen("ip-to-country/"); - if (tor_inet_aton(question, &in) != 0) { - ip = ntohl(in.s_addr); - c = geoip_get_country_by_ip(ip); - *answer = tor_strdup(geoip_get_country_name(c)); + family = tor_addr_parse(&addr, question); + if (family != AF_INET && family != AF_INET6) { + *errmsg = "Invalid address family"; + return -1; } + if (!geoip_is_loaded(family)) { + *errmsg = "GeoIP data not loaded"; + return -1; + } + if (family == AF_INET) + c = geoip_get_country_by_ipv4(tor_addr_to_ipv4h(&addr)); + else /* AF_INET6 */ + c = geoip_get_country_by_ipv6(tor_addr_to_in6(&addr)); + *answer = tor_strdup(geoip_get_country_name(c)); } return 0; } -/** Release all storage held by the GeoIP database. */ +/** Release all storage held by the GeoIP databases and country list. */ static void clear_geoip_db(void) { @@ -1465,13 +1652,20 @@ clear_geoip_db(void) } strmap_free(country_idxplus1_by_lc_code, NULL); - if (geoip_entries) { - SMARTLIST_FOREACH(geoip_entries, geoip_entry_t *, ent, tor_free(ent)); - smartlist_free(geoip_entries); + if (geoip_ipv4_entries) { + SMARTLIST_FOREACH(geoip_ipv4_entries, geoip_ipv4_entry_t *, ent, + tor_free(ent)); + smartlist_free(geoip_ipv4_entries); + } + if (geoip_ipv6_entries) { + SMARTLIST_FOREACH(geoip_ipv6_entries, geoip_ipv6_entry_t *, ent, + tor_free(ent)); + smartlist_free(geoip_ipv6_entries); } geoip_countries = NULL; country_idxplus1_by_lc_code = NULL; - geoip_entries = NULL; + geoip_ipv4_entries = NULL; + geoip_ipv6_entries = NULL; } /** Release all storage held in this file. */ diff --git a/src/or/geoip.h b/src/or/geoip.h index 4aed4e07b..227248647 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -9,20 +9,21 @@ * \brief Header file for geoip.c. **/ -#ifndef _TOR_GEOIP_H -#define _TOR_GEOIP_H +#ifndef TOR_GEOIP_H +#define TOR_GEOIP_H #ifdef GEOIP_PRIVATE -int geoip_parse_entry(const char *line); +int geoip_parse_entry(const char *line, sa_family_t family); +int geoip_get_country_by_ipv4(uint32_t ipaddr); +int geoip_get_country_by_ipv6(const struct in6_addr *addr); #endif int should_record_bridge_info(const or_options_t *options); -int geoip_load_file(const char *filename, const or_options_t *options); -int geoip_get_country_by_ip(uint32_t ipaddr); +int geoip_load_file(sa_family_t family, const char *filename); int geoip_get_country_by_addr(const tor_addr_t *addr); int geoip_get_n_countries(void); const char *geoip_get_country_name(country_t num); -int geoip_is_loaded(void); -const char *geoip_db_digest(void); +int geoip_is_loaded(sa_family_t family); +const char *geoip_db_digest(sa_family_t family); country_t geoip_get_country(const char *countrycode); void geoip_note_client_seen(geoip_client_action_t action, @@ -31,7 +32,8 @@ void geoip_remove_old_clients(time_t cutoff); void geoip_note_ns_response(geoip_client_action_t action, geoip_ns_response_t response); -char *geoip_get_client_history(geoip_client_action_t action); +int geoip_get_client_history(geoip_client_action_t action, + char **country_str, char **ipver_str); char *geoip_get_request_history(geoip_client_action_t action); int getinfo_helper_geoip(control_connection_t *control_conn, const char *question, char **answer, diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 3a9c1e422..72089962a 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -23,12 +23,15 @@ hibernating, phase 2: #define HIBERNATE_PRIVATE #include "or.h" +#include "channel.h" +#include "channeltls.h" #include "config.h" #include "connection.h" #include "connection_edge.h" #include "hibernate.h" #include "main.h" #include "router.h" +#include "statefile.h" extern long stats_n_seconds_working; /* published uptime */ @@ -845,7 +848,13 @@ hibernate_go_dormant(time_t now) if (conn->type == CONN_TYPE_AP) /* send socks failure if needed */ connection_mark_unattached_ap(TO_ENTRY_CONN(conn), END_STREAM_REASON_HIBERNATING); - else + else if (conn->type == CONN_TYPE_OR) { + if (TO_OR_CONN(conn)->chan) { + channel_mark_for_close(TLS_CHAN_TO_BASE(TO_OR_CONN(conn)->chan)); + } else { + connection_mark_for_close(conn); + } + } else connection_mark_for_close(conn); } @@ -881,12 +890,12 @@ hibernate_end_time_elapsed(time_t now) /* We weren't sleeping before; we should sleep now. */ log_notice(LD_ACCT, "Accounting period ended. Commencing hibernation until " - "%s GMT", buf); + "%s UTC", buf); hibernate_go_dormant(now); } else { log_notice(LD_ACCT, "Accounting period ended. This period, we will hibernate" - " until %s GMT",buf); + " until %s UTC",buf); } } } diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 9aa026b7b..5f99cde01 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -9,8 +9,8 @@ * \brief Header file for hibernate.c. **/ -#ifndef _TOR_HIBERNATE_H -#define _TOR_HIBERNATE_H +#ifndef TOR_HIBERNATE_H +#define TOR_HIBERNATE_H int accounting_parse_options(const or_options_t *options, int validate_only); int accounting_is_enabled(const or_options_t *options); diff --git a/src/or/include.am b/src/or/include.am new file mode 100644 index 000000000..08a27e17b --- /dev/null +++ b/src/or/include.am @@ -0,0 +1,193 @@ +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/ext/eventdns.c +endif + +if CURVE25519_ENABLED +onion_ntor_source=src/or/onion_ntor.c +else +onion_ntor_source= +endif + +src_or_libtor_a_SOURCES = \ + src/or/addressmap.c \ + src/or/buffers.c \ + src/or/channel.c \ + src/or/channeltls.c \ + src/or/circuitbuild.c \ + src/or/circuitlist.c \ + src/or/circuitmux.c \ + src/or/circuitmux_ewma.c \ + src/or/circuitstats.c \ + src/or/circuituse.c \ + src/or/command.c \ + src/or/config.c \ + src/or/confparse.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/entrynodes.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/onion_fast.c \ + src/or/onion_tap.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/routerset.c \ + src/or/statefile.c \ + src/or/status.c \ + $(evdns_source) \ + $(tor_platform_source) \ + $(onion_ntor_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.o: 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 $(LIBDONNA) \ + src/common/libor-event.a \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ + +ORHEADERS = \ + src/or/addressmap.h \ + src/or/buffers.h \ + src/or/channel.h \ + src/or/channeltls.h \ + src/or/circuitbuild.h \ + src/or/circuitlist.h \ + src/or/circuitmux.h \ + src/or/circuitmux_ewma.h \ + src/or/circuitstats.h \ + src/or/circuituse.h \ + src/or/command.h \ + src/or/config.h \ + src/or/confparse.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_tor.h \ + src/or/geoip.h \ + src/or/entrynodes.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/onion_fast.h \ + src/or/onion_ntor.h \ + src/or/onion_tap.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/routerset.h \ + src/or/routerparse.h \ + src/or/statefile.h \ + src/or/status.h + +noinst_HEADERS+= $(ORHEADERS) 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) $(ORHEADERS) + $(AM_V_GEN)if test "@SHA1SUM@" != none; then \ + (cd "$(srcdir)" && "@SHA1SUM@" $(src_or_tor_SOURCES) $(src_or_libtor_a_SOURCES) $(ORHEADERS) ) | \ + "@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) $(ORHEADERS)) | \ + "@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 34bf3e50f..abb1e34fc 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -12,7 +12,10 @@ #define MAIN_PRIVATE #include "or.h" +#include "addressmap.h" #include "buffers.h" +#include "channel.h" +#include "channeltls.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" @@ -28,6 +31,7 @@ #include "dirvote.h" #include "dns.h" #include "dnsserv.h" +#include "entrynodes.h" #include "geoip.h" #include "hibernate.h" #include "main.h" @@ -46,6 +50,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "statefile.h" #include "status.h" #ifdef USE_DMALLOC #include <dmalloc.h> @@ -397,6 +402,18 @@ connection_unlink(connection_t *conn) if (conn->type == CONN_TYPE_OR) { if (!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest)) connection_or_remove_from_identity_map(TO_OR_CONN(conn)); + /* connection_unlink() can only get called if the connection + * was already on the closeable list, and it got there by + * connection_mark_for_close(), which was called from + * connection_or_close_normally() or + * connection_or_close_for_error(), so the channel should + * already be in CHANNEL_STATE_CLOSING, and then the + * connection_about_to_close_connection() goes to + * connection_or_about_to_close(), which calls channel_closed() + * to notify the channel_t layer, and closed the channel, so + * nothing more to do here to deal with the channel associated + * with an orconn. + */ } connection_free(conn); } @@ -796,7 +813,8 @@ conn_close_if_marked(int i) } #endif - log_debug(LD_NET,"Cleaning up connection (fd %d).",conn->s); + log_debug(LD_NET,"Cleaning up connection (fd "TOR_SOCKET_T_FORMAT").", + conn->s); /* If the connection we are about to close was trying to connect to a proxy server and failed, the client won't be able to use that @@ -953,7 +971,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()); @@ -1044,7 +1063,8 @@ run_connection_housekeeping(int i, time_t now) tor_assert(conn->outbuf); #endif - if (or_conn->is_bad_for_new_circs && !or_conn->n_circuits) { + if (channel_is_bad_for_new_circs(TLS_CHAN_TO_BASE(or_conn->chan)) && + !connection_or_get_num_circuits(or_conn)) { /* It's bad for new circuits, and has no unmarked circuits on it: * mark it now. */ log_info(LD_OR, @@ -1054,28 +1074,29 @@ run_connection_housekeeping(int i, time_t now) connection_or_connect_failed(TO_OR_CONN(conn), END_OR_CONN_REASON_TIMEOUT, "Tor gave up on the connection"); - connection_mark_and_flush(conn); + connection_or_close_normally(TO_OR_CONN(conn), 1); } else if (!connection_state_is_open(conn)) { if (past_keepalive) { /* We never managed to actually get this connection open and happy. */ log_info(LD_OR,"Expiring non-open OR connection to fd %d (%s:%d).", (int)conn->s,conn->address, conn->port); - connection_mark_for_close(conn); + connection_or_close_normally(TO_OR_CONN(conn), 0); } - } else if (we_are_hibernating() && !or_conn->n_circuits && + } else if (we_are_hibernating() && + !connection_or_get_num_circuits(or_conn) && !connection_get_outbuf_len(conn)) { /* We're hibernating, there's no circuits, and nothing to flush.*/ log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " "[Hibernating or exiting].", (int)conn->s,conn->address, conn->port); - connection_mark_and_flush(conn); - } else if (!or_conn->n_circuits && + connection_or_close_normally(TO_OR_CONN(conn), 1); + } else if (!connection_or_get_num_circuits(or_conn) && now >= or_conn->timestamp_last_added_nonpadding + IDLE_OR_CONN_TIMEOUT) { log_info(LD_OR,"Expiring non-used OR connection to fd %d (%s:%d) " "[idle %d].", (int)conn->s,conn->address, conn->port, (int)(now - or_conn->timestamp_last_added_nonpadding)); - connection_mark_for_close(conn); + connection_or_close_normally(TO_OR_CONN(conn), 0); } else if ( now >= or_conn->timestamp_lastempty + options->KeepalivePeriod*10 && now >= conn->timestamp_lastwritten + options->KeepalivePeriod*10) { @@ -1085,7 +1106,7 @@ run_connection_housekeeping(int i, time_t now) (int)conn->s, conn->address, conn->port, (int)connection_get_outbuf_len(conn), (int)(now-conn->timestamp_lastwritten)); - connection_mark_for_close(conn); + connection_or_close_normally(TO_OR_CONN(conn), 0); } else if (past_keepalive && !connection_get_outbuf_len(conn)) { /* send a padding cell */ log_fn(LOG_DEBUG,LD_OR,"Sending keepalive to (%s:%d)", @@ -1519,6 +1540,10 @@ run_scheduled_events(time_t now) * flush it. */ or_state_save(now); + /** 8c. Do channel cleanup just like for connections */ + channel_run_cleanup(); + channel_listener_run_cleanup(); + /** 9. and if we're a server, check whether our DNS is telling stories to * us. */ if (!net_is_disabled() && @@ -1546,11 +1571,15 @@ run_scheduled_events(time_t now) options->PortForwarding && is_server) { #define PORT_FORWARDING_CHECK_INTERVAL 5 - /* XXXXX this should take a list of ports, not just two! */ - tor_check_port_forwarding(options->PortForwardingHelper, - get_primary_dir_port(), - get_primary_or_port(), - now); + smartlist_t *ports_to_forward = get_list_of_ports_to_forward(); + if (ports_to_forward) { + tor_check_port_forwarding(options->PortForwardingHelper, + ports_to_forward, + now); + + SMARTLIST_FOREACH(ports_to_forward, char *, cp, tor_free(cp)); + smartlist_free(ports_to_forward); + } time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; } @@ -1561,7 +1590,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; } } @@ -2144,6 +2174,10 @@ dumpstats(int severity) circuit_dump_by_conn(conn, severity); /* dump info about all the circuits * using this conn */ } SMARTLIST_FOREACH_END(conn); + + channel_dumpstats(severity); + channel_listener_dumpstats(severity); + log(severity, LD_NET, "Cells processed: "U64_FORMAT" padding\n" " "U64_FORMAT" create\n" @@ -2286,6 +2320,9 @@ tor_init(int argc, char *argv[]) quiet = 1; if (!strcmp(argv[i], "--quiet")) quiet = 2; + /* --version implies --quiet */ + if (!strcmp(argv[i], "--version")) + quiet = 2; } /* give it somewhere to log to initially */ switch (quiet) { @@ -2302,12 +2339,17 @@ tor_init(int argc, char *argv[]) { const char *version = get_version(); + const char *bev_str = #ifdef USE_BUFFEREVENTS - log_notice(LD_GENERAL, "Tor v%s (with bufferevents) running on %s.", - version, get_uname()); + "(with bufferevents) "; #else - log_notice(LD_GENERAL, "Tor v%s running on %s.", version, get_uname()); + ""; #endif + log_notice(LD_GENERAL, "Tor v%s %srunning on %s with Libevent %s " + "and OpenSSL %s.", version, bev_str, + get_uname(), + tor_libevent_get_version_str(), + crypto_openssl_get_version_str()); log_notice(LD_GENERAL, "Tor can't help you if you use it wrong! " "Learn how to be safe at " @@ -2441,6 +2483,8 @@ tor_free_all(int postfork) circuit_free_all(); entry_guards_free_all(); pt_free_all(); + channel_tls_free_all(); + channel_free_all(); connection_free_all(); buf_shrink_freelists(1); memarea_clear_freelist(); @@ -2448,6 +2492,7 @@ tor_free_all(int postfork) microdesc_free_all(); if (!postfork) { config_free_all(); + or_state_free_all(); router_free_all(); policies_free_all(); } @@ -2461,6 +2506,10 @@ tor_free_all(int postfork) smartlist_free(closeable_connection_lst); smartlist_free(active_linked_connection_lst); periodic_timer_free(second_timer); +#ifndef USE_BUFFEREVENTS + periodic_timer_free(refill_timer); +#endif + if (!postfork) { release_lockfile(); } @@ -2631,7 +2680,7 @@ tor_main(int argc, char *argv[]) { /* Instruct OpenSSL to use our internal wrappers for malloc, realloc and free. */ - int r = CRYPTO_set_mem_ex_functions(_tor_malloc, _tor_realloc, _tor_free); + int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_); tor_assert(r); } #endif diff --git a/src/or/main.h b/src/or/main.h index f843b6f9f..da2bcfd49 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -9,8 +9,8 @@ * \brief Header file for main.c. **/ -#ifndef _TOR_MAIN_H -#define _TOR_MAIN_H +#ifndef TOR_MAIN_H +#define TOR_MAIN_H extern int can_complete_circuit; diff --git a/src/or/microdesc.c b/src/or/microdesc.c index b4d22c1c6..a48429913 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -6,6 +6,7 @@ #include "config.h" #include "directory.h" #include "dirserv.h" +#include "entrynodes.h" #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" @@ -42,7 +43,7 @@ struct microdesc_cache_t { /** Helper: computes a hash of <b>md</b> to place it in a hash table. */ static INLINE unsigned int -_microdesc_hash(microdesc_t *md) +microdesc_hash_(microdesc_t *md) { unsigned *d = (unsigned*)md->digest; #if SIZEOF_INT == 4 @@ -54,15 +55,15 @@ _microdesc_hash(microdesc_t *md) /** Helper: compares <b>a</b> and </b> for equality for hash-table purposes. */ static INLINE int -_microdesc_eq(microdesc_t *a, microdesc_t *b) +microdesc_eq_(microdesc_t *a, microdesc_t *b) { return tor_memeq(a->digest, b->digest, DIGEST256_LEN); } HT_PROTOTYPE(microdesc_map, microdesc_t, node, - _microdesc_hash, _microdesc_eq); + microdesc_hash_, microdesc_eq_); HT_GENERATE(microdesc_map, microdesc_t, node, - _microdesc_hash, _microdesc_eq, 0.6, + microdesc_hash_, microdesc_eq_, 0.6, malloc, realloc, free); /** Write the body of <b>md</b> into <b>f</b>, with appropriate annotations. @@ -74,7 +75,7 @@ dump_microdescriptor(FILE *f, microdesc_t *md, size_t *annotation_len_out) { ssize_t r = 0; size_t written; - /* XXXX drops unkown annotations. */ + /* XXXX drops unknown annotations. */ if (md->last_listed) { char buf[ISO_TIME_LEN+1]; char annotation[ISO_TIME_LEN+32]; @@ -131,7 +132,7 @@ get_microdesc_cache(void) */ /** Decode the microdescriptors from the string starting at <b>s</b> and - * ending at <b>eos</b>, and store them in <b>cache</b>. If <b>no-save</b>, + * ending at <b>eos</b>, and store them in <b>cache</b>. If <b>no_save</b>, * mark them as non-writable to disk. If <b>where</b> is SAVED_IN_CACHE, * leave their bodies as pointers to the mmap'd cache. If where is * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is positive, @@ -159,7 +160,7 @@ microdescs_add_to_cache(microdesc_cache_t *cache, md->last_listed = listed_at); } if (requested_digests256) { - digestmap_t *requested; /* XXXX actuqlly we should just use a + digestmap_t *requested; /* XXXX actually we should just use a digest256map */ requested = digestmap_new(); SMARTLIST_FOREACH(requested_digests256, const char *, cp, @@ -168,7 +169,7 @@ microdescs_add_to_cache(microdesc_cache_t *cache, if (digestmap_get(requested, md->digest)) { digestmap_set(requested, md->digest, (void*)2); } else { - log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Received non-requested microcdesc"); + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Received non-requested microdesc"); microdesc_free(md); SMARTLIST_DEL_CURRENT(descriptors, md); } @@ -187,7 +188,7 @@ microdescs_add_to_cache(microdesc_cache_t *cache, return added; } -/** As microdescs_add_to_cache, but takes a list of micrdescriptors instead of +/** As microdescs_add_to_cache, but takes a list of microdescriptors instead of * a string to decode. Frees any members of <b>descriptors</b> that it does * not add. */ smartlist_t * @@ -231,7 +232,7 @@ microdescs_add_list_to_cache(microdesc_cache_t *cache, size_t annotation_len; size = dump_microdescriptor(f, md, &annotation_len); if (size < 0) { - /* we already warned in dump_microdescriptor; */ + /* we already warned in dump_microdescriptor */ abort_writing_to_file(open_file); smartlist_clear(added); return added; @@ -323,8 +324,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 */); @@ -478,7 +479,7 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) if (PREDICT_UNLIKELY( md->bodylen < 9 || fast_memneq(md->body, "onion-key", 9) != 0)) { /* XXXX once bug 2022 is solved, we can kill this block and turn it - * into just the tor_assert(!memcmp) */ + * into just the tor_assert(fast_memeq) */ off_t avail = cache->cache_content->size - md->off; char *bad_str; tor_assert(avail >= 0); @@ -574,6 +575,7 @@ microdesc_free(microdesc_t *md) if (md->onion_pkey) crypto_pk_free(md->onion_pkey); + tor_free(md->onion_curve25519_pkey); if (md->body && md->saved_location != SAVED_IN_CACHE) tor_free(md->body); @@ -582,6 +584,7 @@ microdesc_free(microdesc_t *md) smartlist_free(md->family); } short_policy_free(md->exit_policy); + short_policy_free(md->ipv6_exit_policy); tor_free(md); } diff --git a/src/or/microdesc.h b/src/or/microdesc.h index 5646fc7a8..4b18caaae 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -9,8 +9,8 @@ * \brief Header file for microdesc.c. **/ -#ifndef _TOR_MICRODESC_H -#define _TOR_MICRODESC_H +#ifndef TOR_MICRODESC_H +#define TOR_MICRODESC_H microdesc_cache_t *get_microdesc_cache(void); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 2553a74e5..9d402403c 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -11,7 +11,10 @@ */ #include "or.h" -#include "circuitbuild.h" +#include "channel.h" +#include "circuitmux.h" +#include "circuitmux_ewma.h" +#include "circuitstats.h" #include "config.h" #include "connection.h" #include "connection_or.h" @@ -19,6 +22,7 @@ #include "directory.h" #include "dirserv.h" #include "dirvote.h" +#include "entrynodes.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -215,8 +219,6 @@ router_reload_consensus_networkstatus(void) { char *filename; char *s; - struct stat st; - const or_options_t *options = get_options(); const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS; int flav; @@ -259,25 +261,6 @@ router_reload_consensus_networkstatus(void) tor_free(filename); } - if (!current_consensus || - (stat(options->FallbackNetworkstatusFile, &st)==0 && - st.st_mtime > current_consensus->valid_after)) { - s = read_file_to_str(options->FallbackNetworkstatusFile, - RFTS_IGNORE_MISSING, NULL); - if (s) { - if (networkstatus_set_current_consensus(s, "ns", - flags|NSSET_ACCEPT_OBSOLETE)) { - log_info(LD_FS, "Couldn't load consensus networkstatus from \"%s\"", - options->FallbackNetworkstatusFile); - } else { - log_notice(LD_FS, - "Loaded fallback consensus networkstatus from \"%s\"", - options->FallbackNetworkstatusFile); - } - tor_free(s); - } - } - if (!current_consensus) { if (!named_server_map) named_server_map = strmap_new(); @@ -561,7 +544,7 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus, /* Now see whether we're missing any voters entirely. */ SMARTLIST_FOREACH(router_get_trusted_dir_servers(), - trusted_dir_server_t *, ds, + dir_server_t *, ds, { if ((ds->type & V3_DIRINFO) && !networkstatus_get_voter_by_id(consensus, ds->v3_identity_digest)) @@ -593,7 +576,7 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus, voter->contact?voter->contact:"n/a", hex_str(voter->identity_digest, DIGEST_LEN)); }); - SMARTLIST_FOREACH(missing_authorities, trusted_dir_server_t *, ds, + SMARTLIST_FOREACH(missing_authorities, dir_server_t *, ds, { log(severity, LD_DIR, "Consensus does not include configured " "authority '%s' at %s:%d (identity %s)", @@ -667,7 +650,7 @@ networkstatus_get_cache_filename(const char *identity_digest) /** Helper for smartlist_sort: Compare two networkstatus objects by * publication date. */ static int -_compare_networkstatus_v2_published_on(const void **_a, const void **_b) +compare_networkstatus_v2_published_on_(const void **_a, const void **_b) { const networkstatus_v2_t *a = *_a, *b = *_b; if (a->published_on < b->published_on) @@ -735,7 +718,7 @@ router_set_networkstatus_v2(const char *s, time_t arrived_at, int i, found; time_t now; int skewed = 0; - trusted_dir_server_t *trusted_dir = NULL; + dir_server_t *trusted_dir = NULL; const char *source_desc = NULL; char fp[HEX_DIGEST_LEN+1]; char published[ISO_TIME_LEN+1]; @@ -771,7 +754,7 @@ router_set_networkstatus_v2(const char *s, time_t arrived_at, long delta = now - ns->published_on; format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Network status from %s was published %s in the " - "future (%s GMT). Check your time and date settings! " + "future (%s UTC). Check your time and date settings! " "Not caching.", source_desc, dbuf, published); control_event_general_status(LOG_WARN, @@ -901,7 +884,7 @@ router_set_networkstatus_v2(const char *s, time_t arrived_at, networkstatus_v2_list_has_changed = 1; smartlist_sort(networkstatus_v2_list, - _compare_networkstatus_v2_published_on); + compare_networkstatus_v2_published_on_); if (!skewed) add_networkstatus_to_cache(s, source, ns); @@ -1140,7 +1123,7 @@ update_v2_networkstatus_cache_downloads(time_t now) if (authority) { /* An authority launches a separate connection for everybody. */ - SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) + SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, dir_server_t *, ds) { char resource[HEX_DIGEST_LEN+6]; /* fp/hexdigit.z\0 */ tor_addr_t addr; @@ -1168,7 +1151,7 @@ update_v2_networkstatus_cache_downloads(time_t now) directory_initiate_command_routerstatus( &ds->fake_status, DIR_PURPOSE_FETCH_V2_NETWORKSTATUS, ROUTER_PURPOSE_GENERAL, - 0, /* Not private */ + DIRIND_ONEHOP, resource, NULL, 0 /* No payload. */, 0 /* No I-M-S. */); @@ -1530,13 +1513,7 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) a->is_bad_exit != b->is_bad_exit || a->is_bad_directory != b->is_bad_directory || a->is_hs_dir != b->is_hs_dir || - a->version_known != b->version_known || - a->version_supports_begindir != b->version_supports_begindir || - a->version_supports_extrainfo_upload != - b->version_supports_extrainfo_upload || - a->version_supports_conditional_consensus != - b->version_supports_conditional_consensus || - a->version_supports_v3_dir != b->version_supports_v3_dir; + a->version_known != b->version_known; } /** Notify controllers of any router status entries that changed between @@ -1640,6 +1617,7 @@ networkstatus_set_current_consensus(const char *consensus, consensus_waiting_for_certs_t *waiting = NULL; time_t current_valid_after = 0; int free_consensus = 1; /* Free 'c' at the end of the function */ + int old_ewma_enabled; if (flav < 0) { /* XXXX we don't handle unrecognized flavors yet. */ @@ -1675,9 +1653,6 @@ networkstatus_set_current_consensus(const char *consensus, if (from_cache && !accept_obsolete && c->valid_until < now-OLD_ROUTER_DESC_MAX_AGE) { - /* XXXX If we try to make fallbackconsensus work again, we should - * consider taking this out. Until then, believing obsolete consensuses - * is causing more harm than good. See also bug 887. */ log_info(LD_DIR, "Loaded an expired consensus. Discarding."); goto done; } @@ -1833,7 +1808,18 @@ networkstatus_set_current_consensus(const char *consensus, dirvote_recalculate_timing(options, now); routerstatus_list_update_named_server_map(); - cell_ewma_set_scale_factor(options, current_consensus); + + /* Update ewma and adjust policy if needed; first cache the old value */ + old_ewma_enabled = cell_ewma_enabled(); + /* Change the cell EWMA settings */ + cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus()); + /* If we just enabled ewma, set the cmux policy on all active channels */ + if (cell_ewma_enabled() && !old_ewma_enabled) { + channel_set_cmux_policy_everywhere(&ewma_policy); + } else if (!cell_ewma_enabled() && old_ewma_enabled) { + /* Turn it off everywhere */ + channel_set_cmux_policy_everywhere(NULL); + } /* XXXX024 this call might be unnecessary here: can changing the * current consensus really alter our view of any OR's rate limits? */ @@ -1864,7 +1850,7 @@ networkstatus_set_current_consensus(const char *consensus, format_iso_time(tbuf, c->valid_after); format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Our clock is %s behind the time published in the " - "consensus network status document (%s GMT). Tor needs an " + "consensus network status document (%s UTC). Tor needs an " "accurate clock to work correctly. Please check your time and " "date settings!", dbuf, tbuf); control_event_general_status(LOG_WARN, @@ -1996,7 +1982,7 @@ download_status_map_update_from_v2_networkstatus(void) digestmap_set(dl_status, d, s); } SMARTLIST_FOREACH_END(rs); } SMARTLIST_FOREACH_END(ns); - digestmap_free(v2_download_status_map, _tor_free); + digestmap_free(v2_download_status_map, tor_free_); v2_download_status_map = dl_status; networkstatus_v2_list_has_changed = 0; } @@ -2009,7 +1995,7 @@ routerstatus_list_update_named_server_map(void) if (!current_consensus) return; - strmap_free(named_server_map, _tor_free); + strmap_free(named_server_map, tor_free_); named_server_map = strmap_new(); strmap_free(unnamed_server_map, NULL); unnamed_server_map = strmap_new(); @@ -2032,7 +2018,7 @@ void routers_update_status_from_consensus_networkstatus(smartlist_t *routers, int reset_failures) { - trusted_dir_server_t *ds; + dir_server_t *ds; const or_options_t *options = get_options(); int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); networkstatus_t *ns = current_consensus; @@ -2052,7 +2038,7 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, /* We have a routerstatus for this router. */ const char *digest = router->cache_info.identity_digest; - ds = router_get_trusteddirserver_by_digest(digest); + ds = router_get_fallback_dirserver_by_digest(digest); /* Is it the same descriptor, or only the same identity? */ if (tor_memeq(router->cache_info.signed_descriptor_digest, @@ -2310,6 +2296,30 @@ networkstatus_parse_flavor_name(const char *flavname) return -1; } +/** Return 0 if this routerstatus is obsolete, too new, isn't + * running, or otherwise not a descriptor that we would make any + * use of even if we had it. Else return 1. */ +int +client_would_use_router(const routerstatus_t *rs, time_t now, + const or_options_t *options) +{ + if (!rs->is_flagged_running && !options->FetchUselessDescriptors) { + /* If we had this router descriptor, we wouldn't even bother using it. + * But, if we want to have a complete list, fetch it anyway. */ + return 0; + } + if (rs->published_on + options->TestingEstimatedDescriptorPropagationTime + > now) { + /* Most caches probably don't have this descriptor yet. */ + return 0; + } + if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) { + /* We'd drop it immediately for being too old. */ + return 0; + } + return 1; +} + /** If <b>question</b> is a string beginning with "ns/" in a format the * control interface expects for a GETINFO question, set *<b>answer</b> to a * newly-allocated string containing networkstatus lines for the appropriate @@ -2340,8 +2350,11 @@ getinfo_helper_networkstatus(control_connection_t *conn, return 0; } else if (!strcmpstart(question, "ns/id/")) { char d[DIGEST_LEN]; + const char *q = question + 6; + if (*q == '$') + ++q; - if (base16_decode(d, DIGEST_LEN, question+6, strlen(question+6))) { + if (base16_decode(d, DIGEST_LEN, q, strlen(q))) { *errmsg = "Data not decodeable as hex"; return -1; } @@ -2372,7 +2385,7 @@ networkstatus_free_all(void) networkstatus_v2_list = NULL; } - digestmap_free(v2_download_status_map, _tor_free); + digestmap_free(v2_download_status_map, tor_free_); v2_download_status_map = NULL; networkstatus_vote_free(current_ns_consensus); networkstatus_vote_free(current_md_consensus); @@ -2387,7 +2400,7 @@ networkstatus_free_all(void) tor_free(waiting->body); } - strmap_free(named_server_map, _tor_free); + strmap_free(named_server_map, tor_free_); strmap_free(unnamed_server_map, NULL); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 0af17512d..fe16f3391 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -9,8 +9,8 @@ * \brief Header file for networkstatus.c. **/ -#ifndef _TOR_NETWORKSTATUS_H -#define _TOR_NETWORKSTATUS_H +#ifndef TOR_NETWORKSTATUS_H +#define TOR_NETWORKSTATUS_H /** How old do we allow a v2 network-status to get before removing it * completely? */ @@ -71,6 +71,8 @@ int should_delay_dir_fetches(const or_options_t *options); void update_networkstatus_downloads(time_t now); void update_certificate_downloads(time_t now); int consensus_is_waiting_for_certs(void); +int client_would_use_router(const routerstatus_t *rs, time_t now, + const or_options_t *options); networkstatus_v2_t *networkstatus_v2_get_by_digest(const char *digest); networkstatus_t *networkstatus_get_latest_consensus(void); networkstatus_t *networkstatus_get_latest_consensus_by_flavor( diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d17850888..d71372bcd 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -5,19 +5,26 @@ /* See LICENSE for licensing information */ #include "or.h" +#include "address.h" #include "config.h" +#include "control.h" #include "dirserv.h" +#include "geoip.h" +#include "main.h" #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "rendservice.h" #include "router.h" #include "routerlist.h" +#include "routerset.h" #include <string.h> static void nodelist_drop_node(node_t *node, int remove_from_ht); static void node_free(node_t *node); +static void update_router_have_minimum_dir_info(void); /** A nodelist_t holds a node_t object for every router we're "willing to use * for something". Specifically, it should hold a node_t for every node that @@ -115,19 +122,47 @@ 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->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 +202,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 +212,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 +249,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 +272,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); @@ -582,7 +624,7 @@ node_is_dir(const node_t *node) } /** Return true iff <b>node</b> has either kind of usable descriptor -- that - * is, a routerdecriptor or a microdescriptor. */ + * is, a routerdescriptor or a microdescriptor. */ int node_has_descriptor(const node_t *node) { @@ -682,19 +724,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 +747,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 +817,616 @@ 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 it prefers 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) +{ + const or_options_t *options = get_options(); + tor_assert(ap_out); + + /* Cheap implementation of config option ClientUseIPv6 -- simply + don't prefer IPv6 when ClientUseIPv6 is not set and we're not a + client running with bridges. 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 ((options->ClientUseIPv6 || options->UseBridges) && + 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; + } +} + +/** Return true iff <b>node</b> has a curve25519 onion key. */ +int +node_has_curve25519_onion_key(const node_t *node) +{ + if (node->ri) + return node->ri->onion_curve25519_pkey != NULL; + else if (node->md) + return node->md->onion_curve25519_pkey != NULL; + else + return 0; +} + +/** Refresh the country code of <b>ri</b>. This function MUST be called on + * each router when the GeoIP database is reloaded, and on all new routers. */ +void +node_set_country(node_t *node) +{ + tor_addr_t addr = TOR_ADDR_NULL; + + /* XXXXipv6 */ + if (node->rs) + tor_addr_from_ipv4h(&addr, node->rs->addr); + else if (node->ri) + tor_addr_from_ipv4h(&addr, node->ri->addr); + + node->country = geoip_get_country_by_addr(&addr); +} + +/** Set the country code of all routers in the routerlist. */ +void +nodelist_refresh_countries(void) +{ + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, node_t *, node, + node_set_country(node)); +} + +/** Return true iff router1 and router2 have similar enough network addresses + * that we should treat them as being in the same family */ +static INLINE int +addrs_in_same_network_family(const tor_addr_t *a1, + const tor_addr_t *a2) +{ + return 0 == tor_addr_compare_masked(a1, a2, 16, CMP_SEMANTIC); +} + +/** Return true if <b>node</b>'s nickname matches <b>nickname</b> + * (case-insensitive), or if <b>node's</b> identity key digest + * matches a hexadecimal value stored in <b>nickname</b>. Return + * false otherwise. */ +static int +node_nickname_matches(const node_t *node, const char *nickname) +{ + const char *n = node_get_nickname(node); + if (n && nickname[0]!='$' && !strcasecmp(n, nickname)) + return 1; + return hex_digest_nickname_matches(nickname, + node->identity, + n, + node_is_named(node)); +} + +/** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ +static INLINE int +node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) +{ + if (!lst) return 0; + SMARTLIST_FOREACH(lst, const char *, name, { + if (node_nickname_matches(node, name)) + return 1; + }); + return 0; +} + +/** Return true iff r1 and r2 are in the same family, but not the same + * router. */ +int +nodes_in_same_family(const node_t *node1, const node_t *node2) +{ + const or_options_t *options = get_options(); + + /* Are they in the same family because of their addresses? */ + if (options->EnforceDistinctSubnets) { + tor_addr_t a1, a2; + node_get_addr(node1, &a1); + node_get_addr(node2, &a2); + if (addrs_in_same_network_family(&a1, &a2)) + return 1; + } + + /* Are they in the same family because the agree they are? */ + { + const smartlist_t *f1, *f2; + f1 = node_get_declared_family(node1); + f2 = node_get_declared_family(node2); + if (f1 && f2 && + node_in_nickname_smartlist(f1, node2) && + node_in_nickname_smartlist(f2, node1)) + return 1; + } + + /* Are they in the same option because the user says they are? */ + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { + if (routerset_contains_node(rs, node1) && + routerset_contains_node(rs, node2)) + return 1; + }); + } + + return 0; +} + +/** + * Add all the family of <b>node</b>, including <b>node</b> itself, to + * the smartlist <b>sl</b>. + * + * This is used to make sure we don't pick siblings in a single path, or + * pick more than one relay from a family for our entry guard list. + * Note that a node may be added to <b>sl</b> more than once if it is + * part of <b>node</b>'s family for more than one reason. + */ +void +nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) +{ + const smartlist_t *all_nodes = nodelist_get_list(); + const smartlist_t *declared_family; + const or_options_t *options = get_options(); + + tor_assert(node); + + declared_family = node_get_declared_family(node); + + /* Let's make sure that we have the node itself, if it's a real node. */ + { + const node_t *real_node = node_get_by_id(node->identity); + if (real_node) + smartlist_add(sl, (node_t*)real_node); + } + + /* First, add any nodes with similar network addresses. */ + if (options->EnforceDistinctSubnets) { + tor_addr_t node_addr; + node_get_addr(node, &node_addr); + + SMARTLIST_FOREACH_BEGIN(all_nodes, const node_t *, node2) { + tor_addr_t a; + node_get_addr(node2, &a); + if (addrs_in_same_network_family(&a, &node_addr)) + smartlist_add(sl, (void*)node2); + } SMARTLIST_FOREACH_END(node2); + } + + /* Now, add all nodes in the declared_family of this node, if they + * also declare this node to be in their family. */ + if (declared_family) { + /* Add every r such that router declares familyness with node, and node + * declares familyhood with router. */ + SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { + const node_t *node2; + const smartlist_t *family2; + if (!(node2 = node_get_by_nickname(name, 0))) + continue; + if (!(family2 = node_get_declared_family(node2))) + continue; + SMARTLIST_FOREACH_BEGIN(family2, const char *, name2) { + if (node_nickname_matches(node, name2)) { + smartlist_add(sl, (void*)node2); + break; + } + } SMARTLIST_FOREACH_END(name2); + } SMARTLIST_FOREACH_END(name); + } + + /* If the user declared any families locally, honor those too. */ + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { + if (routerset_contains_node(rs, node)) { + routerset_get_all_nodes(sl, rs, NULL, 0); + } + }); + } +} + +/** Find a router that's up, that has this IP address, and + * that allows exit to this address:port, or return NULL if there + * isn't a good one. + * Don't exit enclave to excluded relays -- it wouldn't actually + * hurt anything, but this way there are fewer confused users. + */ +const node_t * +router_find_exact_exit_enclave(const char *address, uint16_t port) +{/*XXXX MOVE*/ + uint32_t addr; + struct in_addr in; + tor_addr_t a; + const or_options_t *options = get_options(); + + if (!tor_inet_aton(address, &in)) + return NULL; /* it's not an IP already */ + addr = ntohl(in.s_addr); + + tor_addr_from_ipv4h(&a, addr); + + SMARTLIST_FOREACH(nodelist_get_list(), const node_t *, node, { + if (node_get_addr_ipv4h(node) == addr && + node->is_running && + compare_tor_addr_to_node_policy(&a, port, node) == + ADDR_POLICY_ACCEPTED && + !routerset_contains_node(options->ExcludeExitNodesUnion_, node)) + return node; + }); + return NULL; +} + +/** Return 1 if <b>router</b> is not suitable for these parameters, else 0. + * If <b>need_uptime</b> is non-zero, we require a minimum uptime. + * If <b>need_capacity</b> is non-zero, we require a minimum advertised + * bandwidth. + * If <b>need_guard</b>, we require that the router is a possible entry guard. + */ +int +node_is_unreliable(const node_t *node, int need_uptime, + int need_capacity, int need_guard) +{ + if (need_uptime && !node->is_stable) + return 1; + if (need_capacity && !node->is_fast) + return 1; + if (need_guard && !node->is_possible_guard) + return 1; + return 0; +} + +/** Return 1 if all running sufficiently-stable routers we can use will reject + * addr:port. Return 0 if any might accept it. */ +int +router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, + int need_uptime) +{ + addr_policy_result_t r; + + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { + if (node->is_running && + !node_is_unreliable(node, need_uptime, 0, 0)) { + + r = compare_tor_addr_to_node_policy(addr, port, node); + + if (r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED) + return 0; /* this one could be ok. good enough. */ + } + } SMARTLIST_FOREACH_END(node); + return 1; /* all will reject. */ +} + +/** Mark the router with ID <b>digest</b> as running or non-running + * in our routerlist. */ +void +router_set_status(const char *digest, int up) +{ + node_t *node; + tor_assert(digest); + + SMARTLIST_FOREACH(router_get_fallback_dir_servers(), + dir_server_t *, d, + if (tor_memeq(d->digest, digest, DIGEST_LEN)) + d->is_running = up); + + SMARTLIST_FOREACH(router_get_trusted_dir_servers(), + dir_server_t *, d, + if (tor_memeq(d->digest, digest, DIGEST_LEN)) + d->is_running = up); + + node = node_get_mutable_by_id(digest); + if (node) { +#if 0 + log_debug(LD_DIR,"Marking router %s as %s.", + node_describe(node), up ? "up" : "down"); +#endif + if (!up && node_is_me(node) && !net_is_disabled()) + log_warn(LD_NET, "We just marked ourself as down. Are your external " + "addresses reachable?"); + node->is_running = up; + } + + router_dir_info_changed(); +} + +/** True iff, the last time we checked whether we had enough directory info + * to build circuits, the answer was "yes". */ +static int have_min_dir_info = 0; +/** True iff enough has changed since the last time we checked whether we had + * enough directory info to build circuits that our old answer can no longer + * be trusted. */ +static int need_to_update_have_min_dir_info = 1; +/** String describing what we're missing before we have enough directory + * info. */ +static char dir_info_status[128] = ""; + +/** Return true iff we have enough networkstatus and router information to + * start building circuits. Right now, this means "more than half the + * networkstatus documents, and at least 1/4 of expected routers." */ +//XXX should consider whether we have enough exiting nodes here. +int +router_have_minimum_dir_info(void) +{ + if (PREDICT_UNLIKELY(need_to_update_have_min_dir_info)) { + update_router_have_minimum_dir_info(); + need_to_update_have_min_dir_info = 0; + } + return have_min_dir_info; +} + +/** Called when our internal view of the directory has changed. This can be + * when the authorities change, networkstatuses change, the list of routerdescs + * changes, or number of running routers changes. + */ +void +router_dir_info_changed(void) +{ + need_to_update_have_min_dir_info = 1; + rend_hsdir_routers_changed(); +} + +/** Return a string describing what we're missing before we have enough + * directory info. */ +const char * +get_dir_info_status_string(void) +{ + return dir_info_status; +} + +/** Iterate over the servers listed in <b>consensus</b>, and count how many of + * them seem like ones we'd use, and how many of <em>those</em> we have + * descriptors for. Store the former in *<b>num_usable</b> and the latter in + * *<b>num_present</b>. If <b>in_set</b> is non-NULL, only consider those + * routers in <b>in_set</b>. If <b>exit_only</b> is true, only consider nodes + * with the Exit flag. + */ +static void +count_usable_descriptors(int *num_present, int *num_usable, + const networkstatus_t *consensus, + const or_options_t *options, time_t now, + routerset_t *in_set, int exit_only) +{ + const int md = (consensus->flavor == FLAV_MICRODESC); + *num_present = 0, *num_usable=0; + + SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, rs) + { + if (exit_only && ! rs->is_exit) + continue; + if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1)) + continue; + if (client_would_use_router(rs, now, options)) { + const char * const digest = rs->descriptor_digest; + int present; + ++*num_usable; /* the consensus says we want it. */ + if (md) + present = NULL != microdesc_cache_lookup_by_digest256(NULL, digest); + else + present = NULL != router_get_by_descriptor_digest(digest); + if (present) { + /* we have the descriptor listed in the consensus. */ + ++*num_present; + } + } + } + SMARTLIST_FOREACH_END(rs); + + log_debug(LD_DIR, "%d usable, %d present (%s%s).", + *num_usable, *num_present, + md ? "microdesc" : "desc", exit_only ? " exits" : "s"); +} + +/** We just fetched a new set of descriptors. Compute how far through + * the "loading descriptors" bootstrapping phase we are, so we can inform + * the controller of our progress. */ +int +count_loading_descriptors_progress(void) +{ + int num_present = 0, num_usable=0; + time_t now = time(NULL); + const networkstatus_t *consensus = + networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); + double fraction; + + if (!consensus) + return 0; /* can't count descriptors if we have no list of them */ + + count_usable_descriptors(&num_present, &num_usable, + consensus, get_options(), now, NULL, 0); + + if (num_usable == 0) + return 0; /* don't div by 0 */ + fraction = num_present / (num_usable/4.); + if (fraction > 1.0) + return 0; /* it's not the number of descriptors holding us back */ + return BOOTSTRAP_STATUS_LOADING_DESCRIPTORS + (int) + (fraction*(BOOTSTRAP_STATUS_CONN_OR-1 - + BOOTSTRAP_STATUS_LOADING_DESCRIPTORS)); +} + +/** Change the value of have_min_dir_info, setting it true iff we have enough + * network and router information to build circuits. Clear the value of + * need_to_update_have_min_dir_info. */ +static void +update_router_have_minimum_dir_info(void) +{ + int num_present = 0, num_usable=0; + int num_exit_present = 0, num_exit_usable = 0; + time_t now = time(NULL); + int res; + const or_options_t *options = get_options(); + const networkstatus_t *consensus = + networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); + int using_md; + + if (!consensus) { + if (!networkstatus_get_latest_consensus()) + strlcpy(dir_info_status, "We have no usable consensus.", + sizeof(dir_info_status)); + else + strlcpy(dir_info_status, "We have no recent usable consensus.", + sizeof(dir_info_status)); + res = 0; + goto done; + } + + if (should_delay_dir_fetches(get_options())) { + log_notice(LD_DIR, "no known bridge descriptors running yet; stalling"); + strlcpy(dir_info_status, "No live bridge descriptors.", + sizeof(dir_info_status)); + res = 0; + goto done; + } + + using_md = consensus->flavor == FLAV_MICRODESC; + + count_usable_descriptors(&num_present, &num_usable, consensus, options, now, + NULL, 0); + count_usable_descriptors(&num_exit_present, &num_exit_usable, + consensus, options, now, options->ExitNodes, 1); + +/* What fraction of desired server descriptors do we need before we will + * build circuits? */ +#define FRAC_USABLE_NEEDED .75 +/* What fraction of desired _exit_ server descriptors do we need before we + * will build circuits? */ +#define FRAC_EXIT_USABLE_NEEDED .5 + + if (num_present < num_usable * FRAC_USABLE_NEEDED) { + tor_snprintf(dir_info_status, sizeof(dir_info_status), + "We have only %d/%d usable %sdescriptors.", + num_present, num_usable, using_md ? "micro" : ""); + res = 0; + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0); + goto done; + } else if (num_present < 2) { + tor_snprintf(dir_info_status, sizeof(dir_info_status), + "Only %d %sdescriptor%s here and believed reachable!", + num_present, using_md ? "micro" : "", num_present ? "" : "s"); + res = 0; + goto done; + } else if (num_exit_present < num_exit_usable * FRAC_EXIT_USABLE_NEEDED) { + tor_snprintf(dir_info_status, sizeof(dir_info_status), + "We have only %d/%d usable exit node descriptors.", + num_exit_present, num_exit_usable); + res = 0; + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0); + goto done; + } + + /* Check for entry nodes. */ + if (options->EntryNodes) { + count_usable_descriptors(&num_present, &num_usable, consensus, options, + now, options->EntryNodes, 0); + + if (!num_usable || !num_present) { + tor_snprintf(dir_info_status, sizeof(dir_info_status), + "We have only %d/%d usable entry node %sdescriptors.", + num_present, num_usable, using_md?"micro":""); + res = 0; + goto done; + } + } + + res = 1; + + done: + if (res && !have_min_dir_info) { + log(LOG_NOTICE, LD_DIR, + "We now have enough directory information to build circuits."); + control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO"); + control_event_bootstrap(BOOTSTRAP_STATUS_CONN_OR, 0); + } + if (!res && have_min_dir_info) { + int quiet = directory_too_idle_to_fetch_descriptors(options, now); + log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR, + "Our directory information is no longer up-to-date " + "enough to build circuits: %s", dir_info_status); + + /* a) make us log when we next complete a circuit, so we know when Tor + * is back up and usable, and b) disable some activities that Tor + * should only do while circuits are working, like reachability tests + * and fetching bridge descriptors only over circuits. */ + can_complete_circuit = 0; + + control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO"); + } + have_min_dir_info = res; + need_to_update_have_min_dir_info = 0; +} + diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 1e9da88d4..39f094877 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 +#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,19 @@ 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); +int node_has_curve25519_onion_key(const node_t *node); smartlist_t *nodelist_get_list(void); @@ -57,11 +62,22 @@ smartlist_t *nodelist_get_list(void); void node_get_addr(const node_t *node, tor_addr_t *addr_out); #define node_get_addr_ipv4h(n) node_get_prim_addr_ipv4h((n)) -/* XXXX These need to move out of routerlist.c */ void nodelist_refresh_countries(void); void node_set_country(node_t *node); void nodelist_add_node_and_family(smartlist_t *nodes, const node_t *node); int nodes_in_same_family(const node_t *node1, const node_t *node2); +const node_t *router_find_exact_exit_enclave(const char *address, + uint16_t port); +int node_is_unreliable(const node_t *router, int need_uptime, + int need_capacity, int need_guard); +int router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, + int need_uptime); +void router_set_status(const char *digest, int up); +int router_have_minimum_dir_info(void); +void router_dir_info_changed(void); +const char *get_dir_info_status_string(void); +int count_loading_descriptors_progress(void); + #endif diff --git a/src/or/ntmain.h b/src/or/ntmain.h index 07fdcf466..95a835a42 100644 --- a/src/or/ntmain.h +++ b/src/or/ntmain.h @@ -9,8 +9,8 @@ * \brief Header file for ntmain.c. **/ -#ifndef _TOR_NTMAIN_H -#define _TOR_NTMAIN_H +#ifndef TOR_NTMAIN_H +#define TOR_NTMAIN_H #ifdef _WIN32 #if !defined (WINCE) diff --git a/src/or/onion.c b/src/or/onion.c index f8c4d72b5..fc3e621f7 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -6,21 +6,26 @@ /** * \file onion.c - * \brief Functions to queue create cells, and handle onionskin - * parsing and creation. + * \brief Functions to queue create cells, wrap the various onionskin types, + * and parse and create the CREATE cell and its allies. **/ #include "or.h" #include "circuitlist.h" #include "config.h" #include "onion.h" +#include "onion_fast.h" +#include "onion_ntor.h" +#include "onion_tap.h" +#include "relay.h" #include "rephist.h" +#include "router.h" /** Type for a linked list of circuits that are waiting for a free CPU worker * to process a waiting onion handshake. */ typedef struct onion_queue_t { or_circuit_t *circ; - char *onionskin; + create_cell_t *onionskin; time_t when_added; struct onion_queue_t *next; } onion_queue_t; @@ -37,11 +42,13 @@ static onion_queue_t *ol_tail=NULL; /** Length of ol_list */ static int ol_length=0; +/* XXXX Check lengths vs MAX_ONIONSKIN_{CHALLENGE,REPLY}_LEN */ + /** Add <b>circ</b> to the end of ol_list and return 0, except * if ol_list is too long, in which case do nothing and return -1. */ int -onion_pending_add(or_circuit_t *circ, char *onionskin) +onion_pending_add(or_circuit_t *circ, create_cell_t *onionskin) { onion_queue_t *tmp; time_t now = time(NULL); @@ -98,7 +105,7 @@ onion_pending_add(or_circuit_t *circ, char *onionskin) * NULL if the list is empty. */ or_circuit_t * -onion_next_task(char **onionskin_out) +onion_next_task(create_cell_t **onionskin_out) { or_circuit_t *circ; @@ -106,7 +113,7 @@ onion_next_task(char **onionskin_out) return NULL; /* no onions pending, we're done */ tor_assert(ol_list->circ); - tor_assert(ol_list->circ->p_conn); /* make sure it's still valid */ + tor_assert(ol_list->circ->p_chan); /* make sure it's still valid */ tor_assert(ol_length > 0); circ = ol_list->circ; *onionskin_out = ol_list->onionskin; @@ -157,292 +164,843 @@ onion_pending_remove(or_circuit_t *circ) tor_free(victim); } -/*----------------------------------------------------------------------*/ +/** Remove all circuits from the pending list. Called from tor_free_all. */ +void +clear_pending_onions(void) +{ + while (ol_list) { + onion_queue_t *victim = ol_list; + ol_list = victim->next; + tor_free(victim->onionskin); + tor_free(victim); + } + ol_list = ol_tail = NULL; + ol_length = 0; +} + +/* ============================================================ */ -/** Given a router's 128 byte public key, - * stores the following in onion_skin_out: - * - [42 bytes] OAEP padding - * - [16 bytes] Symmetric key for encrypting blob past RSA - * - [70 bytes] g^x part 1 (inside the RSA) - * - [58 bytes] g^x part 2 (symmetrically encrypted) - * - * Stores the DH private key into handshake_state_out for later completion - * of the handshake. - * - * The meeting point/cookies and auth are zeroed out for now. +/** Fill in a server_onion_keys_t object at <b>keys</b> with all of the keys + * and other info we might need to do onion handshakes. (We make a copy of + * our keys for each cpuworker to avoid race conditions with the main thread, + * and to avoid locking) */ +void +setup_server_onion_keys(server_onion_keys_t *keys) +{ + memset(keys, 0, sizeof(server_onion_keys_t)); + memcpy(keys->my_identity, router_get_my_id_digest(), DIGEST_LEN); + dup_onion_keys(&keys->onion_key, &keys->last_onion_key); +#ifdef CURVE25519_ENABLED + keys->curve25519_key_map = construct_ntor_key_map(); + keys->junk_keypair = tor_malloc_zero(sizeof(curve25519_keypair_t)); + curve25519_keypair_generate(keys->junk_keypair, 0); +#endif +} + +/** Release all storage held in <b>keys</b>, but do not free <b>keys</b> + * itself (as it's likely to be stack-allocated.) */ +void +release_server_onion_keys(server_onion_keys_t *keys) +{ + if (! keys) + return; + + crypto_pk_free(keys->onion_key); + crypto_pk_free(keys->last_onion_key); +#ifdef CURVE25519_ENABLED + ntor_key_map_free(keys->curve25519_key_map); + tor_free(keys->junk_keypair); +#endif + memset(keys, 0, sizeof(server_onion_keys_t)); +} + +/** Release whatever storage is held in <b>state</b>, depending on its + * type, and clear its pointer. */ +void +onion_handshake_state_release(onion_handshake_state_t *state) +{ + switch (state->tag) { + case ONION_HANDSHAKE_TYPE_TAP: + crypto_dh_free(state->u.tap); + state->u.tap = NULL; + break; + case ONION_HANDSHAKE_TYPE_FAST: + fast_handshake_state_free(state->u.fast); + state->u.fast = NULL; + break; +#ifdef CURVE25519_ENABLED + case ONION_HANDSHAKE_TYPE_NTOR: + ntor_handshake_state_free(state->u.ntor); + state->u.ntor = NULL; + break; +#endif + default: + log_warn(LD_BUG, "called with unknown handshake state type %d", + (int)state->tag); + tor_fragile_assert(); + } +} + +/** Perform the first step of a circuit-creation handshake of type <b>type</b> + * (one of ONION_HANDSHAKE_TYPE_*): generate the initial "onion skin" in + * <b>onion_skin_out</b>, and store any state information in <b>state_out</b>. + * Return -1 on failure, and the length of the onionskin on acceptance. */ int -onion_skin_create(crypto_pk_t *dest_router_key, - crypto_dh_t **handshake_state_out, - char *onion_skin_out) /* ONIONSKIN_CHALLENGE_LEN bytes */ +onion_skin_create(int type, + const extend_info_t *node, + onion_handshake_state_t *state_out, + uint8_t *onion_skin_out) { - char challenge[DH_KEY_LEN]; - crypto_dh_t *dh = NULL; - int dhbytes, pkbytes; + int r = -1; + + switch (type) { + case ONION_HANDSHAKE_TYPE_TAP: + if (!node->onion_key) + return -1; + + if (onion_skin_TAP_create(node->onion_key, + &state_out->u.tap, + (char*)onion_skin_out) < 0) + return -1; + + r = TAP_ONIONSKIN_CHALLENGE_LEN; + break; + case ONION_HANDSHAKE_TYPE_FAST: + if (fast_onionskin_create(&state_out->u.fast, onion_skin_out) < 0) + return -1; + + r = CREATE_FAST_LEN; + break; + case ONION_HANDSHAKE_TYPE_NTOR: +#ifdef CURVE25519_ENABLED + if (tor_mem_is_zero((const char*)node->curve25519_onion_key.public_key, + CURVE25519_PUBKEY_LEN)) + return -1; + if (onion_skin_ntor_create((const uint8_t*)node->identity_digest, + &node->curve25519_onion_key, + &state_out->u.ntor, + onion_skin_out) < 0) + return -1; + + r = NTOR_ONIONSKIN_LEN; +#else + return -1; +#endif + break; + default: + log_warn(LD_BUG, "called with unknown handshake state type %d", type); + tor_fragile_assert(); + r = -1; + } + + if (r > 0) + state_out->tag = (uint16_t) type; + + return r; +} + +/** Perform the second (server-side) step of a circuit-creation handshake of + * type <b>type</b>, responding to the client request in <b>onion_skin</b> + * using the keys in <b>keys</b>. On success, write our response into + * <b>reply_out</b>, generate <b>keys_out_len</b> bytes worth of key material + * in <b>keys_out_len</b>, a hidden service nonce to <b>rend_nonce_out</b>, + * and return the length of the reply. On failure, return -1. + */ +int +onion_skin_server_handshake(int type, + const uint8_t *onion_skin, size_t onionskin_len, + const server_onion_keys_t *keys, + uint8_t *reply_out, + uint8_t *keys_out, size_t keys_out_len, + uint8_t *rend_nonce_out) +{ + int r = -1; - tor_assert(dest_router_key); - tor_assert(handshake_state_out); - tor_assert(onion_skin_out); - *handshake_state_out = NULL; - memset(onion_skin_out, 0, ONIONSKIN_CHALLENGE_LEN); + switch (type) { + case ONION_HANDSHAKE_TYPE_TAP: + if (onionskin_len != TAP_ONIONSKIN_CHALLENGE_LEN) + return -1; + if (onion_skin_TAP_server_handshake((const char*)onion_skin, + keys->onion_key, keys->last_onion_key, + (char*)reply_out, + (char*)keys_out, keys_out_len)<0) + return -1; + r = TAP_ONIONSKIN_REPLY_LEN; + memcpy(rend_nonce_out, reply_out+DH_KEY_LEN, DIGEST_LEN); + break; + case ONION_HANDSHAKE_TYPE_FAST: + if (onionskin_len != CREATE_FAST_LEN) + return -1; + if (fast_server_handshake(onion_skin, reply_out, keys_out, keys_out_len)<0) + return -1; + r = CREATED_FAST_LEN; + memcpy(rend_nonce_out, reply_out+DIGEST_LEN, DIGEST_LEN); + break; + case ONION_HANDSHAKE_TYPE_NTOR: +#ifdef CURVE25519_ENABLED + if (onionskin_len < NTOR_ONIONSKIN_LEN) + return -1; + { + size_t keys_tmp_len = keys_out_len + DIGEST_LEN; + uint8_t *keys_tmp = tor_malloc(keys_out_len + DIGEST_LEN); + + if (onion_skin_ntor_server_handshake( + onion_skin, keys->curve25519_key_map, + keys->junk_keypair, + keys->my_identity, + reply_out, keys_tmp, keys_tmp_len)<0) { + tor_free(keys_tmp); + return -1; + } + memcpy(keys_out, keys_tmp, keys_out_len); + memcpy(rend_nonce_out, keys_tmp+keys_out_len, DIGEST_LEN); + memwipe(keys_tmp, 0, keys_tmp_len); + tor_free(keys_tmp); + r = NTOR_REPLY_LEN; + } +#else + return -1; +#endif + break; + default: + log_warn(LD_BUG, "called with unknown handshake state type %d", type); + tor_fragile_assert(); + return -1; + } - if (!(dh = crypto_dh_new(DH_TYPE_CIRCUIT))) - goto err; + return r; +} - dhbytes = crypto_dh_get_bytes(dh); - pkbytes = (int) crypto_pk_keysize(dest_router_key); - tor_assert(dhbytes == 128); - tor_assert(pkbytes == 128); +/** Perform the final (client-side) step of a circuit-creation handshake of + * type <b>type</b>, using our state in <b>handshake_state</b> and the + * server's response in <b>reply</b> On success, generate <b>keys_out_len</b> + * bytes worth of key material in <b>keys_out_len</b>, set + * <b>rend_authenticator_out</b> to the "KH" field that can be used to + * establish introduction points at this hop, and return 0. On failure, + * return -1. */ +int +onion_skin_client_handshake(int type, + const onion_handshake_state_t *handshake_state, + const uint8_t *reply, size_t reply_len, + uint8_t *keys_out, size_t keys_out_len, + uint8_t *rend_authenticator_out) +{ + if (handshake_state->tag != type) + return -1; - if (crypto_dh_get_public(dh, challenge, dhbytes)) - goto err; + switch (type) { + case ONION_HANDSHAKE_TYPE_TAP: + if (reply_len != TAP_ONIONSKIN_REPLY_LEN) + return -1; + if (onion_skin_TAP_client_handshake(handshake_state->u.tap, + (const char*)reply, + (char *)keys_out, keys_out_len) < 0) + return -1; - note_crypto_pk_op(ENC_ONIONSKIN); + memcpy(rend_authenticator_out, reply+DH_KEY_LEN, DIGEST_LEN); + + return 0; + case ONION_HANDSHAKE_TYPE_FAST: + if (reply_len != CREATED_FAST_LEN) + return -1; + if (fast_client_handshake(handshake_state->u.fast, reply, + keys_out, keys_out_len) < 0) + return -1; + + memcpy(rend_authenticator_out, reply+DIGEST_LEN, DIGEST_LEN); + return 0; +#ifdef CURVE25519_ENABLED + case ONION_HANDSHAKE_TYPE_NTOR: + if (reply_len < NTOR_REPLY_LEN) + return -1; + { + size_t keys_tmp_len = keys_out_len + DIGEST_LEN; + uint8_t *keys_tmp = tor_malloc(keys_tmp_len); + if (onion_skin_ntor_client_handshake(handshake_state->u.ntor, + reply, + keys_tmp, keys_tmp_len) < 0) { + tor_free(keys_tmp); + return -1; + } + memcpy(keys_out, keys_tmp, keys_out_len); + memcpy(rend_authenticator_out, keys_tmp + keys_out_len, DIGEST_LEN); + memwipe(keys_tmp, 0, keys_tmp_len); + tor_free(keys_tmp); + } + return 0; +#endif + default: + log_warn(LD_BUG, "called with unknown handshake state type %d", type); + tor_fragile_assert(); + return -1; + } +} - /* set meeting point, meeting cookie, etc here. Leave zero for now. */ - if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, - ONIONSKIN_CHALLENGE_LEN, - challenge, DH_KEY_LEN, - PK_PKCS1_OAEP_PADDING, 1)<0) - goto err; +/** Helper: return 0 if <b>cell</b> appears valid, -1 otherwise. If + * <b>unknown_ok</b> is true, allow cells with handshake types we don't + * recognize. */ +static int +check_create_cell(const create_cell_t *cell, int unknown_ok) +{ + switch (cell->cell_type) { + case CELL_CREATE: + if (cell->handshake_type != ONION_HANDSHAKE_TYPE_TAP && + cell->handshake_type != ONION_HANDSHAKE_TYPE_NTOR) + return -1; + break; + case CELL_CREATE_FAST: + if (cell->handshake_type != ONION_HANDSHAKE_TYPE_FAST) + return -1; + break; + case CELL_CREATE2: + break; + default: + return -1; + } - memwipe(challenge, 0, sizeof(challenge)); - *handshake_state_out = dh; + switch (cell->handshake_type) { + case ONION_HANDSHAKE_TYPE_TAP: + if (cell->handshake_len != TAP_ONIONSKIN_CHALLENGE_LEN) + return -1; + break; + case ONION_HANDSHAKE_TYPE_FAST: + if (cell->handshake_len != CREATE_FAST_LEN) + return -1; + break; +#ifdef CURVE25519_ENABLED + case ONION_HANDSHAKE_TYPE_NTOR: + if (cell->handshake_len != NTOR_ONIONSKIN_LEN) + return -1; + break; +#endif + default: + if (! unknown_ok) + return -1; + } return 0; - err: - memwipe(challenge, 0, sizeof(challenge)); - if (dh) crypto_dh_free(dh); - return -1; } -/** Given an encrypted DH public key as generated by onion_skin_create, - * and the private key for this onion router, generate the reply (128-byte - * DH plus the first 20 bytes of shared key material), and store the - * next key_out_len bytes of key material in key_out. +/** Helper: parse the CREATE2 payload at <b>p</b>, which could be up to + * <b>p_len</b> bytes long, and use it to fill the fields of + * <b>cell_out</b>. Return 0 on success and -1 on failure. + * + * Note that part of the body of an EXTEND2 cell is a CREATE2 payload, so + * this function is also used for parsing those. */ +static int +parse_create2_payload(create_cell_t *cell_out, const uint8_t *p, size_t p_len) +{ + if (p_len < 4) + return -1; + cell_out->cell_type = CELL_CREATE2; + cell_out->handshake_type = ntohs(get_uint16(p)); + cell_out->handshake_len = ntohs(get_uint16(p+2)); + if (cell_out->handshake_len > CELL_PAYLOAD_SIZE - 4 || + cell_out->handshake_len > p_len - 4) + return -1; + if (cell_out->handshake_type == ONION_HANDSHAKE_TYPE_FAST) + return -1; + memcpy(cell_out->onionskin, p+4, cell_out->handshake_len); + return 0; +} + +/** Magic string which, in a CREATE or EXTEND cell, indicates that a seeming + * TAP payload is really an ntor payload. We'd do away with this if every + * relay supported EXTEND2, but we want to be able to extend from A to B with + * ntor even when A doesn't understand EXTEND2 and so can't generate a + * CREATE2 cell. + **/ +#define NTOR_CREATE_MAGIC "ntorNTORntorNTOR" + +/** Parse a CREATE, CREATE_FAST, or CREATE2 cell from <b>cell_in</b> into + * <b>cell_out</b>. Return 0 on success, -1 on failure. (We reject some + * syntactically valid CREATE2 cells that we can't generate or react to.) */ int -onion_skin_server_handshake(const char *onion_skin, /*ONIONSKIN_CHALLENGE_LEN*/ - crypto_pk_t *private_key, - crypto_pk_t *prev_private_key, - char *handshake_reply_out, /*ONIONSKIN_REPLY_LEN*/ - char *key_out, - size_t key_out_len) +create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in) { - char challenge[ONIONSKIN_CHALLENGE_LEN]; - crypto_dh_t *dh = NULL; - ssize_t len; - char *key_material=NULL; - size_t key_material_len=0; - int i; - crypto_pk_t *k; - - len = -1; - for (i=0;i<2;++i) { - k = i==0?private_key:prev_private_key; - if (!k) - break; - note_crypto_pk_op(DEC_ONIONSKIN); - len = crypto_pk_private_hybrid_decrypt(k, challenge, - ONIONSKIN_CHALLENGE_LEN, - onion_skin, ONIONSKIN_CHALLENGE_LEN, - PK_PKCS1_OAEP_PADDING,0); - if (len>0) - break; - } - if (len<0) { - log_info(LD_PROTOCOL, - "Couldn't decrypt onionskin: client may be using old onion key"); - goto err; - } else if (len != DH_KEY_LEN) { - log_warn(LD_PROTOCOL, "Unexpected onionskin length after decryption: %ld", - (long)len); - goto err; + memset(cell_out, 0, sizeof(*cell_out)); + + switch (cell_in->command) { + case CELL_CREATE: + cell_out->cell_type = CELL_CREATE; + if (tor_memeq(cell_in->payload, NTOR_CREATE_MAGIC, 16)) { + cell_out->handshake_type = ONION_HANDSHAKE_TYPE_NTOR; + cell_out->handshake_len = NTOR_ONIONSKIN_LEN; + memcpy(cell_out->onionskin, cell_in->payload+16, NTOR_ONIONSKIN_LEN); + } else { + cell_out->handshake_type = ONION_HANDSHAKE_TYPE_TAP; + cell_out->handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN; + memcpy(cell_out->onionskin, cell_in->payload, + TAP_ONIONSKIN_CHALLENGE_LEN); + } + break; + case CELL_CREATE_FAST: + cell_out->cell_type = CELL_CREATE_FAST; + cell_out->handshake_type = ONION_HANDSHAKE_TYPE_FAST; + cell_out->handshake_len = CREATE_FAST_LEN; + memcpy(cell_out->onionskin, cell_in->payload, CREATE_FAST_LEN); + break; + case CELL_CREATE2: + if (parse_create2_payload(cell_out, cell_in->payload, + CELL_PAYLOAD_SIZE) < 0) + return -1; + break; + default: + return -1; } - dh = crypto_dh_new(DH_TYPE_CIRCUIT); - if (!dh) { - log_warn(LD_BUG, "Couldn't allocate DH key"); - goto err; + return check_create_cell(cell_out, 0); +} + +/** Helper: return 0 if <b>cell</b> appears valid, -1 otherwise. */ +static int +check_created_cell(const created_cell_t *cell) +{ + switch (cell->cell_type) { + case CELL_CREATED: + if (cell->handshake_len != TAP_ONIONSKIN_REPLY_LEN) + return -1; + break; + case CELL_CREATED_FAST: + if (cell->handshake_len != CREATED_FAST_LEN) + return -1; + break; + case CELL_CREATED2: + if (cell->handshake_len > RELAY_PAYLOAD_SIZE-2) + return -1; + break; } - if (crypto_dh_get_public(dh, handshake_reply_out, DH_KEY_LEN)) { - log_info(LD_GENERAL, "crypto_dh_get_public failed."); - goto err; + + return 0; +} + +/** Parse a CREATED, CREATED_FAST, or CREATED2 cell from <b>cell_in</b> into + * <b>cell_out</b>. Return 0 on success, -1 on failure. */ +int +created_cell_parse(created_cell_t *cell_out, const cell_t *cell_in) +{ + memset(cell_out, 0, sizeof(*cell_out)); + + switch (cell_in->command) { + case CELL_CREATED: + cell_out->cell_type = CELL_CREATED; + cell_out->handshake_len = TAP_ONIONSKIN_REPLY_LEN; + memcpy(cell_out->reply, cell_in->payload, TAP_ONIONSKIN_REPLY_LEN); + break; + case CELL_CREATED_FAST: + cell_out->cell_type = CELL_CREATED_FAST; + cell_out->handshake_len = CREATED_FAST_LEN; + memcpy(cell_out->reply, cell_in->payload, CREATED_FAST_LEN); + break; + case CELL_CREATED2: + { + const uint8_t *p = cell_in->payload; + cell_out->cell_type = CELL_CREATED2; + cell_out->handshake_len = ntohs(get_uint16(p)); + if (cell_out->handshake_len > CELL_PAYLOAD_SIZE - 2) + return -1; + memcpy(cell_out->reply, p+2, cell_out->handshake_len); + break; + } } - key_material_len = DIGEST_LEN+key_out_len; - key_material = tor_malloc(key_material_len); - len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge, - DH_KEY_LEN, key_material, - key_material_len); - if (len < 0) { - log_info(LD_GENERAL, "crypto_dh_compute_secret failed."); - goto err; + return check_created_cell(cell_out); +} + +/** Helper: return 0 if <b>cell</b> appears valid, -1 otherwise. */ +static int +check_extend_cell(const extend_cell_t *cell) +{ + if (tor_digest_is_zero((const char*)cell->node_id)) + return -1; + /* We don't currently allow EXTEND2 cells without an IPv4 address */ + if (tor_addr_family(&cell->orport_ipv4.addr) == AF_UNSPEC) + return -1; + if (cell->create_cell.cell_type == CELL_CREATE) { + if (cell->cell_type != RELAY_COMMAND_EXTEND) + return -1; + } else if (cell->create_cell.cell_type == CELL_CREATE2) { + if (cell->cell_type != RELAY_COMMAND_EXTEND2 && + cell->cell_type != RELAY_COMMAND_EXTEND) + return -1; + } else { + /* In particular, no CREATE_FAST cells are allowed */ + return -1; } + if (cell->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_FAST) + return -1; - /* send back H(K|0) as proof that we learned K. */ - memcpy(handshake_reply_out+DH_KEY_LEN, key_material, DIGEST_LEN); + return check_create_cell(&cell->create_cell, 1); +} - /* use the rest of the key material for our shared keys, digests, etc */ - memcpy(key_out, key_material+DIGEST_LEN, key_out_len); +/** Protocol constants for specifier types in EXTEND2 + * @{ + */ +#define SPECTYPE_IPV4 0 +#define SPECTYPE_IPV6 1 +#define SPECTYPE_LEGACY_ID 2 +/** @} */ + +/** Parse an EXTEND or EXTEND2 cell (according to <b>command</b>) from the + * <b>payload_length</b> bytes of <b>payload</b> into <b>cell_out</b>. Return + * 0 on success, -1 on failure. */ +int +extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, + const uint8_t *payload, size_t payload_length) +{ + const uint8_t *eop; - memwipe(challenge, 0, sizeof(challenge)); - memwipe(key_material, 0, key_material_len); - tor_free(key_material); - crypto_dh_free(dh); - return 0; - err: - memwipe(challenge, 0, sizeof(challenge)); - if (key_material) { - memwipe(key_material, 0, key_material_len); - tor_free(key_material); + memset(cell_out, 0, sizeof(*cell_out)); + if (payload_length > RELAY_PAYLOAD_SIZE) + return -1; + eop = payload + payload_length; + + switch (command) { + case RELAY_COMMAND_EXTEND: + { + if (payload_length != 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN) + return -1; + + cell_out->cell_type = RELAY_COMMAND_EXTEND; + tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr, get_uint32(payload)); + cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4)); + tor_addr_make_unspec(&cell_out->orport_ipv6.addr); + if (tor_memeq(payload + 6, NTOR_CREATE_MAGIC, 16)) { + cell_out->create_cell.cell_type = CELL_CREATE2; + cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_NTOR; + cell_out->create_cell.handshake_len = NTOR_ONIONSKIN_LEN; + memcpy(cell_out->create_cell.onionskin, payload + 22, + NTOR_ONIONSKIN_LEN); + } else { + cell_out->create_cell.cell_type = CELL_CREATE; + cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_TAP; + cell_out->create_cell.handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN; + memcpy(cell_out->create_cell.onionskin, payload + 6, + TAP_ONIONSKIN_CHALLENGE_LEN); + } + memcpy(cell_out->node_id, payload + 6 + TAP_ONIONSKIN_CHALLENGE_LEN, + DIGEST_LEN); + break; + } + case RELAY_COMMAND_EXTEND2: + { + uint8_t n_specs = *payload, spectype, speclen; + int i; + int found_ipv4 = 0, found_ipv6 = 0, found_id = 0; + tor_addr_make_unspec(&cell_out->orport_ipv4.addr); + tor_addr_make_unspec(&cell_out->orport_ipv6.addr); + + cell_out->cell_type = RELAY_COMMAND_EXTEND2; + ++payload; + /* Parse the specifiers. We'll only take the first IPv4 and first IPv6 + * addres, and the node ID, and ignore everything else */ + for (i = 0; i < n_specs; ++i) { + if (eop - payload < 2) + return -1; + spectype = payload[0]; + speclen = payload[1]; + payload += 2; + if (eop - payload < speclen) + return -1; + switch (spectype) { + case SPECTYPE_IPV4: + if (speclen != 6) + return -1; + if (!found_ipv4) { + tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr, + get_uint32(payload)); + cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4)); + found_ipv4 = 1; + } + break; + case SPECTYPE_IPV6: + if (speclen != 18) + return -1; + if (!found_ipv6) { + tor_addr_from_ipv6_bytes(&cell_out->orport_ipv6.addr, + (const char*)payload); + cell_out->orport_ipv6.port = ntohs(get_uint16(payload+16)); + found_ipv6 = 1; + } + break; + case SPECTYPE_LEGACY_ID: + if (speclen != 20) + return -1; + if (found_id) + return -1; + memcpy(cell_out->node_id, payload, 20); + found_id = 1; + break; + } + payload += speclen; + } + if (!found_id || !found_ipv4) + return -1; + if (parse_create2_payload(&cell_out->create_cell,payload,eop-payload)<0) + return -1; + break; + } + default: + return -1; } - if (dh) crypto_dh_free(dh); - return -1; + return check_extend_cell(cell_out); } -/** Finish the client side of the DH handshake. - * Given the 128 byte DH reply + 20 byte hash as generated by - * onion_skin_server_handshake and the handshake state generated by - * onion_skin_create, verify H(K) with the first 20 bytes of shared - * key material, then generate key_out_len more bytes of shared key - * material and store them in key_out. - * - * After the invocation, call crypto_dh_free on handshake_state. - */ +/** Helper: return 0 if <b>cell</b> appears valid, -1 otherwise. */ +static int +check_extended_cell(const extended_cell_t *cell) +{ + if (cell->created_cell.cell_type == CELL_CREATED) { + if (cell->cell_type != RELAY_COMMAND_EXTENDED) + return -1; + } else if (cell->created_cell.cell_type == CELL_CREATED2) { + if (cell->cell_type != RELAY_COMMAND_EXTENDED2) + return -1; + } else { + return -1; + } + + return check_created_cell(&cell->created_cell); +} + +/** Parse an EXTENDED or EXTENDED2 cell (according to <b>command</b>) from the + * <b>payload_length</b> bytes of <b>payload</b> into <b>cell_out</b>. Return + * 0 on success, -1 on failure. */ int -onion_skin_client_handshake(crypto_dh_t *handshake_state, - const char *handshake_reply, /* ONIONSKIN_REPLY_LEN bytes */ - char *key_out, - size_t key_out_len) +extended_cell_parse(extended_cell_t *cell_out, + const uint8_t command, const uint8_t *payload, + size_t payload_len) { - ssize_t len; - char *key_material=NULL; - size_t key_material_len; - tor_assert(crypto_dh_get_bytes(handshake_state) == DH_KEY_LEN); - - key_material_len = DIGEST_LEN + key_out_len; - key_material = tor_malloc(key_material_len); - len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state, - handshake_reply, DH_KEY_LEN, key_material, - key_material_len); - if (len < 0) - goto err; - - if (tor_memneq(key_material, handshake_reply+DH_KEY_LEN, DIGEST_LEN)) { - /* H(K) does *not* match. Something fishy. */ - log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on onion handshake. " - "Bug or attack."); - goto err; + memset(cell_out, 0, sizeof(*cell_out)); + if (payload_len > RELAY_PAYLOAD_SIZE) + return -1; + + switch (command) { + case RELAY_COMMAND_EXTENDED: + if (payload_len != TAP_ONIONSKIN_REPLY_LEN) + return -1; + cell_out->cell_type = RELAY_COMMAND_EXTENDED; + cell_out->created_cell.cell_type = CELL_CREATED; + cell_out->created_cell.handshake_len = TAP_ONIONSKIN_REPLY_LEN; + memcpy(cell_out->created_cell.reply, payload, TAP_ONIONSKIN_REPLY_LEN); + break; + case RELAY_COMMAND_EXTENDED2: + { + cell_out->cell_type = RELAY_COMMAND_EXTENDED2; + cell_out->created_cell.cell_type = CELL_CREATED2; + cell_out->created_cell.handshake_len = ntohs(get_uint16(payload)); + if (cell_out->created_cell.handshake_len > RELAY_PAYLOAD_SIZE - 2 || + cell_out->created_cell.handshake_len > payload_len - 2) + return -1; + memcpy(cell_out->created_cell.reply, payload+2, + cell_out->created_cell.handshake_len); + } + break; + default: + return -1; } - /* use the rest of the key material for our shared keys, digests, etc */ - memcpy(key_out, key_material+DIGEST_LEN, key_out_len); + return check_extended_cell(cell_out); +} + +/** Fill <b>cell_out</b> with a correctly formatted version of the + * CREATE{,_FAST,2} cell in <b>cell_in</b>. Return 0 on success, -1 on + * failure. This is a cell we didn't originate if <b>relayed</b> is true. */ +static int +create_cell_format_impl(cell_t *cell_out, const create_cell_t *cell_in, + int relayed) +{ + uint8_t *p; + size_t space; + if (check_create_cell(cell_in, relayed) < 0) + return -1; + + memset(cell_out->payload, 0, sizeof(cell_out->payload)); + cell_out->command = cell_in->cell_type; + + p = cell_out->payload; + space = sizeof(cell_out->payload); + + switch (cell_in->cell_type) { + case CELL_CREATE: + if (cell_in->handshake_type == ONION_HANDSHAKE_TYPE_NTOR) { + memcpy(p, NTOR_CREATE_MAGIC, 16); + p += 16; + space -= 16; + } + /* Fall through */ + case CELL_CREATE_FAST: + tor_assert(cell_in->handshake_len <= space); + memcpy(p, cell_in->onionskin, cell_in->handshake_len); + break; + case CELL_CREATE2: + tor_assert(cell_in->handshake_len <= sizeof(cell_out->payload)-4); + set_uint16(cell_out->payload, htons(cell_in->handshake_type)); + set_uint16(cell_out->payload+2, htons(cell_in->handshake_len)); + memcpy(cell_out->payload + 4, cell_in->onionskin, cell_in->handshake_len); + break; + default: + return -1; + } - memwipe(key_material, 0, key_material_len); - tor_free(key_material); return 0; - err: - memwipe(key_material, 0, key_material_len); - tor_free(key_material); - return -1; } -/** Implement the server side of the CREATE_FAST abbreviated handshake. The - * client has provided DIGEST_LEN key bytes in <b>key_in</b> ("x"). We - * generate a reply of DIGEST_LEN*2 bytes in <b>key_out</b>, consisting of a - * new random "y", followed by H(x|y) to check for correctness. We set - * <b>key_out_len</b> bytes of key material in <b>key_out</b>. - * Return 0 on success, <0 on failure. - **/ int -fast_server_handshake(const uint8_t *key_in, /* DIGEST_LEN bytes */ - uint8_t *handshake_reply_out, /* DIGEST_LEN*2 bytes */ - uint8_t *key_out, - size_t key_out_len) +create_cell_format(cell_t *cell_out, const create_cell_t *cell_in) { - char tmp[DIGEST_LEN+DIGEST_LEN]; - char *out = NULL; - size_t out_len; - int r = -1; + return create_cell_format_impl(cell_out, cell_in, 0); +} - if (crypto_rand((char*)handshake_reply_out, DIGEST_LEN)<0) +int +create_cell_format_relayed(cell_t *cell_out, const create_cell_t *cell_in) +{ + return create_cell_format_impl(cell_out, cell_in, 1); +} + +/** Fill <b>cell_out</b> with a correctly formatted version of the + * CREATED{,_FAST,2} cell in <b>cell_in</b>. Return 0 on success, -1 on + * failure. */ +int +created_cell_format(cell_t *cell_out, const created_cell_t *cell_in) +{ + if (check_created_cell(cell_in) < 0) return -1; - memcpy(tmp, key_in, DIGEST_LEN); - memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN); - out_len = key_out_len+DIGEST_LEN; - out = tor_malloc(out_len); - if (crypto_expand_key_material(tmp, sizeof(tmp), out, out_len)) { - goto done; + memset(cell_out->payload, 0, sizeof(cell_out->payload)); + cell_out->command = cell_in->cell_type; + + switch (cell_in->cell_type) { + case CELL_CREATED: + case CELL_CREATED_FAST: + tor_assert(cell_in->handshake_len <= sizeof(cell_out->payload)); + memcpy(cell_out->payload, cell_in->reply, cell_in->handshake_len); + break; + case CELL_CREATED2: + tor_assert(cell_in->handshake_len <= sizeof(cell_out->payload)-2); + set_uint16(cell_out->payload, htons(cell_in->handshake_len)); + memcpy(cell_out->payload + 2, cell_in->reply, cell_in->handshake_len); + break; + default: + return -1; } - memcpy(handshake_reply_out+DIGEST_LEN, out, DIGEST_LEN); - memcpy(key_out, out+DIGEST_LEN, key_out_len); - r = 0; - done: - memwipe(tmp, 0, sizeof(tmp)); - memwipe(out, 0, out_len); - tor_free(out); - return r; + return 0; } -/** Implement the second half of the client side of the CREATE_FAST handshake. - * We sent the server <b>handshake_state</b> ("x") already, and the server - * told us <b>handshake_reply_out</b> (y|H(x|y)). Make sure that the hash is - * correct, and generate key material in <b>key_out</b>. Return 0 on success, - * true on failure. - * - * NOTE: The "CREATE_FAST" handshake path is distinguishable from regular - * "onionskin" handshakes, and is not secure if an adversary can see or modify - * the messages. Therefore, it should only be used by clients, and only as - * the first hop of a circuit (since the first hop is already authenticated - * and protected by TLS). - */ +/** Format the EXTEND{,2} cell in <b>cell_in</b>, storing its relay payload in + * <b>payload_out</b>, the number of bytes used in *<b>len_out</b>, and the + * relay command in *<b>command_out</b>. The <b>payload_out</b> must have + * RELAY_PAYLOAD_SIZE bytes available. Return 0 on success, -1 on failure. */ int -fast_client_handshake(const uint8_t *handshake_state,/*DIGEST_LEN bytes*/ - const uint8_t *handshake_reply_out,/*DIGEST_LEN*2 bytes*/ - uint8_t *key_out, - size_t key_out_len) +extend_cell_format(uint8_t *command_out, uint16_t *len_out, + uint8_t *payload_out, const extend_cell_t *cell_in) { - char tmp[DIGEST_LEN+DIGEST_LEN]; - char *out; - size_t out_len; - int r = -1; + uint8_t *p, *eop; + if (check_extend_cell(cell_in) < 0) + return -1; - memcpy(tmp, handshake_state, DIGEST_LEN); - memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN); - out_len = key_out_len+DIGEST_LEN; - out = tor_malloc(out_len); - if (crypto_expand_key_material(tmp, sizeof(tmp), out, out_len)) { - goto done; - } - if (tor_memneq(out, handshake_reply_out+DIGEST_LEN, DIGEST_LEN)) { - /* H(K) does *not* match. Something fishy. */ - log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on fast handshake. " - "Bug or attack."); - goto done; + p = payload_out; + eop = payload_out + RELAY_PAYLOAD_SIZE; + + memset(p, 0, RELAY_PAYLOAD_SIZE); + + switch (cell_in->cell_type) { + case RELAY_COMMAND_EXTEND: + { + *command_out = RELAY_COMMAND_EXTEND; + *len_out = 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN; + set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr)); + set_uint16(p+4, ntohs(cell_in->orport_ipv4.port)); + if (cell_in->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_NTOR) { + memcpy(p+6, NTOR_CREATE_MAGIC, 16); + memcpy(p+22, cell_in->create_cell.onionskin, NTOR_ONIONSKIN_LEN); + } else { + memcpy(p+6, cell_in->create_cell.onionskin, + TAP_ONIONSKIN_CHALLENGE_LEN); + } + memcpy(p+6+TAP_ONIONSKIN_CHALLENGE_LEN, cell_in->node_id, DIGEST_LEN); + } + break; + case RELAY_COMMAND_EXTEND2: + { + uint8_t n = 2; + *command_out = RELAY_COMMAND_EXTEND2; + + *p++ = n; /* 2 identifiers */ + *p++ = SPECTYPE_IPV4; /* First is IPV4. */ + *p++ = 6; /* It's 6 bytes long. */ + set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr)); + set_uint16(p+4, htons(cell_in->orport_ipv4.port)); + p += 6; + *p++ = SPECTYPE_LEGACY_ID; /* Next is an identity digest. */ + *p++ = 20; /* It's 20 bytes long */ + memcpy(p, cell_in->node_id, DIGEST_LEN); + p += 20; + + /* Now we can send the handshake */ + set_uint16(p, htons(cell_in->create_cell.handshake_type)); + set_uint16(p+2, htons(cell_in->create_cell.handshake_len)); + p += 4; + + if (cell_in->create_cell.handshake_len > eop - p) + return -1; + + memcpy(p, cell_in->create_cell.onionskin, + cell_in->create_cell.handshake_len); + + p += cell_in->create_cell.handshake_len; + *len_out = p - payload_out; + } + break; + default: + return -1; } - memcpy(key_out, out+DIGEST_LEN, key_out_len); - r = 0; - done: - memwipe(tmp, 0, sizeof(tmp)); - memwipe(out, 0, out_len); - tor_free(out); - return r; + + return 0; } -/** Remove all circuits from the pending list. Called from tor_free_all. */ -void -clear_pending_onions(void) +/** Format the EXTENDED{,2} cell in <b>cell_in</b>, storing its relay payload + * in <b>payload_out</b>, the number of bytes used in *<b>len_out</b>, and the + * relay command in *<b>command_out</b>. The <b>payload_out</b> must have + * RELAY_PAYLOAD_SIZE bytes available. Return 0 on success, -1 on failure. */ +int +extended_cell_format(uint8_t *command_out, uint16_t *len_out, + uint8_t *payload_out, const extended_cell_t *cell_in) { - while (ol_list) { - onion_queue_t *victim = ol_list; - ol_list = victim->next; - tor_free(victim->onionskin); - tor_free(victim); + uint8_t *p; + if (check_extended_cell(cell_in) < 0) + return -1; + + p = payload_out; + memset(p, 0, RELAY_PAYLOAD_SIZE); + + switch (cell_in->cell_type) { + case RELAY_COMMAND_EXTENDED: + { + *command_out = RELAY_COMMAND_EXTENDED; + *len_out = TAP_ONIONSKIN_REPLY_LEN; + memcpy(payload_out, cell_in->created_cell.reply, + TAP_ONIONSKIN_REPLY_LEN); + } + break; + case RELAY_COMMAND_EXTENDED2: + { + *command_out = RELAY_COMMAND_EXTENDED2; + *len_out = 2 + cell_in->created_cell.handshake_len; + set_uint16(payload_out, htons(cell_in->created_cell.handshake_len)); + if (2+cell_in->created_cell.handshake_len > RELAY_PAYLOAD_SIZE) + return -1; + memcpy(payload_out+2, cell_in->created_cell.reply, + cell_in->created_cell.handshake_len); + } + break; + default: + return -1; } - ol_list = ol_tail = NULL; - ol_length = 0; + + return 0; } diff --git a/src/or/onion.h b/src/or/onion.h index 7e0f873c7..33bf341bb 100644 --- a/src/or/onion.h +++ b/src/or/onion.h @@ -9,40 +9,110 @@ * \brief Header file for onion.c. **/ -#ifndef _TOR_ONION_H -#define _TOR_ONION_H +#ifndef TOR_ONION_H +#define TOR_ONION_H -int onion_pending_add(or_circuit_t *circ, char *onionskin); -or_circuit_t *onion_next_task(char **onionskin_out); +struct create_cell_t; +int onion_pending_add(or_circuit_t *circ, struct create_cell_t *onionskin); +or_circuit_t *onion_next_task(struct create_cell_t **onionskin_out); void onion_pending_remove(or_circuit_t *circ); +void clear_pending_onions(void); + +typedef struct server_onion_keys_t { + uint8_t my_identity[DIGEST_LEN]; + crypto_pk_t *onion_key; + crypto_pk_t *last_onion_key; +#ifdef CURVE25519_ENABLED + di_digest256_map_t *curve25519_key_map; + curve25519_keypair_t *junk_keypair; +#endif +} server_onion_keys_t; -int onion_skin_create(crypto_pk_t *router_key, - crypto_dh_t **handshake_state_out, - char *onion_skin_out); +#define MAX_ONIONSKIN_CHALLENGE_LEN 255 +#define MAX_ONIONSKIN_REPLY_LEN 255 -int onion_skin_server_handshake(const char *onion_skin, - crypto_pk_t *private_key, - crypto_pk_t *prev_private_key, - char *handshake_reply_out, - char *key_out, - size_t key_out_len); +void setup_server_onion_keys(server_onion_keys_t *keys); +void release_server_onion_keys(server_onion_keys_t *keys); -int onion_skin_client_handshake(crypto_dh_t *handshake_state, - const char *handshake_reply, - char *key_out, - size_t key_out_len); +void onion_handshake_state_release(onion_handshake_state_t *state); -int fast_server_handshake(const uint8_t *key_in, - uint8_t *handshake_reply_out, - uint8_t *key_out, - size_t key_out_len); +int onion_skin_create(int type, + const extend_info_t *node, + onion_handshake_state_t *state_out, + uint8_t *onion_skin_out); +int onion_skin_server_handshake(int type, + const uint8_t *onion_skin, size_t onionskin_len, + const server_onion_keys_t *keys, + uint8_t *reply_out, + uint8_t *keys_out, size_t key_out_len, + uint8_t *rend_nonce_out); +int onion_skin_client_handshake(int type, + const onion_handshake_state_t *handshake_state, + const uint8_t *reply, size_t reply_len, + uint8_t *keys_out, size_t key_out_len, + uint8_t *rend_authenticator_out); -int fast_client_handshake(const uint8_t *handshake_state, - const uint8_t *handshake_reply_out, - uint8_t *key_out, - size_t key_out_len); +/** A parsed CREATE, CREATE_FAST, or CREATE2 cell. */ +typedef struct create_cell_t { + /** The cell command. One of CREATE{,_FAST,2} */ + uint8_t cell_type; + /** One of the ONION_HANDSHAKE_TYPE_* values */ + uint16_t handshake_type; + /** The number of bytes used in <b>onionskin</b>. */ + uint16_t handshake_len; + /** The client-side message for the circuit creation handshake. */ + uint8_t onionskin[CELL_PAYLOAD_SIZE - 4]; +} create_cell_t; -void clear_pending_onions(void); +/** A parsed CREATED, CREATED_FAST, or CREATED2 cell. */ +typedef struct created_cell_t { + /** The cell command. One of CREATED{,_FAST,2} */ + uint8_t cell_type; + /** The number of bytes used in <b>reply</b>. */ + uint16_t handshake_len; + /** The server-side message for the circuit creation handshake. */ + uint8_t reply[CELL_PAYLOAD_SIZE - 2]; +} created_cell_t; + +/** A parsed RELAY_EXTEND or RELAY_EXTEND2 cell */ +typedef struct extend_cell_t { + /** One of RELAY_EXTEND or RELAY_EXTEND2 */ + uint8_t cell_type; + /** An IPv4 address and port for the node we're connecting to. */ + tor_addr_port_t orport_ipv4; + /** An IPv6 address and port for the node we're connecting to. Not currently + * used. */ + tor_addr_port_t orport_ipv6; + /** Identity fingerprint of the node we're conecting to.*/ + uint8_t node_id[DIGEST_LEN]; + /** The "create cell" embedded in this extend cell. Note that unlike the + * create cells we generate ourself, this once can have a handshake type we + * don't recognize. */ + create_cell_t create_cell; +} extend_cell_t; + +/** A parsed RELAY_EXTEND or RELAY_EXTEND2 cell */ +typedef struct extended_cell_t { + /** One of RELAY_EXTENDED or RELAY_EXTENDED2. */ + uint8_t cell_type; + /** The "created cell" embedded in this extended cell. */ + created_cell_t created_cell; +} extended_cell_t; + +int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in); +int created_cell_parse(created_cell_t *cell_out, const cell_t *cell_in); +int extend_cell_parse(extend_cell_t *cell_out, uint8_t command, + const uint8_t *payload_in, size_t payload_len); +int extended_cell_parse(extended_cell_t *cell_out, uint8_t command, + const uint8_t *payload_in, size_t payload_len); + +int create_cell_format(cell_t *cell_out, const create_cell_t *cell_in); +int create_cell_format_relayed(cell_t *cell_out, const create_cell_t *cell_in); +int created_cell_format(cell_t *cell_out, const created_cell_t *cell_in); +int extend_cell_format(uint8_t *command_out, uint16_t *len_out, + uint8_t *payload_out, const extend_cell_t *cell_in); +int extended_cell_format(uint8_t *command_out, uint16_t *len_out, + uint8_t *payload_out, const extended_cell_t *cell_in); #endif diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c new file mode 100644 index 000000000..c1a05233e --- /dev/null +++ b/src/or/onion_fast.c @@ -0,0 +1,123 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file onion_fast.c + * \brief Functions implement the CREATE_FAST circuit handshake. + **/ + +#include "or.h" +#include "onion_fast.h" + +/** Release all state held in <b>victim</b>. */ +void +fast_handshake_state_free(fast_handshake_state_t *victim) +{ + if (! victim) + return; + memwipe(victim, 0, sizeof(fast_handshake_state_t)); + tor_free(victim); +} + +/** Create the state needed to perform a CREATE_FAST hasnshake. Return 0 + * on success, -1 on failure. */ +int +fast_onionskin_create(fast_handshake_state_t **handshake_state_out, + uint8_t *handshake_out) +{ + fast_handshake_state_t *s; + *handshake_state_out = s = tor_malloc(sizeof(fast_handshake_state_t)); + if (crypto_rand((char*)s->state, sizeof(s->state)) < 0) { + tor_free(s); + return -1; + } + memcpy(handshake_out, s->state, DIGEST_LEN); + return 0; +} + +/** Implement the server side of the CREATE_FAST abbreviated handshake. The + * client has provided DIGEST_LEN key bytes in <b>key_in</b> ("x"). We + * generate a reply of DIGEST_LEN*2 bytes in <b>key_out</b>, consisting of a + * new random "y", followed by H(x|y) to check for correctness. We set + * <b>key_out_len</b> bytes of key material in <b>key_out</b>. + * Return 0 on success, <0 on failure. + **/ +int +fast_server_handshake(const uint8_t *key_in, /* DIGEST_LEN bytes */ + uint8_t *handshake_reply_out, /* DIGEST_LEN*2 bytes */ + uint8_t *key_out, + size_t key_out_len) +{ + uint8_t tmp[DIGEST_LEN+DIGEST_LEN]; + uint8_t *out = NULL; + size_t out_len; + int r = -1; + + if (crypto_rand((char*)handshake_reply_out, DIGEST_LEN)<0) + return -1; + + memcpy(tmp, key_in, DIGEST_LEN); + memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN); + out_len = key_out_len+DIGEST_LEN; + out = tor_malloc(out_len); + if (crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len)) { + goto done; + } + memcpy(handshake_reply_out+DIGEST_LEN, out, DIGEST_LEN); + memcpy(key_out, out+DIGEST_LEN, key_out_len); + r = 0; + done: + memwipe(tmp, 0, sizeof(tmp)); + memwipe(out, 0, out_len); + tor_free(out); + return r; +} + +/** Implement the second half of the client side of the CREATE_FAST handshake. + * We sent the server <b>handshake_state</b> ("x") already, and the server + * told us <b>handshake_reply_out</b> (y|H(x|y)). Make sure that the hash is + * correct, and generate key material in <b>key_out</b>. Return 0 on success, + * true on failure. + * + * NOTE: The "CREATE_FAST" handshake path is distinguishable from regular + * "onionskin" handshakes, and is not secure if an adversary can see or modify + * the messages. Therefore, it should only be used by clients, and only as + * the first hop of a circuit (since the first hop is already authenticated + * and protected by TLS). + */ +int +fast_client_handshake(const fast_handshake_state_t *handshake_state, + const uint8_t *handshake_reply_out,/*DIGEST_LEN*2 bytes*/ + uint8_t *key_out, + size_t key_out_len) +{ + uint8_t tmp[DIGEST_LEN+DIGEST_LEN]; + uint8_t *out; + size_t out_len; + int r = -1; + + memcpy(tmp, handshake_state->state, DIGEST_LEN); + memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN); + out_len = key_out_len+DIGEST_LEN; + out = tor_malloc(out_len); + if (crypto_expand_key_material_TAP(tmp, sizeof(tmp), out, out_len)) { + goto done; + } + if (tor_memneq(out, handshake_reply_out+DIGEST_LEN, DIGEST_LEN)) { + /* H(K) does *not* match. Something fishy. */ + log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on fast handshake. " + "Bug or attack."); + goto done; + } + memcpy(key_out, out+DIGEST_LEN, key_out_len); + r = 0; + done: + memwipe(tmp, 0, sizeof(tmp)); + memwipe(out, 0, out_len); + tor_free(out); + return r; +} + diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h new file mode 100644 index 000000000..2d652cc53 --- /dev/null +++ b/src/or/onion_fast.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file onion_fast.h + * \brief Header file for onion_fast.c. + **/ + +#ifndef TOR_ONION_FAST_H +#define TOR_ONION_FAST_H + +#define CREATE_FAST_LEN DIGEST_LEN +#define CREATED_FAST_LEN DIGEST_LEN*2 + +typedef struct fast_handshake_state_t { + uint8_t state[DIGEST_LEN]; +} fast_handshake_state_t; + +void fast_handshake_state_free(fast_handshake_state_t *victim); + +int fast_onionskin_create(fast_handshake_state_t **handshake_state_out, + uint8_t *handshake_out); + +int fast_server_handshake(const uint8_t *message_in, + uint8_t *handshake_reply_out, + uint8_t *key_out, + size_t key_out_len); + +int fast_client_handshake(const fast_handshake_state_t *handshake_state, + const uint8_t *handshake_reply_out, + uint8_t *key_out, + size_t key_out_len); + +#endif + diff --git a/src/or/onion_ntor.c b/src/or/onion_ntor.c new file mode 100644 index 000000000..58ab107f1 --- /dev/null +++ b/src/or/onion_ntor.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#include "crypto.h" +#define ONION_NTOR_PRIVATE +#include "onion_ntor.h" +#include "torlog.h" +#include "util.h" + +/** Free storage held in an ntor handshake state. */ +void +ntor_handshake_state_free(ntor_handshake_state_t *state) +{ + if (!state) + return; + memwipe(state, 0, sizeof(*state)); + tor_free(state); +} + +/** Convenience function to represent HMAC_SHA256 as our instantiation of + * ntor's "tweaked hash'. Hash the <b>inp_len</b> bytes at <b>inp</b> into + * a DIGEST256_LEN-byte digest at <b>out</b>, with the hash changing + * depending on the value of <b>tweak</b>. */ +static void +h_tweak(uint8_t *out, + const uint8_t *inp, size_t inp_len, + const char *tweak) +{ + size_t tweak_len = strlen(tweak); + crypto_hmac_sha256((char*)out, tweak, tweak_len, (const char*)inp, inp_len); +} + +/** Wrapper around a set of tweak-values for use with the ntor handshake. */ +typedef struct tweakset_t { + const char *t_mac; + const char *t_key; + const char *t_verify; + const char *m_expand; +} tweakset_t; + +/** The tweaks to be used with our handshake. */ +const tweakset_t proto1_tweaks = { +#define PROTOID "ntor-curve25519-sha256-1" +#define PROTOID_LEN 24 + PROTOID ":mac", + PROTOID ":key_extract", + PROTOID ":verify", + PROTOID ":key_expand" +}; + +/** Convenience macro: copy <b>len</b> bytes from <b>inp</b> to <b>ptr</b>, + * and advance <b>ptr</b> by the number of bytes copied. */ +#define APPEND(ptr, inp, len) \ + STMT_BEGIN { \ + memcpy(ptr, (inp), (len)); \ + ptr += len; \ + } STMT_END + +/** + * Compute the first client-side step of the ntor handshake for communicating + * with a server whose DIGEST_LEN-byte server identity is <b>router_id</b>, + * and whose onion key is <b>router_key</b>. Store the NTOR_ONIONSKIN_LEN-byte + * message in <b>onion_skin_out</b>, and store the handshake state in + * *<b>handshake_state_out</b>. Return 0 on success, -1 on failure. + */ +int +onion_skin_ntor_create(const uint8_t *router_id, + const curve25519_public_key_t *router_key, + ntor_handshake_state_t **handshake_state_out, + uint8_t *onion_skin_out) +{ + ntor_handshake_state_t *state; + uint8_t *op; + + state = tor_malloc_zero(sizeof(ntor_handshake_state_t)); + + memcpy(state->router_id, router_id, DIGEST_LEN); + memcpy(&state->pubkey_B, router_key, sizeof(curve25519_public_key_t)); + if (curve25519_secret_key_generate(&state->seckey_x, 0) < 0) { + tor_free(state); + return -1; + } + curve25519_public_key_generate(&state->pubkey_X, &state->seckey_x); + + op = onion_skin_out; + APPEND(op, router_id, DIGEST_LEN); + APPEND(op, router_key->public_key, CURVE25519_PUBKEY_LEN); + APPEND(op, state->pubkey_X.public_key, CURVE25519_PUBKEY_LEN); + tor_assert(op == onion_skin_out + NTOR_ONIONSKIN_LEN); + + *handshake_state_out = state; + + return 0; +} + +#define SERVER_STR "Server" +#define SERVER_STR_LEN 6 + +#define SECRET_INPUT_LEN (CURVE25519_PUBKEY_LEN * 3 + \ + CURVE25519_OUTPUT_LEN * 2 + \ + DIGEST_LEN + PROTOID_LEN) +#define AUTH_INPUT_LEN (DIGEST256_LEN + DIGEST_LEN + \ + CURVE25519_PUBKEY_LEN*3 + \ + PROTOID_LEN + SERVER_STR_LEN) + +/** + * Perform the server side of an ntor handshake. Given an + * NTOR_ONIONSKIN_LEN-byte message in <b>onion_skin</b>, our own identity + * fingerprint as <b>my_node_id</b>, and an associative array mapping public + * onion keys to curve25519_keypair_t in <b>private_keys</b>, attempt to + * perform the handshake. Use <b>junk_keys</b> if present if the handshake + * indicates an unrecognized public key. Write an NTOR_REPLY_LEN-byte + * message to send back to the client into <b>handshake_reply_out</b>, and + * generate <b>key_out_len</b> bytes of key material in <b>key_out</b>. Return + * 0 on success, -1 on failure. + */ +int +onion_skin_ntor_server_handshake(const uint8_t *onion_skin, + const di_digest256_map_t *private_keys, + const curve25519_keypair_t *junk_keys, + const uint8_t *my_node_id, + uint8_t *handshake_reply_out, + uint8_t *key_out, + size_t key_out_len) +{ + const tweakset_t *T = &proto1_tweaks; + /* Sensitive stack-allocated material. Kept in an anonymous struct to make + * it easy to wipe. */ + struct { + uint8_t secret_input[SECRET_INPUT_LEN]; + uint8_t auth_input[AUTH_INPUT_LEN]; + curve25519_public_key_t pubkey_X; + curve25519_secret_key_t seckey_y; + curve25519_public_key_t pubkey_Y; + uint8_t verify[DIGEST256_LEN]; + } s; + uint8_t *si = s.secret_input, *ai = s.auth_input; + const curve25519_keypair_t *keypair_bB; + int bad; + + /* Decode the onion skin */ + /* XXXX Does this possible early-return business threaten our security? */ + if (tor_memneq(onion_skin, my_node_id, DIGEST_LEN)) + return -1; + /* Note that on key-not-found, we go through with this operation anyway, + * using "junk_keys". This will result in failed authentication, but won't + * leak whether we recognized the key. */ + keypair_bB = dimap_search(private_keys, onion_skin + DIGEST_LEN, + (void*)junk_keys); + if (!keypair_bB) + return -1; + + memcpy(s.pubkey_X.public_key, onion_skin+DIGEST_LEN+DIGEST256_LEN, + CURVE25519_PUBKEY_LEN); + + /* Make y, Y */ + curve25519_secret_key_generate(&s.seckey_y, 0); + curve25519_public_key_generate(&s.pubkey_Y, &s.seckey_y); + + /* NOTE: If we ever use a group other than curve25519, or a different + * representation for its points, we may need to perform different or + * additional checks on X here and on Y in the client handshake, or lose our + * security properties. What checks we need would depend on the properties + * of the group and its representation. + * + * In short: if you use anything other than curve25519, this aspect of the + * code will need to be reconsidered carefully. */ + + /* build secret_input */ + curve25519_handshake(si, &s.seckey_y, &s.pubkey_X); + bad = safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); + si += CURVE25519_OUTPUT_LEN; + curve25519_handshake(si, &keypair_bB->seckey, &s.pubkey_X); + bad |= safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); + si += CURVE25519_OUTPUT_LEN; + + APPEND(si, my_node_id, DIGEST_LEN); + APPEND(si, keypair_bB->pubkey.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, s.pubkey_X.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, PROTOID, PROTOID_LEN); + tor_assert(si == s.secret_input + sizeof(s.secret_input)); + + /* Compute hashes of secret_input */ + h_tweak(s.verify, s.secret_input, sizeof(s.secret_input), T->t_verify); + + /* Compute auth_input */ + APPEND(ai, s.verify, DIGEST256_LEN); + APPEND(ai, my_node_id, DIGEST_LEN); + APPEND(ai, keypair_bB->pubkey.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, s.pubkey_X.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, PROTOID, PROTOID_LEN); + APPEND(ai, SERVER_STR, SERVER_STR_LEN); + tor_assert(ai == s.auth_input + sizeof(s.auth_input)); + + /* Build the reply */ + memcpy(handshake_reply_out, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); + h_tweak(handshake_reply_out+CURVE25519_PUBKEY_LEN, + s.auth_input, sizeof(s.auth_input), + T->t_mac); + + /* Generate the key material */ + crypto_expand_key_material_rfc5869_sha256( + s.secret_input, sizeof(s.secret_input), + (const uint8_t*)T->t_key, strlen(T->t_key), + (const uint8_t*)T->m_expand, strlen(T->m_expand), + key_out, key_out_len); + + /* Wipe all of our local state */ + memwipe(&s, 0, sizeof(s)); + + return bad ? -1 : 0; +} + +/** + * Perform the final client side of the ntor handshake, using the state in + * <b>handshake_state</b> and the server's NTOR_REPLY_LEN-byte reply in + * <b>handshake_reply</b>. Generate <b>key_out_len</b> bytes of key material + * in <b>key_out</b>. Return 0 on success, -1 on failure. + */ +int +onion_skin_ntor_client_handshake( + const ntor_handshake_state_t *handshake_state, + const uint8_t *handshake_reply, + uint8_t *key_out, + size_t key_out_len) +{ + const tweakset_t *T = &proto1_tweaks; + /* Sensitive stack-allocated material. Kept in an anonymous struct to make + * it easy to wipe. */ + struct { + curve25519_public_key_t pubkey_Y; + uint8_t secret_input[SECRET_INPUT_LEN]; + uint8_t verify[DIGEST256_LEN]; + uint8_t auth_input[AUTH_INPUT_LEN]; + uint8_t auth[DIGEST256_LEN]; + } s; + uint8_t *ai = s.auth_input, *si = s.secret_input; + const uint8_t *auth_candidate; + int bad; + + /* Decode input */ + memcpy(s.pubkey_Y.public_key, handshake_reply, CURVE25519_PUBKEY_LEN); + auth_candidate = handshake_reply + CURVE25519_PUBKEY_LEN; + + /* See note in server_handshake above about checking points. The + * circumstances under which we'd need to check Y for membership are + * different than those under which we'd be checking X. */ + + /* Compute secret_input */ + curve25519_handshake(si, &handshake_state->seckey_x, &s.pubkey_Y); + bad = safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); + si += CURVE25519_OUTPUT_LEN; + curve25519_handshake(si, &handshake_state->seckey_x, + &handshake_state->pubkey_B); + bad |= safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); + si += CURVE25519_OUTPUT_LEN; + APPEND(si, handshake_state->router_id, DIGEST_LEN); + APPEND(si, handshake_state->pubkey_B.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, handshake_state->pubkey_X.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); + APPEND(si, PROTOID, PROTOID_LEN); + tor_assert(si == s.secret_input + sizeof(s.secret_input)); + + /* Compute verify from secret_input */ + h_tweak(s.verify, s.secret_input, sizeof(s.secret_input), T->t_verify); + + /* Compute auth_input */ + APPEND(ai, s.verify, DIGEST256_LEN); + APPEND(ai, handshake_state->router_id, DIGEST_LEN); + APPEND(ai, handshake_state->pubkey_B.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, handshake_state->pubkey_X.public_key, CURVE25519_PUBKEY_LEN); + APPEND(ai, PROTOID, PROTOID_LEN); + APPEND(ai, SERVER_STR, SERVER_STR_LEN); + tor_assert(ai == s.auth_input + sizeof(s.auth_input)); + + /* Compute auth */ + h_tweak(s.auth, s.auth_input, sizeof(s.auth_input), T->t_mac); + + bad |= tor_memneq(s.auth, auth_candidate, DIGEST256_LEN); + + crypto_expand_key_material_rfc5869_sha256( + s.secret_input, sizeof(s.secret_input), + (const uint8_t*)T->t_key, strlen(T->t_key), + (const uint8_t*)T->m_expand, strlen(T->m_expand), + key_out, key_out_len); + + memwipe(&s, 0, sizeof(s)); + return bad ? -1 : 0; +} + diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h new file mode 100644 index 000000000..61ff5c0ad --- /dev/null +++ b/src/or/onion_ntor.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_ONION_NTOR_H +#define TOR_ONION_NTOR_H + +#include "torint.h" +#include "crypto_curve25519.h" +#include "di_ops.h" + +/** State to be maintained by a client between sending an ntor onionskin + * and receiving a reply. */ +typedef struct ntor_handshake_state_t ntor_handshake_state_t; + +/** Length of an ntor onionskin, as sent from the client to server. */ +#define NTOR_ONIONSKIN_LEN 84 +/** Length of an ntor reply, as sent from server to client. */ +#define NTOR_REPLY_LEN 64 + +#ifdef CURVE25519_ENABLED +void ntor_handshake_state_free(ntor_handshake_state_t *state); + +int onion_skin_ntor_create(const uint8_t *router_id, + const curve25519_public_key_t *router_key, + ntor_handshake_state_t **handshake_state_out, + uint8_t *onion_skin_out); + +int onion_skin_ntor_server_handshake(const uint8_t *onion_skin, + const di_digest256_map_t *private_keys, + const curve25519_keypair_t *junk_keypair, + const uint8_t *my_node_id, + uint8_t *handshake_reply_out, + uint8_t *key_out, + size_t key_out_len); + +int onion_skin_ntor_client_handshake( + const ntor_handshake_state_t *handshake_state, + const uint8_t *handshake_reply, + uint8_t *key_out, + size_t key_out_len); + +#ifdef ONION_NTOR_PRIVATE + +/** Storage held by a client while waiting for an ntor reply from a server. */ +struct ntor_handshake_state_t { + /** Identity digest of the router we're talking to. */ + uint8_t router_id[DIGEST_LEN]; + /** Onion key of the router we're talking to. */ + curve25519_public_key_t pubkey_B; + + /** + * Short-lived keypair for use with this handshake. + * @{ */ + curve25519_secret_key_t seckey_x; + curve25519_public_key_t pubkey_X; + /** @} */ +}; +#endif + +#endif + +#endif + diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c new file mode 100644 index 000000000..0ec526cd0 --- /dev/null +++ b/src/or/onion_tap.c @@ -0,0 +1,218 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file onion_tap.c + * \brief Functions to implement the original Tor circuit extension handshake + * (a.k.a TAP). + * + * We didn't call it "TAP" ourselves -- Ian Goldberg named it in "On the + * Security of the Tor Authentication Protocol". (Spoiler: it's secure, but + * its security is kind of fragile and implementation dependent. Never modify + * this implementation without reading and understanding that paper at least.) + **/ + +#include "or.h" +#include "config.h" +#include "onion_tap.h" +#include "rephist.h" + +/*----------------------------------------------------------------------*/ + +/** Given a router's 128 byte public key, + * stores the following in onion_skin_out: + * - [42 bytes] OAEP padding + * - [16 bytes] Symmetric key for encrypting blob past RSA + * - [70 bytes] g^x part 1 (inside the RSA) + * - [58 bytes] g^x part 2 (symmetrically encrypted) + * + * Stores the DH private key into handshake_state_out for later completion + * of the handshake. + * + * The meeting point/cookies and auth are zeroed out for now. + */ +int +onion_skin_TAP_create(crypto_pk_t *dest_router_key, + crypto_dh_t **handshake_state_out, + char *onion_skin_out) /* TAP_ONIONSKIN_CHALLENGE_LEN bytes */ +{ + char challenge[DH_KEY_LEN]; + crypto_dh_t *dh = NULL; + int dhbytes, pkbytes; + + tor_assert(dest_router_key); + tor_assert(handshake_state_out); + tor_assert(onion_skin_out); + *handshake_state_out = NULL; + memset(onion_skin_out, 0, TAP_ONIONSKIN_CHALLENGE_LEN); + + if (!(dh = crypto_dh_new(DH_TYPE_CIRCUIT))) + goto err; + + dhbytes = crypto_dh_get_bytes(dh); + pkbytes = (int) crypto_pk_keysize(dest_router_key); + tor_assert(dhbytes == 128); + tor_assert(pkbytes == 128); + + if (crypto_dh_get_public(dh, challenge, dhbytes)) + goto err; + + note_crypto_pk_op(ENC_ONIONSKIN); + + /* set meeting point, meeting cookie, etc here. Leave zero for now. */ + if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, + TAP_ONIONSKIN_CHALLENGE_LEN, + challenge, DH_KEY_LEN, + PK_PKCS1_OAEP_PADDING, 1)<0) + goto err; + + memwipe(challenge, 0, sizeof(challenge)); + *handshake_state_out = dh; + + return 0; + err: + memwipe(challenge, 0, sizeof(challenge)); + if (dh) crypto_dh_free(dh); + return -1; +} + +/** Given an encrypted DH public key as generated by onion_skin_create, + * and the private key for this onion router, generate the reply (128-byte + * DH plus the first 20 bytes of shared key material), and store the + * next key_out_len bytes of key material in key_out. + */ +int +onion_skin_TAP_server_handshake( + /*TAP_ONIONSKIN_CHALLENGE_LEN*/ + const char *onion_skin, + crypto_pk_t *private_key, + crypto_pk_t *prev_private_key, + /*TAP_ONIONSKIN_REPLY_LEN*/ + char *handshake_reply_out, + char *key_out, + size_t key_out_len) +{ + char challenge[TAP_ONIONSKIN_CHALLENGE_LEN]; + crypto_dh_t *dh = NULL; + ssize_t len; + char *key_material=NULL; + size_t key_material_len=0; + int i; + crypto_pk_t *k; + + len = -1; + for (i=0;i<2;++i) { + k = i==0?private_key:prev_private_key; + if (!k) + break; + note_crypto_pk_op(DEC_ONIONSKIN); + len = crypto_pk_private_hybrid_decrypt(k, challenge, + TAP_ONIONSKIN_CHALLENGE_LEN, + onion_skin, + TAP_ONIONSKIN_CHALLENGE_LEN, + PK_PKCS1_OAEP_PADDING,0); + if (len>0) + break; + } + if (len<0) { + log_info(LD_PROTOCOL, + "Couldn't decrypt onionskin: client may be using old onion key"); + goto err; + } else if (len != DH_KEY_LEN) { + log_warn(LD_PROTOCOL, "Unexpected onionskin length after decryption: %ld", + (long)len); + goto err; + } + + dh = crypto_dh_new(DH_TYPE_CIRCUIT); + if (!dh) { + log_warn(LD_BUG, "Couldn't allocate DH key"); + goto err; + } + if (crypto_dh_get_public(dh, handshake_reply_out, DH_KEY_LEN)) { + log_info(LD_GENERAL, "crypto_dh_get_public failed."); + goto err; + } + + key_material_len = DIGEST_LEN+key_out_len; + key_material = tor_malloc(key_material_len); + len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge, + DH_KEY_LEN, key_material, + key_material_len); + if (len < 0) { + log_info(LD_GENERAL, "crypto_dh_compute_secret failed."); + goto err; + } + + /* send back H(K|0) as proof that we learned K. */ + memcpy(handshake_reply_out+DH_KEY_LEN, key_material, DIGEST_LEN); + + /* use the rest of the key material for our shared keys, digests, etc */ + memcpy(key_out, key_material+DIGEST_LEN, key_out_len); + + memwipe(challenge, 0, sizeof(challenge)); + memwipe(key_material, 0, key_material_len); + tor_free(key_material); + crypto_dh_free(dh); + return 0; + err: + memwipe(challenge, 0, sizeof(challenge)); + if (key_material) { + memwipe(key_material, 0, key_material_len); + tor_free(key_material); + } + if (dh) crypto_dh_free(dh); + + return -1; +} + +/** Finish the client side of the DH handshake. + * Given the 128 byte DH reply + 20 byte hash as generated by + * onion_skin_server_handshake and the handshake state generated by + * onion_skin_create, verify H(K) with the first 20 bytes of shared + * key material, then generate key_out_len more bytes of shared key + * material and store them in key_out. + * + * After the invocation, call crypto_dh_free on handshake_state. + */ +int +onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, + const char *handshake_reply, /* TAP_ONIONSKIN_REPLY_LEN bytes */ + char *key_out, + size_t key_out_len) +{ + ssize_t len; + char *key_material=NULL; + size_t key_material_len; + tor_assert(crypto_dh_get_bytes(handshake_state) == DH_KEY_LEN); + + key_material_len = DIGEST_LEN + key_out_len; + key_material = tor_malloc(key_material_len); + len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state, + handshake_reply, DH_KEY_LEN, key_material, + key_material_len); + if (len < 0) + goto err; + + if (tor_memneq(key_material, handshake_reply+DH_KEY_LEN, DIGEST_LEN)) { + /* H(K) does *not* match. Something fishy. */ + log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on onion handshake. " + "Bug or attack."); + goto err; + } + + /* use the rest of the key material for our shared keys, digests, etc */ + memcpy(key_out, key_material+DIGEST_LEN, key_out_len); + + memwipe(key_material, 0, key_material_len); + tor_free(key_material); + return 0; + err: + memwipe(key_material, 0, key_material_len); + tor_free(key_material); + return -1; +} + diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h new file mode 100644 index 000000000..deae1bf8c --- /dev/null +++ b/src/or/onion_tap.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file onion_tap.h + * \brief Header file for onion_tap.c. + **/ + +#ifndef TOR_ONION_TAP_H +#define TOR_ONION_TAP_H + +#define TAP_ONIONSKIN_CHALLENGE_LEN (PKCS1_OAEP_PADDING_OVERHEAD+\ + CIPHER_KEY_LEN+\ + DH_KEY_LEN) +#define TAP_ONIONSKIN_REPLY_LEN (DH_KEY_LEN+DIGEST_LEN) + +int onion_skin_TAP_create(crypto_pk_t *router_key, + crypto_dh_t **handshake_state_out, + char *onion_skin_out); + +int onion_skin_TAP_server_handshake(const char *onion_skin, + crypto_pk_t *private_key, + crypto_pk_t *prev_private_key, + char *handshake_reply_out, + char *key_out, + size_t key_out_len); + +int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, + const char *handshake_reply, + char *key_out, + size_t key_out_len); + +#endif + diff --git a/src/or/or.h b/src/or/or.h index 51c23d305..eb0dc81b1 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -9,8 +9,8 @@ * \brief Master header file for Tor-specific functionality. **/ -#ifndef _TOR_OR_H -#define _TOR_OR_H +#ifndef TOR_OR_H +#define TOR_OR_H #include "orconfig.h" @@ -98,6 +98,8 @@ #include "address.h" #include "compat_libevent.h" #include "ht.h" +#include "replaycache.h" +#include "crypto_curve25519.h" /* These signals are defined to help handle_control_signal work. */ @@ -197,7 +199,7 @@ typedef enum { CIRC_ID_TYPE_NEITHER=2 } circ_id_type_t; -#define _CONN_TYPE_MIN 3 +#define CONN_TYPE_MIN_ 3 /** Type for sockets listening for OR connections. */ #define CONN_TYPE_OR_LISTENER 3 /** A bidirectional TLS connection transmitting a sequence of cells. @@ -228,8 +230,8 @@ typedef enum { #define CONN_TYPE_AP_NATD_LISTENER 14 /** Type for sockets listening for DNS requests. */ #define CONN_TYPE_AP_DNS_LISTENER 15 -#define _CONN_TYPE_MAX 15 -/* !!!! If _CONN_TYPE_MAX is ever over 15, we must grow the type field in +#define CONN_TYPE_MAX_ 15 +/* !!!! If CONN_TYPE_MAX_ is ever over 15, we must grow the type field in * connection_t. */ /* Proxy client types */ @@ -269,17 +271,18 @@ typedef enum { /** State for any listener connection. */ #define LISTENER_STATE_READY 0 -#define _CPUWORKER_STATE_MIN 1 +#define CPUWORKER_STATE_MIN_ 1 /** State for a connection to a cpuworker process that's idle. */ #define CPUWORKER_STATE_IDLE 1 /** State for a connection to a cpuworker process that's processing a * handshake. */ #define CPUWORKER_STATE_BUSY_ONION 2 -#define _CPUWORKER_STATE_MAX 2 +#define CPUWORKER_STATE_MAX_ 2 #define CPUWORKER_TASK_ONION CPUWORKER_STATE_BUSY_ONION +#define CPUWORKER_TASK_SHUTDOWN 255 -#define _OR_CONN_STATE_MIN 1 +#define OR_CONN_STATE_MIN_ 1 /** State for a connection to an OR: waiting for connect() to finish. */ #define OR_CONN_STATE_CONNECTING 1 /** State for a connection to an OR: waiting for proxy handshake to complete */ @@ -304,9 +307,9 @@ typedef enum { #define OR_CONN_STATE_OR_HANDSHAKING_V3 7 /** State for an OR connection: Ready to send/receive cells. */ #define OR_CONN_STATE_OPEN 8 -#define _OR_CONN_STATE_MAX 8 +#define OR_CONN_STATE_MAX_ 8 -#define _EXIT_CONN_STATE_MIN 1 +#define EXIT_CONN_STATE_MIN_ 1 /** State for an exit connection: waiting for response from DNS farm. */ #define EXIT_CONN_STATE_RESOLVING 1 /** State for an exit connection: waiting for connect() to finish. */ @@ -315,10 +318,10 @@ typedef enum { #define EXIT_CONN_STATE_OPEN 3 /** State for an exit connection: waiting to be removed. */ #define EXIT_CONN_STATE_RESOLVEFAILED 4 -#define _EXIT_CONN_STATE_MAX 4 +#define EXIT_CONN_STATE_MAX_ 4 /* The AP state values must be disjoint from the EXIT state values. */ -#define _AP_CONN_STATE_MIN 5 +#define AP_CONN_STATE_MIN_ 5 /** State for a SOCKS connection: waiting for SOCKS request. */ #define AP_CONN_STATE_SOCKS_WAIT 5 /** State for a SOCKS connection: got a y.onion URL; waiting to receive @@ -338,14 +341,14 @@ typedef enum { /** State for a transparent natd connection: waiting for original * destination. */ #define AP_CONN_STATE_NATD_WAIT 12 -#define _AP_CONN_STATE_MAX 12 +#define AP_CONN_STATE_MAX_ 12 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding * edge connection is not attached to any circuit. */ #define AP_CONN_STATE_IS_UNATTACHED(s) \ ((s) <= AP_CONN_STATE_CIRCUIT_WAIT || (s) == AP_CONN_STATE_NATD_WAIT) -#define _DIR_CONN_STATE_MIN 1 +#define DIR_CONN_STATE_MIN_ 1 /** State for connection to directory server: waiting for connect(). */ #define DIR_CONN_STATE_CONNECTING 1 /** State for connection to directory server: sending HTTP request. */ @@ -358,21 +361,21 @@ typedef enum { #define DIR_CONN_STATE_SERVER_COMMAND_WAIT 5 /** State for connection at directory server: sending HTTP response. */ #define DIR_CONN_STATE_SERVER_WRITING 6 -#define _DIR_CONN_STATE_MAX 6 +#define DIR_CONN_STATE_MAX_ 6 /** True iff the purpose of <b>conn</b> means that it's a server-side * directory connection. */ #define DIR_CONN_IS_SERVER(conn) ((conn)->purpose == DIR_PURPOSE_SERVER) -#define _CONTROL_CONN_STATE_MIN 1 +#define CONTROL_CONN_STATE_MIN_ 1 /** State for a control connection: Authenticated and accepting v1 commands. */ #define CONTROL_CONN_STATE_OPEN 1 /** State for a control connection: Waiting for authentication; speaking * protocol v1. */ #define CONTROL_CONN_STATE_NEEDAUTH 2 -#define _CONTROL_CONN_STATE_MAX 2 +#define CONTROL_CONN_STATE_MAX_ 2 -#define _DIR_PURPOSE_MIN 3 +#define DIR_PURPOSE_MIN_ 3 /** A connection to a directory server: download a rendezvous * descriptor. */ #define DIR_PURPOSE_FETCH_RENDDESC 3 @@ -420,7 +423,7 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define _DIR_PURPOSE_MAX 19 +#define DIR_PURPOSE_MAX_ 19 /** True iff <b>p</b> is a purpose corresponding to uploading data to a * directory server. */ @@ -430,12 +433,12 @@ typedef enum { (p)==DIR_PURPOSE_UPLOAD_VOTE || \ (p)==DIR_PURPOSE_UPLOAD_SIGNATURES) -#define _EXIT_PURPOSE_MIN 1 +#define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ #define EXIT_PURPOSE_CONNECT 1 /** This exit stream wants to do a resolve (either normal or reverse). */ #define EXIT_PURPOSE_RESOLVE 2 -#define _EXIT_PURPOSE_MAX 2 +#define EXIT_PURPOSE_MAX_ 2 /* !!!! If any connection purpose is ever over 31, we must grow the type * field in connection_t. */ @@ -444,16 +447,16 @@ typedef enum { #define CIRCUIT_STATE_BUILDING 0 /** Circuit state: Waiting to process the onionskin. */ #define CIRCUIT_STATE_ONIONSKIN_PENDING 1 -/** Circuit state: I'd like to deliver a create, but my n_conn is still +/** Circuit state: I'd like to deliver a create, but my n_chan is still * connecting. */ -#define CIRCUIT_STATE_OR_WAIT 2 +#define CIRCUIT_STATE_CHAN_WAIT 2 /** Circuit state: onionskin(s) processed, ready to send/receive cells. */ #define CIRCUIT_STATE_OPEN 3 -#define _CIRCUIT_PURPOSE_MIN 1 +#define CIRCUIT_PURPOSE_MIN_ 1 /* these circuits were initiated elsewhere */ -#define _CIRCUIT_PURPOSE_OR_MIN 1 +#define CIRCUIT_PURPOSE_OR_MIN_ 1 /** OR-side circuit purpose: normal circuit, at OR. */ #define CIRCUIT_PURPOSE_OR 1 /** OR-side circuit purpose: At OR, from Bob, waiting for intro from Alices. */ @@ -462,7 +465,7 @@ typedef enum { #define CIRCUIT_PURPOSE_REND_POINT_WAITING 3 /** OR-side circuit purpose: At OR, both circuits have this purpose. */ #define CIRCUIT_PURPOSE_REND_ESTABLISHED 4 -#define _CIRCUIT_PURPOSE_OR_MAX 4 +#define CIRCUIT_PURPOSE_OR_MAX_ 4 /* these circuits originate at this node */ @@ -505,7 +508,7 @@ typedef enum { #define CIRCUIT_PURPOSE_C_REND_JOINED 12 /** This circuit is used for build time measurement only */ #define CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT 13 -#define _CIRCUIT_PURPOSE_C_MAX 13 +#define CIRCUIT_PURPOSE_C_MAX_ 13 /** Hidden-service-side circuit purpose: at Bob, waiting for introductions. */ #define CIRCUIT_PURPOSE_S_ESTABLISH_INTRO 14 /** Hidden-service-side circuit purpose: at Bob, successfully established @@ -519,19 +522,21 @@ typedef enum { #define CIRCUIT_PURPOSE_TESTING 18 /** A controller made this circuit and Tor should not use it. */ #define CIRCUIT_PURPOSE_CONTROLLER 19 -#define _CIRCUIT_PURPOSE_MAX 19 +/** This circuit is used for path bias probing only */ +#define CIRCUIT_PURPOSE_PATH_BIAS_TESTING 20 +#define CIRCUIT_PURPOSE_MAX_ 20 /** A catch-all for unrecognized purposes. Currently we don't expect * to make or see any circuits with this purpose. */ #define CIRCUIT_PURPOSE_UNKNOWN 255 /** True iff the circuit purpose <b>p</b> is for a circuit that * originated at this node. */ -#define CIRCUIT_PURPOSE_IS_ORIGIN(p) ((p)>_CIRCUIT_PURPOSE_OR_MAX) +#define CIRCUIT_PURPOSE_IS_ORIGIN(p) ((p)>CIRCUIT_PURPOSE_OR_MAX_) /** True iff the circuit purpose <b>p</b> is for a circuit that originated * here to serve as a client. (Hidden services don't count here.) */ #define CIRCUIT_PURPOSE_IS_CLIENT(p) \ - ((p)> _CIRCUIT_PURPOSE_OR_MAX && \ - (p)<=_CIRCUIT_PURPOSE_C_MAX) + ((p)> CIRCUIT_PURPOSE_OR_MAX_ && \ + (p)<=CIRCUIT_PURPOSE_C_MAX_) /** True iff the circuit_t <b>c</b> is actually an origin_circuit_t. */ #define CIRCUIT_IS_ORIGIN(c) (CIRCUIT_PURPOSE_IS_ORIGIN((c)->purpose)) /** True iff the circuit purpose <b>p</b> is for an established rendezvous @@ -559,6 +564,8 @@ typedef enum { #define RELAY_COMMAND_RESOLVE 11 #define RELAY_COMMAND_RESOLVED 12 #define RELAY_COMMAND_BEGIN_DIR 13 +#define RELAY_COMMAND_EXTEND2 14 +#define RELAY_COMMAND_EXTENDED2 15 #define RELAY_COMMAND_ESTABLISH_INTRO 32 #define RELAY_COMMAND_ESTABLISH_RENDEZVOUS 33 @@ -664,7 +671,7 @@ typedef enum { /* Reasons why we (or a remote OR) might close a circuit. See tor-spec.txt for * documentation of these. */ -#define _END_CIRC_REASON_MIN 0 +#define END_CIRC_REASON_MIN_ 0 #define END_CIRC_REASON_NONE 0 #define END_CIRC_REASON_TORPROTOCOL 1 #define END_CIRC_REASON_INTERNAL 2 @@ -673,12 +680,12 @@ typedef enum { #define END_CIRC_REASON_RESOURCELIMIT 5 #define END_CIRC_REASON_CONNECTFAILED 6 #define END_CIRC_REASON_OR_IDENTITY 7 -#define END_CIRC_REASON_OR_CONN_CLOSED 8 +#define END_CIRC_REASON_CHANNEL_CLOSED 8 #define END_CIRC_REASON_FINISHED 9 #define END_CIRC_REASON_TIMEOUT 10 #define END_CIRC_REASON_DESTROYED 11 #define END_CIRC_REASON_NOSUCHSERVICE 12 -#define _END_CIRC_REASON_MAX 12 +#define END_CIRC_REASON_MAX_ 12 /** Bitwise-OR this with the argument to circuit_mark_for_close() or * control_event_circuit_status() to indicate that the reason was @@ -825,6 +832,8 @@ typedef enum { #define CELL_VERSIONS 7 #define CELL_NETINFO 8 #define CELL_RELAY_EARLY 9 +#define CELL_CREATE2 10 +#define CELL_CREATED2 11 #define CELL_VPADDING 128 #define CELL_CERTS 129 @@ -878,6 +887,147 @@ typedef uint16_t circid_t; /** Identifies a stream on a circuit */ typedef uint16_t streamid_t; +/* channel_t typedef; struct channel_s is in channel.h */ + +typedef struct channel_s channel_t; + +/* channel_listener_t typedef; struct channel_listener_s is in channel.h */ + +typedef struct channel_listener_s channel_listener_t; + +/* channel states for channel_t */ + +typedef enum { + /* + * Closed state - channel is inactive + * + * Permitted transitions from: + * - CHANNEL_STATE_CLOSING + * Permitted transitions to: + * - CHANNEL_STATE_OPENING + */ + CHANNEL_STATE_CLOSED = 0, + /* + * Opening state - channel is trying to connect + * + * Permitted transitions from: + * - CHANNEL_STATE_CLOSED + * Permitted transitions to: + * - CHANNEL_STATE_CLOSING + * - CHANNEL_STATE_ERROR + * - CHANNEL_STATE_OPEN + */ + CHANNEL_STATE_OPENING, + /* + * Open state - channel is active and ready for use + * + * Permitted transitions from: + * - CHANNEL_STATE_MAINT + * - CHANNEL_STATE_OPENING + * Permitted transitions to: + * - CHANNEL_STATE_CLOSING + * - CHANNEL_STATE_ERROR + * - CHANNEL_STATE_MAINT + */ + CHANNEL_STATE_OPEN, + /* + * Maintenance state - channel is temporarily offline for subclass specific + * maintenance activities such as TLS renegotiation. + * + * Permitted transitions from: + * - CHANNEL_STATE_OPEN + * Permitted transitions to: + * - CHANNEL_STATE_CLOSING + * - CHANNEL_STATE_ERROR + * - CHANNEL_STATE_OPEN + */ + CHANNEL_STATE_MAINT, + /* + * Closing state - channel is shutting down + * + * Permitted transitions from: + * - CHANNEL_STATE_MAINT + * - CHANNEL_STATE_OPEN + * Permitted transitions to: + * - CHANNEL_STATE_CLOSED, + * - CHANNEL_STATE_ERROR + */ + CHANNEL_STATE_CLOSING, + /* + * Error state - channel has experienced a permanent error + * + * Permitted transitions from: + * - CHANNEL_STATE_CLOSING + * - CHANNEL_STATE_MAINT + * - CHANNEL_STATE_OPENING + * - CHANNEL_STATE_OPEN + * Permitted transitions to: + * - None + */ + CHANNEL_STATE_ERROR, + /* + * Placeholder for maximum state value + */ + CHANNEL_STATE_LAST +} channel_state_t; + +/* channel listener states for channel_listener_t */ + +typedef enum { + /* + * Closed state - channel listener is inactive + * + * Permitted transitions from: + * - CHANNEL_LISTENER_STATE_CLOSING + * Permitted transitions to: + * - CHANNEL_LISTENER_STATE_LISTENING + */ + CHANNEL_LISTENER_STATE_CLOSED = 0, + /* + * Listening state - channel listener is listening for incoming + * connections + * + * Permitted transitions from: + * - CHANNEL_LISTENER_STATE_CLOSED + * Permitted transitions to: + * - CHANNEL_LISTENER_STATE_CLOSING + * - CHANNEL_LISTENER_STATE_ERROR + */ + CHANNEL_LISTENER_STATE_LISTENING, + /* + * Closing state - channel listener is shutting down + * + * Permitted transitions from: + * - CHANNEL_LISTENER_STATE_LISTENING + * Permitted transitions to: + * - CHANNEL_LISTENER_STATE_CLOSED, + * - CHANNEL_LISTENER_STATE_ERROR + */ + CHANNEL_LISTENER_STATE_CLOSING, + /* + * Error state - channel listener has experienced a permanent error + * + * Permitted transitions from: + * - CHANNEL_STATE_CLOSING + * - CHANNEL_STATE_LISTENING + * Permitted transitions to: + * - None + */ + CHANNEL_LISTENER_STATE_ERROR, + /* + * Placeholder for maximum state value + */ + CHANNEL_LISTENER_STATE_LAST +} channel_listener_state_t; + +/* TLS channel stuff */ + +typedef struct channel_tls_s channel_tls_t; + +/* circuitmux_t typedef; struct circuitmux_s is in circuitmux.h */ + +typedef struct circuitmux_s circuitmux_t; + /** Parsed onion routing cell. All communication between nodes * is via cells. */ typedef struct cell_t { @@ -1060,14 +1210,11 @@ typedef struct connection_t { /** Unique identifier for this connection on this Tor instance. */ uint64_t global_identifier; - - /** Unique ID for measuring tunneled network status requests. */ - uint64_t dirreq_id; } connection_t; /** Subtype of connection_t; used for a listener socket. */ typedef struct listener_connection_t { - connection_t _base; + connection_t base_; /** If the connection is a CONN_TYPE_AP_DNS_LISTENER, this field points * to the evdns_server_port it uses to listen to and answer connections. */ @@ -1086,6 +1233,36 @@ typedef struct listener_connection_t { uint8_t isolation_flags; /**@}*/ + /** For a SOCKS listeners, these fields describe whether we should + * allow IPv4 and IPv6 addresses from our exit nodes, respectively. + * + * @{ + */ + unsigned int socks_ipv4_traffic : 1; + unsigned int socks_ipv6_traffic : 1; + /** @} */ + /** For a socks listener: should we tell the exit that we prefer IPv6 + * addresses? */ + unsigned int socks_prefer_ipv6 : 1; + + /** For a socks listener: should we cache IPv4/IPv6 DNS information that + * exit nodes tell us? + * + * @{ */ + unsigned int cache_ipv4_answers : 1; + unsigned int cache_ipv6_answers : 1; + /** @} */ + /** For a socks listeners: if we find an answer in our client-side DNS cache, + * should we use it? + * + * @{ */ + unsigned int use_cached_ipv4_answers : 1; + unsigned int use_cached_ipv6_answers : 1; + /** @} */ + /** For socks listeners: When we can automap an address to IPv4 or IPv6, + * do we prefer IPv6? */ + unsigned int prefer_ipv6_virtaddr : 1; + } listener_connection_t; /** Minimum length of the random part of an AUTH_CHALLENGE cell. */ @@ -1190,7 +1367,7 @@ typedef struct or_handshake_state_t { /** Subtype of connection_t for an "OR connection" -- that is, one that speaks * cells over TLS. */ typedef struct or_connection_t { - connection_t _base; + connection_t base_; /** Hash of the public RSA key for the other side's identity key, or zeroes * if the other side hasn't shown us a valid identity key. */ @@ -1201,29 +1378,22 @@ typedef struct or_connection_t { int tls_error; /**< Last tor_tls error code. */ /** When we last used this conn for any client traffic. If not * recent, we can rate limit it further. */ - time_t client_used; + + /* Channel using this connection */ + channel_tls_t *chan; tor_addr_t real_addr; /**< The actual address that this connection came from * or went to. The <b>addr</b> field is prone to * getting overridden by the address from the router * descriptor matching <b>identity_digest</b>. */ - circ_id_type_t circ_id_type:2; /**< When we send CREATE cells along this - * connection, which half of the space should - * we use? */ /** Should this connection be used for extending circuits to the server * matching the <b>identity_digest</b> field? Set to true if we're pretty * sure we aren't getting MITMed, either because we're connected to an * address listed in a server descriptor, or because an authenticated * NETINFO cell listed the address we're connected to as recognized. */ unsigned int is_canonical:1; - /** True iff this connection shouldn't get any new circs attached to it, - * because the connection is too old, or because there's a better one. - * More generally, this flag is used to note an unhealthy connection; - * for example, if a bad connection fails we shouldn't assume that the - * router itself has a problem. - */ - unsigned int is_bad_for_new_circs:1; + /** True iff we have decided that the other end of this connection * is a client. Connections with this flag set should never be used * to satisfy an EXTEND request. */ @@ -1233,12 +1403,10 @@ typedef struct or_connection_t { unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */ uint8_t link_proto; /**< What protocol version are we using? 0 for * "none negotiated yet." */ - circid_t next_circ_id; /**< Which circ_id do we try to use next on - * this connection? This is always in the - * range 0..1<<15-1. */ or_handshake_state_t *handshake_state; /**< If we are setting this connection * up, state information to do so. */ + time_t timestamp_lastempty; /**< When was the outbuf last completely empty?*/ time_t timestamp_last_added_nonpadding; /** When did we last add a * non-padding cell to the outbuf? */ @@ -1257,24 +1425,7 @@ typedef struct or_connection_t { /* XXXX we could share this among all connections. */ struct ev_token_bucket_cfg *bucket_cfg; #endif - int n_circuits; /**< How many circuits use this connection as p_conn or - * n_conn ? */ - - /** Double-linked ring of circuits with queued cells waiting for room to - * free up on this connection's outbuf. Every time we pull cells from a - * circuit, we advance this pointer to the next circuit in the ring. */ - struct circuit_t *active_circuits; - /** Priority queue of cell_ewma_t for circuits with queued cells waiting for - * room to free up on this connection's outbuf. Kept in heap order - * according to EWMA. - * - * This is redundant with active_circuits; if we ever decide only to use the - * cell_ewma algorithm for choosing circuits, we can remove active_circuits. - */ - smartlist_t *active_circuit_pqueue; - /** The tick on which the cell_ewma_ts in active_circuit_pqueue last had - * their ewma values rescaled. */ - unsigned active_circuit_pqueue_last_recalibrated; + struct or_connection_t *next_with_same_id; /**< Next connection with same * identity digest as this one. */ } or_connection_t; @@ -1282,7 +1433,7 @@ typedef struct or_connection_t { /** Subtype of connection_t for an "edge connection" -- that is, an entry (ap) * connection, or an exit. */ typedef struct edge_connection_t { - connection_t _base; + connection_t base_; struct edge_connection_t *next_stream; /**< Points to the next stream at this * edge, if any */ @@ -1302,6 +1453,8 @@ typedef struct edge_connection_t { uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit * connection. Exit connections only. */ + uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell + * for this connection */ streamid_t stream_id; /**< The stream ID used for this edge connection on its * circuit */ @@ -1317,6 +1470,8 @@ typedef struct edge_connection_t { /** True iff this connection is for a DNS request only. */ unsigned int is_dns_request:1; + /** True iff this connection is for a PTR DNS request. (exit only) */ + unsigned int is_reverse_dns_lookup:1; unsigned int edge_has_sent_end:1; /**< For debugging; only used on edge * connections. Set once we've set the stream end, @@ -1326,12 +1481,16 @@ typedef struct edge_connection_t { * cells. */ unsigned int edge_blocked_on_circ:1; + /** Unique ID for directory requests; this used to be in connection_t, but + * that's going away and being used on channels instead. We still tag + * edge connections with dirreq_id from circuits, so it's copied here. */ + uint64_t dirreq_id; } edge_connection_t; /** Subtype of edge_connection_t for an "entry connection" -- that is, a SOCKS * connection, a DNS request, a TransPort connection or a NATD connection */ typedef struct entry_connection_t { - edge_connection_t _edge; + edge_connection_t edge_; /** Nickname of planned exit node -- used with .exit support. */ char *chosen_exit_name; @@ -1404,12 +1563,39 @@ typedef struct entry_connection_t { */ unsigned int may_use_optimistic_data : 1; + /** Should we permit IPv4 and IPv6 traffic to use this connection? + * + * @{ */ + unsigned int ipv4_traffic_ok : 1; + unsigned int ipv6_traffic_ok : 1; + /** @} */ + /** Should we say we prefer IPv6 traffic? */ + unsigned int prefer_ipv6_traffic : 1; + + /** For a socks listener: should we cache IPv4/IPv6 DNS information that + * exit nodes tell us? + * + * @{ */ + unsigned int cache_ipv4_answers : 1; + unsigned int cache_ipv6_answers : 1; + /** @} */ + /** For a socks listeners: if we find an answer in our client-side DNS cache, + * should we use it? + * + * @{ */ + unsigned int use_cached_ipv4_answers : 1; + unsigned int use_cached_ipv6_answers : 1; + /** @} */ + /** For socks listeners: When we can automap an address to IPv4 or IPv6, + * do we prefer IPv6? */ + unsigned int prefer_ipv6_virtaddr : 1; + } entry_connection_t; /** Subtype of connection_t for an "directory connection" -- that is, an HTTP * connection to retrieve or serve directory material. */ typedef struct dir_connection_t { - connection_t _base; + connection_t base_; /** Which 'resource' did we ask the directory for? This is typically the part * of the URL string that defines, relative to the directory conn purpose, @@ -1448,11 +1634,15 @@ typedef struct dir_connection_t { char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for * the directory server's signing key. */ + /** Unique ID for directory requests; this used to be in connection_t, but + * that's going away and being used on channels instead. The dirserver still + * needs this for the incoming side, so it's moved here. */ + uint64_t dirreq_id; } dir_connection_t; /** Subtype of connection_t for an connection to a controller. */ typedef struct control_connection_t { - connection_t _base; + connection_t base_; uint32_t event_mask; /**< Bitfield: which events does this controller * care about? */ @@ -1479,12 +1669,12 @@ typedef struct control_connection_t { } control_connection_t; /** Cast a connection_t subtype pointer to a connection_t **/ -#define TO_CONN(c) (&(((c)->_base))) -/** Helper macro: Given a pointer to to._base, of type from*, return &to. */ -#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, _base)) +#define TO_CONN(c) (&(((c)->base_))) +/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */ +#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_)) /** Cast a entry_connection_t subtype pointer to a edge_connection_t **/ -#define ENTRY_TO_EDGE_CONN(c) (&(((c))->_edge)) +#define ENTRY_TO_EDGE_CONN(c) (&(((c))->edge_)) /** Cast a entry_connection_t subtype pointer to a connection_t **/ #define ENTRY_TO_CONN(c) (TO_CONN(ENTRY_TO_EDGE_CONN(c))) @@ -1529,12 +1719,12 @@ static INLINE edge_connection_t *TO_EDGE_CONN(connection_t *c) static INLINE entry_connection_t *TO_ENTRY_CONN(connection_t *c) { tor_assert(c->magic == ENTRY_CONNECTION_MAGIC); - return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, _edge._base); + return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, edge_.base_); } static INLINE entry_connection_t *EDGE_TO_ENTRY_CONN(edge_connection_t *c) { - tor_assert(c->_base.magic == ENTRY_CONNECTION_MAGIC); - return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, _edge); + tor_assert(c->base_.magic == ENTRY_CONNECTION_MAGIC); + return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, edge_); } static INLINE control_connection_t *TO_CONTROL_CONN(connection_t *c) { @@ -1610,7 +1800,15 @@ typedef struct addr_policy_t { maskbits_t maskbits; /**< Accept/reject all addresses <b>a</b> such that the * first <b>maskbits</b> bits of <b>a</b> match * <b>addr</b>. */ - tor_addr_t addr; /**< Base address to accept or reject. */ + /** Base address to accept or reject. + * + * Note that wildcards are treated + * differntly depending on address family. An AF_UNSPEC address means + * "All addresses, IPv4 or IPv6." An AF_INET address with maskbits==0 means + * "All IPv4 addresses" and an AF_INET6 address with maskbits == 0 means + * "All IPv6 addresses". + **/ + tor_addr_t addr; uint16_t prt_min; /**< Lowest port number to accept/reject. */ uint16_t prt_max; /**< Highest port number to accept/reject. */ } addr_policy_t; @@ -1740,6 +1938,8 @@ typedef struct { crypto_pk_t *onion_pkey; /**< Public RSA key for onions. */ crypto_pk_t *identity_pkey; /**< Public RSA key for signing. */ + /** Public curve25519 key for onions */ + curve25519_public_key_t *onion_curve25519_pkey; char *platform; /**< What software/operating system is this OR using? */ @@ -1750,7 +1950,10 @@ typedef struct { /** How many bytes/s is this router known to handle? */ uint32_t bandwidthcapacity; smartlist_t *exit_policy; /**< What streams will this OR permit - * to exit? NULL for 'reject *:*'. */ + * to exit on IPv4? NULL for 'reject *:*'. */ + /** What streams will this OR permit to exit on IPv6? + * NULL for 'reject *:*' */ + struct short_policy_t *ipv6_exit_policy; long uptime; /**< How many seconds the router claims to have been up */ smartlist_t *declared_family; /**< Nicknames of router which this router * claims are its family. */ @@ -1769,8 +1972,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 +1994,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 +2025,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. */ @@ -1862,22 +2056,16 @@ typedef struct routerstatus_t { * included.) We'll replace all these with a big tor_version_t or a char[] * if the number of traits we care about ever becomes incredibly big. */ unsigned int version_known:1; - /** True iff this router is a version that supports BEGIN_DIR cells. */ - unsigned int version_supports_begindir:1; - /** True iff this router is a version that supports conditional consensus - * downloads (signed by list of authorities). */ - unsigned int version_supports_conditional_consensus:1; - /** True iff this router is a version that we can post extrainfo docs to. */ - unsigned int version_supports_extrainfo_upload:1; - /** True iff this router is a version that, if it caches directory info, - * we can get v3 downloads from. */ - unsigned int version_supports_v3_dir:1; + /** True iff this router is a version that, if it caches directory info, * we can get microdescriptors from. */ unsigned int version_supports_microdesc_cache:1; /** True iff this router is a version that allows DATA cells to arrive on * a stream before it has sent a CONNECTED cell. */ unsigned int version_supports_optimistic_data:1; + /** True iff this router has a version that allows it to accept EXTEND2 + * cells */ + unsigned int version_supports_extend2_cells:1; unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ @@ -1968,10 +2156,19 @@ typedef struct microdesc_t { /** As routerinfo_t.onion_pkey */ crypto_pk_t *onion_pkey; + /** As routerinfo_t.onion_curve25519_pkey */ + curve25519_public_key_t *onion_curve25519_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 */ + /** IPv4 exit policy summary */ short_policy_t *exit_policy; + /** IPv6 exit policy summary */ + short_policy_t *ipv6_exit_policy; + } microdesc_t; /** A node_t represents a Tor router. @@ -2006,13 +2203,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 +2232,20 @@ 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. */ + } node_t; /** How many times will we try to download a router's descriptor before giving @@ -2099,6 +2308,9 @@ typedef struct vote_microdesc_hash_t { typedef struct vote_routerstatus_t { routerstatus_t status; /**< Underlying 'status' object for this router. * Flags are redundant. */ + /** How many known-flags are allowed in a vote? This is the width of + * the flags field of vote_routerstatus_t */ +#define MAX_KNOWN_FLAGS_IN_VOTE 64 uint64_t flags; /**< Bit-field for all recognized flags; index into * networkstatus_t.known_flags. */ char *version; /**< The version that the authority says this router is @@ -2305,6 +2517,9 @@ typedef struct extend_info_t { uint16_t port; /**< OR port. */ tor_addr_t addr; /**< IP address. */ crypto_pk_t *onion_key; /**< Current onionskin key. */ +#ifdef CURVE25519_ENABLED + curve25519_public_key_t curve25519_onion_key; +#endif } extend_info_t; /** Certificate for v3 directory protocol: binds long-term authority identity @@ -2357,8 +2572,24 @@ typedef enum { MICRODESC_DIRINFO=1 << 6, } dirinfo_type_t; +#define ALL_DIRINFO ((dirinfo_type_t)((1<<7)-1)) + #define CRYPT_PATH_MAGIC 0x70127012u +struct fast_handshake_state_t; +struct ntor_handshake_state_t; +#define ONION_HANDSHAKE_TYPE_TAP 0x0000 +#define ONION_HANDSHAKE_TYPE_FAST 0x0001 +#define ONION_HANDSHAKE_TYPE_NTOR 0x0002 +typedef struct { + uint16_t tag; + union { + struct fast_handshake_state_t *fast; + crypto_dh_t *tap; + struct ntor_handshake_state_t *ntor; + } u; +} onion_handshake_state_t; + /** Holds accounting information for a single step in the layered encryption * performed by a circuit. Used only at the client edge of a circuit. */ typedef struct crypt_path_t { @@ -2377,17 +2608,15 @@ typedef struct crypt_path_t { /** Digest state for cells heading away from the OR at this step. */ crypto_digest_t *b_digest; - /** Current state of Diffie-Hellman key negotiation with the OR at this + /** Current state of the handshake as performed with the OR at this * step. */ - crypto_dh_t *dh_handshake_state; - /** Current state of 'fast' (non-PK) key negotiation with the OR at this - * step. Used to save CPU when TLS is already providing all the - * authentication, secrecy, and integrity we need, and we're already - * distinguishable from an OR. - */ - uint8_t fast_handshake_state[DIGEST_LEN]; + onion_handshake_state_t handshake_state; + /** Diffie-hellman handshake state for performing an introduction + * operations */ + crypto_dh_t *rend_dh_handshake_state; + /** Negotiated key material shared with the OR at this step. */ - char handshake_digest[DIGEST_LEN];/* KH in tor-spec.txt */ + char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */ /** Information to extend to the OR at this step. */ extend_info_t *extend_info; @@ -2428,10 +2657,6 @@ typedef struct { #define CPATH_KEY_MATERIAL_LEN (20*2+16*2) #define DH_KEY_LEN DH_BYTES -#define ONIONSKIN_CHALLENGE_LEN (PKCS1_OAEP_PADDING_OVERHEAD+\ - CIPHER_KEY_LEN+\ - DH_KEY_LEN) -#define ONIONSKIN_REPLY_LEN (DH_KEY_LEN+DIGEST_LEN) /** Information used to build a circuit. */ typedef struct { @@ -2460,32 +2685,11 @@ typedef struct { time_t expiry_time; } cpath_build_state_t; -/** - * The cell_ewma_t structure keeps track of how many cells a circuit has - * transferred recently. It keeps an EWMA (exponentially weighted moving - * average) of the number of cells flushed from the circuit queue onto a - * connection in connection_or_flush_from_first_active_circuit(). - */ -typedef struct { - /** The last 'tick' at which we recalibrated cell_count. - * - * A cell sent at exactly the start of this tick has weight 1.0. Cells sent - * since the start of this tick have weight greater than 1.0; ones sent - * earlier have less weight. */ - unsigned last_adjusted_tick; - /** The EWMA of the cell count. */ - double cell_count; - /** True iff this is the cell count for a circuit's previous - * connection. */ - unsigned int is_for_p_conn : 1; - /** The position of the circuit within the OR connection's priority - * queue. */ - int heap_index; -} cell_ewma_t; - #define ORIGIN_CIRCUIT_MAGIC 0x35315243u #define OR_CIRCUIT_MAGIC 0x98ABC04Fu +struct create_cell_t; + /** * A circuit is a path over the onion routing * network. Applications can connect to one end of the circuit, and can @@ -2513,23 +2717,39 @@ typedef struct circuit_t { uint32_t magic; /**< For memory and type debugging: must equal * ORIGIN_CIRCUIT_MAGIC or OR_CIRCUIT_MAGIC. */ - /** Queue of cells waiting to be transmitted on n_conn. */ - cell_queue_t n_conn_cells; - /** The OR connection that is next in this circuit. */ - or_connection_t *n_conn; - /** The circuit_id used in the next (forward) hop of this circuit. */ + /** The channel that is next in this circuit. */ + channel_t *n_chan; + + /** + * The circuit_id used in the next (forward) hop of this circuit; + * this is unique to n_chan, but this ordered pair is globally + * unique: + * + * (n_chan->global_identifier, n_circ_id) + */ circid_t n_circ_id; - /** The hop to which we want to extend this circuit. Should be NULL if - * the circuit has attached to a connection. */ + /** + * Circuit mux associated with n_chan to which this circuit is attached; + * NULL if we have no n_chan. + */ + circuitmux_t *n_mux; + + /** Queue of cells waiting to be transmitted on n_chan */ + cell_queue_t n_chan_cells; + + /** + * The hop to which we want to extend this circuit. Should be NULL if + * the circuit has attached to a channel. + */ extend_info_t *n_hop; - /** True iff we are waiting for n_conn_cells to become less full before + /** True iff we are waiting for n_chan_cells to become less full before * allowing p_streams to add any more cells. (Origin circuit only.) */ - unsigned int streams_blocked_on_n_conn : 1; - /** True iff we are waiting for p_conn_cells to become less full before + unsigned int streams_blocked_on_n_chan : 1; + /** True iff we are waiting for p_chan_cells to become less full before * allowing n_streams to add any more cells. (OR circuit only.) */ - unsigned int streams_blocked_on_p_conn : 1; + unsigned int streams_blocked_on_p_chan : 1; uint8_t state; /**< Current status of this circuit. */ uint8_t purpose; /**< Why are we creating this circuit? */ @@ -2544,15 +2764,24 @@ typedef struct circuit_t { * more. */ int deliver_window; - /** For storage while n_conn is pending - * (state CIRCUIT_STATE_OR_WAIT). When defined, it is always - * length ONIONSKIN_CHALLENGE_LEN. */ - char *n_conn_onionskin; + /** For storage while n_chan is pending (state CIRCUIT_STATE_CHAN_WAIT). */ + struct create_cell_t *n_chan_create_cell; - /** When was this circuit created? We keep this timestamp with a higher - * resolution than most so that the circuit-build-time tracking code can - * get millisecond resolution. */ + /** When did circuit construction actually begin (ie send the + * CREATE cell or begin cannibalization). + * + * Note: This timer will get reset if we decide to cannibalize + * a circuit. It may also get reset during certain phases of hidden + * service circuit use. + * + * We keep this timestamp with a higher resolution than most so that the + * circuit-build-time tracking code can get millisecond resolution. + */ + struct timeval timestamp_began; + + /** This timestamp marks when the init_circuit_base constructor ran. */ struct timeval timestamp_created; + /** When the circuit was first used, or 0 if the circuit is clean. * * XXXX023 Note that some code will artifically adjust this value backward @@ -2573,23 +2802,19 @@ typedef struct circuit_t { const char *marked_for_close_file; /**< For debugging: in which file was this * circuit marked for close? */ + /** Unique ID for measuring tunneled network status requests. */ + uint64_t dirreq_id; + + struct circuit_t *next; /**< Next circuit in linked list of all circuits. */ + /** Next circuit in the doubly-linked ring of circuits waiting to add * cells to n_conn. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ - struct circuit_t *next_active_on_n_conn; + struct circuit_t *next_active_on_n_chan; /** Previous circuit in the doubly-linked ring of circuits waiting to add * cells to n_conn. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ - struct circuit_t *prev_active_on_n_conn; - struct circuit_t *next; /**< Next circuit in linked list of all circuits. */ - - /** Unique ID for measuring tunneled network status requests. */ - uint64_t dirreq_id; - - /** The EWMA count for the number of cells flushed from the - * n_conn_cells queue. Used to determine which circuit to flush from next. - */ - cell_ewma_t n_cell_ewma; + struct circuit_t *prev_active_on_n_chan; } circuit_t; /** Largest number of relay_early cells that we can send on a given @@ -2605,18 +2830,32 @@ typedef enum { /** This circuit is "new". It has not yet completed a first hop * or been counted by the path bias code. */ PATH_STATE_NEW_CIRC = 0, - /** This circuit has completed a first hop, and has been counted by + /** This circuit has completed one/two hops, and has been counted by * the path bias logic. */ - PATH_STATE_DID_FIRST_HOP = 1, - /** This circuit has been completely built, and has been counted as - * successful by the path bias logic. */ - PATH_STATE_SUCCEEDED = 2, + PATH_STATE_BUILD_ATTEMPTED = 1, + /** This circuit has been completely built */ + PATH_STATE_BUILD_SUCCEEDED = 2, + /** Did any SOCKS streams or hidserv introductions actually succeed on + * this circuit? + * + * Note: If we ever implement end-to-end stream timing through test + * stream probes (#5707), we must *not* set this for those probes + * (or any other automatic streams) because the adversary could + * just tag at a later point. + */ + PATH_STATE_USE_SUCCEEDED = 3, + + /** + * This is a special state to indicate that we got a corrupted + * relay cell on a circuit and we don't intend to probe it. + */ + PATH_STATE_USE_FAILED = 4, } path_state_t; /** An origin_circuit_t holds data necessary to build and use a circuit. */ typedef struct origin_circuit_t { - circuit_t _base; + circuit_t base_; /** Linked list of AP streams (or EXIT streams if hidden service) * associated with this circuit. */ @@ -2648,7 +2887,15 @@ typedef struct origin_circuit_t { /** Kludge to help us prevent the warn in bug #6475 and eventually * debug why we are not seeing first hops in some cases. */ - path_state_t path_state : 2; + path_state_t path_state : 3; + + /** For path probing. Store the temporary probe stream ID + * for response comparison */ + streamid_t pathbias_probe_id; + + /** For path probing. Store the temporary probe address nonce + * (in host byte order) for response comparison. */ + uint32_t pathbias_probe_nonce; /** Set iff this is a hidden-service circuit which has timed out * according to our current circuit-build timeout, but which has @@ -2666,6 +2913,10 @@ typedef struct origin_circuit_t { * service-side introduction circuits never have this flag set.) */ unsigned int hs_circ_has_timed_out : 1; + /** Set iff this circuit has been given a relaxed timeout because + * no circuits have opened. Used to prevent spamming logs. */ + unsigned int relaxed_timeout : 1; + /** Set iff this is a service-side rendezvous circuit for which a * new connection attempt has been launched. We consider launching * a new service-side rend circ to a client when the previous one @@ -2749,23 +3000,28 @@ typedef struct origin_circuit_t { /** An or_circuit_t holds information needed to implement a circuit at an * OR. */ typedef struct or_circuit_t { - circuit_t _base; + circuit_t base_; /** Next circuit in the doubly-linked ring of circuits waiting to add - * cells to p_conn. NULL if we have no cells pending, or if we're not + * cells to p_chan. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ - struct circuit_t *next_active_on_p_conn; + struct circuit_t *next_active_on_p_chan; /** Previous circuit in the doubly-linked ring of circuits waiting to add - * cells to p_conn. NULL if we have no cells pending, or if we're not + * cells to p_chan. NULL if we have no cells pending, or if we're not * linked to an OR connection. */ - struct circuit_t *prev_active_on_p_conn; + struct circuit_t *prev_active_on_p_chan; /** The circuit_id used in the previous (backward) hop of this circuit. */ circid_t p_circ_id; /** Queue of cells waiting to be transmitted on p_conn. */ - cell_queue_t p_conn_cells; - /** The OR connection that is previous in this circuit. */ - or_connection_t *p_conn; + cell_queue_t p_chan_cells; + /** The channel that is previous in this circuit. */ + channel_t *p_chan; + /** + * Circuit mux associated with p_chan to which this circuit is attached; + * NULL if we have no p_chan. + */ + circuitmux_t *p_mux; /** Linked list of Exit streams associated with this circuit. */ edge_connection_t *n_streams; /** Linked list of Exit streams associated with this circuit that are @@ -2805,7 +3061,8 @@ typedef struct or_circuit_t { char rend_token[REND_TOKEN_LEN]; /* ???? move to a subtype or adjunct structure? Wastes 20 bytes -NM */ - char handshake_digest[DIGEST_LEN]; /**< Stores KH for the handshake. */ + /** Stores KH for the handshake. */ + char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */ /** How many more relay_early cells can we send on this circuit, according * to the specification? */ @@ -2822,14 +3079,10 @@ typedef struct or_circuit_t { * exit-ward queues of this circuit; reset every time when writing * buffer stats to disk. */ uint64_t total_cell_waiting_time; - - /** The EWMA count for the number of cells flushed from the - * p_conn_cells queue. */ - cell_ewma_t p_cell_ewma; } or_circuit_t; /** Convert a circuit subtype to a circuit_t. */ -#define TO_CIRCUIT(x) (&((x)->_base)) +#define TO_CIRCUIT(x) (&((x)->base_)) /** Convert a circuit_t* to a pointer to the enclosing or_circuit_t. Assert * if the cast is impossible. */ @@ -2915,8 +3168,31 @@ typedef struct port_cfg_t { unsigned int no_advertise : 1; unsigned int no_listen : 1; unsigned int all_addrs : 1; - unsigned int ipv4_only : 1; - unsigned int ipv6_only : 1; + unsigned int bind_ipv4_only : 1; + unsigned int bind_ipv6_only : 1; + + /* Client port types only: */ + unsigned int ipv4_traffic : 1; + unsigned int ipv6_traffic : 1; + unsigned int prefer_ipv6 : 1; + + /** For a socks listener: should we cache IPv4/IPv6 DNS information that + * exit nodes tell us? + * + * @{ */ + unsigned int cache_ipv4_answers : 1; + unsigned int cache_ipv6_answers : 1; + /** @} */ + /** For a socks listeners: if we find an answer in our client-side DNS cache, + * should we use it? + * + * @{ */ + unsigned int use_cached_ipv4_answers : 1; + unsigned int use_cached_ipv6_answers : 1; + /** @} */ + /** For socks listeners: When we can automap an address to IPv4 or IPv6, + * do we prefer IPv6? */ + unsigned int prefer_ipv6_virtaddr : 1; /* Unix sockets only: */ /** Path for an AF_UNIX address */ @@ -2952,7 +3228,7 @@ typedef struct routerset_t routerset_t; /** Configuration options for a Tor process. */ typedef struct { - uint32_t _magic; + uint32_t magic_; /** What should the tor process actually do? */ enum { @@ -2994,7 +3270,7 @@ typedef struct { * ORs not to consider as exits. */ /** Union of ExcludeNodes and ExcludeExitNodes */ - routerset_t *_ExcludeExitNodesUnion; + routerset_t *ExcludeExitNodesUnion_; int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our * process for all current and future memory. */ @@ -3002,7 +3278,7 @@ typedef struct { /** List of "entry", "middle", "exit", "introduction", "rendezvous". */ smartlist_t *AllowInvalidNodes; /** Bitmask; derived from AllowInvalidNodes. */ - invalid_router_usage_t _AllowInvalid; + invalid_router_usage_t AllowInvalid_; config_line_t *ExitPolicy; /**< Lists of exit policy components. */ int ExitPolicyRejectPrivate; /**< Should we not exit to local addresses? */ config_line_t *SocksPolicy; /**< Lists of socks policy components */ @@ -3023,7 +3299,11 @@ typedef struct { /** Addresses to bind for listening for control connections. */ config_line_t *ControlListenAddress; /** Local address to bind outbound sockets */ - char *OutboundBindAddress; + config_line_t *OutboundBindAddress; + /** IPv4 address derived from OutboundBindAddress. */ + tor_addr_t OutboundBindAddressIPv4_; + /** IPv6 address derived from OutboundBindAddress. */ + tor_addr_t OutboundBindAddressIPv6_; /** Directory server only: which versions of * Tor should we tell users to run? */ config_line_t *RecommendedVersions; @@ -3092,7 +3372,7 @@ typedef struct { char *BridgePassword; /** If BridgePassword is set, this is a SHA256 digest of the basic http * authenticator for it. Used so we can do a time-independent comparison. */ - char *_BridgePassword_AuthDigest; + char *BridgePassword_AuthDigest_; int UseBridges; /**< Boolean: should we start all circuits with a bridge? */ config_line_t *Bridges; /**< List of bootstrap bridge addresses. */ @@ -3103,6 +3383,9 @@ typedef struct { config_line_t *ServerTransportPlugin; /**< List of client transport plugins. */ + /** List of TCP/IP addresses that transports should listen at. */ + config_line_t *ServerTransportListenAddr; + int BridgeRelay; /**< Boolean: are we acting as a bridge relay? We make * this explicit so we can change how we behave in the * future. */ @@ -3118,7 +3401,7 @@ typedef struct { * "v1", "v2", "v3", "bridge", or "". */ smartlist_t *PublishServerDescriptor; /** A bitfield of authority types, derived from PublishServerDescriptor. */ - dirinfo_type_t _PublishServerDescriptor; + dirinfo_type_t PublishServerDescriptor_; /** Boolean: do we publish hidden service descriptors to the HS auths? */ int PublishHidServDescriptors; int FetchServerDescriptors; /**< Do we fetch server descriptors as normal? */ @@ -3151,7 +3434,7 @@ typedef struct { int CloseHSServiceRendCircuitsImmediatelyOnTimeout; int ConnLimit; /**< Demanded minimum number of simultaneous connections. */ - int _ConnLimit; /**< Maximum allowed number of simultaneous connections. */ + int ConnLimit_; /**< Maximum allowed number of simultaneous connections. */ int RunAsDaemon; /**< If true, run in the background. (Unix only) */ int FascistFirewall; /**< Whether to prefer ORs reachable on open ports. */ smartlist_t *FirewallPorts; /**< Which ports our firewall allows @@ -3264,7 +3547,14 @@ typedef struct { /** List of configuration lines for replacement directory authorities. * If you just want to replace one class of authority at a time, * use the "Alternate*Authority" options below instead. */ - config_line_t *DirServers; + config_line_t *DirAuthorities; + + /** List of fallback directory servers */ + config_line_t *FallbackDir; + + /** Weight to apply to all directory authority rates if considering them + * along with fallbackdirs */ + double DirAuthorityFallbackRate; /** If set, use these main (currently v3) directory authorities and * not the default ones. */ @@ -3312,6 +3602,7 @@ typedef struct { int AuthDirMaxServersPerAuthAddr; /**< Do not permit more than this * number of servers per IP address shared * with an authority. */ + int AuthDirHasIPv6Connectivity; /**< Boolean: are we on IPv6? */ /** If non-zero, always vote the Fast flag for any relay advertising * this amount of capacity or more. */ @@ -3354,7 +3645,7 @@ typedef struct { /* Derived from SafeLogging */ enum { SAFELOG_SCRUB_ALL, SAFELOG_SCRUB_RELAY, SAFELOG_SCRUB_NONE - } _SafeLogging; + } SafeLogging_; int SafeSocks; /**< Boolean: should we outright refuse application * connections that use socks4 or socks5-with-local-dns? */ @@ -3373,6 +3664,9 @@ typedef struct { int UseEntryGuards; /**< Boolean: Do we try to enter from a smallish number * of fixed nodes? */ int NumEntryGuards; /**< How many entry guards do we try to establish? */ + int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info + * from a smallish number of fixed nodes? */ + int NumDirectoryGuards; /**< How many dir guards do we try to establish? */ int RephistTrackTime; /**< How many seconds do we keep rephist info? */ int FastFirstHopPK; /**< If Tor believes it is safe, should we save a third * of our PK time by sending CREATE_FAST cells? */ @@ -3383,8 +3677,10 @@ typedef struct { /** Should we fetch our dir info at the start of the consensus period? */ int FetchDirInfoExtraEarly; - char *VirtualAddrNetwork; /**< Address and mask to hand out for virtual - * MAPADDRESS requests. */ + char *VirtualAddrNetworkIPv4; /**< Address and mask to hand out for virtual + * MAPADDRESS requests for IPv4 addresses */ + char *VirtualAddrNetworkIPv6; /**< Address and mask to hand out for virtual + * MAPADDRESS requests for IPv6 addresses */ int ServerDNSSearchDomains; /**< Boolean: If set, we don't force exit * addresses to be FQDNs, but rather search for them in * the local domains. */ @@ -3478,6 +3774,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. */ @@ -3525,17 +3828,14 @@ typedef struct { * of certain configuration options. */ int TestingTorNetwork; - /** File to check for a consensus networkstatus, if we don't have one - * cached. */ - char *FallbackNetworkstatusFile; - /** If true, and we have GeoIP data, and we're a bridge, keep a per-country * count of how many client addresses have contacted us so that we can help * the bridge authority guess which countries have blocked access to us. */ int BridgeRecordUsageByCountry; - /** Optionally, a file with GeoIP data. */ + /** Optionally, IPv4 and IPv6 GeoIP data. */ char *GeoIPFile; + char *GeoIPv6File; /** If true, SIGHUP should reload the torrc. Sometimes controllers want * to make this false. */ @@ -3559,13 +3859,13 @@ typedef struct { /** If true, do not enable IOCP on windows with bufferevents, even if * we think we could. */ int DisableIOCP; - /** For testing only: will go away in 0.2.3.x. */ - int _UseFilteringSSLBufferevents; + /** For testing only: will go away eventually. */ + int UseFilteringSSLBufferevents; /** Set to true if the TestingTorNetwork configuration option is set. * This is used so that options_validate() has a chance to realize that * the defaults have changed. */ - int _UsingTestNetworkDefaults; + int UsingTestNetworkDefaults_; /** If 1, we try to use microdescriptors to build circuits. If 0, we don't. * If -1, Tor decides. */ @@ -3600,16 +3900,26 @@ typedef struct { */ int PathBiasCircThreshold; double PathBiasNoticeRate; - double PathBiasDisableRate; + double PathBiasWarnRate; + double PathBiasExtremeRate; + int PathBiasDropGuards; int PathBiasScaleThreshold; int PathBiasScaleFactor; + int PathBiasMultFactor; + int PathBiasUseCloseCounts; /** @} */ + int IPv6Exit; /**< Do we support exiting to IPv6 addresses? */ + + char *TLSECGroup; /**< One of "P256", "P224", or nil for auto */ + + /** Autobool: should we use the ntor handshake if we can? */ + int UseNTorHandshake; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ typedef struct { - uint32_t _magic; + uint32_t magic_; /** The time at which we next plan to write the state to the disk. Equal to * TIME_MAX if there are no savable changes, 0 if there are changes that * should be saved right away. */ @@ -4121,10 +4431,10 @@ typedef enum { /** Flushed last cell from queue of the circuit that initiated a * tunneled request to the outbuf of the OR connection. */ DIRREQ_CIRC_QUEUE_FLUSHED = 3, - /** Flushed last byte from buffer of the OR connection belonging to the + /** Flushed last byte from buffer of the channel belonging to the * circuit that initiated a tunneled request; completes a tunneled * request. */ - DIRREQ_OR_CONN_BUFFER_FLUSHED = 4 + DIRREQ_CHANNEL_BUFFER_FLUSHED = 4 } dirreq_state_t; #define WRITE_STATS_INTERVAL (24*60*60) @@ -4251,12 +4561,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 @@ -4312,19 +4625,23 @@ typedef struct rend_cache_entry_t { /********************************* routerlist.c ***************************/ -/** Represents information about a single trusted directory server. */ -typedef struct trusted_dir_server_t { +/** Represents information about a single trusted or fallback directory + * server. */ +typedef struct dir_server_t { char *description; char *nickname; char *address; /**< Hostname. */ uint32_t addr; /**< IPv4 address. */ uint16_t dir_port; /**< Directory port. */ uint16_t or_port; /**< OR port: Used for tunneling connections. */ + double weight; /** Weight used when selecting this node at random */ char digest[DIGEST_LEN]; /**< Digest of identity key. */ char v3_identity_digest[DIGEST_LEN]; /**< Digest of v3 (authority only, * high-security) identity key. */ unsigned int is_running:1; /**< True iff we think this server is running. */ + unsigned int is_authority:1; /**< True iff this is a directory authority + * of some kind. */ /** True iff this server has accepted the most recent server descriptor * we tried to upload to it. */ @@ -4343,7 +4660,7 @@ typedef struct trusted_dir_server_t { * as a routerstatus_t. Not updated by the * router-status management code! **/ -} trusted_dir_server_t; +} dir_server_t; #define ROUTER_REQUIRED_MIN_BANDWIDTH (20*1024) @@ -4379,7 +4696,7 @@ typedef struct trusted_dir_server_t { #define PDS_NO_EXISTING_SERVERDESC_FETCH (1<<3) #define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4) -#define _PDS_PREFER_TUNNELED_DIR_CONNS (1<<16) +#define PDS_PREFER_TUNNELED_DIR_CONNS_ (1<<16) /** Possible ways to weight routers when choosing one randomly. See * routerlist_sl_choose_by_bandwidth() for more information.*/ diff --git a/src/or/policies.c b/src/or/policies.c index 81e480968..27cdd84af 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -59,8 +59,10 @@ typedef struct policy_summary_item_t { static const char *private_nets[] = { "0.0.0.0/8", "169.254.0.0/16", "127.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12", - // "fc00::/7", "fe80::/10", "fec0::/10", "::/127", - NULL }; + "[::]/8", + "[fc00::]/7", "[fe80::]/10", "[fec0::]/10", "[ff00::]/8", "[::]/127", + NULL +}; /** Replace all "private" entries in *<b>policy</b> with their expanded * equivalents. */ @@ -87,7 +89,8 @@ policy_expand_private(smartlist_t **policy) memcpy(&newpolicy, p, sizeof(addr_policy_t)); newpolicy.is_private = 0; newpolicy.is_canonical = 0; - if (tor_addr_parse_mask_ports(private_nets[i], &newpolicy.addr, + if (tor_addr_parse_mask_ports(private_nets[i], 0, + &newpolicy.addr, &newpolicy.maskbits, &port_min, &port_max)<0) { tor_assert(0); } @@ -100,6 +103,49 @@ policy_expand_private(smartlist_t **policy) *policy = tmp; } +/** Expand each of the AF_UNSPEC elements in *<b>policy</b> (which indicate + * protocol-neutral wildcards) into a pair of wildcard elements: one IPv4- + * specific and one IPv6-specific. */ +void +policy_expand_unspec(smartlist_t **policy) +{ + smartlist_t *tmp; + if (!*policy) + return; + + tmp = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(*policy, addr_policy_t *, p) { + sa_family_t family = tor_addr_family(&p->addr); + if (family == AF_INET6 || family == AF_INET || p->is_private) { + smartlist_add(tmp, p); + } else if (family == AF_UNSPEC) { + addr_policy_t newpolicy_ipv4; + addr_policy_t newpolicy_ipv6; + memcpy(&newpolicy_ipv4, p, sizeof(addr_policy_t)); + memcpy(&newpolicy_ipv6, p, sizeof(addr_policy_t)); + newpolicy_ipv4.is_canonical = 0; + newpolicy_ipv6.is_canonical = 0; + if (p->maskbits != 0) { + log_warn(LD_BUG, "AF_UNSPEC policy with maskbits==%d", p->maskbits); + newpolicy_ipv4.maskbits = 0; + newpolicy_ipv6.maskbits = 0; + } + tor_addr_from_ipv4h(&newpolicy_ipv4.addr, 0); + tor_addr_from_ipv6_bytes(&newpolicy_ipv6.addr, + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + smartlist_add(tmp, addr_policy_get_canonical_entry(&newpolicy_ipv4)); + smartlist_add(tmp, addr_policy_get_canonical_entry(&newpolicy_ipv6)); + addr_policy_free(p); + } else { + log_warn(LD_BUG, "Funny-looking address policy with family %d", family); + smartlist_add(tmp, p); + } + } SMARTLIST_FOREACH_END(p); + + smartlist_free(*policy); + *policy = tmp; +} + /** * Given a linked list of config lines containing "allow" and "deny" * tokens, parse them and append the result to <b>dest</b>. Return -1 @@ -144,6 +190,7 @@ parse_addr_policy(config_line_t *cfg, smartlist_t **dest, addr_policy_list_free(result); } else { policy_expand_private(&result); + policy_expand_unspec(&result); if (*dest) { smartlist_add_all(*dest, result); @@ -319,9 +366,13 @@ addr_is_in_cc_list(uint32_t addr, const smartlist_t *cc_list) { country_t country; const char *name; + tor_addr_t tar; + if (!cc_list) return 0; - country = geoip_get_country_by_ip(addr); + /* XXXXipv6 */ + tor_addr_from_ipv4h(&tar, addr); + country = geoip_get_country_by_addr(&tar); name = geoip_get_country_name(country); return smartlist_string_isin_case(cc_list, name); } @@ -386,6 +437,7 @@ validate_addr_policies(const or_options_t *options, char **msg) *msg = NULL; if (policies_parse_exit_policy(options->ExitPolicy, &addr_policy, + options->IPv6Exit, options->ExitPolicyRejectPrivate, NULL, !options->BridgeRelay)) REJECT("Error in ExitPolicy entry."); @@ -730,6 +782,10 @@ compare_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, static int addr_policy_covers(addr_policy_t *a, addr_policy_t *b) { + if (tor_addr_family(&a->addr) != tor_addr_family(&b->addr)) { + /* You can't cover a different family. */ + return 0; + } /* We can ignore accept/reject, since "accept *:80, reject *:80" reduces * to "accept *:80". */ if (a->maskbits > b->maskbits) { @@ -785,20 +841,32 @@ append_exit_policy_string(smartlist_t **policy, const char *more) static void exit_policy_remove_redundancies(smartlist_t *dest) { - addr_policy_t *ap, *tmp, *victim; + addr_policy_t *ap, *tmp; int i, j; - /* Step one: find a *:* entry and cut off everything after it. */ - for (i = 0; i < smartlist_len(dest); ++i) { - ap = smartlist_get(dest, i); - if (ap->maskbits == 0 && ap->prt_min <= 1 && ap->prt_max >= 65535) { - /* This is a catch-all line -- later lines are unreachable. */ - while (i+1 < smartlist_len(dest)) { - victim = smartlist_get(dest, i+1); - smartlist_del(dest, i+1); - addr_policy_free(victim); + /* Step one: kill every ipv4 thing after *4:*, every IPv6 thing after *6:* + */ + { + int kill_v4=0, kill_v6=0; + for (i = 0; i < smartlist_len(dest); ++i) { + sa_family_t family; + ap = smartlist_get(dest, i); + family = tor_addr_family(&ap->addr); + if ((family == AF_INET && kill_v4) || + (family == AF_INET6 && kill_v6)) { + smartlist_del_keeporder(dest, i--); + addr_policy_free(ap); + continue; + } + + if (ap->maskbits == 0 && ap->prt_min <= 1 && ap->prt_max >= 65535) { + /* This is a catch-all line -- later lines are unreachable. */ + if (family == AF_INET) { + kill_v4 = 1; + } else if (family == AF_INET6) { + kill_v6 = 1; + } } - break; } } @@ -864,12 +932,20 @@ exit_policy_remove_redundancies(smartlist_t *dest) * policy afterwards. If <b>rejectprivate</b> is true, prepend * "reject private:*" to the policy. Return -1 if we can't parse cfg, * else return 0. + * + * This function is used to parse the exit policy from our torrc. For + * the functions used to parse the exit policy from a router descriptor, + * see router_add_exit_policy. */ int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, + int ipv6_exit, int rejectprivate, const char *local_address, int add_default_policy) { + if (!ipv6_exit) { + append_exit_policy_string(dest, "reject *6:*"); + } if (rejectprivate) { append_exit_policy_string(dest, "reject private:*"); if (local_address) { @@ -880,10 +956,12 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, } if (parse_addr_policy(cfg, dest, -1)) return -1; - if (add_default_policy) + if (add_default_policy) { append_exit_policy_string(dest, DEFAULT_EXIT_POLICY); - else - append_exit_policy_string(dest, "reject *:*"); + } else { + append_exit_policy_string(dest, "reject *4:*"); + append_exit_policy_string(dest, "reject *6:*"); + } exit_policy_remove_redundancies(*dest); return 0; @@ -894,7 +972,8 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, void policies_exit_policy_append_reject_star(smartlist_t **dest) { - append_exit_policy_string(dest, "reject *:*"); + append_exit_policy_string(dest, "reject *4:*"); + append_exit_policy_string(dest, "reject *6:*"); } /** Replace the exit policy of <b>node</b> with reject *:* */ @@ -970,18 +1049,23 @@ exit_policy_is_general_exit(smartlist_t *policy) /** Return false if <b>policy</b> might permit access to some addr:port; * otherwise if we are certain it rejects everything, return true. */ int -policy_is_reject_star(const smartlist_t *policy) +policy_is_reject_star(const smartlist_t *policy, sa_family_t family) { if (!policy) /*XXXX disallow NULL policies? */ return 1; - SMARTLIST_FOREACH(policy, addr_policy_t *, p, { - if (p->policy_type == ADDR_POLICY_ACCEPT) + SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, p) { + if (p->policy_type == ADDR_POLICY_ACCEPT && + (tor_addr_family(&p->addr) == family || + tor_addr_family(&p->addr) == AF_UNSPEC)) { return 0; - else if (p->policy_type == ADDR_POLICY_REJECT && - p->prt_min <= 1 && p->prt_max == 65535 && - p->maskbits == 0) + } else if (p->policy_type == ADDR_POLICY_REJECT && + p->prt_min <= 1 && p->prt_max == 65535 && + p->maskbits == 0 && + (tor_addr_family(&p->addr) == family || + tor_addr_family(&p->addr) == AF_UNSPEC)) { return 1; - }); + } + } SMARTLIST_FOREACH_END(p); return 1; } @@ -996,20 +1080,28 @@ policy_write_item(char *buf, size_t buflen, addr_policy_t *policy, const char *addrpart; int result; const int is_accept = policy->policy_type == ADDR_POLICY_ACCEPT; - const int is_ip6 = tor_addr_family(&policy->addr) == AF_INET6; + const sa_family_t family = tor_addr_family(&policy->addr); + const int is_ip6 = (family == AF_INET6); tor_addr_to_str(addrbuf, &policy->addr, sizeof(addrbuf), 1); /* write accept/reject 1.2.3.4 */ - if (policy->is_private) + if (policy->is_private) { addrpart = "private"; - else if (policy->maskbits == 0) - addrpart = "*"; - else + } else if (policy->maskbits == 0) { + if (format_for_desc) + addrpart = "*"; + else if (family == AF_INET6) + addrpart = "*6"; + else if (family == AF_INET) + addrpart = "*4"; + else + addrpart = "*"; + } 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); @@ -1189,8 +1281,8 @@ policy_summary_add_item(smartlist_t *summary, addr_policy_t *p) for (i = 0; private_nets[i]; ++i) { tor_addr_t addr; maskbits_t maskbits; - if (tor_addr_parse_mask_ports(private_nets[i], &addr, - &maskbits, NULL, NULL)<0) { + if (tor_addr_parse_mask_ports(private_nets[i], 0, &addr, + &maskbits, NULL, NULL)<0) { tor_assert(0); } if (tor_addr_compare(&p->addr, &addr, CMP_EXACT) == 0 && @@ -1216,7 +1308,7 @@ policy_summary_add_item(smartlist_t *summary, addr_policy_t *p) * is an exception to the shorter-representation-wins rule). */ char * -policy_summarize(smartlist_t *policy) +policy_summarize(smartlist_t *policy, sa_family_t family) { smartlist_t *summary = policy_summary_create(); smartlist_t *accepts, *rejects; @@ -1228,9 +1320,16 @@ policy_summarize(smartlist_t *policy) tor_assert(policy); /* Create the summary list */ - SMARTLIST_FOREACH(policy, addr_policy_t *, p, { + SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, p) { + sa_family_t f = tor_addr_family(&p->addr); + if (f != AF_INET && f != AF_INET6) { + log_warn(LD_BUG, "Weird family when summarizing address policy"); + } + if (f != family) + continue; + /* XXXX-ipv6 More family work is needed */ policy_summary_add_item(summary, p); - }); + } SMARTLIST_FOREACH_END(p); /* Now create two lists of strings, one for accepted and one * for rejected ports. We take care to merge ranges so that @@ -1517,7 +1616,7 @@ short_policy_is_reject_star(const short_policy_t *policy) policy->entries[0].max_port == 65535); } -/** Decides whether addr:port is probably or definitely accepted or rejcted by +/** Decide whether addr:port is probably or definitely accepted or rejected by * <b>node</b>. See compare_tor_addr_to_addr_policy for details on addr/port * interpretation. */ addr_policy_result_t @@ -1527,16 +1626,29 @@ compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, if (node->rejects_all) return ADDR_POLICY_REJECTED; - if (node->ri) + if (addr && tor_addr_family(addr) == AF_INET6) { + const short_policy_t *p = NULL; + if (node->ri) + p = node->ri->ipv6_exit_policy; + else if (node->md) + p = node->md->ipv6_exit_policy; + if (p) + return compare_tor_addr_to_short_policy(addr, port, p); + else + return ADDR_POLICY_REJECTED; + } + + if (node->ri) { return compare_tor_addr_to_addr_policy(addr, port, node->ri->exit_policy); - else if (node->md) { + } else if (node->md) { if (node->md->exit_policy == NULL) return ADDR_POLICY_REJECTED; else return compare_tor_addr_to_short_policy(addr, port, node->md->exit_policy); - } else + } else { return ADDR_POLICY_PROBABLY_REJECTED; + } } /** Implementation for GETINFO control command: knows the answer for questions diff --git a/src/or/policies.h b/src/or/policies.h index f00d8299b..d9983e800 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -9,8 +9,8 @@ * \brief Header file for policies.c. **/ -#ifndef _TOR_POLICIES_H -#define _TOR_POLICIES_H +#ifndef TOR_POLICIES_H +#define TOR_POLICIES_H /* (length of "accept 255.255.255.255/255.255.255.255:65535-65535\n" plus a * NUL.) @@ -31,6 +31,7 @@ int authdir_policy_badexit_address(uint32_t addr, uint16_t port); int validate_addr_policies(const or_options_t *options, char **msg); void policy_expand_private(smartlist_t **policy); +void policy_expand_unspec(smartlist_t **policy); int policies_parse_from_options(const or_options_t *options); addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent); @@ -42,12 +43,13 @@ addr_policy_result_t compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, const node_t *node); int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, + int ipv6exit, int rejectprivate, const char *local_address, int add_default_policy); void policies_exit_policy_append_reject_star(smartlist_t **dest); void policies_set_node_exitpolicy_to_reject_all(node_t *exitrouter); int exit_policy_is_general_exit(smartlist_t *policy); -int policy_is_reject_star(const smartlist_t *policy); +int policy_is_reject_star(const smartlist_t *policy, sa_family_t family); int getinfo_helper_policies(control_connection_t *conn, const char *question, char **answer, const char **errmsg); @@ -58,7 +60,7 @@ void addr_policy_list_free(smartlist_t *p); void addr_policy_free(addr_policy_t *p); void policies_free_all(void); -char *policy_summarize(smartlist_t *policy); +char *policy_summarize(smartlist_t *policy, sa_family_t family); short_policy_t *parse_short_policy(const char *summary); char *write_short_policy(const short_policy_t *policy); diff --git a/src/or/reasons.c b/src/or/reasons.c index c51d8ee6f..874a86774 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -300,8 +300,13 @@ errno_to_orconn_end_reason(int e) const char * circuit_end_reason_to_control_string(int reason) { - if (reason >= 0 && reason & END_CIRC_REASON_FLAG_REMOTE) + int is_remote = 0; + + if (reason >= 0 && reason & END_CIRC_REASON_FLAG_REMOTE) { reason &= ~END_CIRC_REASON_FLAG_REMOTE; + is_remote = 1; + } + switch (reason) { case END_CIRC_AT_ORIGIN: /* This shouldn't get passed here; it's a catch-all reason. */ @@ -323,8 +328,8 @@ circuit_end_reason_to_control_string(int reason) return "CONNECTFAILED"; case END_CIRC_REASON_OR_IDENTITY: return "OR_IDENTITY"; - case END_CIRC_REASON_OR_CONN_CLOSED: - return "OR_CONN_CLOSED"; + case END_CIRC_REASON_CHANNEL_CLOSED: + return "CHANNEL_CLOSED"; case END_CIRC_REASON_FINISHED: return "FINISHED"; case END_CIRC_REASON_TIMEOUT: @@ -338,7 +343,18 @@ circuit_end_reason_to_control_string(int reason) case END_CIRC_REASON_MEASUREMENT_EXPIRED: return "MEASUREMENT_EXPIRED"; default: - log_warn(LD_BUG, "Unrecognized reason code %d", (int)reason); + if (is_remote) { + /* + * If it's remote, it's not a bug *here*, so don't use LD_BUG, but + * do note that the someone we're talking to is speaking the Tor + * protocol with a weird accent. + */ + log_warn(LD_PROTOCOL, + "Remote server sent bogus reason code %d", reason); + } else { + log_warn(LD_BUG, + "Unrecognized reason code %d", reason); + } return NULL; } } diff --git a/src/or/reasons.h b/src/or/reasons.h index 377b61b11..0dbf5aa69 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -9,8 +9,8 @@ * \brief Header file for reasons.c. **/ -#ifndef _TOR_REASONS_H -#define _TOR_REASONS_H +#ifndef TOR_REASONS_H +#define TOR_REASONS_H const char *stream_end_reason_to_control_string(int reason); const char *stream_end_reason_to_string(int reason); diff --git a/src/or/relay.c b/src/or/relay.c index 5f7fcd8b7..874aade21 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -10,10 +10,11 @@ * receiving from circuits, plus queuing on circuits. **/ -#include <math.h> #define RELAY_PRIVATE #include "or.h" +#include "addressmap.h" #include "buffers.h" +#include "channel.h" #include "circuitbuild.h" #include "circuitlist.h" #include "config.h" @@ -26,6 +27,7 @@ #include "mempool.h" #include "networkstatus.h" #include "nodelist.h" +#include "onion.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -166,7 +168,7 @@ int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, cell_direction_t cell_direction) { - or_connection_t *or_conn=NULL; + channel_t *chan = NULL; crypt_path_t *layer_hint=NULL; char recognized=0; int reason; @@ -184,7 +186,17 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, } if (recognized) { - edge_connection_t *conn = relay_lookup_conn(circ, cell, cell_direction, + edge_connection_t *conn = NULL; + + if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING) { + pathbias_check_probe_response(circ, cell); + + /* We need to drop this cell no matter what to avoid code that expects + * a certain purpose (such as the hidserv code). */ + return 0; + } + + conn = relay_lookup_conn(circ, cell, cell_direction, layer_hint); if (cell_direction == CELL_DIRECTION_OUT) { ++stats_n_relay_cells_delivered; @@ -213,24 +225,32 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, /* not recognized. pass it on. */ if (cell_direction == CELL_DIRECTION_OUT) { cell->circ_id = circ->n_circ_id; /* switch it */ - or_conn = circ->n_conn; + chan = circ->n_chan; } else if (! CIRCUIT_IS_ORIGIN(circ)) { cell->circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; /* switch it */ - or_conn = TO_OR_CIRCUIT(circ)->p_conn; + chan = TO_OR_CIRCUIT(circ)->p_chan; } else { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Dropping unrecognized inbound cell on origin circuit."); - return 0; + /* If we see unrecognized cells on path bias testing circs, + * it's bad mojo. Those circuits need to die. + * XXX: Shouldn't they always die? */ + if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING) { + TO_ORIGIN_CIRCUIT(circ)->path_state = PATH_STATE_USE_FAILED; + return -END_CIRC_REASON_TORPROTOCOL; + } else { + return 0; + } } - if (!or_conn) { + if (!chan) { // XXXX Can this splice stuff be done more cleanly? if (! CIRCUIT_IS_ORIGIN(circ) && TO_OR_CIRCUIT(circ)->rend_splice && cell_direction == CELL_DIRECTION_OUT) { or_circuit_t *splice = TO_OR_CIRCUIT(circ)->rend_splice; tor_assert(circ->purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED); - tor_assert(splice->_base.purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED); + tor_assert(splice->base_.purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED); cell->circ_id = splice->p_circ_id; cell->command = CELL_RELAY; /* can't be relay_early anyway */ if ((reason = circuit_receive_relay_cell(cell, TO_CIRCUIT(splice), @@ -254,7 +274,7 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, * we might kill the circ before we relay * the cells. */ - append_cell_to_circuit_queue(circ, or_conn, cell, cell_direction, 0); + append_cell_to_circuit_queue(circ, chan, cell, cell_direction, 0); return 0; } @@ -353,13 +373,13 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, cell_direction_t cell_direction, crypt_path_t *layer_hint, streamid_t on_stream) { - or_connection_t *conn; /* where to send the cell */ + channel_t *chan; /* where to send the cell */ if (cell_direction == CELL_DIRECTION_OUT) { crypt_path_t *thishop; /* counter for repeated crypts */ - conn = circ->n_conn; - if (!CIRCUIT_IS_ORIGIN(circ) || !conn) { - log_warn(LD_BUG,"outgoing relay cell has n_conn==NULL. Dropping."); + chan = circ->n_chan; + if (!CIRCUIT_IS_ORIGIN(circ) || !chan) { + log_warn(LD_BUG,"outgoing relay cell has n_chan==NULL. Dropping."); return 0; /* just drop it */ } @@ -388,14 +408,14 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, return 0; /* just drop it */ } or_circ = TO_OR_CIRCUIT(circ); - conn = or_circ->p_conn; + chan = or_circ->p_chan; relay_set_digest(or_circ->p_digest, cell); if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0) return -1; } ++stats_n_relay_cells_relayed; - append_cell_to_circuit_queue(circ, conn, cell, cell_direction, on_stream); + append_cell_to_circuit_queue(circ, chan, cell, cell_direction, on_stream); return 0; } @@ -422,7 +442,7 @@ relay_lookup_conn(circuit_t *circ, cell_t *cell, for (tmpconn = TO_ORIGIN_CIRCUIT(circ)->p_streams; tmpconn; tmpconn=tmpconn->next_stream) { if (rh.stream_id == tmpconn->stream_id && - !tmpconn->_base.marked_for_close && + !tmpconn->base_.marked_for_close && tmpconn->cpath_layer == layer_hint) { log_debug(LD_APP,"found conn for stream %d.", rh.stream_id); return tmpconn; @@ -432,7 +452,7 @@ relay_lookup_conn(circuit_t *circ, cell_t *cell, for (tmpconn = TO_OR_CIRCUIT(circ)->n_streams; tmpconn; tmpconn=tmpconn->next_stream) { if (rh.stream_id == tmpconn->stream_id && - !tmpconn->_base.marked_for_close) { + !tmpconn->base_.marked_for_close) { log_debug(LD_EXIT,"found conn for stream %d.", rh.stream_id); if (cell_direction == CELL_DIRECTION_OUT || connection_edge_is_rendezvous_stream(tmpconn)) @@ -442,7 +462,7 @@ relay_lookup_conn(circuit_t *circ, cell_t *cell, for (tmpconn = TO_OR_CIRCUIT(circ)->resolving_streams; tmpconn; tmpconn=tmpconn->next_stream) { if (rh.stream_id == tmpconn->stream_id && - !tmpconn->_base.marked_for_close) { + !tmpconn->base_.marked_for_close) { log_debug(LD_EXIT,"found conn for stream %d.", rh.stream_id); return tmpconn; } @@ -561,15 +581,16 @@ relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, geoip_change_dirreq_state(circ->dirreq_id, DIRREQ_TUNNELED, DIRREQ_END_CELL_SENT); - if (cell_direction == CELL_DIRECTION_OUT && circ->n_conn) { + if (cell_direction == CELL_DIRECTION_OUT && circ->n_chan) { /* if we're using relaybandwidthrate, this conn wants priority */ - circ->n_conn->client_used = approx_time(); + channel_timestamp_client(circ->n_chan); } if (cell_direction == CELL_DIRECTION_OUT) { origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); if (origin_circ->remaining_relay_early_cells > 0 && (relay_command == RELAY_COMMAND_EXTEND || + relay_command == RELAY_COMMAND_EXTEND2 || cpath_layer != origin_circ->cpath)) { /* If we've got any relay_early cells left and (we're sending * an extend cell or we're not talking to the first hop), use @@ -583,7 +604,8 @@ relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, * task 878. */ origin_circ->relay_early_commands[ origin_circ->relay_early_cells_sent++] = relay_command; - } else if (relay_command == RELAY_COMMAND_EXTEND) { + } else if (relay_command == RELAY_COMMAND_EXTEND || + relay_command == RELAY_COMMAND_EXTEND2) { /* If no RELAY_EARLY cells can be sent over this circuit, log which * commands have been sent as RELAY_EARLY cells before; helps debug * task 878. */ @@ -631,16 +653,16 @@ connection_edge_send_command(edge_connection_t *fromconn, tor_assert(fromconn); circ = fromconn->on_circuit; - if (fromconn->_base.marked_for_close) { + if (fromconn->base_.marked_for_close) { log_warn(LD_BUG, "called on conn that's already marked for close at %s:%d.", - fromconn->_base.marked_for_close_file, - fromconn->_base.marked_for_close); + fromconn->base_.marked_for_close_file, + fromconn->base_.marked_for_close); return 0; } if (!circ) { - if (fromconn->_base.type == CONN_TYPE_AP) { + if (fromconn->base_.type == CONN_TYPE_AP) { log_info(LD_APP,"no circ. Closing conn."); connection_mark_unattached_ap(EDGE_TO_ENTRY_CONN(fromconn), END_STREAM_REASON_INTERNAL); @@ -692,6 +714,26 @@ connection_ap_process_end_not_open( edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); (void) layer_hint; /* unused */ + if (rh->length > 0) { + if (reason == END_STREAM_REASON_TORPROTOCOL || + reason == END_STREAM_REASON_INTERNAL || + reason == END_STREAM_REASON_DESTROY) { + /* All three of these reasons could mean a failed tag + * hit the exit and it complained. Do not probe. + * Fail the circuit. */ + circ->path_state = PATH_STATE_USE_FAILED; + return -END_CIRC_REASON_TORPROTOCOL; + } else { + /* Path bias: If we get a valid reason code from the exit, + * it wasn't due to tagging. + * + * We rely on recognized+digest being strong enough to make + * tags unlikely to allow us to get tagged, yet 'recognized' + * reason codes here. */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + } + } + if (rh->length > 0 && edge_reason_is_retriable(reason) && /* avoid retry if rend */ !connection_edge_is_rendezvous_stream(edge_conn)) { @@ -704,28 +746,57 @@ connection_ap_process_end_not_open( switch (reason) { case END_STREAM_REASON_EXITPOLICY: if (rh->length >= 5) { - uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); - int ttl; - if (!addr) { + tor_addr_t addr; + + int ttl = -1; + tor_addr_make_unspec(&addr); + if (rh->length == 5 || rh->length == 9) { + tor_addr_from_ipv4n(&addr, + get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); + if (rh->length == 9) + ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+5)); + } else if (rh->length == 17 || rh->length == 21) { + tor_addr_from_ipv6_bytes(&addr, + (char*)(cell->payload+RELAY_HEADER_SIZE+1)); + if (rh->length == 21) + ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+17)); + } + if (tor_addr_is_null(&addr)) { log_info(LD_APP,"Address '%s' resolved to 0.0.0.0. Closing,", safe_str(conn->socks_request->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return 0; } - if (rh->length >= 9) - ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+5)); - else - ttl = -1; + if ((tor_addr_family(&addr) == AF_INET && !conn->ipv4_traffic_ok) || + (tor_addr_family(&addr) == AF_INET6 && !conn->ipv6_traffic_ok)) { + log_fn(LOG_PROTOCOL_WARN, LD_APP, + "Got an EXITPOLICY failure on a connection with a " + "mismatched family. Closing."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return 0; + } if (get_options()->ClientDNSRejectInternalAddresses && - is_internal_IP(addr, 0)) { + tor_addr_is_internal(&addr, 0)) { log_info(LD_APP,"Address '%s' resolved to internal. Closing,", safe_str(conn->socks_request->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return 0; } - client_dns_set_addressmap(conn->socks_request->address, addr, + + client_dns_set_addressmap(conn, + conn->socks_request->address, &addr, conn->chosen_exit_name, ttl); + + { + char new_addr[TOR_ADDR_BUF_LEN]; + tor_addr_to_str(new_addr, &addr, sizeof(new_addr), 1); + if (strcmp(conn->socks_request->address, new_addr)) { + strlcpy(conn->socks_request->address, new_addr, + sizeof(conn->socks_request->address)); + control_event_stream_status(conn, STREAM_EVENT_REMAP, 0); + } + } } /* check if he *ought* to have allowed it */ if (exitrouter && @@ -738,12 +809,7 @@ connection_ap_process_end_not_open( node_describe(exitrouter)); policies_set_node_exitpolicy_to_reject_all(exitrouter); } - /* rewrite it to an IP if we learned one. */ - if (addressmap_rewrite(conn->socks_request->address, - sizeof(conn->socks_request->address), - NULL, NULL)) { - control_event_stream_status(conn, STREAM_EVENT_REMAP, 0); - } + if (conn->chosen_exit_optional || conn->chosen_exit_retries) { /* stop wanting a specific exit */ @@ -777,8 +843,8 @@ connection_ap_process_end_not_open( circuit_log_path(LOG_INFO,LD_APP,circ); /* Mark this circuit "unusable for new streams". */ /* XXXX024 this is a kludgy way to do this. */ - tor_assert(circ->_base.timestamp_dirty); - circ->_base.timestamp_dirty -= get_options()->MaxCircuitDirtiness; + tor_assert(circ->base_.timestamp_dirty); + circ->base_.timestamp_dirty -= get_options()->MaxCircuitDirtiness; if (conn->chosen_exit_optional) { /* stop wanting a specific exit */ @@ -827,20 +893,60 @@ connection_ap_process_end_not_open( } /** Helper: change the socks_request->address field on conn to the - * dotted-quad representation of <b>new_addr</b> (given in host order), + * dotted-quad representation of <b>new_addr</b>, * and send an appropriate REMAP event. */ static void -remap_event_helper(entry_connection_t *conn, uint32_t new_addr) +remap_event_helper(entry_connection_t *conn, const tor_addr_t *new_addr) { - struct in_addr in; - - in.s_addr = htonl(new_addr); - tor_inet_ntoa(&in, conn->socks_request->address, - sizeof(conn->socks_request->address)); + tor_addr_to_str(conn->socks_request->address, new_addr, + sizeof(conn->socks_request->address), + 1); control_event_stream_status(conn, STREAM_EVENT_REMAP, REMAP_STREAM_SOURCE_EXIT); } +/** Extract the contents of a connected cell in <b>cell</b>, whose relay + * header has already been parsed into <b>rh</b>. On success, set + * <b>addr_out</b> to the address we're connected to, and <b>ttl_out</b> to + * the ttl of that address, in seconds, and return 0. On failure, return + * -1. */ +int +connected_cell_parse(const relay_header_t *rh, const cell_t *cell, + tor_addr_t *addr_out, int *ttl_out) +{ + uint32_t bytes; + const uint8_t *payload = cell->payload + RELAY_HEADER_SIZE; + + tor_addr_make_unspec(addr_out); + *ttl_out = -1; + if (rh->length == 0) + return 0; + if (rh->length < 4) + return -1; + bytes = ntohl(get_uint32(payload)); + + /* If bytes is 0, this is maybe a v6 address. Otherwise it's a v4 address */ + if (bytes != 0) { + /* v4 address */ + tor_addr_from_ipv4h(addr_out, bytes); + if (rh->length >= 8) { + bytes = ntohl(get_uint32(payload + 4)); + if (bytes <= INT32_MAX) + *ttl_out = bytes; + } + } else { + if (rh->length < 25) /* 4 bytes of 0s, 1 addr, 16 ipv4, 4 ttl. */ + return -1; + if (get_uint8(payload + 4) != 6) + return -1; + tor_addr_from_ipv6_bytes(addr_out, (char*)(payload + 5)); + bytes = ntohl(get_uint32(payload + 21)); + if (bytes <= INT32_MAX) + *ttl_out = (int) bytes; + } + return 0; +} + /** An incoming relay cell has arrived from circuit <b>circ</b> to * stream <b>conn</b>. * @@ -854,7 +960,7 @@ connection_edge_process_relay_cell_not_open( edge_connection_t *conn, crypt_path_t *layer_hint) { if (rh->command == RELAY_COMMAND_END) { - if (CIRCUIT_IS_ORIGIN(circ) && conn->_base.type == CONN_TYPE_AP) { + if (CIRCUIT_IS_ORIGIN(circ) && conn->base_.type == CONN_TYPE_AP) { return connection_ap_process_end_not_open(rh, cell, TO_ORIGIN_CIRCUIT(circ), EDGE_TO_ENTRY_CONN(conn), @@ -869,38 +975,55 @@ connection_edge_process_relay_cell_not_open( } } - if (conn->_base.type == CONN_TYPE_AP && + if (conn->base_.type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_CONNECTED) { + tor_addr_t addr; + int ttl; entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); tor_assert(CIRCUIT_IS_ORIGIN(circ)); - if (conn->_base.state != AP_CONN_STATE_CONNECT_WAIT) { + if (conn->base_.state != AP_CONN_STATE_CONNECT_WAIT) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got 'connected' while not in state connect_wait. Dropping."); return 0; } - conn->_base.state = AP_CONN_STATE_OPEN; + conn->base_.state = AP_CONN_STATE_OPEN; log_info(LD_APP,"'connected' received after %d seconds.", - (int)(time(NULL) - conn->_base.timestamp_lastread)); - if (rh->length >= 4) { - uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE)); - int ttl; - if (!addr || (get_options()->ClientDNSRejectInternalAddresses && - is_internal_IP(addr, 0))) { + (int)(time(NULL) - conn->base_.timestamp_lastread)); + if (connected_cell_parse(rh, cell, &addr, &ttl) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_APP, + "Got a badly formatted connected cell. Closing."); + connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); + } + if (tor_addr_family(&addr) != AF_UNSPEC) { + const sa_family_t family = tor_addr_family(&addr); + if (tor_addr_is_null(&addr) || + (get_options()->ClientDNSRejectInternalAddresses && + tor_addr_is_internal(&addr, 0))) { log_info(LD_APP, "...but it claims the IP address was %s. Closing.", - fmt_addr32(addr)); + fmt_addr(&addr)); connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); return 0; } - if (rh->length >= 8) - ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+4)); - else - ttl = -1; - client_dns_set_addressmap(entry_conn->socks_request->address, addr, + + if ((family == AF_INET && ! entry_conn->ipv4_traffic_ok) || + (family == AF_INET6 && ! entry_conn->ipv6_traffic_ok)) { + log_fn(LOG_PROTOCOL_WARN, LD_APP, + "Got a connected cell to %s with unsupported address family." + " Closing.", fmt_addr(&addr)); + connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_TORPROTOCOL); + return 0; + } + + client_dns_set_addressmap(entry_conn, + entry_conn->socks_request->address, &addr, entry_conn->chosen_exit_name, ttl); - remap_event_helper(entry_conn, addr); + remap_event_helper(entry_conn, &addr); } circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ @@ -944,13 +1067,13 @@ connection_edge_process_relay_cell_not_open( } return 0; } - if (conn->_base.type == CONN_TYPE_AP && + if (conn->base_.type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_RESOLVED) { int ttl; int answer_len; uint8_t answer_type; entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); - if (conn->_base.state != AP_CONN_STATE_RESOLVE_WAIT) { + if (conn->base_.state != AP_CONN_STATE_RESOLVE_WAIT) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a 'resolved' cell while " "not in state resolve_wait. Dropping."); return 0; @@ -990,8 +1113,15 @@ connection_edge_process_relay_cell_not_open( ttl, -1); if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) { - uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); - remap_event_helper(entry_conn, addr); + tor_addr_t addr; + tor_addr_from_ipv4n(&addr, + get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); + remap_event_helper(entry_conn, &addr); + } else if (answer_type == RESOLVED_TYPE_IPV6 && answer_len == 16) { + tor_addr_t addr; + tor_addr_from_ipv6_bytes(&addr, + (char*)(cell->payload+RELAY_HEADER_SIZE+2)); + remap_event_helper(entry_conn, &addr); } connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_DONE | @@ -1001,8 +1131,8 @@ connection_edge_process_relay_cell_not_open( log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Got an unexpected relay command %d, in state %d (%s). Dropping.", - rh->command, conn->_base.state, - conn_state_to_string(conn->_base.type, conn->_base.state)); + rh->command, conn->base_.state, + conn_state_to_string(conn->base_.type, conn->base_.state)); return 0; /* for forward compatibility, don't kill the circuit */ // connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); // connection_mark_for_close(conn); @@ -1050,9 +1180,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, * conn points to the recognized stream. */ if (conn && !connection_state_is_open(TO_CONN(conn))) { - if (conn->_base.type == CONN_TYPE_EXIT && - (conn->_base.state == EXIT_CONN_STATE_CONNECTING || - conn->_base.state == EXIT_CONN_STATE_RESOLVING) && + if (conn->base_.type == CONN_TYPE_EXIT && + (conn->base_.state == EXIT_CONN_STATE_CONNECTING || + conn->base_.state == EXIT_CONN_STATE_RESOLVING) && rh.command == RELAY_COMMAND_DATA) { /* Allow DATA cells to be delivered to an exit node in state * EXIT_CONN_STATE_CONNECTING or EXIT_CONN_STATE_RESOLVING. @@ -1095,7 +1225,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, * and linked. */ static uint64_t next_id = 0; circ->dirreq_id = ++next_id; - TO_CONN(TO_OR_CIRCUIT(circ)->p_conn)->dirreq_id = circ->dirreq_id; + TO_OR_CIRCUIT(circ)->p_chan->dirreq_id = circ->dirreq_id; } return connection_exit_begin_conn(cell, circ); @@ -1151,11 +1281,12 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, return 0; } /* XXX add to this log_fn the exit node's nickname? */ - log_info(domain,"%d: end cell (%s) for stream %d. Removing stream.", - conn->_base.s, + log_info(domain,TOR_SOCKET_T_FORMAT": end cell (%s) for stream %d. " + "Removing stream.", + conn->base_.s, stream_end_reason_to_string(reason), conn->stream_id); - if (conn->_base.type == CONN_TYPE_AP) { + if (conn->base_.type == CONN_TYPE_AP) { entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); if (entry_conn->socks_request && !entry_conn->socks_request->has_finished) @@ -1166,16 +1297,17 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, conn->edge_has_sent_end = 1; if (!conn->end_reason) conn->end_reason = reason | END_STREAM_REASON_FLAG_REMOTE; - if (!conn->_base.marked_for_close) { + if (!conn->base_.marked_for_close) { /* only mark it if not already marked. it's possible to * get the 'end' right around when the client hangs up on us. */ connection_mark_and_flush(TO_CONN(conn)); } return 0; - case RELAY_COMMAND_EXTEND: { + case RELAY_COMMAND_EXTEND: + case RELAY_COMMAND_EXTEND2: { 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; @@ -1207,17 +1339,27 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, return circuit_extend(cell, circ); } case RELAY_COMMAND_EXTENDED: + case RELAY_COMMAND_EXTENDED2: if (!layer_hint) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "'extended' unsupported at non-origin. Dropping."); return 0; } log_debug(domain,"Got an extended cell! Yay."); - if ((reason = circuit_finish_handshake(TO_ORIGIN_CIRCUIT(circ), - CELL_CREATED, - cell->payload+RELAY_HEADER_SIZE)) < 0) { - log_warn(domain,"circuit_finish_handshake failed."); - return reason; + { + extended_cell_t extended_cell; + if (extended_cell_parse(&extended_cell, rh.command, + (const uint8_t*)cell->payload+RELAY_HEADER_SIZE, + rh.length)<0) { + log_warn(LD_PROTOCOL, + "Can't parse EXTENDED cell; killing circuit."); + return -END_CIRC_REASON_TORPROTOCOL; + } + if ((reason = circuit_finish_handshake(TO_ORIGIN_CIRCUIT(circ), + &extended_cell.created_cell)) < 0) { + log_warn(domain,"circuit_finish_handshake failed."); + return reason; + } } if ((reason=circuit_send_next_onion_skin(TO_ORIGIN_CIRCUIT(circ)))<0) { log_info(domain,"circuit_send_next_onion_skin() failed."); @@ -1230,12 +1372,12 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, "'truncate' unsupported at origin. Dropping."); return 0; } - if (circ->n_conn) { - uint8_t trunc_reason = *(uint8_t*)(cell->payload + RELAY_HEADER_SIZE); - circuit_clear_cell_queue(circ, circ->n_conn); - connection_or_send_destroy(circ->n_circ_id, circ->n_conn, - trunc_reason); - circuit_set_n_circid_orconn(circ, 0, NULL); + if (circ->n_chan) { + uint8_t trunc_reason = get_uint8(cell->payload + RELAY_HEADER_SIZE); + circuit_clear_cell_queue(circ, circ->n_chan); + channel_send_destroy(circ->n_circ_id, circ->n_chan, + trunc_reason); + circuit_set_n_circid_chan(circ, 0, NULL); } log_debug(LD_EXIT, "Processed 'truncate', replying."); { @@ -1251,7 +1393,8 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, "'truncated' unsupported at non-origin. Dropping."); return 0; } - circuit_truncated(TO_ORIGIN_CIRCUIT(circ), layer_hint); + circuit_truncated(TO_ORIGIN_CIRCUIT(circ), layer_hint, + get_uint8(cell->payload + RELAY_HEADER_SIZE)); return 0; case RELAY_COMMAND_CONNECTED: if (conn) { @@ -1267,7 +1410,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 +1423,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; @@ -1389,21 +1534,21 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, size_t bytes_to_process, length; char payload[CELL_PAYLOAD_SIZE]; circuit_t *circ; - const unsigned domain = conn->_base.type == CONN_TYPE_AP ? LD_APP : LD_EXIT; + const unsigned domain = conn->base_.type == CONN_TYPE_AP ? LD_APP : LD_EXIT; int sending_from_optimistic = 0; const int sending_optimistically = - conn->_base.type == CONN_TYPE_AP && - conn->_base.state != AP_CONN_STATE_OPEN; + conn->base_.type == CONN_TYPE_AP && + conn->base_.state != AP_CONN_STATE_OPEN; entry_connection_t *entry_conn = - conn->_base.type == CONN_TYPE_AP ? EDGE_TO_ENTRY_CONN(conn) : NULL; + conn->base_.type == CONN_TYPE_AP ? EDGE_TO_ENTRY_CONN(conn) : NULL; crypt_path_t *cpath_layer = conn->cpath_layer; tor_assert(conn); - if (conn->_base.marked_for_close) { + if (conn->base_.marked_for_close) { log_warn(LD_BUG, "called on conn that's already marked for close at %s:%d.", - conn->_base.marked_for_close_file, conn->_base.marked_for_close); + conn->base_.marked_for_close_file, conn->base_.marked_for_close); return 0; } @@ -1470,9 +1615,18 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, connection_fetch_from_buf(payload, length, TO_CONN(conn)); } - log_debug(domain,"(%d) Packaging %d bytes (%d waiting).", conn->_base.s, + log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).", + conn->base_.s, (int)length, (int)connection_get_inbuf_len(TO_CONN(conn))); + if (conn->base_.type == CONN_TYPE_AP) { + char *text = tor_memdup(payload, length+1); + text[length] = 0; + log_notice(LD_APP, "Incoming socks text (%d):===\n%s\n===", + conn->base_.s, text); + tor_free(text); + } + if (sending_optimistically && !sending_from_optimistic) { /* This is new optimistic data; remember it in case we need to detach and retry */ @@ -1536,9 +1690,9 @@ connection_edge_consider_sending_sendme(edge_connection_t *conn) } while (conn->deliver_window <= STREAMWINDOW_START - STREAMWINDOW_INCREMENT) { - log_debug(conn->_base.type == CONN_TYPE_AP ?LD_APP:LD_EXIT, + log_debug(conn->base_.type == CONN_TYPE_AP ?LD_APP:LD_EXIT, "Outbuf %d, Queuing stream sendme.", - (int)conn->_base.outbuf_flushlen); + (int)conn->base_.outbuf_flushlen); conn->deliver_window += STREAMWINDOW_INCREMENT; if (connection_edge_send_command(conn, RELAY_COMMAND_SENDME, NULL, 0) < 0) { @@ -1591,10 +1745,10 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, * needed to fill the cell queue. */ int max_to_package = circ->package_window; if (CIRCUIT_IS_ORIGIN(circ)) { - cells_on_queue = circ->n_conn_cells.n; + cells_on_queue = circ->n_chan_cells.n; } else { or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - cells_on_queue = or_circ->p_conn_cells.n; + cells_on_queue = or_circ->p_chan_cells.n; } if (CELL_QUEUE_HIGHWATER_SIZE - cells_on_queue < max_to_package) max_to_package = CELL_QUEUE_HIGHWATER_SIZE - cells_on_queue; @@ -1627,7 +1781,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, /* Activate reading starting from the chosen stream */ for (conn=chosen_stream; conn; conn = conn->next_stream) { /* Start reading for the streams starting from here */ - if (conn->_base.marked_for_close || conn->package_window <= 0) + if (conn->base_.marked_for_close || conn->package_window <= 0) continue; if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); @@ -1638,7 +1792,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, } /* Go back and do the ones we skipped, circular-style */ for (conn = first_conn; conn != chosen_stream; conn = conn->next_stream) { - if (conn->_base.marked_for_close || conn->package_window <= 0) + if (conn->base_.marked_for_close || conn->package_window <= 0) continue; if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); @@ -1664,7 +1818,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, * package. */ for (conn=first_conn; conn; conn=conn->next_stream) { - if (conn->_base.marked_for_close || conn->package_window <= 0) + if (conn->base_.marked_for_close || conn->package_window <= 0) continue; if (!layer_hint || conn->cpath_layer == layer_hint) { int n = cells_per_conn, r; @@ -1775,10 +1929,10 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) } #ifdef ACTIVE_CIRCUITS_PARANOIA -#define assert_active_circuits_ok_paranoid(conn) \ - assert_active_circuits_ok(conn) +#define assert_cmux_ok_paranoid(chan) \ + assert_circuit_mux_okay(chan) #else -#define assert_active_circuits_ok_paranoid(conn) +#define assert_cmux_ok_paranoid(chan) #endif /** The total number of cells we have allocated from the memory pool. */ @@ -1833,12 +1987,19 @@ packed_cell_free_unchecked(packed_cell_t *cell) /** Allocate and return a new packed_cell_t. */ static INLINE packed_cell_t * -packed_cell_alloc(void) +packed_cell_new(void) { ++total_cells_allocated; return mp_pool_get(cell_pool); } +/** Return a packed cell used outside by channel_t lower layer */ +void +packed_cell_free(packed_cell_t *cell) +{ + packed_cell_free_unchecked(cell); +} + /** Log current statistics for cell pool allocation at log level * <b>severity</b>. */ void @@ -1847,10 +2008,10 @@ dump_cell_pool_usage(int severity) circuit_t *c; int n_circs = 0; int n_cells = 0; - for (c = _circuit_get_global_list(); c; c = c->next) { - n_cells += c->n_conn_cells.n; + for (c = circuit_get_global_list_(); c; c = c->next) { + n_cells += c->n_chan_cells.n; if (!CIRCUIT_IS_ORIGIN(c)) - n_cells += TO_OR_CIRCUIT(c)->p_conn_cells.n; + n_cells += TO_OR_CIRCUIT(c)->p_chan_cells.n; ++n_circs; } log(severity, LD_MM, "%d cells allocated on %d circuits. %d cells leaked.", @@ -1862,7 +2023,7 @@ dump_cell_pool_usage(int severity) static INLINE packed_cell_t * packed_cell_copy(const cell_t *cell) { - packed_cell_t *c = packed_cell_alloc(); + packed_cell_t *c = packed_cell_new(); cell_pack(c, cell); c->next = NULL; return c; @@ -1961,363 +2122,68 @@ cell_queue_pop(cell_queue_t *queue) return cell; } -/** Return a pointer to the "next_active_on_{n,p}_conn" pointer of <b>circ</b>, - * depending on whether <b>conn</b> matches n_conn or p_conn. */ -static INLINE circuit_t ** -next_circ_on_conn_p(circuit_t *circ, or_connection_t *conn) -{ - tor_assert(circ); - tor_assert(conn); - if (conn == circ->n_conn) { - return &circ->next_active_on_n_conn; - } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - tor_assert(conn == orcirc->p_conn); - return &orcirc->next_active_on_p_conn; - } -} - -/** Return a pointer to the "prev_active_on_{n,p}_conn" pointer of <b>circ</b>, - * depending on whether <b>conn</b> matches n_conn or p_conn. */ -static INLINE circuit_t ** -prev_circ_on_conn_p(circuit_t *circ, or_connection_t *conn) -{ - tor_assert(circ); - tor_assert(conn); - if (conn == circ->n_conn) { - return &circ->prev_active_on_n_conn; - } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - tor_assert(conn == orcirc->p_conn); - return &orcirc->prev_active_on_p_conn; - } -} - -/** Helper for sorting cell_ewma_t values in their priority queue. */ -static int -compare_cell_ewma_counts(const void *p1, const void *p2) -{ - const cell_ewma_t *e1=p1, *e2=p2; - if (e1->cell_count < e2->cell_count) - return -1; - else if (e1->cell_count > e2->cell_count) - return 1; - else - return 0; -} - -/** Given a cell_ewma_t, return a pointer to the circuit containing it. */ -static circuit_t * -cell_ewma_to_circuit(cell_ewma_t *ewma) -{ - if (ewma->is_for_p_conn) { - /* This is an or_circuit_t's p_cell_ewma. */ - or_circuit_t *orcirc = SUBTYPE_P(ewma, or_circuit_t, p_cell_ewma); - return TO_CIRCUIT(orcirc); - } else { - /* This is some circuit's n_cell_ewma. */ - return SUBTYPE_P(ewma, circuit_t, n_cell_ewma); - } -} - -/* ==== Functions for scaling cell_ewma_t ==== - - When choosing which cells to relay first, we favor circuits that have been - quiet recently. This gives better latency on connections that aren't - pushing lots of data, and makes the network feel more interactive. - - Conceptually, we take an exponentially weighted mean average of the number - of cells a circuit has sent, and allow active circuits (those with cells to - relay) to send cells in reverse order of their exponentially-weighted mean - average (EWMA) cell count. [That is, a cell sent N seconds ago 'counts' - F^N times as much as a cell sent now, for 0<F<1.0, and we favor the - circuit that has sent the fewest cells] - - If 'double' had infinite precision, we could do this simply by counting a - cell sent at startup as having weight 1.0, and a cell sent N seconds later - as having weight F^-N. This way, we would never need to re-scale - any already-sent cells. - - To prevent double from overflowing, we could count a cell sent now as - having weight 1.0 and a cell sent N seconds ago as having weight F^N. - This, however, would mean we'd need to re-scale *ALL* old circuits every - time we wanted to send a cell. - - So as a compromise, we divide time into 'ticks' (currently, 10-second - increments) and say that a cell sent at the start of a current tick is - worth 1.0, a cell sent N seconds before the start of the current tick is - worth F^N, and a cell sent N seconds after the start of the current tick is - worth F^-N. This way we don't overflow, and we don't need to constantly - rescale. - */ - -/** How long does a tick last (seconds)? */ -#define EWMA_TICK_LEN 10 - -/** The default per-tick scale factor, if it hasn't been overridden by a - * consensus or a configuration setting. zero means "disabled". */ -#define EWMA_DEFAULT_HALFLIFE 0.0 - -/** Given a timeval <b>now</b>, compute the cell_ewma tick in which it occurs - * and the fraction of the tick that has elapsed between the start of the tick - * and <b>now</b>. Return the former and store the latter in - * *<b>remainder_out</b>. - * - * These tick values are not meant to be shared between Tor instances, or used - * for other purposes. */ -static unsigned -cell_ewma_tick_from_timeval(const struct timeval *now, - double *remainder_out) -{ - unsigned res = (unsigned) (now->tv_sec / EWMA_TICK_LEN); - /* rem */ - double rem = (now->tv_sec % EWMA_TICK_LEN) + - ((double)(now->tv_usec)) / 1.0e6; - *remainder_out = rem / EWMA_TICK_LEN; - return res; -} - -/** Compute and return the current cell_ewma tick. */ -unsigned -cell_ewma_get_tick(void) -{ - return ((unsigned)approx_time() / EWMA_TICK_LEN); -} - -/** The per-tick scale factor to be used when computing cell-count EWMA - * values. (A cell sent N ticks before the start of the current tick - * has value ewma_scale_factor ** N.) +/** + * Update the number of cells available on the circuit's n_chan or p_chan's + * circuit mux. */ -static double ewma_scale_factor = 0.1; -/* DOCDOC ewma_enabled */ -static int ewma_enabled = 0; - -/*DOCDOC*/ -#define EPSILON 0.00001 -/*DOCDOC*/ -#define LOG_ONEHALF -0.69314718055994529 - -/** Adjust the global cell scale factor based on <b>options</b> */ void -cell_ewma_set_scale_factor(const or_options_t *options, - const networkstatus_t *consensus) -{ - int32_t halflife_ms; - double halflife; - const char *source; - if (options && options->CircuitPriorityHalflife >= -EPSILON) { - halflife = options->CircuitPriorityHalflife; - source = "CircuitPriorityHalflife in configuration"; - } else if (consensus && (halflife_ms = networkstatus_get_param( - consensus, "CircuitPriorityHalflifeMsec", - -1, -1, INT32_MAX)) >= 0) { - halflife = ((double)halflife_ms)/1000.0; - source = "CircuitPriorityHalflifeMsec in consensus"; - } else { - halflife = EWMA_DEFAULT_HALFLIFE; - source = "Default value"; - } - - if (halflife <= EPSILON) { - /* The cell EWMA algorithm is disabled. */ - ewma_scale_factor = 0.1; - ewma_enabled = 0; - log_info(LD_OR, - "Disabled cell_ewma algorithm because of value in %s", - source); - } else { - /* convert halflife into halflife-per-tick. */ - halflife /= EWMA_TICK_LEN; - /* compute per-tick scale factor. */ - ewma_scale_factor = exp( LOG_ONEHALF / halflife ); - ewma_enabled = 1; - log_info(LD_OR, - "Enabled cell_ewma algorithm because of value in %s; " - "scale factor is %f per %d seconds", - source, ewma_scale_factor, EWMA_TICK_LEN); - } -} - -/** Return the multiplier necessary to convert the value of a cell sent in - * 'from_tick' to one sent in 'to_tick'. */ -static INLINE double -get_scale_factor(unsigned from_tick, unsigned to_tick) +update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction, + const char *file, int lineno) { - /* This math can wrap around, but that's okay: unsigned overflow is - well-defined */ - int diff = (int)(to_tick - from_tick); - return pow(ewma_scale_factor, diff); -} - -/** Adjust the cell count of <b>ewma</b> so that it is scaled with respect to - * <b>cur_tick</b> */ -static void -scale_single_cell_ewma(cell_ewma_t *ewma, unsigned cur_tick) -{ - double factor = get_scale_factor(ewma->last_adjusted_tick, cur_tick); - ewma->cell_count *= factor; - ewma->last_adjusted_tick = cur_tick; -} + channel_t *chan = NULL; + or_circuit_t *or_circ = NULL; + circuitmux_t *cmux = NULL; -/** Adjust the cell count of every active circuit on <b>conn</b> so - * that they are scaled with respect to <b>cur_tick</b> */ -static void -scale_active_circuits(or_connection_t *conn, unsigned cur_tick) -{ - - double factor = get_scale_factor( - conn->active_circuit_pqueue_last_recalibrated, - cur_tick); - /** Ordinarily it isn't okay to change the value of an element in a heap, - * but it's okay here, since we are preserving the order. */ - SMARTLIST_FOREACH(conn->active_circuit_pqueue, cell_ewma_t *, e, { - tor_assert(e->last_adjusted_tick == - conn->active_circuit_pqueue_last_recalibrated); - e->cell_count *= factor; - e->last_adjusted_tick = cur_tick; - }); - conn->active_circuit_pqueue_last_recalibrated = cur_tick; -} - -/** Rescale <b>ewma</b> to the same scale as <b>conn</b>, and add it to - * <b>conn</b>'s priority queue of active circuits */ -static void -add_cell_ewma_to_conn(or_connection_t *conn, cell_ewma_t *ewma) -{ - tor_assert(ewma->heap_index == -1); - scale_single_cell_ewma(ewma, - conn->active_circuit_pqueue_last_recalibrated); - - smartlist_pqueue_add(conn->active_circuit_pqueue, - compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), - ewma); -} - -/** Remove <b>ewma</b> from <b>conn</b>'s priority queue of active circuits */ -static void -remove_cell_ewma_from_conn(or_connection_t *conn, cell_ewma_t *ewma) -{ - tor_assert(ewma->heap_index != -1); - smartlist_pqueue_remove(conn->active_circuit_pqueue, - compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), - ewma); -} - -/** Remove and return the first cell_ewma_t from conn's priority queue of - * active circuits. Requires that the priority queue is nonempty. */ -static cell_ewma_t * -pop_first_cell_ewma_from_conn(or_connection_t *conn) -{ - return smartlist_pqueue_pop(conn->active_circuit_pqueue, - compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index)); -} - -/** Add <b>circ</b> to the list of circuits with pending cells on - * <b>conn</b>. No effect if <b>circ</b> is already linked. */ -void -make_circuit_active_on_conn(circuit_t *circ, or_connection_t *conn) -{ - circuit_t **nextp = next_circ_on_conn_p(circ, conn); - circuit_t **prevp = prev_circ_on_conn_p(circ, conn); - - if (*nextp && *prevp) { - /* Already active. */ - return; - } - - assert_active_circuits_ok_paranoid(conn); - - if (! conn->active_circuits) { - conn->active_circuits = circ; - *prevp = *nextp = circ; - } else { - circuit_t *head = conn->active_circuits; - circuit_t *old_tail = *prev_circ_on_conn_p(head, conn); - *next_circ_on_conn_p(old_tail, conn) = circ; - *nextp = head; - *prev_circ_on_conn_p(head, conn) = circ; - *prevp = old_tail; - } + tor_assert(circ); - if (circ->n_conn == conn) { - add_cell_ewma_to_conn(conn, &circ->n_cell_ewma); + /* Okay, get the channel */ + if (direction == CELL_DIRECTION_OUT) { + chan = circ->n_chan; } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - tor_assert(conn == orcirc->p_conn); - add_cell_ewma_to_conn(conn, &orcirc->p_cell_ewma); + or_circ = TO_OR_CIRCUIT(circ); + chan = or_circ->p_chan; } - assert_active_circuits_ok_paranoid(conn); -} + tor_assert(chan); + tor_assert(chan->cmux); -/** Remove <b>circ</b> from the list of circuits with pending cells on - * <b>conn</b>. No effect if <b>circ</b> is already unlinked. */ -void -make_circuit_inactive_on_conn(circuit_t *circ, or_connection_t *conn) -{ - circuit_t **nextp = next_circ_on_conn_p(circ, conn); - circuit_t **prevp = prev_circ_on_conn_p(circ, conn); - circuit_t *next = *nextp, *prev = *prevp; + /* Now get the cmux */ + cmux = chan->cmux; - if (!next && !prev) { - /* Already inactive. */ + /* Cmux sanity check */ + if (! circuitmux_is_circuit_attached(cmux, circ)) { + log_warn(LD_BUG, "called on non-attachd circuit from %s:%d", + file, lineno); return; } + tor_assert(circuitmux_attached_circuit_direction(cmux, circ) == direction); - assert_active_circuits_ok_paranoid(conn); - - tor_assert(next && prev); - tor_assert(*prev_circ_on_conn_p(next, conn) == circ); - tor_assert(*next_circ_on_conn_p(prev, conn) == circ); - - if (next == circ) { - conn->active_circuits = NULL; - } else { - *prev_circ_on_conn_p(next, conn) = prev; - *next_circ_on_conn_p(prev, conn) = next; - if (conn->active_circuits == circ) - conn->active_circuits = next; - } - *prevp = *nextp = NULL; + assert_cmux_ok_paranoid(chan); - if (circ->n_conn == conn) { - remove_cell_ewma_from_conn(conn, &circ->n_cell_ewma); + /* Update the number of cells we have for the circuit mux */ + if (direction == CELL_DIRECTION_OUT) { + circuitmux_set_num_cells(cmux, circ, circ->n_chan_cells.n); } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - tor_assert(conn == orcirc->p_conn); - remove_cell_ewma_from_conn(conn, &orcirc->p_cell_ewma); + circuitmux_set_num_cells(cmux, circ, or_circ->p_chan_cells.n); } - assert_active_circuits_ok_paranoid(conn); + assert_cmux_ok_paranoid(chan); } -/** Remove all circuits from the list of circuits with pending cells on - * <b>conn</b>. */ +/** Remove all circuits from the cmux on <b>chan</b>. */ void -connection_or_unlink_all_active_circs(or_connection_t *orconn) +channel_unlink_all_circuits(channel_t *chan) { - circuit_t *head = orconn->active_circuits; - circuit_t *cur = head; - if (! head) - return; - do { - circuit_t *next = *next_circ_on_conn_p(cur, orconn); - *prev_circ_on_conn_p(cur, orconn) = NULL; - *next_circ_on_conn_p(cur, orconn) = NULL; - cur = next; - } while (cur != head); - orconn->active_circuits = NULL; - - SMARTLIST_FOREACH(orconn->active_circuit_pqueue, cell_ewma_t *, e, - e->heap_index = -1); - smartlist_clear(orconn->active_circuit_pqueue); + tor_assert(chan); + tor_assert(chan->cmux); + + circuitmux_detach_all_circuits(chan->cmux); + chan->num_n_circuits = 0; + chan->num_p_circuits = 0; } /** Block (if <b>block</b> is true) or unblock (if <b>block</b> is false) - * every edge connection that is using <b>circ</b> to write to <b>orconn</b>, + * every edge connection that is using <b>circ</b> to write to <b>chan</b>, * and start or stop reading as appropriate. * * If <b>stream_id</b> is nonzero, block only the edge connection whose @@ -2326,17 +2192,17 @@ connection_or_unlink_all_active_circs(or_connection_t *orconn) * Returns the number of streams whose status we changed. */ static int -set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn, +set_streams_blocked_on_circ(circuit_t *circ, channel_t *chan, int block, streamid_t stream_id) { edge_connection_t *edge = NULL; int n = 0; - if (circ->n_conn == orconn) { - circ->streams_blocked_on_n_conn = block; + if (circ->n_chan == chan) { + circ->streams_blocked_on_n_chan = block; if (CIRCUIT_IS_ORIGIN(circ)) edge = TO_ORIGIN_CIRCUIT(circ)->p_streams; } else { - circ->streams_blocked_on_p_conn = block; + circ->streams_blocked_on_p_chan = block; tor_assert(!CIRCUIT_IS_ORIGIN(circ)); edge = TO_OR_CIRCUIT(circ)->n_streams; } @@ -2371,58 +2237,51 @@ set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn, } /** Pull as many cells as possible (but no more than <b>max</b>) from the - * queue of the first active circuit on <b>conn</b>, and write them to - * <b>conn</b>->outbuf. Return the number of cells written. Advance + * queue of the first active circuit on <b>chan</b>, and write them to + * <b>chan</b>->outbuf. Return the number of cells written. Advance * the active circuit pointer to the next active circuit in the ring. */ int -connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, - time_t now) +channel_flush_from_first_active_circuit(channel_t *chan, int max) { - int n_flushed; + circuitmux_t *cmux = NULL; + int n_flushed = 0; cell_queue_t *queue; circuit_t *circ; + or_circuit_t *or_circ; int streams_blocked; - - /* The current (hi-res) time */ - struct timeval now_hires; - - /* The EWMA cell counter for the circuit we're flushing. */ - cell_ewma_t *cell_ewma = NULL; - double ewma_increment = -1; - - circ = conn->active_circuits; - if (!circ) return 0; - assert_active_circuits_ok_paranoid(conn); - - /* See if we're doing the ewma circuit selection algorithm. */ - if (ewma_enabled) { - unsigned tick; - double fractional_tick; - tor_gettimeofday_cached(&now_hires); - tick = cell_ewma_tick_from_timeval(&now_hires, &fractional_tick); - - if (tick != conn->active_circuit_pqueue_last_recalibrated) { - scale_active_circuits(conn, tick); + packed_cell_t *cell; + + /* Get the cmux */ + tor_assert(chan); + tor_assert(chan->cmux); + cmux = chan->cmux; + + /* Main loop: pick a circuit, send a cell, update the cmux */ + while (n_flushed < max) { + circ = circuitmux_get_first_active_circuit(cmux); + /* If it returns NULL, no cells left to send */ + if (!circ) break; + assert_cmux_ok_paranoid(chan); + + if (circ->n_chan == chan) { + queue = &circ->n_chan_cells; + streams_blocked = circ->streams_blocked_on_n_chan; + } else { + or_circ = TO_OR_CIRCUIT(circ); + tor_assert(or_circ->p_chan == chan); + queue = &TO_OR_CIRCUIT(circ)->p_chan_cells; + streams_blocked = circ->streams_blocked_on_p_chan; } - ewma_increment = pow(ewma_scale_factor, -fractional_tick); - - cell_ewma = smartlist_get(conn->active_circuit_pqueue, 0); - circ = cell_ewma_to_circuit(cell_ewma); - } + /* Circuitmux told us this was active, so it should have cells */ + tor_assert(queue->n > 0); - if (circ->n_conn == conn) { - queue = &circ->n_conn_cells; - streams_blocked = circ->streams_blocked_on_n_conn; - } else { - queue = &TO_OR_CIRCUIT(circ)->p_conn_cells; - streams_blocked = circ->streams_blocked_on_p_conn; - } - tor_assert(*next_circ_on_conn_p(circ,conn)); - - for (n_flushed = 0; n_flushed < max && queue->head; ) { - packed_cell_t *cell = cell_queue_pop(queue); - tor_assert(*next_circ_on_conn_p(circ,conn)); + /* + * Get just one cell here; once we've sent it, that can change the circuit + * selection, so we have to loop around for another even if this circuit + * has more than one. + */ + cell = cell_queue_pop(queue); /* Calculate the exact time that this cell has spent in the queue. */ if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { @@ -2438,8 +2297,8 @@ connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, "Looks like the CellStatistics option was " "recently enabled."); } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); insertion_time_elem_t *elem = it_queue->first; + or_circ = TO_OR_CIRCUIT(circ); cell_waiting_time = (uint32_t)((flushed * 10L + SECONDS_IN_A_DAY * 1000L - elem->insertion_time * 10L) % @@ -2452,66 +2311,58 @@ connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, it_queue->last = NULL; mp_pool_release(elem); } - orcirc->total_cell_waiting_time += cell_waiting_time; - orcirc->processed_cells++; + or_circ->total_cell_waiting_time += cell_waiting_time; + or_circ->processed_cells++; } } /* If we just flushed our queue and this circuit is used for a * tunneled directory request, possibly advance its state. */ - if (queue->n == 0 && TO_CONN(conn)->dirreq_id) - geoip_change_dirreq_state(TO_CONN(conn)->dirreq_id, + if (queue->n == 0 && chan->dirreq_id) + geoip_change_dirreq_state(chan->dirreq_id, DIRREQ_TUNNELED, DIRREQ_CIRC_QUEUE_FLUSHED); - connection_write_to_buf(cell->body, CELL_NETWORK_SIZE, TO_CONN(conn)); + /* Now send the cell */ + channel_write_packed_cell(chan, cell); + cell = NULL; - packed_cell_free_unchecked(cell); + /* + * Don't packed_cell_free_unchecked(cell) here because the channel will + * do so when it gets out of the channel queue (probably already did, in + * which case that was an immediate double-free bug). + */ + + /* Update the counter */ ++n_flushed; - if (cell_ewma) { - cell_ewma_t *tmp; - cell_ewma->cell_count += ewma_increment; - /* We pop and re-add the cell_ewma_t here, not above, since we need to - * re-add it immediately to keep the priority queue consistent with - * the linked-list implementation */ - tmp = pop_first_cell_ewma_from_conn(conn); - tor_assert(tmp == cell_ewma); - add_cell_ewma_to_conn(conn, cell_ewma); - } - if (!ewma_enabled && circ != conn->active_circuits) { - /* If this happens, the current circuit just got made inactive by - * a call in connection_write_to_buf(). That's nothing to worry about: - * circuit_make_inactive_on_conn() already advanced conn->active_circuits - * for us. - */ - assert_active_circuits_ok_paranoid(conn); - goto done; - } - } - tor_assert(*next_circ_on_conn_p(circ,conn)); - assert_active_circuits_ok_paranoid(conn); - conn->active_circuits = *next_circ_on_conn_p(circ, conn); - /* Is the cell queue low enough to unblock all the streams that are waiting - * to write to this circuit? */ - if (streams_blocked && queue->n <= CELL_QUEUE_LOWWATER_SIZE) - set_streams_blocked_on_circ(circ, conn, 0, 0); /* unblock streams */ + /* + * Now update the cmux; tell it we've just sent a cell, and how many + * we have left. + */ + circuitmux_notify_xmit_cells(cmux, circ, 1); + circuitmux_set_num_cells(cmux, circ, queue->n); + if (queue->n == 0) + log_debug(LD_GENERAL, "Made a circuit inactive."); + + /* Is the cell queue low enough to unblock all the streams that are waiting + * to write to this circuit? */ + if (streams_blocked && queue->n <= CELL_QUEUE_LOWWATER_SIZE) + set_streams_blocked_on_circ(circ, chan, 0, 0); /* unblock streams */ - /* Did we just run out of cells on this circuit's queue? */ - if (queue->n == 0) { - log_debug(LD_GENERAL, "Made a circuit inactive."); - make_circuit_inactive_on_conn(circ, conn); + /* If n_flushed < max still, loop around and pick another circuit */ } - done: - if (n_flushed) - conn->timestamp_last_added_nonpadding = now; + + /* Okay, we're done sending now */ + assert_cmux_ok_paranoid(chan); + return n_flushed; } -/** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>orconn</b> +/** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ void -append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, +append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, cell_t *cell, cell_direction_t direction, streamid_t fromstream) { @@ -2521,12 +2372,12 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, return; if (direction == CELL_DIRECTION_OUT) { - queue = &circ->n_conn_cells; - streams_blocked = circ->streams_blocked_on_n_conn; + queue = &circ->n_chan_cells; + streams_blocked = circ->streams_blocked_on_n_chan; } else { or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - queue = &orcirc->p_conn_cells; - streams_blocked = circ->streams_blocked_on_p_conn; + queue = &orcirc->p_chan_cells; + streams_blocked = circ->streams_blocked_on_p_chan; } cell_queue_append_packed_copy(queue, cell); @@ -2534,27 +2385,27 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, /* If we have too many cells on the circuit, we should stop reading from * the edge streams for a while. */ if (!streams_blocked && queue->n >= CELL_QUEUE_HIGHWATER_SIZE) - set_streams_blocked_on_circ(circ, orconn, 1, 0); /* block streams */ + set_streams_blocked_on_circ(circ, chan, 1, 0); /* block streams */ if (streams_blocked && fromstream) { /* This edge connection is apparently not blocked; block it. */ - set_streams_blocked_on_circ(circ, orconn, 1, fromstream); + set_streams_blocked_on_circ(circ, chan, 1, fromstream); } + update_circuit_on_cmux(circ, direction); if (queue->n == 1) { - /* This was the first cell added to the queue. We need to make this + /* This was the first cell added to the queue. We just made this * circuit active. */ log_debug(LD_GENERAL, "Made a circuit active."); - make_circuit_active_on_conn(circ, orconn); } - if (! connection_get_outbuf_len(TO_CONN(orconn))) { + if (!channel_has_queued_writes(chan)) { /* There is no data at all waiting to be sent on the outbuf. Add a * cell, so that we can notice when it gets flushed, flushed_some can * get called, and we can start putting more data onto the buffer then. */ log_debug(LD_GENERAL, "Primed a buffer."); - connection_or_flush_from_first_active_circuit(orconn, 1, approx_time()); + channel_flush_from_first_active_circuit(chan, 1); } } @@ -2618,58 +2469,40 @@ decode_address_from_payload(tor_addr_t *addr_out, const uint8_t *payload, return payload + 2 + payload[1]; } -/** Remove all the cells queued on <b>circ</b> for <b>orconn</b>. */ +/** Remove all the cells queued on <b>circ</b> for <b>chan</b>. */ void -circuit_clear_cell_queue(circuit_t *circ, or_connection_t *orconn) +circuit_clear_cell_queue(circuit_t *circ, channel_t *chan) { cell_queue_t *queue; - if (circ->n_conn == orconn) { - queue = &circ->n_conn_cells; + cell_direction_t direction; + + if (circ->n_chan == chan) { + queue = &circ->n_chan_cells; + direction = CELL_DIRECTION_OUT; } else { or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); - tor_assert(orcirc->p_conn == orconn); - queue = &orcirc->p_conn_cells; + tor_assert(orcirc->p_chan == chan); + queue = &orcirc->p_chan_cells; + direction = CELL_DIRECTION_IN; } - if (queue->n) - make_circuit_inactive_on_conn(circ,orconn); - + /* Clear the queue */ cell_queue_clear(queue); + + /* Update the cell counter in the cmux */ + if (chan->cmux && circuitmux_is_circuit_attached(chan->cmux, circ)) + update_circuit_on_cmux(circ, direction); } -/** Fail with an assert if the active circuits ring on <b>orconn</b> is - * corrupt. */ +/** Fail with an assert if the circuit mux on chan is corrupt + */ void -assert_active_circuits_ok(or_connection_t *orconn) +assert_circuit_mux_okay(channel_t *chan) { - circuit_t *head = orconn->active_circuits; - circuit_t *cur = head; - int n = 0; - if (! head) - return; - do { - circuit_t *next = *next_circ_on_conn_p(cur, orconn); - circuit_t *prev = *prev_circ_on_conn_p(cur, orconn); - cell_ewma_t *ewma; - tor_assert(next); - tor_assert(prev); - tor_assert(*next_circ_on_conn_p(prev, orconn) == cur); - tor_assert(*prev_circ_on_conn_p(next, orconn) == cur); - if (orconn == cur->n_conn) { - ewma = &cur->n_cell_ewma; - tor_assert(!ewma->is_for_p_conn); - } else { - ewma = &TO_OR_CIRCUIT(cur)->p_cell_ewma; - tor_assert(ewma->is_for_p_conn); - } - tor_assert(ewma->heap_index != -1); - tor_assert(ewma == smartlist_get(orconn->active_circuit_pqueue, - ewma->heap_index)); - n++; - cur = next; - } while (cur != head); - - tor_assert(n == smartlist_len(orconn->active_circuit_pqueue)); + tor_assert(chan); + tor_assert(chan->cmux); + + circuitmux_assert_okay(chan->cmux); } /** Return 1 if we shouldn't restart reading on this circuit, even if @@ -2679,9 +2512,9 @@ static int circuit_queue_streams_are_blocked(circuit_t *circ) { if (CIRCUIT_IS_ORIGIN(circ)) { - return circ->streams_blocked_on_n_conn; + return circ->streams_blocked_on_n_chan; } else { - return circ->streams_blocked_on_p_conn; + return circ->streams_blocked_on_p_chan; } } diff --git a/src/or/relay.h b/src/or/relay.h index 41675e210..57400fdbd 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -9,8 +9,8 @@ * \brief Header file for relay.c. **/ -#ifndef _TOR_RELAY_H -#define _TOR_RELAY_H +#ifndef TOR_RELAY_H +#define TOR_RELAY_H extern uint64_t stats_n_relay_cells_relayed; extern uint64_t stats_n_relay_cells_delivered; @@ -41,32 +41,35 @@ void free_cell_pool(void); void clean_cell_pool(void); void dump_cell_pool_usage(int severity); +/* For channeltls.c */ +void packed_cell_free(packed_cell_t *cell); + void cell_queue_clear(cell_queue_t *queue); void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell); void cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell); -void append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, +void append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, cell_t *cell, cell_direction_t direction, streamid_t fromstream); -void connection_or_unlink_all_active_circs(or_connection_t *conn); -int connection_or_flush_from_first_active_circuit(or_connection_t *conn, - int max, time_t now); -void assert_active_circuits_ok(or_connection_t *orconn); -void make_circuit_inactive_on_conn(circuit_t *circ, or_connection_t *conn); -void make_circuit_active_on_conn(circuit_t *circ, or_connection_t *conn); +void channel_unlink_all_circuits(channel_t *chan); +int channel_flush_from_first_active_circuit(channel_t *chan, int max); +void assert_circuit_mux_okay(channel_t *chan); +void update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction, + const char *file, int lineno); +#define update_circuit_on_cmux(circ, direction) \ + update_circuit_on_cmux_((circ), (direction), SHORT_FILE__, __LINE__) int append_address_to_payload(uint8_t *payload_out, const tor_addr_t *addr); const uint8_t *decode_address_from_payload(tor_addr_t *addr_out, const uint8_t *payload, int payload_len); -unsigned cell_ewma_get_tick(void); -void cell_ewma_set_scale_factor(const or_options_t *options, - const networkstatus_t *consensus); -void circuit_clear_cell_queue(circuit_t *circ, or_connection_t *orconn); +void circuit_clear_cell_queue(circuit_t *circ, channel_t *chan); #ifdef RELAY_PRIVATE int relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t **layer_hint, char *recognized); +int connected_cell_parse(const relay_header_t *rh, const cell_t *cell, + tor_addr_t *addr_out, int *ttl_out); #endif #endif diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 3a0cd1a66..2219fe8a1 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -23,6 +23,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" +#include "routerset.h" static extend_info_t *rend_client_get_random_intro_impl( const rend_cache_entry_t *rend_query, @@ -43,7 +44,7 @@ rend_client_purge_state(void) void rend_client_introcirc_has_opened(origin_circuit_t *circ) { - tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); tor_assert(circ->cpath); log_info(LD_REND,"introcirc is open"); @@ -56,7 +57,7 @@ rend_client_introcirc_has_opened(origin_circuit_t *circ) static int rend_client_send_establish_rendezvous(origin_circuit_t *circ) { - tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); tor_assert(circ->rend_data); log_info(LD_REND, "Sending an ESTABLISH_RENDEZVOUS cell"); @@ -65,6 +66,11 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return -1; } + + /* Set timestamp_dirty, because circuit_expire_building expects it, + * and the rend cookie also means we've used the circ. */ + circ->base_.timestamp_dirty = time(NULL); + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), RELAY_COMMAND_ESTABLISH_RENDEZVOUS, circ->rend_data->rend_cookie, @@ -99,16 +105,17 @@ rend_client_reextend_intro_circuit(origin_circuit_t *circ) circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return -1; } + // XXX: should we not re-extend if hs_circ_has_timed_out? if (circ->remaining_relay_early_cells) { log_info(LD_REND, "Re-extending circ %d, this time to %s.", - circ->_base.n_circ_id, + circ->base_.n_circ_id, safe_str_client(extend_info_describe(extend_info))); result = circuit_extend_to_new_exit(circ, extend_info); } else { log_info(LD_REND, "Closing intro circ %d (out of RELAY_EARLY cells).", - circ->_base.n_circ_id); + circ->base_.n_circ_id); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ result = 0; @@ -132,9 +139,10 @@ 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); + tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY); tor_assert(introcirc->rend_data); tor_assert(rendcirc->rend_data); tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address, @@ -161,7 +169,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 +193,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; } @@ -200,12 +212,14 @@ rend_client_send_introduction(origin_circuit_t *introcirc, cpath = rendcirc->build_state->pending_final_cpath = tor_malloc_zero(sizeof(crypt_path_t)); cpath->magic = CRYPT_PATH_MAGIC; - if (!(cpath->dh_handshake_state = crypto_dh_new(DH_TYPE_REND))) { + if (!(cpath->rend_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) { + if (crypto_dh_generate_public(cpath->rend_dh_handshake_state)<0) { log_warn(LD_BUG, "Internal error: couldn't generate g^x."); + status = -2; goto perm_err; } } @@ -253,9 +267,10 @@ rend_client_send_introduction(origin_circuit_t *introcirc, dh_offset = MAX_NICKNAME_LEN+1+REND_COOKIE_LEN; } - if (crypto_dh_get_public(cpath->dh_handshake_state, tmp+dh_offset, + if (crypto_dh_get_public(cpath->rend_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 +284,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 +304,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. */ @@ -297,14 +314,19 @@ rend_client_send_introduction(origin_circuit_t *introcirc, /* Set timestamp_dirty, because circuit_expire_building expects it * to specify when a circuit entered the _C_INTRODUCE_ACK_WAIT * state. */ - introcirc->_base.timestamp_dirty = time(NULL); + introcirc->base_.timestamp_dirty = time(NULL); + + goto cleanup; - return 0; perm_err: - if (!introcirc->_base.marked_for_close) + 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: + memwipe(payload, 0, sizeof(payload)); + memwipe(tmp, 0, sizeof(tmp)); + + return status; } /** Called when a rendezvous circuit is open; sends a establish @@ -312,7 +334,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, void rend_client_rendcirc_has_opened(origin_circuit_t *circ) { - tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); log_info(LD_REND,"rendcirc is open"); @@ -322,6 +344,32 @@ rend_client_rendcirc_has_opened(origin_circuit_t *circ) } } +/** + * Called to close other intro circuits we launched in parallel + * due to timeout. + */ +static void +rend_client_close_other_intros(const char *onion_address) +{ + circuit_t *c; + /* abort parallel intro circs, if any */ + for (c = circuit_get_global_list_(); c; c = c->next) { + if ((c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || + c->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) && + !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { + origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); + if (oc->rend_data && + !rend_cmp_service_ids(onion_address, + oc->rend_data->onion_address)) { + log_info(LD_REND|LD_CIRC, "Closing introduction circuit %d that we " + "built in parallel (Purpose %d).", oc->global_identifier, + c->purpose); + circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT); + } + } + } +} + /** Called when get an ACK or a NAK for a REND_INTRODUCE1 cell. */ int @@ -331,10 +379,10 @@ rend_client_introduction_acked(origin_circuit_t *circ, origin_circuit_t *rendcirc; (void) request; // XXXX Use this. - if (circ->_base.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { log_warn(LD_PROTOCOL, "Received REND_INTRODUCE_ACK on unexpected circuit %d.", - circ->_base.n_circ_id); + circ->base_.n_circ_id); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); return -1; } @@ -345,6 +393,10 @@ rend_client_introduction_acked(origin_circuit_t *circ, #endif tor_assert(circ->rend_data); + /* For path bias: This circuit was used successfully. Valid + * nacks and acks count. */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ /* Locate the rend circ which is waiting to hear about this ack, @@ -361,7 +413,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, /* Set timestamp_dirty, because circuit_expire_building expects * it to specify when a circuit entered the * _C_REND_READY_INTRO_ACKED state. */ - rendcirc->_base.timestamp_dirty = time(NULL); + rendcirc->base_.timestamp_dirty = time(NULL); } else { log_info(LD_REND,"...Found no rend circ. Dropping on the floor."); } @@ -369,6 +421,9 @@ rend_client_introduction_acked(origin_circuit_t *circ, circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + + /* close any other intros launched in parallel */ + rend_client_close_other_intros(circ->rend_data->onion_address); } else { /* It's a NAK; the introduction point didn't relay our request. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); @@ -525,7 +580,7 @@ rend_client_purge_last_hid_serv_requests(void) if (old_last_hid_serv_requests != NULL) { log_info(LD_REND, "Purging client last-HS-desc-request-time table"); - strmap_free(old_last_hid_serv_requests, _tor_free); + strmap_free(old_last_hid_serv_requests, tor_free_); } } @@ -602,7 +657,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) directory_initiate_command_routerstatus_rend(hs_dir, DIR_PURPOSE_FETCH_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, - !tor2web_mode, desc_id_base32, + tor2web_mode?DIRIND_ONEHOP:DIRIND_ANONYMOUS, + desc_id_base32, NULL, 0, 0, rend_query); log_info(LD_REND, "Sending fetch request for v2 descriptor for " @@ -659,10 +715,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 +733,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: + memwipe(descriptor_id, 0, sizeof(descriptor_id)); + return; } @@ -818,7 +885,7 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, (void) request; (void) request_len; /* we just got an ack for our establish-rendezvous. switch purposes. */ - if (circ->_base.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. " "Closing circ."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); @@ -829,7 +896,14 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); /* Set timestamp_dirty, because circuit_expire_building expects it * to specify when a circuit entered the _C_REND_READY state. */ - circ->_base.timestamp_dirty = time(NULL); + circ->base_.timestamp_dirty = time(NULL); + + /* From a path bias point of view, this circuit is now successfully used. + * Waiting any longer opens us up to attacks from Bob. He could induce + * Alice to attempt to connect to his hidden service and never reply + * to her rend requests */ + circ->path_state = PATH_STATE_USE_SUCCEEDED; + /* XXXX This is a pretty brute-force approach. It'd be better to * attach only the connections that are waiting on this circuit, rather * than trying to attach them all. See comments bug 743. */ @@ -847,8 +921,8 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, crypt_path_t *hop; char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; - if ((circ->_base.purpose != CIRCUIT_PURPOSE_C_REND_READY && - circ->_base.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) + if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && + circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) || !circ->build_state->pending_final_cpath) { log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not " "expecting it. Closing."); @@ -868,9 +942,9 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, tor_assert(circ->build_state); tor_assert(circ->build_state->pending_final_cpath); hop = circ->build_state->pending_final_cpath; - tor_assert(hop->dh_handshake_state); + tor_assert(hop->rend_dh_handshake_state); if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, - hop->dh_handshake_state, (char*)request, + hop->rend_dh_handshake_state, (char*)request, DH_KEY_LEN, keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { log_warn(LD_GENERAL, "Couldn't complete DH handshake."); @@ -886,8 +960,8 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, goto err; } - crypto_dh_free(hop->dh_handshake_state); - hop->dh_handshake_state = NULL; + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; /* All is well. Extend the circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED); @@ -1172,11 +1246,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);); @@ -1222,7 +1296,7 @@ rend_parse_service_authorization(const or_options_t *options, descriptor_cookie); goto err; } - auth_type_val = (descriptor_cookie_tmp[16] >> 4) + 1; + auth_type_val = (((uint8_t)descriptor_cookie_tmp[16]) >> 4) + 1; if (auth_type_val < 1 || auth_type_val > 2) { log_warn(LD_CONFIG, "Authorization cookie has unknown authorization " "type encoded."); @@ -1253,6 +1327,8 @@ rend_parse_service_authorization(const or_options_t *options, } else { strmap_free(parsed, rend_service_authorization_strmap_item_free); } + memwipe(descriptor_cookie_tmp, 0, sizeof(descriptor_cookie_tmp)); + memwipe(descriptor_cookie_base64ext, 0, sizeof(descriptor_cookie_base64ext)); return res; } diff --git a/src/or/rendclient.h b/src/or/rendclient.h index 393b556e3..b71fe48be 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -9,8 +9,8 @@ * \brief Header file for rendclient.c. **/ -#ifndef _TOR_RENDCLIENT_H -#define _TOR_RENDCLIENT_H +#ifndef TOR_RENDCLIENT_H +#define TOR_RENDCLIENT_H void rend_client_purge_state(void); diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 4722690c1..9e306bdf7 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); @@ -800,7 +800,7 @@ rend_cache_entry_free(rend_cache_entry_t *e) /** Helper: deallocate a rend_cache_entry_t. (Used with strmap_free(), which * requires a function pointer whose argument is void*). */ static void -_rend_cache_entry_free(void *p) +rend_cache_entry_free_(void *p) { rend_cache_entry_free(p); } @@ -809,8 +809,8 @@ _rend_cache_entry_free(void *p) void rend_cache_free_all(void) { - strmap_free(rend_cache, _rend_cache_entry_free); - digestmap_free(rend_cache_v2_dir, _rend_cache_entry_free); + strmap_free(rend_cache, rend_cache_entry_free_); + digestmap_free(rend_cache_v2_dir, rend_cache_entry_free_); rend_cache = NULL; rend_cache_v2_dir = NULL; } @@ -844,7 +844,7 @@ rend_cache_purge(void) { if (rend_cache) { log_info(LD_REND, "Purging client/v0-HS-authority HS descriptor cache"); - strmap_free(rend_cache, _rend_cache_entry_free); + strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); } diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index be6bd13d2..fe574b152 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -9,8 +9,8 @@ * \brief Header file for rendcommon.c. **/ -#ifndef _TOR_RENDCOMMON_H -#define _TOR_RENDCOMMON_H +#ifndef TOR_RENDCOMMON_H +#define TOR_RENDCOMMON_H /** Free all storage associated with <b>data</b> */ static INLINE void diff --git a/src/or/rendmid.c b/src/or/rendmid.c index bacd0ef93..8234265ca 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -35,7 +35,7 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, "Received an ESTABLISH_INTRO request on circuit %d", circ->p_circ_id); - if (circ->_base.purpose != CIRCUIT_PURPOSE_OR || circ->_base.n_conn) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Rejecting ESTABLISH_INTRO on non-OR or non-edge circuit."); reason = END_CIRC_REASON_TORPROTOCOL; @@ -56,8 +56,8 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, goto err; } - /* Next 20 bytes: Hash of handshake_digest | "INTRODUCE" */ - memcpy(buf, circ->handshake_digest, DIGEST_LEN); + /* Next 20 bytes: Hash of rend_circ_nonce | "INTRODUCE" */ + memcpy(buf, circ->rend_circ_nonce, DIGEST_LEN); memcpy(buf+DIGEST_LEN, "INTRODUCE", 9); if (crypto_digest(expected_digest, buf, DIGEST_LEN+9) < 0) { log_warn(LD_BUG, "Internal error computing digest."); @@ -142,7 +142,7 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, log_info(LD_REND, "Received an INTRODUCE1 request on circuit %d", circ->p_circ_id); - if (circ->_base.purpose != CIRCUIT_PURPOSE_OR || circ->_base.n_conn) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { log_warn(LD_PROTOCOL, "Rejecting INTRODUCE1 on non-OR or non-edge circuit %d.", circ->p_circ_id); @@ -224,7 +224,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, log_info(LD_REND, "Received an ESTABLISH_RENDEZVOUS request on circuit %d", circ->p_circ_id); - if (circ->_base.purpose != CIRCUIT_PURPOSE_OR || circ->_base.n_conn) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { log_warn(LD_PROTOCOL, "Tried to establish rendezvous on non-OR or non-edge circuit."); goto err; @@ -277,7 +277,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, char hexid[9]; int reason = END_CIRC_REASON_INTERNAL; - if (circ->_base.purpose != CIRCUIT_PURPOSE_OR || circ->_base.n_conn) { + if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) { log_info(LD_REND, "Tried to complete rendezvous on non-OR or non-edge circuit %d.", circ->p_circ_id); diff --git a/src/or/rendmid.h b/src/or/rendmid.h index 0af6436de..74ba16b31 100644 --- a/src/or/rendmid.h +++ b/src/or/rendmid.h @@ -9,8 +9,8 @@ * \brief Header file for rendmid.c. **/ -#ifndef _TOR_RENDMID_H -#define _TOR_RENDMID_H +#ifndef TOR_RENDMID_H +#define TOR_RENDMID_H int rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, size_t request_len); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index d235f089f..fc76f5654 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,16 +23,42 @@ #include "router.h" #include "relay.h" #include "rephist.h" +#include "replaycache.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" 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 +119,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 +159,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); + memwipe(client->descriptor_cookie, 0, sizeof(client->descriptor_cookie)); tor_free(client); } @@ -171,7 +197,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); } @@ -244,8 +272,8 @@ rend_add_service(rend_service_t *service) service->directory); for (i = 0; i < smartlist_len(service->ports); ++i) { p = smartlist_get(service->ports, i); - log_debug(LD_REND,"Service maps port %d to %s:%d", - p->virtual_port, fmt_addr(&p->real_addr), p->real_port); + log_debug(LD_REND,"Service maps port %d to %s", + p->virtual_port, fmt_addrport(&p->real_addr, p->real_port)); } } } @@ -515,7 +543,7 @@ rend_config_services(const or_options_t *options, int validate_only) /* XXXX it would be nicer if we had a nicer abstraction to use here, * so we could just iterate over the list of services to close, but * once again, this isn't critical-path code. */ - for (circ = _circuit_get_global_list(); circ; circ = circ->next) { + for (circ = circuit_get_global_list_(); circ; circ = circ->next) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -582,7 +610,7 @@ rend_service_update_descriptor(rend_service_t *service) } circ = find_intro_circuit(intro_svc, service->pk_digest); - if (!circ || circ->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) { + if (!circ || circ->base_.purpose != CIRCUIT_PURPOSE_S_INTRO) { /* This intro point's circuit isn't finished yet. Don't list it. */ continue; } @@ -609,231 +637,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."); + memwipe(buf, 0, sizeof(buf)); + return -1; + } + memwipe(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. + */ + memwipe(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); + memwipe(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); + + memwipe(cfname, 0, sizeof(cfname)); + + /* Clear stack buffers that held key-derived material. */ + memwipe(buf, 0, sizeof(buf)); + memwipe(desc_cook_out, 0, sizeof(desc_cook_out)); + memwipe(service_id, 0, sizeof(service_id)); + memwipe(extended_desc_cookie, 0, sizeof(extended_desc_cookie)); + return r; } @@ -906,26 +977,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 +1084,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; - if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) { + /* 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; + circuit->base_.n_circ_id); + goto err; } #ifndef NON_ANONYMOUS_MODE_ENABLED @@ -1076,218 +1140,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 +1290,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 +1321,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 +1341,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 +1349,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 +1357,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 +1365,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)); @@ -1397,24 +1378,886 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, cpath->magic = CRYPT_PATH_MAGIC; launched->build_state->expiry_time = now + MAX_REND_TIMEOUT; - cpath->dh_handshake_state = dh; + cpath->rend_dh_handshake_state = dh; dh = NULL; 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); + memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN); - memwipe(keys, 0, sizeof(keys)); - return 0; + /* For path bias: This intro circuit was used successfully */ + circuit->path_state = PATH_STATE_USE_SUCCEEDED; + + 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: - memwipe(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: + memwipe(keys, 0, sizeof(keys)); + memwipe(buf, 0, sizeof(buf)); + memwipe(serviceid, 0, sizeof(serviceid)); + memwipe(hexcookie, 0, sizeof(hexcookie)); + memwipe(intro_key_digest, 0, sizeof(intro_key_digest)); + memwipe(auth_data, 0, sizeof(auth_data)); + memwipe(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 */ + memwipe(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) { + memwipe(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 */ + memwipe(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 */ + memwipe(buf, 0, sizeof(buf)); + memwipe(key_digest, 0, sizeof(key_digest)); + memwipe(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. */ @@ -1424,7 +2267,7 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) origin_circuit_t *newcirc; cpath_build_state_t *newstate, *oldstate; - tor_assert(oldcirc->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); /* Don't relaunch the same rend circ twice. */ if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) { @@ -1529,7 +2372,7 @@ rend_service_launch_establish_intro(rend_service_t *service, sizeof(launched->rend_data->onion_address)); memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN); launched->intro_key = crypto_pk_dup_key(intro->intro_key); - if (launched->_base.state == CIRCUIT_STATE_OPEN) + if (launched->base_.state == CIRCUIT_STATE_OPEN) rend_service_intro_has_opened(launched); return 0; } @@ -1541,7 +2384,7 @@ count_established_intro_points(const char *query) { int num_ipos = 0; circuit_t *circ; - for (circ = _circuit_get_global_list(); circ; circ = circ->next) { + for (circ = circuit_get_global_list_(); circ; circ = circ->next) { if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN && (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || @@ -1570,7 +2413,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) int reason = END_CIRC_REASON_TORPROTOCOL; crypto_pk_t *intro_key; - tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); #ifndef NON_ANONYMOUS_MODE_ENABLED tor_assert(!(circuit->build_state->onehop_tunnel)); #endif @@ -1584,7 +2427,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) circuit->rend_data->rend_pk_digest); if (!service) { log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.", - serviceid, circuit->_base.n_circ_id); + serviceid, circuit->base_.n_circ_id); reason = END_CIRC_REASON_NOSUCHSERVICE; goto err; } @@ -1600,8 +2443,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,13 +2465,13 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) } circuit_has_opened(circuit); - return; + goto done; } } log_info(LD_REND, "Established circuit %d as introduction point for service %s", - circuit->_base.n_circ_id, serviceid); + circuit->base_.n_circ_id, serviceid); /* Use the intro key instead of the service key in ESTABLISH_INTRO. */ intro_key = circuit->intro_key; @@ -1643,7 +2486,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) len = r; set_uint16(buf, htons((uint16_t)len)); len += 2; - memcpy(auth, circuit->cpath->prev->handshake_digest, DIGEST_LEN); + memcpy(auth, circuit->cpath->prev->rend_circ_nonce, DIGEST_LEN); memcpy(auth+DIGEST_LEN, "INTRODUCE", 9); if (crypto_digest(buf+len, auth, DIGEST_LEN+9)) goto err; @@ -1663,14 +2506,21 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) buf, len, circuit->cpath->prev)<0) { log_info(LD_GENERAL, "Couldn't send introduction request for service %s on circuit %d", - serviceid, circuit->_base.n_circ_id); + serviceid, circuit->base_.n_circ_id); reason = END_CIRC_REASON_INTERNAL; goto err; } - return; + goto done; + err: circuit_mark_for_close(TO_CIRCUIT(circuit), reason); + done: + memwipe(buf, 0, sizeof(buf)); + memwipe(auth, 0, sizeof(auth)); + memwipe(serviceid, 0, sizeof(serviceid)); + + return; } /** Called when we get an INTRO_ESTABLISHED cell; mark the circuit as a @@ -1686,7 +2536,7 @@ rend_service_intro_established(origin_circuit_t *circuit, (void) request; (void) request_len; - if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + if (circuit->base_.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { log_warn(LD_PROTOCOL, "received INTRO_ESTABLISHED cell on non-intro circuit."); goto err; @@ -1696,7 +2546,7 @@ rend_service_intro_established(origin_circuit_t *circuit, circuit->rend_data->rend_pk_digest); if (!service) { log_warn(LD_REND, "Unknown service on introduction circuit %d.", - circuit->_base.n_circ_id); + circuit->base_.n_circ_id); goto err; } service->desc_is_dirty = time(NULL); @@ -1706,7 +2556,7 @@ rend_service_intro_established(origin_circuit_t *circuit, circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN); log_info(LD_REND, "Received INTRO_ESTABLISHED cell on circuit %d for service %s", - circuit->_base.n_circ_id, serviceid); + circuit->base_.n_circ_id, serviceid); return 0; err: @@ -1727,13 +2577,18 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) char hexcookie[9]; int reason; - tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); tor_assert(circuit->cpath); tor_assert(circuit->build_state); #ifndef NON_ANONYMOUS_MODE_ENABLED tor_assert(!(circuit->build_state->onehop_tunnel)); #endif tor_assert(circuit->rend_data); + + /* Declare the circuit dirty to avoid reuse, and for path-bias */ + if (!circuit->base_.timestamp_dirty) + circuit->base_.timestamp_dirty = time(NULL); + hop = circuit->build_state->service_pending_final_cpath_ref->cpath; base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4); @@ -1743,7 +2598,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) log_info(LD_REND, "Done building circuit %d to rendezvous with " "cookie %s for service %s", - circuit->_base.n_circ_id, hexcookie, serviceid); + circuit->base_.n_circ_id, hexcookie, serviceid); /* Clear the 'in-progress HS circ has timed out' flag for * consistency with what happens on the client side; this line has @@ -1777,13 +2632,13 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */ memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN); - if (crypto_dh_get_public(hop->dh_handshake_state, + if (crypto_dh_get_public(hop->rend_dh_handshake_state, buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) { log_warn(LD_GENERAL,"Couldn't get DH public key."); reason = END_CIRC_REASON_INTERNAL; goto err; } - memcpy(buf+REND_COOKIE_LEN+DH_KEY_LEN, hop->handshake_digest, + memcpy(buf+REND_COOKIE_LEN+DH_KEY_LEN, hop->rend_circ_nonce, DIGEST_LEN); /* Send the cell */ @@ -1796,8 +2651,8 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) goto err; } - crypto_dh_free(hop->dh_handshake_state); - hop->dh_handshake_state = NULL; + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; /* Append the cpath entry. */ hop->state = CPATH_STATE_OPEN; @@ -1813,9 +2668,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: + memwipe(buf, 0, sizeof(buf)); + memwipe(serviceid, 0, sizeof(serviceid)); + memwipe(hexcookie, 0, sizeof(hexcookie)); + + return; } /* @@ -1876,7 +2738,7 @@ find_intro_point(origin_circuit_t *circ) if (service == NULL) return NULL; SMARTLIST_FOREACH(service->intro_nodes, rend_intro_point_t *, intro_point, - if (crypto_pk_cmp_keys(intro_point->intro_key, circ->intro_key) == 0) { + if (crypto_pk_eq_keys(intro_point->intro_key, circ->intro_key)) { return intro_point; }); @@ -1929,7 +2791,8 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, directory_initiate_command_routerstatus(hs_dir, DIR_PURPOSE_UPLOAD_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, - 1, NULL, desc->desc_str, + DIRIND_ANONYMOUS, NULL, + desc->desc_str, strlen(desc->desc_str), 0); base32_encode(desc_id_base32, sizeof(desc_id_base32), desc->desc_id, DIGEST_LEN); @@ -2091,11 +2954,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 @@ -2299,7 +3158,7 @@ rend_services_introduce(void) j < (int)n_intro_points_to_open; ++j) { /* XXXX remove casts */ router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; - if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION) + if (get_options()->AllowInvalid_ & ALLOW_INVALID_INTRODUCTION) flags |= CRN_ALLOW_INVALID; node = router_choose_random_node(intro_nodes, options->ExcludeNodes, flags); @@ -2449,7 +3308,7 @@ rend_service_dump_stats(int severity) continue; } log(severity, LD_GENERAL, " Intro point %d at %s: circuit is %s", - j, safe_name, circuit_state_to_string(circ->_base.state)); + j, safe_name, circuit_state_to_string(circ->base_.state)); } } } @@ -2468,7 +3327,7 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, smartlist_t *matching_ports; rend_service_port_config_t *chosen_port; - tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); log_debug(LD_REND,"beginning to hunt for addr/port"); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, @@ -2478,25 +3337,25 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, if (!service) { log_warn(LD_REND, "Couldn't find any service associated with pk %s on " "rendezvous circuit %d; closing.", - serviceid, circ->_base.n_circ_id); + serviceid, circ->base_.n_circ_id); return -1; } matching_ports = smartlist_new(); SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, { - if (conn->_base.port == p->virtual_port) { + if (conn->base_.port == p->virtual_port) { smartlist_add(matching_ports, p); } }); chosen_port = smartlist_choose(matching_ports); smartlist_free(matching_ports); if (chosen_port) { - tor_addr_copy(&conn->_base.addr, &chosen_port->real_addr); - conn->_base.port = chosen_port->real_port; + tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); + conn->base_.port = chosen_port->real_port; return 0; } log_info(LD_REND, "No virtual port mapping exists for port %d on service %s", - conn->_base.port,serviceid); + conn->base_.port,serviceid); return -1; } diff --git a/src/or/rendservice.h b/src/or/rendservice.h index e5848785a..167160234 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -9,12 +9,70 @@ * \brief Header file for rendservice.c. **/ -#ifndef _TOR_RENDSERVICE_H -#define _TOR_RENDSERVICE_H +#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 3b0d9dd35..e61e599d7 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -160,7 +160,7 @@ get_link_history(const char *from_id, const char *to_id) /** Helper: free storage held by a single link history entry. */ static void -_free_link_history(void *val) +free_link_history_(void *val) { rephist_total_alloc -= sizeof(link_history_t); tor_free(val); @@ -171,7 +171,7 @@ static void free_or_history(void *_hist) { or_history_t *hist = _hist; - digestmap_free(hist->link_history_map, _free_link_history); + digestmap_free(hist->link_history_map, free_link_history_); rephist_total_alloc -= sizeof(or_history_t); rephist_total_num--; tor_free(hist); @@ -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 @@ -2131,7 +2131,7 @@ rep_hist_exit_stats_term(void) * but works fine for sorting an array of port numbers, which is what we use * it for. */ static int -_compare_int(const void *x, const void *y) +compare_int_(const void *x, const void *y) { return (*(int*)x - *(int*)y); } @@ -2218,7 +2218,7 @@ rep_hist_format_exit_stats(time_t now) other_streams = total_streams; /* Sort the ports; this puts them out of sync with top_bytes, but we * won't be using top_bytes again anyway */ - qsort(top_ports, top_elements, sizeof(int), _compare_int); + qsort(top_ports, top_elements, sizeof(int), compare_int_); for (j = 0; j < top_elements; j++) { cur_port = top_ports[j]; if (exit_bytes_written[cur_port] > 0) { @@ -2440,7 +2440,7 @@ rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval) /** Sorting helper: return -1, 1, or 0 based on comparison of two * circ_buffer_stats_t */ static int -_buffer_stats_compare_entries(const void **_a, const void **_b) +buffer_stats_compare_entries_(const void **_a, const void **_b) { const circ_buffer_stats_t *a = *_a, *b = *_b; if (a->processed_cells < b->processed_cells) @@ -2505,7 +2505,7 @@ rep_hist_format_buffer_stats(time_t now) number_of_circuits = smartlist_len(circuits_for_buffer_stats); if (number_of_circuits > 0) { smartlist_sort(circuits_for_buffer_stats, - _buffer_stats_compare_entries); + buffer_stats_compare_entries_); i = 0; SMARTLIST_FOREACH_BEGIN(circuits_for_buffer_stats, circ_buffer_stats_t *, stat) @@ -2590,7 +2590,7 @@ rep_hist_buffer_stats_write(time_t now) goto done; /* Not ready to write */ /* Add open circuits to the history. */ - for (circ = _circuit_get_global_list(); circ; circ = circ->next) { + for (circ = circuit_get_global_list_(); circ; circ = circ->next) { rep_hist_buffer_stats_add_circ(circ, now); } @@ -3003,6 +3003,8 @@ rep_hist_free_all(void) digestmap_free(history_map, free_or_history); tor_free(read_array); tor_free(write_array); + tor_free(dir_read_array); + tor_free(dir_write_array); tor_free(last_stability_doc); tor_free(exit_bytes_read); tor_free(exit_bytes_written); diff --git a/src/or/rephist.h b/src/or/rephist.h index d47724edb..28dec8f90 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -9,8 +9,8 @@ * \brief Header file for rephist.c. **/ -#ifndef _TOR_REPHIST_H -#define _TOR_REPHIST_H +#ifndef TOR_REPHIST_H +#define TOR_REPHIST_H void rep_hist_init(void); void rep_hist_note_connect_failed(const char* nickname, time_t when); diff --git a/src/or/replaycache.c b/src/or/replaycache.c new file mode 100644 index 000000000..868b21c9b --- /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 000000000..757102b96 --- /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 38f1cdd49..29011c253 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -13,6 +13,7 @@ #include "config.h" #include "connection.h" #include "control.h" +#include "crypto_curve25519.h" #include "directory.h" #include "dirserv.h" #include "dns.h" @@ -27,6 +28,9 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "statefile.h" +#include "transports.h" +#include "routerset.h" /** * \file router.c @@ -51,6 +55,13 @@ static crypto_pk_t *onionkey=NULL; /** Previous private onionskin decryption key: used to decode CREATE cells * generated by clients that have an older version of our descriptor. */ static crypto_pk_t *lastonionkey=NULL; +#ifdef CURVE25519_ENABLED +/** Current private ntor secret key: used to perform the ntor handshake. */ +static curve25519_keypair_t curve25519_onion_key; +/** Previous private ntor secret key: used to perform the ntor handshake + * with clients that have an older version of our descriptor. */ +static curve25519_keypair_t last_curve25519_onion_key; +#endif /** Private server "identity key": used to sign directory info and TLS * certificates. Never changes. */ static crypto_pk_t *server_identitykey=NULL; @@ -84,7 +95,7 @@ static authority_cert_t *legacy_key_certificate = NULL; static void set_onion_key(crypto_pk_t *k) { - if (onionkey && !crypto_pk_cmp_keys(onionkey, k)) { + if (onionkey && crypto_pk_eq_keys(onionkey, k)) { /* k is already our onion key; free it and return */ crypto_pk_free(k); return; @@ -123,6 +134,55 @@ dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last) tor_mutex_release(key_lock); } +#ifdef CURVE25519_ENABLED +/** Return the current secret onion key for the ntor handshake. Must only + * be called from the main thread. */ +static const curve25519_keypair_t * +get_current_curve25519_keypair(void) +{ + return &curve25519_onion_key; +} +/** Return a map from KEYID (the key itself) to keypairs for use in the ntor + * handshake. Must only be called from the main thread. */ +di_digest256_map_t * +construct_ntor_key_map(void) +{ + di_digest256_map_t *m = NULL; + + dimap_add_entry(&m, + curve25519_onion_key.pubkey.public_key, + tor_memdup(&curve25519_onion_key, + sizeof(curve25519_keypair_t))); + if (!tor_mem_is_zero((const char*) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN)) { + dimap_add_entry(&m, + last_curve25519_onion_key.pubkey.public_key, + tor_memdup(&last_curve25519_onion_key, + sizeof(curve25519_keypair_t))); + } + + return m; +} +/** Helper used to deallocate a di_digest256_map_t returned by + * construct_ntor_key_map. */ +static void +ntor_key_map_free_helper(void *arg) +{ + curve25519_keypair_t *k = arg; + memwipe(k, 0, sizeof(*k)); + tor_free(k); +} +/** Release all storage from a keymap returned by construct_ntor_key_map. */ +void +ntor_key_map_free(di_digest256_map_t *map) +{ + if (!map) + return; + dimap_free(map, ntor_key_map_free_helper); +} +#endif + /** Return the time when the onion key was last set. This is either the time * when the process launched, or the time of the most recent key rotation since * the process launched. @@ -152,12 +212,11 @@ assert_identity_keys_ok(void) if (public_server_mode(get_options())) { /* assert that we have set the client and server keys to be equal */ tor_assert(server_identitykey); - tor_assert(0==crypto_pk_cmp_keys(client_identitykey, server_identitykey)); + tor_assert(crypto_pk_eq_keys(client_identitykey, server_identitykey)); } else { /* assert that we have set the client and server keys to be unequal */ if (server_identitykey) - tor_assert(0!=crypto_pk_cmp_keys(client_identitykey, - server_identitykey)); + tor_assert(!crypto_pk_eq_keys(client_identitykey, server_identitykey)); } } @@ -251,11 +310,18 @@ void rotate_onion_key(void) { char *fname, *fname_prev; - crypto_pk_t *prkey; + crypto_pk_t *prkey = NULL; or_state_t *state = get_or_state(); +#ifdef CURVE25519_ENABLED + curve25519_keypair_t new_curve25519_keypair; +#endif time_t now; fname = get_datadir_fname2("keys", "secret_onion_key"); fname_prev = get_datadir_fname2("keys", "secret_onion_key.old"); + if (file_status(fname) == FN_FILE) { + if (replace_file(fname, fname_prev)) + goto error; + } if (!(prkey = crypto_pk_new())) { log_err(LD_GENERAL,"Error constructing rotated onion key"); goto error; @@ -264,19 +330,38 @@ rotate_onion_key(void) log_err(LD_BUG,"Error generating onion key"); goto error; } + if (crypto_pk_write_private_key_to_filename(prkey, fname)) { + log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname); + goto error; + } +#ifdef CURVE25519_ENABLED + tor_free(fname); + tor_free(fname_prev); + fname = get_datadir_fname2("keys", "secret_onion_key_ntor"); + fname_prev = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); + if (curve25519_keypair_generate(&new_curve25519_keypair, 1) < 0) + goto error; if (file_status(fname) == FN_FILE) { if (replace_file(fname, fname_prev)) goto error; } - if (crypto_pk_write_private_key_to_filename(prkey, fname)) { - log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname); + if (curve25519_keypair_write_to_file(&new_curve25519_keypair, fname, + "onion") < 0) { + log_err(LD_FS,"Couldn't write curve25519 onion key to \"%s\".",fname); goto error; } +#endif log_info(LD_GENERAL, "Rotating onion key"); tor_mutex_acquire(key_lock); crypto_pk_free(lastonionkey); lastonionkey = onionkey; onionkey = prkey; +#ifdef CURVE25519_ENABLED + memcpy(&last_curve25519_onion_key, &curve25519_onion_key, + sizeof(curve25519_keypair_t)); + memcpy(&curve25519_onion_key, &new_curve25519_keypair, + sizeof(curve25519_keypair_t)); +#endif now = time(NULL); state->LastRotatedOnionKey = onionkey_set_at = now; tor_mutex_release(key_lock); @@ -288,6 +373,9 @@ rotate_onion_key(void) if (prkey) crypto_pk_free(prkey); done: +#ifdef CURVE25519_ENABLED + memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair)); +#endif tor_free(fname); tor_free(fname_prev); } @@ -361,6 +449,77 @@ init_key_from_file(const char *fname, int generate, int severity) return NULL; } +#ifdef CURVE25519_ENABLED +/** Load a curve25519 keypair from the file <b>fname</b>, writing it into + * <b>keys_out</b>. If the file isn't found and <b>generate</b> is true, + * create a new keypair and write it into the file. If there are errors, log + * them at level <b>severity</b>. Generate files using <b>tag</b> in their + * ASCII wrapper. */ +static int +init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out, + const char *fname, + int generate, + int severity, + const char *tag) +{ + switch (file_status(fname)) { + case FN_DIR: + case FN_ERROR: + log(severity, LD_FS,"Can't read key from \"%s\"", fname); + goto error; + case FN_NOENT: + if (generate) { + if (!have_lockfile()) { + if (try_locking(get_options(), 0)<0) { + /* Make sure that --list-fingerprint only creates new keys + * if there is no possibility for a deadlock. */ + log(severity, LD_FS, "Another Tor process has locked \"%s\". Not " + "writing any new keys.", fname); + /*XXXX The 'other process' might make a key in a second or two; + * maybe we should wait for it. */ + goto error; + } + } + log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.", + fname); + if (curve25519_keypair_generate(keys_out, 1) < 0) + goto error; + if (curve25519_keypair_write_to_file(keys_out, fname, tag)<0) { + log(severity, LD_FS, + "Couldn't write generated key to \"%s\".", fname); + memset(keys_out, 0, sizeof(*keys_out)); + goto error; + } + } else { + log_info(LD_GENERAL, "No key found in \"%s\"", fname); + } + return 0; + case FN_FILE: + { + char *tag_in=NULL; + if (curve25519_keypair_read_from_file(keys_out, &tag_in, fname) < 0) { + log(severity, LD_GENERAL,"Error loading private key."); + tor_free(tag_in); + goto error; + } + if (!tag_in || strcmp(tag_in, tag)) { + log(severity, LD_GENERAL,"Unexpected tag %s on private key.", + escaped(tag_in)); + tor_free(tag_in); + goto error; + } + tor_free(tag_in); + return 0; + } + default: + tor_assert(0); + } + + error: + return -1; +} +#endif + /** Try to load the vote-signing private key and certificate for being a v3 * directory authority, and make sure they match. If <b>legacy</b>, load a * legacy key/cert set for emergency key migration; otherwise load the regular @@ -397,7 +556,7 @@ load_authority_keyset(int legacy, crypto_pk_t **key_out, log_warn(LD_DIR, "Unable to parse certificate in %s", fname); goto done; } - if (crypto_pk_cmp_keys(signing_key, parsed->signing_key) != 0) { + if (!crypto_pk_eq_keys(signing_key, parsed->signing_key)) { log_warn(LD_DIR, "Stored signing key does not match signing key in " "certificate"); goto done; @@ -489,7 +648,18 @@ v3_authority_check_key_expiry(void) int router_initialize_tls_context(void) { - return tor_tls_context_init(public_server_mode(get_options()), + unsigned int flags = 0; + const or_options_t *options = get_options(); + if (public_server_mode(options)) + flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER; + if (options->TLSECGroup) { + if (!strcasecmp(options->TLSECGroup, "P256")) + flags |= TOR_TLS_CTX_USE_ECDHE_P256; + else if (!strcasecmp(options->TLSECGroup, "P224")) + flags |= TOR_TLS_CTX_USE_ECDHE_P224; + } + + return tor_tls_context_init(flags, get_tlsclient_identity_key(), server_mode(get_options()) ? get_server_identity_key() : NULL, @@ -515,7 +685,7 @@ init_keys(void) const or_options_t *options = get_options(); dirinfo_type_t type; time_t now = time(NULL); - trusted_dir_server_t *ds; + dir_server_t *ds; int v3_digest_set = 0; authority_cert_t *cert = NULL; @@ -628,12 +798,35 @@ init_keys(void) keydir = get_datadir_fname2("keys", "secret_onion_key.old"); if (!lastonionkey && file_status(keydir) == FN_FILE) { - prkey = init_key_from_file(keydir, 1, LOG_ERR); + prkey = init_key_from_file(keydir, 1, LOG_ERR); /* XXXX Why 1? */ if (prkey) lastonionkey = prkey; } tor_free(keydir); +#ifdef CURVE25519_ENABLED + { + /* 2b. Load curve25519 onion keys. */ + int r; + keydir = get_datadir_fname2("keys", "secret_onion_key_ntor"); + r = init_curve25519_keypair_from_file(&curve25519_onion_key, + keydir, 1, LOG_ERR, "onion"); + tor_free(keydir); + if (r<0) + return -1; + + keydir = get_datadir_fname2("keys", "secret_onion_key_ntor.old"); + if (tor_mem_is_zero((const char *) + last_curve25519_onion_key.pubkey.public_key, + CURVE25519_PUBKEY_LEN) && + file_status(keydir) == FN_FILE) { + init_curve25519_keypair_from_file(&last_curve25519_onion_key, + keydir, 0, LOG_ERR, "onion"); + } + tor_free(keydir); + } +#endif + /* 3. Initialize link key and TLS context. */ if (router_initialize_tls_context() < 0) { log_err(LD_GENERAL,"Error initializing TLS context"); @@ -671,7 +864,7 @@ init_keys(void) * we don't really need new keys yet so the descriptor doesn't * change and the old one is still fresh. */ log_info(LD_GENERAL, "Couldn't add own descriptor to directory " - "after key init: %s. This is usually not a problem.", + "after key init: %s This is usually not a problem.", m?m:"<unknown error>"); } } @@ -730,17 +923,18 @@ init_keys(void) ds = router_get_trusteddirserver_by_digest(digest); if (!ds) { - ds = add_trusted_dir_server(options->Nickname, NULL, + ds = trusted_dir_server_new(options->Nickname, NULL, router_get_advertised_dir_port(options, 0), router_get_advertised_or_port(options), digest, v3_digest, - type); + type, 0.0); if (!ds) { log_err(LD_GENERAL,"We want to be a directory authority, but we " "couldn't add ourselves to the authority list. Failing."); return -1; } + dir_server_add(ds); } if (ds->type != type) { log_warn(LD_DIR, "Configured authority type does not match authority " @@ -879,6 +1073,22 @@ 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_new(r->nickname, r->cache_info.identity_digest, + r->onion_pkey, r->onion_curve25519_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 +1130,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); @@ -939,11 +1148,10 @@ consider_testing_reachability(int test_or, int test_dir) /* ask myself, via tor, for my server descriptor. */ directory_initiate_command(me->address, &addr, me->or_port, me->dir_port, - 0, /* does not matter */ - 0, me->cache_info.identity_digest, + me->cache_info.identity_digest, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_GENERAL, - 1, "authority.z", NULL, 0, 0); + DIRIND_ANON_DIRPORT, "authority.z", NULL, 0, 0); } } @@ -955,7 +1163,7 @@ router_orport_found_reachable(void) if (!can_reach_or_port && me) { log_notice(LD_OR,"Self-testing indicates your ORPort is reachable from " "the outside. Excellent.%s", - get_options()->_PublishServerDescriptor != NO_DIRINFO ? + get_options()->PublishServerDescriptor_ != NO_DIRINFO ? " Publishing server descriptor." : ""); can_reach_or_port = 1; mark_my_descriptor_dirty("ORPort found reachable"); @@ -998,9 +1206,9 @@ router_perform_bandwidth_test(int num_circs, time_t now) CIRCUIT_PURPOSE_TESTING))) { /* dump cells_per_circuit drop cells onto this circ */ int i = cells_per_circuit; - if (circ->_base.state != CIRCUIT_STATE_OPEN) + if (circ->base_.state != CIRCUIT_STATE_OPEN) continue; - circ->_base.timestamp_dirty = now; + circ->base_.timestamp_dirty = now; while (i-- > 0) { if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), RELAY_COMMAND_DROP, @@ -1194,7 +1402,7 @@ decide_if_publishable_server(void) if (options->ClientOnly) return 0; - if (options->_PublishServerDescriptor == NO_DIRINFO) + if (options->PublishServerDescriptor_ == NO_DIRINFO) return 0; if (!server_mode(options)) return 0; @@ -1315,7 +1523,7 @@ router_upload_dir_desc_to_dirservers(int force) extrainfo_t *ei; char *msg; size_t desc_len, extra_len = 0, total_len; - dirinfo_type_t auth = get_options()->_PublishServerDescriptor; + dirinfo_type_t auth = get_options()->PublishServerDescriptor_; ri = router_get_my_routerinfo(); if (!ri) { @@ -1355,22 +1563,34 @@ router_upload_dir_desc_to_dirservers(int force) * conn. Return 0 if we accept; non-0 if we reject. */ int -router_compare_to_my_exit_policy(edge_connection_t *conn) +router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) { if (!router_get_my_routerinfo()) /* make sure desc_routerinfo exists */ return -1; /* make sure it's resolved to something. this way we can't get a 'maybe' below. */ - if (tor_addr_is_null(&conn->_base.addr)) + if (tor_addr_is_null(addr)) return -1; - /* XXXX IPv6 */ - if (tor_addr_family(&conn->_base.addr) != AF_INET) + /* look at desc_routerinfo->exit_policy for both the v4 and the v6 + * policies. The exit_policy field in desc_routerinfo is a bit unusual, + * in that it contains IPv6 and IPv6 entries. We don't want to look + * at desc_routerinfio->ipv6_exit_policy, since that's a port summary. */ + if ((tor_addr_family(addr) == AF_INET || + tor_addr_family(addr) == AF_INET6)) { + return compare_tor_addr_to_addr_policy(addr, port, + desc_routerinfo->exit_policy) != ADDR_POLICY_ACCEPTED; +#if 0 + } else if (tor_addr_family(addr) == AF_INET6) { + return get_options()->IPv6Exit && + desc_routerinfo->ipv6_exit_policy && + compare_tor_addr_to_short_policy(addr, port, + desc_routerinfo->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED; +#endif + } else { return -1; - - return compare_tor_addr_to_addr_policy(&conn->_base.addr, conn->_base.port, - desc_routerinfo->exit_policy) != ADDR_POLICY_ACCEPTED; + } } /** Return true iff my exit policy is reject *:*. Return -1 if we don't @@ -1393,6 +1613,13 @@ router_digest_is_me(const char *digest) tor_memeq(server_identitykey_digest, digest, DIGEST_LEN)); } +/** Return my identity digest. */ +const uint8_t * +router_get_my_id_digest(void) +{ + return (const uint8_t *)server_identitykey_digest; +} + /** Return true iff I'm a server and <b>digest</b> is equal to * my identity digest. */ int @@ -1539,13 +1766,19 @@ 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. */ +#ifdef CURVE25519_ENABLED + ri->onion_curve25519_pkey = + tor_memdup(&get_current_curve25519_keypair()->pubkey, + sizeof(curve25519_public_key_t)); +#endif + + /* 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 && ! p->no_advertise && - ! p->ipv4_only && + ! p->bind_ipv4_only && tor_addr_family(&p->addr) == AF_INET6) { if (! tor_addr_is_internal(&p->addr, 0)) { ipv6_orport = p; @@ -1565,6 +1798,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) { @@ -1587,11 +1821,20 @@ router_rebuild_descriptor(int force) policies_exit_policy_append_reject_star(&ri->exit_policy); } else { policies_parse_exit_policy(options->ExitPolicy, &ri->exit_policy, + options->IPv6Exit, options->ExitPolicyRejectPrivate, ri->address, !options->BridgeRelay); } ri->policy_is_reject_star = - policy_is_reject_star(ri->exit_policy); + policy_is_reject_star(ri->exit_policy, AF_INET) && + policy_is_reject_star(ri->exit_policy, AF_INET6); + + if (options->IPv6Exit) { + char *p_tmp = policy_summarize(ri->exit_policy, AF_INET6); + if (p_tmp) + ri->ipv6_exit_policy = parse_short_policy(p_tmp); + tor_free(p_tmp); + } #if 0 /* XXXX NM NM I belive this is safe to remove */ @@ -1779,7 +2022,7 @@ void mark_my_descriptor_dirty(const char *reason) { const or_options_t *options = get_options(); - if (server_mode(options) && options->_PublishServerDescriptor) + if (server_mode(options) && options->PublishServerDescriptor_) log_info(LD_OR, "Decided to publish new relay descriptor: %s", reason); desc_clean_since = 0; if (!desc_dirty_reason) @@ -1911,7 +2154,7 @@ router_new_address_suggestion(const char *suggestion, /* Don't believe anybody who says our IP is, say, 127.0.0.1. */ return; } - if (tor_addr_eq(&d_conn->_base.addr, &addr)) { + if (tor_addr_eq(&d_conn->base_.addr, &addr)) { /* Don't believe anybody who says our IP is their IP. */ log_debug(LD_DIR, "A directory server told us our IP address is %s, " "but he's just reporting his own IP address. Ignoring.", @@ -1927,7 +2170,7 @@ router_new_address_suggestion(const char *suggestion, "EXTERNAL_ADDRESS ADDRESS=%s METHOD=DIRSERV", suggestion); log_addr_has_changed(LOG_NOTICE, &last_guessed_ip, &addr, - d_conn->_base.address); + d_conn->base_.address); ip_address_changed(0); tor_addr_copy(&last_guessed_ip, &addr); /* router_rebuild_descriptor() will fetch it */ @@ -1984,13 +2227,12 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, size_t onion_pkeylen, identity_pkeylen; size_t written; int result=0; - addr_policy_t *tmpe; char *family_line; char *extra_or_address = NULL; const or_options_t *options = get_options(); /* Make sure the identity key matches the one in the routerinfo. */ - if (crypto_pk_cmp_keys(ident_key, router->identity_pkey)) { + if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) { log_warn(LD_BUG,"Tried to sign a router with a private key that didn't " "match router's public key!"); return -1; @@ -2054,9 +2296,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 +2317,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); @@ -2109,15 +2351,32 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, written += result; } +#ifdef CURVE25519_ENABLED + if (router->onion_curve25519_pkey) { + char kbuf[128]; + base64_encode(kbuf, sizeof(kbuf), + (const char *)router->onion_curve25519_pkey->public_key, + CURVE25519_PUBKEY_LEN); + result = tor_snprintf(s+written,maxlen-written, "ntor-onion-key %s", + kbuf); + if (result<0) { + log_warn(LD_BUG,"descriptor snprintf ran out of room!"); + return -1; + } + written += result; + } +#endif + /* Write the exit policy to the end of 's'. */ if (!router->exit_policy || !smartlist_len(router->exit_policy)) { strlcat(s+written, "reject *:*\n", maxlen-written); written += strlen("reject *:*\n"); - tmpe = NULL; } else if (router->exit_policy) { int i; for (i = 0; i < smartlist_len(router->exit_policy); ++i) { - tmpe = smartlist_get(router->exit_policy, i); + addr_policy_t *tmpe = smartlist_get(router->exit_policy, i); + if (tor_addr_family(&tmpe->addr) == AF_INET6) + continue; /* Don't include IPv6 parts of address policy */ result = policy_write_item(s+written, maxlen-written, tmpe, 1); if (result < 0) { log_warn(LD_BUG,"descriptor policy_write_item ran out of room!"); @@ -2133,6 +2392,20 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, } } + if (router->ipv6_exit_policy) { + char *p6 = write_short_policy(router->ipv6_exit_policy); + if (p6 && strcmp(p6, "reject 1-65535")) { + result = tor_snprintf(s+written, maxlen-written, + "ipv6-policy %s\n", p6); + if (result<0) { + log_warn(LD_BUG,"Descriptor printf of policy ran out of room"); + return -1; + } + written += result; + } + tor_free(p6); + } + if (written + DIROBJ_MAX_SIG_LEN > maxlen) { /* Not enough room for signature. */ log_warn(LD_BUG,"not enough room left in descriptor for signature!"); @@ -2194,40 +2467,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)); + return + tor_addr_eq_ipv4h(addr, router->addr) || + tor_addr_eq(&router->ipv6_addr, addr); } -/** 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); -} - -/** 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 @@ -2312,9 +2569,12 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, tor_free(bandwidth_usage); smartlist_add(chunks, pre); - if (geoip_is_loaded()) { - smartlist_add_asprintf(chunks, "geoip-db-digest %s\n", geoip_db_digest()); - } + if (geoip_is_loaded(AF_INET)) + smartlist_add_asprintf(chunks, "geoip-db-digest %s\n", + geoip_db_digest(AF_INET)); + if (geoip_is_loaded(AF_INET6)) + smartlist_add_asprintf(chunks, "geoip6-db-digest %s\n", + geoip_db_digest(AF_INET6)); if (options->ExtraInfoStatistics && write_stats_to_extrainfo) { log_info(LD_GENERAL, "Adding stats to extra-info descriptor."); @@ -2345,6 +2605,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) { @@ -2748,9 +3015,41 @@ router_free_all(void) crypto_pk_free(legacy_signing_key); authority_cert_free(legacy_key_certificate); +#ifdef CURVE25519_ENABLED + memwipe(&curve25519_onion_key, 0, sizeof(curve25519_onion_key)); + memwipe(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key)); +#endif + if (warned_nonexistent_family) { SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp)); smartlist_free(warned_nonexistent_family); } } +/** 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 69805d6f2..ea0b2ab4e 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -9,8 +9,8 @@ * \brief Header file for router.c. **/ -#ifndef _TOR_ROUTER_H -#define _TOR_ROUTER_H +#ifndef TOR_ROUTER_H +#define TOR_ROUTER_H crypto_pk_t *get_onion_key(void); time_t get_onion_key_set_at(void); @@ -30,6 +30,11 @@ crypto_pk_t *init_key_from_file(const char *fname, int generate, int severity); void v3_authority_check_key_expiry(void); +#ifdef CURVE25519_ENABLED +di_digest256_map_t *construct_ntor_key_map(void); +void ntor_key_map_free(di_digest256_map_t *map); +#endif + int router_initialize_tls_context(void); int init_keys(void); @@ -72,13 +77,14 @@ void check_descriptor_bandwidth_changed(time_t now); void check_descriptor_ipaddress_changed(time_t now); void router_new_address_suggestion(const char *suggestion, const dir_connection_t *d_conn); -int router_compare_to_my_exit_policy(edge_connection_t *conn); +int router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port); int router_my_exit_policy_is_reject_star(void); const routerinfo_t *router_get_my_routerinfo(void); extrainfo_t *router_get_my_extrainfo(void); const char *router_get_my_descriptor(void); const char *router_get_descriptor_gen_reason(void); int router_digest_is_me(const char *digest); +const uint8_t *router_get_my_id_digest(void); int router_extrainfo_digest_is_me(const char *digest); int router_is_me(const routerinfo_t *router); int router_fingerprint_is_me(const char *fp); @@ -93,6 +99,9 @@ void router_get_pref_orport(const routerinfo_t *router, 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 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 extrainfo_dump_to_string(char **s, extrainfo_t *extrainfo, crypto_pk_t *ident_key); int is_legal_nickname(const char *s); @@ -132,6 +141,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 3c39e362d..a597c6bfc 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -11,14 +11,16 @@ * servers. **/ +#define ROUTERLIST_PRIVATE #include "or.h" -#include "circuitbuild.h" +#include "circuitstats.h" #include "config.h" #include "connection.h" #include "control.h" #include "directory.h" #include "dirserv.h" #include "dirvote.h" +#include "entrynodes.h" #include "geoip.h" #include "hibernate.h" #include "main.h" @@ -33,6 +35,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "routerset.h" // #define DEBUG_ROUTERLIST @@ -42,15 +45,16 @@ static const routerstatus_t *router_pick_directory_server_impl( dirinfo_type_t auth, int flags); static const routerstatus_t *router_pick_trusteddirserver_impl( - dirinfo_type_t auth, int flags, int *n_busy_out); -static void mark_all_trusteddirservers_up(void); + const smartlist_t *sourcelist, dirinfo_type_t auth, + int flags, int *n_busy_out); +static const routerstatus_t *router_pick_dirserver_generic( + smartlist_t *sourcelist, + dirinfo_type_t type, int flags); +static void mark_all_dirservers_up(smartlist_t *server_list); static int router_nickname_matches(const routerinfo_t *router, const char *nickname); -static int node_nickname_matches(const node_t *router, - const char *nickname); -static void trusted_dir_server_free(trusted_dir_server_t *ds); +static void dir_server_free(dir_server_t *ds); static int signed_desc_digest_is_recognized(signed_descriptor_t *desc); -static void update_router_have_minimum_dir_info(void); static const char *signed_descriptor_get_body_impl( const signed_descriptor_t *desc, int with_annotations); @@ -72,9 +76,12 @@ DECLARE_TYPED_DIGESTMAP_FNS(eimap_, digest_ei_map_t, extrainfo_t) /****************************************************************************/ -/** Global list of a trusted_dir_server_t object for each trusted directory - * server. */ +/** Global list of a dir_server_t object for each directory + * authority. */ static smartlist_t *trusted_dir_servers = NULL; +/** Global list of dir_server_t objects for all directory authorities + * and all fallback directory servers. */ +static smartlist_t *fallback_dir_servers = NULL; /** List of for a given authority, and download status for latest certificate. */ @@ -119,7 +126,7 @@ get_n_authorities(dirinfo_type_t type) int n = 0; if (!trusted_dir_servers) return 0; - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ds, if (ds->type & type) ++n); return n; @@ -190,7 +197,7 @@ int trusted_dirs_load_certs_from_string(const char *contents, int from_store, int flush) { - trusted_dir_server_t *ds; + dir_server_t *ds; const char *s, *eos; int failure_code = 0; @@ -530,7 +537,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } SMARTLIST_FOREACH_END(sig); } SMARTLIST_FOREACH_END(voter); } - SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) { + SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, dir_server_t *, ds) { int found = 0; if (!(ds->type & V3_DIRINFO)) continue; @@ -655,7 +662,7 @@ signed_desc_append_to_journal(signed_descriptor_t *desc, * signed_descriptor_t* in *<b>a</b> is older, the same age as, or newer than * the signed_descriptor_t* in *<b>b</b>. */ static int -_compare_signed_descriptors_by_age(const void **_a, const void **_b) +compare_signed_descriptors_by_age_(const void **_a, const void **_b) { const signed_descriptor_t *r1 = *_a, *r2 = *_b; return (int)(r1->published_on - r2->published_on); @@ -728,7 +735,7 @@ router_rebuild_store(int flags, desc_store_t *store) smartlist_add(signed_descriptors, &ri->cache_info)); } - smartlist_sort(signed_descriptors, _compare_signed_descriptors_by_age); + smartlist_sort(signed_descriptors, compare_signed_descriptors_by_age_); /* Now, add the appropriate members to chunk_list */ SMARTLIST_FOREACH_BEGIN(signed_descriptors, signed_descriptor_t *, sd) { @@ -914,11 +921,11 @@ router_reload_router_list(void) return 0; } -/** Return a smartlist containing a list of trusted_dir_server_t * for all +/** Return a smartlist containing a list of dir_server_t * for all * known trusted dirservers. Callers must not modify the list or its * contents. */ -smartlist_t * +const smartlist_t * router_get_trusted_dir_servers(void) { if (!trusted_dir_servers) @@ -927,6 +934,15 @@ router_get_trusted_dir_servers(void) return trusted_dir_servers; } +const smartlist_t * +router_get_fallback_dir_servers(void) +{ + if (!fallback_dir_servers) + fallback_dir_servers = smartlist_new(); + + return fallback_dir_servers; +} + /** Try to find a running dirserver that supports operations of <b>type</b>. * * If there are no running dirservers in our routerlist and the @@ -947,7 +963,7 @@ router_pick_directory_server(dirinfo_type_t type, int flags) { const routerstatus_t *choice; if (get_options()->PreferTunneledDirConns) - flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; + flags |= PDS_PREFER_TUNNELED_DIR_CONNS_; if (!routerlist) return NULL; @@ -960,7 +976,7 @@ router_pick_directory_server(dirinfo_type_t type, int flags) "No reachable router entries for dirservers. " "Trying them all again."); /* mark all authdirservers as up again */ - mark_all_trusteddirservers_up(); + mark_all_dirservers_up(fallback_dir_servers); /* try again */ choice = router_pick_directory_server_impl(type, flags); return choice; @@ -995,7 +1011,7 @@ router_get_my_share_of_directory_requests(double *v2_share_out, } } - if (rs->version_supports_v3_dir) { + { sl_last_total_weighted_bw = 0; router_pick_directory_server(V3_DIRINFO, pds_flags); if (sl_last_total_weighted_bw != 0) { @@ -1007,16 +1023,34 @@ router_get_my_share_of_directory_requests(double *v2_share_out, return 0; } -/** Return the trusted_dir_server_t for the directory authority whose identity +/** Return the dir_server_t for the directory authority whose identity * key hashes to <b>digest</b>, or NULL if no such authority is known. */ -trusted_dir_server_t * +dir_server_t * router_get_trusteddirserver_by_digest(const char *digest) { if (!trusted_dir_servers) return NULL; - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ds, + { + if (tor_memeq(ds->digest, digest, DIGEST_LEN)) + return ds; + }); + + return NULL; +} + +/** Return the dir_server_t for the fallback dirserver whose identity + * key hashes to <b>digest</b>, or NULL if no such authority is known. + */ +dir_server_t * +router_get_fallback_dirserver_by_digest(const char *digest) +{ + if (!trusted_dir_servers) + return NULL; + + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ds, { if (tor_memeq(ds->digest, digest, DIGEST_LEN)) return ds; @@ -1025,17 +1059,17 @@ router_get_trusteddirserver_by_digest(const char *digest) return NULL; } -/** Return the trusted_dir_server_t for the directory authority whose +/** Return the dir_server_t for the directory authority whose * v3 identity key hashes to <b>digest</b>, or NULL if no such authority * is known. */ -trusted_dir_server_t * +dir_server_t * trusteddirserver_get_by_v3_auth_digest(const char *digest) { if (!trusted_dir_servers) return NULL; - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ds, { if (tor_memeq(ds->v3_identity_digest, digest, DIGEST_LEN) && (ds->type & V3_DIRINFO)) @@ -1045,18 +1079,37 @@ trusteddirserver_get_by_v3_auth_digest(const char *digest) return NULL; } -/** Try to find a running trusted dirserver. Flags are as for +/** Try to find a running directory authority. Flags are as for * router_pick_directory_server. */ const routerstatus_t * router_pick_trusteddirserver(dirinfo_type_t type, int flags) { + return router_pick_dirserver_generic(trusted_dir_servers, type, flags); +} + +/** Try to find a running fallback directory Flags are as for + * router_pick_directory_server. + */ +const routerstatus_t * +router_pick_fallback_dirserver(dirinfo_type_t type, int flags) +{ + return router_pick_dirserver_generic(fallback_dir_servers, type, flags); +} + +/** Try to find a running fallback directory Flags are as for + * router_pick_directory_server. + */ +static const routerstatus_t * +router_pick_dirserver_generic(smartlist_t *sourcelist, + dirinfo_type_t type, int flags) +{ const routerstatus_t *choice; int busy = 0; if (get_options()->PreferTunneledDirConns) - flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; + flags |= PDS_PREFER_TUNNELED_DIR_CONNS_; - choice = router_pick_trusteddirserver_impl(type, flags, &busy); + choice = router_pick_trusteddirserver_impl(sourcelist, type, flags, &busy); if (choice || !(flags & PDS_RETRY_IF_NO_SERVERS)) return choice; if (busy) { @@ -1069,9 +1122,9 @@ router_pick_trusteddirserver(dirinfo_type_t type, int flags) } log_info(LD_DIR, - "No trusted dirservers are reachable. Trying them all again."); - mark_all_trusteddirservers_up(); - return router_pick_trusteddirserver_impl(type, flags, NULL); + "No dirservers are reachable. Trying them all again."); + mark_all_dirservers_up(sourcelist); + return router_pick_trusteddirserver_impl(sourcelist, type, flags, NULL); } /** How long do we avoid using a directory server after it's given us a 503? */ @@ -1081,7 +1134,7 @@ router_pick_trusteddirserver(dirinfo_type_t type, int flags) * routerlist. Arguments are as for router_pick_directory_server(), except * that RETRY_IF_NO_SERVERS is ignored, and: * - * If the _PDS_PREFER_TUNNELED_DIR_CONNS flag is set, prefer directory servers + * If the PDS_PREFER_TUNNELED_DIR_CONNS_ flag is set, prefer directory servers * that we can use with BEGINDIR. */ static const routerstatus_t * @@ -1096,7 +1149,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) const networkstatus_t *consensus = networkstatus_get_latest_consensus(); int requireother = ! (flags & PDS_ALLOW_SELF); int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); - int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS); + int prefer_tunnel = (flags & PDS_PREFER_TUNNELED_DIR_CONNS_); int try_excluding = 1, n_excluded = 0; if (!consensus) @@ -1127,12 +1180,6 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) continue; if (requireother && router_digest_is_me(node->identity)) continue; - if (type & V3_DIRINFO) { - if (!(status->version_supports_v3_dir || - router_digest_is_trusted_dir_type(node->identity, - V3_DIRINFO))) - continue; - } is_trusted = router_digest_is_trusted_dir(node->identity); if ((type & V2_DIRINFO) && !(node->rs->is_v2_dir || is_trusted)) continue; @@ -1155,7 +1202,6 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; if (prefer_tunnel && - status->version_supports_begindir && (!fascistfirewall || fascist_firewall_allows_address_or(&addr, status->or_port))) smartlist_add(is_trusted ? trusted_tunnel : @@ -1203,28 +1249,56 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags) return result ? result->rs : NULL; } -/** Choose randomly from among the trusted dirservers that are up. Flags - * are as for router_pick_directory_server_impl(). +/** Pick a random element from a list of dir_server_t, weighting by their + * <b>weight</b> field. */ +static const dir_server_t * +dirserver_choose_by_weight(const smartlist_t *servers, double authority_weight) +{ + int n = smartlist_len(servers); + int i; + u64_dbl_t *weights; + const dir_server_t *ds; + + weights = tor_malloc(sizeof(u64_dbl_t) * n); + for (i = 0; i < n; ++i) { + ds = smartlist_get(servers, i); + weights[i].dbl = ds->weight; + if (ds->is_authority) + weights[i].dbl *= authority_weight; + } + + scale_array_elements_to_u64(weights, n, NULL); + i = choose_array_element_by_weight(weights, n); + tor_free(weights); + return (i < 0) ? NULL : smartlist_get(servers, i); +} + +/** Choose randomly from among the dir_server_ts in sourcelist that + * are up. Flags are as for router_pick_directory_server_impl(). */ static const routerstatus_t * -router_pick_trusteddirserver_impl(dirinfo_type_t type, int flags, +router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, + dirinfo_type_t type, int flags, int *n_busy_out) { const or_options_t *options = get_options(); smartlist_t *direct, *tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; const routerinfo_t *me = router_get_my_routerinfo(); - const routerstatus_t *result; + const routerstatus_t *result = NULL; time_t now = time(NULL); const int requireother = ! (flags & PDS_ALLOW_SELF); const int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL); - const int prefer_tunnel = (flags & _PDS_PREFER_TUNNELED_DIR_CONNS); + const int prefer_tunnel = (flags & PDS_PREFER_TUNNELED_DIR_CONNS_); const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH); const int no_microdesc_fetching =(flags & PDS_NO_EXISTING_MICRODESC_FETCH); + const double auth_weight = (sourcelist == fallback_dir_servers) ? + options->DirAuthorityFallbackRate : 1.0; + smartlist_t *pick_from; int n_busy = 0; int try_excluding = 1, n_excluded = 0; - if (!trusted_dir_servers) + if (!sourcelist) return NULL; retry_without_exclude: @@ -1234,7 +1308,7 @@ router_pick_trusteddirserver_impl(dirinfo_type_t type, int flags, overloaded_direct = smartlist_new(); overloaded_tunnel = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, d) + SMARTLIST_FOREACH_BEGIN(sourcelist, const dir_server_t *, d) { int is_overloaded = d->fake_status.last_dir_503_at + DIR_503_TIMEOUT > now; @@ -1280,23 +1354,29 @@ router_pick_trusteddirserver_impl(dirinfo_type_t type, int flags, d->or_port && (!fascistfirewall || fascist_firewall_allows_address_or(&addr, d->or_port))) - smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, - &d->fake_status); + smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, (void*)d); else if (!fascistfirewall || fascist_firewall_allows_address_dir(&addr, d->dir_port)) - smartlist_add(is_overloaded ? overloaded_direct : direct, - &d->fake_status); + smartlist_add(is_overloaded ? overloaded_direct : direct, (void*)d); } SMARTLIST_FOREACH_END(d); if (smartlist_len(tunnel)) { - result = smartlist_choose(tunnel); + pick_from = tunnel; } else if (smartlist_len(overloaded_tunnel)) { - result = smartlist_choose(overloaded_tunnel); + pick_from = overloaded_tunnel; } else if (smartlist_len(direct)) { - result = smartlist_choose(direct); + pick_from = direct; } else { - result = smartlist_choose(overloaded_direct); + pick_from = overloaded_direct; + } + + { + const dir_server_t *selection = + dirserver_choose_by_weight(pick_from, auth_weight); + + if (selection) + result = &selection->fake_status; } if (n_busy_out) @@ -1318,19 +1398,19 @@ router_pick_trusteddirserver_impl(dirinfo_type_t type, int flags, return result; } -/** Go through and mark the authoritative dirservers as up. */ +/** Mark as running every dir_server_t in <b>server_list</b>. */ static void -mark_all_trusteddirservers_up(void) +mark_all_dirservers_up(smartlist_t *server_list) { - SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, { - if (router_digest_is_trusted_dir(node->identity)) - node->is_running = 1; - }); - if (trusted_dir_servers) { - SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, dir) { + if (server_list) { + SMARTLIST_FOREACH_BEGIN(server_list, dir_server_t *, dir) { routerstatus_t *rs; + node_t *node; dir->is_running = 1; download_status_reset(&dir->v2_ns_dl_status); + node = node_get_mutable_by_id(dir->digest); + if (node) + node->is_running = 1; rs = router_get_mutable_consensus_status_by_id(dir->digest); if (rs) { rs->last_dir_503_at = 0; @@ -1343,9 +1423,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 @@ -1353,89 +1435,7 @@ routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2) void router_reset_status_download_failures(void) { - mark_all_trusteddirservers_up(); -} - -/** Return true iff router1 and router2 have similar enough network addresses - * that we should treat them as being in the same family */ -static INLINE int -addrs_in_same_network_family(const tor_addr_t *a1, - const tor_addr_t *a2) -{ - /* XXXX MOVE ? */ - return 0 == tor_addr_compare_masked(a1, a2, 16, CMP_SEMANTIC); -} - -/** - * Add all the family of <b>node</b>, including <b>node</b> itself, to - * the smartlist <b>sl</b>. - * - * This is used to make sure we don't pick siblings in a single path, or - * pick more than one relay from a family for our entry guard list. - * Note that a node may be added to <b>sl</b> more than once if it is - * part of <b>node</b>'s family for more than one reason. - */ -void -nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) -{ - /* XXXX MOVE */ - const smartlist_t *all_nodes = nodelist_get_list(); - const smartlist_t *declared_family; - const or_options_t *options = get_options(); - - tor_assert(node); - - declared_family = node_get_declared_family(node); - - /* Let's make sure that we have the node itself, if it's a real node. */ - { - const node_t *real_node = node_get_by_id(node->identity); - if (real_node) - smartlist_add(sl, (node_t*)real_node); - } - - /* First, add any nodes with similar network addresses. */ - if (options->EnforceDistinctSubnets) { - tor_addr_t node_addr; - node_get_addr(node, &node_addr); - - SMARTLIST_FOREACH_BEGIN(all_nodes, const node_t *, node2) { - tor_addr_t a; - node_get_addr(node2, &a); - if (addrs_in_same_network_family(&a, &node_addr)) - smartlist_add(sl, (void*)node2); - } SMARTLIST_FOREACH_END(node2); - } - - /* Now, add all nodes in the declared_family of this node, if they - * also declare this node to be in their family. */ - if (declared_family) { - /* Add every r such that router declares familyness with node, and node - * declares familyhood with router. */ - SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { - const node_t *node2; - const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, 0))) - continue; - if (!(family2 = node_get_declared_family(node2))) - continue; - SMARTLIST_FOREACH_BEGIN(family2, const char *, name2) { - if (node_nickname_matches(node, name2)) { - smartlist_add(sl, (void*)node2); - break; - } - } SMARTLIST_FOREACH_END(name2); - } SMARTLIST_FOREACH_END(name); - } - - /* If the user declared any families locally, honor those too. */ - if (options->NodeFamilySets) { - SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { - if (routerset_contains_node(rs, node)) { - routerset_get_all_nodes(sl, rs, NULL, 0); - } - }); - } + mark_all_dirservers_up(fallback_dir_servers); } /** Given a <b>router</b>, add every node_t in its family (including the @@ -1459,59 +1459,6 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) nodelist_add_node_and_family(sl, node); } -/** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ -static INLINE int -node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) -{ - /* XXXX MOVE */ - if (!lst) return 0; - SMARTLIST_FOREACH(lst, const char *, name, { - if (node_nickname_matches(node, name)) - return 1; - }); - return 0; -} - -/** Return true iff r1 and r2 are in the same family, but not the same - * router. */ -int -nodes_in_same_family(const node_t *node1, const node_t *node2) -{ - /* XXXX MOVE */ - const or_options_t *options = get_options(); - - /* Are they in the same family because of their addresses? */ - if (options->EnforceDistinctSubnets) { - tor_addr_t a1, a2; - node_get_addr(node1, &a1); - node_get_addr(node2, &a2); - if (addrs_in_same_network_family(&a1, &a2)) - return 1; - } - - /* Are they in the same family because the agree they are? */ - { - const smartlist_t *f1, *f2; - f1 = node_get_declared_family(node1); - f2 = node_get_declared_family(node2); - if (f1 && f2 && - node_in_nickname_smartlist(f1, node2) && - node_in_nickname_smartlist(f2, node1)) - return 1; - } - - /* Are they in the same option because the user says they are? */ - if (options->NodeFamilySets) { - SMARTLIST_FOREACH(options->NodeFamilySets, const routerset_t *, rs, { - if (routerset_contains_node(rs, node1) && - routerset_contains_node(rs, node2)) - return 1; - }); - } - - return 0; -} - /** Return 1 iff any member of the (possibly NULL) comma-separated list * <b>list</b> is an acceptable nickname or hexdigest for <b>router</b>. Else * return 0. @@ -1575,56 +1522,6 @@ routerlist_find_my_routerinfo(void) return NULL; } -/** Find a router that's up, that has this IP address, and - * that allows exit to this address:port, or return NULL if there - * isn't a good one. - * Don't exit enclave to excluded relays -- it wouldn't actually - * hurt anything, but this way there are fewer confused users. - */ -const node_t * -router_find_exact_exit_enclave(const char *address, uint16_t port) -{/*XXXX MOVE*/ - uint32_t addr; - struct in_addr in; - tor_addr_t a; - const or_options_t *options = get_options(); - - if (!tor_inet_aton(address, &in)) - return NULL; /* it's not an IP already */ - addr = ntohl(in.s_addr); - - tor_addr_from_ipv4h(&a, addr); - - SMARTLIST_FOREACH(nodelist_get_list(), const node_t *, node, { - if (node_get_addr_ipv4h(node) == addr && - node->is_running && - compare_tor_addr_to_node_policy(&a, port, node) == - ADDR_POLICY_ACCEPTED && - !routerset_contains_node(options->_ExcludeExitNodesUnion, node)) - return node; - }); - return NULL; -} - -/** Return 1 if <b>router</b> is not suitable for these parameters, else 0. - * If <b>need_uptime</b> is non-zero, we require a minimum uptime. - * If <b>need_capacity</b> is non-zero, we require a minimum advertised - * bandwidth. - * If <b>need_guard</b>, we require that the router is a possible entry guard. - */ -int -node_is_unreliable(const node_t *node, int need_uptime, - int need_capacity, int need_guard) -{ - if (need_uptime && !node->is_stable) - return 1; - if (need_capacity && !node->is_fast) - return 1; - if (need_guard && !node->is_possible_guard) - return 1; - return 0; -} - /** Return the smaller of the router's configured BandwidthRate * and its advertised capacity. */ uint32_t @@ -1652,6 +1549,92 @@ router_get_advertised_bandwidth_capped(const routerinfo_t *router) return result; } +/** Given an array of double/uint64_t unions that are currently being used as + * doubles, convert them to uint64_t, and try to scale them linearly so as to + * much of the range of uint64_t. If <b>total_out</b> is provided, set it to + * the sum of all elements in the array _before_ scaling. */ +/* private */ void +scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, + uint64_t *total_out) +{ + double total = 0.0; + double scale_factor; + int i; + /* big, but far away from overflowing an int64_t */ +#define SCALE_TO_U64_MAX (INT64_MAX / 4) + + for (i = 0; i < n_entries; ++i) + total += entries[i].dbl; + + scale_factor = SCALE_TO_U64_MAX / total; + + for (i = 0; i < n_entries; ++i) + entries[i].u64 = tor_llround(entries[i].dbl * scale_factor); + + if (total_out) + *total_out = (uint64_t) total; + +#undef SCALE_TO_U64_MAX +} + +/** Time-invariant 64-bit greater-than; works on two integers in the range + * (0,INT64_MAX). */ +#if SIZEOF_VOID_P == 8 +#define gt_i64_timei(a,b) ((a) > (b)) +#else +static INLINE int +gt_i64_timei(uint64_t a, uint64_t b) +{ + int64_t diff = (int64_t) (b - a); + int res = diff >> 63; + return res & 1; +} +#endif + +/** Pick a random element of <b>n_entries</b>-element array <b>entries</b>, + * choosing each element with a probability proportional to its (uint64_t) + * value, and return the index of that element. If all elements are 0, choose + * an index at random. Return -1 on error. + */ +/* private */ int +choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries) +{ + int i, i_chosen=-1, n_chosen=0; + uint64_t total_so_far = 0; + uint64_t rand_val; + uint64_t total = 0; + + for (i = 0; i < n_entries; ++i) + total += entries[i].u64; + + if (n_entries < 1) + return -1; + + if (total == 0) + return crypto_rand_int(n_entries); + + tor_assert(total < INT64_MAX); + + rand_val = crypto_rand_uint64(total); + + for (i = 0; i < n_entries; ++i) { + total_so_far += entries[i].u64; + if (gt_i64_timei(total_so_far, rand_val)) { + i_chosen = i; + n_chosen++; + /* Set rand_val to INT64_MAX rather than stopping the loop. This way, + * the time we spend in the loop does not leak which element we chose. */ + rand_val = INT64_MAX; + } + } + tor_assert(total_so_far == total); + tor_assert(n_chosen == 1); + tor_assert(i_chosen >= 0); + tor_assert(i_chosen < n_entries); + + return i_chosen; +} + /** When weighting bridges, enforce these values as lower and upper * bound for believable bandwidth, because there is no way for us * to verify a bridge's bandwidth currently. */ @@ -1702,16 +1685,10 @@ smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, bandwidth_weight_rule_t rule) { int64_t weight_scale; - int64_t rand_bw; double Wg = -1, Wm = -1, We = -1, Wd = -1; double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1; - double weighted_bw = 0, unweighted_bw = 0; - double *bandwidths; - double tmp = 0; - unsigned int i; - unsigned int i_chosen; - unsigned int i_has_been_chosen; - int have_unknown = 0; /* true iff sl contains element not in consensus. */ + uint64_t weighted_bw = 0; + u64_dbl_t *bandwidths; /* Can't choose exit and guard at same time */ tor_assert(rule == NO_WEIGHTING || @@ -1792,7 +1769,7 @@ smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, Web /= weight_scale; Wdb /= weight_scale; - bandwidths = tor_malloc_zero(sizeof(double)*smartlist_len(sl)); + bandwidths = tor_malloc_zero(sizeof(u64_dbl_t)*smartlist_len(sl)); // Cycle through smartlist and total the bandwidth. SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { @@ -1815,7 +1792,6 @@ smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, } else if (node->ri) { /* bridge or other descriptor not in our consensus */ this_bw = bridge_get_advertised_bandwidth_bounded(node->ri); - have_unknown = 1; } else { /* We can't use this one. */ continue; @@ -1831,72 +1807,32 @@ smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, } else { // middle weight = (is_dir ? Wmb*Wm : Wm); } - - bandwidths[node_sl_idx] = weight*this_bw; - weighted_bw += weight*this_bw; - unweighted_bw += this_bw; + /* These should be impossible; but overflows here would be bad, so let's + * make sure. */ + if (this_bw < 0) + this_bw = 0; + if (weight < 0.0) + weight = 0.0; + + bandwidths[node_sl_idx].dbl = weight*this_bw + 0.5; if (is_me) - sl_last_weighted_bw_of_me = weight*this_bw; + sl_last_weighted_bw_of_me = (uint64_t) bandwidths[node_sl_idx].dbl; } SMARTLIST_FOREACH_END(node); - /* XXXX this is a kludge to expose these values. */ - sl_last_total_weighted_bw = weighted_bw; - log_debug(LD_CIRC, "Choosing node for rule %s based on weights " - "Wg=%f Wm=%f We=%f Wd=%f with total bw %f", + "Wg=%f Wm=%f We=%f Wd=%f with total bw "U64_FORMAT, bandwidth_weight_rule_to_string(rule), - Wg, Wm, We, Wd, weighted_bw); - - /* If there is no bandwidth, choose at random */ - if (DBL_TO_U64(weighted_bw) == 0) { - /* Don't warn when using bridges/relays not in the consensus */ - if (!have_unknown) { -#define ZERO_BANDWIDTH_WARNING_INTERVAL (15) - static ratelim_t zero_bandwidth_warning_limit = - RATELIM_INIT(ZERO_BANDWIDTH_WARNING_INTERVAL); - char *msg; - if ((msg = rate_limit_log(&zero_bandwidth_warning_limit, - approx_time()))) { - log_warn(LD_CIRC, - "Weighted bandwidth is %f in node selection for rule %s " - "(unweighted was %f) %s", - weighted_bw, bandwidth_weight_rule_to_string(rule), - unweighted_bw, msg); - } - } - tor_free(bandwidths); - return smartlist_choose(sl); - } + Wg, Wm, We, Wd, U64_PRINTF_ARG(weighted_bw)); - rand_bw = crypto_rand_uint64(DBL_TO_U64(weighted_bw)); - rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count - * from 1 below. See bug 1203 for details. */ - - /* Last, count through sl until we get to the element we picked */ - i_chosen = (unsigned)smartlist_len(sl); - i_has_been_chosen = 0; - tmp = 0.0; - for (i=0; i < (unsigned)smartlist_len(sl); i++) { - tmp += bandwidths[i]; - if (tmp >= rand_bw && !i_has_been_chosen) { - i_chosen = i; - i_has_been_chosen = 1; - } - } - i = i_chosen; - - if (i == (unsigned)smartlist_len(sl)) { - /* This was once possible due to round-off error, but shouldn't be able - * to occur any longer. */ - tor_fragile_assert(); - --i; - log_warn(LD_BUG, "Round-off error in computing bandwidth had an effect on " - " which router we chose. Please tell the developers. " - "%f " U64_FORMAT " %f", tmp, U64_PRINTF_ARG(rand_bw), - weighted_bw); + scale_array_elements_to_u64(bandwidths, smartlist_len(sl), + &sl_last_total_weighted_bw); + + { + int idx = choose_array_element_by_weight(bandwidths, + smartlist_len(sl)); + tor_free(bandwidths); + return idx < 0 ? NULL : smartlist_get(sl, idx); } - tor_free(bandwidths); - return smartlist_get(sl, i); } /** Helper function: @@ -1917,17 +1853,16 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule) { unsigned int i; - unsigned int i_chosen; - unsigned int i_has_been_chosen; - int32_t *bandwidths; + u64_dbl_t *bandwidths; int is_exit; int is_guard; - uint64_t total_nonexit_bw = 0, total_exit_bw = 0, total_bw = 0; - uint64_t total_nonguard_bw = 0, total_guard_bw = 0; - uint64_t rand_bw, tmp; + int is_fast; + double total_nonexit_bw = 0, total_exit_bw = 0; + double total_nonguard_bw = 0, total_guard_bw = 0; double exit_weight; double guard_weight; int n_unknown = 0; + bitarray_t *fast_bits; bitarray_t *exit_bits; bitarray_t *guard_bits; int me_idx = -1; @@ -1951,10 +1886,9 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, } /* First count the total bandwidth weight, and make a list - * of each value. <0 means "unknown; no routerinfo." We use the - * bits of negative values to remember whether the router was fast (-x)&1 - * and whether it was an exit (-x)&2 or guard (-x)&4. Yes, it's a hack. */ - bandwidths = tor_malloc(sizeof(int32_t)*smartlist_len(sl)); + * of each value. We use UINT64_MAX to indicate "unknown". */ + bandwidths = tor_malloc_zero(sizeof(u64_dbl_t)*smartlist_len(sl)); + fast_bits = bitarray_init_zero(smartlist_len(sl)); exit_bits = bitarray_init_zero(smartlist_len(sl)); guard_bits = bitarray_init_zero(smartlist_len(sl)); @@ -1962,7 +1896,6 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { /* first, learn what bandwidth we think i has */ int is_known = 1; - int32_t flags = 0; uint32_t this_bw = 0; i = node_sl_idx; @@ -1975,12 +1908,7 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, if (node->rs->has_bandwidth) { this_bw = kb_to_bytes(node->rs->bandwidth); } else { /* guess */ - /* XXX024 once consensuses always list bandwidths, we can take - * this guessing business out. -RD */ is_known = 0; - flags = node->rs->is_fast ? 1 : 0; - flags |= is_exit ? 2 : 0; - flags |= is_guard ? 4 : 0; } } else if (node->ri) { /* Must be a bridge if we're willing to use it */ @@ -1991,12 +1919,11 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, bitarray_set(exit_bits, i); if (is_guard) bitarray_set(guard_bits, i); + if (node->is_fast) + bitarray_set(fast_bits, i); + if (is_known) { - bandwidths[i] = (int32_t) this_bw; - /* Casting this_bw to int32_t is safe because both kb_to_bytes - and bridge_get_advertised_bandwidth_bounded limit it to below - INT32_MAX. */ - tor_assert(bandwidths[i] >= 0); + bandwidths[i].dbl = this_bw; if (is_guard) total_guard_bw += this_bw; else @@ -2007,14 +1934,16 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, total_nonexit_bw += this_bw; } else { ++n_unknown; - bandwidths[node_sl_idx] = -flags; + bandwidths[i].dbl = -1.0; } } SMARTLIST_FOREACH_END(node); +#define EPSILON .1 + /* Now, fill in the unknown values. */ if (n_unknown) { int32_t avg_fast, avg_slow; - if (total_exit_bw+total_nonexit_bw) { + if (total_exit_bw+total_nonexit_bw < EPSILON) { /* if there's some bandwidth, there's at least one known router, * so no worries about div by 0 here */ int n_known = smartlist_len(sl)-n_unknown; @@ -2025,26 +1954,27 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, avg_slow = 20000; } for (i=0; i<(unsigned)smartlist_len(sl); ++i) { - int32_t bw = bandwidths[i]; - if (bw>=0) + if (bandwidths[i].dbl >= 0.0) continue; - is_exit = ((-bw)&2); - is_guard = ((-bw)&4); - bandwidths[i] = ((-bw)&1) ? avg_fast : avg_slow; + is_fast = bitarray_is_set(fast_bits, i); + is_exit = bitarray_is_set(exit_bits, i); + is_guard = bitarray_is_set(guard_bits, i); + bandwidths[i].dbl = is_fast ? avg_fast : avg_slow; if (is_exit) - total_exit_bw += bandwidths[i]; + total_exit_bw += bandwidths[i].dbl; else - total_nonexit_bw += bandwidths[i]; + total_nonexit_bw += bandwidths[i].dbl; if (is_guard) - total_guard_bw += bandwidths[i]; + total_guard_bw += bandwidths[i].dbl; else - total_nonguard_bw += bandwidths[i]; + total_nonguard_bw += bandwidths[i].dbl; } } /* If there's no bandwidth at all, pick at random. */ - if (!(total_exit_bw+total_nonexit_bw)) { + if (total_exit_bw+total_nonexit_bw < EPSILON) { tor_free(bandwidths); + tor_free(fast_bits); tor_free(exit_bits); tor_free(guard_bits); return smartlist_choose(sl); @@ -2059,12 +1989,12 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, * For detailed derivation of this formula, see * http://archives.seul.org/or/dev/Jul-2007/msg00056.html */ - if (rule == WEIGHT_FOR_EXIT || !total_exit_bw) + if (rule == WEIGHT_FOR_EXIT || total_exit_bw<EPSILON) exit_weight = 1.0; else exit_weight = 1.0 - all_bw/(3.0*exit_bw); - if (rule == WEIGHT_FOR_GUARD || !total_guard_bw) + if (rule == WEIGHT_FOR_GUARD || total_guard_bw<EPSILON) guard_weight = 1.0; else guard_weight = 1.0 - all_bw/(3.0*guard_bw); @@ -2075,29 +2005,25 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, if (guard_weight <= 0.0) guard_weight = 0.0; - total_bw = 0; sl_last_weighted_bw_of_me = 0; for (i=0; i < (unsigned)smartlist_len(sl); i++) { - uint64_t bw; + tor_assert(bandwidths[i].dbl >= 0.0); + is_exit = bitarray_is_set(exit_bits, i); is_guard = bitarray_is_set(guard_bits, i); if (is_exit && is_guard) - bw = ((uint64_t)(bandwidths[i] * exit_weight * guard_weight)); + bandwidths[i].dbl *= exit_weight * guard_weight; else if (is_guard) - bw = ((uint64_t)(bandwidths[i] * guard_weight)); + bandwidths[i].dbl *= guard_weight; else if (is_exit) - bw = ((uint64_t)(bandwidths[i] * exit_weight)); - else - bw = bandwidths[i]; - total_bw += bw; + bandwidths[i].dbl *= exit_weight; + if (i == (unsigned) me_idx) - sl_last_weighted_bw_of_me = bw; + sl_last_weighted_bw_of_me = (uint64_t) bandwidths[i].dbl; } } - /* XXXX this is a kludge to expose these values. */ - sl_last_total_weighted_bw = total_bw; - +#if 0 log_debug(LD_CIRC, "Total weighted bw = "U64_FORMAT ", exit bw = "U64_FORMAT ", nonexit bw = "U64_FORMAT", exit weight = %f " @@ -2110,50 +2036,20 @@ smartlist_choose_node_by_bandwidth(smartlist_t *sl, exit_weight, (int)(rule == WEIGHT_FOR_EXIT), U64_PRINTF_ARG(total_guard_bw), U64_PRINTF_ARG(total_nonguard_bw), guard_weight, (int)(rule == WEIGHT_FOR_GUARD)); +#endif - /* Almost done: choose a random value from the bandwidth weights. */ - rand_bw = crypto_rand_uint64(total_bw); - rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count - * from 1 below. See bug 1203 for details. */ - - /* Last, count through sl until we get to the element we picked */ - tmp = 0; - i_chosen = (unsigned)smartlist_len(sl); - i_has_been_chosen = 0; - for (i=0; i < (unsigned)smartlist_len(sl); i++) { - is_exit = bitarray_is_set(exit_bits, i); - is_guard = bitarray_is_set(guard_bits, i); - - /* Weights can be 0 if not counting guards/exits */ - if (is_exit && is_guard) - tmp += ((uint64_t)(bandwidths[i] * exit_weight * guard_weight)); - else if (is_guard) - tmp += ((uint64_t)(bandwidths[i] * guard_weight)); - else if (is_exit) - tmp += ((uint64_t)(bandwidths[i] * exit_weight)); - else - tmp += bandwidths[i]; + scale_array_elements_to_u64(bandwidths, smartlist_len(sl), + &sl_last_total_weighted_bw); - if (tmp >= rand_bw && !i_has_been_chosen) { - i_chosen = i; - i_has_been_chosen = 1; - } - } - i = i_chosen; - if (i == (unsigned)smartlist_len(sl)) { - /* This was once possible due to round-off error, but shouldn't be able - * to occur any longer. */ - tor_fragile_assert(); - --i; - log_warn(LD_BUG, "Round-off error in computing bandwidth had an effect on " - " which router we chose. Please tell the developers. " - U64_FORMAT " " U64_FORMAT " " U64_FORMAT, U64_PRINTF_ARG(tmp), - U64_PRINTF_ARG(rand_bw), U64_PRINTF_ARG(total_bw)); + { + int idx = choose_array_element_by_weight(bandwidths, + smartlist_len(sl)); + tor_free(bandwidths); + tor_free(fast_bits); + tor_free(exit_bits); + tor_free(guard_bits); + return idx < 0 ? NULL : smartlist_get(sl, idx); } - tor_free(bandwidths); - tor_free(exit_bits); - tor_free(guard_bits); - return smartlist_get(sl, i); } /** Choose a random element of status list <b>sl</b>, weighted by @@ -2306,7 +2202,7 @@ hex_digest_nickname_decode(const char *hexdigest, * combination of a router, encoded in hexadecimal, matches <b>hexdigest</b> * (which is optionally prefixed with a single dollar sign). Return false if * <b>hexdigest</b> is malformed, or it doesn't match. */ -static int +int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, const char *nickname, int is_named) { @@ -2366,129 +2262,6 @@ router_nickname_matches(const routerinfo_t *router, const char *nickname) return router_hex_digest_matches(router, nickname); } -/** Return true if <b>node</b>'s nickname matches <b>nickname</b> - * (case-insensitive), or if <b>node's</b> identity key digest - * matches a hexadecimal value stored in <b>nickname</b>. Return - * false otherwise. */ -static int -node_nickname_matches(const node_t *node, const char *nickname) -{ - const char *n = node_get_nickname(node); - if (n && nickname[0]!='$' && !strcasecmp(n, nickname)) - return 1; - return hex_digest_nickname_matches(nickname, - node->identity, - n, - node_is_named(node)); -} - -/** Return the router in our routerlist whose (case-insensitive) - * nickname or (case-sensitive) hexadecimal key digest is - * <b>nickname</b>. Return NULL if no such router is known. - */ -const routerinfo_t * -router_get_by_nickname(const char *nickname, int warn_if_unnamed) -{ -#if 1 - const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); - if (node) - return node->ri; - else - return NULL; -#else - int maybedigest; - char digest[DIGEST_LEN]; - routerinfo_t *best_match=NULL; - int n_matches = 0; - const char *named_digest = NULL; - - tor_assert(nickname); - if (!routerlist) - return NULL; - if (nickname[0] == '$') - return router_get_by_hexdigest(nickname); - if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) - return NULL; - - maybedigest = (strlen(nickname) >= HEX_DIGEST_LEN) && - (base16_decode(digest,DIGEST_LEN,nickname,HEX_DIGEST_LEN) == 0); - - if ((named_digest = networkstatus_get_router_digest_by_nickname(nickname))) { - return rimap_get(routerlist->identity_map, named_digest); - } - if (networkstatus_nickname_is_unnamed(nickname)) - return NULL; - - /* If we reach this point, there's no canonical value for the nickname. */ - - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (!strcasecmp(router->nickname, nickname)) { - ++n_matches; - if (n_matches <= 1 || router->is_running) - best_match = router; - } else if (maybedigest && - tor_memeq(digest, router->cache_info.identity_digest, - DIGEST_LEN)) { - if (router_hex_digest_matches(router, nickname)) - return router; - /* If we reach this point, we have a ID=name syntax that matches the - * identity but not the name. That isn't an acceptable match. */ - } - }); - - if (best_match) { - if (warn_if_unnamed && n_matches > 1) { - smartlist_t *fps = smartlist_new(); - int any_unwarned = 0; - SMARTLIST_FOREACH_BEGIN(routerlist->routers, routerinfo_t *, router) { - routerstatus_t *rs; - char fp[HEX_DIGEST_LEN+1]; - if (strcasecmp(router->nickname, nickname)) - continue; - rs = router_get_mutable_consensus_status_by_id( - router->cache_info.identity_digest); - if (rs && !rs->name_lookup_warned) { - rs->name_lookup_warned = 1; - any_unwarned = 1; - } - base16_encode(fp, sizeof(fp), - router->cache_info.identity_digest, DIGEST_LEN); - smartlist_add_asprintf(fps, "\"$%s\" for the one at %s:%d", - fp, router->address, router->or_port); - } SMARTLIST_FOREACH_END(router); - if (any_unwarned) { - char *alternatives = smartlist_join_strings(fps, "; ",0,NULL); - log_warn(LD_CONFIG, - "There are multiple matches for the nickname \"%s\"," - " but none is listed as named by the directory authorities. " - "Choosing one arbitrarily. If you meant one in particular, " - "you should say %s.", nickname, alternatives); - tor_free(alternatives); - } - SMARTLIST_FOREACH(fps, char *, cp, tor_free(cp)); - smartlist_free(fps); - } else if (warn_if_unnamed) { - routerstatus_t *rs = router_get_mutable_consensus_status_by_id( - best_match->cache_info.identity_digest); - if (rs && !rs->name_lookup_warned) { - char fp[HEX_DIGEST_LEN+1]; - base16_encode(fp, sizeof(fp), - best_match->cache_info.identity_digest, DIGEST_LEN); - log_warn(LD_CONFIG, "You specified a server \"%s\" by name, but this " - "name is not registered, so it could be used by any server, " - "not just the one you meant. " - "To make sure you get the same server in the future, refer to " - "it by key, as \"$%s\".", nickname, fp); - rs->name_lookup_warned = 1; - } - } - return best_match; - } - return NULL; -#endif -} - /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> * is zero, any authority is okay. */ @@ -2499,7 +2272,7 @@ router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type) return 0; if (authdir_mode(get_options()) && router_digest_is_me(digest)) return 1; - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ent, + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ent, if (tor_memeq(digest, ent->digest, DIGEST_LEN)) { return (!type) || ((type & ent->type) != 0); }); @@ -2513,7 +2286,7 @@ router_addr_is_trusted_dir(uint32_t addr) { if (!trusted_dir_servers) return 0; - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ent, + SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ent, if (ent->addr == addr) return 1; ); @@ -2535,18 +2308,6 @@ hexdigest_to_digest(const char *hexdigest, char *digest) return 0; } -/** Return the router in our routerlist whose hexadecimal key digest - * is <b>hexdigest</b>. Return NULL if no such router is known. */ -const routerinfo_t * -router_get_by_hexdigest(const char *hexdigest) -{ - if (is_legal_nickname(hexdigest)) - return NULL; - - /* It's not a legal nickname, so it must be a hexdigest or nothing. */ - return router_get_by_nickname(hexdigest, 1); -} - /** As router_get_by_id_digest,but return a pointer that you're allowed to * modify */ routerinfo_t * @@ -2721,6 +2482,7 @@ routerinfo_free(routerinfo_t *router) tor_free(router->contact_info); if (router->onion_pkey) crypto_pk_free(router->onion_pkey); + tor_free(router->onion_curve25519_pkey); if (router->identity_pkey) crypto_pk_free(router->identity_pkey); if (router->declared_family) { @@ -2728,6 +2490,7 @@ routerinfo_free(routerinfo_t *router) smartlist_free(router->declared_family); } addr_policy_list_free(router->exit_policy); + short_policy_free(router->ipv6_exit_policy); memset(router, 77, sizeof(routerinfo_t)); @@ -2778,7 +2541,7 @@ signed_descriptor_from_routerinfo(routerinfo_t *ri) /** Helper: free the storage held by the extrainfo_t in <b>e</b>. */ static void -_extrainfo_free(void *e) +extrainfo_free_(void *e) { extrainfo_free(e); } @@ -2792,7 +2555,7 @@ routerlist_free(routerlist_t *rl) rimap_free(rl->identity_map, NULL); sdmap_free(rl->desc_digest_map, NULL); sdmap_free(rl->desc_by_eid_map, NULL); - eimap_free(rl->extra_info_map, _extrainfo_free); + eimap_free(rl->extra_info_map, extrainfo_free_); SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, routerinfo_free(r)); SMARTLIST_FOREACH(rl->old_routers, signed_descriptor_t *, sd, @@ -2834,7 +2597,7 @@ dump_routerlist_mem_usage(int severity) * <b>ri</b>. Return the index of <b>ri</b> in <b>sl</b>, or -1 if <b>ri</b> * is not in <b>sl</b>. */ static INLINE int -_routerlist_find_elt(smartlist_t *sl, void *ri, int idx) +routerlist_find_elt_(smartlist_t *sl, void *ri, int idx) { if (idx < 0) { idx = -1; @@ -2890,7 +2653,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 +2882,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) { @@ -3128,7 +2894,7 @@ routerlist_replace(routerlist_t *rl, routerinfo_t *ri_old, ri_old->cache_info.routerlist_index = -1; ri_new->cache_info.routerlist_index = idx; /* Check that ri_old is not in rl->routers anymore: */ - tor_assert( _routerlist_find_elt(rl->routers, ri_old, -1) == -1 ); + tor_assert( routerlist_find_elt_(rl->routers, ri_old, -1) == -1 ); } else { log_warn(LD_BUG, "Appending entry from routerlist_replace."); routerlist_insert(rl, ri_new); @@ -3232,12 +2998,10 @@ routerlist_free_all(void) smartlist_free(warned_nicknames); warned_nicknames = NULL; } - if (trusted_dir_servers) { - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, - trusted_dir_server_free(ds)); - smartlist_free(trusted_dir_servers); - trusted_dir_servers = NULL; - } + clear_dir_servers(); + smartlist_free(trusted_dir_servers); + smartlist_free(fallback_dir_servers); + trusted_dir_servers = fallback_dir_servers = NULL; if (trusted_dir_certs) { DIGESTMAP_FOREACH(trusted_dir_certs, key, cert_list_t *, cl) { SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, @@ -3263,33 +3027,6 @@ routerlist_reset_warnings(void) networkstatus_reset_warnings(); } -/** Mark the router with ID <b>digest</b> as running or non-running - * in our routerlist. */ -void -router_set_status(const char *digest, int up) -{ - node_t *node; - tor_assert(digest); - - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, d, - if (tor_memeq(d->digest, digest, DIGEST_LEN)) - d->is_running = up); - - node = node_get_mutable_by_id(digest); - if (node) { -#if 0 - log_debug(LD_DIR,"Marking router %s as %s.", - node_describe(node), up ? "up" : "down"); -#endif - if (!up && node_is_me(node) && !net_is_disabled()) - log_warn(LD_NET, "We just marked ourself as down. Are your external " - "addresses reachable?"); - node->is_running = up; - } - - router_dir_info_changed(); -} - /** Add <b>router</b> to the routerlist, if we don't already have it. Replace * older entries (if any) with the same key. Note: Callers should not hold * their pointers to <b>router</b> if this function fails; <b>router</b> @@ -3457,11 +3194,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, @@ -3522,7 +3254,7 @@ router_add_extrainfo_to_routerlist(extrainfo_t *ei, const char **msg, * signed_descriptor_t* in *<b>a</b> has an identity digest preceding, equal * to, or later than that of *<b>b</b>. */ static int -_compare_old_routers_by_identity(const void **_a, const void **_b) +compare_old_routers_by_identity_(const void **_a, const void **_b) { int i; const signed_descriptor_t *r1 = *_a, *r2 = *_b; @@ -3542,7 +3274,7 @@ struct duration_idx_t { /** Sorting helper: compare two duration_idx_t by their duration. */ static int -_compare_duration_idx(const void *_d1, const void *_d2) +compare_duration_idx_(const void *_d1, const void *_d2) { const struct duration_idx_t *d1 = _d1; const struct duration_idx_t *d2 = _d2; @@ -3619,7 +3351,7 @@ routerlist_remove_old_cached_routers_with_id(time_t now, * the duration of liveness, and remove the ones we're not already going to * remove based on how long they were alive. **/ - qsort(lifespans, n, sizeof(struct duration_idx_t), _compare_duration_idx); + qsort(lifespans, n, sizeof(struct duration_idx_t), compare_duration_idx_); for (i = 0; i < n && n_rmv < n_extra; ++i) { if (!must_keep[lifespans[i].idx-lo] && !lifespans[i].old) { rmv[lifespans[i].idx-lo] = 1; @@ -3773,7 +3505,7 @@ routerlist_remove_old_routers(void) goto done; /* Sort by identity, then fix indices. */ - smartlist_sort(routerlist->old_routers, _compare_old_routers_by_identity); + smartlist_sort(routerlist->old_routers, compare_old_routers_by_identity_); /* Fix indices. */ for (i = 0; i < smartlist_len(routerlist->old_routers); ++i) { signed_descriptor_t *r = smartlist_get(routerlist->old_routers, i); @@ -4068,27 +3800,6 @@ routerlist_retry_directory_downloads(time_t now) update_all_descriptor_downloads(now); } -/** Return 1 if all running sufficiently-stable routers we can use will reject - * addr:port, return 0 if any might accept it. */ -int -router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, - int need_uptime) -{ /* XXXX MOVE */ - addr_policy_result_t r; - - SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { - if (node->is_running && - !node_is_unreliable(node, need_uptime, 0, 0)) { - - r = compare_tor_addr_to_node_policy(addr, port, node); - - if (r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED) - return 0; /* this one could be ok. good enough. */ - } - } SMARTLIST_FOREACH_END(node); - return 1; /* all will reject. */ -} - /** Return true iff <b>router</b> does not permit exit streams. */ int @@ -4097,47 +3808,47 @@ router_exit_policy_rejects_all(const routerinfo_t *router) return router->policy_is_reject_star; } -/** Add to the list of authoritative directory servers one at - * <b>address</b>:<b>port</b>, with identity key <b>digest</b>. If - * <b>address</b> is NULL, add ourself. Return the new trusted directory - * server entry on success or NULL if we couldn't add it. */ -trusted_dir_server_t * -add_trusted_dir_server(const char *nickname, const char *address, - uint16_t dir_port, uint16_t or_port, - const char *digest, const char *v3_auth_digest, - dirinfo_type_t type) -{ - trusted_dir_server_t *ent; +/** Create an directory server at <b>address</b>:<b>port</b>, with OR identity + * key <b>digest</b>. If <b>address</b> is NULL, add ourself. If + * <b>is_authority</b>, this is a directory authority. Return the new + * directory server entry on success or NULL on failure. */ +static dir_server_t * +dir_server_new(int is_authority, + const char *nickname, + const tor_addr_t *addr, + const char *hostname, + uint16_t dir_port, uint16_t or_port, + const char *digest, const char *v3_auth_digest, + dirinfo_type_t type, + double weight) +{ + dir_server_t *ent; uint32_t a; - char *hostname = NULL; - if (!trusted_dir_servers) - trusted_dir_servers = smartlist_new(); + char *hostname_ = NULL; - if (!address) { /* The address is us; we should guess. */ - if (resolve_my_address(LOG_WARN, get_options(), &a, &hostname) < 0) { - log_warn(LD_CONFIG, - "Couldn't find a suitable address when adding ourself as a " - "trusted directory server."); - return NULL; - } - } else { - if (tor_lookup_hostname(address, &a)) { - log_warn(LD_CONFIG, - "Unable to lookup address for directory server at '%s'", - address); - return NULL; - } - hostname = tor_strdup(address); - } + if (weight < 0) + return NULL; + + if (tor_addr_family(addr) == AF_INET) + a = tor_addr_to_ipv4h(addr); + else + return NULL; /*XXXX Support IPv6 */ + + if (!hostname) + hostname_ = tor_dup_addr(addr); + else + hostname_ = tor_strdup(hostname); - ent = tor_malloc_zero(sizeof(trusted_dir_server_t)); + ent = tor_malloc_zero(sizeof(dir_server_t)); ent->nickname = nickname ? tor_strdup(nickname) : NULL; - ent->address = hostname; + ent->address = hostname_; ent->addr = a; ent->dir_port = dir_port; ent->or_port = or_port; ent->is_running = 1; + ent->is_authority = is_authority; ent->type = type; + ent->weight = weight; memcpy(ent->digest, digest, DIGEST_LEN); if (v3_auth_digest && (type & V3_DIRINFO)) memcpy(ent->v3_identity_digest, v3_auth_digest, DIGEST_LEN); @@ -4159,14 +3870,75 @@ add_trusted_dir_server(const char *nickname, const char *address, ent->fake_status.dir_port = ent->dir_port; ent->fake_status.or_port = ent->or_port; - if (ent->or_port) - ent->fake_status.version_supports_begindir = 1; + return ent; +} - ent->fake_status.version_supports_conditional_consensus = 1; +/** Create an authoritative directory server at + * <b>address</b>:<b>port</b>, with identity key <b>digest</b>. If + * <b>address</b> is NULL, add ourself. Return the new trusted directory + * server entry on success or NULL if we couldn't add it. */ +dir_server_t * +trusted_dir_server_new(const char *nickname, const char *address, + uint16_t dir_port, uint16_t or_port, + const char *digest, const char *v3_auth_digest, + dirinfo_type_t type, double weight) +{ + uint32_t a; + tor_addr_t addr; + char *hostname=NULL; + dir_server_t *result; - smartlist_add(trusted_dir_servers, ent); + if (!address) { /* The address is us; we should guess. */ + if (resolve_my_address(LOG_WARN, get_options(), &a, &hostname) < 0) { + log_warn(LD_CONFIG, + "Couldn't find a suitable address when adding ourself as a " + "trusted directory server."); + return NULL; + } + } else { + if (tor_lookup_hostname(address, &a)) { + log_warn(LD_CONFIG, + "Unable to lookup address for directory server at '%s'", + address); + return NULL; + } + hostname = tor_strdup(address); + } + tor_addr_from_ipv4h(&addr, a); + + result = dir_server_new(1, nickname, &addr, hostname, + dir_port, or_port, digest, + v3_auth_digest, type, weight); + tor_free(hostname); + return result; +} + +/** Return a new dir_server_t for a fallback directory server at + * <b>addr</b>:<b>or_port</b>/<b>dir_port</b>, with identity key digest + * <b>id_digest</b> */ +dir_server_t * +fallback_dir_server_new(const tor_addr_t *addr, + uint16_t dir_port, uint16_t or_port, + const char *id_digest, double weight) +{ + return dir_server_new(0, NULL, addr, NULL, dir_port, or_port, id_digest, + NULL, ALL_DIRINFO, weight); +} + +/** Add a directory server to the global list(s). */ +void +dir_server_add(dir_server_t *ent) +{ + if (!trusted_dir_servers) + trusted_dir_servers = smartlist_new(); + if (!fallback_dir_servers) + fallback_dir_servers = smartlist_new(); + + if (ent->is_authority) + smartlist_add(trusted_dir_servers, ent); + + smartlist_add(fallback_dir_servers, ent); router_dir_info_changed(); - return ent; } /** Free storage held in <b>cert</b>. */ @@ -4185,7 +3957,7 @@ authority_cert_free(authority_cert_t *cert) /** Free storage held in <b>ds</b>. */ static void -trusted_dir_server_free(trusted_dir_server_t *ds) +dir_server_free(dir_server_t *ds) { if (!ds) return; @@ -4196,13 +3968,18 @@ trusted_dir_server_free(trusted_dir_server_t *ds) tor_free(ds); } -/** Remove all members from the list of trusted dir servers. */ +/** Remove all members from the list of dir servers. */ void -clear_trusted_dir_servers(void) +clear_dir_servers(void) { + if (fallback_dir_servers) { + SMARTLIST_FOREACH(fallback_dir_servers, dir_server_t *, ent, + dir_server_free(ent)); + smartlist_clear(fallback_dir_servers); + } else { + fallback_dir_servers = smartlist_new(); + } if (trusted_dir_servers) { - SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ent, - trusted_dir_server_free(ent)); smartlist_clear(trusted_dir_servers); } else { trusted_dir_servers = smartlist_new(); @@ -4338,7 +4115,7 @@ initiate_descriptor_downloads(const routerstatus_t *source, /* We know which authority we want. */ directory_initiate_command_routerstatus(source, purpose, ROUTER_PURPOSE_GENERAL, - 0, /* not private */ + DIRIND_ONEHOP, resource, NULL, 0, 0); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, @@ -4347,30 +4124,6 @@ initiate_descriptor_downloads(const routerstatus_t *source, tor_free(resource); } -/** Return 0 if this routerstatus is obsolete, too new, isn't - * running, or otherwise not a descriptor that we would make any - * use of even if we had it. Else return 1. */ -static INLINE int -client_would_use_router(const routerstatus_t *rs, time_t now, - const or_options_t *options) -{ - if (!rs->is_flagged_running && !options->FetchUselessDescriptors) { - /* If we had this router descriptor, we wouldn't even bother using it. - * But, if we want to have a complete list, fetch it anyway. */ - return 0; - } - if (rs->published_on + options->TestingEstimatedDescriptorPropagationTime - > now) { - /* Most caches probably don't have this descriptor yet. */ - return 0; - } - if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) { - /* We'd drop it immediately for being too old. */ - return 0; - } - return 1; -} - /** Max amount of hashes to download per request. * Since squid does not like URLs >= 4096 bytes we limit it to 96. * 4096 - strlen(http://255.255.255.255/tor/server/d/.z) == 4058 @@ -4440,11 +4193,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 +4232,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, @@ -4537,7 +4285,7 @@ update_router_descriptor_cache_downloads_v2(time_t now) */ n_download = 0; SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - trusted_dir_server_t *ds; + dir_server_t *ds; smartlist_t *dl; dl = downloadable[ns_sl_idx] = smartlist_new(); download_from[ns_sl_idx] = smartlist_new(); @@ -4612,7 +4360,7 @@ update_router_descriptor_cache_downloads_v2(time_t now) /* Now, we can actually launch our requests. */ for (i=0; i<n; ++i) { networkstatus_v2_t *ns = smartlist_get(networkstatus_v2_list, i); - trusted_dir_server_t *ds = + dir_server_t *ds = router_get_trusteddirserver_by_digest(ns->identity_digest); smartlist_t *dl = download_from[i]; int pds_flags = PDS_RETRY_IF_NO_SERVERS; @@ -4665,7 +4413,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, if (is_vote) { /* where's it from, so we know whom to ask for descriptors */ - trusted_dir_server_t *ds; + dir_server_t *ds; networkstatus_voter_info_t *voter = smartlist_get(consensus->voters, 0); tor_assert(voter); ds = trusteddirserver_get_by_v3_auth_digest(voter->identity_digest); @@ -4689,7 +4437,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, sd->signed_descriptor_digest, DIGEST_LEN)) { /* We have a descriptor with this digest, but either there is no * entry in routerlist with the same ID (!ri), or there is one, - * but the identity digest differs (memcmp). + * but the identity digest differs (memneq). */ smartlist_add(no_longer_old, sd); ++n_in_oldrouters; /* We have it in old_routers. */ @@ -4888,230 +4636,6 @@ update_extrainfo_downloads(time_t now) smartlist_free(wanted); } -/** True iff, the last time we checked whether we had enough directory info - * to build circuits, the answer was "yes". */ -static int have_min_dir_info = 0; -/** True iff enough has changed since the last time we checked whether we had - * enough directory info to build circuits that our old answer can no longer - * be trusted. */ -static int need_to_update_have_min_dir_info = 1; -/** String describing what we're missing before we have enough directory - * info. */ -static char dir_info_status[128] = ""; - -/** Return true iff we have enough networkstatus and router information to - * start building circuits. Right now, this means "more than half the - * networkstatus documents, and at least 1/4 of expected routers." */ -//XXX should consider whether we have enough exiting nodes here. -int -router_have_minimum_dir_info(void) -{ - if (PREDICT_UNLIKELY(need_to_update_have_min_dir_info)) { - update_router_have_minimum_dir_info(); - need_to_update_have_min_dir_info = 0; - } - return have_min_dir_info; -} - -/** Called when our internal view of the directory has changed. This can be - * when the authorities change, networkstatuses change, the list of routerdescs - * changes, or number of running routers changes. - */ -void -router_dir_info_changed(void) -{ - need_to_update_have_min_dir_info = 1; - rend_hsdir_routers_changed(); -} - -/** Return a string describing what we're missing before we have enough - * directory info. */ -const char * -get_dir_info_status_string(void) -{ - return dir_info_status; -} - -/** Iterate over the servers listed in <b>consensus</b>, and count how many of - * them seem like ones we'd use, and how many of <em>those</em> we have - * descriptors for. Store the former in *<b>num_usable</b> and the latter in - * *<b>num_present</b>. If <b>in_set</b> is non-NULL, only consider those - * routers in <b>in_set</b>. If <b>exit_only</b> is true, only consider nodes - * with the Exit flag. - */ -static void -count_usable_descriptors(int *num_present, int *num_usable, - const networkstatus_t *consensus, - const or_options_t *options, time_t now, - routerset_t *in_set, int exit_only) -{ - const int md = (consensus->flavor == FLAV_MICRODESC); - *num_present = 0, *num_usable=0; - - SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, rs) - { - if (exit_only && ! rs->is_exit) - continue; - if (in_set && ! routerset_contains_routerstatus(in_set, rs, -1)) - continue; - if (client_would_use_router(rs, now, options)) { - const char * const digest = rs->descriptor_digest; - int present; - ++*num_usable; /* the consensus says we want it. */ - if (md) - present = NULL != microdesc_cache_lookup_by_digest256(NULL, digest); - else - present = NULL != router_get_by_descriptor_digest(digest); - if (present) { - /* we have the descriptor listed in the consensus. */ - ++*num_present; - } - } - } - SMARTLIST_FOREACH_END(rs); - - log_debug(LD_DIR, "%d usable, %d present.", *num_usable, *num_present); -} - -/** We just fetched a new set of descriptors. Compute how far through - * the "loading descriptors" bootstrapping phase we are, so we can inform - * the controller of our progress. */ -int -count_loading_descriptors_progress(void) -{ - int num_present = 0, num_usable=0; - time_t now = time(NULL); - const networkstatus_t *consensus = - networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); - double fraction; - - if (!consensus) - return 0; /* can't count descriptors if we have no list of them */ - - count_usable_descriptors(&num_present, &num_usable, - consensus, get_options(), now, NULL, 0); - - if (num_usable == 0) - return 0; /* don't div by 0 */ - fraction = num_present / (num_usable/4.); - if (fraction > 1.0) - return 0; /* it's not the number of descriptors holding us back */ - return BOOTSTRAP_STATUS_LOADING_DESCRIPTORS + (int) - (fraction*(BOOTSTRAP_STATUS_CONN_OR-1 - - BOOTSTRAP_STATUS_LOADING_DESCRIPTORS)); -} - -/** Change the value of have_min_dir_info, setting it true iff we have enough - * network and router information to build circuits. Clear the value of - * need_to_update_have_min_dir_info. */ -static void -update_router_have_minimum_dir_info(void) -{ - int num_present = 0, num_usable=0; - int num_exit_present = 0, num_exit_usable = 0; - time_t now = time(NULL); - int res; - const or_options_t *options = get_options(); - const networkstatus_t *consensus = - networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); - int using_md; - - if (!consensus) { - if (!networkstatus_get_latest_consensus()) - strlcpy(dir_info_status, "We have no usable consensus.", - sizeof(dir_info_status)); - else - strlcpy(dir_info_status, "We have no recent usable consensus.", - sizeof(dir_info_status)); - res = 0; - goto done; - } - - if (should_delay_dir_fetches(get_options())) { - log_notice(LD_DIR, "no known bridge descriptors running yet; stalling"); - strlcpy(dir_info_status, "No live bridge descriptors.", - sizeof(dir_info_status)); - res = 0; - goto done; - } - - using_md = consensus->flavor == FLAV_MICRODESC; - - count_usable_descriptors(&num_present, &num_usable, consensus, options, now, - NULL, 0); - count_usable_descriptors(&num_exit_present, &num_exit_usable, - consensus, options, now, options->ExitNodes, 1); - -/* What fraction of desired server descriptors do we need before we will - * build circuits? */ -#define FRAC_USABLE_NEEDED .75 -/* What fraction of desired _exit_ server descriptors do we need before we - * will build circuits? */ -#define FRAC_EXIT_USABLE_NEEDED .5 - - if (num_present < num_usable * FRAC_USABLE_NEEDED) { - tor_snprintf(dir_info_status, sizeof(dir_info_status), - "We have only %d/%d usable %sdescriptors.", - num_present, num_usable, using_md ? "micro" : ""); - res = 0; - control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0); - goto done; - } else if (num_present < 2) { - tor_snprintf(dir_info_status, sizeof(dir_info_status), - "Only %d %sdescriptor%s here and believed reachable!", - num_present, using_md ? "micro" : "", num_present ? "" : "s"); - res = 0; - goto done; - } else if (num_exit_present < num_exit_usable * FRAC_EXIT_USABLE_NEEDED) { - tor_snprintf(dir_info_status, sizeof(dir_info_status), - "We have only %d/%d usable exit node descriptors.", - num_exit_present, num_exit_usable); - res = 0; - control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0); - goto done; - } - - /* Check for entry nodes. */ - if (options->EntryNodes) { - count_usable_descriptors(&num_present, &num_usable, consensus, options, - now, options->EntryNodes, 0); - - if (!num_usable || !num_present) { - tor_snprintf(dir_info_status, sizeof(dir_info_status), - "We have only %d/%d usable entry node %sdescriptors.", - num_present, num_usable, using_md?"micro":""); - res = 0; - goto done; - } - } - - res = 1; - - done: - if (res && !have_min_dir_info) { - log(LOG_NOTICE, LD_DIR, - "We now have enough directory information to build circuits."); - control_event_client_status(LOG_NOTICE, "ENOUGH_DIR_INFO"); - control_event_bootstrap(BOOTSTRAP_STATUS_CONN_OR, 0); - } - if (!res && have_min_dir_info) { - int quiet = directory_too_idle_to_fetch_descriptors(options, now); - log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR, - "Our directory information is no longer up-to-date " - "enough to build circuits: %s", dir_info_status); - - /* a) make us log when we next complete a circuit, so we know when Tor - * is back up and usable, and b) disable some activities that Tor - * should only do while circuits are working, like reachability tests - * and fetching bridge descriptors only over circuits. */ - can_complete_circuit = 0; - - control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO"); - } - have_min_dir_info = res; - need_to_update_have_min_dir_info = 0; -} - /** Reset the descriptor download failure count on all routers, so that we * can retry any long-failed routers immediately. */ @@ -5164,8 +4688,8 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2) r1->ipv6_orport != r2->ipv6_orport || r1->dir_port != r2->dir_port || r1->purpose != r2->purpose || - crypto_pk_cmp_keys(r1->onion_pkey, r2->onion_pkey) || - crypto_pk_cmp_keys(r1->identity_pkey, r2->identity_pkey) || + !crypto_pk_eq_keys(r1->onion_pkey, r2->onion_pkey) || + !crypto_pk_eq_keys(r1->identity_pkey, r2->identity_pkey) || strcasecmp(r1->platform, r2->platform) || (r1->contact_info && !r2->contact_info) || /* contact_info is optional */ (!r1->contact_info && r2->contact_info) || @@ -5410,7 +4934,7 @@ esc_router_info(const routerinfo_t *router) /** Helper for sorting: compare two routerinfos by their identity * digest. */ static int -_compare_routerinfo_by_id_digest(const void **a, const void **b) +compare_routerinfo_by_id_digest_(const void **a, const void **b) { routerinfo_t *first = *(routerinfo_t **)a, *second = *(routerinfo_t **)b; return fast_memcmp(first->cache_info.identity_digest, @@ -5422,153 +4946,10 @@ _compare_routerinfo_by_id_digest(const void **a, const void **b) void routers_sort_by_identity(smartlist_t *routers) { - smartlist_sort(routers, _compare_routerinfo_by_id_digest); -} - -/** A routerset specifies constraints on a set of possible routerinfos, based - * on their names, identities, or addresses. It is optimized for determining - * whether a router is a member or not, in O(1+P) time, where P is the number - * of address policy constraints. */ -struct routerset_t { - /** A list of strings for the elements of the policy. Each string is either - * a nickname, a hexadecimal identity fingerprint, or an address policy. A - * router belongs to the set if its nickname OR its identity OR its address - * matches an entry here. */ - smartlist_t *list; - /** A map from lowercase nicknames of routers in the set to (void*)1 */ - strmap_t *names; - /** A map from identity digests routers in the set to (void*)1 */ - digestmap_t *digests; - /** An address policy for routers in the set. For implementation reasons, - * a router belongs to the set if it is _rejected_ by this policy. */ - smartlist_t *policies; - - /** A human-readable description of what this routerset is for. Used in - * log messages. */ - char *description; - - /** A list of the country codes in this set. */ - smartlist_t *country_names; - /** Total number of countries we knew about when we built <b>countries</b>.*/ - int n_countries; - /** Bit array mapping the return value of geoip_get_country() to 1 iff the - * country is a member of this routerset. Note that we MUST call - * routerset_refresh_countries() whenever the geoip country list is - * reloaded. */ - bitarray_t *countries; -}; - -/** Return a new empty routerset. */ -routerset_t * -routerset_new(void) -{ - routerset_t *result = tor_malloc_zero(sizeof(routerset_t)); - result->list = smartlist_new(); - result->names = strmap_new(); - result->digests = digestmap_new(); - result->policies = smartlist_new(); - result->country_names = smartlist_new(); - return result; -} - -/** If <b>c</b> is a country code in the form {cc}, return a newly allocated - * string holding the "cc" part. Else, return NULL. */ -static char * -routerset_get_countryname(const char *c) -{ - char *country; - - if (strlen(c) < 4 || c[0] !='{' || c[3] !='}') - return NULL; - - country = tor_strndup(c+1, 2); - tor_strlower(country); - return country; -} - -/** Update the routerset's <b>countries</b> bitarray_t. Called whenever - * the GeoIP database is reloaded. - */ -void -routerset_refresh_countries(routerset_t *target) -{ - int cc; - bitarray_free(target->countries); - - if (!geoip_is_loaded()) { - target->countries = NULL; - target->n_countries = 0; - return; - } - target->n_countries = geoip_get_n_countries(); - target->countries = bitarray_init_zero(target->n_countries); - SMARTLIST_FOREACH_BEGIN(target->country_names, const char *, country) { - cc = geoip_get_country(country); - if (cc >= 0) { - tor_assert(cc < target->n_countries); - bitarray_set(target->countries, cc); - } else { - log(LOG_WARN, LD_CONFIG, "Country code '%s' is not recognized.", - country); - } - } SMARTLIST_FOREACH_END(country); -} - -/** Parse the string <b>s</b> to create a set of routerset entries, and add - * them to <b>target</b>. In log messages, refer to the string as - * <b>description</b>. Return 0 on success, -1 on failure. - * - * Three kinds of elements are allowed in routersets: nicknames, IP address - * patterns, and fingerprints. They may be surrounded by optional space, and - * must be separated by commas. - */ -int -routerset_parse(routerset_t *target, const char *s, const char *description) -{ - int r = 0; - int added_countries = 0; - char *countryname; - smartlist_t *list = smartlist_new(); - smartlist_split_string(list, s, ",", - SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(list, char *, nick) { - addr_policy_t *p; - if (is_legal_hexdigest(nick)) { - char d[DIGEST_LEN]; - if (*nick == '$') - ++nick; - log_debug(LD_CONFIG, "Adding identity %s to %s", nick, description); - base16_decode(d, sizeof(d), nick, HEX_DIGEST_LEN); - digestmap_set(target->digests, d, (void*)1); - } else if (is_legal_nickname(nick)) { - log_debug(LD_CONFIG, "Adding nickname %s to %s", nick, description); - strmap_set_lc(target->names, nick, (void*)1); - } else if ((countryname = routerset_get_countryname(nick)) != NULL) { - log_debug(LD_CONFIG, "Adding country %s to %s", nick, - description); - smartlist_add(target->country_names, countryname); - added_countries = 1; - } else if ((strchr(nick,'.') || strchr(nick, '*')) && - (p = router_parse_addr_policy_item_from_string( - nick, ADDR_POLICY_REJECT))) { - log_debug(LD_CONFIG, "Adding address %s to %s", nick, description); - smartlist_add(target->policies, p); - } else { - log_warn(LD_CONFIG, "Entry '%s' in %s is misformed.", nick, - description); - r = -1; - tor_free(nick); - SMARTLIST_DEL_CURRENT(list, nick); - } - } SMARTLIST_FOREACH_END(nick); - smartlist_add_all(target->list, list); - smartlist_free(list); - if (added_countries) - routerset_refresh_countries(target); - return r; + smartlist_sort(routers, compare_routerinfo_by_id_digest_); } -/** Called when we change a node set, or when we reload the geoip list: +/** Called when we change a node set, or when we reload the geoip IPv4 list: * recompute all country info in all configuration node sets and in the * routerlist. */ void @@ -5584,303 +4965,12 @@ refresh_all_country_info(void) routerset_refresh_countries(options->ExcludeNodes); if (options->ExcludeExitNodes) routerset_refresh_countries(options->ExcludeExitNodes); - if (options->_ExcludeExitNodesUnion) - routerset_refresh_countries(options->_ExcludeExitNodesUnion); + if (options->ExcludeExitNodesUnion_) + routerset_refresh_countries(options->ExcludeExitNodesUnion_); nodelist_refresh_countries(); } -/** Add all members of the set <b>source</b> to <b>target</b>. */ -void -routerset_union(routerset_t *target, const routerset_t *source) -{ - char *s; - tor_assert(target); - if (!source || !source->list) - return; - s = routerset_to_string(source); - routerset_parse(target, s, "other routerset"); - tor_free(s); -} - -/** Return true iff <b>set</b> lists only nicknames and digests, and includes - * no IP ranges or countries. */ -int -routerset_is_list(const routerset_t *set) -{ - return smartlist_len(set->country_names) == 0 && - smartlist_len(set->policies) == 0; -} - -/** Return true iff we need a GeoIP IP-to-country database to make sense of - * <b>set</b>. */ -int -routerset_needs_geoip(const routerset_t *set) -{ - return set && smartlist_len(set->country_names); -} - -/** Return true iff there are no entries in <b>set</b>. */ -int -routerset_is_empty(const routerset_t *set) -{ - return !set || smartlist_len(set->list) == 0; -} - -/** Helper. Return true iff <b>set</b> contains a router based on the other - * provided fields. Return higher values for more specific subentries: a - * single router is more specific than an address range of routers, which is - * more specific in turn than a country code. - * - * (If country is -1, then we take the country - * from addr.) */ -static int -routerset_contains(const routerset_t *set, const tor_addr_t *addr, - uint16_t orport, - const char *nickname, const char *id_digest, - country_t country) -{ - if (!set || !set->list) - return 0; - if (nickname && strmap_get_lc(set->names, nickname)) - return 4; - if (id_digest && digestmap_get(set->digests, id_digest)) - return 4; - if (addr && compare_tor_addr_to_addr_policy(addr, orport, set->policies) - == ADDR_POLICY_REJECTED) - return 3; - if (set->countries) { - if (country < 0 && addr) - country = geoip_get_country_by_ip(tor_addr_to_ipv4h(addr)); - - if (country >= 0 && country < set->n_countries && - bitarray_is_set(set->countries, country)) - return 2; - } - return 0; -} - -/** Return true iff we can tell that <b>ei</b> is a member of <b>set</b>. */ -int -routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei) -{ - return routerset_contains(set, - &ei->addr, - ei->port, - ei->nickname, - ei->identity_digest, - -1 /*country*/); -} - -/** Return true iff <b>ri</b> is in <b>set</b>. If country is <b>-1</b>, we - * look up the country. */ -int -routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, - country_t country) -{ - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, ri->addr); - return routerset_contains(set, - &addr, - ri->or_port, - ri->nickname, - ri->cache_info.identity_digest, - country); -} - -/** Return true iff <b>rs</b> is in <b>set</b>. If country is <b>-1</b>, we - * look up the country. */ -int -routerset_contains_routerstatus(const routerset_t *set, - const routerstatus_t *rs, - country_t country) -{ - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, rs->addr); - return routerset_contains(set, - &addr, - rs->or_port, - rs->nickname, - rs->identity_digest, - country); -} - -/** Return true iff <b>node</b> is in <b>set</b>. */ -int -routerset_contains_node(const routerset_t *set, const node_t *node) -{ - if (node->rs) - return routerset_contains_routerstatus(set, node->rs, node->country); - else if (node->ri) - return routerset_contains_router(set, node->ri, node->country); - else - return 0; -} - -/** Add every known node_t that is a member of <b>routerset</b> to - * <b>out</b>, but never add any that are part of <b>excludeset</b>. - * If <b>running_only</b>, only add the running ones. */ -void -routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, - const routerset_t *excludeset, int running_only) -{ /* XXXX MOVE */ - tor_assert(out); - if (!routerset || !routerset->list) - return; - - if (routerset_is_list(routerset)) { - /* No routers are specified by type; all are given by name or digest. - * we can do a lookup in O(len(routerset)). */ - SMARTLIST_FOREACH(routerset->list, const char *, name, { - const node_t *node = node_get_by_nickname(name, 1); - if (node) { - if (!running_only || node->is_running) - if (!routerset_contains_node(excludeset, node)) - smartlist_add(out, (void*)node); - } - }); - } else { - /* We need to iterate over the routerlist to get all the ones of the - * right kind. */ - smartlist_t *nodes = nodelist_get_list(); - SMARTLIST_FOREACH(nodes, const node_t *, node, { - if (running_only && !node->is_running) - continue; - if (routerset_contains_node(routerset, node) && - !routerset_contains_node(excludeset, node)) - smartlist_add(out, (void*)node); - }); - } -} - -#if 0 -/** Add to <b>target</b> every node_t from <b>source</b> except: - * - * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in - * <b>include</b>; and - * 2) Don't add it if <b>exclude</b> is non-empty and the relay is - * excluded in a more specific fashion by <b>exclude</b>. - * 3) If <b>running_only</b>, don't add non-running routers. - */ -void -routersets_get_node_disjunction(smartlist_t *target, - const smartlist_t *source, - const routerset_t *include, - const routerset_t *exclude, int running_only) -{ - SMARTLIST_FOREACH(source, const node_t *, node, { - int include_result; - if (running_only && !node->is_running) - continue; - if (!routerset_is_empty(include)) - include_result = routerset_contains_node(include, node); - else - include_result = 1; - - if (include_result) { - int exclude_result = routerset_contains_node(exclude, node); - if (include_result >= exclude_result) - smartlist_add(target, (void*)node); - } - }); -} -#endif - -/** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */ -void -routerset_subtract_nodes(smartlist_t *lst, const routerset_t *routerset) -{ /*XXXX MOVE ? */ - tor_assert(lst); - if (!routerset) - return; - SMARTLIST_FOREACH(lst, const node_t *, node, { - if (routerset_contains_node(routerset, node)) { - //log_debug(LD_DIR, "Subtracting %s",r->nickname); - SMARTLIST_DEL_CURRENT(lst, node); - } - }); -} - -/** Return a new string that when parsed by routerset_parse_string() will - * yield <b>set</b>. */ -char * -routerset_to_string(const routerset_t *set) -{ - if (!set || !set->list) - return tor_strdup(""); - return smartlist_join_strings(set->list, ",", 0, NULL); -} - -/** Helper: return true iff old and new are both NULL, or both non-NULL - * equal routersets. */ -int -routerset_equal(const routerset_t *old, const routerset_t *new) -{ - if (routerset_is_empty(old) && routerset_is_empty(new)) { - /* Two empty sets are equal */ - return 1; - } else if (routerset_is_empty(old) || routerset_is_empty(new)) { - /* An empty set is equal to nothing else. */ - return 0; - } - tor_assert(old != NULL); - tor_assert(new != NULL); - - if (smartlist_len(old->list) != smartlist_len(new->list)) - return 0; - - SMARTLIST_FOREACH(old->list, const char *, cp1, { - const char *cp2 = smartlist_get(new->list, cp1_sl_idx); - if (strcmp(cp1, cp2)) - return 0; - }); - - return 1; -} - -/** Free all storage held in <b>routerset</b>. */ -void -routerset_free(routerset_t *routerset) -{ - if (!routerset) - return; - - SMARTLIST_FOREACH(routerset->list, char *, cp, tor_free(cp)); - smartlist_free(routerset->list); - SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p, - addr_policy_free(p)); - smartlist_free(routerset->policies); - SMARTLIST_FOREACH(routerset->country_names, char *, cp, tor_free(cp)); - smartlist_free(routerset->country_names); - - strmap_free(routerset->names, NULL); - digestmap_free(routerset->digests, NULL); - bitarray_free(routerset->countries); - tor_free(routerset); -} - -/** Refresh the country code of <b>ri</b>. This function MUST be called on - * each router when the GeoIP database is reloaded, and on all new routers. */ -void -node_set_country(node_t *node) -{ - if (node->rs) - node->country = geoip_get_country_by_ip(node->rs->addr); - else if (node->ri) - node->country = geoip_get_country_by_ip(node->ri->addr); - else - node->country = -1; -} - -/** Set the country code of all routers in the routerlist. */ -void -nodelist_refresh_countries(void) /* MOVE */ -{ - smartlist_t *nodes = nodelist_get_list(); - SMARTLIST_FOREACH(nodes, node_t *, node, - node_set_country(node)); -} - /** Determine the routers that are responsible for <b>id</b> (binary) and * add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>. * Return -1 if we're returning an empty smartlist, else return 0. diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 8dcc6eb02..81ba1ac54 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -8,8 +8,8 @@ * \brief Header file for routerlist.c. **/ -#ifndef _TOR_ROUTERLIST_H -#define _TOR_ROUTERLIST_H +#ifndef TOR_ROUTERLIST_H +#define TOR_ROUTERLIST_H int get_n_authorities(dirinfo_type_t type); int trusted_dirs_reload_certs(void); @@ -25,24 +25,25 @@ void authority_cert_dl_failed(const char *id_digest, int status); void authority_certs_fetch_missing(networkstatus_t *status, time_t now); int router_reload_router_list(void); int authority_cert_dl_looks_uncertain(const char *id_digest); -smartlist_t *router_get_trusted_dir_servers(void); +const smartlist_t *router_get_trusted_dir_servers(void); +const smartlist_t *router_get_fallback_dir_servers(void); const routerstatus_t *router_pick_directory_server(dirinfo_type_t type, int flags); -trusted_dir_server_t *router_get_trusteddirserver_by_digest(const char *d); -trusted_dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); +dir_server_t *router_get_trusteddirserver_by_digest(const char *d); +dir_server_t *router_get_fallback_dirserver_by_digest( + const char *digest); +dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int flags); +const routerstatus_t *router_pick_fallback_dirserver(dirinfo_type_t type, + int flags); int router_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, - uint16_t port); -int node_is_unreliable(const node_t *router, int need_uptime, - int need_capacity, int need_guard); uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); uint32_t router_get_advertised_bandwidth_capped(const routerinfo_t *router); @@ -53,8 +54,6 @@ const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -const routerinfo_t *router_get_by_nickname(const char *nickname, - int warn_if_unnamed); int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type); @@ -63,7 +62,6 @@ int router_digest_is_trusted_dir_type(const char *digest, int router_addr_is_trusted_dir(uint32_t addr); int hexdigest_to_digest(const char *hexdigest, char *digest); -const routerinfo_t *router_get_by_hexdigest(const char *hexdigest); const routerinfo_t *router_get_by_id_digest(const char *digest); routerinfo_t *router_get_mutable_by_digest(const char *digest); signed_descriptor_t *router_get_by_descriptor_digest(const char *digest); @@ -80,7 +78,6 @@ void routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old, time_t now); void routerlist_free_all(void); void routerlist_reset_warnings(void); -void router_set_status(const char *digest, int up); static int WRA_WAS_ADDED(was_router_added_t s); static int WRA_WAS_OUTDATED(was_router_added_t s); @@ -133,27 +130,26 @@ void router_load_extrainfo_from_string(const char *s, const char *eos, int descriptor_digests); void routerlist_retry_directory_downloads(time_t now); -int router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, - int need_uptime); int router_exit_policy_rejects_all(const routerinfo_t *router); -trusted_dir_server_t *add_trusted_dir_server(const char *nickname, - const char *address, - uint16_t dir_port, uint16_t or_port, - const char *digest, const char *v3_auth_digest, - dirinfo_type_t type); + +dir_server_t *trusted_dir_server_new(const char *nickname, const char *address, + uint16_t dir_port, uint16_t or_port, + const char *digest, const char *v3_auth_digest, + dirinfo_type_t type, double weight); +dir_server_t *fallback_dir_server_new(const tor_addr_t *addr, + uint16_t dir_port, uint16_t or_port, + const char *id_digest, double weight); +void dir_server_add(dir_server_t *ent); + void authority_cert_free(authority_cert_t *cert); -void clear_trusted_dir_servers(void); +void clear_dir_servers(void); int any_trusted_dir_is_v1_authority(void); void update_consensus_router_descriptor_downloads(time_t now, int is_vote, networkstatus_t *consensus); void update_router_descriptor_downloads(time_t now); void update_all_descriptor_downloads(time_t now); void update_extrainfo_downloads(time_t now); -int router_have_minimum_dir_info(void); -void router_dir_info_changed(void); -const char *get_dir_info_status_string(void); -int count_loading_descriptors_progress(void); void router_reset_descriptor_download_failures(void); int router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2); @@ -166,38 +162,6 @@ void routerlist_assert_ok(const routerlist_t *rl); const char *esc_router_info(const routerinfo_t *router); void routers_sort_by_identity(smartlist_t *routers); -routerset_t *routerset_new(void); -void routerset_refresh_countries(routerset_t *rs); -int routerset_parse(routerset_t *target, const char *s, - const char *description); -void routerset_union(routerset_t *target, const routerset_t *source); -int routerset_is_list(const routerset_t *set); -int routerset_needs_geoip(const routerset_t *set); -int routerset_is_empty(const routerset_t *set); -int routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, - country_t country); -int routerset_contains_routerstatus(const routerset_t *set, - const routerstatus_t *rs, - country_t country); -int routerset_contains_extendinfo(const routerset_t *set, - const extend_info_t *ei); - -int routerset_contains_node(const routerset_t *set, const node_t *node); -void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, - const routerset_t *excludeset, - int running_only); -#if 0 -void routersets_get_node_disjunction(smartlist_t *target, - const smartlist_t *source, - const routerset_t *include, - const routerset_t *exclude, int running_only); -#endif -void routerset_subtract_nodes(smartlist_t *out, - const routerset_t *routerset); - -char *routerset_to_string(const routerset_t *routerset); -int routerset_equal(const routerset_t *old, const routerset_t *new); -void routerset_free(routerset_t *routerset); void refresh_all_country_info(void); int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, @@ -215,6 +179,23 @@ int hex_digest_nickname_decode(const char *hexdigest, char *digest_out, char *nickname_qualifier_out, char *nickname_out); +int hex_digest_nickname_matches(const char *hexdigest, + const char *identity_digest, + const char *nickname, int is_named); + +#ifdef ROUTERLIST_PRIVATE +/** Helper type for choosing routers by bandwidth: contains a union of + * double and uint64_t. Before we call scale_array_elements_to_u64, it holds + * a double; after, it holds a uint64_t. */ +typedef union u64_dbl_t { + uint64_t u64; + double dbl; +} u64_dbl_t; + +int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries); +void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, + uint64_t *total_out); +#endif #endif diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 299d07d37..dca4176d4 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -11,7 +11,7 @@ #include "or.h" #include "config.h" -#include "circuitbuild.h" +#include "circuitstats.h" #include "dirserv.h" #include "dirvote.h" #include "policies.h" @@ -29,8 +29,8 @@ /****************************************************************************/ /** Enumeration of possible token types. The ones starting with K_ correspond - * to directory 'keywords'. _ERR is an error in the tokenizing process, _EOF - * is an end-of-file marker, and _NIL is used to encode not-a-token. + * to directory 'keywords'. ERR_ is an error in the tokenizing process, EOF_ + * is an end-of-file marker, and NIL_ is used to encode not-a-token. */ typedef enum { K_ACCEPT = 0, @@ -43,6 +43,7 @@ typedef enum { K_SIGNED_DIRECTORY, K_SIGNING_KEY, K_ONION_KEY, + K_ONION_KEY_NTOR, K_ROUTER_SIGNATURE, K_PUBLISHED, K_RUNNING_ROUTERS, @@ -66,7 +67,9 @@ typedef enum { K_SERVER_VERSIONS, K_OR_ADDRESS, K_P, + K_P6, K_R, + K_A, K_S, K_V, K_W, @@ -76,6 +79,7 @@ typedef enum { K_CACHES_EXTRA_INFO, K_HIDDEN_SERVICE_DIR, K_ALLOW_SINGLE_HOP_EXITS, + K_IPV6_POLICY, K_DIRREQ_END, K_DIRREQ_V2_IPS, @@ -130,7 +134,7 @@ typedef enum { A_PURPOSE, A_LAST_LISTED, - _A_UNKNOWN, + A_UNKNOWN_, R_RENDEZVOUS_SERVICE_DESCRIPTOR, R_VERSION, @@ -151,13 +155,13 @@ typedef enum { C_DESCRIPTOR_COOKIE, C_CLIENT_KEY, - _ERR, - _EOF, - _NIL + ERR_, + EOF_, + NIL_ } directory_keyword; #define MIN_ANNOTATION A_PURPOSE -#define MAX_ANNOTATION _A_UNKNOWN +#define MAX_ANNOTATION A_UNKNOWN_ /** Structure to hold a single directory token. * @@ -180,7 +184,7 @@ typedef struct directory_token_t { crypto_pk_t *key; /**< For public keys only. Heap-allocated. */ - char *error; /**< For _ERR tokens only. */ + char *error; /**< For ERR_ tokens only. */ } directory_token_t; /* ********************************************************************** */ @@ -234,7 +238,7 @@ typedef struct token_rule_t { */ /** Appears to indicate the end of a table. */ -#define END_OF_TABLE { NULL, _NIL, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 } +#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 } /** An item with no restrictions: used for obsolete document types */ #define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 } /** An item with no restrictions on multiplicity or location. */ @@ -270,8 +274,10 @@ static token_rule_t routerdesc_token_table[] = { T0N("reject6", K_REJECT6, ARGS, NO_OBJ ), T0N("accept6", K_ACCEPT6, ARGS, NO_OBJ ), T1_START( "router", K_ROUTER, GE(5), NO_OBJ ), + T01("ipv6-policy", K_IPV6_POLICY, CONCAT_ARGS, NO_OBJ), T1( "signing-key", K_SIGNING_KEY, NO_ARGS, NEED_KEY_1024 ), T1( "onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024 ), + T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), T1_END( "router-signature", K_ROUTER_SIGNATURE, NO_ARGS, NEED_OBJ ), T1( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), T01("uptime", K_UPTIME, GE(1), NO_OBJ ), @@ -338,6 +344,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 ), @@ -371,25 +378,6 @@ static token_rule_t dir_footer_token_table[] = { END_OF_TABLE }; -/** List of tokens recognized in v1 directory headers/footers. */ -static token_rule_t dir_token_table[] = { - /* don't enforce counts; this is obsolete. */ - T( "network-status", K_NETWORK_STATUS, NO_ARGS, NO_OBJ ), - T( "directory-signature", K_DIRECTORY_SIGNATURE, ARGS, NEED_OBJ ), - T( "recommended-software",K_RECOMMENDED_SOFTWARE,CONCAT_ARGS, NO_OBJ ), - T( "signed-directory", K_SIGNED_DIRECTORY, NO_ARGS, NO_OBJ ), - - T( "running-routers", K_RUNNING_ROUTERS, ARGS, NO_OBJ ), - T( "router-status", K_ROUTER_STATUS, ARGS, NO_OBJ ), - T( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), - T( "opt", K_OPT, CONCAT_ARGS, OBJ_OK ), - T( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), - T( "dir-signing-key", K_DIR_SIGNING_KEY, ARGS, OBJ_OK ), - T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), - - END_OF_TABLE -}; - /** List of tokens common to V3 authority certificates and V3 consensuses. */ #define CERTIFICATE_MEMBERS \ T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, \ @@ -522,8 +510,11 @@ 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), + T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), + T0N("a", K_A, GE(1), NO_OBJ ), T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("p", K_P, CONCAT_ARGS, NO_OBJ ), + T01("p6", K_P6, CONCAT_ARGS, NO_OBJ ), A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), END_OF_TABLE }; @@ -532,7 +523,8 @@ static token_rule_t microdesc_token_table[] = { /* static function prototypes */ static int router_add_exit_policy(routerinfo_t *router,directory_token_t *tok); -static addr_policy_t *router_parse_addr_policy(directory_token_t *tok); +static addr_policy_t *router_parse_addr_policy(directory_token_t *tok, + unsigned fmt_flags); static addr_policy_t *router_parse_addr_policy_private(directory_token_t *tok); static int router_get_hash_impl(const char *s, size_t s_len, char *digest, @@ -546,10 +538,10 @@ static int router_get_hashes_impl(const char *s, size_t s_len, static void token_clear(directory_token_t *tok); static smartlist_t *find_all_by_keyword(smartlist_t *s, directory_keyword k); static smartlist_t *find_all_exitpolicy(smartlist_t *s); -static directory_token_t *_find_by_keyword(smartlist_t *s, +static directory_token_t *find_by_keyword_(smartlist_t *s, directory_keyword keyword, const char *keyword_str); -#define find_by_keyword(s, keyword) _find_by_keyword((s), (keyword), #keyword) +#define find_by_keyword(s, keyword) find_by_keyword_((s), (keyword), #keyword) static directory_token_t *find_opt_by_keyword(smartlist_t *s, directory_keyword keyword); @@ -573,7 +565,6 @@ static int check_signature_token(const char *digest, crypto_pk_t *pkey, int flags, const char *doctype); -static crypto_pk_t *find_dir_signing_key(const char *str, const char *eos); #undef DEBUG_AREA_ALLOC @@ -816,218 +807,6 @@ tor_version_is_obsolete(const char *myversion, const char *versionlist) return ret; } -/** Read a signed directory from <b>str</b>. If it's well-formed, return 0. - * Otherwise, return -1. If we're a directory cache, cache it. - */ -int -router_parse_directory(const char *str) -{ - directory_token_t *tok; - char digest[DIGEST_LEN]; - time_t published_on; - int r; - const char *end, *cp, *str_dup = str; - smartlist_t *tokens = NULL; - crypto_pk_t *declared_key = NULL; - memarea_t *area = memarea_new(); - - /* XXXX This could be simplified a lot, but it will all go away - * once pre-0.1.1.8 is obsolete, and for now it's better not to - * touch it. */ - - if (router_get_dir_hash(str, digest)) { - log_warn(LD_DIR, "Unable to compute digest of directory"); - goto err; - } - log_debug(LD_DIR,"Received directory hashes to %s",hex_str(digest,4)); - - /* Check signature first, before we try to tokenize. */ - cp = str; - while (cp && (end = strstr(cp+1, "\ndirectory-signature"))) - cp = end; - if (cp == str || !cp) { - log_warn(LD_DIR, "No signature found on directory."); goto err; - } - ++cp; - tokens = smartlist_new(); - if (tokenize_string(area,cp,strchr(cp,'\0'),tokens,dir_token_table,0)) { - log_warn(LD_DIR, "Error tokenizing directory signature"); goto err; - } - if (smartlist_len(tokens) != 1) { - log_warn(LD_DIR, "Unexpected number of tokens in signature"); goto err; - } - tok=smartlist_get(tokens,0); - if (tok->tp != K_DIRECTORY_SIGNATURE) { - log_warn(LD_DIR,"Expected a single directory signature"); goto err; - } - declared_key = find_dir_signing_key(str, str+strlen(str)); - note_crypto_pk_op(VERIFY_DIR); - if (check_signature_token(digest, DIGEST_LEN, tok, declared_key, - CST_CHECK_AUTHORITY, "directory")<0) - goto err; - - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_clear(tokens); - memarea_clear(area); - - /* Now try to parse the first part of the directory. */ - if ((end = strstr(str,"\nrouter "))) { - ++end; - } else if ((end = strstr(str, "\ndirectory-signature"))) { - ++end; - } else { - end = str + strlen(str); - } - - if (tokenize_string(area,str,end,tokens,dir_token_table,0)) { - log_warn(LD_DIR, "Error tokenizing directory"); goto err; - } - - tok = find_by_keyword(tokens, K_PUBLISHED); - tor_assert(tok->n_args == 1); - - if (parse_iso_time(tok->args[0], &published_on) < 0) { - goto err; - } - - /* Now that we know the signature is okay, and we have a - * publication time, cache the directory. */ - if (directory_caches_v1_dir_info(get_options()) && - !authdir_mode_v1(get_options())) - dirserv_set_cached_directory(str, published_on, 0); - - r = 0; - goto done; - err: - dump_desc(str_dup, "v1 directory"); - r = -1; - done: - if (declared_key) crypto_pk_free(declared_key); - if (tokens) { - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_free(tokens); - } - if (area) { - DUMP_AREA(area, "v1 directory"); - memarea_drop_all(area); - } - return r; -} - -/** Read a signed router status statement from <b>str</b>. If it's - * well-formed, return 0. Otherwise, return -1. If we're a directory cache, - * cache it.*/ -int -router_parse_runningrouters(const char *str) -{ - char digest[DIGEST_LEN]; - directory_token_t *tok; - time_t published_on; - int r = -1; - crypto_pk_t *declared_key = NULL; - smartlist_t *tokens = NULL; - const char *eos = str + strlen(str), *str_dup = str; - memarea_t *area = NULL; - - if (router_get_runningrouters_hash(str, digest)) { - log_warn(LD_DIR, "Unable to compute digest of running-routers"); - goto err; - } - area = memarea_new(); - tokens = smartlist_new(); - if (tokenize_string(area,str,eos,tokens,dir_token_table,0)) { - log_warn(LD_DIR, "Error tokenizing running-routers"); goto err; - } - tok = smartlist_get(tokens,0); - if (tok->tp != K_NETWORK_STATUS) { - log_warn(LD_DIR, "Network-status starts with wrong token"); - goto err; - } - - tok = find_by_keyword(tokens, K_PUBLISHED); - tor_assert(tok->n_args == 1); - if (parse_iso_time(tok->args[0], &published_on) < 0) { - goto err; - } - if (!(tok = find_opt_by_keyword(tokens, K_DIRECTORY_SIGNATURE))) { - log_warn(LD_DIR, "Missing signature on running-routers"); - goto err; - } - declared_key = find_dir_signing_key(str, eos); - note_crypto_pk_op(VERIFY_DIR); - if (check_signature_token(digest, DIGEST_LEN, tok, declared_key, - CST_CHECK_AUTHORITY, "running-routers") - < 0) - goto err; - - /* Now that we know the signature is okay, and we have a - * publication time, cache the list. */ - if (get_options()->DirPort_set && !authdir_mode_v1(get_options())) - dirserv_set_cached_directory(str, published_on, 1); - - r = 0; - err: - dump_desc(str_dup, "v1 running-routers"); - if (declared_key) crypto_pk_free(declared_key); - if (tokens) { - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - smartlist_free(tokens); - } - if (area) { - DUMP_AREA(area, "v1 running-routers"); - memarea_drop_all(area); - } - return r; -} - -/** Given a directory or running-routers string in <b>str</b>, try to - * find the its dir-signing-key token (if any). If this token is - * present, extract and return the key. Return NULL on failure. */ -static crypto_pk_t * -find_dir_signing_key(const char *str, const char *eos) -{ - const char *cp; - directory_token_t *tok; - crypto_pk_t *key = NULL; - memarea_t *area = NULL; - tor_assert(str); - tor_assert(eos); - - /* Is there a dir-signing-key in the directory? */ - cp = tor_memstr(str, eos-str, "\nopt dir-signing-key"); - if (!cp) - cp = tor_memstr(str, eos-str, "\ndir-signing-key"); - if (!cp) - return NULL; - ++cp; /* Now cp points to the start of the token. */ - - area = memarea_new(); - tok = get_next_token(area, &cp, eos, dir_token_table); - if (!tok) { - log_warn(LD_DIR, "Unparseable dir-signing-key token"); - goto done; - } - if (tok->tp != K_DIR_SIGNING_KEY) { - log_warn(LD_DIR, "Dir-signing-key token did not parse as expected"); - goto done; - } - - if (tok->key) { - key = tok->key; - tok->key = NULL; /* steal reference. */ - } else { - log_warn(LD_DIR, "Dir-signing-key token contained no key"); - } - - done: - if (tok) token_clear(tok); - if (area) { - DUMP_AREA(area, "dir-signing-key token"); - memarea_drop_all(area); - } - return key; -} - /** Return true iff <b>key</b> is allowed to sign directories. */ static int @@ -1257,6 +1036,43 @@ 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], 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 @@ -1471,6 +1287,17 @@ router_parse_entry_from_string(const char *s, const char *end, router->onion_pkey = tok->key; tok->key = NULL; /* Prevent free */ + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + curve25519_public_key_t k; + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus ntor-onion-key in routerinfo"); + goto err; + } + router->onion_curve25519_pkey = + tor_memdup(&k, sizeof(curve25519_public_key_t)); + } + tok = find_by_keyword(tokens, K_SIGNING_KEY); router->identity_pkey = tok->key; tok->key = NULL; /* Prevent free */ @@ -1513,21 +1340,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); } } @@ -1542,7 +1356,18 @@ router_parse_entry_from_string(const char *s, const char *end, goto err; }); policy_expand_private(&router->exit_policy); - if (policy_is_reject_star(router->exit_policy)) + + if ((tok = find_opt_by_keyword(tokens, K_IPV6_POLICY)) && tok->n_args) { + router->ipv6_exit_policy = parse_short_policy(tok->args[0]); + if (! router->ipv6_exit_policy) { + log_warn(LD_DIR , "Error in ipv6-policy %s", escaped(tok->args[0])); + goto err; + } + } + + if (policy_is_reject_star(router->exit_policy, AF_INET) && + (!router->ipv6_exit_policy || + short_policy_is_reject_star(router->ipv6_exit_policy))) router->policy_is_reject_star = 1; if ((tok = find_opt_by_keyword(tokens, K_FAMILY)) && tok->n_args) { @@ -2060,6 +1885,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; @@ -2067,7 +1900,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, for (i=0; i < tok->n_args; ++i) { int p = smartlist_string_pos(vote->known_flags, tok->args[i]); if (p >= 0) { - vote_rs->flags |= (1<<p); + vote_rs->flags |= (U64_LITERAL(1)<<p); } else { log_warn(LD_DIR, "Flags line had a flag %s not listed in known_flags.", escaped(tok->args[i])); @@ -2112,24 +1945,15 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_assert(tok->n_args == 1); rs->version_known = 1; if (strcmpstart(tok->args[0], "Tor ")) { - rs->version_supports_begindir = 1; - rs->version_supports_extrainfo_upload = 1; - rs->version_supports_conditional_consensus = 1; rs->version_supports_microdesc_cache = 1; rs->version_supports_optimistic_data = 1; } else { - rs->version_supports_begindir = - tor_version_as_new_as(tok->args[0], "0.2.0.1-alpha"); - rs->version_supports_extrainfo_upload = - tor_version_as_new_as(tok->args[0], "0.2.0.0-alpha-dev (r10070)"); - rs->version_supports_v3_dir = - tor_version_as_new_as(tok->args[0], "0.2.0.8-alpha"); - rs->version_supports_conditional_consensus = - tor_version_as_new_as(tok->args[0], "0.2.1.1-alpha"); rs->version_supports_microdesc_cache = tor_version_supports_microdescriptors(tok->args[0]); rs->version_supports_optimistic_data = tor_version_as_new_as(tok->args[0], "0.2.3.1-alpha"); + rs->version_supports_extend2_cells = + tor_version_as_new_as(tok->args[0], "0.2.4.7-alpha"); } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); @@ -2240,7 +2064,7 @@ compare_routerstatus_entries(const void **_a, const void **_b) /** Helper: used in call to _smartlist_uniq to clear out duplicate entries. */ static void -_free_duplicate_routerstatus_entry(void *e) +free_duplicate_routerstatus_entry_(void *e) { log_warn(LD_DIR, "Network-status has two entries for the same router. " @@ -2381,7 +2205,7 @@ networkstatus_v2_parse_from_string(const char *s) } smartlist_sort(ns->entries, compare_routerstatus_entries); smartlist_uniq(ns->entries, compare_routerstatus_entries, - _free_duplicate_routerstatus_entry); + free_duplicate_routerstatus_entry_); if (tokenize_string(area,s, NULL, footer_tokens, dir_footer_token_table,0)) { log_warn(LD_DIR, "Error tokenizing network-status footer."); @@ -2981,6 +2805,16 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, log_warn(LD_DIR, "known-flags not in order"); goto err; } + if (ns->type != NS_TYPE_CONSENSUS && + smartlist_len(ns->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) { + /* If we allowed more than 64 flags in votes, then parsing them would make + * us invoke undefined behavior whenever we used 1<<flagnum to do a + * bit-shift. This is only for votes and opinions: consensus users don't + * care about flags they don't recognize, and so don't build a bitfield + * for them. */ + log_warn(LD_DIR, "Too many known-flags in consensus vote or opinion"); + goto err; + } tok = find_opt_by_keyword(tokens, K_PARAMS); if (tok) { @@ -3599,6 +3433,10 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos) /** Parse the addr policy in the string <b>s</b> and return it. If * assume_action is nonnegative, then insert its action (ADDR_POLICY_ACCEPT or * ADDR_POLICY_REJECT) for items that specify no action. + * + * The addr_policy_t returned by this function can have its address set to + * AF_UNSPEC for '*'. Use policy_expand_unspec() to turn this into a pair + * of AF_INET and AF_INET6 items. */ addr_policy_t * router_parse_addr_policy_item_from_string(const char *s, int assume_action) @@ -3628,7 +3466,7 @@ router_parse_addr_policy_item_from_string(const char *s, int assume_action) eos = cp + strlen(cp); area = memarea_new(); tok = get_next_token(area, &cp, eos, routerdesc_token_table); - if (tok->tp == _ERR) { + if (tok->tp == ERR_) { log_warn(LD_DIR, "Error reading address policy: %s", tok->error); goto err; } @@ -3638,7 +3476,7 @@ router_parse_addr_policy_item_from_string(const char *s, int assume_action) goto err; } - r = router_parse_addr_policy(tok); + r = router_parse_addr_policy(tok, TAPMP_EXTENDED_STAR); goto done; err: r = NULL; @@ -3657,7 +3495,7 @@ static int router_add_exit_policy(routerinfo_t *router, directory_token_t *tok) { addr_policy_t *newe; - newe = router_parse_addr_policy(tok); + newe = router_parse_addr_policy(tok, 0); if (!newe) return -1; if (! router->exit_policy) @@ -3682,7 +3520,7 @@ router_add_exit_policy(routerinfo_t *router, directory_token_t *tok) /** Given a K_ACCEPT or K_REJECT token and a router, create and return * a new exit_policy_t corresponding to the token. */ static addr_policy_t * -router_parse_addr_policy(directory_token_t *tok) +router_parse_addr_policy(directory_token_t *tok, unsigned fmt_flags) { addr_policy_t newe; char *arg; @@ -3704,7 +3542,7 @@ router_parse_addr_policy(directory_token_t *tok) else newe.policy_type = ADDR_POLICY_ACCEPT; - if (tor_addr_parse_mask_ports(arg, &newe.addr, &newe.maskbits, + if (tor_addr_parse_mask_ports(arg, fmt_flags, &newe.addr, &newe.maskbits, &newe.prt_min, &newe.prt_max) < 0) { log_warn(LD_DIR,"Couldn't parse line %s. Dropping", escaped(arg)); return NULL; @@ -3781,14 +3619,14 @@ token_clear(directory_token_t *tok) STMT_BEGIN \ if (tok) token_clear(tok); \ tok = ALLOC_ZERO(sizeof(directory_token_t)); \ - tok->tp = _ERR; \ + tok->tp = ERR_; \ tok->error = STRDUP(msg); \ goto done_tokenizing; \ STMT_END /** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>. - * Return <b>tok</b> on success, or a new _ERR token if the token didn't + * Return <b>tok</b> on success, or a new ERR_ token if the token didn't * conform to the syntax we wanted. **/ static INLINE directory_token_t * @@ -3907,7 +3745,7 @@ get_next_token(memarea_t *area, tor_assert(area); tok = ALLOC_ZERO(sizeof(directory_token_t)); - tok->tp = _ERR; + tok->tp = ERR_; /* Set *s to first token, eol to end-of-line, next to after first token */ *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */ @@ -3964,10 +3802,10 @@ get_next_token(memarea_t *area, } } - if (tok->tp == _ERR) { + if (tok->tp == ERR_) { /* No keyword matched; call it an "K_opt" or "A_unrecognized" */ if (**s == '@') - tok->tp = _A_UNKNOWN; + tok->tp = A_UNKNOWN_; else tok->tp = K_OPT; tok->args = ALLOC(sizeof(char*)); @@ -4057,7 +3895,7 @@ tokenize_string(memarea_t *area, { const char **s; directory_token_t *tok = NULL; - int counts[_NIL]; + int counts[NIL_]; int i; int first_nonannotation; int prev_len = smartlist_len(out); @@ -4066,14 +3904,14 @@ tokenize_string(memarea_t *area, s = &start; if (!end) end = start+strlen(start); - for (i = 0; i < _NIL; ++i) + for (i = 0; i < NIL_; ++i) counts[i] = 0; SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]); - while (*s < end && (!tok || tok->tp != _EOF)) { + while (*s < end && (!tok || tok->tp != EOF_)) { tok = get_next_token(area, s, end, table); - if (tok->tp == _ERR) { + if (tok->tp == ERR_) { log_warn(LD_DIR, "parse error: %s", tok->error); token_clear(tok); return -1; @@ -4163,7 +4001,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) * with an assert if no such keyword is found. */ static directory_token_t * -_find_by_keyword(smartlist_t *s, directory_keyword keyword, +find_by_keyword_(smartlist_t *s, directory_keyword keyword, const char *keyword_as_string) { directory_token_t *tok = find_opt_by_keyword(s, keyword); @@ -4251,8 +4089,8 @@ router_get_hash_impl_helper(const char *s, size_t s_len, /** Compute the digest of the substring of <b>s</b> taken from the first * occurrence of <b>start_str</b> through the first instance of c after the - * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in - * <b>digest</b>; return 0 on success. + * first subsequent occurrence of <b>end_str</b>; store the 20-byte or 32-byte + * result in <b>digest</b>; return 0 on success. * * If no such substring exists, return -1. */ @@ -4421,6 +4259,25 @@ microdescs_parse_from_string(const char *s, const char *eos, md->onion_pkey = tok->key; tok->key = NULL; + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + curve25519_public_key_t k; + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); + goto next; + } + md->onion_curve25519_pkey = + tor_memdup(&k, sizeof(curve25519_public_key_t)); + } + + { + 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(); @@ -4437,6 +4294,9 @@ microdescs_parse_from_string(const char *s, const char *eos, if ((tok = find_opt_by_keyword(tokens, K_P))) { md->exit_policy = parse_short_policy(tok->args[0]); } + if ((tok = find_opt_by_keyword(tokens, K_P6))) { + md->ipv6_exit_policy = parse_short_policy(tok->args[0]); + } crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); @@ -4654,7 +4514,7 @@ tor_version_same_series(tor_version_t *a, tor_version_t *b) * if _a precedes _b, 1 if _b precedes _a, and 0 if they are equivalent. * Used to sort a list of versions. */ static int -_compare_tor_version_str_ptr(const void **_a, const void **_b) +compare_tor_version_str_ptr_(const void **_a, const void **_b) { const char *a = *_a, *b = *_b; int ca, cb; @@ -4678,10 +4538,10 @@ _compare_tor_version_str_ptr(const void **_a, const void **_b) void sort_version_list(smartlist_t *versions, int remove_duplicates) { - smartlist_sort(versions, _compare_tor_version_str_ptr); + smartlist_sort(versions, compare_tor_version_str_ptr_); if (remove_duplicates) - smartlist_uniq(versions, _compare_tor_version_str_ptr, _tor_free); + smartlist_uniq(versions, compare_tor_version_str_ptr_, tor_free_); } /** Parse and validate the ASCII-encoded v2 descriptor in <b>desc</b>, diff --git a/src/or/routerparse.h b/src/or/routerparse.h index c6382a7f6..256fcca57 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -9,8 +9,8 @@ * \brief Header file for routerparse.c. **/ -#ifndef _TOR_ROUTERPARSE_H -#define _TOR_ROUTERPARSE_H +#ifndef TOR_ROUTERPARSE_H +#define TOR_ROUTERPARSE_H int router_get_router_hash(const char *s, size_t s_len, char *digest); int router_get_dir_hash(const char *s, char *digest); @@ -31,8 +31,6 @@ int router_parse_list_from_string(const char **s, const char *eos, int is_extrainfo, int allow_annotations, const char *prepend_annotations); -int router_parse_runningrouters(const char *str); -int router_parse_directory(const char *str); routerinfo_t *router_parse_entry_from_string(const char *s, const char *end, int cache_copy, diff --git a/src/or/routerset.c b/src/or/routerset.c new file mode 100644 index 000000000..a495863d8 --- /dev/null +++ b/src/or/routerset.c @@ -0,0 +1,427 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "geoip.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerparse.h" +#include "routerset.h" + +/** A routerset specifies constraints on a set of possible routerinfos, based + * on their names, identities, or addresses. It is optimized for determining + * whether a router is a member or not, in O(1+P) time, where P is the number + * of address policy constraints. */ +struct routerset_t { + /** A list of strings for the elements of the policy. Each string is either + * a nickname, a hexadecimal identity fingerprint, or an address policy. A + * router belongs to the set if its nickname OR its identity OR its address + * matches an entry here. */ + smartlist_t *list; + /** A map from lowercase nicknames of routers in the set to (void*)1 */ + strmap_t *names; + /** A map from identity digests routers in the set to (void*)1 */ + digestmap_t *digests; + /** An address policy for routers in the set. For implementation reasons, + * a router belongs to the set if it is _rejected_ by this policy. */ + smartlist_t *policies; + + /** A human-readable description of what this routerset is for. Used in + * log messages. */ + char *description; + + /** A list of the country codes in this set. */ + smartlist_t *country_names; + /** Total number of countries we knew about when we built <b>countries</b>.*/ + int n_countries; + /** Bit array mapping the return value of geoip_get_country() to 1 iff the + * country is a member of this routerset. Note that we MUST call + * routerset_refresh_countries() whenever the geoip country list is + * reloaded. */ + bitarray_t *countries; +}; + +/** Return a new empty routerset. */ +routerset_t * +routerset_new(void) +{ + routerset_t *result = tor_malloc_zero(sizeof(routerset_t)); + result->list = smartlist_new(); + result->names = strmap_new(); + result->digests = digestmap_new(); + result->policies = smartlist_new(); + result->country_names = smartlist_new(); + return result; +} + +/** If <b>c</b> is a country code in the form {cc}, return a newly allocated + * string holding the "cc" part. Else, return NULL. */ +static char * +routerset_get_countryname(const char *c) +{ + char *country; + + if (strlen(c) < 4 || c[0] !='{' || c[3] !='}') + return NULL; + + country = tor_strndup(c+1, 2); + tor_strlower(country); + return country; +} + +/** Update the routerset's <b>countries</b> bitarray_t. Called whenever + * the GeoIP IPv4 database is reloaded. + */ +void +routerset_refresh_countries(routerset_t *target) +{ + int cc; + bitarray_free(target->countries); + + if (!geoip_is_loaded(AF_INET)) { + target->countries = NULL; + target->n_countries = 0; + return; + } + target->n_countries = geoip_get_n_countries(); + target->countries = bitarray_init_zero(target->n_countries); + SMARTLIST_FOREACH_BEGIN(target->country_names, const char *, country) { + cc = geoip_get_country(country); + if (cc >= 0) { + tor_assert(cc < target->n_countries); + bitarray_set(target->countries, cc); + } else { + log(LOG_WARN, LD_CONFIG, "Country code '%s' is not recognized.", + country); + } + } SMARTLIST_FOREACH_END(country); +} + +/** Parse the string <b>s</b> to create a set of routerset entries, and add + * them to <b>target</b>. In log messages, refer to the string as + * <b>description</b>. Return 0 on success, -1 on failure. + * + * Three kinds of elements are allowed in routersets: nicknames, IP address + * patterns, and fingerprints. They may be surrounded by optional space, and + * must be separated by commas. + */ +int +routerset_parse(routerset_t *target, const char *s, const char *description) +{ + int r = 0; + int added_countries = 0; + char *countryname; + smartlist_t *list = smartlist_new(); + smartlist_split_string(list, s, ",", + SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(list, char *, nick) { + addr_policy_t *p; + if (is_legal_hexdigest(nick)) { + char d[DIGEST_LEN]; + if (*nick == '$') + ++nick; + log_debug(LD_CONFIG, "Adding identity %s to %s", nick, description); + base16_decode(d, sizeof(d), nick, HEX_DIGEST_LEN); + digestmap_set(target->digests, d, (void*)1); + } else if (is_legal_nickname(nick)) { + log_debug(LD_CONFIG, "Adding nickname %s to %s", nick, description); + strmap_set_lc(target->names, nick, (void*)1); + } else if ((countryname = routerset_get_countryname(nick)) != NULL) { + log_debug(LD_CONFIG, "Adding country %s to %s", nick, + description); + smartlist_add(target->country_names, countryname); + added_countries = 1; + } else if ((strchr(nick,'.') || strchr(nick, '*')) && + (p = router_parse_addr_policy_item_from_string( + nick, ADDR_POLICY_REJECT))) { + log_debug(LD_CONFIG, "Adding address %s to %s", nick, description); + smartlist_add(target->policies, p); + } else { + log_warn(LD_CONFIG, "Entry '%s' in %s is misformed.", nick, + description); + r = -1; + tor_free(nick); + SMARTLIST_DEL_CURRENT(list, nick); + } + } SMARTLIST_FOREACH_END(nick); + policy_expand_unspec(&target->policies); + smartlist_add_all(target->list, list); + smartlist_free(list); + if (added_countries) + routerset_refresh_countries(target); + return r; +} + +/** Add all members of the set <b>source</b> to <b>target</b>. */ +void +routerset_union(routerset_t *target, const routerset_t *source) +{ + char *s; + tor_assert(target); + if (!source || !source->list) + return; + s = routerset_to_string(source); + routerset_parse(target, s, "other routerset"); + tor_free(s); +} + +/** Return true iff <b>set</b> lists only nicknames and digests, and includes + * no IP ranges or countries. */ +int +routerset_is_list(const routerset_t *set) +{ + return smartlist_len(set->country_names) == 0 && + smartlist_len(set->policies) == 0; +} + +/** Return true iff we need a GeoIP IP-to-country database to make sense of + * <b>set</b>. */ +int +routerset_needs_geoip(const routerset_t *set) +{ + return set && smartlist_len(set->country_names); +} + +/** Return true iff there are no entries in <b>set</b>. */ +int +routerset_is_empty(const routerset_t *set) +{ + return !set || smartlist_len(set->list) == 0; +} + +/** Helper. Return true iff <b>set</b> contains a router based on the other + * provided fields. Return higher values for more specific subentries: a + * single router is more specific than an address range of routers, which is + * more specific in turn than a country code. + * + * (If country is -1, then we take the country + * from addr.) */ +static int +routerset_contains(const routerset_t *set, const tor_addr_t *addr, + uint16_t orport, + const char *nickname, const char *id_digest, + country_t country) +{ + if (!set || !set->list) + return 0; + if (nickname && strmap_get_lc(set->names, nickname)) + return 4; + if (id_digest && digestmap_get(set->digests, id_digest)) + return 4; + if (addr && compare_tor_addr_to_addr_policy(addr, orport, set->policies) + == ADDR_POLICY_REJECTED) + return 3; + if (set->countries) { + if (country < 0 && addr) + country = geoip_get_country_by_addr(addr); + + if (country >= 0 && country < set->n_countries && + bitarray_is_set(set->countries, country)) + return 2; + } + return 0; +} + +/** Return true iff we can tell that <b>ei</b> is a member of <b>set</b>. */ +int +routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei) +{ + return routerset_contains(set, + &ei->addr, + ei->port, + ei->nickname, + ei->identity_digest, + -1 /*country*/); +} + +/** Return true iff <b>ri</b> is in <b>set</b>. If country is <b>-1</b>, we + * look up the country. */ +int +routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, + country_t country) +{ + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, ri->addr); + return routerset_contains(set, + &addr, + ri->or_port, + ri->nickname, + ri->cache_info.identity_digest, + country); +} + +/** Return true iff <b>rs</b> is in <b>set</b>. If country is <b>-1</b>, we + * look up the country. */ +int +routerset_contains_routerstatus(const routerset_t *set, + const routerstatus_t *rs, + country_t country) +{ + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, rs->addr); + return routerset_contains(set, + &addr, + rs->or_port, + rs->nickname, + rs->identity_digest, + country); +} + +/** Return true iff <b>node</b> is in <b>set</b>. */ +int +routerset_contains_node(const routerset_t *set, const node_t *node) +{ + if (node->rs) + return routerset_contains_routerstatus(set, node->rs, node->country); + else if (node->ri) + return routerset_contains_router(set, node->ri, node->country); + else + return 0; +} + +/** Add every known node_t that is a member of <b>routerset</b> to + * <b>out</b>, but never add any that are part of <b>excludeset</b>. + * If <b>running_only</b>, only add the running ones. */ +void +routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, + const routerset_t *excludeset, int running_only) +{ + tor_assert(out); + if (!routerset || !routerset->list) + return; + + if (routerset_is_list(routerset)) { + /* No routers are specified by type; all are given by name or digest. + * we can do a lookup in O(len(routerset)). */ + SMARTLIST_FOREACH(routerset->list, const char *, name, { + const node_t *node = node_get_by_nickname(name, 1); + if (node) { + if (!running_only || node->is_running) + if (!routerset_contains_node(excludeset, node)) + smartlist_add(out, (void*)node); + } + }); + } else { + /* We need to iterate over the routerlist to get all the ones of the + * right kind. */ + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (running_only && !node->is_running) + continue; + if (routerset_contains_node(routerset, node) && + !routerset_contains_node(excludeset, node)) + smartlist_add(out, (void*)node); + }); + } +} + +#if 0 +/** Add to <b>target</b> every node_t from <b>source</b> except: + * + * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in + * <b>include</b>; and + * 2) Don't add it if <b>exclude</b> is non-empty and the relay is + * excluded in a more specific fashion by <b>exclude</b>. + * 3) If <b>running_only</b>, don't add non-running routers. + */ +void +routersets_get_node_disjunction(smartlist_t *target, + const smartlist_t *source, + const routerset_t *include, + const routerset_t *exclude, int running_only) +{ + SMARTLIST_FOREACH(source, const node_t *, node, { + int include_result; + if (running_only && !node->is_running) + continue; + if (!routerset_is_empty(include)) + include_result = routerset_contains_node(include, node); + else + include_result = 1; + + if (include_result) { + int exclude_result = routerset_contains_node(exclude, node); + if (include_result >= exclude_result) + smartlist_add(target, (void*)node); + } + }); +} +#endif + +/** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */ +void +routerset_subtract_nodes(smartlist_t *lst, const routerset_t *routerset) +{ + tor_assert(lst); + if (!routerset) + return; + SMARTLIST_FOREACH(lst, const node_t *, node, { + if (routerset_contains_node(routerset, node)) { + //log_debug(LD_DIR, "Subtracting %s",r->nickname); + SMARTLIST_DEL_CURRENT(lst, node); + } + }); +} + +/** Return a new string that when parsed by routerset_parse_string() will + * yield <b>set</b>. */ +char * +routerset_to_string(const routerset_t *set) +{ + if (!set || !set->list) + return tor_strdup(""); + return smartlist_join_strings(set->list, ",", 0, NULL); +} + +/** Helper: return true iff old and new are both NULL, or both non-NULL + * equal routersets. */ +int +routerset_equal(const routerset_t *old, const routerset_t *new) +{ + if (routerset_is_empty(old) && routerset_is_empty(new)) { + /* Two empty sets are equal */ + return 1; + } else if (routerset_is_empty(old) || routerset_is_empty(new)) { + /* An empty set is equal to nothing else. */ + return 0; + } + tor_assert(old != NULL); + tor_assert(new != NULL); + + if (smartlist_len(old->list) != smartlist_len(new->list)) + return 0; + + SMARTLIST_FOREACH(old->list, const char *, cp1, { + const char *cp2 = smartlist_get(new->list, cp1_sl_idx); + if (strcmp(cp1, cp2)) + return 0; + }); + + return 1; +} + +/** Free all storage held in <b>routerset</b>. */ +void +routerset_free(routerset_t *routerset) +{ + if (!routerset) + return; + + SMARTLIST_FOREACH(routerset->list, char *, cp, tor_free(cp)); + smartlist_free(routerset->list); + SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p, + addr_policy_free(p)); + smartlist_free(routerset->policies); + SMARTLIST_FOREACH(routerset->country_names, char *, cp, tor_free(cp)); + smartlist_free(routerset->country_names); + + strmap_free(routerset->names, NULL); + digestmap_free(routerset->digests, NULL); + bitarray_free(routerset->countries); + tor_free(routerset); +} + diff --git a/src/or/routerset.h b/src/or/routerset.h new file mode 100644 index 000000000..ad0832e4d --- /dev/null +++ b/src/or/routerset.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file routerlist.h + * \brief Header file for routerset.c + **/ + +#ifndef TOR_ROUTERSET_H +#define TOR_ROUTERSET_H + +routerset_t *routerset_new(void); +void routerset_refresh_countries(routerset_t *rs); +int routerset_parse(routerset_t *target, const char *s, + const char *description); +void routerset_union(routerset_t *target, const routerset_t *source); +int routerset_is_list(const routerset_t *set); +int routerset_needs_geoip(const routerset_t *set); +int routerset_is_empty(const routerset_t *set); +int routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, + country_t country); +int routerset_contains_routerstatus(const routerset_t *set, + const routerstatus_t *rs, + country_t country); +int routerset_contains_extendinfo(const routerset_t *set, + const extend_info_t *ei); + +int routerset_contains_node(const routerset_t *set, const node_t *node); +void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, + const routerset_t *excludeset, + int running_only); +#if 0 +void routersets_get_node_disjunction(smartlist_t *target, + const smartlist_t *source, + const routerset_t *include, + const routerset_t *exclude, int running_only); +#endif +void routerset_subtract_nodes(smartlist_t *out, + const routerset_t *routerset); + +char *routerset_to_string(const routerset_t *routerset); +int routerset_equal(const routerset_t *old, const routerset_t *new); +void routerset_free(routerset_t *routerset); + +#endif + diff --git a/src/or/statefile.c b/src/or/statefile.c new file mode 100644 index 000000000..1429efda1 --- /dev/null +++ b/src/or/statefile.c @@ -0,0 +1,615 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "circuitstats.h" +#include "config.h" +#include "confparse.h" +#include "entrynodes.h" +#include "hibernate.h" +#include "rephist.h" +#include "router.h" +#include "statefile.h" + +/** A list of state-file "abbreviations," for compatibility. */ +static config_abbrev_t state_abbrevs_[] = { + { "AccountingBytesReadInterval", "AccountingBytesReadInInterval", 0, 0 }, + { "HelperNode", "EntryGuard", 0, 0 }, + { "HelperNodeDownSince", "EntryGuardDownSince", 0, 0 }, + { "HelperNodeUnlistedSince", "EntryGuardUnlistedSince", 0, 0 }, + { "EntryNode", "EntryGuard", 0, 0 }, + { "EntryNodeDownSince", "EntryGuardDownSince", 0, 0 }, + { "EntryNodeUnlistedSince", "EntryGuardUnlistedSince", 0, 0 }, + { NULL, NULL, 0, 0}, +}; + +/*XXXX these next two are duplicates or near-duplicates from config.c */ +#define VAR(name,conftype,member,initvalue) \ + { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \ + initvalue } +/** As VAR, but the option name and member name are the same. */ +#define V(member,conftype,initvalue) \ + VAR(#member, conftype, member, initvalue) + +/** Array of "state" variables saved to the ~/.tor/state file. */ +static config_var_t state_vars_[] = { + /* Remember to document these in state-contents.txt ! */ + + V(AccountingBytesReadInInterval, MEMUNIT, NULL), + V(AccountingBytesWrittenInInterval, MEMUNIT, NULL), + V(AccountingExpectedUsage, MEMUNIT, NULL), + V(AccountingIntervalStart, ISOTIME, NULL), + V(AccountingSecondsActive, INTERVAL, NULL), + V(AccountingSecondsToReachSoftLimit,INTERVAL, NULL), + V(AccountingSoftLimitHitAt, ISOTIME, NULL), + V(AccountingBytesAtSoftLimit, MEMUNIT, NULL), + + VAR("EntryGuard", LINELIST_S, EntryGuards, NULL), + VAR("EntryGuardDownSince", LINELIST_S, EntryGuards, NULL), + VAR("EntryGuardUnlistedSince", LINELIST_S, EntryGuards, NULL), + VAR("EntryGuardAddedBy", LINELIST_S, EntryGuards, NULL), + VAR("EntryGuardPathBias", LINELIST_S, EntryGuards, NULL), + V(EntryGuards, LINELIST_V, NULL), + + VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), + V(TransportProxies, LINELIST_V, NULL), + + V(BWHistoryReadEnds, ISOTIME, NULL), + V(BWHistoryReadInterval, UINT, "900"), + V(BWHistoryReadValues, CSV, ""), + V(BWHistoryReadMaxima, CSV, ""), + V(BWHistoryWriteEnds, ISOTIME, NULL), + V(BWHistoryWriteInterval, UINT, "900"), + V(BWHistoryWriteValues, CSV, ""), + V(BWHistoryWriteMaxima, CSV, ""), + V(BWHistoryDirReadEnds, ISOTIME, NULL), + V(BWHistoryDirReadInterval, UINT, "900"), + V(BWHistoryDirReadValues, CSV, ""), + V(BWHistoryDirReadMaxima, CSV, ""), + V(BWHistoryDirWriteEnds, ISOTIME, NULL), + V(BWHistoryDirWriteInterval, UINT, "900"), + V(BWHistoryDirWriteValues, CSV, ""), + V(BWHistoryDirWriteMaxima, CSV, ""), + + V(TorVersion, STRING, NULL), + + V(LastRotatedOnionKey, ISOTIME, NULL), + V(LastWritten, ISOTIME, NULL), + + V(TotalBuildTimes, UINT, NULL), + V(CircuitBuildAbandonedCount, UINT, "0"), + VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), + VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } +}; + +#undef VAR +#undef V + +static int or_state_validate(or_state_t *old_options, or_state_t *options, + int from_setconf, char **msg); + +/** Magic value for or_state_t. */ +#define OR_STATE_MAGIC 0x57A73f57 + +/** "Extra" variable in the state that receives lines we can't parse. This + * lets us preserve options from versions of Tor newer than us. */ +static config_var_t state_extra_var = { + "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL +}; + +/** Configuration format for or_state_t. */ +static const config_format_t state_format = { + sizeof(or_state_t), + OR_STATE_MAGIC, + STRUCT_OFFSET(or_state_t, magic_), + state_abbrevs_, + state_vars_, + (validate_fn_t)or_state_validate, + &state_extra_var, +}; + +/** Persistent serialized state. */ +static or_state_t *global_state = NULL; + +/** Return the persistent state struct for this Tor. */ +or_state_t * +get_or_state(void) +{ + tor_assert(global_state); + return global_state; +} + +/** Return true iff we have loaded the global state for this Tor */ +int +or_state_loaded(void) +{ + return global_state != NULL; +} + +/** Return true if <b>line</b> is a valid state TransportProxy line. + * Return false otherwise. */ +static int +state_transport_line_is_valid(const char *line) +{ + smartlist_t *items = NULL; + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + int r; + + items = smartlist_new(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + if (smartlist_len(items) != 2) { + log_warn(LD_CONFIG, "state: Not enough arguments in TransportProxy line."); + goto err; + } + + addrport = smartlist_get(items, 1); + if (tor_addr_port_lookup(addrport, &addr, &port) < 0) { + log_warn(LD_CONFIG, "state: Could not parse addrport."); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, "state: Transport line did not contain port."); + goto err; + } + + r = 1; + goto done; + + err: + r = 0; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + return r; +} + +/** Return 0 if all TransportProxy lines in <b>state</b> are well + * formed. Otherwise, return -1. */ +static int +validate_transports_in_state(or_state_t *state) +{ + int broken = 0; + config_line_t *line; + + for (line = state->TransportProxies ; line ; line = line->next) { + tor_assert(!strcmp(line->key, "TransportProxy")); + if (!state_transport_line_is_valid(line->value)) + broken = 1; + } + + if (broken) + log_warn(LD_CONFIG, "state: State file seems to be broken."); + + return 0; +} + +/** Return 0 if every setting in <b>state</b> is reasonable, and a + * permissible transition from <b>old_state</b>. Else warn and return -1. + * Should have no side effects, except for normalizing the contents of + * <b>state</b>. + */ +/* XXX from_setconf is here because of bug 238 */ +static int +or_state_validate(or_state_t *old_state, or_state_t *state, + int from_setconf, char **msg) +{ + /* We don't use these; only options do. Still, we need to match that + * signature. */ + (void) from_setconf; + (void) old_state; + + if (entry_guards_parse_state(state, 0, msg)<0) + return -1; + + if (validate_transports_in_state(state)<0) + return -1; + + return 0; +} + +/** Replace the current persistent state with <b>new_state</b> */ +static int +or_state_set(or_state_t *new_state) +{ + char *err = NULL; + int ret = 0; + tor_assert(new_state); + config_free(&state_format, global_state); + global_state = new_state; + if (entry_guards_parse_state(global_state, 1, &err)<0) { + log_warn(LD_GENERAL,"%s",err); + tor_free(err); + ret = -1; + } + if (rep_hist_load_state(global_state, &err)<0) { + log_warn(LD_GENERAL,"Unparseable bandwidth history state: %s",err); + tor_free(err); + ret = -1; + } + if (circuit_build_times_parse_state(&circ_times, global_state) < 0) { + ret = -1; + } + return ret; +} + +/** + * Save a broken state file to a backup location. + */ +static void +or_state_save_broken(char *fname) +{ + int i; + file_status_t status; + char *fname2 = NULL; + for (i = 0; i < 100; ++i) { + tor_asprintf(&fname2, "%s.%d", fname, i); + status = file_status(fname2); + if (status == FN_NOENT) + break; + tor_free(fname2); + } + if (i == 100) { + log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad " + "state files to move aside. Discarding the old state file.", + fname); + unlink(fname); + } else { + log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside " + "to \"%s\". This could be a bug in Tor; please tell " + "the developers.", fname, fname2); + if (rename(fname, fname2) < 0) { + log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The " + "OS gave an error of %s", strerror(errno)); + } + } + tor_free(fname2); +} + +/** Reload the persistent state from disk, generating a new state as needed. + * Return 0 on success, less than 0 on failure. + */ +int +or_state_load(void) +{ + or_state_t *new_state = NULL; + char *contents = NULL, *fname; + char *errmsg = NULL; + int r = -1, badstate = 0; + + fname = get_datadir_fname("state"); + switch (file_status(fname)) { + case FN_FILE: + if (!(contents = read_file_to_str(fname, 0, NULL))) { + log_warn(LD_FS, "Unable to read state file \"%s\"", fname); + goto done; + } + break; + case FN_NOENT: + break; + case FN_ERROR: + case FN_DIR: + default: + log_warn(LD_GENERAL,"State file \"%s\" is not a file? Failing.", fname); + goto done; + } + new_state = tor_malloc_zero(sizeof(or_state_t)); + new_state->magic_ = OR_STATE_MAGIC; + config_init(&state_format, new_state); + if (contents) { + config_line_t *lines=NULL; + int assign_retval; + if (config_get_lines(contents, &lines, 0)<0) + goto done; + assign_retval = config_assign(&state_format, new_state, + lines, 0, 0, &errmsg); + config_free_lines(lines); + if (assign_retval<0) + badstate = 1; + if (errmsg) { + log_warn(LD_GENERAL, "%s", errmsg); + tor_free(errmsg); + } + } + + if (!badstate && or_state_validate(NULL, new_state, 1, &errmsg) < 0) + badstate = 1; + + if (errmsg) { + log_warn(LD_GENERAL, "%s", errmsg); + tor_free(errmsg); + } + + if (badstate && !contents) { + log_warn(LD_BUG, "Uh oh. We couldn't even validate our own default state." + " This is a bug in Tor."); + goto done; + } else if (badstate && contents) { + or_state_save_broken(fname); + + tor_free(contents); + config_free(&state_format, new_state); + + new_state = tor_malloc_zero(sizeof(or_state_t)); + new_state->magic_ = OR_STATE_MAGIC; + config_init(&state_format, new_state); + } else if (contents) { + log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); + } else { + log_info(LD_GENERAL, "Initialized state"); + } + if (or_state_set(new_state) == -1) { + or_state_save_broken(fname); + } + new_state = NULL; + if (!contents) { + global_state->next_write = 0; + or_state_save(time(NULL)); + } + r = 0; + + done: + tor_free(fname); + tor_free(contents); + if (new_state) + config_free(&state_format, new_state); + + return r; +} + +/** Did the last time we tried to write the state file fail? If so, we + * should consider disabling such features as preemptive circuit generation + * to compute circuit-build-time. */ +static int last_state_file_write_failed = 0; + +/** Return whether the state file failed to write last time we tried. */ +int +did_last_state_file_write_fail(void) +{ + return last_state_file_write_failed; +} + +/** If writing the state to disk fails, try again after this many seconds. */ +#define STATE_WRITE_RETRY_INTERVAL 3600 + +/** If we're a relay, how often should we checkpoint our state file even + * if nothing else dirties it? This will checkpoint ongoing stats like + * bandwidth used, per-country user stats, etc. */ +#define STATE_RELAY_CHECKPOINT_INTERVAL (12*60*60) + +/** Write the persistent state to disk. Return 0 for success, <0 on failure. */ +int +or_state_save(time_t now) +{ + char *state, *contents; + char tbuf[ISO_TIME_LEN+1]; + char *fname; + + tor_assert(global_state); + + if (global_state->next_write > now) + return 0; + + /* Call everything else that might dirty the state even more, in order + * to avoid redundant writes. */ + entry_guards_update_state(global_state); + rep_hist_update_state(global_state); + circuit_build_times_update_state(&circ_times, global_state); + if (accounting_is_enabled(get_options())) + accounting_run_housekeeping(now); + + global_state->LastWritten = now; + + tor_free(global_state->TorVersion); + tor_asprintf(&global_state->TorVersion, "Tor %s", get_version()); + + state = config_dump(&state_format, NULL, global_state, 1, 0); + format_local_iso_time(tbuf, now); + tor_asprintf(&contents, + "# Tor state file last generated on %s local time\n" + "# Other times below are in UTC\n" + "# You *do not* need to edit this file.\n\n%s", + tbuf, state); + tor_free(state); + fname = get_datadir_fname("state"); + if (write_str_to_file(fname, contents, 0)<0) { + log_warn(LD_FS, "Unable to write state to file \"%s\"; " + "will try again later", fname); + last_state_file_write_failed = 1; + tor_free(fname); + tor_free(contents); + /* Try again after STATE_WRITE_RETRY_INTERVAL (or sooner, if the state + * changes sooner). */ + global_state->next_write = now + STATE_WRITE_RETRY_INTERVAL; + return -1; + } + + last_state_file_write_failed = 0; + log_info(LD_GENERAL, "Saved state to \"%s\"", fname); + tor_free(fname); + tor_free(contents); + + if (server_mode(get_options())) + global_state->next_write = now + STATE_RELAY_CHECKPOINT_INTERVAL; + else + global_state->next_write = TIME_MAX; + + return 0; +} + +/** Return the config line for transport <b>transport</b> in the current state. + * Return NULL if there is no config line for <b>transport</b>. */ +static config_line_t * +get_transport_in_state_by_name(const char *transport) +{ + or_state_t *or_state = get_or_state(); + config_line_t *line; + config_line_t *ret = NULL; + smartlist_t *items = NULL; + + for (line = or_state->TransportProxies ; line ; line = line->next) { + tor_assert(!strcmp(line->key, "TransportProxy")); + + items = smartlist_new(); + smartlist_split_string(items, line->value, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) != 2) /* broken state */ + goto done; + + if (!strcmp(smartlist_get(items, 0), transport)) { + ret = line; + goto done; + } + + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + items = NULL; + } + + done: + if (items) { + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + } + return ret; +} + +/** Return string containing the address:port part of the + * TransportProxy <b>line</b> for transport <b>transport</b>. + * If the line is corrupted, return NULL. */ +static const char * +get_transport_bindaddr(const char *line, const char *transport) +{ + char *line_tmp = NULL; + + if (strlen(line) < strlen(transport) + 2) { + goto broken_state; + } else { + /* line should start with the name of the transport and a space. + (for example, "obfs2 127.0.0.1:47245") */ + tor_asprintf(&line_tmp, "%s ", transport); + if (strcmpstart(line, line_tmp)) + goto broken_state; + + tor_free(line_tmp); + return (line+strlen(transport)+1); + } + + broken_state: + tor_free(line_tmp); + return NULL; +} + +/** Return a string containing the address:port that a proxy transport + * should bind on. The string is stored on the heap and must be freed + * by the caller of this function. */ +char * +get_stored_bindaddr_for_server_transport(const char *transport) +{ + char *default_addrport = NULL; + const char *stored_bindaddr = NULL; + config_line_t *line = NULL; + + { + /* See if the user explicitly asked for a specific listening + address for this transport. */ + char *conf_bindaddr = get_transport_bindaddr_from_config(transport); + if (conf_bindaddr) + return conf_bindaddr; + } + + line = get_transport_in_state_by_name(transport); + if (!line) /* Found no references in state for this transport. */ + goto no_bindaddr_found; + + stored_bindaddr = get_transport_bindaddr(line->value, transport); + if (stored_bindaddr) /* found stored bindaddr in state file. */ + return tor_strdup(stored_bindaddr); + + no_bindaddr_found: + /** If we didn't find references for this pluggable transport in the + state file, we should instruct the pluggable transport proxy to + listen on INADDR_ANY on a random ephemeral port. */ + tor_asprintf(&default_addrport, "%s:%s", fmt_addr32(INADDR_ANY), "0"); + return default_addrport; +} + +/** Save <b>transport</b> listening on <b>addr</b>:<b>port</b> to + state */ +void +save_transport_to_state(const char *transport, + const tor_addr_t *addr, uint16_t port) +{ + or_state_t *state = get_or_state(); + + char *transport_addrport=NULL; + + /** find where to write on the state */ + config_line_t **next, *line; + + /* see if this transport is already stored in state */ + config_line_t *transport_line = + get_transport_in_state_by_name(transport); + + if (transport_line) { /* if transport already exists in state... */ + const char *prev_bindaddr = /* get its addrport... */ + get_transport_bindaddr(transport_line->value, transport); + transport_addrport = tor_strdup(fmt_addrport(addr, port)); + + /* if transport in state has the same address as this one, life is good */ + if (!strcmp(prev_bindaddr, transport_addrport)) { + log_info(LD_CONFIG, "Transport seems to have spawned on its usual " + "address:port."); + goto done; + } else { /* if addrport in state is different than the one we got */ + log_info(LD_CONFIG, "Transport seems to have spawned on different " + "address:port. Let's update the state file with the new " + "address:port"); + tor_free(transport_line->value); /* free the old line */ + /* replace old addrport line with new line */ + tor_asprintf(&transport_line->value, "%s %s", transport, + fmt_addrport(addr, port)); + } + } else { /* never seen this one before; save it in state for next time */ + log_info(LD_CONFIG, "It's the first time we see this transport. " + "Let's save its address:port"); + next = &state->TransportProxies; + /* find the last TransportProxy line in the state and point 'next' + right after it */ + line = state->TransportProxies; + while (line) { + next = &(line->next); + line = line->next; + } + + /* allocate space for the new line and fill it in */ + *next = line = tor_malloc_zero(sizeof(config_line_t)); + line->key = tor_strdup("TransportProxy"); + tor_asprintf(&line->value, "%s %s", transport, fmt_addrport(addr, port)); + + next = &(line->next); + } + + if (!get_options()->AvoidDiskWrites) + or_state_mark_dirty(state, 0); + + done: + tor_free(transport_addrport); +} + +void +or_state_free_all(void) +{ + config_free(&state_format, global_state); + global_state = NULL; +} + diff --git a/src/or/statefile.h b/src/or/statefile.h new file mode 100644 index 000000000..4770d500d --- /dev/null +++ b/src/or/statefile.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2012, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_STATEFILE_H +#define TOR_STATEFILE_H + +or_state_t *get_or_state(void); +int did_last_state_file_write_fail(void); +int or_state_save(time_t now); + +void save_transport_to_state(const char *transport_name, + const tor_addr_t *addr, uint16_t port); +char *get_stored_bindaddr_for_server_transport(const char *transport); +int or_state_load(void); +int or_state_loaded(void); +void or_state_free_all(void); + +#endif + diff --git a/src/or/status.c b/src/or/status.c index 04cd96eed..1a4e56885 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -10,6 +10,7 @@ #include "config.h" #include "status.h" #include "nodelist.h" +#include "relay.h" #include "router.h" #include "circuitlist.h" #include "main.h" @@ -21,7 +22,7 @@ count_circuits(void) circuit_t *circ; int nr=0; - for (circ = _circuit_get_global_list(); circ; circ = circ->next) + for (circ = circuit_get_global_list_(); circ; circ = circ->next) nr++; return nr; @@ -106,6 +107,11 @@ log_heartbeat(time_t now) "circuits open. I've sent %s and received %s.", uptime, count_circuits(),bw_sent,bw_rcvd); + if (stats_n_data_cells_packaged) + log(LOG_NOTICE, LD_HEARTBEAT, "Average packaged cell fullness: %2.3f%%", + 100*(U64_TO_DBL(stats_n_data_bytes_packaged) / + U64_TO_DBL(stats_n_data_cells_packaged*RELAY_PAYLOAD_SIZE)) ); + tor_free(uptime); tor_free(bw_sent); tor_free(bw_rcvd); diff --git a/src/or/status.h b/src/or/status.h index 189ac789e..de49d751c 100644 --- a/src/or/status.h +++ b/src/or/status.h @@ -1,8 +1,8 @@ /* Copyright (c) 2010-2012, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -#ifndef _TOR_STATUS_H -#define _TOR_STATUS_H +#ifndef TOR_STATUS_H +#define TOR_STATUS_H int log_heartbeat(time_t now); diff --git a/src/or/transports.c b/src/or/transports.c index 4ba239562..031907109 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> @@ -90,6 +94,7 @@ #include "transports.h" #include "util.h" #include "router.h" +#include "statefile.h" static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp); @@ -127,6 +132,224 @@ 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 */ + char *new_transport_addrport = + tor_strdup(fmt_addrport(&t->addr, t->port)); + if (t_tmp->marked_for_removal) { /* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s' " + "but there was already a transport marked for deletion at " + "'%s'. We deleted the old transport and registered the " + "new one.", t->name, new_transport_addrport, + fmt_addrport(&t_tmp->addr, t_tmp->port)); + smartlist_remove(transport_list, t_tmp); + transport_free(t_tmp); + tor_free(new_transport_addrport); + } else { /* *not* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s' " + "but the same transport already exists at '%s'. " + "Skipping.", t->name, new_transport_addrport, + fmt_addrport(&t_tmp->addr, t_tmp->port)); + tor_free(new_transport_addrport); + return -1; + } + tor_free(new_transport_addrport); + } + } + + 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. Skipping.", + t->name, fmt_addrport(&t->addr, t->port)); + transport_free(t); + return -1; + case 1: + log_info(LD_GENERAL, "Succesfully registered transport %s at %s.", + t->name, fmt_addrport(&t->addr, t->port)); + transport_free(t); /* falling */ + return 0; + case 0: + log_info(LD_GENERAL, "Succesfully registered transport %s at %s.", + t->name, fmt_addrport(&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 +440,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 +452,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 +468,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 +479,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 +540,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,22 +578,25 @@ 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(); -} -#ifdef _WIN32 + if (at_least_a_proxy_config_finished) + mark_my_descriptor_dirty("configured managed proxies"); +} /** Attempt to continue configuring managed proxy <b>mp</b>. */ static void configure_proxy(managed_proxy_t *mp) { - int pos; - char stdout_buf[200]; - smartlist_t *lines = NULL; + smartlist_t *proxy_output = NULL; + enum stream_status stream_status = 0; /* if we haven't launched the proxy yet, do it now */ if (mp->conf_state == PT_PROTO_INFANT) { @@ -381,28 +610,25 @@ configure_proxy(managed_proxy_t *mp) tor_assert(mp->conf_state != PT_PROTO_INFANT); tor_assert(mp->process_handle); - pos = tor_read_all_handle(tor_process_get_stdout_pipe(mp->process_handle), - stdout_buf, sizeof(stdout_buf) - 1, NULL); - if (pos < 0) { - log_notice(LD_GENERAL, "Failed to read data from managed proxy '%s'.", - mp->argv[0]); - mp->conf_state = PT_PROTO_BROKEN; + proxy_output = + tor_get_lines_from_handle(tor_process_get_stdout_pipe(mp->process_handle), + &stream_status); + if (!proxy_output) { /* failed to get input from proxy */ + if (stream_status != IO_STREAM_EAGAIN) { /* bad stream status! */ + mp->conf_state = PT_PROTO_BROKEN; + log_warn(LD_GENERAL, "The communication stream of managed proxy '%s' " + "is '%s'. Most probably the managed proxy stopped running. " + "This might be a bug of the managed proxy, a bug of Tor, or " + "a misconfiguration. Please enable logging on your managed " + "proxy and check the logs for errors.", + mp->argv[0], stream_status_to_string(stream_status)); + } + goto done; } - if (pos == 0) /* proxy has nothing interesting to say. */ - return; - - /* End with a null even if there isn't a \r\n at the end */ - /* TODO: What if this is a partial line? */ - stdout_buf[pos] = '\0'; - - /* Split up the buffer */ - lines = smartlist_new(); - tor_split_lines(lines, stdout_buf, pos); - /* Handle lines. */ - SMARTLIST_FOREACH_BEGIN(lines, const char *, line) { + SMARTLIST_FOREACH_BEGIN(proxy_output, const char *, line) { handle_proxy_line(line, mp); if (proxy_configuration_finished(mp)) goto done; @@ -413,123 +639,56 @@ configure_proxy(managed_proxy_t *mp) if (proxy_configuration_finished(mp)) handle_finished_proxy(mp); - if (lines) - smartlist_free(lines); -} - -#else /* _WIN32 */ - -/** Attempt to continue configuring managed proxy <b>mp</b>. */ -static void -configure_proxy(managed_proxy_t *mp) -{ - enum stream_status r; - char stdout_buf[200]; - - /* if we haven't launched the proxy yet, do it now */ - if (mp->conf_state == PT_PROTO_INFANT) { - if (launch_managed_proxy(mp) < 0) { /* launch fail */ - mp->conf_state = PT_PROTO_FAILED_LAUNCH; - handle_finished_proxy(mp); - } - return; - } - - tor_assert(mp->conf_state != PT_PROTO_INFANT); - tor_assert(mp->process_handle); - - while (1) { - r = get_string_from_pipe(tor_process_get_stdout_pipe(mp->process_handle), - stdout_buf, sizeof(stdout_buf) - 1); - - if (r == IO_STREAM_OKAY) { /* got a line; handle it! */ - handle_proxy_line((const char *)stdout_buf, mp); - } else if (r == IO_STREAM_EAGAIN) { /* check back later */ - return; - } else if (r == IO_STREAM_CLOSED || r == IO_STREAM_TERM) { /* snap! */ - log_warn(LD_GENERAL, "Our communication channel with the managed proxy " - "'%s' closed. Most probably application stopped running.", - mp->argv[0]); - mp->conf_state = PT_PROTO_BROKEN; - } else { /* unknown stream status */ - log_warn(LD_BUG, "Unknown stream status '%d' while configuring managed " - "proxy '%s'.", (int)r, mp->argv[0]); - } - - /* if the proxy finished configuring, exit the loop. */ - if (proxy_configuration_finished(mp)) { - handle_finished_proxy(mp); - return; - } + if (proxy_output) { + SMARTLIST_FOREACH(proxy_output, char *, cp, tor_free(cp)); + smartlist_free(proxy_output); } } -#endif /* _WIN32 */ - /** 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)); + log_notice(LD_GENERAL, "Registered server transport '%s' at '%s'", + t->name, fmt_addrport(&t->addr, t->port)); } 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 +701,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); @@ -815,7 +971,7 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) } addrport = smartlist_get(items, 2); - if (tor_addr_port_split(LOG_PROTOCOL_WARN, addrport, &address, &port)<0) { + if (tor_addr_port_split(LOG_WARN, addrport, &address, &port)<0) { log_warn(LD_CONFIG, "Error parsing transport " "address '%s'", addrport); goto err; @@ -907,7 +1063,7 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) } addrport = smartlist_get(items, 3); - if (tor_addr_port_split(LOG_PROTOCOL_WARN, addrport, &address, &port)<0) { + if (tor_addr_port_split(LOG_WARN, addrport, &address, &port)<0) { log_warn(LD_CONFIG, "Error parsing transport " "address '%s'", addrport); goto err; @@ -1039,7 +1195,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp) SMARTLIST_FOREACH_BEGIN(envs, const char *, env_var) { set_environment_variable_in_smartlist(merged_env_vars, env_var, - _tor_free, 1); + tor_free_, 1); } SMARTLIST_FOREACH_END(env_var); env = process_environment_make(merged_env_vars); @@ -1143,7 +1299,7 @@ free_execve_args(char **arg) char **tmp = arg; while (*tmp) /* use the fact that the last element of the array is a NULL pointer to know when to stop freeing */ - _tor_free(*tmp++); + tor_free_(*tmp++); tor_free(arg); } @@ -1181,6 +1337,93 @@ pt_prepare_proxy_list_for_config_read(void) tor_assert(unconfigured_proxies_n == 0); } +/** Return a smartlist containing the ports where our pluggable + * transports are listening. */ +smartlist_t * +get_transport_proxy_ports(void) +{ + smartlist_t *sl = NULL; + + if (!managed_proxy_list) + return NULL; + + /** XXX assume that external proxy ports have been forwarded + manually */ + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, const managed_proxy_t *, mp) { + if (!mp->is_server || mp->conf_state != PT_PROTO_COMPLETED) + continue; + + if (!sl) sl = smartlist_new(); + + tor_assert(mp->transports); + SMARTLIST_FOREACH(mp->transports, const transport_t *, t, + smartlist_add_asprintf(sl, "%u:%u", t->port, t->port)); + + } SMARTLIST_FOREACH_END(mp); + + return sl; +} + +/** 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 *addrport = NULL; + uint32_t external_ip_address = 0; + if (tor_addr_is_null(&t->addr) && + router_pick_published_address(get_options(), + &external_ip_address) >= 0) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, external_ip_address); + addrport = fmt_addrport(&addr, t->port); + } else { + addrport = fmt_addrport(&t->addr, t->port); + } + + smartlist_add_asprintf(string_chunks, + "transport %s %s", + t->name, addrport); + } 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 +1447,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 02f159a5d..86a2530fc 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,11 +47,15 @@ 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); void sweep_proxy_list(void); +smartlist_t *get_transport_proxy_ports(void); + #ifdef PT_PRIVATE /** State of the managed proxy configuration protocol. */ enum pt_proto_state { @@ -68,28 +96,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; |