diff options
Diffstat (limited to 'src/or')
66 files changed, 14001 insertions, 4130 deletions
diff --git a/src/or/Makefile.am b/src/or/Makefile.am index a9ac3cdee1..67adf504df 100644 --- a/src/or/Makefile.am +++ b/src/or/Makefile.am @@ -7,7 +7,7 @@ else tor_platform_source= endif -EXTRA_DIST=ntmain.c or_sha1.i +EXTRA_DIST=ntmain.c or_sha1.i Makefile.nmake if USE_EXTERNAL_EVDNS evdns_source= @@ -15,16 +15,46 @@ 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 $(tor_platform_source) \ - microdesc.c \ - networkstatus.c onion.c policies.c \ - reasons.c relay.c rendcommon.c rendclient.c rendmid.c \ - rendservice.c rephist.c router.c routerlist.c routerparse.c \ - $(evdns_source) config_codedigest.c +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 @@ -40,18 +70,55 @@ AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ # 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@ -lm @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_edge.h connection.h connection_or.h \ - control.h cpuworker.h directory.h dirserv.h dirvote.h dns.h \ - dnsserv.h geoip.h hibernate.h main.h microdesc.h networkstatus.h \ - ntmain.h onion.h policies.h reasons.h relay.h rendclient.h \ - rendcommon.h rendmid.h rendservice.h rephist.h router.h routerlist.h \ - routerparse.h or.h eventdns.h eventdns_tor.h micro-revision.i + @TOR_ZLIB_LIBS@ -lm @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 @@ -72,11 +139,13 @@ micro-revision.i: FORCE mv micro-revision.tmp micro-revision.i; \ fi; true -or_sha1.i: $(tor_SOURCES) +or_sha1.i: $(tor_SOURCES) $(libtor_a_SOURCES) if test "@SHA1SUM@" != none; then \ - @SHA1SUM@ $(tor_SOURCES) | @SED@ -n 's/^\(.*\)$$/"\1\\n"/p' > or_sha1.i; \ + @SHA1SUM@ $(tor_SOURCES) $(libtor_a_SOURCES) | \ + @SED@ -n 's/^\(.*\)$$/"\1\\n"/p' > or_sha1.i; \ elif test "@OPENSSL@" != none; then \ - @OPENSSL@ sha1 $(tor_SOURCES) | @SED@ -n 's/SHA1(\(.*\))= \(.*\)/"\2 \1\\n"/p' > or_sha1.i; \ + @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; \ diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake new file mode 100644 index 0000000000..919edbbf22 --- /dev/null +++ b/src/or/Makefile.nmake @@ -0,0 +1,28 @@ +all: tor.exe
+
+CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common
+
+LIBS = ..\..\..\build-alpha\lib\libevent.a \
+ ..\..\..\build-alpha\lib\libcrypto.a \
+ ..\..\..\build-alpha\lib\libssl.a \
+ ..\..\..\build-alpha\lib\libz.a \
+ ws2_32.lib advapi32.lib shell32.lib
+
+LIBTOR_OBJECTS = buffers.obj circuitbuild.obj circuitlist.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
+
+libtor.lib: $(LIBTOR_OBJECTS)
+ lib $(LIBTOR_OBJECTS) /out:libtor.lib
+
+tor.exe: libtor.lib tor_main.obj
+ $(CC) $(CFLAGS) $(LIBS) libtor.lib ..\common\*.lib tor_main.obj
+
+clean:
+ del $(LIBTOR_OBJECTS) *.lib tor.exe
diff --git a/src/or/buffers.c b/src/or/buffers.c index 05163637f2..f4aac0f0e4 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -23,9 +23,6 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif -#ifdef HAVE_SYS_UIO_H -#include <sys/uio.h> -#endif //#define PARANOIA @@ -56,6 +53,13 @@ * forever. */ +static int parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); + /* Chunk manipulation functions */ /** A single chunk on a buffer or in a freelist. */ @@ -64,8 +68,8 @@ typedef struct chunk_t { size_t datalen; /**< The number of bytes stored in this chunk */ size_t memlen; /**< The number of usable bytes of storage in <b>mem</b>. */ char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ - char mem[1]; /**< The actual memory used for storage in this chunk. May be - * more than one byte long. */ + char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in + * this chunk. */ } chunk_t; #define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) @@ -547,11 +551,45 @@ buf_free(buf_t *buf) { if (!buf) return; + buf_clear(buf); buf->magic = 0xdeadbeef; tor_free(buf); } +/** Return a new copy of <b>in_chunk</b> */ +static chunk_t * +chunk_copy(const chunk_t *in_chunk) +{ + chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); + newch->next = NULL; + if (in_chunk->data) { + off_t offset = in_chunk->data - in_chunk->mem; + newch->data = newch->mem + offset; + } + return newch; +} + +/** Return a new copy of <b>buf</b> */ +buf_t * +buf_copy(const buf_t *buf) +{ + chunk_t *ch; + buf_t *out = buf_new(); + out->default_chunk_size = buf->default_chunk_size; + for (ch = buf->head; ch; ch = ch->next) { + chunk_t *newch = chunk_copy(ch); + if (out->tail) { + out->tail->next = newch; + out->tail = newch; + } else { + out->head = out->tail = newch; + } + } + out->datalen = buf->datalen; + return out; +} + /** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger * than MAX_CHUNK_ALLOC. */ @@ -578,10 +616,6 @@ buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) return chunk; } -/** If we're using readv and writev, how many chunks are we willing to - * read/write at a time? */ -#define N_IOV 3 - /** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, @@ -591,25 +625,9 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, int *reached_eof, int *socket_error) { ssize_t read_result; -#if 0 && defined(HAVE_READV) && !defined(WIN32) - struct iovec iov[N_IOV]; - int i; - size_t remaining = at_most; - for (i=0; chunk && i < N_IOV && remaining; ++i) { - iov[i].iov_base = CHUNK_WRITE_PTR(chunk); - if (remaining > CHUNK_REMAINING_CAPACITY(chunk)) - iov[i].iov_len = CHUNK_REMAINING_CAPACITY(chunk); - else - iov[i].iov_len = remaining; - remaining -= iov[i].iov_len; - chunk = chunk->next; - } - read_result = readv(fd, iov, i); -#else if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) at_most = CHUNK_REMAINING_CAPACITY(chunk); read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); -#endif if (read_result < 0) { int e = tor_socket_errno(fd); @@ -628,14 +646,6 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, return 0; } else { /* actually got bytes. */ buf->datalen += read_result; -#if 0 && defined(HAVE_READV) && !defined(WIN32) - while ((size_t)read_result > CHUNK_REMAINING_CAPACITY(chunk)) { - chunk->datalen += CHUNK_REMAINING_CAPACITY(chunk); - read_result -= CHUNK_REMAINING_CAPACITY(chunk); - chunk = chunk->next; - tor_assert(chunk); - } -#endif chunk->datalen += read_result; log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, (int)buf->datalen); @@ -771,25 +781,10 @@ flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, size_t *buf_flushlen) { ssize_t write_result; -#if 0 && defined(HAVE_WRITEV) && !defined(WIN32) - struct iovec iov[N_IOV]; - int i; - size_t remaining = sz; - for (i=0; chunk && i < N_IOV && remaining; ++i) { - iov[i].iov_base = chunk->data; - if (remaining > chunk->datalen) - iov[i].iov_len = chunk->datalen; - else - iov[i].iov_len = remaining; - remaining -= iov[i].iov_len; - chunk = chunk->next; - } - write_result = writev(s, iov, i); -#else + if (sz > chunk->datalen) sz = chunk->datalen; write_result = tor_socket_send(s, chunk->data, sz, 0); -#endif if (write_result < 0) { int e = tor_socket_errno(s); @@ -1010,6 +1005,33 @@ fetch_from_buf(char *string, size_t string_len, buf_t *buf) return (int)buf->datalen; } +/** True iff the cell command <b>command</b> is one that implies a + * variable-length cell in Tor link protocol <b>linkproto</b>. */ +static inline int +cell_command_is_var_length(uint8_t command, int linkproto) +{ + /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells + * work as implemented here. If it's 1, there are no variable-length cells. + * Tor does not support other versions right now, and so can't negotiate + * them. + */ + switch (linkproto) { + case 1: + /* Link protocol version 1 has no variable-length cells. */ + return 0; + case 2: + /* In link protocol version 2, VERSIONS is the only variable-length cell */ + return command == CELL_VERSIONS; + case 0: + case 3: + default: + /* In link protocol version 3 and later, and in version "unknown", + * commands 128 and higher indicate variable-length. VERSIONS is + * grandfathered in. */ + return command == CELL_VERSIONS || command >= 128; + } +} + /** Check <b>buf</b> for a variable-length cell according to the rules of link * protocol version <b>linkproto</b>. If one is found, pull it off the buffer * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. @@ -1024,12 +1046,6 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) var_cell_t *result; uint8_t command; uint16_t length; - /* If linkproto is unknown (0) or v2 (2), variable-length cells work as - * implemented here. If it's 1, there are no variable-length cells. Tor - * does not support other versions right now, and so can't negotiate them. - */ - if (linkproto == 1) - return 0; check(); *out = NULL; if (buf->datalen < VAR_CELL_HEADER_SIZE) @@ -1037,7 +1053,7 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) peek_from_buf(hdr, sizeof(hdr), buf); command = get_uint8(hdr+2); - if (!(CELL_COMMAND_IS_VAR_LENGTH(command))) + if (!(cell_command_is_var_length(command, linkproto))) return 0; length = ntohs(get_uint16(hdr+3)); @@ -1056,6 +1072,91 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) return 1; } +#ifdef USE_BUFFEREVENTS +/** Try to read <b>n</b> bytes from <b>buf</b> at <b>pos</b> (which may be + * NULL for the start of the buffer), copying the data only if necessary. Set + * *<b>data_out</b> to a pointer to the desired bytes. Set <b>free_out</b> + * to 1 if we needed to malloc *<b>data</b> because the original bytes were + * noncontiguous; 0 otherwise. Return the number of bytes actually available + * at *<b>data_out</b>. + */ +static ssize_t +inspect_evbuffer(struct evbuffer *buf, char **data_out, size_t n, + int *free_out, struct evbuffer_ptr *pos) +{ + int n_vecs, i; + + if (evbuffer_get_length(buf) < n) + n = evbuffer_get_length(buf); + if (n == 0) + return 0; + n_vecs = evbuffer_peek(buf, n, pos, NULL, 0); + tor_assert(n_vecs > 0); + if (n_vecs == 1) { + struct evbuffer_iovec v; + i = evbuffer_peek(buf, n, pos, &v, 1); + tor_assert(i == 1); + *data_out = v.iov_base; + *free_out = 0; + return v.iov_len; + } else { + ev_ssize_t copied; + *data_out = tor_malloc(n); + *free_out = 1; + copied = evbuffer_copyout(buf, *data_out, n); + tor_assert(copied >= 0 && (size_t)copied == n); + return copied; + } +} + +/** As fetch_var_cell_from_buf, buf works on an evbuffer. */ +int +fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, + int linkproto) +{ + char *hdr = NULL; + int free_hdr = 0; + size_t n; + size_t buf_len; + uint8_t command; + uint16_t cell_length; + var_cell_t *cell; + int result = 0; + + *out = NULL; + buf_len = evbuffer_get_length(buf); + if (buf_len < VAR_CELL_HEADER_SIZE) + return 0; + + n = inspect_evbuffer(buf, &hdr, VAR_CELL_HEADER_SIZE, &free_hdr, NULL); + tor_assert(n >= VAR_CELL_HEADER_SIZE); + + command = get_uint8(hdr+2); + if (!(cell_command_is_var_length(command, linkproto))) { + goto done; + } + + cell_length = ntohs(get_uint16(hdr+3)); + if (buf_len < (size_t)(VAR_CELL_HEADER_SIZE+cell_length)) { + result = 1; /* Not all here yet. */ + goto done; + } + + cell = var_cell_new(cell_length); + cell->command = command; + cell->circ_id = ntohs(get_uint16(hdr)); + evbuffer_drain(buf, VAR_CELL_HEADER_SIZE); + evbuffer_remove(buf, cell->payload, cell_length); + *out = cell; + result = 1; + + done: + if (free_hdr && hdr) + tor_free(hdr); + return result; +} +#endif + /** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. * Return the number of bytes actually copied. @@ -1063,8 +1164,7 @@ fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) { - /* XXXX we can do way better here, but this doesn't turn up in any - * profiles. */ + /* We can do way better here, but this doesn't turn up in any profiles. */ char b[4096]; size_t cp, len; len = *buf_flushlen; @@ -1299,6 +1399,94 @@ fetch_from_buf_http(buf_t *buf, return 1; } +#ifdef USE_BUFFEREVENTS +/** As fetch_from_buf_http, buf works on an evbuffer. */ +int +fetch_from_evbuffer_http(struct evbuffer *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + struct evbuffer_ptr crlf, content_length; + size_t headerlen, bodylen, contentlen; + + /* Find the first \r\n\r\n in the buffer */ + crlf = evbuffer_search(buf, "\r\n\r\n", 4, NULL); + if (crlf.pos < 0) { + /* We didn't find one. */ + if (evbuffer_get_length(buf) > max_headerlen) + return -1; /* Headers too long. */ + return 0; /* Headers not here yet. */ + } else if (crlf.pos > (int)max_headerlen) { + return -1; /* Headers too long. */ + } + + headerlen = crlf.pos + 4; /* Skip over the \r\n\r\n */ + bodylen = evbuffer_get_length(buf) - headerlen; + if (bodylen > max_bodylen) + return -1; /* body too long */ + + /* Look for the first occurrence of CONTENT_LENGTH insize buf before the + * crlfcrlf */ + content_length = evbuffer_search_range(buf, CONTENT_LENGTH, + strlen(CONTENT_LENGTH), NULL, &crlf); + + if (content_length.pos >= 0) { + /* We found a content_length: parse it and figure out if the body is here + * yet. */ + struct evbuffer_ptr eol; + char *data = NULL; + int free_data = 0; + int n, i; + n = evbuffer_ptr_set(buf, &content_length, strlen(CONTENT_LENGTH), + EVBUFFER_PTR_ADD); + tor_assert(n == 0); + eol = evbuffer_search_eol(buf, &content_length, NULL, EVBUFFER_EOL_CRLF); + tor_assert(eol.pos > content_length.pos); + tor_assert(eol.pos <= crlf.pos); + inspect_evbuffer(buf, &data, eol.pos - content_length.pos, &free_data, + &content_length); + + i = atoi(data); + if (free_data) + tor_free(data); + if (i < 0) { + log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like " + "someone is trying to crash us."); + return -1; + } + contentlen = i; + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } + + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + evbuffer_remove(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = '\0'; + } + if (body_out) { + tor_assert(headers_out); + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + evbuffer_remove(buf, *body_out, bodylen); + (*body_out)[bodylen] = '\0'; + } + return 1; +} +#endif + /** * Wait this many seconds before warning the user about using SOCKS unsafely * again (requires that WarnUnsafeSocks is turned on). */ @@ -1313,7 +1501,7 @@ log_unsafe_socks_warning(int socks_protocol, const char *address, { static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); char *m = NULL; if (! options->WarnUnsafeSocks) return; @@ -1340,6 +1528,31 @@ log_unsafe_socks_warning(int socks_protocol, const char *address, * actually significantly higher than the longest possible socks message. */ #define MAX_SOCKS_MESSAGE_LEN 512 +/** Return a new socks_request_t. */ +socks_request_t * +socks_request_new(void) +{ + return tor_malloc_zero(sizeof(socks_request_t)); +} + +/** Free all storage held in the socks_request_t <b>req</b>. */ +void +socks_request_free(socks_request_t *req) +{ + if (!req) + return; + if (req->username) { + memset(req->username, 0x10, req->usernamelen); + tor_free(req->username); + } + if (req->password) { + memset(req->password, 0x04, req->passwordlen); + tor_free(req->password); + } + memset(req, 0xCC, sizeof(socks_request_t)); + tor_free(req); +} + /** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one * of the forms * - socks4: "socksheader username\\0" @@ -1369,59 +1582,241 @@ int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int log_sockstype, int safe_socks) { + int res; + ssize_t n_drain; + size_t want_length = 128; + + if (buf->datalen < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, 0); + tor_assert(buf->head && buf->head->datalen >= 2); + want_length = 0; + + res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_remove_from_front(buf, n_drain); + + } while (res == 0 && buf->head && want_length < buf->datalen && + buf->datalen >= 2); + + return res; +} + +#ifdef USE_BUFFEREVENTS +/* As fetch_from_buf_socks(), but targets an evbuffer instead. */ +int +fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + char *data; + ssize_t n_drain; + size_t datalen, buflen, want_length; + int res; + + buflen = evbuffer_get_length(buf); + if (buflen < 2) + return 0; + + { + /* See if we can find the socks request in the first chunk of the buffer. + */ + struct evbuffer_iovec v; + int i; + n_drain = 0; + i = evbuffer_peek(buf, -1, NULL, &v, 1); + tor_assert(i == 1); + data = v.iov_base; + datalen = v.iov_len; + want_length = 0; + + res = parse_socks(data, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + else if (n_drain > 0) + evbuffer_drain(buf, n_drain); + + if (res) + return res; + } + + /* Okay, the first chunk of the buffer didn't have a complete socks request. + * That means that either we don't have a whole socks request at all, or + * it's gotten split up. We're going to try passing parse_socks() bigger + * and bigger chunks until either it says "Okay, I got it", or it says it + * will need more data than we currently have. */ + + /* Loop while we have more data that we haven't given parse_socks() yet. */ + do { + int free_data = 0; + const size_t last_wanted = want_length; + n_drain = 0; + data = NULL; + datalen = inspect_evbuffer(buf, &data, want_length, &free_data, NULL); + + want_length = 0; + res = parse_socks(data, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (free_data) + tor_free(data); + + if (n_drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + else if (n_drain > 0) + evbuffer_drain(buf, n_drain); + + if (res == 0 && n_drain == 0 && want_length <= last_wanted) { + /* If we drained nothing, and we didn't ask for more than last time, + * then we probably wanted more data than the buffer actually had, + * and we're finding out that we're not satisified with it. It's + * time to break until we have more data. */ + break; + } + + buflen = evbuffer_get_length(buf); + } while (res == 0 && want_length <= buflen && buflen >= 2); + + return res; +} +#endif + +/** Implementation helper to implement fetch_from_*_socks. Instead of looking + * at a buffer's contents, we look at the <b>datalen</b> bytes of data in + * <b>data</b>. Instead of removing data from the buffer, we set + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ unsigned int len; char tmpbuf[TOR_ADDR_BUF_LEN+1]; tor_addr_t destaddr; uint32_t destip; uint8_t socksver; - enum {socks4, socks4a} socks4_prot = socks4a; char *next, *startaddr; + unsigned char usernamelen, passlen; struct in_addr in; - if (buf->datalen < 2) /* version and another byte */ + if (datalen < 2) { + /* We always need at least 2 bytes. */ + *want_length_out = 2; return 0; + } - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, 0); - tor_assert(buf->head && buf->head->datalen >= 2); + if (req->socks_version == 5 && !req->got_auth) { + /* See if we have received authentication. Strictly speaking, we should + also check whether we actually negotiated username/password + authentication. But some broken clients will send us authentication + even if we negotiated SOCKS_NO_AUTH. */ + if (*data == 1) { /* username/pass version 1 */ + /* Format is: authversion [1 byte] == 1 + usernamelen [1 byte] + username [usernamelen bytes] + passlen [1 byte] + password [passlen bytes] */ + usernamelen = (unsigned char)*(data + 1); + if (datalen < 2u + usernamelen + 1u) { + *want_length_out = 2u + usernamelen + 1u; + return 0; + } + passlen = (unsigned char)*(data + 2u + usernamelen); + if (datalen < 2u + usernamelen + 1u + passlen) { + *want_length_out = 2u + usernamelen + 1u + passlen; + return 0; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; + req->reply[1] = 0; /* authentication successful */ + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + if (usernamelen) { + req->username = tor_memdup(data+2u, usernamelen); + req->usernamelen = usernamelen; + } + if (passlen) { + req->password = tor_memdup(data+3u+usernamelen, passlen); + req->passwordlen = passlen; + } + *drain_out = 2u + usernamelen + 1u + passlen; + req->got_auth = 1; + *want_length_out = 7; /* Minimal socks5 sommand. */ + return 0; + } else if (req->auth_type == SOCKS_USER_PASS) { + /* unknown version byte */ + log_warn(LD_APP, "Socks5 username/password version %d not recognized; " + "rejecting.", (int)*data); + return -1; + } + } - socksver = *buf->head->data; + socksver = *data; switch (socksver) { /* which version of socks? */ - case 5: /* socks5 */ if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(buf->head->data+1); + unsigned char nummethods = (unsigned char)*(data+1); + int r=0; tor_assert(!req->socks_version); - if (buf->datalen < 2u+nummethods) + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; return 0; - buf_pullup(buf, 2u+nummethods, 0); - if (!nummethods || !memchr(buf->head->data+2, 0, nummethods)) { + } + if (!nummethods) + return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + if (memchr(data+2, SOCKS_NO_AUTH, nummethods)) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + } else if (memchr(data+2, SOCKS_USER_PASS, nummethods)) { + req->auth_type = SOCKS_USER_PASS; + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else { log_warn(LD_APP, - "socks5: offered methods don't include 'no auth'. " - "Rejecting."); - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); req->reply[1] = '\xFF'; /* reject all methods */ - return -1; + r=-1; } - /* remove packet from buf. also remove any other extraneous - * bytes, to support broken socks clients. */ - buf_clear(buf); + /* Remove packet from buf. Some SOCKS clients will have sent extra + * junk at this point; let's hope it's an authentication message. */ + *drain_out = 2u + nummethods; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - req->reply[1] = 0; /* tell client to use "none" auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0"); - return 0; + return r; + } + if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { + log_warn(LD_APP, + "socks5: negotiated authentication, but none provided"); + return -1; } /* we know the method; read in the request */ log_debug(LD_APP,"socks5: checking request"); - if (buf->datalen < 8) /* basic info plus >=2 for addr plus 2 for port */ + if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ + *want_length_out = 7; return 0; /* not yet */ - tor_assert(buf->head->datalen >= 8); - req->command = (unsigned char) *(buf->head->data+1); + } + req->command = (unsigned char) *(data+1); if (req->command != SOCKS_COMMAND_CONNECT && req->command != SOCKS_COMMAND_RESOLVE && req->command != SOCKS_COMMAND_RESOLVE_PTR) { @@ -1430,19 +1825,21 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, req->command); return -1; } - switch (*(buf->head->data+3)) { /* address type */ + switch (*(data+3)) { /* address type */ case 1: /* IPv4 address */ case 4: /* IPv6 address */ { - const int is_v6 = *(buf->head->data+3) == 4; + const int is_v6 = *(data+3) == 4; const unsigned addrlen = is_v6 ? 16 : 4; log_debug(LD_APP,"socks5: ipv4 address type"); - if (buf->datalen < 6+addrlen) /* ip/port there? */ + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; return 0; /* not yet */ + } if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, buf->head->data+4); + tor_addr_from_ipv6_bytes(&destaddr, data+4); else - tor_addr_from_ipv4n(&destaddr, get_uint32(buf->head->data+4)); + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); @@ -1454,8 +1851,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(buf->head->data+4+addrlen)); - buf_remove_from_front(buf, 6+addrlen); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; if (req->command != SOCKS_COMMAND_RESOLVE_PTR && !addressmap_have_mapping(req->address,0)) { log_unsafe_socks_warning(5, req->address, req->port, safe_socks); @@ -1471,21 +1868,21 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, "hostname type. Rejecting."); return -1; } - len = (unsigned char)*(buf->head->data+4); - if (buf->datalen < 7+len) /* addr/port there? */ + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; return 0; /* not yet */ - buf_pullup(buf, 7+len, 0); - tor_assert(buf->head->datalen >= 7+len); + } if (len+1 > MAX_SOCKS_ADDR_LEN) { log_warn(LD_APP, "socks5 hostname is %d bytes, which doesn't fit in " "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); return -1; } - memcpy(req->address,buf->head->data+5,len); + memcpy(req->address,data+5,len); req->address[len] = 0; - req->port = ntohs(get_uint16(buf->head->data+5+len)); - buf_remove_from_front(buf, 5+len+2); + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { log_warn(LD_PROTOCOL, "Your application (using socks5 to port %d) gave Tor " @@ -1495,25 +1892,29 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, } if (log_sockstype) log_notice(LD_APP, - "Your application (using socks5 to port %d) gave " - "Tor a hostname, which means Tor will do the DNS resolve " - "for you. This is good.", req->port); + "Your application (using socks5 to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); return 1; default: /* unsupported */ log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", - (int) *(buf->head->data+3)); + (int) *(data+3)); return -1; } tor_assert(0); - case 4: /* socks4 */ - /* http://archive.socks.permeo.com/protocol/socks4.protocol */ - /* http://archive.socks.permeo.com/protocol/socks4a.protocol */ + case 4: { /* socks4 */ + enum {socks4, socks4a} socks4_prot = socks4a; + const char *authstart, *authend; + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ req->socks_version = 4; - if (buf->datalen < SOCKS4_NETWORK_LEN) /* basic info available? */ + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; return 0; /* not yet */ - buf_pullup(buf, 1280, 0); - req->command = (unsigned char) *(buf->head->data+1); + } + // buf_pullup(buf, 1280, 0); + req->command = (unsigned char) *(data+1); if (req->command != SOCKS_COMMAND_CONNECT && req->command != SOCKS_COMMAND_RESOLVE) { /* not a connect or resolve? we don't support it. (No resolve_ptr with @@ -1523,8 +1924,8 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } - req->port = ntohs(get_uint16(buf->head->data+2)); - destip = ntohl(get_uint32(buf->head->data+4)); + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); return -1; @@ -1544,17 +1945,20 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, socks4_prot = socks4; } - next = memchr(buf->head->data+SOCKS4_NETWORK_LEN, 0, - buf->head->datalen-SOCKS4_NETWORK_LEN); + authstart = data + SOCKS4_NETWORK_LEN; + next = memchr(authstart, 0, + datalen-SOCKS4_NETWORK_LEN); if (!next) { - if (buf->head->datalen >= 1024) { + if (datalen >= 1024) { log_debug(LD_APP, "Socks4 user name too long; rejecting."); return -1; } log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* More than we need, but safe */ return 0; } - tor_assert(next < CHUNK_WRITE_PTR(buf->head)); + authend = next; + tor_assert(next < data+datalen); startaddr = NULL; if (socks4_prot != socks4a && @@ -1565,18 +1969,20 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, return -1; } if (socks4_prot == socks4a) { - if (next+1 == CHUNK_WRITE_PTR(buf->head)) { + if (next+1 == data+datalen) { log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ return 0; } startaddr = next+1; - next = memchr(startaddr, 0, CHUNK_WRITE_PTR(buf->head)-startaddr); + next = memchr(startaddr, 0, data + datalen - startaddr); if (!next) { - if (buf->head->datalen >= 1024) { + if (datalen >= 1024) { log_debug(LD_APP,"socks4: Destaddr too long."); return -1; } log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ return 0; } if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { @@ -1587,9 +1993,9 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, if (log_sockstype) log_notice(LD_APP, - "Your application (using socks4a to port %d) gave " - "Tor a hostname, which means Tor will do the DNS resolve " - "for you. This is good.", req->port); + "Your application (using socks4a to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); } log_debug(LD_APP,"socks4: Everything is here. Success."); strlcpy(req->address, startaddr ? startaddr : tmpbuf, @@ -1601,15 +2007,20 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, req->port, escaped(req->address)); return -1; } + if (authend != authstart) { + req->got_auth = 1; + req->usernamelen = authend - authstart; + req->username = tor_memdup(authstart, authend - authstart); + } /* next points to the final \0 on inbuf */ - buf_remove_from_front(buf, next - buf->head->data + 1); + *drain_out = next - data + 1; return 1; - + } case 'G': /* get */ case 'H': /* head */ case 'P': /* put/post */ case 'C': /* connect */ - strlcpy(req->reply, + strlcpy((char*)req->reply, "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" "<html>\n" @@ -1635,14 +2046,15 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, "</body>\n" "</html>\n" , MAX_SOCKS_REPLY_LEN); - req->replylen = strlen(req->reply)+1; + req->replylen = strlen((char*)req->reply)+1; /* fall through */ default: /* version is not socks4 or socks5 */ log_warn(LD_APP, "Socks version %d not recognized. (Tor is not an http proxy.)", - *(buf->head->data)); + *(data)); { - char *tmp = tor_strndup(buf->head->data, 8); /*XXXX what if longer?*/ + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); control_event_client_status(LOG_WARN, "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", escaped(tmp)); @@ -1664,21 +2076,67 @@ fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) { - unsigned char *data; - size_t addrlen; - + ssize_t drain = 0; + int r; if (buf->datalen < 2) return 0; buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, 0); tor_assert(buf->head && buf->head->datalen >= 2); - data = (unsigned char *) buf->head->data; + r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, + state, reason, &drain); + if (drain > 0) + buf_remove_from_front(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +#ifdef USE_BUFFEREVENTS +/** As fetch_from_buf_socks_client, buf works on an evbuffer */ +int +fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state, + char **reason) +{ + ssize_t drain = 0; + uint8_t *data; + size_t datalen; + int r; + + /* Linearize the SOCKS response in the buffer, up to 128 bytes. + * (parse_socks_client shouldn't need to see anything beyond that.) */ + datalen = evbuffer_get_length(buf); + if (datalen > MAX_SOCKS_MESSAGE_LEN) + datalen = MAX_SOCKS_MESSAGE_LEN; + data = evbuffer_pullup(buf, datalen); + + r = parse_socks_client(data, datalen, state, reason, &drain); + if (drain > 0) + evbuffer_drain(buf, drain); + else if (drain < 0) + evbuffer_drain(buf, evbuffer_get_length(buf)); + + return r; +} +#endif + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; switch (state) { case PROXY_SOCKS4_WANT_CONNECT_OK: /* Wait for the complete response */ - if (buf->head->datalen < 8) + if (datalen < 8) return 0; if (data[1] != 0x5a) { @@ -1687,7 +2145,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } /* Success */ - buf_remove_from_front(buf, 8); + *drain_out = 8; return 1; case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: @@ -1699,7 +2157,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - buf_clear(buf); + *drain_out = -1; return 1; case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: @@ -1709,11 +2167,11 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) case 0x00: log_info(LD_NET, "SOCKS 5 client: we have auth details but server " "doesn't require authentication."); - buf_clear(buf); + *drain_out = -1; return 1; case 0x02: log_info(LD_NET, "SOCKS 5 client: need authentication."); - buf_clear(buf); + *drain_out = -1; return 2; /* fall through */ } @@ -1730,7 +2188,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } log_info(LD_NET, "SOCKS 5 client: authentication successful."); - buf_clear(buf); + *drain_out = -1; return 1; case PROXY_SOCKS5_WANT_CONNECT_OK: @@ -1739,7 +2197,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) * the data used */ /* wait for address type field to arrive */ - if (buf->datalen < 4) + if (datalen < 4) return 0; switch (data[3]) { @@ -1750,7 +2208,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) addrlen = 16; break; case 0x03: /* fqdn (can this happen here?) */ - if (buf->datalen < 5) + if (datalen < 5) return 0; addrlen = 1 + data[4]; break; @@ -1760,7 +2218,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) } /* wait for address and port */ - if (buf->datalen < 6 + addrlen) + if (datalen < 6 + addrlen) return 0; if (data[1] != 0x00) { @@ -1768,7 +2226,7 @@ fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) return -1; } - buf_remove_from_front(buf, 6 + addrlen); + *drain_out = 6 + addrlen; return 1; } @@ -1794,6 +2252,27 @@ peek_buf_has_control0_command(buf_t *buf) return 0; } +#ifdef USE_BUFFEREVENTS +int +peek_evbuffer_has_control0_command(struct evbuffer *buf) +{ + int result = 0; + if (evbuffer_get_length(buf) >= 4) { + int free_out = 0; + char *data = NULL; + size_t n = inspect_evbuffer(buf, &data, 4, &free_out, NULL); + uint16_t cmd; + tor_assert(n >= 4); + cmd = ntohs(get_uint16(data+2)); + if (cmd <= 0x14) + result = 1; + if (free_out) + tor_free(data); + } + return result; +} +#endif + /** Return the index within <b>buf</b> at which <b>ch</b> first appears, * or -1 if <b>ch</b> does not appear on buf. */ static off_t @@ -1811,12 +2290,12 @@ buf_find_offset_of_char(buf_t *buf, char ch) return -1; } -/** Try to read a single LF-terminated line from <b>buf</b>, and write it, - * NUL-terminated, into the *<b>data_len</b> byte buffer at <b>data_out</b>. - * Set *<b>data_len</b> to the number of bytes in the line, not counting the - * terminating NUL. Return 1 if we read a whole line, return 0 if we don't - * have a whole line yet, and return -1 if the line length exceeds - * *<b>data_len</b>. +/** Try to read a single LF-terminated line from <b>buf</b>, and write it + * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer + * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the + * line, not counting the terminating NUL. Return 1 if we read a whole line, + * return 0 if we don't have a whole line yet, and return -1 if the line + * length exceeds *<b>data_len</b>. */ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) @@ -1890,6 +2369,96 @@ write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state, return 0; } +#ifdef USE_BUFFEREVENTS +int +write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, + const char *data, size_t data_len, + int done) +{ + char *next; + size_t old_avail, avail; + int over = 0, n; + struct evbuffer_iovec vec[1]; + do { + { + size_t cap = data_len / 4; + if (cap < 128) + cap = 128; + /* XXXX NM this strategy is fragmentation-prone. We should really have + * two iovecs, and write first into the one, and then into the + * second if the first gets full. */ + n = evbuffer_reserve_space(buf, cap, vec, 1); + tor_assert(n == 1); + } + + next = vec[0].iov_base; + avail = old_avail = vec[0].iov_len; + + switch (tor_zlib_process(state, &next, &avail, &data, &data_len, done)) { + case TOR_ZLIB_DONE: + over = 1; + break; + case TOR_ZLIB_ERR: + return -1; + case TOR_ZLIB_OK: + if (data_len == 0) + over = 1; + break; + case TOR_ZLIB_BUF_FULL: + if (avail) { + /* Zlib says we need more room (ZLIB_BUF_FULL). Start a new chunk + * automatically, whether were going to or not. */ + } + break; + } + + /* XXXX possible infinite loop on BUF_FULL. */ + vec[0].iov_len = old_avail - avail; + evbuffer_commit_space(buf, vec, 1); + + } while (!over); + check(); + return 0; +} +#endif + +/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ +int +generic_buffer_set_to_copy(generic_buffer_t **output, + const generic_buffer_t *input) +{ +#ifdef USE_BUFFEREVENTS + struct evbuffer_ptr ptr; + size_t remaining = evbuffer_get_length(input); + if (*output) { + evbuffer_drain(*output, evbuffer_get_length(*output)); + } else { + if (!(*output = evbuffer_new())) + return -1; + } + evbuffer_ptr_set((struct evbuffer*)input, &ptr, 0, EVBUFFER_PTR_SET); + while (remaining) { + struct evbuffer_iovec v[4]; + int n_used, i; + n_used = evbuffer_peek((struct evbuffer*)input, -1, &ptr, v, 4); + if (n_used < 0) + return -1; + for (i=0;i<n_used;++i) { + evbuffer_add(*output, v[i].iov_base, v[i].iov_len); + tor_assert(v[i].iov_len <= remaining); + remaining -= v[i].iov_len; + evbuffer_ptr_set((struct evbuffer*)input, + &ptr, v[i].iov_len, EVBUFFER_PTR_ADD); + } + } +#else + if (*output) + buf_free(*output); + *output = buf_copy(input); +#endif + return 0; +} + /** Log an error and exit if <b>buf</b> is corrupted. */ void diff --git a/src/or/buffers.h b/src/or/buffers.h index 63fab4957a..7b2a2acc3c 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -16,6 +16,7 @@ buf_t *buf_new(void); buf_t *buf_new_with_capacity(size_t size); void buf_free(buf_t *buf); void buf_clear(buf_t *buf); +buf_t *buf_copy(const buf_t *buf); void buf_shrink(buf_t *buf); void buf_shrink_freelists(int free_all); void buf_dump_freelist_sizes(int severity); @@ -41,6 +42,8 @@ int fetch_from_buf_http(buf_t *buf, char **headers_out, size_t max_headerlen, char **body_out, size_t *body_used, size_t max_bodylen, int force_complete); +socks_request_t *socks_request_new(void); +void socks_request_free(socks_request_t *req); int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, int log_sockstype, int safe_socks); int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); @@ -48,6 +51,41 @@ int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); int peek_buf_has_control0_command(buf_t *buf); +#ifdef USE_BUFFEREVENTS +int fetch_var_cell_from_evbuffer(struct evbuffer *buf, var_cell_t **out, + int linkproto); +int fetch_from_evbuffer_socks(struct evbuffer *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_evbuffer_socks_client(struct evbuffer *buf, int state, + char **reason); +int fetch_from_evbuffer_http(struct evbuffer *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +int peek_evbuffer_has_control0_command(struct evbuffer *buf); +int write_to_evbuffer_zlib(struct evbuffer *buf, tor_zlib_state_t *state, + const char *data, size_t data_len, + int done); +#endif + +#ifdef USE_BUFFEREVENTS +#define generic_buffer_new() evbuffer_new() +#define generic_buffer_len(b) evbuffer_get_length((b)) +#define generic_buffer_add(b,dat,len) evbuffer_add((b),(dat),(len)) +#define generic_buffer_get(b,buf,buflen) evbuffer_remove((b),(buf),(buflen)) +#define generic_buffer_clear(b) evbuffer_drain((b), evbuffer_get_length((b))) +#define generic_buffer_free(b) evbuffer_free((b)) +#else +#define generic_buffer_new() buf_new() +#define generic_buffer_len(b) buf_datalen((b)) +#define generic_buffer_add(b,dat,len) write_to_buf((dat),(len),(b)) +#define generic_buffer_get(b,buf,buflen) fetch_from_buf((buf),(buflen),(b)) +#define generic_buffer_clear(b) buf_clear((b)) +#define generic_buffer_free(b) buf_free((b)) +#endif +int generic_buffer_set_to_copy(generic_buffer_t **output, + const generic_buffer_t *input); + void assert_buf_ok(buf_t *buf); #ifdef BUFFERS_PRIVATE diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index c864fd2497..c4b697c9a6 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -23,8 +23,10 @@ #include "directory.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "onion.h" #include "policies.h" +#include "transports.h" #include "relay.h" #include "rephist.h" #include "router.h" @@ -55,8 +57,8 @@ 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 routerinfo_t, since we want to remember these even when we - * don't have a directory. */ + * 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]; @@ -78,6 +80,28 @@ typedef struct { * at which we last failed to connect to it. */ } 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 @@ -95,11 +119,13 @@ static int circuit_deliver_create_cell(circuit_t *circ, 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_routers(smartlist_t *routers); +static int count_acceptable_nodes(smartlist_t *routers); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); static void entry_guards_changed(void); +static void bridge_free(bridge_info_t *bridge); + /** * This function decides if CBT learning should be disabled. It returns * true if one or more of the following four conditions are met: @@ -1532,10 +1558,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) hop = circ->cpath; do { - routerinfo_t *ri; - routerstatus_t *rs; char *elt; const char *id; + const node_t *node; if (!hop) break; if (!verbose && hop->state != CPATH_STATE_OPEN) @@ -1545,10 +1570,8 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) id = hop->extend_info->identity_digest; if (verbose_names) { elt = tor_malloc(MAX_VERBOSE_NICKNAME_LEN+1); - if ((ri = router_get_by_digest(id))) { - router_get_verbose_nickname(elt, ri); - } else if ((rs = router_get_consensus_status_by_id(id))) { - routerstatus_get_verbose_nickname(elt, rs); + if ((node = node_get_by_id(id))) { + node_get_verbose_nickname(node, elt); } else if (is_legal_nickname(hop->extend_info->nickname)) { elt[0] = '$'; base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); @@ -1560,9 +1583,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - if ((ri = router_get_by_digest(id)) && - ri->is_named) { - elt = tor_strdup(hop->extend_info->nickname); + node = node_get_by_id(id); + if (node && node_is_named(node)) { + elt = tor_strdup(node_get_nickname(node)); } else { elt = tor_malloc(HEX_DIGEST_LEN+2); elt[0] = '$'; @@ -1631,31 +1654,28 @@ void circuit_rep_hist_note_result(origin_circuit_t *circ) { crypt_path_t *hop; - char *prev_digest = NULL; - routerinfo_t *router; + const char *prev_digest = NULL; hop = circ->cpath; if (!hop) /* circuit hasn't started building yet. */ return; if (server_mode(get_options())) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return; prev_digest = me->cache_info.identity_digest; } do { - router = router_get_by_digest(hop->extend_info->identity_digest); - if (router) { + const node_t *node = node_get_by_id(hop->extend_info->identity_digest); + if (node) { /* Why do we check this? We know the identity. -NM XXXX */ if (prev_digest) { if (hop->state == CPATH_STATE_OPEN) - rep_hist_note_extend_succeeded(prev_digest, - router->cache_info.identity_digest); + rep_hist_note_extend_succeeded(prev_digest, node->identity); else { - rep_hist_note_extend_failed(prev_digest, - router->cache_info.identity_digest); + rep_hist_note_extend_failed(prev_digest, node->identity); break; } } - prev_digest = router->cache_info.identity_digest; + prev_digest = node->identity; } else { prev_digest = NULL; } @@ -1924,7 +1944,7 @@ int inform_testing_reachability(void) { char dirbuf[128]; - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return 0; control_event_server_status(LOG_NOTICE, @@ -1953,7 +1973,7 @@ inform_testing_reachability(void) static INLINE int should_use_create_fast_for_circuit(origin_circuit_t *circ) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); tor_assert(circ->cpath); tor_assert(circ->cpath->extend_info); @@ -1997,7 +2017,7 @@ int circuit_send_next_onion_skin(origin_circuit_t *circ) { crypt_path_t *hop; - routerinfo_t *router; + const node_t *node; char payload[2+4+DIGEST_LEN+ONIONSKIN_CHALLENGE_LEN]; char *onionskin; size_t payload_len; @@ -2013,7 +2033,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) else control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - router = router_get_by_digest(circ->_base.n_conn->identity_digest); + node = node_get_by_id(circ->_base.n_conn->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 @@ -2047,7 +2067,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", fast ? "CREATE_FAST" : "CREATE", - router ? router_describe(router) : "<unnamed>"); + node ? node_describe(node) : "<unnamed>"); } else { tor_assert(circ->cpath->state == CPATH_STATE_OPEN); tor_assert(circ->_base.state == CIRCUIT_STATE_BUILDING); @@ -2089,7 +2109,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) if (circ->build_state->onehop_tunnel) control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); if (!can_complete_circuit && !circ->build_state->onehop_tunnel) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); can_complete_circuit=1; /* FFFF Log a count of known routers here */ log_notice(LD_GENERAL, @@ -2097,6 +2117,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ) "Looks like client functionality is working."); control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0); control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); + clear_broken_connection_map(1); if (server_mode(options) && !check_whether_orport_reachable()) { inform_testing_reachability(); consider_testing_reachability(1, 1); @@ -2524,12 +2545,12 @@ onionskin_answer(or_circuit_t *circ, uint8_t cell_type, const char *payload, */ static int new_route_len(uint8_t purpose, extend_info_t *exit, - smartlist_t *routers) + smartlist_t *nodes) { int num_acceptable_routers; int routelen; - tor_assert(routers); + tor_assert(nodes); routelen = DEFAULT_ROUTE_LEN; if (exit && @@ -2537,10 +2558,10 @@ new_route_len(uint8_t purpose, extend_info_t *exit, purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) routelen++; - num_acceptable_routers = count_acceptable_routers(routers); + num_acceptable_routers = count_acceptable_nodes(nodes); log_debug(LD_CIRC,"Chosen route length %d (%d/%d routers suitable).", - routelen, num_acceptable_routers, smartlist_len(routers)); + routelen, num_acceptable_routers, smartlist_len(nodes)); if (num_acceptable_routers < 2) { log_info(LD_CIRC, @@ -2558,24 +2579,12 @@ new_route_len(uint8_t purpose, extend_info_t *exit, return routelen; } -/** Fetch the list of predicted ports, dup it into a smartlist of - * uint16_t's, remove the ones that are already handled by an - * existing circuit, and return it. - */ +/** Return a newly allocated list of uint16_t * for each predicted port not + * handled by a current circuit. */ static smartlist_t * circuit_get_unhandled_ports(time_t now) { - smartlist_t *source = rep_hist_get_predicted_ports(now); - smartlist_t *dest = smartlist_create(); - uint16_t *tmp; - int i; - - for (i = 0; i < smartlist_len(source); ++i) { - tmp = tor_malloc(sizeof(uint16_t)); - memcpy(tmp, smartlist_get(source, i), sizeof(uint16_t)); - smartlist_add(dest, tmp); - } - + smartlist_t *dest = rep_hist_get_predicted_ports(now); circuit_remove_handled_ports(dest); return dest; } @@ -2609,12 +2618,12 @@ circuit_all_predicted_ports_handled(time_t now, int *need_uptime, return enough; } -/** Return 1 if <b>router</b> can handle one or more of the ports in +/** Return 1 if <b>node</b> can handle one or more of the ports in * <b>needed_ports</b>, else return 0. */ static int -router_handles_some_port(routerinfo_t *router, smartlist_t *needed_ports) -{ +node_handles_some_port(const node_t *node, smartlist_t *needed_ports) +{ /* XXXX MOVE */ int i; uint16_t port; @@ -2624,7 +2633,10 @@ router_handles_some_port(routerinfo_t *router, smartlist_t *needed_ports) needed_ports is explicitly a smartlist of uint16_t's */ port = *(uint16_t *)smartlist_get(needed_ports, i); tor_assert(port); - r = compare_addr_to_addr_policy(0, port, router->exit_policy); + if (node) + r = compare_tor_addr_to_node_policy(NULL, port, node); + else + continue; if (r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED) return 1; } @@ -2636,14 +2648,18 @@ router_handles_some_port(routerinfo_t *router, smartlist_t *needed_ports) static int ap_stream_wants_exit_attention(connection_t *conn) { - if (conn->type == CONN_TYPE_AP && - conn->state == AP_CONN_STATE_CIRCUIT_WAIT && + entry_connection_t *entry; + if (conn->type != CONN_TYPE_AP) + return 0; + entry = TO_ENTRY_CONN(conn); + + if (conn->state == AP_CONN_STATE_CIRCUIT_WAIT && !conn->marked_for_close && - !(TO_EDGE_CONN(conn)->want_onehop) && /* ignore one-hop streams */ - !(TO_EDGE_CONN(conn)->use_begindir) && /* ignore targeted dir fetches */ - !(TO_EDGE_CONN(conn)->chosen_exit_name) && /* ignore defined streams */ + !(entry->want_onehop) && /* ignore one-hop streams */ + !(entry->use_begindir) && /* ignore targeted dir fetches */ + !(entry->chosen_exit_name) && /* ignore defined streams */ !connection_edge_is_rendezvous_stream(TO_EDGE_CONN(conn)) && - !circuit_stream_is_being_handled(TO_EDGE_CONN(conn), 0, + !circuit_stream_is_being_handled(TO_ENTRY_CONN(conn), 0, MIN_CIRCUITS_HANDLING_STREAM)) return 1; return 0; @@ -2657,18 +2673,17 @@ ap_stream_wants_exit_attention(connection_t *conn) * * Return NULL if we can't find any suitable routers. */ -static routerinfo_t * -choose_good_exit_server_general(routerlist_t *dir, int need_uptime, - int need_capacity) +static const node_t * +choose_good_exit_server_general(int need_uptime, int need_capacity) { int *n_supported; - int i; int n_pending_connections = 0; smartlist_t *connections; int best_support = -1; int n_best_support=0; - routerinfo_t *router; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); + const smartlist_t *the_nodes; + const node_t *node=NULL; connections = get_connection_array(); @@ -2689,10 +2704,11 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * * -1 means "Don't use this router at all." */ - n_supported = tor_malloc(sizeof(int)*smartlist_len(dir->routers)); - for (i = 0; i < smartlist_len(dir->routers); ++i) {/* iterate over routers */ - router = smartlist_get(dir->routers, i); - if (router_is_me(router)) { + the_nodes = nodelist_get_list(); + n_supported = tor_malloc(sizeof(int)*smartlist_len(the_nodes)); + SMARTLIST_FOREACH_BEGIN(the_nodes, const node_t *, node) { + const int i = node_sl_idx; + if (router_digest_is_me(node->identity)) { n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s -- it's me.", router->nickname); /* XXX there's probably a reverse predecessor attack here, but @@ -2700,41 +2716,44 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, */ continue; } - if (!router->is_running || router->is_bad_exit) { + if (!node_has_descriptor(node)) { + n_supported[i] = -1; + continue; + } + if (!node->is_running || node->is_bad_exit) { n_supported[i] = -1; continue; /* skip routers that are known to be down or bad exits */ } - - if (options->_ExcludeExitNodesUnion && - routerset_contains_router(options->_ExcludeExitNodesUnion, router)) { + if (routerset_contains_node(options->_ExcludeExitNodesUnion, node)) { n_supported[i] = -1; continue; /* user asked us not to use it, no matter what */ } if (options->ExitNodes && - !routerset_contains_router(options->ExitNodes, router)) { + !routerset_contains_node(options->ExitNodes, node)) { n_supported[i] = -1; continue; /* not one of our chosen exit nodes */ } - if (router_is_unreliable(router, need_uptime, need_capacity, 0)) { + if (node_is_unreliable(node, need_uptime, need_capacity, 0)) { n_supported[i] = -1; continue; /* skip routers that are not suitable. Don't worry if * this makes us reject all the possible routers: if so, * we'll retry later in this function with need_update and * need_capacity set to 0. */ } - if (!(router->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.", // router->nickname, i); continue; /* skip invalid routers */ } - if (options->ExcludeSingleHopRelays && router->allow_single_hop_exits) { + if (options->ExcludeSingleHopRelays && + node_allows_single_hop_exits(node)) { n_supported[i] = -1; continue; } - if (router_exit_policy_rejects_all(router)) { + if (node_exit_policy_rejects_all(node)) { n_supported[i] = -1; // log_fn(LOG_DEBUG,"Skipping node %s (index %d) -- it rejects all.", // router->nickname, i); @@ -2742,11 +2761,10 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, } n_supported[i] = 0; /* iterate over connections */ - SMARTLIST_FOREACH(connections, connection_t *, conn, - { + SMARTLIST_FOREACH_BEGIN(connections, connection_t *, conn) { if (!ap_stream_wants_exit_attention(conn)) continue; /* Skip everything but APs in CIRCUIT_WAIT */ - if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router)) { + if (connection_ap_can_use_exit(TO_ENTRY_CONN(conn), node)) { ++n_supported[i]; // log_fn(LOG_DEBUG,"%s is supported. n_supported[%d] now %d.", // router->nickname, i, n_supported[i]); @@ -2754,7 +2772,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, // log_fn(LOG_DEBUG,"%s (index %d) would reject this stream.", // router->nickname, i); } - }); /* End looping over connections. */ + } SMARTLIST_FOREACH_END(conn); if (n_pending_connections > 0 && n_supported[i] == 0) { /* Leave best_support at -1 if that's where it is, so we can * distinguish it later. */ @@ -2771,7 +2789,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * count of equally good routers.*/ ++n_best_support; } - } + } SMARTLIST_FOREACH_END(node); log_info(LD_CIRC, "Found %d servers that might support %d/%d pending connections.", n_best_support, best_support >= 0 ? best_support : 0, @@ -2782,11 +2800,12 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, if (best_support > 0) { smartlist_t *supporting = smartlist_create(); - for (i = 0; i < smartlist_len(dir->routers); i++) - if (n_supported[i] == best_support) - smartlist_add(supporting, smartlist_get(dir->routers, i)); + SMARTLIST_FOREACH(the_nodes, const node_t *, node, { + if (n_supported[node_sl_idx] == best_support) + smartlist_add(supporting, (void*)node); + }); - router = routerlist_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); + node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); smartlist_free(supporting); } else { /* Either there are no pending connections, or no routers even seem to @@ -2804,7 +2823,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, need_capacity?", fast":"", need_uptime?", stable":""); tor_free(n_supported); - return choose_good_exit_server_general(dir, 0, 0); + return choose_good_exit_server_general(0, 0); } log_notice(LD_CIRC, "All routers are down or won't exit%s -- " "choosing a doomed exit at random.", @@ -2815,18 +2834,17 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, for (attempt = 0; attempt < 2; attempt++) { /* try once to pick only from routers that satisfy a needed port, * then if there are none, pick from any that support exiting. */ - for (i = 0; i < smartlist_len(dir->routers); i++) { - router = smartlist_get(dir->routers, i); - if (n_supported[i] != -1 && - (attempt || router_handles_some_port(router, needed_ports))) { + SMARTLIST_FOREACH_BEGIN(the_nodes, const node_t *, node) { + if (n_supported[node_sl_idx] != -1 && + (attempt || node_handles_some_port(node, needed_ports))) { // log_fn(LOG_DEBUG,"Try %d: '%s' is a possibility.", // try, router->nickname); - smartlist_add(supporting, router); + smartlist_add(supporting, (void*)node); } - } + } SMARTLIST_FOREACH_END(node); - router = routerlist_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); - if (router) + node = node_sl_choose_by_bandwidth(supporting, WEIGHT_FOR_EXIT); + if (node) break; smartlist_clear(supporting); } @@ -2836,9 +2854,9 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, } tor_free(n_supported); - if (router) { - log_info(LD_CIRC, "Chose exit server '%s'", router_describe(router)); - return router; + if (node) { + log_info(LD_CIRC, "Chose exit server '%s'", node_describe(node)); + return node; } if (options->ExitNodes) { log_warn(LD_CIRC, @@ -2859,12 +2877,12 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime, * For client-side rendezvous circuits, choose a random node, weighted * toward the preferences in 'options'. */ -static routerinfo_t * -choose_good_exit_server(uint8_t purpose, routerlist_t *dir, +static const node_t * +choose_good_exit_server(uint8_t purpose, int need_uptime, int need_capacity, int is_internal) { - or_options_t *options = get_options(); - router_crn_flags_t flags = 0; + const or_options_t *options = get_options(); + router_crn_flags_t flags = CRN_NEED_DESC; if (need_uptime) flags |= CRN_NEED_UPTIME; if (need_capacity) @@ -2877,7 +2895,7 @@ choose_good_exit_server(uint8_t purpose, routerlist_t *dir, 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(dir,need_uptime,need_capacity); + return choose_good_exit_server_general(need_uptime,need_capacity); case CIRCUIT_PURPOSE_C_ESTABLISH_REND: if (options->_AllowInvalid & ALLOW_INVALID_RENDEZVOUS) flags |= CRN_ALLOW_INVALID; @@ -2893,7 +2911,7 @@ choose_good_exit_server(uint8_t purpose, routerlist_t *dir, static void warn_if_last_router_excluded(origin_circuit_t *circ, const extend_info_t *exit) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); routerset_t *rs = options->ExcludeNodes; const char *description; uint8_t purpose = circ->_base.purpose; @@ -2970,13 +2988,12 @@ static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) { cpath_build_state_t *state = circ->build_state; - routerlist_t *rl = router_get_routerlist(); if (state->onehop_tunnel) { 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, rl->routers); + 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; @@ -2988,14 +3005,15 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit) extend_info_describe(exit)); exit = extend_info_dup(exit); } else { /* we have to decide one */ - routerinfo_t *router = - choose_good_exit_server(circ->_base.purpose, rl, state->need_uptime, + const node_t *node = + choose_good_exit_server(circ->_base.purpose, state->need_uptime, state->need_capacity, state->is_internal); - if (!router) { + if (!node) { log_warn(LD_CIRC,"failed to choose an exit server"); return -1; } - exit = extend_info_from_router(router); + exit = extend_info_from_node(node); + tor_assert(exit); } state->chosen_exit = exit; return 0; @@ -3046,35 +3064,30 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit) * and available for building circuits through. */ static int -count_acceptable_routers(smartlist_t *routers) +count_acceptable_nodes(smartlist_t *nodes) { - int i, n; int num=0; - routerinfo_t *r; - n = smartlist_len(routers); - for (i=0;i<n;i++) { - r = smartlist_get(routers, i); -// log_debug(LD_CIRC, + SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) { + // log_debug(LD_CIRC, // "Contemplating whether router %d (%s) is a new option.", // i, r->nickname); - if (r->is_running == 0) { + if (! node->is_running) // log_debug(LD_CIRC,"Nope, the directory says %d is not running.",i); - goto next_i_loop; - } - if (r->is_valid == 0) { + continue; + if (! node->is_valid) // log_debug(LD_CIRC,"Nope, the directory says %d is not valid.",i); - goto next_i_loop; + continue; + if (! node_has_descriptor(node)) + continue; /* XXX This clause makes us count incorrectly: if AllowInvalidRouters * allows this node in some places, then we're getting an inaccurate * count. For now, be conservative and don't count it. But later we * should try to be smarter. */ - } - num++; + ++num; + } SMARTLIST_FOREACH_END(node); + // log_debug(LD_CIRC,"I like %d. num_acceptable_routers now %d.",i, num); - next_i_loop: - ; /* C requires an explicit statement after the label */ - } return num; } @@ -3102,31 +3115,29 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) * circuit. In particular, make sure we don't pick the exit node or its * family, and make sure we don't duplicate any previous nodes or their * families. */ -static routerinfo_t * +static const node_t * choose_good_middle_server(uint8_t purpose, cpath_build_state_t *state, crypt_path_t *head, int cur_len) { int i; - routerinfo_t *r, *choice; + const node_t *r, *choice; crypt_path_t *cpath; smartlist_t *excluded; - or_options_t *options = get_options(); - router_crn_flags_t flags = 0; + 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); log_debug(LD_CIRC, "Contemplating intermediate hop: random choice."); excluded = smartlist_create(); - if ((r = build_state_get_exit_router(state))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((r = build_state_get_exit_node(state))) { + nodelist_add_node_and_family(excluded, r); } for (i = 0, cpath = head; i < cur_len; ++i, cpath=cpath->next) { - if ((r = router_get_by_digest(cpath->extend_info->identity_digest))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((r = node_get_by_id(cpath->extend_info->identity_digest))) { + nodelist_add_node_and_family(excluded, r); } } @@ -3149,44 +3160,43 @@ 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 routerinfo_t * +static const node_t * choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) { - routerinfo_t *r, *choice; + const node_t *choice; smartlist_t *excluded; - or_options_t *options = get_options(); - router_crn_flags_t flags = CRN_NEED_GUARD; + const or_options_t *options = get_options(); + router_crn_flags_t flags = CRN_NEED_GUARD|CRN_NEED_DESC; + const node_t *node; if (state && options->UseEntryGuards && (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { + /* This is request for an entry server to use for a regular circuit, + * and we use entry guard nodes. Just return one of the guard nodes. */ return choose_random_entry(state); } excluded = smartlist_create(); - if (state && (r = build_state_get_exit_router(state))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if (state && (node = build_state_get_exit_node(state))) { + /* Exclude the exit node from the state, if we have one. Also exclude its + * family. */ + nodelist_add_node_and_family(excluded, node); } if (firewall_is_fascist_or()) { - /*XXXX This could slow things down a lot; use a smarter implementation */ - /* exclude all ORs that listen on the wrong port, if anybody notices. */ - routerlist_t *rl = router_get_routerlist(); - int i; - - for (i=0; i < smartlist_len(rl->routers); i++) { - r = smartlist_get(rl->routers, i); - if (!fascist_firewall_allows_or(r)) - smartlist_add(excluded, r); - } + /* Exclude all ORs that we can't reach through our firewall */ + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (!fascist_firewall_allows_node(node)) + smartlist_add(excluded, (void*)node); + }); } - /* and exclude current entry guards, if applicable */ + /* and exclude current entry guards and their families, if applicable */ if (options->UseEntryGuards && entry_guards) { SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, { - if ((r = router_get_by_digest(entry->identity))) { - smartlist_add(excluded, r); - routerlist_add_family(excluded, r); + if ((node = node_get_by_id(entry->identity))) { + nodelist_add_node_and_family(excluded, node); } }); } @@ -3242,14 +3252,18 @@ onion_extend_cpath(origin_circuit_t *circ) if (cur_len == state->desired_path_len - 1) { /* Picking last node */ info = extend_info_dup(state->chosen_exit); } else if (cur_len == 0) { /* picking first node */ - routerinfo_t *r = choose_good_entry_server(purpose, state); - if (r) - info = extend_info_from_router(r); + const node_t *r = choose_good_entry_server(purpose, state); + if (r) { + info = extend_info_from_node(r); + tor_assert(info); + } } else { - routerinfo_t *r = + const node_t *r = choose_good_middle_server(purpose, state, circ->cpath, cur_len); - if (r) - info = extend_info_from_router(r); + if (r) { + info = extend_info_from_node(r); + tor_assert(info); + } } if (!info) { @@ -3309,7 +3323,7 @@ extend_info_alloc(const char *nickname, const char *digest, /** Allocate and return a new extend_info_t that can be used to build a * circuit to or through the router <b>r</b>. */ extend_info_t * -extend_info_from_router(routerinfo_t *r) +extend_info_from_router(const routerinfo_t *r) { tor_addr_t addr; tor_assert(r); @@ -3318,6 +3332,29 @@ extend_info_from_router(routerinfo_t *r) r->onion_pkey, &addr, r->or_port); } +/** Allocate and return a new extend_info that can be used to build a ircuit + * to or through the node <b>node</b>. 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) +{ + if (node->ri) { + return extend_info_from_router(node->ri); + } 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, + node->identity, + node->md->onion_pkey, + &addr, + node->rs->or_port); + } else { + return NULL; + } +} + /** Release storage held by an extend_info_t struct. */ void extend_info_free(extend_info_t *info) @@ -3348,12 +3385,12 @@ extend_info_dup(extend_info_t *info) * If there is no chosen exit, or if we don't know the routerinfo_t for * the chosen exit, return NULL. */ -routerinfo_t * -build_state_get_exit_router(cpath_build_state_t *state) +const node_t * +build_state_get_exit_node(cpath_build_state_t *state) { if (!state || !state->chosen_exit) return NULL; - return router_get_by_digest(state->chosen_exit->identity_digest); + return node_get_by_id(state->chosen_exit->identity_digest); } /** Return the nickname for the chosen exit router in <b>state</b>. If @@ -3375,10 +3412,10 @@ build_state_get_exit_nickname(cpath_build_state_t *state) * * If it's not usable, set *<b>reason</b> to a static string explaining why. */ -/*XXXX take a routerstatus, not a routerinfo. */ static int -entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri, - time_t now, or_options_t *options, const char **reason) +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; @@ -3386,18 +3423,19 @@ entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri, *reason = NULL; /* Do we want to mark this guard as bad? */ - if (!ri) + if (!node) *reason = "unlisted"; - else if (!ri->is_running) + else if (!node->is_running) *reason = "down"; - else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE) + else if (options->UseBridges && (!node->ri || + node->ri->purpose != ROUTER_PURPOSE_BRIDGE)) *reason = "not a bridge"; - else if (options->UseBridges && !routerinfo_is_a_configured_bridge(ri)) + else if (options->UseBridges && !node_is_a_configured_bridge(node)) *reason = "not a configured bridge"; - else if (!options->UseBridges && !ri->is_possible_guard && - !routerset_contains_router(options->EntryNodes,ri)) + else if (!options->UseBridges && !node->is_possible_guard && + !routerset_contains_node(options->EntryNodes,node)) *reason = "not recommended as a guard"; - else if (routerset_contains_router(options->ExcludeNodes, ri)) + else if (routerset_contains_node(options->ExcludeNodes, node)) *reason = "excluded"; if (*reason && ! e->bad_since) { @@ -3441,7 +3479,7 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) return now > (e->last_attempted + 36*60*60); } -/** Return the router corresponding to <b>e</b>, if <b>e</b> is +/** 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; @@ -3455,12 +3493,12 @@ entry_is_time_to_retry(entry_guard_t *e, time_t now) * * If the answer is no, set *<b>msg</b> to an explanation of why. */ -static INLINE routerinfo_t * +static INLINE const node_t * entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, int assume_reachable, const char **msg) { - routerinfo_t *r; - or_options_t *options = get_options(); + const node_t *node; + const or_options_t *options = get_options(); tor_assert(msg); if (e->bad_since) { @@ -3473,38 +3511,39 @@ entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity, *msg = "unreachable"; return NULL; } - r = router_get_by_digest(e->identity); - if (!r) { + node = node_get_by_id(e->identity); + if (!node || !node_has_descriptor(node)) { *msg = "no descriptor"; return NULL; } - if (options->UseBridges) { - if (r->purpose != ROUTER_PURPOSE_BRIDGE) { + if (get_options()->UseBridges) { + if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) { *msg = "not a bridge"; return NULL; } - if (!routerinfo_is_a_configured_bridge(r)) { + if (!node_is_a_configured_bridge(node)) { *msg = "not a configured bridge"; return NULL; } - } else if (r->purpose != ROUTER_PURPOSE_GENERAL) { - *msg = "not general-purpose"; - return NULL; + } else { /* !get_options()->UseBridges */ + if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) { + *msg = "not general-purpose"; + return NULL; + } } - if (options->EntryNodes && - routerset_contains_router(options->EntryNodes, r)) { + if (routerset_contains_node(options->EntryNodes, node)) { /* they asked for it, they get it */ need_uptime = need_capacity = 0; } - if (router_is_unreliable(r, 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_or(r)) { + if (!fascist_firewall_allows_node(node)) { *msg = "unreachable by config"; return NULL; } - return r; + return node; } /** Return the number of entry guards that we think are usable. */ @@ -3584,7 +3623,7 @@ control_event_guard_deferred(void) #if 0 int n = 0; const char *msg; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!entry_guards) return; SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, @@ -3606,15 +3645,15 @@ control_event_guard_deferred(void) * 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 routerinfo_t * -add_an_entry_guard(routerinfo_t *chosen, int reset_status) +static const node_t * +add_an_entry_guard(const node_t *chosen, int reset_status, int prepend) { - routerinfo_t *router; + const node_t *node; entry_guard_t *entry; if (chosen) { - router = chosen; - entry = is_an_entry_guard(router->cache_info.identity_digest); + node = chosen; + entry = is_an_entry_guard(node->identity); if (entry) { if (reset_status) { entry->bad_since = 0; @@ -3623,15 +3662,15 @@ add_an_entry_guard(routerinfo_t *chosen, int reset_status) return NULL; } } else { - router = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL); - if (!router) + 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.", - router_describe(router)); - strlcpy(entry->nickname, router->nickname, sizeof(entry->nickname)); - memcpy(entry->identity, router->cache_info.identity_digest, DIGEST_LEN); + 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 @@ -3639,27 +3678,27 @@ add_an_entry_guard(routerinfo_t *chosen, int reset_status) * 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 (chosen) /* prepend */ + if (prepend) smartlist_insert(entry_guards, 0, entry); - else /* append */ + else smartlist_add(entry_guards, entry); control_event_guard(entry->nickname, entry->identity, "NEW"); control_event_guard_deferred(); log_entry_guards(LOG_INFO); - return router; + 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(or_options_t *options) +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)) + if (!add_an_entry_guard(NULL, 0, 0)) break; changed = 1; } @@ -3785,7 +3824,7 @@ remove_dead_entry_guards(time_t now) * think that things are unlisted. */ void -entry_guards_compute_status(or_options_t *options, time_t now) +entry_guards_compute_status(const or_options_t *options, time_t now) { int changed = 0; digestmap_t *reasons; @@ -3799,7 +3838,7 @@ entry_guards_compute_status(or_options_t *options, time_t now) reasons = digestmap_new(); SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { - routerinfo_t *r = router_get_by_digest(entry->identity); + 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; @@ -3818,7 +3857,7 @@ entry_guards_compute_status(or_options_t *options, time_t now) SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *reason = digestmap_get(reasons, entry->identity); const char *live_msg = ""; - routerinfo_t *r = entry_is_live(entry, 0, 1, 0, &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), @@ -3936,7 +3975,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, break; if (e->made_contact) { const char *msg; - routerinfo_t *r = entry_is_live(e, 0, 1, 1, &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; @@ -3972,12 +4011,12 @@ entry_nodes_should_be_added(void) should_add_entry_nodes = 1; } -/** Add all nodes in EntryNodes that aren't currently guard nodes to the list - * of guard nodes, at the front. */ +/** 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_prepend_from_config(or_options_t *options) +entry_guards_set_from_config(const or_options_t *options) { - smartlist_t *entry_routers, *entry_fps; + 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); @@ -3997,23 +4036,19 @@ entry_guards_prepend_from_config(or_options_t *options) tor_free(string); } - entry_routers = smartlist_create(); + entry_nodes = smartlist_create(); + worse_entry_nodes = smartlist_create(); entry_fps = smartlist_create(); old_entry_guards_on_list = smartlist_create(); old_entry_guards_not_on_list = smartlist_create(); /* Split entry guards into those on the list and those not. */ - /* XXXX023 Now that we allow countries and IP ranges in EntryNodes, this is - * potentially an enormous list. For now, we disable such values for - * EntryNodes in options_validate(); really, this wants a better solution. - * Perhaps we should do this calculation once whenever the list of routers - * changes or the entrynodes setting changes. - */ - routerset_get_all_routers(entry_routers, options->EntryNodes, - options->ExcludeNodes, 0); - SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, - smartlist_add(entry_fps,ri->cache_info.identity_digest)); + 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); @@ -4021,27 +4056,47 @@ entry_guards_prepend_from_config(or_options_t *options) smartlist_add(old_entry_guards_not_on_list, e); }); - /* Remove all currently configured entry guards from entry_routers. */ - SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, { - if (is_an_entry_guard(ri->cache_info.identity_digest)) { - SMARTLIST_DEL_CURRENT(entry_routers, ri); + /* 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 (is_an_entry_guard(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(entry_routers, routerinfo_t *, ri, { - add_an_entry_guard(ri, 0); - }); + 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_routers); + 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); @@ -4053,7 +4108,7 @@ entry_guards_prepend_from_config(or_options_t *options) * list already and we must stick to it. */ int -entry_list_is_constrained(or_options_t *options) +entry_list_is_constrained(const or_options_t *options) { if (options->EntryNodes) return 1; @@ -4067,20 +4122,21 @@ entry_list_is_constrained(or_options_t *options) * 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). */ -routerinfo_t * +const node_t * choose_random_entry(cpath_build_state_t *state) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); smartlist_t *live_entry_guards = smartlist_create(); smartlist_t *exit_family = smartlist_create(); - routerinfo_t *chosen_exit = state?build_state_get_exit_router(state) : NULL; - routerinfo_t *r = NULL; + 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) { - routerlist_add_family(exit_family, chosen_exit); + nodelist_add_node_and_family(exit_family, chosen_exit); consider_exit_family = 1; } @@ -4088,7 +4144,7 @@ choose_random_entry(cpath_build_state_t *state) entry_guards = smartlist_create(); if (should_add_entry_nodes) - entry_guards_prepend_from_config(options); + entry_guards_set_from_config(options); if (!entry_list_is_constrained(options) && smartlist_len(entry_guards) < options->NumEntryGuards) @@ -4096,25 +4152,24 @@ choose_random_entry(cpath_build_state_t *state) retry: smartlist_clear(live_entry_guards); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) - { + SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { const char *msg; - r = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); - if (!r) + node = entry_is_live(entry, need_uptime, need_capacity, 0, &msg); + if (!node) continue; /* down, no point */ - if (r == chosen_exit) + if (node == chosen_exit) continue; /* don't pick the same node for entry and exit */ - if (consider_exit_family && smartlist_isin(exit_family, r)) + 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_router(options->EntryNodes, r)) { + !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_prepend_from_config() drops unwanted relays */ + * entry_guards_set_from_config() drops unwanted relays */ tor_fragile_assert(); } else { log_info(LD_CIRC, @@ -4122,7 +4177,7 @@ choose_random_entry(cpath_build_state_t *state) } } #endif - smartlist_add(live_entry_guards, r); + 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 @@ -4131,9 +4186,8 @@ choose_random_entry(cpath_build_state_t *state) goto choose_and_finish; } if (smartlist_len(live_entry_guards) >= options->NumEntryGuards) - break; /* we have enough */ - } - SMARTLIST_FOREACH_END(entry); + 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 @@ -4153,8 +4207,8 @@ choose_random_entry(cpath_build_state_t *state) /* 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 */ - r = add_an_entry_guard(NULL, 0); - if (r) { + 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 @@ -4164,11 +4218,11 @@ choose_random_entry(cpath_build_state_t *state) goto retry; } } - if (!r && need_uptime) { + if (!node && need_uptime) { need_uptime = 0; /* try without that requirement */ goto retry; } - if (!r && need_capacity) { + if (!node && need_capacity) { /* still no? last attempt, try without requiring capacity */ need_capacity = 0; goto retry; @@ -4177,10 +4231,10 @@ choose_random_entry(cpath_build_state_t *state) /* 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 (!r && entry_list_is_constrained(options) && consider_exit_family) { - /* still no? if we're using bridges, - * and our chosen exit is in the same family as all our - * bridges, then be flexible about families. */ + 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; } @@ -4192,16 +4246,16 @@ choose_random_entry(cpath_build_state_t *state) 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. */ - r = routerlist_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD); + 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. */ - r = smartlist_choose(live_entry_guards); + node = smartlist_choose(live_entry_guards); } smartlist_free(live_entry_guards); smartlist_free(exit_family); - return r; + return node; } /** Parse <b>state</b> and learn about the entry guards it describes. @@ -4448,7 +4502,7 @@ getinfo_helper_entry_guards(control_connection_t *conn, char *c = tor_malloc(len); const char *status = NULL; time_t when = 0; - routerinfo_t *ri; + const node_t *node; if (!e->made_contact) { status = "never-connected"; @@ -4459,9 +4513,9 @@ getinfo_helper_entry_guards(control_connection_t *conn, status = "up"; } - ri = router_get_by_digest(e->identity); - if (ri) { - router_get_verbose_nickname(nbuf, ri); + 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); @@ -4484,24 +4538,6 @@ getinfo_helper_entry_guards(control_connection_t *conn, return 0; } -/** 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]; - /** When should we next try to fetch a descriptor for this bridge? */ - download_status_t fetch_status; -} bridge_info_t; - /** 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 @@ -4529,7 +4565,7 @@ sweep_bridge_list(void) SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { if (b->marked_for_removal) { SMARTLIST_DEL_CURRENT(bridge_list, b); - tor_free(b); + bridge_free(b); } } SMARTLIST_FOREACH_END(b); } @@ -4540,10 +4576,243 @@ clear_bridge_list(void) { if (!bridge_list) bridge_list = smartlist_create(); - SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, tor_free(b)); + 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_create(); + 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_create(); + 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_create(); + 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_create(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver) +{ + transport_t *t = tor_malloc_zero(sizeof(transport_t)); + + tor_addr_copy(&t->addr, addr); + t->port = port; + t->name = tor_strdup(name); + t->socks_version = socks_ver; + + return t; +} + +/** Resolve any conflicts that the insertion of transport <b>t</b> + * might cause. + * Return 0 if <b>t</b> is OK and should be registered, 1 if there is + * a transport identical to <b>t</b> already registered and -1 if + * <b>t</b> cannot be added due to conflicts. */ +static int +transport_resolve_conflicts(transport_t *t) +{ + /* This is how we resolve transport conflicts: + + If there is already a transport with the same name and addrport, + we either have duplicate torrc lines OR we are here post-HUP and + this transport was here pre-HUP as well. In any case, mark the + old transport so that it doesn't get removed and ignore the new + one. Our caller has to free the new transport so we return '1' to + signify this. + + If there is already a transport with the same name but different + addrport: + * if it's marked for removal, it means that it either has a lower + priority than 't' in torrc (otherwise the mark would have been + cleared by the paragraph above), or it doesn't exist at all in + the post-HUP torrc. We destroy the old transport and register 't'. + * if it's *not* marked for removal, it means that it was newly + added in the post-HUP torrc or that it's of higher priority, in + this case we ignore 't'. */ + transport_t *t_tmp = transport_get_by_name(t->name); + if (t_tmp) { /* same name */ + if (tor_addr_eq(&t->addr, &t_tmp->addr) && (t->port == t_tmp->port)) { + /* same name *and* addrport */ + t_tmp->marked_for_removal = 0; + return 1; + } else { /* same name but different addrport */ + if (t_tmp->marked_for_removal) { /* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " + "but there was already a transport marked for deletion at " + "'%s:%u'. We deleted the old transport and registered the " + "new one.", t->name, fmt_addr(&t->addr), t->port, + fmt_addr(&t_tmp->addr), t_tmp->port); + smartlist_remove(transport_list, t_tmp); + transport_free(t_tmp); + } else { /* *not* marked for removal */ + log_notice(LD_GENERAL, "You tried to add transport '%s' at '%s:%u' " + "but the same transport already exists at '%s:%u'. " + "Skipping.", t->name, fmt_addr(&t->addr), t->port, + fmt_addr(&t_tmp->addr), t_tmp->port); + return -1; + } + } + } + + 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_create(); + 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_create(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; + } +} + +/** Warn the user of possible pluggable transport misconfiguration. + * Return 0 if the validation happened, -1 if we should postpone the + * validation. */ +int +validate_pluggable_transports_config(void) +{ + /* Don't validate if managed proxies are not yet fully configured. */ + if (bridge_list && !pt_proxies_configuration_pending()) { + SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { + /* Skip bridges without transports. */ + if (!b->transport_name) + continue; + /* See if the user has Bridges that specify nonexistent + pluggable transports. We should warn the user in such case, + since it's probably misconfiguration. */ + if (!transport_get_by_name(b->transport_name)) + log_warn(LD_CONFIG, "You have a Bridge line using the %s " + "pluggable transport, but there doesn't seem to be a " + "corresponding ClientTransportPlugin line.", + b->transport_name); + } SMARTLIST_FOREACH_END(b); + + return 0; + } else { + return -1; + } +} + /** 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. */ @@ -4570,7 +4839,7 @@ get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, /** 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(routerinfo_t *ri) +get_configured_bridge_by_routerinfo(const routerinfo_t *ri) { tor_addr_t addr; tor_addr_from_ipv4h(&addr, ri->addr); @@ -4580,11 +4849,29 @@ get_configured_bridge_by_routerinfo(routerinfo_t *ri) /** Return 1 if <b>ri</b> is one of our known bridges, else 0. */ int -routerinfo_is_a_configured_bridge(routerinfo_t *ri) +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) +{ + tor_addr_t addr; + uint16_t orport; + if (!node) + return 0; + if (node_get_addr(node, &addr) < 0) + return 0; + orport = node_get_orport(node); + if (orport == 0) + return 0; + + return get_configured_bridge_by_addr_port_digest( + &addr, orport, node->identity) != NULL; +} + /** 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. @@ -4604,10 +4891,12 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, /** 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. */ + * 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 *digest, const char *transport_name) { bridge_info_t *b; @@ -4621,6 +4910,8 @@ bridge_add_from_config(const tor_addr_t *addr, uint16_t port, 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_create(); @@ -4658,12 +4949,48 @@ find_bridge_by_digest(const char *digest) 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; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (connection_get_by_type_addr_port_purpose( CONN_TYPE_DIR, &bridge->addr, bridge->port, @@ -4704,15 +5031,20 @@ retry_bridge_descriptor_fetch_directly(const char *digest) * descriptor, fetch a new copy of its descriptor -- either directly * from the bridge or via a bridge authority. */ void -fetch_bridge_descriptors(or_options_t *options, time_t now) +fetch_bridge_descriptors(const or_options_t *options, time_t now) { - int num_bridge_auths = get_n_authorities(BRIDGE_AUTHORITY); + 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, @@ -4771,26 +5103,55 @@ fetch_bridge_descriptors(or_options_t *options, time_t now) } /** If our <b>bridge</b> is configured to be a different address than - * the bridge gives in its routerinfo <b>ri</b>, rewrite the routerinfo + * 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_routerinfo_address_for_bridge(bridge_info_t *bridge, routerinfo_t *ri) +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; - tor_addr_from_ipv4h(&addr, ri->addr); - if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && - bridge->port == ri->or_port) - return; /* they match, so no need to do anything */ + 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) { + /* they match, so no need to do anything */ + } else { + 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); + } + } + if (node->rs) { + routerstatus_t *rs = node->rs; + tor_addr_from_ipv4h(&addr, rs->addr); - 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 '%s' to match configured address %s:%d.", - ri->nickname, ri->address, ri->or_port); + 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 @@ -4804,16 +5165,19 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) int first = !any_bridge_descriptors_known(); bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); time_t now = time(NULL); - ri->is_running = 1; + 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); - rewrite_routerinfo_address_for_bridge(bridge, ri); + 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); - add_an_entry_guard(ri, 1); log_notice(LD_DIR, "new bridge descriptor '%s' (%s)", ri->nickname, from_cache ? "cached" : "fresh"); /* set entry->made_contact so if it goes down we don't drop it from @@ -4866,21 +5230,20 @@ any_pending_bridge_descriptor_fetches(void) * 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(or_options_t *options, int act) +entries_retry_helper(const or_options_t *options, int act) { - routerinfo_t *ri; + const node_t *node; int any_known = 0; int any_running = 0; - int purpose = options->UseBridges ? - ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL; + int need_bridges = options->UseBridges != 0; if (!entry_guards) entry_guards = smartlist_create(); - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, - { - ri = router_get_by_digest(e->identity); - if (ri && ri->purpose == purpose) { + 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 (ri->is_running) + 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 @@ -4889,15 +5252,15 @@ entries_retry_helper(or_options_t *options, int act) * 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(ri->cache_info.identity_digest, 1); + connection_or_set_bad_connections(node->identity, 1); /* mark this entry node for retry */ - router_set_status(ri->cache_info.identity_digest, 1); + 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; @@ -4906,7 +5269,7 @@ entries_retry_helper(or_options_t *options, int act) /** Do we know any descriptors for our bridges / entrynodes, and are * all the ones we have descriptors for down? */ int -entries_known_but_down(or_options_t *options) +entries_known_but_down(const or_options_t *options) { tor_assert(entry_list_is_constrained(options)); return entries_retry_helper(options, 0); @@ -4914,7 +5277,7 @@ entries_known_but_down(or_options_t *options) /** Mark all down known bridges / entrynodes up. */ void -entries_retry_all(or_options_t *options) +entries_retry_all(const or_options_t *options) { tor_assert(entry_list_is_constrained(options)); entries_retry_helper(options, 1); @@ -4932,7 +5295,10 @@ entry_guards_free_all(void) entry_guards = NULL; } clear_bridge_list(); + clear_transport_list(); smartlist_free(bridge_list); + smartlist_free(transport_list); bridge_list = NULL; + transport_list = NULL; } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 0e673e1c05..1052db6153 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -12,6 +12,21 @@ #ifndef _TOR_CIRCUITBUILD_H #define _TOR_CIRCUITBUILD_H +/** Represents a pluggable transport proxy used by a bridge. */ +typedef struct { + /** SOCKS version: One of PROXY_SOCKS4, PROXY_SOCKS5. */ + int socks_version; + /** Name of pluggable transport protocol */ + char *name; + /** Address of proxy */ + tor_addr_t addr; + /** Port of proxy */ + uint16_t port; + /** Boolean: We are re-parsing our transport list, and we are going to remove + * this one if we don't find it in the list of configured transports. */ + unsigned marked_for_removal : 1; +} transport_t; + char *circuit_list_path(origin_circuit_t *circ, int verbose); char *circuit_list_path_for_controller(origin_circuit_t *circ); void circuit_log_path(int severity, unsigned int domain, @@ -44,18 +59,19 @@ 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_env_t *onion_key, const tor_addr_t *addr, uint16_t port); -extend_info_t *extend_info_from_router(routerinfo_t *r); +extend_info_t *extend_info_from_router(const routerinfo_t *r); +extend_info_t *extend_info_from_node(const node_t *node); extend_info_t *extend_info_dup(extend_info_t *info); void extend_info_free(extend_info_t *info); -routerinfo_t *build_state_get_exit_router(cpath_build_state_t *state); +const node_t *build_state_get_exit_node(cpath_build_state_t *state); const char *build_state_get_exit_nickname(cpath_build_state_t *state); -void entry_guards_compute_status(or_options_t *options, time_t now); +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(or_options_t *options); -routerinfo_t *choose_random_entry(cpath_build_state_t *state); +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, @@ -64,18 +80,23 @@ int getinfo_helper_entry_guards(control_connection_t *conn, void mark_bridge_list(void); void sweep_bridge_list(void); -int routerinfo_is_a_configured_bridge(routerinfo_t *ri); +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 *digest, + const char *transport_name); void retry_bridge_descriptor_fetch_directly(const char *digest); -void fetch_bridge_descriptors(or_options_t *options, time_t now); +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(or_options_t *options); -void entries_retry_all(or_options_t *options); +int entries_known_but_down(const or_options_t *options); +void entries_retry_all(const or_options_t *options); void entry_guards_free_all(void); @@ -124,5 +145,19 @@ void circuit_build_times_network_circ_success(circuit_build_times_t *cbt); 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_create(const tor_addr_t *addr, uint16_t port, + const char *name, int socks_ver); + +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); + +int validate_pluggable_transports_config(void); + #endif diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index e9cc9eb1f4..25b80f11f3 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -19,6 +19,7 @@ #include "connection_or.h" #include "control.h" #include "networkstatus.h" +#include "nodelist.h" #include "onion.h" #include "relay.h" #include "rendclient.h" @@ -86,10 +87,7 @@ orconn_circid_circuit_map_t *_last_circid_orconn_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 - * the old entry (if any) and adding a new one. If <b>active</b> is true, - * remove the circuit from the list of active circuits on old_conn and add it - * to the list of active circuits on conn. - * XXX "active" isn't an arg anymore */ + * 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, @@ -552,6 +550,16 @@ circuit_free(circuit_t *circ) crypto_free_pk_env(ocirc->intro_key); rend_data_free(ocirc->rend_data); + + tor_free(ocirc->dest_address); + if (ocirc->socks_username) { + memset(ocirc->socks_username, 0x12, ocirc->socks_username_len); + tor_free(ocirc->socks_username); + } + if (ocirc->socks_password) { + memset(ocirc->socks_password, 0x06, ocirc->socks_password_len); + tor_free(ocirc->socks_password); + } } else { or_circuit_t *ocirc = TO_OR_CIRCUIT(circ); /* Remember cell statistics for this circuit before deallocating. */ @@ -981,7 +989,7 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int need_uptime = (flags & CIRCLAUNCH_NEED_UPTIME) != 0; int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0; int internal = (flags & CIRCLAUNCH_IS_INTERNAL) != 0; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); /* Make sure we're not trying to create a onehop circ by * cannibalization. */ @@ -1003,19 +1011,20 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, (!need_capacity || circ->build_state->need_capacity) && (internal == circ->build_state->is_internal) && circ->remaining_relay_early_cells && - !circ->build_state->onehop_tunnel) { + !circ->build_state->onehop_tunnel && + !circ->isolation_values_set) { if (info) { /* need to make sure we don't duplicate hops */ crypt_path_t *hop = circ->cpath; - routerinfo_t *ri1 = router_get_by_digest(info->identity_digest); + const node_t *ri1 = node_get_by_id(info->identity_digest); do { - routerinfo_t *ri2; + const node_t *ri2; if (tor_memeq(hop->extend_info->identity_digest, info->identity_digest, DIGEST_LEN)) goto next; if (ri1 && - (ri2 = router_get_by_digest(hop->extend_info->identity_digest)) - && routers_in_same_family(ri1, ri2)) + (ri2 = node_get_by_id(hop->extend_info->identity_digest)) + && nodes_in_same_family(ri1, ri2)) goto next; hop=hop->next; } while (hop!=circ->cpath); @@ -1100,7 +1109,7 @@ void circuit_expire_all_dirty_circs(void) { circuit_t *circ; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); for (circ=global_circuitlist; circ; circ = circ->next) { if (CIRCUIT_IS_ORIGIN(circ) && @@ -1119,9 +1128,11 @@ circuit_expire_all_dirty_circs(void) * - If circ isn't open yet: call circuit_build_failed() if we're * the origin, and in either case call circuit_rep_hist_note_result() * to note stats. - * - If purpose is C_INTRODUCE_ACK_WAIT, remove the intro point we - * just tried from our list of intro points for that service - * descriptor. + * - If purpose is C_INTRODUCE_ACK_WAIT, report the intro point + * failure we just had to the hidden service client module. + * - If purpose is C_INTRODUCING and <b>reason</b> isn't TIMEOUT, + * report to the hidden service client module that the intro point + * we just tried may be unreachable. * - Send appropriate destroys and edge_destroys for conns and * streams attached to circ. * - If circ->rend_splice is set (we are the midpoint of a joined @@ -1190,16 +1201,33 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line, } if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); tor_assert(ocirc->rend_data); /* treat this like getting a nack from it */ - log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). " - "Removing from descriptor.", + log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", safe_str_client(ocirc->rend_data->onion_address), + safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), + timed_out ? "Recording timeout." : "Removing from descriptor."); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + timed_out ? + INTRO_POINT_FAILURE_TIMEOUT : + INTRO_POINT_FAILURE_GENERIC); + } else if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCING && + reason != END_CIRC_REASON_TIMEOUT) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + tor_assert(ocirc->build_state->chosen_exit); + tor_assert(ocirc->rend_data); + log_info(LD_REND, "Failed intro circ %s to %s " + "(building circuit to intro point). " + "Marking intro point as possibly unreachable.", + safe_str_client(ocirc->rend_data->onion_address), safe_str_client(build_state_get_exit_nickname(ocirc->build_state))); - rend_client_remove_intro_point(ocirc->build_state->chosen_exit, - ocirc->rend_data); + rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, + ocirc->rend_data, + INTRO_POINT_FAILURE_UNREACHABLE); } if (circ->n_conn) { circuit_clear_cell_queue(circ, circ->n_conn); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 0ad8b3b51b..23efe05348 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -17,6 +17,8 @@ #include "connection.h" #include "connection_edge.h" #include "control.h" +#include "nodelist.h" +#include "networkstatus.h" #include "policies.h" #include "rendclient.h" #include "rendcommon.h" @@ -38,19 +40,19 @@ static void circuit_increment_failure_count(void); * Else return 0. */ static int -circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, +circuit_is_acceptable(const origin_circuit_t *origin_circ, + const entry_connection_t *conn, int must_be_open, uint8_t purpose, int need_uptime, int need_internal, time_t now) { - routerinfo_t *exitrouter; + const circuit_t *circ = TO_CIRCUIT(origin_circ); + const node_t *exitnode; cpath_build_state_t *build_state; tor_assert(circ); tor_assert(conn); tor_assert(conn->socks_request); - if (!CIRCUIT_IS_ORIGIN(circ)) - return 0; /* this circ doesn't start at us */ if (must_be_open && (circ->state != CIRCUIT_STATE_OPEN || !circ->n_conn)) return 0; /* ignore non-open circs */ if (circ->marked_for_close) @@ -85,8 +87,8 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, * circuit, it's the magical extra bob hop. so just check the nickname * of the one we meant to finish at. */ - build_state = TO_ORIGIN_CIRCUIT(circ)->build_state; - exitrouter = build_state_get_exit_router(build_state); + build_state = origin_circ->build_state; + exitnode = build_state_get_exit_node(build_state); if (need_uptime && !build_state->need_uptime) return 0; @@ -94,7 +96,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, return 0; if (purpose == CIRCUIT_PURPOSE_C_GENERAL) { - if (!exitrouter && !build_state->onehop_tunnel) { + if (!exitnode && !build_state->onehop_tunnel) { log_debug(LD_CIRC,"Not considering circuit with unknown router."); return 0; /* this circuit is screwed and doesn't know it yet, * or is a rendezvous circuit. */ @@ -115,7 +117,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, if (tor_digest_is_zero(digest)) { /* we don't know the digest; have to compare addr:port */ tor_addr_t addr; - int r = tor_addr_from_str(&addr, conn->socks_request->address); + int r = tor_addr_parse(&addr, conn->socks_request->address); if (r < 0 || !tor_addr_eq(&build_state->chosen_exit->addr, &addr) || build_state->chosen_exit->port != conn->socks_request->port) @@ -128,30 +130,43 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, return 0; } } - if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter)) { + if (exitnode && !connection_ap_can_use_exit(conn, exitnode)) { /* can't exit from this router */ return 0; } } else { /* not general */ - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - if ((conn->rend_data && !ocirc->rend_data) || - (!conn->rend_data && ocirc->rend_data) || - (conn->rend_data && ocirc->rend_data && - rend_cmp_service_ids(conn->rend_data->onion_address, - ocirc->rend_data->onion_address))) { + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + if ((edge_conn->rend_data && !origin_circ->rend_data) || + (!edge_conn->rend_data && origin_circ->rend_data) || + (edge_conn->rend_data && origin_circ->rend_data && + rend_cmp_service_ids(edge_conn->rend_data->onion_address, + origin_circ->rend_data->onion_address))) { /* this circ is not for this conn */ return 0; } } + + if (!connection_edge_compatible_with_circuit(conn, origin_circ)) { + /* conn needs to be isolated from other conns that have already used + * origin_circ */ + return 0; + } + return 1; } /** Return 1 if circuit <b>a</b> is better than circuit <b>b</b> for - * <b>purpose</b>, and return 0 otherwise. Used by circuit_get_best. + * <b>conn</b>, and return 0 otherwise. Used by circuit_get_best. */ static int -circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) +circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob, + const entry_connection_t *conn) { + const circuit_t *a = TO_CIRCUIT(oa); + const circuit_t *b = TO_CIRCUIT(ob); + const uint8_t purpose = ENTRY_TO_CONN(conn)->purpose; + int a_bits, b_bits; + switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: /* if it's used but less dirty it's best; @@ -165,8 +180,7 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) if (a->timestamp_dirty || timercmp(&a->timestamp_created, &b->timestamp_created, >)) return 1; - if (CIRCUIT_IS_ORIGIN(b) && - TO_ORIGIN_CIRCUIT(b)->build_state->is_internal) + if (ob->build_state->is_internal) /* XXX023 what the heck is this internal thing doing here. I * think we can get rid of it. circuit_is_acceptable() already * makes sure that is_internal is exactly what we need it to @@ -185,6 +199,29 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) return 1; break; } + + /* XXXX023 Maybe this check should get a higher priority to avoid + * using up circuits too rapidly. */ + + a_bits = connection_edge_update_circuit_isolation(conn, + (origin_circuit_t*)oa, 1); + b_bits = connection_edge_update_circuit_isolation(conn, + (origin_circuit_t*)ob, 1); + /* if x_bits < 0, then we have not used x for anything; better not to dirty + * a connection if we can help it. */ + if (a_bits < 0) { + return 0; + } else if (b_bits < 0) { + return 1; + } + a_bits &= ~ oa->isolation_flags_mixed; + a_bits &= ~ ob->isolation_flags_mixed; + if (n_bits_set_u8(a_bits) < n_bits_set_u8(b_bits)) { + /* The fewer new restrictions we need to make on a circuit for stream + * isolation, the better. */ + return 1; + } + return 0; } @@ -205,10 +242,12 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose) * closest introduce-purposed circuit that you can find. */ static origin_circuit_t * -circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, +circuit_get_best(const entry_connection_t *conn, + int must_be_open, uint8_t purpose, int need_uptime, int need_internal) { - circuit_t *circ, *best=NULL; + circuit_t *circ; + origin_circuit_t *best=NULL; struct timeval now; int intro_going_on_but_too_old = 0; @@ -221,7 +260,11 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, tor_gettimeofday(&now); for (circ=global_circuitlist;circ;circ = circ->next) { - if (!circuit_is_acceptable(circ,conn,must_be_open,purpose, + origin_circuit_t *origin_circ; + 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; @@ -235,8 +278,8 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, /* 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. */ - if (!best || circuit_is_better(circ,best,purpose)) - best = circ; + if (!best || circuit_is_better(origin_circ,best,conn)) + best = origin_circ; } if (!best && intro_going_on_but_too_old) @@ -244,7 +287,28 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose, "right now, but it has already taken quite a while. Starting " "one in parallel."); - return best ? TO_ORIGIN_CIRCUIT(best) : NULL; + return best; +} + +/** Return the number of not-yet-open general-purpose origin circuits. */ +static int +count_pending_general_client_circuits(void) +{ + const circuit_t *circ; + + int count = 0; + + for (circ = global_circuitlist; circ; circ = circ->next) { + if (circ->marked_for_close || + circ->state == CIRCUIT_STATE_OPEN || + circ->purpose != CIRCUIT_PURPOSE_C_GENERAL || + !CIRCUIT_IS_ORIGIN(circ)) + continue; + + ++count; + } + + return count; } #if 0 @@ -481,11 +545,11 @@ circuit_remove_handled_ports(smartlist_t *needed_ports) * Else return 0. */ int -circuit_stream_is_being_handled(edge_connection_t *conn, +circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port, int min) { circuit_t *circ; - routerinfo_t *exitrouter; + const node_t *exitnode; int num=0; time_t now = time(NULL); int need_uptime = smartlist_string_num_isin(get_options()->LongLivedPorts, @@ -501,14 +565,14 @@ circuit_stream_is_being_handled(edge_connection_t *conn, if (build_state->is_internal || build_state->onehop_tunnel) continue; - exitrouter = build_state_get_exit_router(build_state); - if (exitrouter && (!need_uptime || build_state->need_uptime)) { + exitnode = build_state_get_exit_node(build_state); + if (exitnode && (!need_uptime || build_state->need_uptime)) { int ok; if (conn) { - ok = connection_ap_can_use_exit(conn, exitrouter); + ok = connection_ap_can_use_exit(conn, exitnode); } else { - addr_policy_result_t r = compare_addr_to_addr_policy( - 0, port, exitrouter->exit_policy); + addr_policy_result_t r; + r = compare_tor_addr_to_node_policy(NULL, port, exitnode); ok = r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED; } if (ok) { @@ -575,7 +639,7 @@ circuit_predict_and_launch_new(void) log_info(LD_CIRC, "Have %d clean circs (%d internal), need another exit circ.", num, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -587,7 +651,7 @@ circuit_predict_and_launch_new(void) "Have %d clean circs (%d internal), need another internal " "circ for my hidden service.", num, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -605,7 +669,7 @@ circuit_predict_and_launch_new(void) "Have %d clean circs (%d uptime-internal, %d internal), need" " another hidden service circ.", num, num_uptime_internal, num_internal); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } @@ -618,7 +682,7 @@ circuit_predict_and_launch_new(void) flags = CIRCLAUNCH_NEED_CAPACITY; log_info(LD_CIRC, "Have %d clean circs need another buildtime test circ.", num); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags); return; } } @@ -635,7 +699,7 @@ void circuit_build_needed_circs(time_t now) { static time_t time_to_new_circuit = 0; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); /* launch a new circ for any pending streams that need one */ connection_ap_attach_pending(); @@ -654,9 +718,9 @@ 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 + TESTING_CIRCUIT_INTERVAL < now) { + circ->timestamp_created.tv_sec + TESTING_CIRCUIT_INTERVAL < now) { log_fn(LOG_INFO,"Creating a new testing circuit."); - circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, 0); + circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } #endif } @@ -675,7 +739,11 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) tor_assert(circ); tor_assert(conn); - conn->cpath_layer = NULL; /* make sure we don't keep a stale pointer */ + if (conn->_base.type == CONN_TYPE_AP) { + entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); + entry_conn->may_use_optimistic_data = 0; + } + conn->cpath_layer = NULL; /* don't keep a stale pointer */ conn->on_circuit = NULL; if (CIRCUIT_IS_ORIGIN(circ)) { @@ -936,6 +1004,7 @@ circuit_testing_failed(origin_circuit_t *circ, int at_last_hop) void circuit_has_opened(origin_circuit_t *circ) { + int can_try_clearing_isolation = 0, tried_clearing_isolation = 0; control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0); /* Remember that this circuit has finished building. Now if we start @@ -943,9 +1012,12 @@ circuit_has_opened(origin_circuit_t *circ) * to consider its build time. */ circ->has_opened = 1; + again: + switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: rend_client_rendcirc_has_opened(circ); + can_try_clearing_isolation = 1; connection_ap_attach_pending(); break; case CIRCUIT_PURPOSE_C_INTRODUCING: @@ -954,6 +1026,7 @@ circuit_has_opened(origin_circuit_t *circ) case CIRCUIT_PURPOSE_C_GENERAL: /* Tell any AP connections that have been waiting for a new * circuit that one is ready. */ + can_try_clearing_isolation = 1; connection_ap_attach_pending(); break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: @@ -971,6 +1044,24 @@ circuit_has_opened(origin_circuit_t *circ) * This won't happen in normal operation, but might happen if the * controller did it. Just let it slide. */ } + + if (/* The circuit may have become non-open if it was cannibalized.*/ + circ->_base.state == CIRCUIT_STATE_OPEN && + /* Only if the purpose is clearable, and only if we haven't tried + * to clear isolation yet, do we try. */ + can_try_clearing_isolation && !tried_clearing_isolation && + /* 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 + * streams attached */ + !circ->isolation_any_streams_attached) { + /* If we have any isolation information set on this circuit, and + * we didn't manage to attach any streams to it, then we can + * and should clear it and try again. */ + circuit_clear_isolation(circ); + tried_clearing_isolation = 1; + goto again; + } } /** Called whenever a circuit could not be successfully built. @@ -1091,17 +1182,9 @@ static int did_circs_fail_last_period = 0; /** Launch a new circuit; see circuit_launch_by_extend_info() for * details on arguments. */ origin_circuit_t * -circuit_launch_by_router(uint8_t purpose, - routerinfo_t *exit, int flags) +circuit_launch(uint8_t purpose, int flags) { - origin_circuit_t *circ; - extend_info_t *info = NULL; - if (exit) - info = extend_info_from_router(exit); - circ = circuit_launch_by_extend_info(purpose, info, flags); - - extend_info_free(info); - return circ; + return circuit_launch_by_extend_info(purpose, NULL, flags); } /** Launch a new circuit with purpose <b>purpose</b> and exit node @@ -1207,7 +1290,7 @@ circuit_reset_failure_count(int timeout) * Write the found or in-progress or launched circ into *circp. */ static int -circuit_get_open_circ_or_launch(edge_connection_t *conn, +circuit_get_open_circ_or_launch(entry_connection_t *conn, uint8_t desired_circuit_purpose, origin_circuit_t **circp) { @@ -1215,15 +1298,15 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, int check_exit_policy; int need_uptime, need_internal; int want_onehop; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); tor_assert(conn); tor_assert(circp); - tor_assert(conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); + tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_CIRCUIT_WAIT); check_exit_policy = conn->socks_request->command == SOCKS_COMMAND_CONNECT && !conn->use_begindir && - !connection_edge_is_rendezvous_stream(conn); + !connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn)); want_onehop = conn->want_onehop; need_uptime = !conn->want_onehop && !conn->use_begindir && @@ -1269,12 +1352,14 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, if (check_exit_policy) { if (!conn->chosen_exit_name) { struct in_addr in; - uint32_t addr = 0; - if (tor_inet_aton(conn->socks_request->address, &in)) - addr = ntohl(in.s_addr); - if (router_exit_policy_all_routers_reject(addr, - conn->socks_request->port, - need_uptime)) { + tor_addr_t addr, *addrp=NULL; + if (tor_inet_aton(conn->socks_request->address, &in)) { + tor_addr_from_in(&addr, &in); + addrp = &addr; + } + if (router_exit_policy_all_nodes_reject(addrp, + conn->socks_request->port, + need_uptime)) { log_notice(LD_APP, "No Tor server allows exit to %s:%d. Rejecting.", safe_str_client(conn->socks_request->address), @@ -1284,9 +1369,9 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, } else { /* XXXX023 Duplicates checks in connection_ap_handshake_attach_circuit: * refactor into a single function? */ - routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; - if (router && !connection_ap_can_use_exit(conn, router)) { + if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, "Requested exit point '%s' is excluded or " "would refuse request. %s.", @@ -1312,22 +1397,37 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, if (!circ) { extend_info_t *extend_info=NULL; uint8_t new_circ_purpose; + const int n_pending = count_pending_general_client_circuits(); + + if (n_pending >= options->MaxClientCircuitsPending) { + static ratelim_t delay_limit = RATELIM_INIT(10*60); + char *m; + 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); + tor_free(m); + } + return 0; + } if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { /* need to pick an intro point */ - tor_assert(conn->rend_data); - extend_info = rend_client_get_random_intro(conn->rend_data); + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; + tor_assert(rend_data); + extend_info = rend_client_get_random_intro(rend_data); if (!extend_info) { log_info(LD_REND, "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(conn->rend_data->onion_address)); - rend_client_refetch_v2_renddesc(conn->rend_data); - conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT; + safe_str_client(rend_data->onion_address)); + rend_client_refetch_v2_renddesc(rend_data); + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(conn->rend_data->onion_address)); + safe_str_client(rend_data->onion_address)); } /* If we have specified a particular exit node for our @@ -1335,11 +1435,11 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) { if (conn->chosen_exit_name) { - routerinfo_t *r; + const node_t *r; int opt = conn->chosen_exit_optional; - r = router_get_by_nickname(conn->chosen_exit_name, 1); - if (r) { - extend_info = extend_info_from_router(r); + r = node_get_by_nickname(conn->chosen_exit_name, 1); + if (r && node_has_descriptor(r)) { + extend_info = extend_info_from_node(r); } else { log_debug(LD_DIR, "considering %d, %s", want_onehop, conn->chosen_exit_name); @@ -1354,7 +1454,7 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, log_info(LD_DIR, "Broken exit digest on tunnel conn. Closing."); return -1; } - if (tor_addr_from_str(&addr, conn->socks_request->address) < 0) { + if (tor_addr_parse(&addr, conn->socks_request->address) < 0) { log_info(LD_DIR, "Broken address %s on tunnel conn. Closing.", escaped_safe_str_client(conn->socks_request->address)); return -1; @@ -1416,18 +1516,26 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn, rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { /* write the service_id into circ */ - circ->rend_data = rend_data_dup(conn->rend_data); + 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) rend_client_rendcirc_has_opened(circ); } } - } - if (!circ) + } /* endif (!circ) */ + if (circ) { + /* Mark the circuit with the isolation fields for this connection. + * When the circuit arrives, we'll clear these flags: this is + * just some internal bookkeeping to make sure that we have + * launched enough circuits. + */ + connection_edge_update_circuit_isolation(conn, circ, 0); + } else { log_info(LD_APP, "No safe circuit (purpose %d) ready for edge " "connection; delaying.", desired_circuit_purpose); + } *circp = circ; return 0; } @@ -1446,39 +1554,86 @@ cpath_is_on_circuit(origin_circuit_t *circ, crypt_path_t *crypt_path) return 0; } +/** Return true iff client-side optimistic data is supported. */ +static int +optimistic_data_enabled(void) +{ + const or_options_t *options = get_options(); + if (options->OptimisticData < 0) { + /* XXX023 consider having auto default to 1 rather than 0 before + * the 0.2.3 branch goes stable. See bug 3617. -RD */ + const int32_t enabled = + networkstatus_get_param(NULL, "UseOptimisticData", 0, 0, 1); + return (int)enabled; + } + return options->OptimisticData; +} + /** Attach the AP stream <b>apconn</b> to circ's linked list of * p_streams. Also set apconn's cpath_layer to <b>cpath</b>, or to the last * hop in circ's cpath if <b>cpath</b> is NULL. */ static void -link_apconn_to_circ(edge_connection_t *apconn, origin_circuit_t *circ, +link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, crypt_path_t *cpath) { + const node_t *exitnode; + /* 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); /* reset it, so we can measure circ timeouts */ - apconn->_base.timestamp_lastread = time(NULL); - apconn->next_stream = circ->p_streams; - apconn->on_circuit = TO_CIRCUIT(circ); + ENTRY_TO_CONN(apconn)->timestamp_lastread = time(NULL); + ENTRY_TO_EDGE_CONN(apconn)->next_stream = circ->p_streams; + ENTRY_TO_EDGE_CONN(apconn)->on_circuit = TO_CIRCUIT(circ); /* assert_connection_ok(conn, time(NULL)); */ - circ->p_streams = apconn; + circ->p_streams = ENTRY_TO_EDGE_CONN(apconn); + + if (connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(apconn))) { + /* We are attaching a stream to a rendezvous circuit. That means + * that an attempt to connect to a hidden service just + * succeeded. Tell rendclient.c. */ + rend_client_note_connection_attempt_ended( + ENTRY_TO_EDGE_CONN(apconn)->rend_data->onion_address); + } if (cpath) { /* we were given one; use it */ tor_assert(cpath_is_on_circuit(circ, cpath)); - apconn->cpath_layer = cpath; - } else { /* use the last hop in the circuit */ + } else { + /* use the last hop in the circuit */ tor_assert(circ->cpath); tor_assert(circ->cpath->prev); tor_assert(circ->cpath->prev->state == CPATH_STATE_OPEN); - apconn->cpath_layer = circ->cpath->prev; + cpath = circ->cpath->prev; + } + ENTRY_TO_EDGE_CONN(apconn)->cpath_layer = cpath; + + circ->isolation_any_streams_attached = 1; + connection_edge_update_circuit_isolation(apconn, circ, 0); + + /* See if we can use optimistic data on this circuit */ + if (cpath->extend_info && + (exitnode = node_get_by_id(cpath->extend_info->identity_digest)) && + exitnode->rs) { + /* Okay; we know what exit node this is. */ + if (optimistic_data_enabled() && + circ->_base.purpose == CIRCUIT_PURPOSE_C_GENERAL && + exitnode->rs->version_supports_optimistic_data) + apconn->may_use_optimistic_data = 1; + else + apconn->may_use_optimistic_data = 0; + log_info(LD_APP, "Looks like completed circuit to %s %s allow " + "optimistic data for connection to %s", + safe_str_client(node_describe(exitnode)), + apconn->may_use_optimistic_data ? "does" : "doesn't", + safe_str_client(apconn->socks_request->address)); } } /** Return true iff <b>address</b> is matched by one of the entries in * TrackHostExits. */ int -hostname_in_track_host_exits(or_options_t *options, const char *address) +hostname_in_track_host_exits(const or_options_t *options, const char *address) { if (!options->TrackHostExits) return 0; @@ -1500,9 +1655,10 @@ hostname_in_track_host_exits(or_options_t *options, const char *address) * <b>conn</b>'s destination. */ static void -consider_recording_trackhost(edge_connection_t *conn, origin_circuit_t *circ) +consider_recording_trackhost(const entry_connection_t *conn, + const origin_circuit_t *circ) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); char *new_address = NULL; char fp[HEX_DIGEST_LEN+1]; @@ -1537,18 +1693,19 @@ consider_recording_trackhost(edge_connection_t *conn, origin_circuit_t *circ) * indicated by <b>cpath</b>, or from the last hop in circ's cpath if * <b>cpath</b> is NULL. */ int -connection_ap_handshake_attach_chosen_circuit(edge_connection_t *conn, +connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath) { + connection_t *base_conn = ENTRY_TO_CONN(conn); tor_assert(conn); - tor_assert(conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT || - conn->_base.state == AP_CONN_STATE_CONTROLLER_WAIT); + tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT || + base_conn->state == AP_CONN_STATE_CONTROLLER_WAIT); tor_assert(conn->socks_request); tor_assert(circ); tor_assert(circ->_base.state == CIRCUIT_STATE_OPEN); - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; if (!circ->_base.timestamp_dirty) circ->_base.timestamp_dirty = time(NULL); @@ -1578,21 +1735,22 @@ connection_ap_handshake_attach_chosen_circuit(edge_connection_t *conn, /* XXXX this function should mark for close whenever it returns -1; * its callers shouldn't have to worry about that. */ int -connection_ap_handshake_attach_circuit(edge_connection_t *conn) +connection_ap_handshake_attach_circuit(entry_connection_t *conn) { + connection_t *base_conn = ENTRY_TO_CONN(conn); int retval; int conn_age; int want_onehop; tor_assert(conn); - tor_assert(conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); + tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT); tor_assert(conn->socks_request); want_onehop = conn->want_onehop; - conn_age = (int)(time(NULL) - conn->_base.timestamp_created); + conn_age = (int)(time(NULL) - base_conn->timestamp_created); if (conn_age >= get_options()->SocksTimeout) { - int severity = (tor_addr_is_null(&conn->_base.addr) && !conn->_base.port) ? + int severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ? LOG_INFO : LOG_NOTICE; log_fn(severity, LD_APP, "Tried for %d seconds to get a connection to %s:%d. Giving up.", @@ -1601,13 +1759,14 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) return -1; } - if (!connection_edge_is_rendezvous_stream(conn)) { /* we're a general conn */ + if (!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn))) { + /* we're a general conn */ origin_circuit_t *circ=NULL; if (conn->chosen_exit_name) { - routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; - if (!router && !want_onehop) { + if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a * hidden service directory for which we didn't have a router * descriptor. See flyspray task 767 for more details. We should @@ -1623,7 +1782,7 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) } return -1; } - if (router && !connection_ap_can_use_exit(conn, router)) { + if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, "Requested exit point '%s' is excluded or " "would refuse request. %s.", @@ -1656,7 +1815,7 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) } else { /* we're a rendezvous conn */ origin_circuit_t *rendcirc=NULL, *introcirc=NULL; - tor_assert(!conn->cpath_layer); + tor_assert(!ENTRY_TO_EDGE_CONN(conn)->cpath_layer); /* start by finding a rendezvous circuit for us */ @@ -1712,8 +1871,9 @@ connection_ap_handshake_attach_circuit(edge_connection_t *conn) !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); if (oc->rend_data && - !rend_cmp_service_ids(conn->rend_data->onion_address, - oc->rend_data->onion_address)) { + !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); diff --git a/src/or/circuituse.h b/src/or/circuituse.h index bfeaea20dc..9867fd8205 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -14,7 +14,7 @@ void circuit_expire_building(void); void circuit_remove_handled_ports(smartlist_t *needed_ports); -int circuit_stream_is_being_handled(edge_connection_t *conn, uint16_t port, +int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port, int min); #if 0 int circuit_conforms_to_options(const origin_circuit_t *circ, @@ -43,15 +43,15 @@ void circuit_build_failed(origin_circuit_t *circ); origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); -origin_circuit_t *circuit_launch_by_router(uint8_t purpose, - routerinfo_t *exit, int flags); +origin_circuit_t *circuit_launch(uint8_t purpose, int flags); void circuit_reset_failure_count(int timeout); -int connection_ap_handshake_attach_chosen_circuit(edge_connection_t *conn, +int connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); -int connection_ap_handshake_attach_circuit(edge_connection_t *conn); +int connection_ap_handshake_attach_circuit(entry_connection_t *conn); -int hostname_in_track_host_exits(or_options_t *options, const char *address); +int hostname_in_track_host_exits(const or_options_t *options, + const char *address); #endif diff --git a/src/or/command.c b/src/or/command.c index 5271a7a907..c02d353bb1 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -25,6 +25,7 @@ #include "control.h" #include "cpuworker.h" #include "hibernate.h" +#include "nodelist.h" #include "onion.h" #include "relay.h" #include "router.h" @@ -45,6 +46,15 @@ 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_cert_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; + /* 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); @@ -53,6 +63,12 @@ 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_cert_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); #ifdef KEEP_TIMING_STATS /** This is a wrapper function around the actual function that processes the @@ -92,7 +108,7 @@ command_time_process_cell(cell_t *cell, or_connection_t *conn, int *time, void command_process_cell(cell_t *cell, or_connection_t *conn) { - int handshaking = (conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING); + 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. */ @@ -136,9 +152,18 @@ command_process_cell(cell_t *cell, or_connection_t *conn) 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) + cell->command != CELL_NETINFO) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received unexpected cell command %d in state %s; ignoring it.", + (int)cell->command, + conn_state_to_string(CONN_TYPE_OR,conn->_base.state)); 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: @@ -211,20 +236,79 @@ command_process_var_cell(var_cell_t *cell, or_connection_t *conn) if (conn->_base.marked_for_close) return; - /* reject all when not handshaking. */ - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING) - return; + switch (conn->_base.state) + { + case OR_CONN_STATE_OR_HANDSHAKING_V2: + if (cell->command != CELL_VERSIONS) + 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 (cell->command != CELL_VERSIONS) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received a non-VERSIONS cell with command %d in state %s; " + "ignoring it.", + (int)cell->command, + conn_state_to_string(CONN_TYPE_OR,conn->_base.state)); + 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; + } 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_CERT: + ++stats_n_cert_cells_processed; + PROCESS_CELL(cert, 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; default: - log_warn(LD_BUG, + log_fn(LOG_INFO, LD_PROTOCOL, "Variable-length cell of unknown type (%d) received.", cell->command); - tor_fragile_assert(); break; } } @@ -238,7 +322,7 @@ static void command_process_create_cell(cell_t *cell, or_connection_t *conn) { or_circuit_t *circ; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int id_is_high; if (we_are_hibernating()) { @@ -276,15 +360,18 @@ command_process_create_cell(cell_t *cell, or_connection_t *conn) } if (circuit_id_in_use_on_orconn(cell->circ_id, conn)) { - routerinfo_t *router = router_get_by_digest(conn->identity_digest); + const node_t *node = node_get_by_id(conn->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)); - if (router) + if (node) { + char *p = esc_for_log(node_get_platform(node)); log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Details: router %s, platform %s.", - router_describe(router), escaped(router->platform)); + node_describe(node), p); + tor_free(p); + } return; } @@ -516,14 +603,40 @@ 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->_base.state != OR_CONN_STATE_OR_HANDSHAKING || (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: + break; + case OR_CONN_STATE_TLS_HANDSHAKING: + case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: + if (started_here) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, + "Received a versions cell while TLS-handshaking not in " + "OR_HANDSHAKING_V3 on a connection we originated."); + } + 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; + } + or_handshake_state_record_var_cell(conn->handshake_state, cell, 1); + break; + case OR_CONN_STATE_OR_HANDSHAKING_V3: + break; + 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) { @@ -545,19 +658,87 @@ command_process_versions_cell(var_cell_t *cell, or_connection_t *conn) "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; } + conn->link_proto = highest_supported_version; conn->handshake_state->received_versions = 1; - log_info(LD_OR, "Negotiated version %d with %s:%d; sending NETINFO.", - highest_supported_version, - safe_str_client(conn->_base.address), - conn->_base.port); - tor_assert(conn->link_proto >= 2); + 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; + 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, or if we have no intention of + * authenticating, send a netinfo cell right now. */ + const int send_netinfo = + !(started_here && public_server_mode(get_options())); + 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_cert_cell(conn) < 0) { + log_warn(LD_OR, "Couldn't send cert 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; + } + } } } @@ -583,13 +764,41 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) conn->link_proto == 0 ? "non-versioned" : "a v1"); return; } - if (conn->_base.state != OR_CONN_STATE_OR_HANDSHAKING) { + 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) { @@ -635,7 +844,7 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) /** 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_digest(conn->identity_digest)) { + router_get_by_id_digest(conn->identity_digest)) { char dbuf[64]; int severity; /*XXXX be smarter about when everybody says we are skewed. */ @@ -662,13 +871,391 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn) * trustworthy. */ (void)my_apparent_addr; - if (connection_or_set_state_open(conn)<0) + 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 + } else { log_info(LD_OR, "Got good NETINFO cell from %s:%d; OR connection is now " - "open, using protocol version %d", + "open, using protocol version %d. Its ID digest is %s", safe_str_client(conn->_base.address), - conn->_base.port, (int)conn->link_proto); + conn->_base.port, (int)conn->link_proto, + hex_str(conn->identity_digest, DIGEST_LEN)); + } assert_connection_ok(TO_CONN(conn),time(NULL)); } +/** Process a CERT cell from an OR connection. + * + * If the other side should not have sent us a CERT 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 +command_process_cert_cell(var_cell_t *cell, or_connection_t *conn) +{ +#define ERR(s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad CERT 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; + + 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_cert_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 CERT 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) { + 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"); + } + if (! tor_tls_cert_is_valid(link_cert, id_cert, 0)) + ERR("The link certificate was not valid"); + if (! tor_tls_cert_is_valid(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_env_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_free_pk_env(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; + } 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(auth_cert, id_cert, 1)) + ERR("The authentication certificate was not valid"); + if (! tor_tls_cert_is_valid(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_cert_cell = 1; + 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. */ +static void +command_process_auth_challenge_cell(var_cell_t *cell, or_connection_t *conn) +{ + 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_cert_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 (use_type && public_server_mode(get_options())) { + 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; + } + if (connection_or_send_netinfo(conn) < 0) { + log_warn(LD_OR, "Couldn't send netinfo cell"); + connection_mark_for_close(TO_CONN(conn)); + return; + } + } else { + log_info(LD_OR, "Got an AUTH_CHALLENGE cell from %s:%d: Not " + "authenticating", + safe_str(conn->_base.address), conn->_base.port); + } +#undef ERR +} + +/** 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 CERT cell, etc) mark the connection. Otherwise, accept + * the identity of the router on the other side of the connection. + */ +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_cert_cell) + ERR("We never got a cert 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_env_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_free_pk_env(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_env_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_free_pk_env(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); + } + +#undef ERR +} + diff --git a/src/or/config.c b/src/or/config.c index ca4e71cccd..1b9f9fb475 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -33,13 +33,18 @@ #include "rendservice.h" #include "rephist.h" #include "router.h" +#include "util.h" #include "routerlist.h" +#include "transports.h" #ifdef MS_WINDOWS #include <shlobj.h> #endif #include "procmon.h" +/* 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. */ @@ -48,9 +53,13 @@ typedef enum config_type_t { 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. */ @@ -199,10 +208,12 @@ static config_var_t _option_vars[] = { V(CircuitStreamTimeout, INTERVAL, "0"), V(CircuitPriorityHalflife, DOUBLE, "-100.0"), /*negative:'Use default'*/ V(ClientDNSRejectInternalAddresses, BOOL,"1"), - V(ClientRejectInternalAddresses, BOOL, "1"), V(ClientOnly, BOOL, "0"), + V(ClientRejectInternalAddresses, BOOL, "1"), + V(ClientTransportPlugin, LINELIST, NULL), V(ConsensusParams, STRING, NULL), V(ConnLimit, UINT, "1000"), + V(ConnDirectionStatistics, BOOL, "0"), V(ConstrainedSockets, BOOL, "0"), V(ConstrainedSockSize, MEMUNIT, "8192"), V(ContactInfo, STRING, NULL), @@ -215,6 +226,7 @@ static config_var_t _option_vars[] = { V(CookieAuthentication, BOOL, "0"), V(CookieAuthFileGroupReadable, BOOL, "0"), V(CookieAuthFile, STRING, NULL), + V(CountPrivateBandwidth, BOOL, "0"), V(DataDirectory, FILENAME, NULL), OBSOLETE("DebugLogFile"), V(DirAllowPrivateAddresses, BOOL, NULL), @@ -232,7 +244,8 @@ static config_var_t _option_vars[] = { V(DirReqStatistics, BOOL, "1"), VAR("DirServer", LINELIST, DirServers, NULL), V(DisableAllSwap, BOOL, "0"), - V(DNSPort, PORT, "0"), + V(DisableIOCP, BOOL, "1"), + V(DNSPort, LINELIST, NULL), V(DNSListenAddress, LINELIST, NULL), V(DownloadExtraInfo, BOOL, "0"), V(EnforceDistinctSubnets, BOOL, "1"), @@ -273,6 +286,7 @@ static config_var_t _option_vars[] = { BOOL, "0"), OBSOLETE("Group"), V(HardwareAccel, BOOL, "0"), + V(HeartbeatPeriod, INTERVAL, "6 hours"), V(AccelName, STRING, NULL), V(AccelDir, FILENAME, NULL), V(HashedControlPassword, LINELIST, NULL), @@ -291,6 +305,7 @@ static config_var_t _option_vars[] = { V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), V(HTTPSProxyAuthenticator, STRING, NULL), + VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(Socks4Proxy, STRING, NULL), V(Socks5Proxy, STRING, NULL), V(Socks5ProxyUsername, STRING, NULL), @@ -302,23 +317,25 @@ static config_var_t _option_vars[] = { OBSOLETE("LinkPadding"), OBSOLETE("LogLevel"), OBSOLETE("LogFile"), + V(LogTimeGranularity, MSEC_INTERVAL, "1 second"), V(LongLivedPorts, CSV, - "21,22,706,1863,5050,5190,5222,5223,6667,6697,8300"), + "21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300"), VAR("MapAddress", LINELIST, AddressMap, NULL), V(MaxAdvertisedBandwidth, MEMUNIT, "1 GB"), V(MaxCircuitDirtiness, INTERVAL, "10 minutes"), + V(MaxClientCircuitsPending, UINT, "32"), V(MaxOnionsPending, UINT, "100"), OBSOLETE("MonthlyAccountingStart"), V(MyFamily, STRING, NULL), V(NewCircuitPeriod, INTERVAL, "30 seconds"), VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"), V(NATDListenAddress, LINELIST, NULL), - V(NATDPort, PORT, "0"), + V(NATDPort, LINELIST, NULL), V(Nickname, STRING, NULL), V(WarnUnsafeSocks, BOOL, "1"), OBSOLETE("NoPublish"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), - V(NumCPUs, UINT, "1"), + V(NumCPUs, UINT, "0"), V(NumEntryGuards, UINT, "3"), V(ORListenAddress, LINELIST, NULL), V(ORPort, PORT, "0"), @@ -328,6 +345,9 @@ static config_var_t _option_vars[] = { V(PerConnBWRate, MEMUNIT, "0"), V(PidFile, STRING, NULL), V(TestingTorNetwork, BOOL, "0"), + V(OptimisticData, AUTOBOOL, "auto"), + V(PortForwarding, BOOL, "0"), + V(PortForwardingHelper, FILENAME, "tor-fw-helper"), V(PreferTunneledDirConns, BOOL, "1"), V(ProtocolWarnings, BOOL, "0"), V(PublishServerDescriptor, CSV, "1"), @@ -339,7 +359,7 @@ static config_var_t _option_vars[] = { V(RecommendedClientVersions, LINELIST, NULL), V(RecommendedServerVersions, LINELIST, NULL), OBSOLETE("RedirectExit"), - V(RefuseUnknownExits, STRING, "auto"), + V(RefuseUnknownExits, AUTOBOOL, "auto"), V(RejectPlaintextPorts, CSV, ""), V(RelayBandwidthBurst, MEMUNIT, "0"), V(RelayBandwidthRate, MEMUNIT, "0"), @@ -364,22 +384,24 @@ static config_var_t _option_vars[] = { V(ShutdownWaitLength, INTERVAL, "30 seconds"), V(SocksListenAddress, LINELIST, NULL), V(SocksPolicy, LINELIST, NULL), - V(SocksPort, PORT, "9050"), + V(SocksPort, LINELIST, NULL), V(SocksTimeout, INTERVAL, "2 minutes"), OBSOLETE("StatusFetchPeriod"), V(StrictNodes, BOOL, "0"), OBSOLETE("SysLog"), V(TestSocks, BOOL, "0"), OBSOLETE("TestVia"), + V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"), V(TrackHostExits, CSV, NULL), V(TrackHostExitsExpire, INTERVAL, "30 minutes"), OBSOLETE("TrafficShaping"), V(TransListenAddress, LINELIST, NULL), - V(TransPort, PORT, "0"), + V(TransPort, LINELIST, NULL), V(TunnelDirConns, BOOL, "1"), V(UpdateBridgesFromAuthority, BOOL, "0"), V(UseBridges, BOOL, "0"), V(UseEntryGuards, BOOL, "1"), + V(UseMicrodescriptors, AUTOBOOL, "auto"), V(User, STRING, NULL), VAR("V1AuthoritativeDirectory",BOOL, V1AuthoritativeDir, "0"), VAR("V2AuthoritativeDirectory",BOOL, V2AuthoritativeDir, "0"), @@ -396,6 +418,7 @@ static config_var_t _option_vars[] = { VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"), V(VirtualAddrNetwork, STRING, "127.192.0.0/10"), V(WarnPlaintextPorts, CSV, "23,109,110,143"), + V(_UseFilteringSSLBufferevents, BOOL, "0"), VAR("__ReloadTorrcOnSIGHUP", BOOL, ReloadTorrcOnSIGHUP, "1"), VAR("__AllDirActionsPrivate", BOOL, AllDirActionsPrivate, "0"), VAR("__DisablePredictedCircuits",BOOL,DisablePredictedCircuits, "0"), @@ -412,7 +435,7 @@ static config_var_t _option_vars[] = { /** Override default values with these if the user sets the TestingTorNetwork * option. */ -static config_var_t testing_tor_network_defaults[] = { +static const config_var_t testing_tor_network_defaults[] = { V(ServerDNSAllowBrokenConfig, BOOL, "1"), V(DirAllowPrivateAddresses, BOOL, "1"), V(EnforceDistinctSubnets, BOOL, "0"), @@ -421,6 +444,7 @@ static config_var_t testing_tor_network_defaults[] = { V(AuthDirMaxServersPerAuthAddr,UINT, "0"), V(ClientDNSRejectInternalAddresses, BOOL,"0"), V(ClientRejectInternalAddresses, BOOL, "0"), + V(CountPrivateBandwidth, BOOL, "1"), V(ExitPolicyRejectPrivate, BOOL, "0"), V(V3AuthVotingInterval, INTERVAL, "5 minutes"), V(V3AuthVoteDelay, INTERVAL, "20 seconds"), @@ -432,6 +456,7 @@ static config_var_t testing_tor_network_defaults[] = { V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), V(_UsingTestNetworkDefaults, BOOL, "1"), + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } }; #undef VAR @@ -457,6 +482,9 @@ static config_var_t _state_vars[] = { VAR("EntryGuardAddedBy", 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, ""), @@ -483,7 +511,6 @@ static config_var_t _state_vars[] = { V(CircuitBuildAbandonedCount, UINT, "0"), VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } }; @@ -534,39 +561,48 @@ 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(config_format_t *fmt, or_options_t *options, - config_var_t *var); -static void option_reset(config_format_t *fmt, or_options_t *options, - config_var_t *var, int use_defaults); -static void config_free(config_format_t *fmt, void *options); +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 option_is_same(config_format_t *fmt, - or_options_t *o1, or_options_t *o2, +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(config_format_t *fmt, or_options_t *old); -static int options_validate(or_options_t *old_options, or_options_t *options, +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); -static int options_act_reversible(or_options_t *old_options, char **msg); -static int options_act(or_options_t *old_options); -static int options_transition_allowed(or_options_t *old, or_options_t *new, +static int options_act_reversible(const or_options_t *old_options, char **msg); +static int options_act(const or_options_t *old_options); +static int options_transition_allowed(const or_options_t *old, + const or_options_t *new, char **msg); -static int options_transition_affects_workers(or_options_t *old_options, - or_options_t *new_options); -static int options_transition_affects_descriptor(or_options_t *old_options, - or_options_t *new_options); +static int options_transition_affects_workers( + const or_options_t *old_options, const or_options_t *new_options); +static int options_transition_affects_descriptor( + const or_options_t *old_options, const or_options_t *new_options); static int check_nickname_list(const char *lst, const char *name, char **msg); -static void config_register_addressmaps(or_options_t *options); 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, - authority_type_t required_type, + dirinfo_type_t required_type, int validate_only); +static void port_cfg_free(port_cfg_t *port); +static int parse_client_ports(const or_options_t *options, int validate_only, + char **msg_out, int *n_ports_out); static int validate_data_directory(or_options_t *options); -static int write_configuration_file(const char *fname, or_options_t *options); -static config_line_t *get_assigned_option(config_format_t *fmt, - void *options, const char *key, - int escape_val); -static void config_init(config_format_t *fmt, void *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); @@ -576,8 +612,9 @@ static int is_listening_on_low_port(int port_option, const config_line_t *listen_options); 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(void); +static void init_libevent(const or_options_t *options); static int opt_streq(const char *s1, const char *s2); /** Magic value for or_options_t. */ @@ -604,7 +641,7 @@ static config_var_t state_extra_var = { }; /** Configuration format for or_state_t. */ -static config_format_t state_format = { +static const config_format_t state_format = { sizeof(or_state_t), OR_STATE_MAGIC, STRUCT_OFFSET(or_state_t, _magic), @@ -628,6 +665,8 @@ static or_state_t *global_state = NULL; static config_line_t *global_cmdline_options = NULL; /** Contents of most recently read DirPortFrontPage file. */ static char *global_dirfrontpagecontents = NULL; +/** List of port_cfg_t for client-level (SOCKS, DNS, Trans, NATD) ports. */ +static smartlist_t *configured_client_ports = NULL; /** Return the contents of our frontpage string, or NULL if not configured. */ const char * @@ -638,7 +677,7 @@ get_dirportfrontpage(void) /** Allocate an empty configuration object of a given format type. */ static void * -config_alloc(config_format_t *fmt) +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; @@ -648,12 +687,19 @@ config_alloc(config_format_t *fmt) /** Return the currently configured options. */ or_options_t * -get_options(void) +get_options_mutable(void) { tor_assert(global_options); return global_options; } +/** Returns the currently configured options */ +const or_options_t * +get_options(void) +{ + return get_options_mutable(); +} + /** Change the current global options to contain <b>new_val</b> instead of * their current value; take action based on the new value; free the old value * as necessary. Returns 0 on success, -1 on failure. @@ -661,6 +707,9 @@ get_options(void) int set_options(or_options_t *new_val, char **msg) { + int i; + smartlist_t *elements; + config_line_t *line; or_options_t *old_options = global_options; global_options = new_val; /* Note that we pass the *old* options below, for comparison. It @@ -675,7 +724,34 @@ set_options(or_options_t *new_val, char **msg) "Acting on config options left us in a broken state. Dying."); exit(1); } - + /* Issues a CONF_CHANGED event to notify controller of the change. If Tor is + * just starting up then the old_options will be undefined. */ + if (old_options) { + elements = smartlist_create(); + for (i=0; options_format.vars[i].name; ++i) { + const config_var_t *var = &options_format.vars[i]; + const char *var_name = var->name; + if (var->type == CONFIG_TYPE_LINELIST_S || + 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 (line) { + for (; line; line = line->next) { + smartlist_add(elements, line->key); + smartlist_add(elements, line->value); + } + } else { + smartlist_add(elements, (char*)options_format.vars[i].name); + smartlist_add(elements, NULL); + } + } + } + control_event_conf_changed(elements); + smartlist_free(elements); + } config_free(&options_format, old_options); return 0; @@ -711,6 +787,11 @@ or_options_free(or_options_t *options) return; routerset_free(options->_ExcludeExitNodesUnion); + if (options->NodeFamilySets) { + SMARTLIST_FOREACH(options->NodeFamilySets, routerset_t *, + rs, routerset_free(rs)); + smartlist_free(options->NodeFamilySets); + } config_free(&options_format, options); } @@ -728,6 +809,13 @@ config_free_all(void) config_free_lines(global_cmdline_options); global_cmdline_options = NULL; + if (configured_client_ports) { + SMARTLIST_FOREACH(configured_client_ports, + port_cfg_t *, p, tor_free(p)); + smartlist_free(configured_client_ports); + configured_client_ports = NULL; + } + tor_free(torrc_fname); tor_free(_version); tor_free(global_dirfrontpagecontents); @@ -793,7 +881,7 @@ escaped_safe_str(const char *address) /** Add the default directory authorities directly into the trusted dir list, * but only add them insofar as they share bits with <b>type</b>. */ static void -add_default_trusted_dir_authorities(authority_type_t type) +add_default_trusted_dir_authorities(dirinfo_type_t type) { int i; const char *dirservers[] = { @@ -867,16 +955,16 @@ 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_AUTHORITY, 1)<0) + if (parse_dir_server_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_AUTHORITY, 1)<0) + if (parse_dir_server_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_AUTHORITY, 1)<0) + if (parse_dir_server_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_AUTHORITY, 1)<0) + if (parse_dir_server_line(cl->value, NO_DIRINFO, 1)<0) return -1; return 0; } @@ -885,8 +973,8 @@ validate_dir_authorities(or_options_t *options, or_options_t *old_options) * as appropriate. */ static int -consider_adding_dir_authorities(or_options_t *options, - or_options_t *old_options) +consider_adding_dir_authorities(const or_options_t *options, + const or_options_t *old_options) { config_line_t *cl; int need_to_update = @@ -907,27 +995,28 @@ consider_adding_dir_authorities(or_options_t *options, if (!options->DirServers) { /* then we may want some of the defaults */ - authority_type_t type = NO_AUTHORITY; + dirinfo_type_t type = NO_DIRINFO; if (!options->AlternateBridgeAuthority) - type |= BRIDGE_AUTHORITY; + type |= BRIDGE_DIRINFO; if (!options->AlternateDirAuthority) - type |= V1_AUTHORITY | V2_AUTHORITY | V3_AUTHORITY; + type |= V1_DIRINFO | V2_DIRINFO | V3_DIRINFO | EXTRAINFO_DIRINFO | + MICRODESC_DIRINFO; if (!options->AlternateHSAuthority) - type |= HIDSERV_AUTHORITY; + type |= HIDSERV_DIRINFO; add_default_trusted_dir_authorities(type); } for (cl = options->DirServers; cl; cl = cl->next) - if (parse_dir_server_line(cl->value, NO_AUTHORITY, 0)<0) + if (parse_dir_server_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_AUTHORITY, 0)<0) + if (parse_dir_server_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_AUTHORITY, 0)<0) + if (parse_dir_server_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_AUTHORITY, 0)<0) + if (parse_dir_server_line(cl->value, NO_DIRINFO, 0)<0) return -1; return 0; } @@ -939,12 +1028,12 @@ consider_adding_dir_authorities(or_options_t *options, * Return 0 if all goes well, return -1 if things went badly. */ static int -options_act_reversible(or_options_t *old_options, char **msg) +options_act_reversible(const or_options_t *old_options, char **msg) { smartlist_t *new_listeners = smartlist_create(); smartlist_t *replaced_listeners = smartlist_create(); static int libevent_initialized = 0; - or_options_t *options = get_options(); + or_options_t *options = get_options_mutable(); int running_tor = options->command == CMD_RUN_TOR; int set_conn_limit = 0; int r = -1; @@ -973,6 +1062,7 @@ options_act_reversible(or_options_t *old_options, char **msg) #endif if (running_tor) { + int n_client_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) { @@ -984,10 +1074,20 @@ options_act_reversible(or_options_t *old_options, char **msg) /* Set up libevent. (We need to do this before we can register the * listeners as listeners.) */ if (running_tor && !libevent_initialized) { - init_libevent(); + init_libevent(options); libevent_initialized = 1; } + /* Adjust the client port configuration so we can launch listeners. */ + if (parse_client_ports(options, 0, msg, &n_client_ports)) { + if (!*msg) + *msg = tor_strdup("Unexpected problem parsing client port config"); + goto rollback; + } + + /* Set the hibernation state appropriately.*/ + consider_hibernation(time(NULL)); + /* Launch the listeners. (We do this before we setuid, so we can bind to * ports under 1024.) We don't want to rebind if we're hibernating. */ if (!we_are_hibernating()) { @@ -1040,7 +1140,7 @@ options_act_reversible(or_options_t *old_options, char **msg) /* Write control ports to disk as appropriate */ control_ports_write_to_file(); - if (directory_caches_v2_dir_info(options)) { + if (directory_caches_v2_dir_info(options)) { size_t len = strlen(options->DataDirectory)+32; char *fn = tor_malloc(len); tor_snprintf(fn, len, "%s"PATH_SEPARATOR"cached-status", @@ -1116,7 +1216,7 @@ options_act_reversible(or_options_t *old_options, char **msg) /** If we need to have a GEOIP ip-to-country map to run with our configured * options, return 1 and set *<b>reason_out</b> to a description of why. */ int -options_need_geoip_info(or_options_t *options, const char **reason_out) +options_need_geoip_info(const or_options_t *options, const char **reason_out) { int bridge_usage = options->BridgeRelay && options->BridgeRecordUsageByCountry; @@ -1141,7 +1241,7 @@ options_need_geoip_info(or_options_t *options, const char **reason_out) /** Return the bandwidthrate that we are going to report to the authorities * based on the config options. */ uint32_t -get_effective_bwrate(or_options_t *options) +get_effective_bwrate(const or_options_t *options) { uint64_t bw = options->BandwidthRate; if (bw > options->MaxAdvertisedBandwidth) @@ -1155,7 +1255,7 @@ get_effective_bwrate(or_options_t *options) /** Return the bandwidthburst that we are going to report to the authorities * based on the config options. */ uint32_t -get_effective_bwburst(or_options_t *options) +get_effective_bwburst(const or_options_t *options) { uint64_t bw = options->BandwidthBurst; if (options->RelayBandwidthBurst > 0 && bw > options->RelayBandwidthBurst) @@ -1174,10 +1274,10 @@ get_effective_bwburst(or_options_t *options) * here yet. Some is still in do_hup() and other places. */ static int -options_act(or_options_t *old_options) +options_act(const or_options_t *old_options) { config_line_t *cl; - or_options_t *options = get_options(); + or_options_t *options = get_options_mutable(); int running_tor = options->command == CMD_RUN_TOR; char *msg; const int transition_affects_workers = @@ -1222,6 +1322,32 @@ options_act(or_options_t *old_options) rep_hist_load_mtbf_data(time(NULL)); } + mark_transport_list(); + pt_prepare_proxy_list_for_config_read(); + if (options->ClientTransportPlugin) { + for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { + if (parse_client_transport_line(cl->value, 0)<0) { + log_warn(LD_BUG, + "Previously validated ClientTransportPlugin line " + "could not be added!"); + return -1; + } + } + } + + if (options->ServerTransportPlugin) { + for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { + if (parse_server_transport_line(cl->value, 0)<0) { + log_warn(LD_BUG, + "Previously validated ServerTransportPlugin line " + "could not be added!"); + return -1; + } + } + } + sweep_transport_list(); + sweep_proxy_list(); + /* Bail out at this point if we're not going to be a client or server: * we want to not fork, and to log stuff to stderr. */ if (!running_tor) @@ -1281,17 +1407,16 @@ options_act(or_options_t *old_options) if (accounting_is_enabled(options)) configure_accounting(time(NULL)); - /* parse RefuseUnknownExits tristate */ - if (!strcmp(options->RefuseUnknownExits, "0")) - options->RefuseUnknownExits_ = 0; - else if (!strcmp(options->RefuseUnknownExits, "1")) - options->RefuseUnknownExits_ = 1; - else if (!strcmp(options->RefuseUnknownExits, "auto")) - options->RefuseUnknownExits_ = -1; - else { - /* Should have caught this in options_validate */ - return -1; - } +#ifdef USE_BUFFEREVENTS + /* If we're using the bufferevents implementation and our rate limits + * changed, we need to tell the rate-limiting system about it. */ + if (!old_options || + old_options->BandwidthRate != options->BandwidthRate || + old_options->BandwidthBurst != options->BandwidthBurst || + old_options->RelayBandwidthRate != options->RelayBandwidthRate || + old_options->RelayBandwidthBurst != options->RelayBandwidthBurst) + connection_bucket_init(); +#endif /* Change the cell EWMA settings */ cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus()); @@ -1411,7 +1536,9 @@ options_act(or_options_t *old_options) } if (options->CellStatistics || options->DirReqStatistics || - options->EntryStatistics || options->ExitPortStatistics) { + options->EntryStatistics || options->ExitPortStatistics || + options->ConnDirectionStatistics || + options->BridgeAuthoritativeDir) { time_t now = time(NULL); int print_notice = 0; if ((!old_options || !old_options->CellStatistics) && @@ -1426,10 +1553,12 @@ options_act(or_options_t *old_options) print_notice = 1; } else { options->DirReqStatistics = 0; - log_notice(LD_CONFIG, "Configured to measure directory request " - "statistics, but no GeoIP database found! " - "Please specify a GeoIP database using the " - "GeoIPFile option!"); + /* Don't warn Tor clients, they don't use statistics */ + if (options->ORPort) + log_notice(LD_CONFIG, "Configured to measure directory request " + "statistics, but no GeoIP database found. " + "Please specify a GeoIP database using the " + "GeoIPFile option."); } } if ((!old_options || !old_options->EntryStatistics) && @@ -1440,9 +1569,9 @@ options_act(or_options_t *old_options) } else { options->EntryStatistics = 0; log_notice(LD_CONFIG, "Configured to measure entry node " - "statistics, but no GeoIP database found! " + "statistics, but no GeoIP database found. " "Please specify a GeoIP database using the " - "GeoIPFile option!"); + "GeoIPFile option."); } } if ((!old_options || !old_options->ExitPortStatistics) && @@ -1450,6 +1579,15 @@ options_act(or_options_t *old_options) rep_hist_exit_stats_init(now); print_notice = 1; } + if ((!old_options || !old_options->ConnDirectionStatistics) && + options->ConnDirectionStatistics) { + rep_hist_conn_stats_init(now); + } + if ((!old_options || !old_options->BridgeAuthoritativeDir) && + options->BridgeAuthoritativeDir) { + rep_hist_desc_stats_init(now); + print_notice = 1; + } if (print_notice) log_notice(LD_CONFIG, "Configured to measure statistics. Look for " "the *-stats files that will first be written to the " @@ -1468,6 +1606,12 @@ options_act(or_options_t *old_options) if (old_options && old_options->ExitPortStatistics && !options->ExitPortStatistics) rep_hist_exit_stats_term(); + if (old_options && old_options->ConnDirectionStatistics && + !options->ConnDirectionStatistics) + rep_hist_conn_stats_term(); + if (old_options && old_options->BridgeAuthoritativeDir && + !options->BridgeAuthoritativeDir) + rep_hist_desc_stats_term(); /* Check if we need to parse and add the EntryNodes config option. */ if (options->EntryNodes && @@ -1525,7 +1669,7 @@ options_act(or_options_t *old_options) * 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(config_format_t *fmt, const char *option, int command_line, +expand_abbrev(const config_format_t *fmt, const char *option, int command_line, int warn_obsolete) { int i; @@ -1685,12 +1829,9 @@ config_free_lines(config_line_t *front) } } -/** If <b>key</b> is a configuration option, return the corresponding - * config_var_t. Otherwise, if <b>key</b> is a non-standard abbreviation, - * warn, and return the corresponding config_var_t. Otherwise return NULL. - */ +/** As config_find_option, but return a non-const pointer. */ static config_var_t * -config_find_option(config_format_t *fmt, const char *key) +config_find_option_mutable(config_format_t *fmt, const char *key) { int i; size_t keylen = strlen(key); @@ -1715,9 +1856,20 @@ config_find_option(config_format_t *fmt, const char *key) 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(config_format_t *fmt) +config_count_options(const config_format_t *fmt) { int i; for (i=0; fmt->vars[i].name; ++i) @@ -1735,11 +1887,11 @@ config_count_options(config_format_t *fmt) * Called from config_assign_line() and option_reset(). */ static int -config_assign_value(config_format_t *fmt, or_options_t *options, +config_assign_value(const config_format_t *fmt, or_options_t *options, config_line_t *c, char **msg) { int i, ok; - config_var_t *var; + const config_var_t *var; void *lvalue; CHECK(fmt, options); @@ -1782,6 +1934,18 @@ config_assign_value(config_format_t *fmt, or_options_t *options, 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) { @@ -1805,6 +1969,20 @@ config_assign_value(config_format_t *fmt, or_options_t *options, *(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); @@ -1875,11 +2053,11 @@ config_assign_value(config_format_t *fmt, or_options_t *options, * Called from config_assign(). */ static int -config_assign_line(config_format_t *fmt, or_options_t *options, +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) { - config_var_t *var; + const config_var_t *var; CHECK(fmt, options); @@ -1940,10 +2118,10 @@ config_assign_line(config_format_t *fmt, or_options_t *options, /** Restore the option named <b>key</b> in options to its default value. * Called from config_assign(). */ static void -config_reset_line(config_format_t *fmt, or_options_t *options, +config_reset_line(const config_format_t *fmt, or_options_t *options, const char *key, int use_defaults) { - config_var_t *var; + const config_var_t *var; CHECK(fmt, options); @@ -1958,7 +2136,7 @@ config_reset_line(config_format_t *fmt, or_options_t *options, int option_is_recognized(const char *key) { - config_var_t *var = config_find_option(&options_format, key); + const config_var_t *var = config_find_option(&options_format, key); return (var != NULL); } @@ -1967,14 +2145,14 @@ option_is_recognized(const char *key) const char * option_get_canonical_name(const char *key) { - config_var_t *var = config_find_option(&options_format, key); + const config_var_t *var = config_find_option(&options_format, key); return var ? var->name : NULL; } /** Return a canonical list of the options assigned for key. */ config_line_t * -option_get_assignment(or_options_t *options, const char *key) +option_get_assignment(const or_options_t *options, const char *key) { return get_assigned_option(&options_format, options, key, 1); } @@ -2027,10 +2205,10 @@ config_lines_dup(const config_line_t *inp) * 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(config_format_t *fmt, void *options, +get_assigned_option(const config_format_t *fmt, const void *options, const char *key, int escape_val) { - config_var_t *var; + const config_var_t *var; const void *value; config_line_t *result; tor_assert(options && key); @@ -2076,6 +2254,7 @@ get_assigned_option(config_format_t *fmt, void *options, } /* fall through */ case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: case CONFIG_TYPE_UINT: /* This means every or_options_t uint or bool element * needs to be an int. Not, say, a uint16_t or char. */ @@ -2091,6 +2270,14 @@ get_assigned_option(config_format_t *fmt, void *options, 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. */ @@ -2203,7 +2390,7 @@ options_trial_assign() calls config_assign(1, 1) returns. */ static int -config_assign(config_format_t *fmt, void *options, config_line_t *list, +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; @@ -2265,7 +2452,7 @@ options_trial_assign(config_line_t *list, int use_defaults, return r; } - if (options_validate(get_options(), trial_options, 1, msg) < 0) { + if (options_validate(get_options_mutable(), trial_options, 1, msg) < 0) { config_free(&options_format, trial_options); return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ } @@ -2287,7 +2474,8 @@ options_trial_assign(config_line_t *list, int use_defaults, /** 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(config_format_t *fmt, or_options_t *options, config_var_t *var) +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 */ @@ -2303,11 +2491,15 @@ option_clear(config_format_t *fmt, or_options_t *options, config_var_t *var) *(time_t*)lvalue = 0; break; case CONFIG_TYPE_INTERVAL: + case CONFIG_TYPE_MSEC_INTERVAL: case CONFIG_TYPE_UINT: 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; @@ -2341,8 +2533,8 @@ option_clear(config_format_t *fmt, or_options_t *options, config_var_t *var) * <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(config_format_t *fmt, or_options_t *options, - config_var_t *var, int use_defaults) +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; @@ -2382,7 +2574,7 @@ list_torrc_options(void) int i; smartlist_t *lines = smartlist_create(); for (i = 0; _option_vars[i].name; ++i) { - config_var_t *var = &_option_vars[i]; + const config_var_t *var = &_option_vars[i]; if (var->type == CONFIG_TYPE_OBSOLETE || var->type == CONFIG_TYPE_LINELIST_V) continue; @@ -2401,7 +2593,7 @@ static uint32_t last_resolved_addr = 0; * public IP address. */ int -resolve_my_address(int warn_severity, or_options_t *options, +resolve_my_address(int warn_severity, const or_options_t *options, uint32_t *addr_out, char **hostname_out) { struct in_addr in; @@ -2410,7 +2602,7 @@ resolve_my_address(int warn_severity, or_options_t *options, int explicit_ip=1; int explicit_hostname=1; int from_interface=0; - char tmpbuf[INET_NTOA_BUF_LEN]; + char *addr_string = NULL; const char *address = options->Address; int notice_severity = warn_severity <= LOG_NOTICE ? LOG_NOTICE : warn_severity; @@ -2452,48 +2644,43 @@ resolve_my_address(int warn_severity, or_options_t *options, return -1; } from_interface = 1; - in.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + addr = interface_ip; log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for " - "local interface. Using that.", tmpbuf); + "local interface. Using that.", fmt_addr32(addr)); strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname)); } else { /* resolved hostname into addr */ - in.s_addr = htonl(addr); - if (!explicit_hostname && - is_internal_IP(ntohl(in.s_addr), 0)) { + is_internal_IP(addr, 0)) { uint32_t interface_ip; - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); log_fn(notice_severity, LD_CONFIG, "Guessed local hostname '%s' " - "resolves to a private IP address (%s). Trying something " - "else.", hostname, tmpbuf); + "resolves to a private IP address (%s). Trying something " + "else.", hostname, fmt_addr32(addr)); if (get_interface_address(warn_severity, &interface_ip)) { log_fn(warn_severity, LD_CONFIG, "Could not get local interface IP address. Too bad."); } else if (is_internal_IP(interface_ip, 0)) { - struct in_addr in2; - in2.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in2,tmpbuf,sizeof(tmpbuf)); log_fn(notice_severity, LD_CONFIG, "Interface IP address '%s' is a private address too. " - "Ignoring.", tmpbuf); + "Ignoring.", fmt_addr32(interface_ip)); } else { from_interface = 1; - in.s_addr = htonl(interface_ip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + addr = interface_ip; log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for local interface." - " Using that.", tmpbuf); + " Using that.", fmt_addr32(addr)); strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname)); } } } + } else { + addr = ntohl(in.s_addr); /* set addr so that addr_string is not + * illformed */ } - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (is_internal_IP(ntohl(in.s_addr), 0)) { + 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 @@ -2501,7 +2688,8 @@ resolve_my_address(int warn_severity, or_options_t *options, 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, tmpbuf); + "IP addresses.", hostname, addr_string); + tor_free(addr_string); return -1; } if (!explicit_ip) { @@ -2509,19 +2697,20 @@ resolve_my_address(int warn_severity, or_options_t *options, * 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 " - "the IP address you want to use.", hostname, tmpbuf); + "the IP address you want to use.", hostname, addr_string); + tor_free(addr_string); return -1; } } - log_debug(LD_CONFIG, "Resolved Address to '%s'.", tmpbuf); - *addr_out = ntohl(in.s_addr); + log_debug(LD_CONFIG, "Resolved Address to '%s'.", fmt_addr32(addr)); + *addr_out = addr; if (last_resolved_addr && last_resolved_addr != *addr_out) { /* Leave this as a notice, regardless of the requested severity, * at least until dynamic IP address support becomes bulletproof. */ log_notice(LD_NET, "Your IP address seems to have changed to %s. Updating.", - tmpbuf); + addr_string); ip_address_changed(0); } if (last_resolved_addr != *addr_out) { @@ -2540,11 +2729,12 @@ resolve_my_address(int warn_severity, or_options_t *options, } control_event_server_status(LOG_NOTICE, "EXTERNAL_ADDRESS ADDRESS=%s METHOD=%s %s%s", - tmpbuf, method, h?"HOSTNAME=":"", h); + addr_string, method, h?"HOSTNAME=":"", h); } last_resolved_addr = *addr_out; if (hostname_out) *hostname_out = tor_strdup(hostname); + tor_free(addr_string); return 0; } @@ -2579,7 +2769,7 @@ is_local_addr(const tor_addr_t *addr) /** Release storage held by <b>options</b>. */ static void -config_free(config_format_t *fmt, void *options) +config_free(const config_format_t *fmt, void *options) { int i; @@ -2618,8 +2808,9 @@ config_lines_eq(config_line_t *a, config_line_t *b) * and <b>o2</b>. Must not be called for LINELIST_S or OBSOLETE options. */ static int -option_is_same(config_format_t *fmt, - or_options_t *o1, or_options_t *o2, const char *name) +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; @@ -2636,7 +2827,7 @@ option_is_same(config_format_t *fmt, /** Copy storage held by <b>old</b> into a new or_options_t and return it. */ static or_options_t * -options_dup(config_format_t *fmt, or_options_t *old) +options_dup(const config_format_t *fmt, const or_options_t *old) { or_options_t *newopts; int i; @@ -2700,7 +2891,7 @@ is_listening_on_low_port(int port_option, return (port_option < 1024); for (l = listen_options; l; l = l->next) { - parse_addr_port(LOG_WARN, l->value, NULL, NULL, &p); + addr_port_lookup(LOG_WARN, l->value, NULL, NULL, &p); if (p<1024) { return 1; } @@ -2712,10 +2903,10 @@ is_listening_on_low_port(int port_option, /** Set all vars in the configuration object <b>options</b> to their default * values. */ static void -config_init(config_format_t *fmt, void *options) +config_init(const config_format_t *fmt, void *options) { int i; - config_var_t *var; + const config_var_t *var; CHECK(fmt, options); for (i=0; fmt->vars[i].name; ++i) { @@ -2731,7 +2922,7 @@ config_init(config_format_t *fmt, void *options) * Else, if comment_defaults, write default values as comments. */ static char * -config_dump(config_format_t *fmt, void *options, int minimal, +config_dump(const config_format_t *fmt, const void *options, int minimal, int comment_defaults) { smartlist_t *elements; @@ -2799,7 +2990,7 @@ config_dump(config_format_t *fmt, void *options, int minimal, * include options that are the same as Tor's defaults. */ char * -options_dump(or_options_t *options, int minimal) +options_dump(const or_options_t *options, int minimal) { return config_dump(&options_format, options, minimal, 0); } @@ -2858,24 +3049,24 @@ static int compute_publishserverdescriptor(or_options_t *options) { smartlist_t *list = options->PublishServerDescriptor; - authority_type_t *auth = &options->_PublishServerDescriptor; - *auth = NO_AUTHORITY; + dirinfo_type_t *auth = &options->_PublishServerDescriptor; + *auth = NO_DIRINFO; if (!list) /* empty list, answer is none */ return 0; SMARTLIST_FOREACH(list, const char *, string, { if (!strcasecmp(string, "v1")) - *auth |= V1_AUTHORITY; + *auth |= V1_DIRINFO; else if (!strcmp(string, "1")) if (options->BridgeRelay) - *auth |= BRIDGE_AUTHORITY; + *auth |= BRIDGE_DIRINFO; else - *auth |= V2_AUTHORITY | V3_AUTHORITY; + *auth |= V2_DIRINFO | V3_DIRINFO; else if (!strcasecmp(string, "v2")) - *auth |= V2_AUTHORITY; + *auth |= V2_DIRINFO; else if (!strcasecmp(string, "v3")) - *auth |= V3_AUTHORITY; + *auth |= V3_DIRINFO; else if (!strcasecmp(string, "bridge")) - *auth |= BRIDGE_AUTHORITY; + *auth |= BRIDGE_DIRINFO; else if (!strcasecmp(string, "hidserv")) log_warn(LD_CONFIG, "PublishServerDescriptor hidserv is invalid. See " @@ -2903,6 +3094,10 @@ compute_publishserverdescriptor(or_options_t *options) * will generate too many circuits and potentially overload the network. */ #define MIN_CIRCUIT_STREAM_TIMEOUT 10 +/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might + * expose more information than we're comfortable with. */ +#define MIN_HEARTBEAT_PERIOD (30*60) + /** Return 0 if every setting in <b>options</b> is reasonable, and a * permissible transition from <b>old_options</b>. Else return -1. * Should have no side effects, except for normalizing the contents of @@ -2922,6 +3117,7 @@ options_validate(or_options_t *old_options, or_options_t *options, int i; config_line_t *cl; const char *uname = get_uname(); + int n_client_ports=0; #define REJECT(arg) \ STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END #define COMPLAIN(arg) STMT_BEGIN log(LOG_WARN, LD_CONFIG, arg); STMT_END @@ -2945,57 +3141,8 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->DirPort == 0 && options->DirListenAddress != NULL) REJECT("DirPort must be defined if DirListenAddress is defined."); - if (options->DNSPort == 0 && options->DNSListenAddress != NULL) - REJECT("DNSPort must be defined if DNSListenAddress is defined."); - - if (options->ControlPort == 0 && options->ControlListenAddress != NULL) - REJECT("ControlPort must be defined if ControlListenAddress is defined."); - - if (options->TransPort == 0 && options->TransListenAddress != NULL) - REJECT("TransPort must be defined if TransListenAddress is defined."); - - if (options->NATDPort == 0 && options->NATDListenAddress != NULL) - REJECT("NATDPort must be defined if NATDListenAddress is defined."); - - /* Don't gripe about SocksPort 0 with SocksListenAddress set; a standard - * configuration does this. */ - - for (i = 0; i < 3; ++i) { - int is_socks = i==0; - int is_trans = i==1; - config_line_t *line, *opt, *old; - const char *tp; - if (is_socks) { - opt = options->SocksListenAddress; - old = old_options ? old_options->SocksListenAddress : NULL; - tp = "SOCKS proxy"; - } else if (is_trans) { - opt = options->TransListenAddress; - old = old_options ? old_options->TransListenAddress : NULL; - tp = "transparent proxy"; - } else { - opt = options->NATDListenAddress; - old = old_options ? old_options->NATDListenAddress : NULL; - tp = "natd proxy"; - } - - for (line = opt; line; line = line->next) { - char *address = NULL; - uint16_t port; - uint32_t addr; - if (parse_addr_port(LOG_WARN, line->value, &address, &addr, &port)<0) - continue; /* We'll warn about this later. */ - if (!is_internal_IP(addr, 1) && - (!old_options || !config_lines_eq(old, opt))) { - log_warn(LD_CONFIG, - "You specified a public address '%s' for a %s. Other " - "people on the Internet might find your computer and use it as " - "an open %s. Please don't allow this unless you have " - "a good reason.", address, tp, tp); - } - tor_free(address); - } - } + if (parse_client_ports(options, 1, msg, &n_client_ports) < 0) + return -1; if (validate_data_directory(options)<0) REJECT("Invalid DataDirectory"); @@ -3019,8 +3166,12 @@ options_validate(or_options_t *old_options, or_options_t *options, "misconfigured or something else goes wrong."); /* Special case on first boot if no Log options are given. */ - if (!options->Logs && !options->RunAsDaemon && !from_setconf) - config_line_append(&options->Logs, "Log", "notice stdout"); + if (!options->Logs && !options->RunAsDaemon && !from_setconf) { + if (quiet_level == 0) + config_line_append(&options->Logs, "Log", "notice stdout"); + else if (quiet_level == 1) + config_line_append(&options->Logs, "Log", "warn stdout"); + } if (options_init_logs(options, 1)<0) /* Validate the log(s) */ REJECT("Failed to validate Log options. See logs for details."); @@ -3032,20 +3183,12 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to resolve/guess local address. See logs for details."); } - if (strcmp(options->RefuseUnknownExits, "0") && - strcmp(options->RefuseUnknownExits, "1") && - strcmp(options->RefuseUnknownExits, "auto")) { - REJECT("RefuseUnknownExits must be 0, 1, or auto"); - } - #ifndef MS_WINDOWS if (options->RunAsDaemon && torrc_fname && path_is_relative(torrc_fname)) REJECT("Can't use a relative path to torrc when RunAsDaemon is set."); #endif - if (options->SocksPort == 0 && options->TransPort == 0 && - options->NATDPort == 0 && options->ORPort == 0 && - options->DNSPort == 0 && !options->RendConfigLines) + if (n_client_ports == 0 && options->ORPort == 0 && !options->RendConfigLines) log(LOG_WARN, LD_CONFIG, "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all " "undefined, and there aren't any hidden services configured. " @@ -3056,6 +3199,11 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("TransPort and TransListenAddress are disabled in this build."); #endif + if (options->TokenBucketRefillInterval <= 0 + || options->TokenBucketRefillInterval > 1000) { + REJECT("TokenBucketRefillInterval must be between 1 and 1000 inclusive."); + } + if (options->AccountingMax && (is_listening_on_low_port(options->ORPort, options->ORListenAddress) || is_listening_on_low_port(options->DirPort, options->DirListenAddress))) @@ -3075,17 +3223,24 @@ options_validate(or_options_t *old_options, or_options_t *options, routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes); } + if (options->NodeFamilies) { + options->NodeFamilySets = smartlist_create(); + for (cl = options->NodeFamilies; cl; cl = cl->next) { + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, cl->value, cl->key) == 0) { + smartlist_add(options->NodeFamilySets, rs); + } else { + routerset_free(rs); + } + } + } + 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 " "features to be broken in unpredictable ways."); } - if (options->EntryNodes && !routerset_is_list(options->EntryNodes)) { - /* XXXX fix this; see entry_guards_prepend_from_config(). */ - REJECT("IPs or countries are not yet supported in EntryNodes."); - } - if (options->AuthoritativeDir) { if (!options->ContactInfo && !options->TestingTorNetwork) REJECT("Authoritative directory servers must set ContactInfo"); @@ -3147,6 +3302,15 @@ options_validate(or_options_t *old_options, or_options_t *options, return -1; } + if (options->MaxClientCircuitsPending <= 0 || + options->MaxClientCircuitsPending > MAX_MAX_CLIENT_CIRCUITS_PENDING) { + tor_asprintf(msg, + "MaxClientCircuitsPending must be between 1 and %d, but " + "was set to %d", MAX_MAX_CLIENT_CIRCUITS_PENDING, + options->MaxClientCircuitsPending); + return -1; + } + if (validate_ports_csv(options->FirewallPorts, "FirewallPorts", msg) < 0) return -1; @@ -3295,9 +3459,9 @@ options_validate(or_options_t *old_options, or_options_t *options, } if ((options->BridgeRelay - || options->_PublishServerDescriptor & BRIDGE_AUTHORITY) + || options->_PublishServerDescriptor & BRIDGE_DIRINFO) && (options->_PublishServerDescriptor - & (V1_AUTHORITY|V2_AUTHORITY|V3_AUTHORITY))) { + & (V1_DIRINFO|V2_DIRINFO|V3_DIRINFO))) { REJECT("Bridges are not supposed to publish router descriptors to the " "directory authorities. Please correct your " "PublishServerDescriptor line."); @@ -3340,6 +3504,13 @@ options_validate(or_options_t *old_options, or_options_t *options, options->CircuitStreamTimeout = MIN_CIRCUIT_STREAM_TIMEOUT; } + if (options->HeartbeatPeriod && + options->HeartbeatPeriod < MIN_HEARTBEAT_PERIOD) { + log_warn(LD_CONFIG, "HeartbeatPeriod option is too short; " + "raising to %d seconds.", MIN_HEARTBEAT_PERIOD); + options->HeartbeatPeriod = MIN_HEARTBEAT_PERIOD; + } + if (options->KeepalivePeriod < 1) REJECT("KeepalivePeriod option must be positive."); @@ -3416,7 +3587,7 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to parse accounting options. See logs for details."); if (options->HTTPProxy) { /* parse it now */ - if (tor_addr_port_parse(options->HTTPProxy, + if (tor_addr_port_lookup(options->HTTPProxy, &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0) REJECT("HTTPProxy failed to parse or resolve. Please fix."); if (options->HTTPProxyPort == 0) { /* give it a default */ @@ -3430,7 +3601,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->HTTPSProxy) { /* parse it now */ - if (tor_addr_port_parse(options->HTTPSProxy, + if (tor_addr_port_lookup(options->HTTPSProxy, &options->HTTPSProxyAddr, &options->HTTPSProxyPort) <0) REJECT("HTTPSProxy failed to parse or resolve. Please fix."); if (options->HTTPSProxyPort == 0) { /* give it a default */ @@ -3444,7 +3615,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->Socks4Proxy) { /* parse it now */ - if (tor_addr_port_parse(options->Socks4Proxy, + if (tor_addr_port_lookup(options->Socks4Proxy, &options->Socks4ProxyAddr, &options->Socks4ProxyPort) <0) REJECT("Socks4Proxy failed to parse or resolve. Please fix."); @@ -3454,7 +3625,7 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->Socks5Proxy) { /* parse it now */ - if (tor_addr_port_parse(options->Socks5Proxy, + if (tor_addr_port_lookup(options->Socks5Proxy, &options->Socks5ProxyAddr, &options->Socks5ProxyPort) <0) REJECT("Socks5Proxy failed to parse or resolve. Please fix."); @@ -3463,8 +3634,11 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - if (options->Socks4Proxy && options->Socks5Proxy) - REJECT("You cannot specify both Socks4Proxy and SOCKS5Proxy"); + /* Check if more than one proxy type has been enabled. */ + if (!!options->Socks4Proxy + !!options->Socks5Proxy + + !!options->HTTPSProxy + !!options->ClientTransportPlugin > 1) + REJECT("You have configured more than one proxy type. " + "(Socks4Proxy|Socks5Proxy|HTTPSProxy|ClientTransportPlugin)"); if (options->Socks5ProxyUsername) { size_t len; @@ -3567,8 +3741,12 @@ options_validate(or_options_t *old_options, or_options_t *options, if (check_nickname_list(options->MyFamily, "MyFamily", msg)) return -1; for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (check_nickname_list(cl->value, "NodeFamily", msg)) + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, cl->value, cl->key)) { + routerset_free(rs); return -1; + } + routerset_free(rs); } if (validate_addr_policies(options, msg) < 0) @@ -3581,11 +3759,20 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("If you set UseBridges, you must specify at least one bridge."); if (options->UseBridges && !options->TunnelDirConns) REJECT("If you set UseBridges, you must set TunnelDirConns."); - if (options->Bridges) { - for (cl = options->Bridges; cl; cl = cl->next) { - if (parse_bridge_line(cl->value, 1)<0) - REJECT("Bridge line did not parse. See logs for details."); - } + + for (cl = options->Bridges; cl; cl = cl->next) { + if (parse_bridge_line(cl->value, 1)<0) + REJECT("Bridge line did not parse. See logs for details."); + } + + for (cl = options->ClientTransportPlugin; cl; cl = cl->next) { + if (parse_client_transport_line(cl->value, 1)<0) + REJECT("Transport line did not parse. See logs for details."); + } + + for (cl = options->ServerTransportPlugin; cl; cl = cl->next) { + if (parse_server_transport_line(cl->value, 1)<0) + REJECT("Server transport line did not parse. See logs for details."); } if (options->ConstrainedSockets) { @@ -3768,17 +3955,13 @@ options_validate(or_options_t *old_options, or_options_t *options, static int opt_streq(const char *s1, const char *s2) { - if (!s1 && !s2) - return 1; - else if (s1 && s2 && !strcmp(s1,s2)) - return 1; - else - return 0; + return 0 == strcmp_opt(s1, s2); } /** Check if any of the previous options have changed but aren't allowed to. */ static int -options_transition_allowed(or_options_t *old, or_options_t *new_val, +options_transition_allowed(const or_options_t *old, + const or_options_t *new_val, char **msg) { if (!old) @@ -3828,14 +4011,26 @@ options_transition_allowed(or_options_t *old, or_options_t *new_val, return -1; } + if (old->TokenBucketRefillInterval != new_val->TokenBucketRefillInterval) { + *msg = tor_strdup("While Tor is running, changing TokenBucketRefill" + "Interval is not allowed"); + return -1; + } + + if (old->DisableIOCP != new_val->DisableIOCP) { + *msg = tor_strdup("While Tor is running, changing DisableIOCP " + "is not allowed."); + return -1; + } + return 0; } /** Return 1 if any change from <b>old_options</b> to <b>new_options</b> * will require us to rotate the CPU and DNS workers; else return 0. */ static int -options_transition_affects_workers(or_options_t *old_options, - or_options_t *new_options) +options_transition_affects_workers(const or_options_t *old_options, + const or_options_t *new_options) { if (!opt_streq(old_options->DataDirectory, new_options->DataDirectory) || old_options->NumCPUs != new_options->NumCPUs || @@ -3858,8 +4053,8 @@ options_transition_affects_workers(or_options_t *old_options, /** Return 1 if any change from <b>old_options</b> to <b>new_options</b> * will require us to generate a new descriptor; else return 0. */ static int -options_transition_affects_descriptor(or_options_t *old_options, - or_options_t *new_options) +options_transition_affects_descriptor(const or_options_t *old_options, + const or_options_t *new_options) { /* XXX We can be smarter here. If your DirPort isn't being * published and you just turned it off, no need to republish. Etc. */ @@ -3880,7 +4075,8 @@ options_transition_affects_descriptor(or_options_t *old_options, !opt_streq(old_options->ContactInfo, new_options->ContactInfo) || !opt_streq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || - old_options->AccountingMax != new_options->AccountingMax) + old_options->AccountingMax != new_options->AccountingMax || + public_server_mode(old_options) != public_server_mode(new_options)) return 1; return 0; @@ -4063,6 +4259,8 @@ load_torrc_from_disk(int argc, char **argv) "Unable to open configuration file \"%s\".", fname); goto err; } + } else { + log(LOG_NOTICE, LD_CONFIG, "Read configuration file \"%s\".", fname); } return cf; @@ -4223,9 +4421,9 @@ options_init_from_string(const char *cf, /* Change defaults. */ int i; for (i = 0; testing_tor_network_defaults[i].name; ++i) { - config_var_t *new_var = &testing_tor_network_defaults[i]; + const config_var_t *new_var = &testing_tor_network_defaults[i]; config_var_t *old_var = - config_find_option(&options_format, new_var->name); + config_find_option_mutable(&options_format, new_var->name); tor_assert(new_var); tor_assert(old_var); old_var->initvalue = new_var->initvalue; @@ -4301,8 +4499,8 @@ get_torrc_fname(void) /** Adjust the address map based on the MapAddress elements in the * configuration <b>options</b> */ -static void -config_register_addressmaps(or_options_t *options) +void +config_register_addressmaps(const or_options_t *options) { smartlist_t *elts; config_line_t *opt; @@ -4351,6 +4549,35 @@ options_init_logs(or_options_t *options, int validate_only) options->RunAsDaemon; #endif + if (options->LogTimeGranularity <= 0) { + log_warn(LD_CONFIG, "Log time granularity '%d' has to be positive.", + options->LogTimeGranularity); + return -1; + } else if (1000 % options->LogTimeGranularity != 0 && + options->LogTimeGranularity % 1000 != 0) { + int granularity = options->LogTimeGranularity; + if (granularity < 40) { + do granularity++; + while (1000 % granularity != 0); + } else if (granularity < 1000) { + granularity = 1000 / granularity; + while (1000 % granularity != 0) + granularity--; + granularity = 1000 / granularity; + } else { + granularity = 1000 * ((granularity / 1000) + 1); + } + log_warn(LD_CONFIG, "Log time granularity '%d' has to be either a " + "divisor or a multiple of 1 second. Changing to " + "'%d'.", + options->LogTimeGranularity, granularity); + if (!validate_only) + set_log_time_granularity(granularity); + } else { + if (!validate_only) + set_log_time_granularity(options->LogTimeGranularity); + } + ok = 1; elts = smartlist_create(); @@ -4440,6 +4667,8 @@ parse_bridge_line(const char *line, int validate_only) smartlist_t *items = NULL; int r; char *addrport=NULL, *fingerprint=NULL; + char *transport_name=NULL; + char *field1=NULL; tor_addr_t addr; uint16_t port = 0; char digest[DIGEST_LEN]; @@ -4451,9 +4680,25 @@ parse_bridge_line(const char *line, int validate_only) log_warn(LD_CONFIG, "Too few arguments to Bridge line."); goto err; } - addrport = smartlist_get(items, 0); + + /* field1 is either a transport name or addrport */ + field1 = smartlist_get(items, 0); smartlist_del_keeporder(items, 0); - if (tor_addr_port_parse(addrport, &addr, &port)<0) { + + if (!(strstr(field1, ".") || strstr(field1, ":"))) { + /* new-style bridge line */ + transport_name = field1; + if (smartlist_len(items) < 1) { + log_warn(LD_CONFIG, "Too few items to Bridge line."); + goto err; + } + addrport = smartlist_get(items, 0); + smartlist_del_keeporder(items, 0); + } else { + addrport = field1; + } + + if (tor_addr_port_lookup(addrport, &addr, &port)<0) { log_warn(LD_CONFIG, "Error parsing Bridge address '%s'", addrport); goto err; } @@ -4477,26 +4722,282 @@ parse_bridge_line(const char *line, int validate_only) } if (!validate_only) { - log_debug(LD_DIR, "Bridge at %s:%d (%s)", fmt_addr(&addr), - (int)port, + log_debug(LD_DIR, "Bridge at %s:%d (transport: %s) (%s)", + fmt_addr(&addr), (int)port, + transport_name ? transport_name : "no transport", fingerprint ? fingerprint : "no key listed"); - bridge_add_from_config(&addr, port, fingerprint ? digest : NULL); + bridge_add_from_config(&addr, port, + fingerprint ? digest : NULL, transport_name); } r = 0; goto done; - err: + err: r = -1; - done: + done: SMARTLIST_FOREACH(items, char*, s, tor_free(s)); smartlist_free(items); tor_free(addrport); + tor_free(transport_name); tor_free(fingerprint); return r; } +/** Read the contents of a ClientTransportPlugin line from + * <b>line</b>. Return 0 if the line is well-formed, and -1 if it + * isn't. + * + * If <b>validate_only</b> is 0, and the line is well-formed: + * - If it's an external proxy line, add the transport described in the line to + * our internal transport list. + * - If it's a managed proxy line, launch the managed proxy. */ +static int +parse_client_transport_line(const char *line, int validate_only) +{ + smartlist_t *items = NULL; + int r; + char *field2=NULL; + + const char *transports=NULL; + smartlist_t *transport_list=NULL; + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + int socks_ver=PROXY_NONE; + + /* managed proxy options */ + int is_managed=0; + char **proxy_argv=NULL; + char **tmp=NULL; + int proxy_argc,i; + + int line_length; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + line_length = smartlist_len(items); + if (line_length < 3) { + log_warn(LD_CONFIG, "Too few arguments on ClientTransportPlugin line."); + goto err; + } + + /* Get the first line element, split it to commas into + transport_list (in case it's multiple transports) and validate + the transport names. */ + transports = smartlist_get(items, 0); + transport_list = smartlist_create(); + smartlist_split_string(transport_list, transports, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) { + if (!string_is_C_identifier(transport_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + transport_name); + goto err; + } + } SMARTLIST_FOREACH_END(transport_name); + + /* field2 is either a SOCKS version or "exec" */ + field2 = smartlist_get(items, 1); + + if (!strcmp(field2,"socks4")) { + socks_ver = PROXY_SOCKS4; + } else if (!strcmp(field2,"socks5")) { + socks_ver = PROXY_SOCKS5; + } else if (!strcmp(field2,"exec")) { + is_managed=1; + } else { + log_warn(LD_CONFIG, "Strange ClientTransportPlugin field '%s'.", + field2); + goto err; + } + + if (is_managed) { /* managed */ + if (!validate_only) { /* if we are not just validating, use the + rest of the line as the argv of the proxy + to be launched */ + proxy_argc = line_length-2; + tor_assert(proxy_argc > 0); + proxy_argv = tor_malloc_zero(sizeof(char*)*(proxy_argc+1)); + tmp = proxy_argv; + for (i=0;i<proxy_argc;i++) { /* store arguments */ + *tmp++ = smartlist_get(items, 2); + smartlist_del_keeporder(items, 2); + } + *tmp = NULL; /*terminated with NUL pointer, just like execve() likes it*/ + + /* kickstart the thing */ + pt_kickstart_client_proxy(transport_list, proxy_argv); + } + } else { /* external */ + if (smartlist_len(transport_list) != 1) { + log_warn(LD_CONFIG, "You can't have an external proxy with " + "more than one transports."); + goto err; + } + + addrport = smartlist_get(items, 2); + + if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + if (!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); + } + } + + r = 0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + if (transport_list) { + SMARTLIST_FOREACH(transport_list, char*, s, tor_free(s)); + smartlist_free(transport_list); + } + + return r; +} + +/** 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. + * If <b>validate_only</b> is 0, the line is well-formed, and it's a + * managed proxy line, launch the managed proxy. */ +static int +parse_server_transport_line(const char *line, int validate_only) +{ + smartlist_t *items = NULL; + int r; + const char *transports=NULL; + smartlist_t *transport_list=NULL; + char *type=NULL; + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + + /* managed proxy options */ + int is_managed=0; + char **proxy_argv=NULL; + char **tmp=NULL; + int proxy_argc,i; + + int line_length; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + + line_length = smartlist_len(items); + if (line_length < 3) { + log_warn(LD_CONFIG, "Too few arguments on ServerTransportPlugin line."); + goto err; + } + + /* Get the first line element, split it to commas into + transport_list (in case it's multiple transports) and validate + the transport names. */ + transports = smartlist_get(items, 0); + transport_list = smartlist_create(); + smartlist_split_string(transport_list, transports, ",", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) { + if (!string_is_C_identifier(transport_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + transport_name); + goto err; + } + } SMARTLIST_FOREACH_END(transport_name); + + type = smartlist_get(items, 1); + + if (!strcmp(type, "exec")) { + is_managed=1; + } else if (!strcmp(type, "proxy")) { + is_managed=0; + } else { + log_warn(LD_CONFIG, "Strange ServerTransportPlugin type '%s'", type); + goto err; + } + + if (is_managed) { /* managed */ + if (!validate_only) { + proxy_argc = line_length-2; + tor_assert(proxy_argc > 0); + proxy_argv = tor_malloc_zero(sizeof(char*)*(proxy_argc+1)); + tmp = proxy_argv; + + for (i=0;i<proxy_argc;i++) { /* store arguments */ + *tmp++ = smartlist_get(items, 2); + smartlist_del_keeporder(items, 2); + } + *tmp = NULL; /*terminated with NUL pointer, just like execve() likes it*/ + + /* kickstart the thing */ + pt_kickstart_server_proxy(transport_list, proxy_argv); + } + } else { /* external */ + if (smartlist_len(transport_list) != 1) { + log_warn(LD_CONFIG, "You can't have an external proxy with " + "more than one transports."); + goto err; + } + + addrport = smartlist_get(items, 2); + + if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + if (!validate_only) { + log_info(LD_DIR, "Server transport '%s' at %s:%d.", + transports, fmt_addr(&addr), (int)port); + } + } + + r = 0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + if (transport_list) { + SMARTLIST_FOREACH(transport_list, char*, s, tor_free(s)); + smartlist_free(transport_list); + } + + return r; +} + /** Read the contents of a DirServer 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> @@ -4504,7 +5005,7 @@ parse_bridge_line(const char *line, int validate_only) * 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, authority_type_t required_type, +parse_dir_server_line(const char *line, dirinfo_type_t required_type, int validate_only) { smartlist_t *items = NULL; @@ -4513,7 +5014,7 @@ parse_dir_server_line(const char *line, authority_type_t required_type, uint16_t dir_port = 0, or_port = 0; char digest[DIGEST_LEN]; char v3_digest[DIGEST_LEN]; - authority_type_t type = V2_AUTHORITY; + dirinfo_type_t type = V2_DIRINFO; int is_not_hidserv_authority = 0, is_not_v2_authority = 0; items = smartlist_create(); @@ -4534,13 +5035,13 @@ parse_dir_server_line(const char *line, authority_type_t required_type, if (TOR_ISDIGIT(flag[0])) break; if (!strcasecmp(flag, "v1")) { - type |= (V1_AUTHORITY | HIDSERV_AUTHORITY); + type |= (V1_DIRINFO | HIDSERV_DIRINFO); } else if (!strcasecmp(flag, "hs")) { - type |= HIDSERV_AUTHORITY; + type |= HIDSERV_DIRINFO; } else if (!strcasecmp(flag, "no-hs")) { is_not_hidserv_authority = 1; } else if (!strcasecmp(flag, "bridge")) { - type |= BRIDGE_AUTHORITY; + type |= BRIDGE_DIRINFO; } else if (!strcasecmp(flag, "no-v2")) { is_not_v2_authority = 1; } else if (!strcasecmpstart(flag, "orport=")) { @@ -4557,7 +5058,7 @@ parse_dir_server_line(const char *line, authority_type_t required_type, log_warn(LD_CONFIG, "Bad v3 identity digest '%s' on DirServer line", flag); } else { - type |= V3_AUTHORITY; + type |= V3_DIRINFO|EXTRAINFO_DIRINFO|MICRODESC_DIRINFO; } } else { log_warn(LD_CONFIG, "Unrecognized flag '%s' on DirServer line", @@ -4567,9 +5068,9 @@ parse_dir_server_line(const char *line, authority_type_t required_type, smartlist_del_keeporder(items, 0); } if (is_not_hidserv_authority) - type &= ~HIDSERV_AUTHORITY; + type &= ~HIDSERV_DIRINFO; if (is_not_v2_authority) - type &= ~V2_AUTHORITY; + type &= ~V2_DIRINFO; if (smartlist_len(items) < 2) { log_warn(LD_CONFIG, "Too few arguments to DirServer line."); @@ -4577,7 +5078,7 @@ parse_dir_server_line(const char *line, authority_type_t required_type, } addrport = smartlist_get(items, 0); smartlist_del_keeporder(items, 0); - if (parse_addr_port(LOG_WARN, addrport, &address, NULL, &dir_port)<0) { + if (addr_port_lookup(LOG_WARN, addrport, &address, NULL, &dir_port)<0) { log_warn(LD_CONFIG, "Error parsing DirServer address '%s'", addrport); goto err; } @@ -4632,6 +5133,374 @@ parse_dir_server_line(const char *line, authority_type_t required_type, return r; } +/** Free all storage held in <b>port</b> */ +static void +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. */ +static void +warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname) +{ + SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { + 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. " + "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); + } 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); + } + } SMARTLIST_FOREACH_END(port); +} + +#define CL_PORT_NO_OPTIONS (1u<<0) +#define CL_PORT_WARN_NONLOCAL (1u<<1) +#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2) + +/** + * Parse port configuration for a single client port type. + * + * Read entries of the "FooPort" type from the list <b>ports</b>, and + * entries of the "FooListenAddress" type from the list + * <b>listenaddrs</b>. Two syntaxes are supported: a legacy syntax + * where FooPort is at most a single entry containing a port number and + * where FooListenAddress has any number of address:port combinations; + * and a new syntax where there are no FooListenAddress entries and + * where FooPort can have any number of entries of the format + * "[Address:][Port] IsolationOptions". + * + * In log messages, describe the port type as <b>portname</b>. + * + * If no address is specified, default to <b>defaultaddr</b>. If no + * FooPort is given, default to defaultport (if 0, there is no default). + * + * If CL_PORT_NO_OPTIONS is set in <b>flags</b>, do not allow stream + * isolation options in the FooPort entries. + * + * If CL_PORT_WARN_NONLOCAL is set in <b>flags</b>, warn if any of the + * ports are not on a local address. + * + * Unless CL_PORT_ALLOW_EXTRA_LISTENADDR is set in <b>flags</b>, warn + * if FooListenAddress is set but FooPort is 0. + * + * 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. + */ +static int +parse_client_port_config(smartlist_t *out, + const config_line_t *ports, + const config_line_t *listenaddrs, + const char *portname, + int listener_type, + const char *defaultaddr, + int defaultport, + unsigned flags) +{ + smartlist_t *elts; + int retval = -1; + const unsigned allow_client_options = !(flags & CL_PORT_NO_OPTIONS); + const unsigned warn_nonlocal = flags & CL_PORT_WARN_NONLOCAL; + const unsigned allow_spurious_listenaddr = + flags & CL_PORT_ALLOW_EXTRA_LISTENADDR; + + /* FooListenAddress is deprecated; let's make it work like it used to work, + * though. */ + if (listenaddrs) { + int mainport = defaultport; + + if (ports && ports->next) { + log_warn(LD_CONFIG, "%sListenAddress can't be used when there are " + "multiple %sPort lines", portname, portname); + return -1; + } else if (ports) { + if (!strcmp(ports->value, "auto")) { + mainport = CFG_AUTO_PORT; + } else { + int ok; + mainport = (int)tor_parse_long(ports->value, 10, 0, 65535, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, "%sListenAddress can only be used with a single " + "%sPort with value \"auto\" or 1-65535.", + portname, portname); + return -1; + } + } + } + + if (mainport == 0) { + if (allow_spurious_listenaddr) + return 1; + log_warn(LD_CONFIG, "%sPort must be defined if %sListenAddress is used", + portname, portname); + return -1; + } + + for (; listenaddrs; listenaddrs = listenaddrs->next) { + tor_addr_t addr; + uint16_t port = 0; + if (tor_addr_port_lookup(listenaddrs->value, &addr, &port) < 0) { + log_warn(LD_CONFIG, "Unable to parse %sListenAddress '%s'", + portname, listenaddrs->value); + return -1; + } + if (out) { + port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + cfg->type = listener_type; + cfg->port = port ? port : mainport; + tor_addr_copy(&cfg->addr, &addr); + cfg->session_group = SESSION_GROUP_UNSET; + cfg->isolation_flags = ISO_DEFAULT; + smartlist_add(out, cfg); + } + } + + if (warn_nonlocal && out) + warn_nonlocal_client_ports(out, portname); + return 0; + } /* end if (listenaddrs) */ + + /* No ListenAddress lines. If there's no FooPort, then maybe make a default + * one. */ + if (! ports) { + if (defaultport && out) { + port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + cfg->type = listener_type; + cfg->port = defaultport; + tor_addr_parse(&cfg->addr, defaultaddr); + cfg->session_group = SESSION_GROUP_UNSET; + cfg->isolation_flags = ISO_DEFAULT; + smartlist_add(out, cfg); + } + return 0; + } + + /* At last we can actually parse the FooPort lines. The syntax is: + * [Addr:](Port|auto) [Options].*/ + elts = smartlist_create(); + + for (; ports; ports = ports->next) { + tor_addr_t addr; + int port; + int sessiongroup = SESSION_GROUP_UNSET; + unsigned isolation = ISO_DEFAULT; + + char *addrport; + uint16_t ptmp=0; + int ok; + + smartlist_split_string(elts, ports->value, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + if (smartlist_len(elts) == 0) { + log_warn(LD_CONFIG, "Invalid %sPort line with no value", portname); + goto err; + } + + if (!allow_client_options && smartlist_len(elts) > 1) { + log_warn(LD_CONFIG, "Too many options on %sPort line", portname); + goto err; + } + + /* Now parse the addr/port value */ + addrport = smartlist_get(elts, 0); + if (!strcmp(addrport, "auto")) { + port = CFG_AUTO_PORT; + tor_addr_parse(&addr, defaultaddr); + } else if (!strcasecmpend(addrport, ":auto")) { + char *addrtmp = tor_strndup(addrport, strlen(addrport)-5); + port = CFG_AUTO_PORT; + if (tor_addr_port_lookup(addrtmp, &addr, &ptmp)<0 || ptmp) { + log_warn(LD_CONFIG, "Invalid address '%s' for %sPort", + escaped(addrport), portname); + tor_free(addrtmp); + goto err; + } + } else { + /* Try parsing integer port before address, because, who knows? + "9050" might be a valid address. */ + port = (int) tor_parse_long(addrport, 10, 0, 65535, &ok, NULL); + if (ok) { + tor_addr_parse(&addr, defaultaddr); + } else if (tor_addr_port_lookup(addrport, &addr, &ptmp) == 0) { + if (ptmp == 0) { + log_warn(LD_CONFIG, "%sPort line has address but no port", portname); + goto err; + } + port = ptmp; + } else { + log_warn(LD_CONFIG, "Couldn't parse address '%s' for %sPort", + escaped(addrport), portname); + goto err; + } + } + + /* Now parse the rest of the options, if any. */ + SMARTLIST_FOREACH_BEGIN(elts, char *, elt) { + int no = 0, isoflag = 0; + const char *elt_orig = elt; + if (elt_sl_idx == 0) + continue; /* Skip addr:port */ + if (!strcasecmpstart(elt, "SessionGroup=")) { + int group = (int)tor_parse_long(elt+strlen("SessionGroup="), + 10, 0, INT_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_CONFIG, "Invalid %sPort option '%s'", + portname, escaped(elt)); + goto err; + } + if (sessiongroup >= 0) { + log_warn(LD_CONFIG, "Multiple SessionGroup options on %sPort", + portname); + goto err; + } + sessiongroup = group; + continue; + } + + if (!strcasecmpstart(elt, "No")) { + no = 1; + elt += 2; + } + if (!strcasecmpend(elt, "s")) + elt[strlen(elt)-1] = '\0'; /* kill plurals. */ + + if (!strcasecmp(elt, "IsolateDestPort")) { + isoflag = ISO_DESTPORT; + } else if (!strcasecmp(elt, "IsolateDestAddr")) { + isoflag = ISO_DESTADDR; + } else if (!strcasecmp(elt, "IsolateSOCKSAuth")) { + isoflag = ISO_SOCKSAUTH; + } else if (!strcasecmp(elt, "IsolateClientProtocol")) { + isoflag = ISO_CLIENTPROTO; + } else if (!strcasecmp(elt, "IsolateClientAddr")) { + isoflag = ISO_CLIENTADDR; + } else { + log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'", + portname, escaped(elt_orig)); + } + + if (no) { + isolation &= ~isoflag; + } else { + isolation |= isoflag; + } + } SMARTLIST_FOREACH_END(elt); + + if (out && port) { + port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t)); + cfg->type = listener_type; + cfg->port = port; + tor_addr_copy(&cfg->addr, &addr); + cfg->session_group = sessiongroup; + cfg->isolation_flags = isolation; + smartlist_add(out, cfg); + } + SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); + smartlist_clear(elts); + } + + if (warn_nonlocal && out) + warn_nonlocal_client_ports(out, portname); + + retval = 0; + err: + SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); + smartlist_free(elts); + return retval; +} + +/** Parse all client port types (Socks, DNS, Trans, NATD) from + * <b>options</b>. On success, set *<b>n_ports_out</b> to the number of + * ports that are listed and return 0. On failure, set *<b>msg</b> to a + * description of the problem and return -1. + * + * If <b>validate_only</b> is false, set configured_client_ports to the + * new list of ports parsed from <b>options</b>. + **/ +static int +parse_client_ports(const or_options_t *options, int validate_only, + char **msg, int *n_ports_out) +{ + smartlist_t *ports; + int retval = -1; + + ports = smartlist_create(); + + *n_ports_out = 0; + + if (parse_client_port_config(ports, + options->SocksPort, options->SocksListenAddress, + "Socks", CONN_TYPE_AP_LISTENER, + "127.0.0.1", 9050, + CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR) < 0) { + *msg = tor_strdup("Invalid SocksPort/SocksListenAddress configuration"); + goto err; + } + if (parse_client_port_config(ports, + options->DNSPort, options->DNSListenAddress, + "DNS", CONN_TYPE_AP_DNS_LISTENER, + "127.0.0.1", 0, + CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid DNSPort/DNSListenAddress configuration"); + goto err; + } + if (parse_client_port_config(ports, + options->TransPort, options->TransListenAddress, + "Trans", CONN_TYPE_AP_TRANS_LISTENER, + "127.0.0.1", 0, + CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid TransPort/TransListenAddress configuration"); + goto err; + } + if (parse_client_port_config(ports, + options->NATDPort, options->NATDListenAddress, + "NATD", CONN_TYPE_AP_NATD_LISTENER, + "127.0.0.1", 0, + CL_PORT_WARN_NONLOCAL) < 0) { + *msg = tor_strdup("Invalid NatdPort/NatdListenAddress configuration"); + goto err; + } + + *n_ports_out = smartlist_len(ports); + + if (!validate_only) { + if (configured_client_ports) { + SMARTLIST_FOREACH(configured_client_ports, + port_cfg_t *, p, port_cfg_free(p)); + smartlist_free(configured_client_ports); + } + configured_client_ports = ports; + ports = NULL; /* prevent free below. */ + } + + retval = 0; + err: + if (ports) { + SMARTLIST_FOREACH(ports, port_cfg_t *, p, port_cfg_free(p)); + smartlist_free(ports); + } + return retval; +} + +/** Return a list of port_cfg_t for client ports parsed from the + * options. */ +const smartlist_t * +get_configured_client_ports(void) +{ + if (!configured_client_ports) + configured_client_ports = smartlist_create(); + return configured_client_ports; +} + /** Adjust the value of options->DataDirectory, or fill it in if it's * absent. Return 0 on success, -1 on failure. */ static int @@ -4703,7 +5572,7 @@ validate_data_directory(or_options_t *options) * doesn't begin with GENERATED_FILE_PREFIX, rename it. Otherwise * replace it. Return 0 on success, -1 on failure. */ static int -write_configuration_file(const char *fname, or_options_t *options) +write_configuration_file(const char *fname, const or_options_t *options) { char *old_val=NULL, *new_val=NULL, *new_conf=NULL; int rename_old = 0, r; @@ -4844,6 +5713,26 @@ static struct unit_table_t time_units[] = { { 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. @@ -4907,6 +5796,25 @@ config_parse_memunit(const char *s, int *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. @@ -4926,13 +5834,29 @@ config_parse_interval(const char *s, int *ok) 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 +get_num_cpus(const or_options_t *options) +{ + if (options->NumCPUs == 0) { + int n = compute_num_cpus(); + return (n >= 1) ? n : 1; + } else { + return options->NumCPUs; + } +} + /** * Initialize the libevent library. */ static void -init_libevent(void) +init_libevent(const or_options_t *options) { const char *badness=NULL; + tor_libevent_cfg cfg; + + tor_assert(options); configure_libevent_logging(); /* If the kernel complains that some method (say, epoll) doesn't @@ -4942,7 +5866,12 @@ init_libevent(void) tor_check_libevent_header_compatibility(); - tor_libevent_initialize(); + memset(&cfg, 0, sizeof(cfg)); + cfg.disable_iocp = options->DisableIOCP; + cfg.num_cpus = get_num_cpus(options); + cfg.msec_per_tick = options->TokenBucketRefillInterval; + + tor_libevent_initialize(&cfg); suppress_libevent_log_msg(NULL); @@ -4981,7 +5910,7 @@ get_or_state(void) * Note: Consider using the get_datadir_fname* macros in or.h. */ char * -options_get_datadir_fname2_suffix(or_options_t *options, +options_get_datadir_fname2_suffix(const or_options_t *options, const char *sub1, const char *sub2, const char *suffix) { @@ -5016,6 +5945,69 @@ options_get_datadir_fname2_suffix(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_create(); + 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 @@ -5034,6 +6026,9 @@ or_state_validate(or_state_t *old_state, or_state_t *state, if (entry_guards_parse_state(state, 0, msg)<0) return -1; + if (validate_transports_in_state(state)<0) + return -1; + return 0; } @@ -5266,6 +6261,150 @@ or_state_save(time_t now) 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_create(); + 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 static string containing the address:port a proxy + * transport should bind on. */ +const char * +get_bindaddr_for_transport(const char *transport) +{ + static const char default_addrport[] = "127.0.0.1:0"; + const char *bindaddr = NULL; + + config_line_t *line = get_transport_in_state_by_name(transport); + if (!line) + return default_addrport; + + bindaddr = get_transport_bindaddr(line->value, transport); + + return bindaddr ? bindaddr : 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 @@ -5298,7 +6437,7 @@ getinfo_helper_config(control_connection_t *conn, smartlist_t *sl = smartlist_create(); int i; for (i = 0; _option_vars[i].name; ++i) { - config_var_t *var = &_option_vars[i]; + const config_var_t *var = &_option_vars[i]; const char *type; char *line; switch (var->type) { @@ -5307,9 +6446,11 @@ getinfo_helper_config(control_connection_t *conn, case CONFIG_TYPE_UINT: type = "Integer"; break; case CONFIG_TYPE_PORT: type = "Port"; break; case CONFIG_TYPE_INTERVAL: type = "TimeInterval"; break; + case CONFIG_TYPE_MSEC_INTERVAL: type = "TimeMsecInterval"; break; case CONFIG_TYPE_MEMUNIT: type = "DataSize"; break; case CONFIG_TYPE_DOUBLE: type = "Float"; break; case CONFIG_TYPE_BOOL: type = "Boolean"; break; + case CONFIG_TYPE_AUTOBOOL: type = "Boolean+Auto"; break; case CONFIG_TYPE_ISOTIME: type = "Time"; break; case CONFIG_TYPE_ROUTERSET: type = "RouterList"; break; case CONFIG_TYPE_CSV: type = "CommaList"; break; diff --git a/src/or/config.h b/src/or/config.h index 78a67dddf5..76f6841d70 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -13,7 +13,8 @@ #define _TOR_CONFIG_H const char *get_dirportfrontpage(void); -or_options_t *get_options(void); +const or_options_t *get_options(void); +or_options_t *get_options_mutable(void); int set_options(or_options_t *new_val, char **msg); void config_free_all(void); const char *safe_str_client(const char *address); @@ -26,21 +27,21 @@ int config_get_lines(const char *string, config_line_t **result); 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, or_options_t *options, +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) ATTR_PURE; void options_init(or_options_t *options); -char *options_dump(or_options_t *options, int minimal); +char *options_dump(const or_options_t *options, int minimal); int options_init_from_torrc(int argc, char **argv); setopt_err_t options_init_from_string(const char *cf, int command, const char *command_arg, char **msg); int option_is_recognized(const char *key); const char *option_get_canonical_name(const char *key); -config_line_t *option_get_assignment(or_options_t *options, +config_line_t *option_get_assignment(const or_options_t *options, const char *key); int options_save_current(void); const char *get_torrc_fname(void); -char *options_get_datadir_fname2_suffix(or_options_t *options, +char *options_get_datadir_fname2_suffix(const or_options_t *options, const char *sub1, const char *sub2, const char *suffix); #define get_datadir_fname2_suffix(sub1, sub2, suffix) \ @@ -57,23 +58,35 @@ char *options_get_datadir_fname2_suffix(or_options_t *options, #define get_datadir_fname_suffix(sub1, suffix) \ get_datadir_fname2_suffix((sub1), NULL, (suffix)) +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); -int options_need_geoip_info(or_options_t *options, const char **reason_out); +const smartlist_t *get_configured_client_ports(void); + +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); +const char *get_bindaddr_for_transport(const char *transport); + int getinfo_helper_config(control_connection_t *conn, const char *question, char **answer, const char **errmsg); const char *tor_get_digests(void); -uint32_t get_effective_bwrate(or_options_t *options); -uint32_t get_effective_bwburst(or_options_t *options); +uint32_t get_effective_bwrate(const or_options_t *options); +uint32_t get_effective_bwburst(const or_options_t *options); #ifdef CONFIG_PRIVATE /* Used only by config.c and test.c */ or_options_t *options_new(void); #endif +void config_register_addressmaps(const or_options_t *options); + #endif diff --git a/src/or/connection.c b/src/or/connection.c index 2049f4240c..bf39a5cb9c 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -36,17 +36,28 @@ #include "router.h" #include "routerparse.h" +#ifdef USE_BUFFEREVENTS +#include <event2/event.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + static connection_t *connection_create_listener( const struct sockaddr *listensockaddr, socklen_t listensocklen, int type, - char* address); + const char *address, + const port_cfg_t *portcfg); static void connection_init(time_t now, connection_t *conn, int type, int socket_family); static int connection_init_accepted_conn(connection_t *conn, - uint8_t listener_type); + const listener_connection_t *listener); static int connection_handle_listener_read(connection_t *conn, int new_type); +#ifndef USE_BUFFEREVENTS static int connection_bucket_should_increase(int bucket, or_connection_t *conn); +#endif static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); @@ -60,6 +71,8 @@ static void set_constrained_socket_buffers(tor_socket_t sock, int size); static const char *connection_proxy_state_to_string(int state); static int connection_read_https_proxy_response(connection_t *conn); static void connection_send_socks5_connect(connection_t *conn); +static const char *proxy_type_to_string(int proxy_type); +static int get_proxy_type(void); /** The last IPv4 address that our network interface seemed to have been * binding to, in host order. We use this to detect when our IP changes. */ @@ -68,6 +81,15 @@ static uint32_t last_interface_ip = 0; * Used to detect IP address changes. */ static smartlist_t *outgoing_addrs = NULL; +#define CASE_ANY_LISTENER_TYPE \ + case CONN_TYPE_OR_LISTENER: \ + case CONN_TYPE_AP_LISTENER: \ + case CONN_TYPE_DIR_LISTENER: \ + case CONN_TYPE_CONTROL_LISTENER: \ + case CONN_TYPE_AP_TRANS_LISTENER: \ + case CONN_TYPE_AP_NATD_LISTENER: \ + case CONN_TYPE_AP_DNS_LISTENER + /**************************************************************/ /** @@ -108,13 +130,7 @@ conn_state_to_string(int type, int state) { static char buf[96]; switch (type) { - case CONN_TYPE_OR_LISTENER: - case CONN_TYPE_AP_LISTENER: - case CONN_TYPE_AP_TRANS_LISTENER: - case CONN_TYPE_AP_NATD_LISTENER: - case CONN_TYPE_AP_DNS_LISTENER: - case CONN_TYPE_DIR_LISTENER: - case CONN_TYPE_CONTROL_LISTENER: + CASE_ANY_LISTENER_TYPE: if (state == LISTENER_STATE_READY) return "ready"; break; @@ -124,10 +140,13 @@ conn_state_to_string(int type, int state) case OR_CONN_STATE_PROXY_HANDSHAKING: return "handshaking (proxy)"; case OR_CONN_STATE_TLS_HANDSHAKING: return "handshaking (TLS)"; case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING: - return "renegotiating (TLS)"; + return "renegotiating (TLS, v2 handshake)"; case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: - return "waiting for renegotiation (TLS)"; - case OR_CONN_STATE_OR_HANDSHAKING: return "handshaking (Tor)"; + return "waiting for renegotiation or V3 handshake"; + case OR_CONN_STATE_OR_HANDSHAKING_V2: + return "handshaking (Tor, v2 handshake)"; + case OR_CONN_STATE_OR_HANDSHAKING_V3: + return "handshaking (Tor, v3 handshake)"; case OR_CONN_STATE_OPEN: return "open"; } break; @@ -183,6 +202,26 @@ conn_state_to_string(int type, int state) return buf; } +#ifdef USE_BUFFEREVENTS +/** Return true iff the connection's type is one that can use a + bufferevent-based implementation. */ +int +connection_type_uses_bufferevent(connection_t *conn) +{ + switch (conn->type) { + case CONN_TYPE_AP: + case CONN_TYPE_EXIT: + case CONN_TYPE_DIR: + case CONN_TYPE_CONTROL: + case CONN_TYPE_OR: + case CONN_TYPE_CPUWORKER: + return 1; + default: + return 0; + } +} +#endif + /** Allocate and return a new dir_connection_t, initialized as by * connection_init(). */ dir_connection_t * @@ -211,16 +250,26 @@ or_connection_new(int socket_family) return or_conn; } +/** Allocate and return a new entry_connection_t, initialized as by + * connection_init(). */ +entry_connection_t * +entry_connection_new(int type, int socket_family) +{ + entry_connection_t *entry_conn = tor_malloc_zero(sizeof(entry_connection_t)); + 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(); + return entry_conn; +} + /** Allocate and return a new edge_connection_t, initialized as by * connection_init(). */ edge_connection_t * edge_connection_new(int type, int socket_family) { edge_connection_t *edge_conn = tor_malloc_zero(sizeof(edge_connection_t)); - tor_assert(type == CONN_TYPE_EXIT || type == CONN_TYPE_AP); + tor_assert(type == CONN_TYPE_EXIT); connection_init(time(NULL), TO_CONN(edge_conn), type, socket_family); - if (type == CONN_TYPE_AP) - edge_conn->socks_request = tor_malloc_zero(sizeof(socks_request_t)); return edge_conn; } @@ -237,6 +286,17 @@ control_connection_new(int socket_family) return control_conn; } +/** Allocate and return a new listener_connection_t, initialized as by + * connection_init(). */ +listener_connection_t * +listener_connection_new(int type, int socket_family) +{ + listener_connection_t *listener_conn = + tor_malloc_zero(sizeof(listener_connection_t)); + connection_init(time(NULL), TO_CONN(listener_conn), type, socket_family); + return listener_conn; +} + /** Allocate, initialize, and return a new connection_t subtype of <b>type</b> * to make or receive connections of address family <b>socket_family</b>. The * type should be one of the CONN_TYPE_* constants. */ @@ -248,15 +308,20 @@ connection_new(int type, int socket_family) return TO_CONN(or_connection_new(socket_family)); case CONN_TYPE_EXIT: - case CONN_TYPE_AP: return TO_CONN(edge_connection_new(type, socket_family)); + case CONN_TYPE_AP: + return ENTRY_TO_CONN(entry_connection_new(type, socket_family)); + case CONN_TYPE_DIR: return TO_CONN(dir_connection_new(socket_family)); case CONN_TYPE_CONTROL: return TO_CONN(control_connection_new(socket_family)); + CASE_ANY_LISTENER_TYPE: + return TO_CONN(listener_connection_new(type, socket_family)); + default: { connection_t *conn = tor_malloc_zero(sizeof(connection_t)); connection_init(time(NULL), conn, type, socket_family); @@ -288,15 +353,20 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family) conn->magic = OR_CONNECTION_MAGIC; break; case CONN_TYPE_EXIT: - case CONN_TYPE_AP: conn->magic = EDGE_CONNECTION_MAGIC; break; + case CONN_TYPE_AP: + conn->magic = ENTRY_CONNECTION_MAGIC; + break; case CONN_TYPE_DIR: conn->magic = DIR_CONNECTION_MAGIC; break; case CONN_TYPE_CONTROL: conn->magic = CONTROL_CONNECTION_MAGIC; break; + CASE_ANY_LISTENER_TYPE: + conn->magic = LISTENER_CONNECTION_MAGIC; + break; default: conn->magic = BASE_CONNECTION_MAGIC; break; @@ -308,10 +378,13 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family) conn->type = type; conn->socket_family = socket_family; - if (!connection_is_listener(conn)) { /* listeners never use their buf */ +#ifndef USE_BUFFEREVENTS + if (!connection_is_listener(conn)) { + /* listeners never use their buf */ conn->inbuf = buf_new(); conn->outbuf = buf_new(); } +#endif conn->timestamp_created = now; conn->timestamp_lastread = now; @@ -350,6 +423,10 @@ _connection_free(connection_t *conn) memlen = sizeof(or_connection_t); break; case CONN_TYPE_AP: + tor_assert(conn->magic == ENTRY_CONNECTION_MAGIC); + mem = TO_ENTRY_CONN(conn); + memlen = sizeof(entry_connection_t); + break; case CONN_TYPE_EXIT: tor_assert(conn->magic == EDGE_CONNECTION_MAGIC); mem = TO_EDGE_CONN(conn); @@ -365,6 +442,11 @@ _connection_free(connection_t *conn) mem = TO_CONTROL_CONN(conn); memlen = sizeof(control_connection_t); break; + CASE_ANY_LISTENER_TYPE: + tor_assert(conn->magic == LISTENER_CONNECTION_MAGIC); + mem = TO_LISTENER_CONN(conn); + memlen = sizeof(listener_connection_t); + break; default: tor_assert(conn->magic == BASE_CONNECTION_MAGIC); mem = conn; @@ -377,7 +459,8 @@ _connection_free(connection_t *conn) "bytes on inbuf, %d on outbuf.", conn_type_to_string(conn->type), conn_state_to_string(conn->type, conn->state), - (int)buf_datalen(conn->inbuf), (int)buf_datalen(conn->outbuf)); + (int)connection_get_inbuf_len(conn), + (int)connection_get_outbuf_len(conn)); } if (!connection_is_listener(conn)) { @@ -407,15 +490,21 @@ _connection_free(connection_t *conn) smartlist_free(or_conn->active_circuit_pqueue); tor_free(or_conn->nickname); } - if (CONN_IS_EDGE(conn)) { - edge_connection_t *edge_conn = TO_EDGE_CONN(conn); - tor_free(edge_conn->chosen_exit_name); - if (edge_conn->socks_request) { - memset(edge_conn->socks_request, 0xcc, sizeof(socks_request_t)); - tor_free(edge_conn->socks_request); + if (conn->type == CONN_TYPE_AP) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(conn); + tor_free(entry_conn->chosen_exit_name); + tor_free(entry_conn->original_dest_address); + if (entry_conn->socks_request) + socks_request_free(entry_conn->socks_request); + if (entry_conn->pending_optimistic_data) { + generic_buffer_free(entry_conn->pending_optimistic_data); + } + if (entry_conn->sending_optimistic_data) { + generic_buffer_free(entry_conn->sending_optimistic_data); } - - rend_data_free(edge_conn->rend_data); + } + if (CONN_IS_EDGE(conn)) { + rend_data_free(TO_EDGE_CONN(conn)->rend_data); } if (conn->type == CONN_TYPE_CONTROL) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); @@ -424,6 +513,15 @@ _connection_free(connection_t *conn) tor_free(conn->read_event); /* Probably already freed by connection_free. */ tor_free(conn->write_event); /* Probably already freed by connection_free. */ + IF_HAS_BUFFEREVENT(conn, { + /* This was a workaround to handle bugs in some old versions of libevent + * where callbacks can occur after calling bufferevent_free(). Setting + * the callbacks to NULL prevented this. It shouldn't be necessary any + * more, but let's not tempt fate for now. */ + bufferevent_setcb(conn->bufev, NULL, NULL, NULL, NULL); + bufferevent_free(conn->bufev); + conn->bufev = NULL; + }); if (conn->type == CONN_TYPE_DIR) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); @@ -450,6 +548,12 @@ _connection_free(connection_t *conn) log_warn(LD_BUG, "called on OR conn with non-zeroed identity_digest"); connection_or_remove_from_identity_map(TO_OR_CONN(conn)); } +#ifdef USE_BUFFEREVENTS + if (conn->type == CONN_TYPE_OR && TO_OR_CONN(conn)->bucket_cfg) { + ev_token_bucket_cfg_free(TO_OR_CONN(conn)->bucket_cfg); + TO_OR_CONN(conn)->bucket_cfg = NULL; + } +#endif memset(mem, 0xCC, memlen); /* poison memory */ tor_free(mem); @@ -485,39 +589,9 @@ connection_free(connection_t *conn) _connection_free(conn); } -/** 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). - * - * Don't do the checks in connection_free(), because they will - * fail. - */ -void -connection_free_all(void) -{ - smartlist_t *conns = get_connection_array(); - - /* We don't want to log any messages to controllers. */ - SMARTLIST_FOREACH(conns, connection_t *, conn, - if (conn->type == CONN_TYPE_CONTROL) - TO_CONTROL_CONN(conn)->event_mask = 0); - - control_update_global_event_mask(); - - /* Unlink everything from the identity map. */ - connection_or_clear_identity_map(); - - SMARTLIST_FOREACH(conns, connection_t *, conn, _connection_free(conn)); - - if (outgoing_addrs) { - SMARTLIST_FOREACH(outgoing_addrs, void*, addr, tor_free(addr)); - smartlist_free(outgoing_addrs); - outgoing_addrs = NULL; - } -} - -/** Do any cleanup needed: +/** + * Called when we're about to finally unlink and free a connection: + * perform necessary accounting and cleanup * - Directory conns that failed to fetch a rendezvous descriptor * need to inform pending rendezvous streams. * - OR conns need to call rep_hist_note_*() to record status. @@ -530,114 +604,20 @@ connection_free_all(void) void connection_about_to_close_connection(connection_t *conn) { - circuit_t *circ; - dir_connection_t *dir_conn; - or_connection_t *or_conn; - edge_connection_t *edge_conn; - time_t now = time(NULL); - tor_assert(conn->marked_for_close); - if (CONN_IS_EDGE(conn)) { - edge_conn = TO_EDGE_CONN(conn); - if (!edge_conn->edge_has_sent_end) { - log_warn(LD_BUG, "(Harmless.) Edge connection (marked at %s:%d) " - "hasn't sent end yet?", - conn->marked_for_close_file, conn->marked_for_close); - tor_fragile_assert(); - } - } - switch (conn->type) { case CONN_TYPE_DIR: - dir_conn = TO_DIR_CONN(conn); - if (conn->state < DIR_CONN_STATE_CLIENT_FINISHED) { - /* It's a directory connection and connecting or fetching - * failed: forget about this router, and maybe try again. */ - connection_dir_request_failed(dir_conn); - } - /* If we were trying to fetch a v2 rend desc and did not succeed, - * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that - * refetching is unnecessary.) */ - if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && - dir_conn->rend_data && - strlen(dir_conn->rend_data->onion_address) == - REND_SERVICE_ID_LEN_BASE32) - rend_client_refetch_v2_renddesc(dir_conn->rend_data); + connection_dir_about_to_close(TO_DIR_CONN(conn)); break; case CONN_TYPE_OR: - or_conn = TO_OR_CONN(conn); - /* 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)) { - or_options_t *options = get_options(); - rep_hist_note_connect_failed(or_conn->identity_digest, now); - entry_guard_register_connect_status(or_conn->identity_digest,0, - !options->HTTPSProxy, now); - if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) { - int reason = tls_error_to_orconn_end_reason(or_conn->tls_error); - control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, - reason); - if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( - orconn_end_reason_to_control_string(reason), reason); - } - } - } else if (conn->hold_open_until_flushed) { - /* We only set hold_open_until_flushed when we're intentionally - * closing a connection. */ - rep_hist_note_disconnect(or_conn->identity_digest, now); - control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED, - tls_error_to_orconn_end_reason(or_conn->tls_error)); - } else if (!tor_digest_is_zero(or_conn->identity_digest)) { - rep_hist_note_connection_died(or_conn->identity_digest, now); - 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); + connection_or_about_to_close(TO_OR_CONN(conn)); break; case CONN_TYPE_AP: - edge_conn = TO_EDGE_CONN(conn); - if (edge_conn->socks_request->has_finished == 0) { - /* since conn gets removed right after this function finishes, - * there's no point trying to send back a reply at this point. */ - log_warn(LD_BUG,"Closing stream (marked at %s:%d) without sending" - " back a socks reply.", - conn->marked_for_close_file, conn->marked_for_close); - } - if (!edge_conn->end_reason) { - log_warn(LD_BUG,"Closing stream (marked at %s:%d) without having" - " set end_reason.", - conn->marked_for_close_file, conn->marked_for_close); - } - if (edge_conn->dns_server_request) { - log_warn(LD_BUG,"Closing stream (marked at %s:%d) without having" - " replied to DNS request.", - conn->marked_for_close_file, conn->marked_for_close); - dnsserv_reject_request(edge_conn); - } - control_event_stream_bandwidth(edge_conn); - control_event_stream_status(edge_conn, STREAM_EVENT_CLOSED, - edge_conn->end_reason); - circ = circuit_get_by_edge_conn(edge_conn); - if (circ) - circuit_detach_stream(circ, edge_conn); + connection_ap_about_to_close(TO_ENTRY_CONN(conn)); break; case CONN_TYPE_EXIT: - edge_conn = TO_EDGE_CONN(conn); - circ = circuit_get_by_edge_conn(edge_conn); - if (circ) - circuit_detach_stream(circ, edge_conn); - if (conn->state == EXIT_CONN_STATE_RESOLVING) { - connection_dns_remove(edge_conn); - } + connection_exit_about_to_close(TO_EDGE_CONN(conn)); break; } } @@ -674,10 +654,9 @@ connection_close_immediate(connection_t *conn) conn->s = -1; if (conn->linked) conn->linked_conn_is_closed = 1; - if (!connection_is_listener(conn)) { + if (conn->outbuf) buf_clear(conn->outbuf); - conn->outbuf_flushlen = 0; - } + conn->outbuf_flushlen = 0; } /** Mark <b>conn</b> to be closed next time we loop through @@ -747,48 +726,6 @@ connection_expire_held_open(void) }); } -/** Create an AF_INET listenaddr struct. - * <b>listenaddress</b> provides the host and optionally the port information - * for the new structure. If no port is provided in <b>listenaddress</b> then - * <b>listenport</b> is used. - * - * If not NULL <b>readable_address</b> will contain a copy of the host part of - * <b>listenaddress</b>. - * - * The listenaddr struct has to be freed by the caller. - */ -static struct sockaddr_in * -create_inet_sockaddr(const char *listenaddress, int listenport, - char **readable_address, socklen_t *socklen_out) { - struct sockaddr_in *listenaddr = NULL; - uint32_t addr; - uint16_t usePort = 0; - - if (parse_addr_port(LOG_WARN, - listenaddress, readable_address, &addr, &usePort)<0) { - log_warn(LD_CONFIG, - "Error parsing/resolving ListenAddress %s", listenaddress); - goto err; - } - if (usePort==0) { - if (listenport != CFG_AUTO_PORT) - usePort = listenport; - } - - listenaddr = tor_malloc_zero(sizeof(struct sockaddr_in)); - listenaddr->sin_addr.s_addr = htonl(addr); - listenaddr->sin_family = AF_INET; - listenaddr->sin_port = htons((uint16_t) usePort); - - *socklen_out = sizeof(struct sockaddr_in); - - return listenaddr; - - err: - tor_free(listenaddr); - return NULL; -} - #ifdef HAVE_SYS_UN_H /** Create an AF_UNIX listenaddr struct. * <b>listenaddress</b> provides the path to the Unix socket. @@ -862,7 +799,7 @@ warn_too_many_conns(void) /** Check whether we should be willing to open an AF_UNIX socket in * <b>path</b>. Return 0 if we should go ahead and -1 if we shouldn't. */ static int -check_location_for_unix_socket(or_options_t *options, const char *path) +check_location_for_unix_socket(const or_options_t *options, const char *path) { int r = -1; char *p = tor_strdup(path); @@ -923,12 +860,20 @@ make_socket_reuseable(tor_socket_t sock) static connection_t * connection_create_listener(const struct sockaddr *listensockaddr, socklen_t socklen, - int type, char* address) + int type, const char *address, + const port_cfg_t *port_cfg) { + listener_connection_t *lis_conn; connection_t *conn; tor_socket_t s; /* the socket we're going to make */ + or_options_t const *options = get_options(); +#if defined(HAVE_PWD_H) && defined(HAVE_SYS_UN_H) + struct passwd *pw = NULL; +#endif uint16_t usePort = 0, gotPort = 0; int start_reading = 0; + static int global_next_session_group = SESSION_GROUP_FIRST_AUTO; + tor_addr_t addr; if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { warn_too_many_conns(); @@ -936,7 +881,6 @@ connection_create_listener(const struct sockaddr *listensockaddr, } if (listensockaddr->sa_family == AF_INET) { - tor_addr_t addr; int is_tcp = (type != CONN_TYPE_AP_DNS_LISTENER); if (is_tcp) start_reading = 1; @@ -950,7 +894,8 @@ connection_create_listener(const struct sockaddr *listensockaddr, is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP: IPPROTO_UDP); if (!SOCKET_OK(s)) { - log_warn(LD_NET,"Socket creation failed."); + log_warn(LD_NET,"Socket creation failed: %s", + tor_socket_strerror(tor_socket_errno(-1))); goto err; } @@ -998,12 +943,14 @@ connection_create_listener(const struct sockaddr *listensockaddr, * and listeners at the same time */ tor_assert(type == CONN_TYPE_CONTROL_LISTENER); - if (check_location_for_unix_socket(get_options(), address) < 0) + if (check_location_for_unix_socket(options, address) < 0) goto err; log_notice(LD_NET, "Opening %s on %s", conn_type_to_string(type), address); + tor_addr_make_unspec(&addr); + if (unlink(address) < 0 && errno != ENOENT) { log_warn(LD_NET, "Could not unlink %s: %s", address, strerror(errno)); @@ -1020,7 +967,20 @@ connection_create_listener(const struct sockaddr *listensockaddr, tor_socket_strerror(tor_socket_errno(s))); goto err; } - if (get_options()->ControlSocketsGroupWritable) { +#ifdef HAVE_PWD_H + if (options->User) { + pw = getpwnam(options->User); + if (pw == NULL) { + log_warn(LD_NET,"Unable to chown() %s socket: user %s not found.", + address, options->User); + } else if (chown(address, pw->pw_uid, pw->pw_gid) < 0) { + log_warn(LD_NET,"Unable to chown() %s socket: %s.", + address, strerror(errno)); + goto err; + } + } +#endif + if (options->ControlSocketsGroupWritable) { /* We need to use chmod; fchmod doesn't work on sockets on all * platforms. */ if (chmod(address, 0660) < 0) { @@ -1045,11 +1005,23 @@ connection_create_listener(const struct sockaddr *listensockaddr, set_socket_nonblocking(s); - conn = connection_new(type, listensockaddr->sa_family); + lis_conn = listener_connection_new(type, listensockaddr->sa_family); + conn = TO_CONN(lis_conn); conn->socket_family = listensockaddr->sa_family; conn->s = s; conn->address = tor_strdup(address); conn->port = gotPort; + tor_addr_copy(&conn->addr, &addr); + + if (port_cfg->isolation_flags) { + lis_conn->isolation_flags = port_cfg->isolation_flags; + if (port_cfg->session_group >= 0) { + lis_conn->session_group = port_cfg->session_group; + } else { + /* XXXX023 This can wrap after ~INT_MAX ports are opened. */ + lis_conn->session_group = global_next_session_group--; + } + } if (connection_add(conn) < 0) { /* no space, forget it */ log_warn(LD_NET,"connection_add for listener failed. Giving up."); @@ -1146,7 +1118,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) struct sockaddr *remote = (struct sockaddr*)addrbuf; /* length of the remote address. Must be whatever accept() needs. */ socklen_t remotelen = (socklen_t)sizeof(addrbuf); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); tor_assert((size_t)remotelen >= sizeof(struct sockaddr_in)); memset(addrbuf, 0, sizeof(addrbuf)); @@ -1260,7 +1232,7 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; /* no need to tear down the parent */ } - if (connection_init_accepted_conn(newconn, conn->type) < 0) { + if (connection_init_accepted_conn(newconn, TO_LISTENER_CONN(conn)) < 0) { if (! newconn->marked_for_close) connection_mark_for_close(newconn); return 0; @@ -1274,7 +1246,8 @@ connection_handle_listener_read(connection_t *conn, int new_type) * and place it in circuit_wait. */ static int -connection_init_accepted_conn(connection_t *conn, uint8_t listener_type) +connection_init_accepted_conn(connection_t *conn, + const listener_connection_t *listener) { connection_start_reading(conn); @@ -1283,16 +1256,20 @@ connection_init_accepted_conn(connection_t *conn, uint8_t listener_type) control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0); return connection_tls_start_handshake(TO_OR_CONN(conn), 1); case CONN_TYPE_AP: - switch (listener_type) { + 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; + switch (TO_CONN(listener)->type) { case CONN_TYPE_AP_LISTENER: conn->state = AP_CONN_STATE_SOCKS_WAIT; break; case CONN_TYPE_AP_TRANS_LISTENER: - TO_EDGE_CONN(conn)->is_transparent_ap = 1; + TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - return connection_ap_process_transparent(TO_EDGE_CONN(conn)); + return connection_ap_process_transparent(TO_ENTRY_CONN(conn)); case CONN_TYPE_AP_NATD_LISTENER: - TO_EDGE_CONN(conn)->is_transparent_ap = 1; + TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_NATD_WAIT; break; } @@ -1325,8 +1302,8 @@ connection_connect(connection_t *conn, const char *address, int inprogress = 0; char addrbuf[256]; struct sockaddr *dest_addr; - socklen_t dest_addr_len; - or_options_t *options = get_options(); + int dest_addr_len; + const or_options_t *options = get_options(); int protocol_family; if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { @@ -1383,7 +1360,7 @@ connection_connect(connection_t *conn, const char *address, make_socket_reuseable(s); - if (connect(s, dest_addr, dest_addr_len) < 0) { + if (connect(s, dest_addr, (socklen_t)dest_addr_len) < 0) { int e = tor_socket_errno(s); if (!ERRNO_IS_CONN_EINPROGRESS(e)) { /* yuck. kill it. */ @@ -1408,7 +1385,7 @@ connection_connect(connection_t *conn, const char *address, escaped_safe_str_client(address), port, inprogress?"in progress":"established", s); conn->s = s; - if (connection_add(conn) < 0) /* no space, forget it */ + if (connection_add_connecting(conn) < 0) /* no space, forget it */ return -1; return inprogress ? 0 : 1; } @@ -1421,6 +1398,7 @@ connection_proxy_state_to_string(int state) static const char *unknown = "???"; static const char *states[] = { "PROXY_NONE", + "PROXY_INFANT", "PROXY_HTTPS_WANT_CONNECT_OK", "PROXY_SOCKS4_WANT_CONNECT_OK", "PROXY_SOCKS5_WANT_AUTH_METHOD_NONE", @@ -1449,7 +1427,7 @@ connection_proxy_state_to_string(int state) int connection_proxy_connect(connection_t *conn, int type) { - or_options_t *options; + const or_options_t *options; tor_assert(conn); @@ -1641,6 +1619,19 @@ connection_send_socks5_connect(connection_t *conn) conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } +/** DOCDOC */ +static int +connection_fetch_from_buf_socks_client(connection_t *conn, + int state, char **reason) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_from_evbuffer_socks_client(input, state, reason); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_socks_client(conn->inbuf, state, reason); + } +} + /** Call this from connection_*_process_inbuf() to advance the proxy * handshake. * @@ -1668,17 +1659,17 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS4_WANT_CONNECT_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); if (ret == 1) conn->proxy_state = PROXY_CONNECTED; break; case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* no auth needed, do connect */ if (ret == 1) { connection_send_socks5_connect(conn); @@ -1687,9 +1678,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* send auth if needed, otherwise do connect */ if (ret == 1) { @@ -1724,9 +1715,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); /* send the connect request */ if (ret == 1) { connection_send_socks5_connect(conn); @@ -1735,9 +1726,9 @@ connection_read_proxy_handshake(connection_t *conn) break; case PROXY_SOCKS5_WANT_CONNECT_OK: - ret = fetch_from_buf_socks_client(conn->inbuf, - conn->proxy_state, - &reason); + ret = connection_fetch_from_buf_socks_client(conn, + conn->proxy_state, + &reason); if (ret == 1) conn->proxy_state = PROXY_CONNECTED; break; @@ -1770,6 +1761,113 @@ connection_read_proxy_handshake(connection_t *conn) return ret; } +/** Given a list of listener connections in <b>old_conns</b>, and list of + * port_cfg_t entries in <b>ports</b>, open a new listener for every port in + * <b>ports</b> that does not already have a listener in <b>old_conns</b>. + * + * Remove from <b>old_conns</b> every connection that has a corresponding + * entry in <b>ports</b>. Add to <b>new_conns</b> new every connection we + * launch. + * + * Return 0 on success, -1 on failure. + **/ +static int +retry_listener_ports(smartlist_t *old_conns, + const smartlist_t *ports, + smartlist_t *new_conns) +{ + smartlist_t *launch = smartlist_create(); + int r = 0; + + smartlist_add_all(launch, ports); + + /* Iterate through old_conns, comparing it to launch: remove from both lists + * each pair of elements that corresponds to the same port. */ + SMARTLIST_FOREACH_BEGIN(old_conns, connection_t *, conn) { + const port_cfg_t *found_port = NULL; + + /* Okay, so this is a listener. Is it configured? */ + SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, wanted) { + if (conn->type != wanted->type) + continue; + if ((conn->socket_family != AF_UNIX && wanted->is_unix_addr) || + (conn->socket_family == AF_UNIX && ! wanted->is_unix_addr)) + continue; + + if (wanted->is_unix_addr) { + if (conn->socket_family == AF_UNIX && + !strcmp(wanted->unix_addr, conn->address)) { + found_port = wanted; + break; + } + } else { + int port_matches; + if (wanted->port == CFG_AUTO_PORT) { + port_matches = 1; + } else { + port_matches = (wanted->port == conn->port); + } + if (port_matches && tor_addr_eq(&wanted->addr, &conn->addr)) { + found_port = wanted; + break; + } + } + } SMARTLIST_FOREACH_END(wanted); + + if (found_port) { + /* This listener is already running; we don't need to launch it. */ + //log_debug(LD_NET, "Already have %s on %s:%d", + // conn_type_to_string(found_port->type), conn->address, conn->port); + smartlist_remove(launch, found_port); + /* And we can remove the connection from old_conns too. */ + SMARTLIST_DEL_CURRENT(old_conns, conn); + } + } SMARTLIST_FOREACH_END(conn); + + /* Now open all the listeners that are configured but not opened. */ + SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, port) { + struct sockaddr *listensockaddr; + socklen_t listensocklen = 0; + char *address=NULL; + connection_t *conn; + int real_port = port->port == CFG_AUTO_PORT ? 0 : port->port; + tor_assert(real_port <= UINT16_MAX); + + if (port->is_unix_addr) { + listensockaddr = (struct sockaddr *) + create_unix_sockaddr(port->unix_addr, + &address, &listensocklen); + } else { + listensockaddr = tor_malloc(sizeof(struct sockaddr_storage)); + listensocklen = tor_addr_to_sockaddr(&port->addr, + real_port, + listensockaddr, + sizeof(struct sockaddr_storage)); + address = tor_dup_addr(&port->addr); + } + + if (listensockaddr) { + conn = connection_create_listener(listensockaddr, listensocklen, + port->type, address, port); + tor_free(listensockaddr); + tor_free(address); + } else { + conn = NULL; + } + + if (!conn) { + r = -1; + } else { + if (new_conns) + smartlist_add(new_conns, conn); + } + } SMARTLIST_FOREACH_END(port); + + smartlist_free(launch); + + return r; +} + /** * Launch any configured listener connections of type <b>type</b>. (A * listener is configured if <b>port_option</b> is non-zero. If any @@ -1777,168 +1875,73 @@ connection_read_proxy_handshake(connection_t *conn) * connection binding to each one. Otherwise, create a single * connection binding to the address <b>default_addr</b>.) * - * Only launch the listeners of this type that are not already open, and - * only close listeners that are no longer wanted. Existing listeners - * that are still configured are not touched. - * - * If <b>disable_all_conns</b> is set, then never open new conns, and - * close the existing ones. + * We assume that we're starting with a list of existing listener connection_t + * pointers in <b>old_conns</b>: we do not launch listeners that are already + * in that list. Instead, we just remove them from the list. * - * Add all old conns that should be closed to <b>replaced_conns</b>. - * Add all new connections to <b>new_conns</b>. + * All new connections we launch are added to <b>new_conns</b>. */ static int -retry_listeners(int type, config_line_t *cfg, +retry_listeners(smartlist_t *old_conns, + int type, const config_line_t *cfg, int port_option, const char *default_addr, - smartlist_t *replaced_conns, smartlist_t *new_conns, - int disable_all_conns, - int socket_family) + int is_sockaddr_un) { - smartlist_t *launch = smartlist_create(), *conns; - int free_launch_elts = 1; - int r; - config_line_t *c; - connection_t *conn; - config_line_t *line; - - tor_assert(socket_family == AF_INET || socket_family == AF_UNIX); + smartlist_t *ports = smartlist_create(); + tor_addr_t dflt_addr; + int retval = 0; - if (cfg && port_option) { - for (c = cfg; c; c = c->next) { - smartlist_add(launch, c); - } - free_launch_elts = 0; - } else if (port_option) { - line = tor_malloc_zero(sizeof(config_line_t)); - line->key = tor_strdup(""); - line->value = tor_strdup(default_addr); - smartlist_add(launch, line); + if (default_addr) { + tor_addr_parse(&dflt_addr, default_addr); + } else { + tor_addr_make_unspec(&dflt_addr); } - /* - SMARTLIST_FOREACH(launch, config_line_t *, l, - log_fn(LOG_NOTICE, "#%s#%s", l->key, l->value)); - */ - - conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type != type || - conn->socket_family != socket_family || - conn->marked_for_close) - continue; - /* Okay, so this is a listener. Is it configured? */ - line = NULL; - SMARTLIST_FOREACH(launch, config_line_t *, wanted, - { - char *address=NULL; - uint16_t port; - switch (socket_family) { - case AF_INET: - if (!parse_addr_port(LOG_WARN, - wanted->value, &address, NULL, &port)) { - int addr_matches = !strcasecmp(address, conn->address); - int port_matches; - tor_free(address); - if (port) { - /* The Listener line has a port */ - port_matches = (port == conn->port); - } else if (port_option == CFG_AUTO_PORT) { - /* The Listener line has no port, and the Port line is "auto". - * "auto" matches anything; transitions from any port to - * "auto" succeed. */ - port_matches = 1; - } else { - /* The Listener line has no port, and the Port line is "auto". - * "auto" matches anything; transitions from any port to - * "auto" succeed. */ - port_matches = (port_option == conn->port); - } - if (port_matches && addr_matches) { - line = wanted; - break; - } - } - break; - case AF_UNIX: - if (!strcasecmp(wanted->value, conn->address)) { - line = wanted; - break; - } - break; - default: - tor_assert(0); - } - }); - if (!line || disable_all_conns) { - /* This one isn't configured. Close it. */ - log_notice(LD_NET, "Closing no-longer-configured %s on %s:%d", - conn_type_to_string(type), conn->address, conn->port); - if (replaced_conns) { - smartlist_add(replaced_conns, conn); - } else { - connection_close_immediate(conn); - connection_mark_for_close(conn); - } + if (port_option) { + if (!cfg) { + port_cfg_t *port = tor_malloc_zero(sizeof(port_cfg_t)); + tor_addr_copy(&port->addr, &dflt_addr); + port->port = port_option; + port->type = type; + smartlist_add(ports, port); } else { - /* It's configured; we don't need to launch it. */ -// log_debug(LD_NET, "Already have %s on %s:%d", -// conn_type_to_string(type), conn->address, conn->port); - smartlist_remove(launch, line); - if (free_launch_elts) - config_free_lines(line); - } - }); - - /* Now open all the listeners that are configured but not opened. */ - r = 0; - if (!disable_all_conns) { - SMARTLIST_FOREACH_BEGIN(launch, config_line_t *, cfg_line) { - char *address = NULL; - struct sockaddr *listensockaddr; - socklen_t listensocklen = 0; - - switch (socket_family) { - case AF_INET: - listensockaddr = (struct sockaddr *) - create_inet_sockaddr(cfg_line->value, - port_option, - &address, &listensocklen); - break; - case AF_UNIX: - listensockaddr = (struct sockaddr *) - create_unix_sockaddr(cfg_line->value, - &address, &listensocklen); - break; - default: - tor_assert(0); - } - - if (listensockaddr) { - conn = connection_create_listener(listensockaddr, listensocklen, - type, address); - tor_free(listensockaddr); - tor_free(address); - } else - conn = NULL; - - if (!conn) { - r = -1; + const config_line_t *c; + for (c = cfg; c; c = c->next) { + port_cfg_t *port; + tor_addr_t addr; + uint16_t portval = 0; + if (is_sockaddr_un) { + size_t len = strlen(c->value); + port = tor_malloc_zero(sizeof(port_cfg_t) + len + 1); + port->is_unix_addr = 1; + memcpy(port->unix_addr, c->value, len+1); } else { - if (new_conns) - smartlist_add(new_conns, conn); + if (tor_addr_port_lookup(c->value, &addr, &portval) < 0) { + log_warn(LD_CONFIG, "Can't parse/resolve %s %s", + c->key, c->value); + retval = -1; + continue; + } + port = tor_malloc_zero(sizeof(port_cfg_t)); + tor_addr_copy(&port->addr, &addr); } - } SMARTLIST_FOREACH_END(cfg_line); + port->type = type; + port->port = portval ? portval : port_option; + smartlist_add(ports, port); + } + } } - if (free_launch_elts) { - SMARTLIST_FOREACH(launch, config_line_t *, cfg_line, - config_free_lines(cfg_line)); - } - smartlist_free(launch); + if (retval == -1) + goto cleanup; - return r; + retval = retry_listener_ports(old_conns, ports, new_conns); + + cleanup: + SMARTLIST_FOREACH(ports, port_cfg_t *, p, tor_free(p)); + smartlist_free(ports); + return retval; } /** Launch listeners for each port you should have open. Only launch @@ -1952,53 +1955,61 @@ int retry_all_listeners(smartlist_t *replaced_conns, smartlist_t *new_conns) { - or_options_t *options = get_options(); + smartlist_t *listeners = smartlist_create(); + const or_options_t *options = get_options(); int retval = 0; const uint16_t old_or_port = router_get_advertised_or_port(options); const uint16_t old_dir_port = router_get_advertised_dir_port(options, 0); - if (retry_listeners(CONN_TYPE_OR_LISTENER, options->ORListenAddress, - options->ORPort, "0.0.0.0", - replaced_conns, new_conns, options->ClientOnly, - AF_INET)<0) - retval = -1; - if (retry_listeners(CONN_TYPE_DIR_LISTENER, options->DirListenAddress, - options->DirPort, "0.0.0.0", - replaced_conns, new_conns, options->ClientOnly, - AF_INET)<0) - retval = -1; - if (retry_listeners(CONN_TYPE_AP_LISTENER, options->SocksListenAddress, - options->SocksPort, "127.0.0.1", - replaced_conns, new_conns, 0, - AF_INET)<0) - retval = -1; - if (retry_listeners(CONN_TYPE_AP_TRANS_LISTENER, options->TransListenAddress, - options->TransPort, "127.0.0.1", - replaced_conns, new_conns, 0, - AF_INET)<0) - retval = -1; - if (retry_listeners(CONN_TYPE_AP_NATD_LISTENER, options->NATDListenAddress, - options->NATDPort, "127.0.0.1", - replaced_conns, new_conns, 0, - AF_INET)<0) - retval = -1; - if (retry_listeners(CONN_TYPE_AP_DNS_LISTENER, options->DNSListenAddress, - options->DNSPort, "127.0.0.1", - replaced_conns, new_conns, 0, - AF_INET)<0) + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (connection_is_listener(conn) && !conn->marked_for_close) + smartlist_add(listeners, conn); + } SMARTLIST_FOREACH_END(conn); + + if (! options->ClientOnly) { + if (retry_listeners(listeners, + CONN_TYPE_OR_LISTENER, options->ORListenAddress, + options->ORPort, "0.0.0.0", + new_conns, 0) < 0) + retval = -1; + if (retry_listeners(listeners, + CONN_TYPE_DIR_LISTENER, options->DirListenAddress, + options->DirPort, "0.0.0.0", + new_conns, 0) < 0) + retval = -1; + } + + if (retry_listener_ports(listeners, + get_configured_client_ports(), + new_conns) < 0) retval = -1; - if (retry_listeners(CONN_TYPE_CONTROL_LISTENER, + if (retry_listeners(listeners, + CONN_TYPE_CONTROL_LISTENER, options->ControlListenAddress, options->ControlPort, "127.0.0.1", - replaced_conns, new_conns, 0, - AF_INET)<0) - return -1; - if (retry_listeners(CONN_TYPE_CONTROL_LISTENER, + new_conns, 0) < 0) + retval = -1; + if (retry_listeners(listeners, + CONN_TYPE_CONTROL_LISTENER, options->ControlSocket, options->ControlSocket ? 1 : 0, NULL, - replaced_conns, new_conns, 0, - AF_UNIX)<0) - return -1; + new_conns, 1) < 0) + retval = -1; + + /* Any members that were still in 'listeners' don't correspond to + * any configured port. Kill 'em. */ + SMARTLIST_FOREACH_BEGIN(listeners, connection_t *, conn) { + log_notice(LD_NET, "Closing no-longer-configured %s on %s:%d", + conn_type_to_string(conn->type), conn->address, conn->port); + if (replaced_conns) { + smartlist_add(replaced_conns, conn); + } else { + connection_close_immediate(conn); + connection_mark_for_close(conn); + } + } SMARTLIST_FOREACH_END(conn); + + smartlist_free(listeners); if (old_or_port != router_get_advertised_or_port(options) || old_dir_port != router_get_advertised_dir_port(options, 0)) { @@ -2012,20 +2023,29 @@ retry_all_listeners(smartlist_t *replaced_conns, return retval; } -/** 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. */ +/** 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. */ static int connection_is_rate_limited(connection_t *conn) { - if (conn->linked || /* internal connection */ - tor_addr_family(&conn->addr) == AF_UNSPEC || /* no address */ - tor_addr_is_internal(&conn->addr, 0)) /* internal address */ - return 0; + const or_options_t *options = get_options(); + if (conn->linked) + return 0; /* Internal connection */ + else if (! options->CountPrivateBandwidth && + (tor_addr_family(&conn->addr) == AF_UNSPEC || /* no address */ + tor_addr_is_internal(&conn->addr, 0))) + return 0; /* Internal address */ else return 1; } +#ifdef USE_BUFFEREVENTS +static struct bufferevent_rate_limit_group *global_rate_limit = NULL; +#else extern int global_read_bucket, global_write_bucket; extern int global_relayed_read_bucket, global_relayed_write_bucket; @@ -2033,11 +2053,13 @@ extern int global_relayed_read_bucket, global_relayed_write_bucket; * we are likely to run dry again this second, so be stingy with the * tokens we just put in. */ static int write_buckets_empty_last_second = 0; +#endif /** How many seconds of no active local circuits will make the * connection revert to the "relayed" bandwidth class? */ #define CLIENT_IDLE_TIME_FOR_PRIORITY 30 +#ifndef USE_BUFFEREVENTS /** Return 1 if <b>conn</b> should use tokens from the "relayed" * bandwidth rates, else 0. Currently, only OR conns with bandwidth * class 1, and directory conns that are serving data out, count. @@ -2148,6 +2170,20 @@ connection_bucket_write_limit(connection_t *conn, time_t now) return connection_bucket_round_robin(base, priority, global_bucket, conn_bucket); } +#else +static ssize_t +connection_bucket_read_limit(connection_t *conn, time_t now) +{ + (void) now; + return bufferevent_get_max_to_read(conn->bufev); +} +ssize_t +connection_bucket_write_limit(connection_t *conn, time_t now) +{ + (void) now; + return bufferevent_get_max_to_write(conn->bufev); +} +#endif /** Return 1 if the global write buckets are low enough that we * shouldn't send <b>attempt</b> bytes of low-priority directory stuff @@ -2172,8 +2208,12 @@ connection_bucket_write_limit(connection_t *conn, time_t now) int global_write_bucket_low(connection_t *conn, size_t attempt, int priority) { +#ifdef USE_BUFFEREVENTS + ssize_t smaller_bucket = bufferevent_get_max_to_write(conn->bufev); +#else int smaller_bucket = global_write_bucket < global_relayed_write_bucket ? global_write_bucket : global_relayed_write_bucket; +#endif if (authdir_mode(get_options()) && priority>1) return 0; /* there's always room to answer v2 if we're an auth dir */ @@ -2183,12 +2223,14 @@ global_write_bucket_low(connection_t *conn, size_t attempt, int priority) if (smaller_bucket < (int)attempt) return 1; /* not enough space no matter the priority */ +#ifndef USE_BUFFEREVENTS if (write_buckets_empty_last_second) return 1; /* we're already hitting our limits, no more please */ +#endif if (priority == 1) { /* old-style v1 query */ /* Could we handle *two* of these requests within the next two seconds? */ - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int64_t can_write = (int64_t)smaller_bucket + 2*(options->RelayBandwidthRate ? options->RelayBandwidthRate : options->BandwidthRate); @@ -2200,23 +2242,11 @@ global_write_bucket_low(connection_t *conn, size_t attempt, int priority) return 0; } -/** We just read <b>num_read</b> and wrote <b>num_written</b> bytes - * onto <b>conn</b>. Decrement buckets appropriately. */ +/** DOCDOC */ static void -connection_buckets_decrement(connection_t *conn, time_t now, - size_t num_read, size_t num_written) +record_num_bytes_transferred_impl(connection_t *conn, + time_t now, size_t num_read, size_t num_written) { - if (num_written >= INT_MAX || num_read >= INT_MAX) { - log_err(LD_BUG, "Value out of range. num_read=%lu, num_written=%lu, " - "connection type=%s, state=%s", - (unsigned long)num_read, (unsigned long)num_written, - conn_type_to_string(conn->type), - conn_state_to_string(conn->type, conn->state)); - if (num_written >= INT_MAX) num_written = 1; - if (num_read >= INT_MAX) num_read = 1; - tor_fragile_assert(); - } - /* Count bytes of answering direct and tunneled directory requests */ if (conn->type == CONN_TYPE_DIR && conn->purpose == DIR_PURPOSE_SERVER) { if (num_read > 0) @@ -2227,6 +2257,11 @@ connection_buckets_decrement(connection_t *conn, time_t now, if (!connection_is_rate_limited(conn)) return; /* local IPs are free */ + + if (conn->type == CONN_TYPE_OR) + rep_hist_note_or_conn_bytes(conn->global_identifier, num_read, + num_written, now); + if (num_read > 0) { rep_hist_note_bytes_read(num_read, now); } @@ -2235,6 +2270,52 @@ connection_buckets_decrement(connection_t *conn, time_t now, } if (conn->type == CONN_TYPE_EXIT) rep_hist_note_exit_bytes(conn->port, num_written, num_read); +} + +#ifdef USE_BUFFEREVENTS +/** DOCDOC */ +static void +record_num_bytes_transferred(connection_t *conn, + time_t now, size_t num_read, size_t num_written) +{ + /* XXX023 check if this is necessary */ + if (num_written >= INT_MAX || num_read >= INT_MAX) { + log_err(LD_BUG, "Value out of range. num_read=%lu, num_written=%lu, " + "connection type=%s, state=%s", + (unsigned long)num_read, (unsigned long)num_written, + conn_type_to_string(conn->type), + conn_state_to_string(conn->type, conn->state)); + if (num_written >= INT_MAX) num_written = 1; + if (num_read >= INT_MAX) num_read = 1; + tor_fragile_assert(); + } + + record_num_bytes_transferred_impl(conn,now,num_read,num_written); +} +#endif + +#ifndef USE_BUFFEREVENTS +/** We just read <b>num_read</b> and wrote <b>num_written</b> bytes + * onto <b>conn</b>. Decrement buckets appropriately. */ +static void +connection_buckets_decrement(connection_t *conn, time_t now, + size_t num_read, size_t num_written) +{ + if (num_written >= INT_MAX || num_read >= INT_MAX) { + log_err(LD_BUG, "Value out of range. num_read=%lu, num_written=%lu, " + "connection type=%s, state=%s", + (unsigned long)num_read, (unsigned long)num_written, + conn_type_to_string(conn->type), + conn_state_to_string(conn->type, conn->state)); + if (num_written >= INT_MAX) num_written = 1; + if (num_read >= INT_MAX) num_read = 1; + tor_fragile_assert(); + } + + record_num_bytes_transferred_impl(conn, now, num_read, num_written); + + if (!connection_is_rate_limited(conn)) + return; /* local IPs are free */ if (connection_counts_as_relayed_traffic(conn, now)) { global_relayed_read_bucket -= (int)num_read; @@ -2300,7 +2381,7 @@ connection_consider_empty_write_buckets(connection_t *conn) void connection_bucket_init(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); /* start it at max traffic */ global_read_bucket = (int)options->BandwidthBurst; global_write_bucket = (int)options->BandwidthBurst; @@ -2313,22 +2394,23 @@ connection_bucket_init(void) } } -/** Refill a single <b>bucket</b> called <b>name</b> with bandwidth rate - * <b>rate</b> and bandwidth burst <b>burst</b>, assuming that - * <b>seconds_elapsed</b> seconds have passed since the last call. - **/ +/** Refill a single <b>bucket</b> called <b>name</b> with bandwidth rate per + * second <b>rate</b> and bandwidth burst <b>burst</b>, assuming that + * <b>milliseconds_elapsed</b> milliseconds have passed since the last + * call. */ static void connection_bucket_refill_helper(int *bucket, int rate, int burst, - int seconds_elapsed, const char *name) + int milliseconds_elapsed, + const char *name) { int starting_bucket = *bucket; - if (starting_bucket < burst && seconds_elapsed) { - if (((burst - starting_bucket)/seconds_elapsed) < rate) { + if (starting_bucket < burst && milliseconds_elapsed > 0) { + int64_t incr = (((int64_t)rate) * milliseconds_elapsed) / 1000; + if ((burst - starting_bucket) < incr) { *bucket = burst; /* We would overflow the bucket; just set it to * the maximum. */ } else { - int incr = rate*seconds_elapsed; - *bucket += incr; + *bucket += (int)incr; if (*bucket > burst || *bucket < starting_bucket) { /* If we overflow the burst, or underflow our starting bucket, * cap the bucket value to burst. */ @@ -2341,41 +2423,46 @@ connection_bucket_refill_helper(int *bucket, int rate, int burst, } } -/** A second has rolled over; increment buckets appropriately. */ +/** Time has passed; increment buckets appropriately. */ void -connection_bucket_refill(int seconds_elapsed, time_t now) +connection_bucket_refill(int milliseconds_elapsed, time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); smartlist_t *conns = get_connection_array(); - int relayrate, relayburst; + int bandwidthrate, bandwidthburst, relayrate, relayburst; + + bandwidthrate = (int)options->BandwidthRate; + bandwidthburst = (int)options->BandwidthBurst; if (options->RelayBandwidthRate) { relayrate = (int)options->RelayBandwidthRate; relayburst = (int)options->RelayBandwidthBurst; } else { - relayrate = (int)options->BandwidthRate; - relayburst = (int)options->BandwidthBurst; + relayrate = bandwidthrate; + relayburst = bandwidthburst; } - tor_assert(seconds_elapsed >= 0); + tor_assert(milliseconds_elapsed >= 0); write_buckets_empty_last_second = global_relayed_write_bucket <= 0 || global_write_bucket <= 0; /* refill the global buckets */ connection_bucket_refill_helper(&global_read_bucket, - (int)options->BandwidthRate, - (int)options->BandwidthBurst, - seconds_elapsed, "global_read_bucket"); + bandwidthrate, bandwidthburst, + milliseconds_elapsed, + "global_read_bucket"); connection_bucket_refill_helper(&global_write_bucket, - (int)options->BandwidthRate, - (int)options->BandwidthBurst, - seconds_elapsed, "global_write_bucket"); + bandwidthrate, bandwidthburst, + milliseconds_elapsed, + "global_write_bucket"); connection_bucket_refill_helper(&global_relayed_read_bucket, - relayrate, relayburst, seconds_elapsed, + relayrate, relayburst, + milliseconds_elapsed, "global_relayed_read_bucket"); connection_bucket_refill_helper(&global_relayed_write_bucket, - relayrate, relayburst, seconds_elapsed, + relayrate, relayburst, + milliseconds_elapsed, "global_relayed_write_bucket"); /* refill the per-connection buckets */ @@ -2383,18 +2470,20 @@ connection_bucket_refill(int seconds_elapsed, time_t now) { if (connection_speaks_cells(conn)) { or_connection_t *or_conn = TO_OR_CONN(conn); + int orbandwidthrate = or_conn->bandwidthrate; + int orbandwidthburst = or_conn->bandwidthburst; if (connection_bucket_should_increase(or_conn->read_bucket, or_conn)) { connection_bucket_refill_helper(&or_conn->read_bucket, - or_conn->bandwidthrate, - or_conn->bandwidthburst, - seconds_elapsed, + orbandwidthrate, + orbandwidthburst, + milliseconds_elapsed, "or_conn->read_bucket"); } if (connection_bucket_should_increase(or_conn->write_bucket, or_conn)) { connection_bucket_refill_helper(&or_conn->write_bucket, - or_conn->bandwidthrate, - or_conn->bandwidthburst, - seconds_elapsed, + orbandwidthrate, + orbandwidthburst, + milliseconds_elapsed, "or_conn->write_bucket"); } } @@ -2443,6 +2532,91 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) return 1; } +#else +static void +connection_buckets_decrement(connection_t *conn, time_t now, + size_t num_read, size_t num_written) +{ + (void) conn; + (void) now; + (void) num_read; + (void) num_written; + /* Libevent does this for us. */ +} + +void +connection_bucket_refill(int seconds_elapsed, time_t now) +{ + (void) seconds_elapsed; + (void) now; + /* Libevent does this for us. */ +} +void +connection_bucket_init(void) +{ + const or_options_t *options = get_options(); + const struct timeval *tick = tor_libevent_get_one_tick_timeout(); + struct ev_token_bucket_cfg *bucket_cfg; + + uint64_t rate, burst; + if (options->RelayBandwidthRate) { + rate = options->RelayBandwidthRate; + burst = options->RelayBandwidthBurst; + } else { + rate = options->BandwidthRate; + burst = options->BandwidthBurst; + } + + /* This can't overflow, since TokenBucketRefillInterval <= 1000, + * and rate started out less than INT32_MAX. */ + rate = (rate * options->TokenBucketRefillInterval) / 1000; + + bucket_cfg = ev_token_bucket_cfg_new((uint32_t)rate, (uint32_t)burst, + (uint32_t)rate, (uint32_t)burst, + tick); + + if (!global_rate_limit) { + global_rate_limit = + bufferevent_rate_limit_group_new(tor_libevent_get_base(), bucket_cfg); + } else { + bufferevent_rate_limit_group_set_cfg(global_rate_limit, bucket_cfg); + } + ev_token_bucket_cfg_free(bucket_cfg); +} + +void +connection_get_rate_limit_totals(uint64_t *read_out, uint64_t *written_out) +{ + if (global_rate_limit == NULL) { + *read_out = *written_out = 0; + } else { + bufferevent_rate_limit_group_get_totals( + global_rate_limit, read_out, written_out); + } +} + +/** DOCDOC */ +void +connection_enable_rate_limiting(connection_t *conn) +{ + if (conn->bufev) { + if (!global_rate_limit) + connection_bucket_init(); + tor_add_bufferevent_to_rate_limit_group(conn->bufev, global_rate_limit); + } +} + +static void +connection_consider_empty_write_buckets(connection_t *conn) +{ + (void) conn; +} +static void +connection_consider_empty_read_buckets(connection_t *conn) +{ + (void) conn; +} +#endif /** Read bytes from conn-\>s and process them. * @@ -2501,8 +2675,10 @@ connection_handle_read_impl(connection_t *conn) if (CONN_IS_EDGE(conn)) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); connection_edge_end_errno(edge_conn); - if (edge_conn->socks_request) /* broken, don't send a socks reply back */ - edge_conn->socks_request->has_finished = 1; + if (conn->type == CONN_TYPE_AP && TO_ENTRY_CONN(conn)->socks_request) { + /* broken, don't send a socks reply back */ + TO_ENTRY_CONN(conn)->socks_request->has_finished = 1; + } } connection_close_immediate(conn); /* Don't flush; connection is dead. */ connection_mark_for_close(conn); @@ -2697,7 +2873,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, } if (n_read > 0) { - /* change *max_to_read */ + /* change *max_to_read */ *max_to_read = at_most - n_read; /* Update edge_conn->n_read */ @@ -2729,11 +2905,212 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, return 0; } +#ifdef USE_BUFFEREVENTS +/* XXXX These generic versions could be simplified by making them + type-specific */ + +/** Callback: Invoked whenever bytes are added to or drained from an input + * evbuffer. Used to track the number of bytes read. */ +static void +evbuffer_inbuf_callback(struct evbuffer *buf, + const struct evbuffer_cb_info *info, void *arg) +{ + connection_t *conn = arg; + (void) buf; + /* XXXX These need to get real counts on the non-nested TLS case. - NM */ + if (info->n_added) { + time_t now = approx_time(); + conn->timestamp_lastread = now; + record_num_bytes_transferred(conn, now, info->n_added, 0); + connection_consider_empty_read_buckets(conn); + if (conn->type == CONN_TYPE_AP) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + /*XXXX022 check for overflow*/ + edge_conn->n_read += (int)info->n_added; + } + } +} + +/** Callback: Invoked whenever bytes are added to or drained from an output + * evbuffer. Used to track the number of bytes written. */ +static void +evbuffer_outbuf_callback(struct evbuffer *buf, + const struct evbuffer_cb_info *info, void *arg) +{ + connection_t *conn = arg; + (void)buf; + if (info->n_deleted) { + time_t now = approx_time(); + conn->timestamp_lastwritten = now; + record_num_bytes_transferred(conn, now, 0, info->n_deleted); + connection_consider_empty_write_buckets(conn); + if (conn->type == CONN_TYPE_AP) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + /*XXXX022 check for overflow*/ + edge_conn->n_written += (int)info->n_deleted; + } + } +} + +/** Callback: invoked whenever a bufferevent has read data. */ +void +connection_handle_read_cb(struct bufferevent *bufev, void *arg) +{ + connection_t *conn = arg; + (void) bufev; + if (!conn->marked_for_close) { + if (connection_process_inbuf(conn, 1)<0) /* XXXX Always 1? */ + if (!conn->marked_for_close) + connection_mark_for_close(conn); + } +} + +/** Callback: invoked whenever a bufferevent has written data. */ +void +connection_handle_write_cb(struct bufferevent *bufev, void *arg) +{ + connection_t *conn = arg; + struct evbuffer *output; + if (connection_flushed_some(conn)<0) { + if (!conn->marked_for_close) + connection_mark_for_close(conn); + return; + } + + output = bufferevent_get_output(bufev); + if (!evbuffer_get_length(output)) { + connection_finished_flushing(conn); + if (conn->marked_for_close && conn->hold_open_until_flushed) { + conn->hold_open_until_flushed = 0; + if (conn->linked) { + /* send eof */ + bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED); + } + } + } +} + +/** Callback: invoked whenever a bufferevent has had an event (like a + * connection, or an eof, or an error) occur. */ +void +connection_handle_event_cb(struct bufferevent *bufev, short event, void *arg) +{ + connection_t *conn = arg; + (void) bufev; + if (event & BEV_EVENT_CONNECTED) { + tor_assert(connection_state_is_connecting(conn)); + if (connection_finished_connecting(conn)<0) + return; + } + if (event & BEV_EVENT_EOF) { + if (!conn->marked_for_close) { + conn->inbuf_reached_eof = 1; + if (connection_reached_eof(conn)<0) + return; + } + } + if (event & BEV_EVENT_ERROR) { + int socket_error = evutil_socket_geterror(conn->s); + if (conn->type == CONN_TYPE_OR && + conn->state == OR_CONN_STATE_CONNECTING) { + connection_or_connect_failed(TO_OR_CONN(conn), + errno_to_orconn_end_reason(socket_error), + tor_socket_strerror(socket_error)); + } else if (CONN_IS_EDGE(conn)) { + edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + if (!edge_conn->edge_has_sent_end) + connection_edge_end_errno(edge_conn); + if (conn->type == CONN_TYPE_AP && TO_ENTRY_CONN(conn)->socks_request) { + /* broken, don't send a socks reply back */ + TO_ENTRY_CONN(conn)->socks_request->has_finished = 1; + } + } + connection_close_immediate(conn); /* Connection is dead. */ + if (!conn->marked_for_close) + connection_mark_for_close(conn); + } +} + +/** Set up the generic callbacks for the bufferevent on <b>conn</b>. */ +void +connection_configure_bufferevent_callbacks(connection_t *conn) +{ + struct bufferevent *bufev; + struct evbuffer *input, *output; + tor_assert(conn->bufev); + bufev = conn->bufev; + bufferevent_setcb(bufev, + connection_handle_read_cb, + connection_handle_write_cb, + connection_handle_event_cb, + conn); + /* Set a fairly high write low-watermark so that we get the write callback + called whenever data is written to bring us under 128K. Leave the + high-watermark at 0. + */ + bufferevent_setwatermark(bufev, EV_WRITE, 128*1024, 0); + + input = bufferevent_get_input(bufev); + output = bufferevent_get_output(bufev); + evbuffer_add_cb(input, evbuffer_inbuf_callback, conn); + evbuffer_add_cb(output, evbuffer_outbuf_callback, conn); +} +#endif + /** A pass-through to fetch_from_buf. */ int connection_fetch_from_buf(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + IF_HAS_BUFFEREVENT(conn, { + /* XXX overflow -seb */ + return (int)bufferevent_read(conn->bufev, string, len); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf(string, len, conn->inbuf); + } +} + +/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +int +connection_fetch_from_buf_line(connection_t *conn, char *data, + size_t *data_len) +{ + IF_HAS_BUFFEREVENT(conn, { + int r; + size_t eol_len=0; + struct evbuffer *input = bufferevent_get_input(conn->bufev); + struct evbuffer_ptr ptr = + evbuffer_search_eol(input, NULL, &eol_len, EVBUFFER_EOL_LF); + if (ptr.pos == -1) + return 0; /* No EOL found. */ + if ((size_t)ptr.pos+eol_len >= *data_len) { + return -1; /* Too long */ + } + *data_len = ptr.pos+eol_len; + r = evbuffer_remove(input, data, ptr.pos+eol_len); + tor_assert(r >= 0); + data[ptr.pos+eol_len] = '\0'; + return 1; + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_line(conn->inbuf, data, data_len); + } +} + +/** As fetch_from_buf_http, but fetches from a conncetion's input buffer_t or + * its bufferevent as appropriate. */ +int +connection_fetch_from_buf_http(connection_t *conn, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, + size_t max_bodylen, int force_complete) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_from_evbuffer_http(input, headers_out, max_headerlen, + body_out, body_used, max_bodylen, force_complete); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_from_buf_http(conn->inbuf, headers_out, max_headerlen, + body_out, body_used, max_bodylen, force_complete); + } } /** Return conn-\>outbuf_flushlen: how many bytes conn wants to flush @@ -2854,6 +3231,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* If we just flushed the last bytes, check if this tunneled dir * request is done. */ + /* 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); @@ -2909,6 +3287,7 @@ connection_handle_write_impl(connection_t *conn, int force) if (n_written && conn->type == CONN_TYPE_AP) { edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + /* Check for overflow: */ if (PREDICT_LIKELY(UINT32_MAX - edge_conn->n_written > n_written)) edge_conn->n_written += (int)n_written; @@ -2952,6 +3331,25 @@ connection_handle_write(connection_t *conn, int force) return res; } +/** + * Try to flush data that's waiting for a write on <b>conn</b>. Return + * -1 on failure, 0 on success. + * + * Don't use this function for regular writing; the buffers/bufferevents + * system should be good enough at scheduling writes there. Instead, this + * function is for cases when we're about to exit or something and we want + * to report it right away. + */ +int +connection_flush(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + int r = bufferevent_flush(conn->bufev, EV_WRITE, BEV_FLUSH); + return (r < 0) ? -1 : 0; + }); + return connection_handle_write(conn, 1); +} + /** 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 @@ -2985,6 +3383,22 @@ _connection_write_to_buf_impl(const char *string, size_t len, if (conn->marked_for_close && !conn->hold_open_until_flushed) return; + IF_HAS_BUFFEREVENT(conn, { + if (zlib) { + int done = zlib < 0; + r = write_to_evbuffer_zlib(bufferevent_get_output(conn->bufev), + TO_DIR_CONN(conn)->zlib_state, + string, len, done); + } else { + r = bufferevent_write(conn->bufev, string, len); + } + if (r < 0) { + /* XXXX mark for close? */ + log_warn(LD_NET, "bufferevent_write failed! That shouldn't happen."); + } + return; + }); + old_datalen = buf_datalen(conn->outbuf); if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); @@ -3012,7 +3426,13 @@ _connection_write_to_buf_impl(const char *string, size_t len, return; } - connection_start_writing(conn); + /* If we receive optimistic data in the EXIT_CONN_STATE_RESOLVING + * state, we don't want to try to write it right away, since + * conn->write_event won't be set yet. Otherwise, write data from + * this conn as the socket is available. */ + if (conn->write_event) { + connection_start_writing(conn); + } if (zlib) { conn->outbuf_flushlen += buf_datalen(conn->outbuf) - old_datalen; } else { @@ -3138,8 +3558,7 @@ connection_get_by_type_state_rendquery(int type, int state, type == CONN_TYPE_AP || type == CONN_TYPE_EXIT); tor_assert(rendquery); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type == type && !conn->marked_for_close && (!state || state == conn->state)) { @@ -3154,7 +3573,33 @@ connection_get_by_type_state_rendquery(int type, int state, TO_EDGE_CONN(conn)->rend_data->onion_address)) return conn; } - }); + } SMARTLIST_FOREACH_END(conn); + return NULL; +} + +/** Return a directory connection (if any one exists) that is fetching + * the item described by <b>state</b>/<b>resource</b> */ +dir_connection_t * +connection_dir_get_by_purpose_and_resource(int purpose, + const char *resource) +{ + smartlist_t *conns = get_connection_array(); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + dir_connection_t *dirconn; + if (conn->type != CONN_TYPE_DIR || conn->marked_for_close || + conn->purpose != purpose) + continue; + dirconn = TO_DIR_CONN(conn); + if (dirconn->requested_resource == NULL) { + if (resource == NULL) + return dirconn; + } else if (resource) { + if (0 == strcmp(resource, dirconn->requested_resource)) + return dirconn; + } + } SMARTLIST_FOREACH_END(conn); + return NULL; } @@ -3408,6 +3853,9 @@ connection_finished_flushing(connection_t *conn) // log_fn(LOG_DEBUG,"entered. Socket %u.", conn->s); + IF_HAS_NO_BUFFEREVENT(conn) + connection_stop_writing(conn); + switch (conn->type) { case CONN_TYPE_OR: return connection_or_finished_flushing(TO_OR_CONN(conn)); @@ -3533,11 +3981,23 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn); tor_assert(conn->type >= _CONN_TYPE_MIN); tor_assert(conn->type <= _CONN_TYPE_MAX); + +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + tor_assert(conn->read_event == NULL); + tor_assert(conn->write_event == NULL); + tor_assert(conn->inbuf == NULL); + tor_assert(conn->outbuf == NULL); + } +#endif + switch (conn->type) { case CONN_TYPE_OR: tor_assert(conn->magic == OR_CONNECTION_MAGIC); break; case CONN_TYPE_AP: + tor_assert(conn->magic == ENTRY_CONNECTION_MAGIC); + break; case CONN_TYPE_EXIT: tor_assert(conn->magic == EDGE_CONNECTION_MAGIC); break; @@ -3547,6 +4007,9 @@ assert_connection_ok(connection_t *conn, time_t now) case CONN_TYPE_CONTROL: tor_assert(conn->magic == CONTROL_CONNECTION_MAGIC); break; + CASE_ANY_LISTENER_TYPE: + tor_assert(conn->magic == LISTENER_CONNECTION_MAGIC); + break; default: tor_assert(conn->magic == BASE_CONNECTION_MAGIC); break; @@ -3560,8 +4023,15 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(!SOCKET_OK(conn->s)); if (conn->outbuf_flushlen > 0) { - tor_assert(connection_is_writing(conn) || conn->write_blocked_on_bw || - (CONN_IS_EDGE(conn) && TO_EDGE_CONN(conn)->edge_blocked_on_circ)); + /* With optimistic data, we may have queued data in + * EXIT_CONN_STATE_RESOLVING while the conn is not yet marked to writing. + * */ + tor_assert((conn->type == CONN_TYPE_EXIT && + conn->state == EXIT_CONN_STATE_RESOLVING) || + connection_is_writing(conn) || + conn->write_blocked_on_bw || + (CONN_IS_EDGE(conn) && + TO_EDGE_CONN(conn)->edge_blocked_on_circ)); } if (conn->hold_open_until_flushed) @@ -3571,10 +4041,10 @@ assert_connection_ok(connection_t *conn, time_t now) * marked_for_close. */ /* buffers */ - if (!connection_is_listener(conn)) { + if (conn->inbuf) assert_buf_ok(conn->inbuf); + if (conn->outbuf) assert_buf_ok(conn->outbuf); - } if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); @@ -3594,21 +4064,18 @@ assert_connection_ok(connection_t *conn, time_t now) } if (CONN_IS_EDGE(conn)) { - edge_connection_t *edge_conn = TO_EDGE_CONN(conn); - if (edge_conn->chosen_exit_optional || edge_conn->chosen_exit_retries) { - tor_assert(conn->type == CONN_TYPE_AP); - tor_assert(edge_conn->chosen_exit_name); - } - /* XXX unchecked: package window, deliver window. */ if (conn->type == CONN_TYPE_AP) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(conn); + if (entry_conn->chosen_exit_optional || entry_conn->chosen_exit_retries) + tor_assert(entry_conn->chosen_exit_name); - tor_assert(edge_conn->socks_request); + tor_assert(entry_conn->socks_request); if (conn->state == AP_CONN_STATE_OPEN) { - tor_assert(edge_conn->socks_request->has_finished); + tor_assert(entry_conn->socks_request->has_finished); if (!conn->marked_for_close) { - tor_assert(edge_conn->cpath_layer); - assert_cpath_layer_ok(edge_conn->cpath_layer); + tor_assert(ENTRY_TO_EDGE_CONN(entry_conn)->cpath_layer); + assert_cpath_layer_ok(ENTRY_TO_EDGE_CONN(entry_conn)->cpath_layer); } } } @@ -3624,13 +4091,7 @@ assert_connection_ok(connection_t *conn, time_t now) switch (conn->type) { - case CONN_TYPE_OR_LISTENER: - case CONN_TYPE_AP_LISTENER: - case CONN_TYPE_AP_TRANS_LISTENER: - case CONN_TYPE_AP_NATD_LISTENER: - case CONN_TYPE_DIR_LISTENER: - case CONN_TYPE_CONTROL_LISTENER: - case CONN_TYPE_AP_DNS_LISTENER: + CASE_ANY_LISTENER_TYPE: tor_assert(conn->state == LISTENER_STATE_READY); break; case CONN_TYPE_OR: @@ -3647,7 +4108,7 @@ assert_connection_ok(connection_t *conn, time_t now) case CONN_TYPE_AP: tor_assert(conn->state >= _AP_CONN_STATE_MIN); tor_assert(conn->state <= _AP_CONN_STATE_MAX); - tor_assert(TO_EDGE_CONN(conn)->socks_request); + tor_assert(TO_ENTRY_CONN(conn)->socks_request); break; case CONN_TYPE_DIR: tor_assert(conn->state >= _DIR_CONN_STATE_MIN); @@ -3668,3 +4129,141 @@ assert_connection_ok(connection_t *conn, time_t now) } } +/** Fills <b>addr</b> and <b>port</b> with the details of the global + * proxy server we are using. + * <b>conn</b> contains the connection we are using the proxy for. + * + * Return 0 on success, -1 on failure. + */ +int +get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, + const connection_t *conn) +{ + const or_options_t *options = get_options(); + + if (options->HTTPSProxy) { + tor_addr_copy(addr, &options->HTTPSProxyAddr); + *port = options->HTTPSProxyPort; + *proxy_type = PROXY_CONNECT; + return 0; + } else if (options->Socks4Proxy) { + tor_addr_copy(addr, &options->Socks4ProxyAddr); + *port = options->Socks4ProxyPort; + *proxy_type = PROXY_SOCKS4; + return 0; + } else if (options->Socks5Proxy) { + tor_addr_copy(addr, &options->Socks5ProxyAddr); + *port = options->Socks5ProxyPort; + *proxy_type = PROXY_SOCKS5; + return 0; + } else if (options->ClientTransportPlugin || + options->Bridges) { + const transport_t *transport = NULL; + int r; + r = find_transport_by_bridge_addrport(&conn->addr, conn->port, &transport); + if (r<0) + return -1; + if (transport) { /* transport found */ + tor_addr_copy(addr, &transport->addr); + *port = transport->port; + *proxy_type = transport->socks_version; + return 0; + } + } + + *proxy_type = PROXY_NONE; + return 0; +} + +/** Returns the global proxy type used by tor. */ +static int +get_proxy_type(void) +{ + const or_options_t *options = get_options(); + + if (options->HTTPSProxy) + return PROXY_CONNECT; + else if (options->Socks4Proxy) + return PROXY_SOCKS4; + else if (options->Socks5Proxy) + return PROXY_SOCKS5; + else if (options->ClientTransportPlugin) + return PROXY_PLUGGABLE; + else + return PROXY_NONE; +} + +/** Log a failed connection to a proxy server. + * <b>conn</b> is the connection we use the proxy server for. */ +void +log_failed_proxy_connection(connection_t *conn) +{ + tor_addr_t proxy_addr; + uint16_t proxy_port; + int proxy_type; + + if (get_proxy_addrport(&proxy_addr, &proxy_port, &proxy_type, conn) != 0) + 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. " + "Make sure that the proxy server is up and running.", + proxy_type_to_string(get_proxy_type()), fmt_addr(&proxy_addr), + proxy_port); +} + +/** Return string representation of <b>proxy_type</b>. */ +static const char * +proxy_type_to_string(int proxy_type) +{ + switch (proxy_type) { + case PROXY_CONNECT: return "HTTP"; + case PROXY_SOCKS4: return "SOCKS4"; + case PROXY_SOCKS5: return "SOCKS5"; + case PROXY_PLUGGABLE: return "pluggable transports SOCKS"; + case PROXY_NONE: return "NULL"; + default: tor_assert(0); + } + return NULL; /*Unreached*/ +} + +/** 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). + * + * Don't do the checks in connection_free(), because they will + * fail. + */ +void +connection_free_all(void) +{ + smartlist_t *conns = get_connection_array(); + + /* We don't want to log any messages to controllers. */ + SMARTLIST_FOREACH(conns, connection_t *, conn, + if (conn->type == CONN_TYPE_CONTROL) + TO_CONTROL_CONN(conn)->event_mask = 0); + + control_update_global_event_mask(); + + /* Unlink everything from the identity map. */ + connection_or_clear_identity_map(); + + /* Clear out our list of broken connections */ + clear_broken_connection_map(0); + + SMARTLIST_FOREACH(conns, connection_t *, conn, _connection_free(conn)); + + if (outgoing_addrs) { + SMARTLIST_FOREACH(outgoing_addrs, void*, addr, tor_free(addr)); + smartlist_free(outgoing_addrs); + outgoing_addrs = NULL; + } + +#ifdef USE_BUFFEREVENTS + if (global_rate_limit) + bufferevent_rate_limit_group_free(global_rate_limit); +#endif +} + diff --git a/src/or/connection.h b/src/or/connection.h index 576d3a63e1..9f11489727 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -12,13 +12,18 @@ #ifndef _TOR_CONNECTION_H #define _TOR_CONNECTION_H +/* XXXX For buf_datalen in inline function */ +#include "buffers.h" + const char *conn_type_to_string(int type); const char *conn_state_to_string(int type, int state); dir_connection_t *dir_connection_new(int socket_family); or_connection_t *or_connection_new(int socket_family); edge_connection_t *edge_connection_new(int type, int socket_family); +entry_connection_t *entry_connection_new(int type, int socket_family); control_connection_t *control_connection_new(int socket_family); +listener_connection_t *listener_connection_new(int type, int socket_family); connection_t *connection_new(int type, int socket_family); void connection_link_connections(connection_t *conn_a, connection_t *conn_b); @@ -31,6 +36,21 @@ void _connection_mark_for_close(connection_t *conn,int line, const char *file); #define connection_mark_for_close(c) \ _connection_mark_for_close((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); \ + _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_)); \ + } while (0) + +#define connection_mark_and_flush(c) \ + _connection_mark_and_flush((c), __LINE__, _SHORT_FILE_) + void connection_expire_held_open(void); int connection_connect(connection_t *conn, const char *address, @@ -39,6 +59,9 @@ int connection_connect(connection_t *conn, const char *address, int connection_proxy_connect(connection_t *conn, int type); int connection_read_proxy_handshake(connection_t *conn); +void log_failed_proxy_connection(connection_t *conn); +int get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, + const connection_t *conn); int retry_all_listeners(smartlist_t *replaced_conns, smartlist_t *new_conns); @@ -51,10 +74,18 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); +int connection_fetch_from_buf_line(connection_t *conn, char *data, + size_t *data_len); +int connection_fetch_from_buf_http(connection_t *conn, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, + size_t max_bodylen, int force_complete); int connection_wants_to_flush(connection_t *conn); 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, connection_t *conn, int zlib); static void connection_write_to_buf(const char *string, size_t len, @@ -73,6 +104,29 @@ connection_write_to_buf_zlib(const char *string, size_t len, _connection_write_to_buf_impl(string, len, TO_CONN(conn), done ? -1 : 1); } +static size_t connection_get_inbuf_len(connection_t *conn); +static size_t connection_get_outbuf_len(connection_t *conn); + +static INLINE size_t +connection_get_inbuf_len(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + return evbuffer_get_length(bufferevent_get_input(conn->bufev)); + }) ELSE_IF_NO_BUFFEREVENT { + return conn->inbuf ? buf_datalen(conn->inbuf) : 0; + } +} + +static INLINE size_t +connection_get_outbuf_len(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + return evbuffer_get_length(bufferevent_get_output(conn->bufev)); + }) ELSE_IF_NO_BUFFEREVENT { + return conn->outbuf ? buf_datalen(conn->outbuf) : 0; + } +} + connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); @@ -83,6 +137,8 @@ connection_t *connection_get_by_type_addr_port_purpose(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +dir_connection_t *connection_dir_get_by_purpose_and_resource( + int state, const char *resource); #define connection_speaks_cells(conn) ((conn)->type == CONN_TYPE_OR) int connection_is_listener(connection_t *conn); @@ -96,5 +152,19 @@ int connection_or_nonopen_was_started_here(or_connection_t *conn); void connection_dump_buffer_mem_stats(int severity); void remove_file_if_very_old(const char *fname, time_t now); +#ifdef USE_BUFFEREVENTS +int connection_type_uses_bufferevent(connection_t *conn); +void connection_configure_bufferevent_callbacks(connection_t *conn); +void connection_handle_read_cb(struct bufferevent *bufev, void *arg); +void connection_handle_write_cb(struct bufferevent *bufev, void *arg); +void connection_handle_event_cb(struct bufferevent *bufev, short event, + void *arg); +void connection_get_rate_limit_totals(uint64_t *read_out, + uint64_t *written_out); +void connection_enable_rate_limiting(connection_t *conn); +#else +#define connection_type_uses_bufferevent(c) (0) +#endif + #endif diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 4763bf59a2..efaad79b6a 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -23,6 +23,7 @@ #include "dirserv.h" #include "hibernate.h" #include "main.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -50,27 +51,43 @@ #define SOCKS4_GRANTED 90 #define SOCKS4_REJECT 91 -static int connection_ap_handshake_process_socks(edge_connection_t *conn); -static int connection_ap_process_natd(edge_connection_t *conn); +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(edge_connection_t *conn, uint16_t port); +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 * a socks reply, send one now (based on endreason). Also set * has_sent_end to 1, and mark the conn. */ void -_connection_mark_unattached_ap(edge_connection_t *conn, int endreason, +_connection_mark_unattached_ap(entry_connection_t *conn, int endreason, int line, const char *file) { - tor_assert(conn->_base.type == CONN_TYPE_AP); - conn->edge_has_sent_end = 1; /* no circ yet */ - - if (conn->_base.marked_for_close) { + connection_t *base_conn = ENTRY_TO_CONN(conn); + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + tor_assert(base_conn->type == CONN_TYPE_AP); + ENTRY_TO_EDGE_CONN(conn)->edge_has_sent_end = 1; /* no circ yet */ + + /* If this is a rendezvous stream and it is failing without ever + * being attached to a circuit, assume that an attempt to connect to + * the destination hidden service has just ended. + * + * XXX023 This condition doesn't limit to only streams failing + * without ever being attached. That sloppiness should be harmless, + * but we should fix it someday anyway. */ + if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) && + connection_edge_is_rendezvous_stream(edge_conn)) { + rend_client_note_connection_attempt_ended( + edge_conn->rend_data->onion_address); + } + + if (base_conn->marked_for_close) { /* This call will warn as appropriate. */ - _connection_mark_for_close(TO_CONN(conn), line, file); + _connection_mark_for_close(base_conn, line, file); return; } @@ -90,9 +107,9 @@ _connection_mark_unattached_ap(edge_connection_t *conn, int endreason, conn->socks_request->has_finished = 1; } - _connection_mark_for_close(TO_CONN(conn), line, file); - conn->_base.hold_open_until_flushed = 1; - conn->end_reason = endreason; + _connection_mark_and_flush(base_conn, line, file); + + ENTRY_TO_EDGE_CONN(conn)->end_reason = endreason; } /** There was an EOF. Send an end and mark the connection for close. @@ -100,7 +117,7 @@ _connection_mark_unattached_ap(edge_connection_t *conn, int endreason, int connection_edge_reached_eof(edge_connection_t *conn) { - if (buf_datalen(conn->_base.inbuf) && + if (connection_get_inbuf_len(TO_CONN(conn)) && connection_state_is_open(TO_CONN(conn))) { /* it still has stuff to process. don't let it die yet. */ return 0; @@ -110,8 +127,11 @@ connection_edge_reached_eof(edge_connection_t *conn) /* 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->socks_request) /* eof, so don't send a socks reply back */ - conn->socks_request->has_finished = 1; + 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; + } connection_mark_for_close(TO_CONN(conn)); } return 0; @@ -134,13 +154,13 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) switch (conn->_base.state) { case AP_CONN_STATE_SOCKS_WAIT: - if (connection_ap_handshake_process_socks(conn) < 0) { + if (connection_ap_handshake_process_socks(EDGE_TO_ENTRY_CONN(conn)) <0) { /* already marked */ return -1; } return 0; case AP_CONN_STATE_NATD_WAIT: - if (connection_ap_process_natd(conn) < 0) { + if (connection_ap_process_natd(EDGE_TO_ENTRY_CONN(conn)) < 0) { /* already marked */ return -1; } @@ -153,10 +173,26 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_CONNECT_WAIT: + if (connection_ap_supports_optimistic_data(EDGE_TO_ENTRY_CONN(conn))) { + 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), + package_partial, + (long)connection_get_inbuf_len(TO_CONN(conn))); + if (connection_edge_package_raw_inbuf(conn, package_partial, NULL)<0) { + /* (We already sent an end cell if possible) */ + connection_mark_for_close(TO_CONN(conn)); + return -1; + } + return 0; + } + /* Fall through if the connection is on a circuit without optimistic + * data support. */ case EXIT_CONN_STATE_CONNECTING: case AP_CONN_STATE_RENDDESC_WAIT: case AP_CONN_STATE_CIRCUIT_WAIT: - case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_RESOLVE_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: log_info(LD_EDGE, @@ -181,9 +217,10 @@ connection_edge_destroy(circid_t circ_id, edge_connection_t *conn) log_info(LD_EDGE, "CircID %d: At an edge. Marking connection for close.", circ_id); if (conn->_base.type == CONN_TYPE_AP) { - connection_mark_unattached_ap(conn, END_STREAM_REASON_DESTROY); + 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); - control_event_stream_status(conn, STREAM_EVENT_CLOSED, + control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, END_STREAM_REASON_DESTROY); conn->end_reason |= END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED; } else { @@ -191,8 +228,7 @@ connection_edge_destroy(circid_t circ_id, edge_connection_t *conn) conn->edge_has_sent_end = 1; conn->end_reason = END_STREAM_REASON_DESTROY; conn->end_reason |= END_STREAM_REASON_FLAG_ALREADY_SENT_CLOSED; - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } } conn->cpath_layer = NULL; @@ -336,7 +372,6 @@ connection_edge_finished_flushing(edge_connection_t *conn) switch (conn->_base.state) { case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: - connection_stop_writing(TO_CONN(conn)); connection_edge_consider_sending_sendme(conn); return 0; case AP_CONN_STATE_SOCKS_WAIT: @@ -345,7 +380,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CIRCUIT_WAIT: case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: - connection_stop_writing(TO_CONN(conn)); + case AP_CONN_STATE_RESOLVE_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->_base.state); @@ -375,8 +410,9 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) rep_hist_note_exit_stream_opened(conn->port); conn->state = EXIT_CONN_STATE_OPEN; - connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */ - if (connection_wants_to_flush(conn)) /* in case there are any queued relay + IF_HAS_NO_BUFFEREVENT(conn) + connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */ + if (connection_get_outbuf_len(conn)) /* in case there are any queued relay * cells */ connection_start_writing(conn); /* deliver a 'connected' relay cell back through the circuit. */ @@ -408,13 +444,79 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) return connection_edge_process_inbuf(edge_conn, 1); } +/** Common code to connection_(ap|exit)_about_to_close. */ +static void +connection_edge_about_to_close(edge_connection_t *edge_conn) +{ + if (!edge_conn->edge_has_sent_end) { + connection_t *conn = TO_CONN(edge_conn); + log_warn(LD_BUG, "(Harmless.) Edge connection (marked at %s:%d) " + "hasn't sent end yet?", + conn->marked_for_close_file, conn->marked_for_close); + tor_fragile_assert(); + } +} + +/* Called when we're about to finally unlink and free an AP (client) + * connection: perform necessary accounting and cleanup */ +void +connection_ap_about_to_close(entry_connection_t *entry_conn) +{ + circuit_t *circ; + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + connection_t *conn = ENTRY_TO_CONN(entry_conn); + + if (entry_conn->socks_request->has_finished == 0) { + /* since conn gets removed right after this function finishes, + * there's no point trying to send back a reply at this point. */ + log_warn(LD_BUG,"Closing stream (marked at %s:%d) without sending" + " back a socks reply.", + conn->marked_for_close_file, conn->marked_for_close); + } + if (!edge_conn->end_reason) { + log_warn(LD_BUG,"Closing stream (marked at %s:%d) without having" + " set end_reason.", + conn->marked_for_close_file, conn->marked_for_close); + } + if (entry_conn->dns_server_request) { + log_warn(LD_BUG,"Closing stream (marked at %s:%d) without having" + " replied to DNS request.", + conn->marked_for_close_file, conn->marked_for_close); + dnsserv_reject_request(entry_conn); + } + control_event_stream_bandwidth(edge_conn); + control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, + edge_conn->end_reason); + circ = circuit_get_by_edge_conn(edge_conn); + if (circ) + circuit_detach_stream(circ, edge_conn); +} + +/* Called when we're about to finally unlink and free an exit + * connection: perform necessary accounting and cleanup */ +void +connection_exit_about_to_close(edge_connection_t *edge_conn) +{ + circuit_t *circ; + connection_t *conn = TO_CONN(edge_conn); + + connection_edge_about_to_close(edge_conn); + + circ = circuit_get_by_edge_conn(edge_conn); + if (circ) + circuit_detach_stream(circ, edge_conn); + if (conn->state == EXIT_CONN_STATE_RESOLVING) { + connection_dns_remove(edge_conn); + } +} + /** Define a schedule for how long to wait between retrying * application connections. Rather than waiting a fixed amount of * time between each retry, we wait 10 seconds each for the first * two tries, and 15 seconds for each retry after * that. Hopefully this will improve the expected user experience. */ static int -compute_retry_timeout(edge_connection_t *conn) +compute_retry_timeout(entry_connection_t *conn) { int timeout = get_options()->CircuitStreamTimeout; if (timeout) /* if our config options override the default, use them */ @@ -437,41 +539,43 @@ void connection_ap_expire_beginning(void) { edge_connection_t *conn; + entry_connection_t *entry_conn; circuit_t *circ; time_t now = time(NULL); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int severity; int cutoff; int seconds_idle, seconds_since_born; smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, c) { - if (c->type != CONN_TYPE_AP || c->marked_for_close) + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + if (base_conn->type != CONN_TYPE_AP || base_conn->marked_for_close) continue; - conn = TO_EDGE_CONN(c); + entry_conn = TO_ENTRY_CONN(base_conn); + conn = ENTRY_TO_EDGE_CONN(entry_conn); /* if it's an internal linked connection, don't yell its status. */ - severity = (tor_addr_is_null(&conn->_base.addr) && !conn->_base.port) + severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ? LOG_INFO : LOG_NOTICE; - seconds_idle = (int)( now - conn->_base.timestamp_lastread ); - seconds_since_born = (int)( now - conn->_base.timestamp_created ); + seconds_idle = (int)( now - base_conn->timestamp_lastread ); + seconds_since_born = (int)( now - base_conn->timestamp_created ); - if (conn->_base.state == AP_CONN_STATE_OPEN) + if (base_conn->state == AP_CONN_STATE_OPEN) continue; /* We already consider SocksTimeout in * connection_ap_handshake_attach_circuit(), but we need to consider * it here too because controllers that put streams in controller_wait * state never ask Tor to attach the circuit. */ - if (AP_CONN_STATE_IS_UNATTACHED(conn->_base.state)) { + if (AP_CONN_STATE_IS_UNATTACHED(base_conn->state)) { if (seconds_since_born >= options->SocksTimeout) { log_fn(severity, LD_APP, "Tried for %d seconds to get a connection to %s:%d. " "Giving up. (%s)", seconds_since_born, - safe_str_client(conn->socks_request->address), - conn->socks_request->port, - conn_state_to_string(CONN_TYPE_AP, conn->_base.state)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT); + safe_str_client(entry_conn->socks_request->address), + entry_conn->socks_request->port, + conn_state_to_string(CONN_TYPE_AP, base_conn->state)); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TIMEOUT); } continue; } @@ -479,14 +583,14 @@ connection_ap_expire_beginning(void) /* We're in state connect_wait or resolve_wait now -- waiting for a * reply to our relay cell. See if we want to retry/give up. */ - cutoff = compute_retry_timeout(conn); + cutoff = compute_retry_timeout(entry_conn); if (seconds_idle < cutoff) continue; circ = circuit_get_by_edge_conn(conn); if (!circ) { /* it's vanished? */ log_info(LD_APP,"Conn is waiting (address %s), but lost its circ.", - safe_str_client(conn->socks_request->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT); + safe_str_client(entry_conn->socks_request->address)); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TIMEOUT); continue; } if (circ->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { @@ -495,9 +599,9 @@ connection_ap_expire_beginning(void) "Rend stream is %d seconds late. Giving up on address" " '%s.onion'.", seconds_idle, - safe_str_client(conn->socks_request->address)); + safe_str_client(entry_conn->socks_request->address)); connection_edge_end(conn, END_STREAM_REASON_TIMEOUT); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TIMEOUT); } continue; } @@ -506,9 +610,10 @@ connection_ap_expire_beginning(void) "We tried for %d seconds to connect to '%s' using exit %s." " Retrying on a new circuit.", seconds_idle, - safe_str_client(conn->socks_request->address), + safe_str_client(entry_conn->socks_request->address), conn->cpath_layer ? - extend_info_describe(conn->cpath_layer->extend_info): "*unnamed*"); + extend_info_describe(conn->cpath_layer->extend_info): + "*unnamed*"); /* send an end down the circuit */ connection_edge_end(conn, END_STREAM_REASON_TIMEOUT); /* un-mark it as ending, since we're going to reuse it */ @@ -522,15 +627,16 @@ connection_ap_expire_beginning(void) circ->timestamp_dirty -= options->MaxCircuitDirtiness; /* give our stream another 'cutoff' seconds to try */ conn->_base.timestamp_lastread += cutoff; - if (conn->num_socks_retries < 250) /* avoid overflow */ - conn->num_socks_retries++; + if (entry_conn->num_socks_retries < 250) /* avoid overflow */ + entry_conn->num_socks_retries++; /* move it back into 'pending' state, and try to attach. */ - if (connection_ap_detach_retriable(conn, TO_ORIGIN_CIRCUIT(circ), + if (connection_ap_detach_retriable(entry_conn, TO_ORIGIN_CIRCUIT(circ), END_STREAM_REASON_TIMEOUT)<0) { - if (!conn->_base.marked_for_close) - connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); + if (!base_conn->marked_for_close) + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_CANT_ATTACH); } - } SMARTLIST_FOREACH_END(conn); + } SMARTLIST_FOREACH_END(base_conn); } /** Tell any AP streams that are waiting for a new circuit to try again, @@ -539,7 +645,7 @@ connection_ap_expire_beginning(void) void connection_ap_attach_pending(void) { - edge_connection_t *edge_conn; + entry_connection_t *entry_conn; smartlist_t *conns = get_connection_array(); SMARTLIST_FOREACH(conns, connection_t *, conn, { @@ -547,10 +653,10 @@ connection_ap_attach_pending(void) conn->type != CONN_TYPE_AP || conn->state != AP_CONN_STATE_CIRCUIT_WAIT) continue; - edge_conn = TO_EDGE_CONN(conn); - if (connection_ap_handshake_attach_circuit(edge_conn) < 0) { - if (!edge_conn->_base.marked_for_close) - connection_mark_unattached_ap(edge_conn, + entry_conn = TO_ENTRY_CONN(conn); + if (connection_ap_handshake_attach_circuit(entry_conn) < 0) { + if (!conn->marked_for_close) + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_CANT_ATTACH); } }); @@ -565,7 +671,7 @@ void connection_ap_fail_onehop(const char *failed_digest, cpath_build_state_t *build_state) { - edge_connection_t *edge_conn; + entry_connection_t *entry_conn; char digest[DIGEST_LEN]; smartlist_t *conns = get_connection_array(); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -573,27 +679,27 @@ connection_ap_fail_onehop(const char *failed_digest, conn->type != CONN_TYPE_AP || conn->state != AP_CONN_STATE_CIRCUIT_WAIT) continue; - edge_conn = TO_EDGE_CONN(conn); - if (!edge_conn->want_onehop) + entry_conn = TO_ENTRY_CONN(conn); + if (!entry_conn->want_onehop) continue; - if (hexdigest_to_digest(edge_conn->chosen_exit_name, digest) < 0 || + if (hexdigest_to_digest(entry_conn->chosen_exit_name, digest) < 0 || tor_memneq(digest, failed_digest, DIGEST_LEN)) continue; if (tor_digest_is_zero(digest)) { /* we don't know the digest; have to compare addr:port */ tor_addr_t addr; if (!build_state || !build_state->chosen_exit || - !edge_conn->socks_request || !edge_conn->socks_request->address) + !entry_conn->socks_request || !entry_conn->socks_request->address) continue; - if (tor_addr_from_str(&addr, edge_conn->socks_request->address)<0 || + if (tor_addr_parse(&addr, entry_conn->socks_request->address)<0 || !tor_addr_eq(&build_state->chosen_exit->addr, &addr) || - build_state->chosen_exit->port != edge_conn->socks_request->port) + build_state->chosen_exit->port != entry_conn->socks_request->port) continue; } log_info(LD_APP, "Closing one-hop stream to '%s/%s' because the OR conn " - "just failed.", edge_conn->chosen_exit_name, - edge_conn->socks_request->address); - connection_mark_unattached_ap(edge_conn, END_STREAM_REASON_TIMEOUT); + "just failed.", entry_conn->chosen_exit_name, + entry_conn->socks_request->address); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TIMEOUT); } SMARTLIST_FOREACH_END(conn); } @@ -604,8 +710,8 @@ connection_ap_fail_onehop(const char *failed_digest, void circuit_discard_optional_exit_enclaves(extend_info_t *info) { - edge_connection_t *edge_conn; - routerinfo_t *r1, *r2; + entry_connection_t *entry_conn; + const node_t *r1, *r2; smartlist_t *conns = get_connection_array(); SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -613,32 +719,32 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) conn->type != CONN_TYPE_AP || conn->state != AP_CONN_STATE_CIRCUIT_WAIT) continue; - edge_conn = TO_EDGE_CONN(conn); - if (!edge_conn->chosen_exit_optional && - !edge_conn->chosen_exit_retries) + entry_conn = TO_ENTRY_CONN(conn); + if (!entry_conn->chosen_exit_optional && + !entry_conn->chosen_exit_retries) continue; - r1 = router_get_by_nickname(edge_conn->chosen_exit_name, 0); - r2 = router_get_by_digest(info->identity_digest); + r1 = node_get_by_nickname(entry_conn->chosen_exit_name, 0); + r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; - tor_assert(edge_conn->socks_request); - if (edge_conn->chosen_exit_optional) { + tor_assert(entry_conn->socks_request); + if (entry_conn->chosen_exit_optional) { log_info(LD_APP, "Giving up on enclave exit '%s' for destination %s.", - safe_str_client(edge_conn->chosen_exit_name), - escaped_safe_str_client(edge_conn->socks_request->address)); - edge_conn->chosen_exit_optional = 0; - tor_free(edge_conn->chosen_exit_name); /* clears it */ + safe_str_client(entry_conn->chosen_exit_name), + escaped_safe_str_client(entry_conn->socks_request->address)); + entry_conn->chosen_exit_optional = 0; + tor_free(entry_conn->chosen_exit_name); /* clears it */ /* if this port is dangerous, warn or reject it now that we don't * think it'll be using an enclave. */ - consider_plaintext_ports(edge_conn, edge_conn->socks_request->port); + consider_plaintext_ports(entry_conn, entry_conn->socks_request->port); } - if (edge_conn->chosen_exit_retries) { - if (--edge_conn->chosen_exit_retries == 0) { /* give up! */ - clear_trackexithost_mappings(edge_conn->chosen_exit_name); - tor_free(edge_conn->chosen_exit_name); /* clears it */ + if (entry_conn->chosen_exit_retries) { + if (--entry_conn->chosen_exit_retries == 0) { /* give up! */ + clear_trackexithost_mappings(entry_conn->chosen_exit_name); + tor_free(entry_conn->chosen_exit_name); /* clears it */ /* if this port is dangerous, warn or reject it now that we don't * think it'll be using an enclave. */ - consider_plaintext_ports(edge_conn, edge_conn->socks_request->port); + consider_plaintext_ports(entry_conn, entry_conn->socks_request->port); } } } SMARTLIST_FOREACH_END(conn); @@ -652,20 +758,27 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) * Returns -1 on err, 1 on success, 0 on not-yet-sure. */ int -connection_ap_detach_retriable(edge_connection_t *conn, origin_circuit_t *circ, +connection_ap_detach_retriable(entry_connection_t *conn, + origin_circuit_t *circ, int reason) { control_event_stream_status(conn, STREAM_EVENT_FAILED_RETRIABLE, reason); - conn->_base.timestamp_lastread = time(NULL); + ENTRY_TO_CONN(conn)->timestamp_lastread = time(NULL); + + if (conn->pending_optimistic_data) { + generic_buffer_set_to_copy(&conn->sending_optimistic_data, + conn->pending_optimistic_data); + } + if (!get_options()->LeaveStreamsUnattached || conn->use_begindir) { /* If we're attaching streams ourself, or if this connection is * a tunneled directory connection, then just attach it. */ - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; - circuit_detach_stream(TO_CIRCUIT(circ),conn); + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CIRCUIT_WAIT; + circuit_detach_stream(TO_CIRCUIT(circ),ENTRY_TO_EDGE_CONN(conn)); return connection_ap_handshake_attach_circuit(conn); } else { - conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT; - circuit_detach_stream(TO_CIRCUIT(circ),conn); + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; + circuit_detach_stream(TO_CIRCUIT(circ),ENTRY_TO_EDGE_CONN(conn)); return 0; } } @@ -813,7 +926,7 @@ clear_trackexithost_mappings(const char *exitname) * host is unknown or no longer allowed, or for which the source address * is no longer in trackexithosts. */ void -addressmap_clear_excluded_trackexithosts(or_options_t *options) +addressmap_clear_excluded_trackexithosts(const or_options_t *options) { const routerset_t *allow_nodes = options->ExitNodes; const routerset_t *exclude_nodes = options->_ExcludeExitNodesUnion; @@ -829,7 +942,7 @@ addressmap_clear_excluded_trackexithosts(or_options_t *options) size_t len; const char *target = ent->new_address, *dot; char *nodename; - routerinfo_t *ri; /* XXX023 Use node_t. */ + const node_t *node; if (!target) { /* DNS resolving in progress */ @@ -849,11 +962,11 @@ addressmap_clear_excluded_trackexithosts(or_options_t *options) dot--; if (*dot == '.') dot++; nodename = tor_strndup(dot, len-5-(dot-target));; - ri = router_get_by_nickname(nodename, 0); + node = node_get_by_nickname(nodename, 0); tor_free(nodename); - if (!ri || - (allow_nodes && !routerset_contains_router(allow_nodes, ri)) || - routerset_contains_router(exclude_nodes, ri) || + 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); @@ -867,7 +980,7 @@ addressmap_clear_excluded_trackexithosts(or_options_t *options) * no longer allowed by AutomapHostsOnResolve, or for which the * target address is no longer in the virtual network. */ void -addressmap_clear_invalid_automaps(or_options_t *options) +addressmap_clear_invalid_automaps(const or_options_t *options) { int clear_all = !options->AutomapHostsOnResolve; const smartlist_t *suffixes = options->AutomapHostsSuffixes; @@ -1314,7 +1427,6 @@ static char * addressmap_get_virtual_address(int type) { char buf[64]; - struct in_addr in; tor_assert(addressmap); if (type == RESOLVED_TYPE_HOSTNAME) { @@ -1328,6 +1440,7 @@ addressmap_get_virtual_address(int type) } 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. */ @@ -1519,9 +1632,9 @@ addressmap_get_mappings(smartlist_t *sl, time_t min_expires, /** Check if <b>conn</b> is using a dangerous port. Then warn and/or * reject depending on our config options. */ static int -consider_plaintext_ports(edge_connection_t *conn, uint16_t port) +consider_plaintext_ports(entry_connection_t *conn, uint16_t port) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int reject = smartlist_string_num_isin(options->RejectPlaintextPorts, port); if (smartlist_string_num_isin(options->WarnPlaintextPorts, port)) { @@ -1554,14 +1667,14 @@ consider_plaintext_ports(edge_connection_t *conn, uint16_t port) * documentation for arguments and return value. */ int -connection_ap_rewrite_and_attach_if_allowed(edge_connection_t *conn, +connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->LeaveStreamsUnattached) { - conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; return 0; } return connection_ap_handshake_rewrite_and_attach(conn, circ, cpath); @@ -1583,13 +1696,13 @@ connection_ap_rewrite_and_attach_if_allowed(edge_connection_t *conn, * <b>cpath</b> is NULL. */ int -connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, +connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath) { socks_request_t *socks = conn->socks_request; hostname_type_t addresstype; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); struct in_addr addr_tmp; /* We set this to true if this is an address we should automatically * remap to a local address in VirtualAddrNetwork */ @@ -1600,6 +1713,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, address, and we remap it to one because of an entry in the addressmap. */ int remapped_to_exit = 0; time_t now = time(NULL); + connection_t *base_conn = ENTRY_TO_CONN(conn); tor_strlower(socks->address); /* normalize it */ strlcpy(orig_address, socks->address, sizeof(orig_address)); @@ -1607,6 +1721,9 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, safe_str_client(socks->address), socks->port); + if (! conn->original_dest_address) + 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) { @@ -1652,7 +1769,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, /* Don't let people try to do a reverse lookup on 10.0.0.1. */ tor_addr_t addr; int ok; - ok = tor_addr_parse_reverse_lookup_name( + ok = tor_addr_parse_PTR_name( &addr, socks->address, AF_UNSPEC, 1); if (ok == 1 && tor_addr_is_internal(&addr, 0)) { connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_ERROR, @@ -1712,15 +1829,14 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */ routerset_t *excludeset = options->StrictNodes ? options->_ExcludeExitNodesUnion : options->ExcludeExitNodes; - /*XXX023 make this a node_t. */ - routerinfo_t *router; + const node_t *node; tor_assert(!automap); if (s) { /* The address was of the form "(stuff).(name).exit */ if (s[1] != '\0') { conn->chosen_exit_name = tor_strdup(s+1); - router = router_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 1); if (remapped_to_exit) /* 5 tries before it expires the addressmap */ conn->chosen_exit_retries = TRACKHOSTEXITS_RETRIES; *s = 0; @@ -1735,15 +1851,16 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, } } else { /* It looks like they just asked for "foo.exit". */ + conn->chosen_exit_name = tor_strdup(socks->address); - router = router_get_by_nickname(conn->chosen_exit_name, 1); - if (router) { + node = node_get_by_nickname(conn->chosen_exit_name, 1); + if (node) { *socks->address = 0; - strlcpy(socks->address, router->address, sizeof(socks->address)); + node_get_address_string(node, socks->address, sizeof(socks->address)); } } /* Now make sure that the chosen exit exists... */ - if (!router) { + if (!node) { log_warn(LD_APP, "Unrecognized relay in exit address '%s.exit'. Refusing.", safe_str_client(socks->address)); @@ -1751,7 +1868,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return -1; } /* ...and make sure that it isn't excluded. */ - if (routerset_contains_router(excludeset, router)) { + if (routerset_contains_node(excludeset, node)) { log_warn(LD_APP, "Excluded relay in exit address '%s.exit'. Refusing.", safe_str_client(socks->address)); @@ -1804,7 +1921,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, if (options->ClientRejectInternalAddresses && !conn->use_begindir && !conn->chosen_exit_name && !circ) { tor_addr_t addr; - if (tor_addr_from_str(&addr, socks->address) >= 0 && + if (tor_addr_parse(&addr, socks->address) >= 0 && tor_addr_is_internal(&addr, 0)) { /* If this is an explicit private address with no chosen exit node, * then we really don't want to try to connect to it. That's @@ -1826,17 +1943,16 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ - routerinfo_t *r = + const node_t *r = router_find_exact_exit_enclave(socks->address, socks->port); if (r) { log_info(LD_APP, "Redirecting address %s to exit at enclave router %s", - safe_str_client(socks->address), - router_describe(r)); + safe_str_client(socks->address), node_describe(r)); /* use the hex digest, not nickname, in case there are two routers with this nickname */ conn->chosen_exit_name = - tor_strdup(hex_str(r->cache_info.identity_digest, DIGEST_LEN)); + tor_strdup(hex_str(r->identity, DIGEST_LEN)); conn->chosen_exit_optional = 1; } } @@ -1856,12 +1972,12 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, } else { tor_fragile_assert(); } - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; if ((circ && connection_ap_handshake_attach_chosen_circuit( conn, circ, cpath) < 0) || (!circ && connection_ap_handshake_attach_circuit(conn) < 0)) { - if (!conn->_base.marked_for_close) + if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); return -1; } @@ -1871,6 +1987,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, rend_cache_entry_t *entry; int r; rend_service_authorization_t *client_auth; + rend_data_t *rend_data; tor_assert(!automap); if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { /* if it's a resolve request, fail it right now, rather than @@ -1892,16 +2009,17 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, return -1; } - conn->rend_data = tor_malloc_zero(sizeof(rend_data_t)); - strlcpy(conn->rend_data->onion_address, socks->address, - sizeof(conn->rend_data->onion_address)); + ENTRY_TO_EDGE_CONN(conn)->rend_data = rend_data = + tor_malloc_zero(sizeof(rend_data_t)); + strlcpy(rend_data->onion_address, socks->address, + sizeof(rend_data->onion_address)); log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(conn->rend_data->onion_address)); + safe_str_client(rend_data->onion_address)); /* see if we already have it cached */ - r = rend_cache_lookup_entry(conn->rend_data->onion_address, -1, &entry); + r = rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); if (r<0) { log_warn(LD_BUG,"Invalid service name '%s'", - safe_str_client(conn->rend_data->onion_address)); + safe_str_client(rend_data->onion_address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return -1; } @@ -1912,24 +2030,24 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, /* Look up if we have client authorization for it. */ client_auth = rend_client_lookup_service_authorization( - conn->rend_data->onion_address); + rend_data->onion_address); if (client_auth) { log_info(LD_REND, "Using previously configured client authorization " "for hidden service request."); - memcpy(conn->rend_data->descriptor_cookie, + memcpy(rend_data->descriptor_cookie, client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN); - conn->rend_data->auth_type = client_auth->auth_type; + rend_data->auth_type = client_auth->auth_type; } if (r==0) { - conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT; + base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(conn->rend_data->onion_address)); - rend_client_refetch_v2_renddesc(conn->rend_data); + safe_str_client(rend_data->onion_address)); + rend_client_refetch_v2_renddesc(rend_data); } else { /* r > 0 */ - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; log_info(LD_REND, "Descriptor is here. Great."); if (connection_ap_handshake_attach_circuit(conn) < 0) { - if (!conn->_base.marked_for_close) + if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); return -1; } @@ -1951,10 +2069,10 @@ get_pf_socket(void) #ifdef OPENBSD /* only works on OpenBSD */ - pf = open("/dev/pf", O_RDONLY); + pf = tor_open_cloexec("/dev/pf", O_RDONLY, 0); #else /* works on NetBSD and FreeBSD */ - pf = open("/dev/pf", O_RDWR); + pf = tor_open_cloexec("/dev/pf", O_RDWR, 0); #endif if (pf < 0) { @@ -1975,7 +2093,7 @@ get_pf_socket(void) * else return 0. */ static int -connection_ap_get_original_destination(edge_connection_t *conn, +connection_ap_get_original_destination(entry_connection_t *conn, socks_request_t *req) { #ifdef TRANS_NETFILTER @@ -1984,9 +2102,9 @@ connection_ap_get_original_destination(edge_connection_t *conn, socklen_t orig_dst_len = sizeof(orig_dst); tor_addr_t addr; - if (getsockopt(conn->_base.s, SOL_IP, SO_ORIGINAL_DST, + if (getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len) < 0) { - int e = tor_socket_errno(conn->_base.s); + int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s); log_warn(LD_NET, "getsockopt() failed: %s", tor_socket_strerror(e)); return -1; } @@ -2003,9 +2121,9 @@ connection_ap_get_original_destination(edge_connection_t *conn, tor_addr_t addr; int pf = -1; - if (getsockname(conn->_base.s, (struct sockaddr*)&proxy_addr, + if (getsockname(ENTRY_TO_CONN(conn)->s, (struct sockaddr*)&proxy_addr, &proxy_addr_len) < 0) { - int e = tor_socket_errno(conn->_base.s); + int e = tor_socket_errno(ENTRY_TO_CONN(conn)->s); log_warn(LD_NET, "getsockname() to determine transocks destination " "failed: %s", tor_socket_strerror(e)); return -1; @@ -2017,16 +2135,16 @@ connection_ap_get_original_destination(edge_connection_t *conn, if (proxy_sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)proxy_sa; pnl.af = AF_INET; - pnl.saddr.v4.s_addr = tor_addr_to_ipv4n(&conn->_base.addr); - pnl.sport = htons(conn->_base.port); + pnl.saddr.v4.s_addr = tor_addr_to_ipv4n(&ENTRY_TO_CONN(conn)->addr); + pnl.sport = htons(ENTRY_TO_CONN(conn)->port); pnl.daddr.v4.s_addr = sin->sin_addr.s_addr; pnl.dport = sin->sin_port; } else if (proxy_sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)proxy_sa; pnl.af = AF_INET6; - memcpy(&pnl.saddr.v6, tor_addr_to_in6(&conn->_base.addr), + memcpy(&pnl.saddr.v6, tor_addr_to_in6(&ENTRY_TO_CONN(conn)->addr), sizeof(struct in6_addr)); - pnl.sport = htons(conn->_base.port); + pnl.sport = htons(ENTRY_TO_CONN(conn)->port); memcpy(&pnl.daddr.v6, &sin6->sin6_addr, sizeof(struct in6_addr)); pnl.dport = sin6->sin6_port; } else { @@ -2077,38 +2195,43 @@ connection_ap_get_original_destination(edge_connection_t *conn, * else return 0. */ static int -connection_ap_handshake_process_socks(edge_connection_t *conn) +connection_ap_handshake_process_socks(entry_connection_t *conn) { socks_request_t *socks; int sockshere; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); + int had_reply = 0; + connection_t *base_conn = ENTRY_TO_CONN(conn); tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_AP); - tor_assert(conn->_base.state == AP_CONN_STATE_SOCKS_WAIT); + tor_assert(base_conn->type == CONN_TYPE_AP); + tor_assert(base_conn->state == AP_CONN_STATE_SOCKS_WAIT); tor_assert(conn->socks_request); socks = conn->socks_request; log_debug(LD_APP,"entered."); - sockshere = fetch_from_buf_socks(conn->_base.inbuf, socks, - options->TestSocks, options->SafeSocks); + IF_HAS_BUFFEREVENT(base_conn, { + struct evbuffer *input = bufferevent_get_input(base_conn->bufev); + sockshere = fetch_from_evbuffer_socks(input, socks, + options->TestSocks, options->SafeSocks); + }) ELSE_IF_NO_BUFFEREVENT { + sockshere = fetch_from_buf_socks(base_conn->inbuf, socks, + options->TestSocks, options->SafeSocks); + }; + + if (socks->replylen) { + had_reply = 1; + connection_write_to_buf((const char*)socks->reply, socks->replylen, + base_conn); + socks->replylen = 0; + } + if (sockshere == 0) { - if (socks->replylen) { - connection_write_to_buf(socks->reply, socks->replylen, TO_CONN(conn)); - /* zero it out so we can do another round of negotiation */ - socks->replylen = 0; - } else { - log_debug(LD_APP,"socks handshake not all here yet."); - } + log_debug(LD_APP,"socks handshake not all here yet."); return 0; } else if (sockshere == -1) { - if (socks->replylen) { /* we should send reply back */ - log_debug(LD_APP,"reply is already set for us. Using it."); - connection_ap_handshake_socks_reply(conn, socks->reply, socks->replylen, - END_STREAM_REASON_SOCKSPROTOCOL); - - } else { + if (!had_reply) { log_warn(LD_APP,"Fetching socks handshake failed. Closing."); connection_ap_handshake_socks_reply(conn, NULL, 0, END_STREAM_REASON_SOCKSPROTOCOL); @@ -2135,12 +2258,11 @@ connection_ap_handshake_process_socks(edge_connection_t *conn) * for close), else return 0. */ int -connection_ap_process_transparent(edge_connection_t *conn) +connection_ap_process_transparent(entry_connection_t *conn) { socks_request_t *socks; tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_AP); tor_assert(conn->socks_request); socks = conn->socks_request; @@ -2176,7 +2298,7 @@ connection_ap_process_transparent(edge_connection_t *conn) * for close), else return 0. */ static int -connection_ap_process_natd(edge_connection_t *conn) +connection_ap_process_natd(entry_connection_t *conn) { char tmp_buf[36], *tbuf, *daddr; size_t tlen = 30; @@ -2184,8 +2306,7 @@ connection_ap_process_natd(edge_connection_t *conn) socks_request_t *socks; tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_AP); - tor_assert(conn->_base.state == AP_CONN_STATE_NATD_WAIT); + tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_NATD_WAIT); tor_assert(conn->socks_request); socks = conn->socks_request; @@ -2193,7 +2314,7 @@ connection_ap_process_natd(edge_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = fetch_from_buf_line(conn->_base.inbuf, tmp_buf, &tlen); + err = connection_fetch_from_buf_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2237,7 +2358,7 @@ connection_ap_process_natd(edge_connection_t *conn) control_event_stream_status(conn, STREAM_EVENT_NEW, 0); - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CIRCUIT_WAIT; return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } @@ -2267,28 +2388,46 @@ get_unique_stream_id_by_circ(origin_circuit_t *circ) return test_stream_id; } +/** Return true iff <b>conn</b> is linked to a circuit and configured to use + * an exit that supports optimistic data. */ +static int +connection_ap_supports_optimistic_data(const entry_connection_t *conn) +{ + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + /* We can only send optimistic data if we're connected to an open + general circuit. */ + if (edge_conn->on_circuit == NULL || + edge_conn->on_circuit->state != CIRCUIT_STATE_OPEN || + edge_conn->on_circuit->purpose != CIRCUIT_PURPOSE_C_GENERAL) + return 0; + + return conn->may_use_optimistic_data; +} + /** Write a relay begin cell, using destaddr and destport from ap_conn's * socks_request field, and send it down circ. * * If ap_conn is broken, mark it for close and return -1. Else return 0. */ int -connection_ap_handshake_send_begin(edge_connection_t *ap_conn) +connection_ap_handshake_send_begin(entry_connection_t *ap_conn) { char payload[CELL_PAYLOAD_SIZE]; int payload_len; int begin_type; origin_circuit_t *circ; - tor_assert(ap_conn->on_circuit); - circ = TO_ORIGIN_CIRCUIT(ap_conn->on_circuit); + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + connection_t *base_conn = TO_CONN(edge_conn); + tor_assert(edge_conn->on_circuit); + circ = TO_ORIGIN_CIRCUIT(edge_conn->on_circuit); - tor_assert(ap_conn->_base.type == CONN_TYPE_AP); - tor_assert(ap_conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); + 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(SOCKS_COMMAND_IS_CONNECT(ap_conn->socks_request->command)); - ap_conn->stream_id = get_unique_stream_id_by_circ(circ); - if (ap_conn->stream_id==0) { + edge_conn->stream_id = get_unique_stream_id_by_circ(circ); + if (edge_conn->stream_id==0) { /* XXXX023 Instead of closing this stream, we should make it get * retried on another circuit. */ connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL); @@ -2306,8 +2445,10 @@ connection_ap_handshake_send_begin(edge_connection_t *ap_conn) ap_conn->socks_request->port); payload_len = (int)strlen(payload)+1; - log_debug(LD_APP, - "Sending relay cell to begin stream %d.", ap_conn->stream_id); + log_info(LD_APP, + "Sending relay cell %d to begin stream %d.", + (int)ap_conn->use_begindir, + edge_conn->stream_id); begin_type = ap_conn->use_begindir ? RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN; @@ -2315,17 +2456,31 @@ connection_ap_handshake_send_begin(edge_connection_t *ap_conn) tor_assert(circ->build_state->onehop_tunnel == 0); } - if (connection_edge_send_command(ap_conn, begin_type, + if (connection_edge_send_command(edge_conn, begin_type, begin_type == RELAY_COMMAND_BEGIN ? payload : NULL, begin_type == RELAY_COMMAND_BEGIN ? payload_len : 0) < 0) return -1; /* circuit is closed, don't continue */ - ap_conn->package_window = STREAMWINDOW_START; - ap_conn->deliver_window = STREAMWINDOW_START; - ap_conn->_base.state = AP_CONN_STATE_CONNECT_WAIT; + 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", - ap_conn->_base.s, circ->_base.n_circ_id); + 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 */ + if ((connection_get_inbuf_len(base_conn) || + ap_conn->sending_optimistic_data) && + connection_ap_supports_optimistic_data(ap_conn)) { + log_info(LD_APP, "Sending up to %ld + %ld bytes of queued-up data", + (long)connection_get_inbuf_len(base_conn), + ap_conn->sending_optimistic_data ? + (long)generic_buffer_len(ap_conn->sending_optimistic_data) : 0); + if (connection_edge_package_raw_inbuf(edge_conn, 1, NULL) < 0) { + connection_mark_for_close(base_conn); + } + } + return 0; } @@ -2335,25 +2490,27 @@ connection_ap_handshake_send_begin(edge_connection_t *ap_conn) * If ap_conn is broken, mark it for close and return -1. Else return 0. */ int -connection_ap_handshake_send_resolve(edge_connection_t *ap_conn) +connection_ap_handshake_send_resolve(entry_connection_t *ap_conn) { int payload_len, command; const char *string_addr; char inaddr_buf[REVERSE_LOOKUP_NAME_BUF_LEN]; origin_circuit_t *circ; - tor_assert(ap_conn->on_circuit); - circ = TO_ORIGIN_CIRCUIT(ap_conn->on_circuit); + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + connection_t *base_conn = TO_CONN(edge_conn); + tor_assert(edge_conn->on_circuit); + circ = TO_ORIGIN_CIRCUIT(edge_conn->on_circuit); - tor_assert(ap_conn->_base.type == CONN_TYPE_AP); - tor_assert(ap_conn->_base.state == AP_CONN_STATE_CIRCUIT_WAIT); + 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); command = ap_conn->socks_request->command; tor_assert(SOCKS_COMMAND_IS_RESOLVE(command)); - ap_conn->stream_id = get_unique_stream_id_by_circ(circ); - if (ap_conn->stream_id==0) { + edge_conn->stream_id = get_unique_stream_id_by_circ(circ); + if (edge_conn->stream_id==0) { /* XXXX023 Instead of closing this stream, we should make it get * retried on another circuit. */ connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL); @@ -2376,7 +2533,7 @@ connection_ap_handshake_send_resolve(edge_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_reverse_lookup_name(&addr, a, AF_INET, 1); + r = tor_addr_parse_PTR_name(&addr, a, AF_INET, 1); if (r <= 0) { log_warn(LD_APP, "Rejecting ill-formed reverse lookup of %s", safe_str_client(a)); @@ -2384,7 +2541,7 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn) return -1; } - r = tor_addr_to_reverse_lookup_name(inaddr_buf, sizeof(inaddr_buf), &addr); + r = tor_addr_to_PTR_name(inaddr_buf, sizeof(inaddr_buf), &addr); if (r < 0) { log_warn(LD_BUG, "Couldn't generate reverse lookup hostname of %s", safe_str_client(a)); @@ -2398,18 +2555,18 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn) } log_debug(LD_APP, - "Sending relay cell to begin stream %d.", ap_conn->stream_id); + "Sending relay cell to begin stream %d.", edge_conn->stream_id); - if (connection_edge_send_command(ap_conn, + if (connection_edge_send_command(edge_conn, RELAY_COMMAND_RESOLVE, string_addr, payload_len) < 0) return -1; /* circuit is closed, don't continue */ - tor_free(ap_conn->_base.address); /* Maybe already set by dnsserv. */ - ap_conn->_base.address = tor_strdup("(Tor_internal)"); - ap_conn->_base.state = AP_CONN_STATE_RESOLVE_WAIT; + 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", - ap_conn->_base.s, circ->_base.n_circ_id); + 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; @@ -2420,19 +2577,25 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn) * and call connection_ap_handshake_attach_circuit(conn) on it. * * Return the other end of the linked connection pair, or -1 if error. + * DOCDOC partner. */ -edge_connection_t * -connection_ap_make_link(char *address, uint16_t port, - const char *digest, int use_begindir, int want_onehop) +entry_connection_t * +connection_ap_make_link(connection_t *partner, + char *address, uint16_t port, + const char *digest, + int session_group, int isolation_flags, + int use_begindir, int want_onehop) { - edge_connection_t *conn; + entry_connection_t *conn; + connection_t *base_conn; log_info(LD_APP,"Making internal %s tunnel to %s:%d ...", want_onehop ? "direct" : "anonymized", safe_str_client(address), port); - conn = edge_connection_new(CONN_TYPE_AP, AF_INET); - conn->_base.linked = 1; /* so that we can add it safely below. */ + conn = entry_connection_new(CONN_TYPE_AP, AF_INET); + base_conn = ENTRY_TO_CONN(conn); + base_conn->linked = 1; /* so that we can add it safely below. */ /* populate conn->socks_request */ @@ -2453,22 +2616,30 @@ connection_ap_make_link(char *address, uint16_t port, digest, DIGEST_LEN); } - conn->_base.address = tor_strdup("(Tor_internal)"); - tor_addr_make_unspec(&conn->_base.addr); - conn->_base.port = 0; + /* Populate isolation fields. */ + conn->socks_request->listener_type = CONN_TYPE_DIR_LISTENER; + conn->original_dest_address = tor_strdup(address); + conn->session_group = session_group; + conn->isolation_flags = isolation_flags; - if (connection_add(TO_CONN(conn)) < 0) { /* no space, forget it */ - connection_free(TO_CONN(conn)); + base_conn->address = tor_strdup("(Tor_internal)"); + tor_addr_make_unspec(&base_conn->addr); + base_conn->port = 0; + + connection_link_connections(partner, base_conn); + + if (connection_add(base_conn) < 0) { /* no space, forget it */ + connection_free(base_conn); return NULL; } - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; control_event_stream_status(conn, STREAM_EVENT_NEW, 0); /* attaching to a dirty circuit is fine */ if (connection_ap_handshake_attach_circuit(conn) < 0) { - if (!conn->_base.marked_for_close) + if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); return NULL; } @@ -2481,7 +2652,7 @@ connection_ap_make_link(char *address, uint16_t port, * or resolve error. Takes the same arguments as does * connection_ap_handshake_socks_resolved(). */ static void -tell_controller_about_resolved_result(edge_connection_t *conn, +tell_controller_about_resolved_result(entry_connection_t *conn, int answer_type, size_t answer_len, const char *answer, @@ -2493,13 +2664,11 @@ tell_controller_about_resolved_result(edge_connection_t *conn, answer_type == RESOLVED_TYPE_HOSTNAME)) { return; /* we already told the controller. */ } else if (answer_type == RESOLVED_TYPE_IPV4 && answer_len >= 4) { - struct in_addr in; - char buf[INET_NTOA_BUF_LEN]; - in.s_addr = get_uint32(answer); - tor_inet_ntoa(&in, buf, sizeof(buf)); + char *cp = tor_dup_ip(get_uint32(answer)); control_event_address_mapped(conn->socks_request->address, - buf, expires, NULL); - } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len <256) { + cp, expires, NULL); + tor_free(cp); + } else if (answer_type == RESOLVED_TYPE_HOSTNAME && answer_len < 256) { char *cp = tor_strndup(answer, answer_len); control_event_address_mapped(conn->socks_request->address, cp, expires, NULL); @@ -2522,7 +2691,7 @@ tell_controller_about_resolved_result(edge_connection_t *conn, /* XXXX023 the use of the ttl and expires fields is nutty. Let's make this * interface and those that use it less ugly. */ void -connection_ap_handshake_socks_resolved(edge_connection_t *conn, +connection_ap_handshake_socks_resolved(entry_connection_t *conn, int answer_type, size_t answer_len, const uint8_t *answer, @@ -2547,7 +2716,7 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn, } } - if (conn->is_dns_request) { + if (ENTRY_TO_EDGE_CONN(conn)->is_dns_request) { if (conn->dns_server_request) { /* We had a request on our DNS port: answer it. */ dnsserv_resolved(conn, answer_type, answer_len, (char*)answer, ttl); @@ -2627,7 +2796,7 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn, * be 0 or REASON_DONE. Send endreason to the controller, if appropriate. */ void -connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, +connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, size_t replylen, int endreason) { char buf[256]; @@ -2646,7 +2815,7 @@ connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, return; } if (replylen) { /* we already have a reply in mind */ - connection_write_to_buf(reply, replylen, TO_CONN(conn)); + connection_write_to_buf(reply, replylen, ENTRY_TO_CONN(conn)); conn->socks_request->has_finished = 1; return; } @@ -2654,7 +2823,7 @@ connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, memset(buf,0,SOCKS4_NETWORK_LEN); buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT); /* leave version, destport, destip zero */ - connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, TO_CONN(conn)); + connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { buf[0] = 5; /* version 5 */ buf[1] = (char)status; @@ -2662,7 +2831,7 @@ connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, buf[3] = 1; /* ipv4 addr */ memset(buf+4,0,6); /* Set external addr/port to 0. The spec doesn't seem to say what to do here. -RD */ - connection_write_to_buf(buf,10,TO_CONN(conn)); + connection_write_to_buf(buf,10,ENTRY_TO_CONN(conn)); } /* If socks_version isn't 4 or 5, don't send anything. * This can happen in the case of AP bridges. */ @@ -2696,7 +2865,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) char *address=NULL; uint16_t port; or_circuit_t *or_circ = NULL; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); assert_circuit_ok(circ); if (!CIRCUIT_IS_ORIGIN(circ)) @@ -2728,9 +2897,9 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) END_STREAM_REASON_TORPROTOCOL, NULL); return 0; } - if (parse_addr_port(LOG_PROTOCOL_WARN, - (char*)(cell->payload+RELAY_HEADER_SIZE), - &address,NULL,&port)<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, @@ -2979,12 +3148,13 @@ connection_exit_connect(edge_connection_t *edge_conn) } conn->state = EXIT_CONN_STATE_OPEN; - if (connection_wants_to_flush(conn)) { + if (connection_get_outbuf_len(conn)) { /* in case there are any queued data cells */ log_warn(LD_BUG,"newly connected conn had data waiting!"); // connection_start_writing(conn); } - connection_watch_events(conn, READ_EVENT); + IF_HAS_NO_BUFFEREVENT(conn) + connection_watch_events(conn, READ_EVENT); /* also, deliver a 'connected' cell back through the circuit. */ if (connection_edge_is_rendezvous_stream(edge_conn)) { @@ -3094,12 +3264,11 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn) * resolved.) */ int -connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) +connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); tor_assert(conn); - tor_assert(conn->_base.type == CONN_TYPE_AP); tor_assert(conn->socks_request); tor_assert(exit); @@ -3107,10 +3276,10 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) * make sure the exit node of the existing circuit matches exactly. */ if (conn->chosen_exit_name) { - routerinfo_t *chosen_exit = - router_get_by_nickname(conn->chosen_exit_name, 1); - if (!chosen_exit || tor_memneq(chosen_exit->cache_info.identity_digest, - exit->cache_info.identity_digest, DIGEST_LEN)) { + const node_t *chosen_exit = + node_get_by_nickname(conn->chosen_exit_name, 1); + if (!chosen_exit || tor_memneq(chosen_exit->identity, + exit->identity, DIGEST_LEN)) { /* doesn't match */ // log_debug(LD_APP,"Requested node '%s', considering node '%s'. No.", // conn->chosen_exit_name, exit->nickname); @@ -3121,12 +3290,13 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) if (conn->socks_request->command == SOCKS_COMMAND_CONNECT && !conn->use_begindir) { struct in_addr in; - uint32_t addr = 0; + tor_addr_t addr, *addrp = NULL; addr_policy_result_t r; - if (tor_inet_aton(conn->socks_request->address, &in)) - addr = ntohl(in.s_addr); - r = compare_addr_to_addr_policy(addr, conn->socks_request->port, - exit->exit_policy); + if (tor_inet_aton(conn->socks_request->address, &in)) { + tor_addr_from_in(&addr, &in); + addrp = &addr; + } + r = compare_tor_addr_to_node_policy(addrp, conn->socks_request->port,exit); if (r == ADDR_POLICY_REJECTED) return 0; /* We know the address, and the exit policy rejects it. */ if (r == ADDR_POLICY_PROBABLY_REJECTED && !conn->chosen_exit_name) @@ -3134,17 +3304,11 @@ connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit) * addresses with this port. Since the user didn't ask for * this node, err on the side of caution. */ } else if (SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)) { - /* Can't support reverse lookups without eventdns. */ - if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR && - exit->has_old_dnsworkers) - return 0; - /* Don't send DNS requests to non-exit servers by default. */ - if (!conn->chosen_exit_name && policy_is_reject_star(exit->exit_policy)) + if (!conn->chosen_exit_name && node_exit_policy_rejects_all(exit)) return 0; } - if (options->_ExcludeExitNodesUnion && - routerset_contains_router(options->_ExcludeExitNodesUnion, exit)) { + if (routerset_contains_node(options->_ExcludeExitNodesUnion, exit)) { /* Not a suitable exit. Refuse it. */ return 0; } @@ -3199,3 +3363,205 @@ parse_extended_hostname(char *address, int allowdotexit) return BAD_HOSTNAME; } +/** Return true iff the (possibly NULL) <b>alen</b>-byte chunk of memory at + * <b>a</b> is equal to the (possibly NULL) <b>blen</b>-byte chunk of memory + * at <b>b</b>. */ +static int +memeq_opt(const char *a, size_t alen, const char *b, size_t blen) +{ + if (a == NULL) { + return (b == NULL); + } else if (b == NULL) { + return 0; + } else if (alen != blen) { + return 0; + } else { + return tor_memeq(a, b, alen); + } +} + +/** + * Return true iff none of the isolation flags and fields in <b>conn</b> + * should prevent it from being attached to <b>circ</b>. + */ +int +connection_edge_compatible_with_circuit(const entry_connection_t *conn, + const origin_circuit_t *circ) +{ + const uint8_t iso = conn->isolation_flags; + const socks_request_t *sr = conn->socks_request; + + /* If circ has never been used for an isolated connection, we can + * totally use it for this one. */ + if (!circ->isolation_values_set) + return 1; + + /* If circ has been used for connections having more than one value + * for some field f, it will have the corresponding bit set in + * isolation_flags_mixed. If isolation_flags_mixed has any bits + * in common with iso, then conn must be isolated from at least + * one stream that has been attached to circ. */ + if ((iso & circ->isolation_flags_mixed) != 0) { + /* For at least one field where conn is isolated, the circuit + * already has mixed streams. */ + return 0; + } + + if (! conn->original_dest_address) { + log_warn(LD_BUG, "Reached connection_edge_compatible_with_circuit without " + "having set conn->original_dest_address"); + ((entry_connection_t*)conn)->original_dest_address = + tor_strdup(conn->socks_request->address); + } + + if ((iso & ISO_STREAM) && + (circ->associated_isolated_stream_global_id != + ENTRY_TO_CONN(conn)->global_identifier)) + return 0; + + if ((iso & ISO_DESTPORT) && conn->socks_request->port != circ->dest_port) + return 0; + if ((iso & ISO_DESTADDR) && + strcasecmp(conn->original_dest_address, circ->dest_address)) + return 0; + if ((iso & ISO_SOCKSAUTH) && + (! memeq_opt(sr->username, sr->usernamelen, + circ->socks_username, circ->socks_username_len) || + ! memeq_opt(sr->password, sr->passwordlen, + circ->socks_password, circ->socks_password_len))) + return 0; + if ((iso & ISO_CLIENTPROTO) && + (conn->socks_request->listener_type != circ->client_proto_type || + conn->socks_request->socks_version != circ->client_proto_socksver)) + return 0; + if ((iso & ISO_CLIENTADDR) && + !tor_addr_eq(&ENTRY_TO_CONN(conn)->addr, &circ->client_addr)) + return 0; + if ((iso & ISO_SESSIONGRP) && conn->session_group != circ->session_group) + return 0; + if ((iso & ISO_NYM_EPOCH) && conn->nym_epoch != circ->nym_epoch) + return 0; + + return 1; +} + +/** + * If <b>dry_run</b> is false, update <b>circ</b>'s isolation flags and fields + * to reflect having had <b>conn</b> attached to it, and return 0. Otherwise, + * if <b>dry_run</b> is true, then make no changes to <b>circ</b>, and return + * a bitfield of isolation flags that we would have to set in + * isolation_flags_mixed to add <b>conn</b> to <b>circ</b>, or -1 if + * <b>circ</b> has had no streams attached to it. + */ +int +connection_edge_update_circuit_isolation(const entry_connection_t *conn, + origin_circuit_t *circ, + int dry_run) +{ + const socks_request_t *sr = conn->socks_request; + if (! conn->original_dest_address) { + log_warn(LD_BUG, "Reached connection_update_circuit_isolation without " + "having set conn->original_dest_address"); + ((entry_connection_t*)conn)->original_dest_address = + tor_strdup(conn->socks_request->address); + } + + if (!circ->isolation_values_set) { + if (dry_run) + return -1; + circ->associated_isolated_stream_global_id = + ENTRY_TO_CONN(conn)->global_identifier; + circ->dest_port = conn->socks_request->port; + circ->dest_address = tor_strdup(conn->original_dest_address); + circ->client_proto_type = conn->socks_request->listener_type; + circ->client_proto_socksver = conn->socks_request->socks_version; + tor_addr_copy(&circ->client_addr, &ENTRY_TO_CONN(conn)->addr); + circ->session_group = conn->session_group; + circ->nym_epoch = conn->nym_epoch; + circ->socks_username = sr->username ? + tor_memdup(sr->username, sr->usernamelen) : NULL; + circ->socks_password = sr->password ? + tor_memdup(sr->password, sr->passwordlen) : NULL; + circ->socks_username_len = sr->usernamelen; + circ->socks_password_len = sr->passwordlen; + + circ->isolation_values_set = 1; + return 0; + } else { + uint8_t mixed = 0; + if (conn->socks_request->port != circ->dest_port) + mixed |= ISO_DESTPORT; + if (strcasecmp(conn->original_dest_address, circ->dest_address)) + mixed |= ISO_DESTADDR; + if (!memeq_opt(sr->username, sr->usernamelen, + circ->socks_username, circ->socks_username_len) || + !memeq_opt(sr->password, sr->passwordlen, + circ->socks_password, circ->socks_password_len)) + mixed |= ISO_SOCKSAUTH; + if ((conn->socks_request->listener_type != circ->client_proto_type || + conn->socks_request->socks_version != circ->client_proto_socksver)) + mixed |= ISO_CLIENTPROTO; + if (!tor_addr_eq(&ENTRY_TO_CONN(conn)->addr, &circ->client_addr)) + mixed |= ISO_CLIENTADDR; + if (conn->session_group != circ->session_group) + mixed |= ISO_SESSIONGRP; + if (conn->nym_epoch != circ->nym_epoch) + mixed |= ISO_NYM_EPOCH; + + if (dry_run) + return mixed; + + if ((mixed & conn->isolation_flags) != 0) { + log_warn(LD_BUG, "Updating a circuit with seemingly incompatible " + "isolation flags."); + } + circ->isolation_flags_mixed |= mixed; + return 0; + } +} + +/** + * Clear the isolation settings on <b>circ</b>. + * + * This only works on an open circuit that has never had a stream attached to + * it, and whose isolation settings are hypothetical. (We set hypothetical + * isolation settings on circuits as we're launching them, so that we + * know whether they can handle more streams or whether we need to launch + * even more circuits. Once the circuit is open, if it turns out that + * we no longer have any streams to attach to it, we clear the isolation flags + * and data so that other streams can have a chance.) + */ +void +circuit_clear_isolation(origin_circuit_t *circ) +{ + if (circ->isolation_any_streams_attached) { + log_warn(LD_BUG, "Tried to clear the isolation status of a dirty circuit"); + return; + } + if (TO_CIRCUIT(circ)->state != CIRCUIT_STATE_OPEN) { + log_warn(LD_BUG, "Tried to clear the isolation status of a non-open " + "circuit"); + return; + } + + circ->isolation_values_set = 0; + circ->isolation_flags_mixed = 0; + circ->associated_isolated_stream_global_id = 0; + circ->client_proto_type = 0; + circ->client_proto_socksver = 0; + circ->dest_port = 0; + tor_addr_make_unspec(&circ->client_addr); + tor_free(circ->dest_address); + circ->session_group = -1; + circ->nym_epoch = 0; + if (circ->socks_username) { + memset(circ->socks_username, 0x11, circ->socks_username_len); + tor_free(circ->socks_username); + } + if (circ->socks_password) { + memset(circ->socks_password, 0x05, circ->socks_password_len); + tor_free(circ->socks_password); + } + circ->socks_username_len = circ->socks_password_len = 0; +} + diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 8ba2fafd08..830667e601 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -15,7 +15,7 @@ #define connection_mark_unattached_ap(conn, endreason) \ _connection_mark_unattached_ap((conn), (endreason), __LINE__, _SHORT_FILE_) -void _connection_mark_unattached_ap(edge_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, @@ -27,16 +27,22 @@ int connection_edge_flushed_some(edge_connection_t *conn); int connection_edge_finished_flushing(edge_connection_t *conn); int connection_edge_finished_connecting(edge_connection_t *conn); -int connection_ap_handshake_send_begin(edge_connection_t *ap_conn); -int connection_ap_handshake_send_resolve(edge_connection_t *ap_conn); +void connection_ap_about_to_close(entry_connection_t *edge_conn); +void connection_exit_about_to_close(edge_connection_t *edge_conn); -edge_connection_t *connection_ap_make_link(char *address, uint16_t port, +int connection_ap_handshake_send_begin(entry_connection_t *ap_conn); +int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn); + +entry_connection_t *connection_ap_make_link(connection_t *partner, + char *address, uint16_t port, const char *digest, + int session_group, + int isolation_flags, int use_begindir, int want_onehop); -void connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply, +void connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, size_t replylen, int endreason); -void connection_ap_handshake_socks_resolved(edge_connection_t *conn, +void connection_ap_handshake_socks_resolved(entry_connection_t *conn, int answer_type, size_t answer_len, const uint8_t *answer, @@ -47,22 +53,23 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ); int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ); void connection_exit_connect(edge_connection_t *conn); int connection_edge_is_rendezvous_stream(edge_connection_t *conn); -int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit); +int connection_ap_can_use_exit(const entry_connection_t *conn, + const node_t *exit); void connection_ap_expire_beginning(void); void connection_ap_attach_pending(void); void connection_ap_fail_onehop(const char *failed_digest, cpath_build_state_t *build_state); void circuit_discard_optional_exit_enclaves(extend_info_t *info); -int connection_ap_detach_retriable(edge_connection_t *conn, +int connection_ap_detach_retriable(entry_connection_t *conn, origin_circuit_t *circ, int reason); -int connection_ap_process_transparent(edge_connection_t *conn); +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(or_options_t *options); -void addressmap_clear_invalid_automaps(or_options_t *options); +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); @@ -81,10 +88,10 @@ void client_dns_set_addressmap(const char *address, uint32_t val, 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(edge_connection_t *conn, +int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); -int connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn, +int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); @@ -98,5 +105,12 @@ hostname_type_t parse_extended_hostname(char *address, int allowdotexit); int get_pf_socket(void); #endif +int connection_edge_compatible_with_circuit(const entry_connection_t *conn, + const origin_circuit_t *circ); +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); + #endif diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 27a34d3d15..4c0960ceca 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -13,6 +13,7 @@ #include "or.h" #include "buffers.h" #include "circuitbuild.h" +#include "circuitlist.h" #include "command.h" #include "config.h" #include "connection.h" @@ -22,21 +23,32 @@ #include "geoip.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "reasons.h" #include "relay.h" #include "rephist.h" #include "router.h" #include "routerlist.h" +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent_ssl.h> +#endif + static int connection_tls_finish_handshake(or_connection_t *conn); +static int connection_or_launch_v3_or_handshake(or_connection_t *conn); static int connection_or_process_cells_from_inbuf(or_connection_t *conn); -static int connection_or_send_versions(or_connection_t *conn); -static int connection_init_or_handshake_state(or_connection_t *conn, - int started_here); static int connection_or_check_valid_tls_handshake(or_connection_t *conn, int started_here, char *digest_rcvd_out); +static void connection_or_tls_renegotiated_cb(tor_tls_t *tls, void *_conn); + +#ifdef USE_BUFFEREVENTS +static void connection_or_handle_event_cb(struct bufferevent *bufev, + short event, void *arg); +#include <event2/buffer.h>/*XXXX REMOVE */ +#endif + /**************************************************************/ /** Map from identity digest of connected OR or desired OR to a connection_t @@ -136,6 +148,142 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) #endif } +/**************************************************************/ + +/** Map from a string describing what a non-open OR connection was doing when + * failed, to an intptr_t describing the count of connections that failed that + * way. Note that the count is stored _as_ the pointer. + */ +static strmap_t *broken_connection_counts; + +/** If true, do not record information in <b>broken_connection_counts</b>. */ +static int disable_broken_connection_counts = 0; + +/** Record that an OR connection failed in <b>state</b>. */ +static void +note_broken_connection(const char *state) +{ + void *ptr; + intptr_t val; + if (disable_broken_connection_counts) + return; + + if (!broken_connection_counts) + broken_connection_counts = strmap_new(); + + ptr = strmap_get(broken_connection_counts, state); + val = (intptr_t)ptr; + val++; + ptr = (void*)val; + strmap_set(broken_connection_counts, state, ptr); +} + +/** Forget all recorded states for failed connections. If + * <b>stop_recording</b> is true, don't record any more. */ +void +clear_broken_connection_map(int stop_recording) +{ + if (broken_connection_counts) + strmap_free(broken_connection_counts, NULL); + broken_connection_counts = NULL; + if (stop_recording) + disable_broken_connection_counts = 1; +} + +/** Write a detailed description the state of <b>orconn</b> into the + * <b>buflen</b>-byte buffer at <b>buf</b>. This description includes not + * only the OR-conn level state but also the TLS state. It's useful for + * diagnosing broken handshakes. */ +static void +connection_or_get_state_description(or_connection_t *orconn, + char *buf, size_t buflen) +{ + connection_t *conn = TO_CONN(orconn); + const char *conn_state; + char tls_state[256]; + + tor_assert(conn->type == CONN_TYPE_OR); + + conn_state = conn_state_to_string(conn->type, conn->state); + tor_tls_get_state_description(orconn->tls, tls_state, sizeof(tls_state)); + + tor_snprintf(buf, buflen, "%s with SSL state %s", conn_state, tls_state); +} + +/** Record the current state of <b>orconn</b> as the state of a broken + * connection. */ +static void +connection_or_note_state_when_broken(or_connection_t *orconn) +{ + char buf[256]; + if (disable_broken_connection_counts) + return; + connection_or_get_state_description(orconn, buf, sizeof(buf)); + log_info(LD_HANDSHAKE,"Connection died in state '%s'", buf); + note_broken_connection(buf); +} + +/** Helper type used to sort connection states and find the most frequent. */ +typedef struct broken_state_count_t { + intptr_t count; + const char *state; +} broken_state_count_t; + +/** Helper function used to sort broken_state_count_t by frequency. */ +static int +broken_state_count_compare(const void **a_ptr, const void **b_ptr) +{ + const broken_state_count_t *a = *a_ptr, *b = *b_ptr; + if (b->count < a->count) + return -1; + else if (b->count == a->count) + return 0; + else + return 1; +} + +/** Upper limit on the number of different states to report for connection + * failure. */ +#define MAX_REASONS_TO_REPORT 10 + +/** Report a list of the top states for failed OR connections at log level + * <b>severity</b>, in log domain <b>domain</b>. */ +void +connection_or_report_broken_states(int severity, int domain) +{ + int total = 0; + smartlist_t *items; + + if (!broken_connection_counts || disable_broken_connection_counts) + return; + + items = smartlist_create(); + STRMAP_FOREACH(broken_connection_counts, state, void *, countptr) { + broken_state_count_t *c = tor_malloc(sizeof(broken_state_count_t)); + c->count = (intptr_t)countptr; + total += (int)c->count; + c->state = state; + smartlist_add(items, c); + } STRMAP_FOREACH_END; + + smartlist_sort(items, broken_state_count_compare); + + log(severity, domain, "%d connections have failed%s", total, + smartlist_len(items) > MAX_REASONS_TO_REPORT ? ". Top reasons:" : ":"); + + SMARTLIST_FOREACH_BEGIN(items, const broken_state_count_t *, c) { + if (c_sl_idx > MAX_REASONS_TO_REPORT) + break; + log(severity, domain, + " %d connections died in state %s", (int)c->count, c->state); + } SMARTLIST_FOREACH_END(c); + + SMARTLIST_FOREACH(items, broken_state_count_t *, c, tor_free(c)); + smartlist_free(items); +} + +/**************************************************************/ + /** Pack the cell_t host-order structure <b>src</b> into network-order * in the buffer <b>dest</b>. See tor-spec.txt for details about the * wire format. @@ -178,7 +326,8 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out) var_cell_t * var_cell_new(uint16_t payload_len) { - var_cell_t *cell = tor_malloc(sizeof(var_cell_t)+payload_len-1); + size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len; + var_cell_t *cell = tor_malloc(size); cell->payload_len = payload_len; cell->command = 0; cell->circ_id = 0; @@ -227,8 +376,17 @@ connection_or_process_inbuf(or_connection_t *conn) } return ret; + case OR_CONN_STATE_TLS_SERVER_RENEGOTIATING: +#ifdef USE_BUFFEREVENTS + if (tor_tls_server_got_renegotiate(conn->tls)) + connection_or_tls_renegotiated_cb(conn->tls, conn); + if (conn->_base.marked_for_close) + return 0; + /* fall through. */ +#endif case OR_CONN_STATE_OPEN: - case OR_CONN_STATE_OR_HANDSHAKING: + case OR_CONN_STATE_OR_HANDSHAKING_V2: + case OR_CONN_STATE_OR_HANDSHAKING_V3: return connection_or_process_cells_from_inbuf(conn); default: return 0; /* don't do anything */ @@ -248,7 +406,7 @@ connection_or_process_inbuf(or_connection_t *conn) int connection_or_flushed_some(or_connection_t *conn) { - size_t datalen = buf_datalen(conn->_base.outbuf); + size_t datalen = connection_get_outbuf_len(TO_CONN(conn)); /* If we're under the low water mark, add cells until we're just over the * high water mark. */ if (datalen < OR_CONN_LOWWATER) { @@ -280,8 +438,8 @@ connection_or_finished_flushing(or_connection_t *conn) switch (conn->_base.state) { case OR_CONN_STATE_PROXY_HANDSHAKING: case OR_CONN_STATE_OPEN: - case OR_CONN_STATE_OR_HANDSHAKING: - connection_stop_writing(TO_CONN(conn)); + 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); @@ -296,7 +454,7 @@ connection_or_finished_flushing(or_connection_t *conn) int connection_or_finished_connecting(or_connection_t *or_conn) { - int proxy_type; + const int proxy_type = or_conn->proxy_type; connection_t *conn; tor_assert(or_conn); conn = TO_CONN(or_conn); @@ -306,15 +464,6 @@ connection_or_finished_connecting(or_connection_t *or_conn) conn->address,conn->port); control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0); - proxy_type = PROXY_NONE; - - if (get_options()->HTTPSProxy) - proxy_type = PROXY_CONNECT; - else if (get_options()->Socks4Proxy) - proxy_type = PROXY_SOCKS4; - else if (get_options()->Socks5Proxy) - proxy_type = PROXY_SOCKS5; - if (proxy_type != PROXY_NONE) { /* start proxy handshake */ if (connection_proxy_connect(conn, proxy_type) < 0) { @@ -335,6 +484,51 @@ connection_or_finished_connecting(or_connection_t *or_conn) return 0; } +/* Called when we're about to finally unlink and free an OR connection: + * perform necessary accounting and cleanup */ +void +connection_or_about_to_close(or_connection_t *or_conn) +{ + time_t now = time(NULL); + connection_t *conn = TO_CONN(or_conn); + + /* 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(); + connection_or_note_state_when_broken(or_conn); + rep_hist_note_connect_failed(or_conn->identity_digest, now); + entry_guard_register_connect_status(or_conn->identity_digest,0, + !options->HTTPSProxy, now); + if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) { + int reason = tls_error_to_orconn_end_reason(or_conn->tls_error); + control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, + reason); + if (!authdir_mode_tests_reachability(options)) + control_event_bootstrap_problem( + orconn_end_reason_to_control_string(reason), reason); + } + } + } else if (conn->hold_open_until_flushed) { + /* We only set hold_open_until_flushed when we're intentionally + * closing a connection. */ + rep_hist_note_disconnect(or_conn->identity_digest, now); + control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED, + tls_error_to_orconn_end_reason(or_conn->tls_error)); + } else if (!tor_digest_is_zero(or_conn->identity_digest)) { + rep_hist_note_connection_died(or_conn->identity_digest, now); + 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 * currently or recently running relay. Otherwise return 0. */ int @@ -342,7 +536,7 @@ connection_or_digest_is_known_relay(const char *id_digest) { if (router_get_consensus_status_by_id(id_digest)) return 1; /* It's in the consensus: "yes" */ - if (router_get_by_digest(id_digest)) + if (router_get_by_id_digest(id_digest)) return 1; /* Not in the consensus, but we have a descriptor for * it. Probably it was in a recent consensus. "Yes". */ return 0; @@ -359,7 +553,7 @@ connection_or_digest_is_known_relay(const char *id_digest) */ static void connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, - or_options_t *options) + const or_options_t *options) { int rate, burst; /* per-connection rate limiting params */ if (connection_or_digest_is_known_relay(conn->identity_digest)) { @@ -382,6 +576,27 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, conn->bandwidthrate = rate; conn->bandwidthburst = burst; +#ifdef USE_BUFFEREVENTS + { + const struct timeval *tick = tor_libevent_get_one_tick_timeout(); + struct ev_token_bucket_cfg *cfg, *old_cfg; + int64_t rate64 = (((int64_t)rate) * options->TokenBucketRefillInterval) + / 1000; + /* This can't overflow, since TokenBucketRefillInterval <= 1000, + * and rate started out less than INT_MAX. */ + int rate_per_tick = (int) rate64; + + cfg = ev_token_bucket_cfg_new(rate_per_tick, burst, rate_per_tick, + burst, tick); + old_cfg = conn->bucket_cfg; + if (conn->_base.bufev) + tor_set_bufferevent_rate_limit(conn->_base.bufev, cfg); + if (old_cfg) + ev_token_bucket_cfg_free(old_cfg); + conn->bucket_cfg = cfg; + (void) reset; /* No way to do this with libevent yet. */ + } +#else if (reset) { /* set up the token buckets to be full */ conn->read_bucket = conn->write_bucket = burst; return; @@ -392,13 +607,15 @@ connection_or_update_token_buckets_helper(or_connection_t *conn, int reset, conn->read_bucket = burst; if (conn->write_bucket > burst) conn->write_bucket = burst; +#endif } /** Either our set of relays or our per-conn rate limits have changed. * Go through all the OR connections and update their token buckets to make * sure they don't exceed their maximum values. */ void -connection_or_update_token_buckets(smartlist_t *conns, or_options_t *options) +connection_or_update_token_buckets(smartlist_t *conns, + const or_options_t *options) { SMARTLIST_FOREACH(conns, connection_t *, conn, { @@ -410,13 +627,13 @@ connection_or_update_token_buckets(smartlist_t *conns, or_options_t *options) /** If we don't necessarily know the router we're connecting to, but we * have an addr/port/id_digest, then fill in as much as we can. Start * by checking to see if this describes a router we know. */ -static void +void connection_or_init_conn_from_address(or_connection_t *conn, const tor_addr_t *addr, uint16_t port, const char *id_digest, int started_here) { - routerinfo_t *r = router_get_by_digest(id_digest); + const node_t *r = node_get_by_id(id_digest); connection_or_set_identity_digest(conn, id_digest); connection_or_update_token_buckets_helper(conn, 1, get_options()); @@ -424,8 +641,10 @@ connection_or_init_conn_from_address(or_connection_t *conn, tor_addr_copy(&conn->_base.addr, addr); tor_addr_copy(&conn->real_addr, addr); if (r) { + tor_addr_t node_addr; + node_get_addr(r, &node_addr); /* XXXX proposal 118 will make this more complex. */ - if (tor_addr_eq_ipv4h(&conn->_base.addr, r->addr)) + if (tor_addr_eq(&conn->_base.addr, &node_addr)) conn->is_canonical = 1; if (!started_here) { /* Override the addr/port, so our log messages will make sense. @@ -438,12 +657,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_from_ipv4h(&conn->_base.addr, r->addr); - conn->_base.port = r->or_port; + tor_addr_copy(&conn->_base.addr, &node_addr); + conn->_base.port = node_get_orport(r); } - conn->nickname = tor_strdup(r->nickname); + conn->nickname = tor_strdup(node_get_nickname(r)); tor_free(conn->_base.address); - conn->_base.address = tor_strdup(r->address); + conn->_base.address = tor_dup_addr(&node_addr); } else { const char *n; /* If we're an authoritative directory server, we may know a @@ -792,11 +1011,15 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, const char *id_digest) { or_connection_t *conn; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int socket_error = 0; - int using_proxy = 0; tor_addr_t addr; + int r; + tor_addr_t proxy_addr; + uint16_t proxy_port; + int proxy_type; + tor_assert(_addr); tor_assert(id_digest); tor_addr_copy(&addr, _addr); @@ -815,19 +1038,20 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, conn->is_outgoing = 1; - /* use a proxy server if available */ - if (options->HTTPSProxy) { - using_proxy = 1; - tor_addr_copy(&addr, &options->HTTPSProxyAddr); - port = options->HTTPSProxyPort; - } else if (options->Socks4Proxy) { - using_proxy = 1; - tor_addr_copy(&addr, &options->Socks4ProxyAddr); - port = options->Socks4ProxyPort; - } else if (options->Socks5Proxy) { - using_proxy = 1; - tor_addr_copy(&addr, &options->Socks5ProxyAddr); - port = options->Socks5ProxyPort; + /* If we are using a proxy server, find it and use it. */ + r = get_proxy_addrport(&proxy_addr, &proxy_port, &proxy_type, TO_CONN(conn)); + if (r == 0) { + conn->proxy_type = proxy_type; + if (proxy_type != PROXY_NONE) { + tor_addr_copy(&addr, &proxy_addr); + port = proxy_port; + conn->_base.proxy_state = PROXY_INFANT; + } + } else { + log_warn(LD_GENERAL, "Tried to connect through proxy, but proxy address " + "could not be found."); + connection_free(TO_CONN(conn)); + return NULL; } switch (connection_connect(TO_CONN(conn), conn->_base.address, @@ -835,7 +1059,7 @@ connection_or_connect(const tor_addr_t *_addr, uint16_t port, case -1: /* If the connection failed immediately, and we're using * a proxy, our proxy is down. Don't blame the Tor server. */ - if (!using_proxy) + if (conn->_base.proxy_state == PROXY_INFANT) entry_guard_register_connect_status(conn->identity_digest, 0, 1, time(NULL)); connection_or_connect_failed(conn, @@ -870,6 +1094,7 @@ int connection_tls_start_handshake(or_connection_t *conn, int receiving) { conn->_base.state = OR_CONN_STATE_TLS_HANDSHAKING; + tor_assert(!conn->tls); conn->tls = tor_tls_new(conn->_base.s, receiving); tor_tls_set_logged_address(conn->tls, // XXX client and relay? escaped_safe_str(conn->_base.address)); @@ -877,12 +1102,38 @@ connection_tls_start_handshake(or_connection_t *conn, int receiving) log_warn(LD_BUG,"tor_tls_new failed. Closing."); return -1; } +#ifdef USE_BUFFEREVENTS + if (connection_type_uses_bufferevent(TO_CONN(conn))) { + const int filtering = get_options()->_UseFilteringSSLBufferevents; + struct bufferevent *b = + tor_tls_init_bufferevent(conn->tls, conn->_base.bufev, conn->_base.s, + receiving, filtering); + if (!b) { + log_warn(LD_BUG,"tor_tls_init_bufferevent failed. Closing."); + return -1; + } + conn->_base.bufev = b; + if (conn->bucket_cfg) + tor_set_bufferevent_rate_limit(conn->_base.bufev, conn->bucket_cfg); + connection_enable_rate_limiting(TO_CONN(conn)); + + connection_configure_bufferevent_callbacks(TO_CONN(conn)); + bufferevent_setcb(b, + connection_handle_read_cb, + connection_handle_write_cb, + connection_or_handle_event_cb,/* overriding this one*/ + TO_CONN(conn)); + } +#endif connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd %d", conn->_base.s); note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); - if (connection_tls_continue_handshake(conn) < 0) { - return -1; + IF_HAS_BUFFEREVENT(TO_CONN(conn), { + /* ???? */; + }) ELSE_IF_NO_BUFFEREVENT { + if (connection_tls_continue_handshake(conn) < 0) + return -1; } return 0; } @@ -936,16 +1187,22 @@ connection_tls_continue_handshake(or_connection_t *conn) if (! tor_tls_used_v1_handshake(conn->tls)) { if (!tor_tls_is_server(conn->tls)) { if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { - log_debug(LD_OR, "Done with initial SSL handshake (client-side). " - "Requesting renegotiation."); - conn->_base.state = OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING; - goto again; + if (tor_tls_received_v3_certificate(conn->tls)) { + log_info(LD_OR, "Client got a v3 cert! Moving on to v3 " + "handshake."); + 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; + goto again; + } } // log_notice(LD_OR,"Done. state was %d.", conn->_base.state); } else { - /* improved handshake, but not a client. */ + /* v2/v3 handshake, but not a client. */ log_debug(LD_OR, "Done with initial SSL handshake (server-side). " - "Expecting renegotiation."); + "Expecting renegotiation or VERSIONS cell"); tor_tls_set_renegotiate_callback(conn->tls, connection_or_tls_renegotiated_cb, conn); @@ -970,6 +1227,85 @@ connection_tls_continue_handshake(or_connection_t *conn) return 0; } +#ifdef USE_BUFFEREVENTS +static void +connection_or_handle_event_cb(struct bufferevent *bufev, short event, + void *arg) +{ + struct or_connection_t *conn = TO_OR_CONN(arg); + + /* XXXX cut-and-paste code; should become a function. */ + if (event & BEV_EVENT_CONNECTED) { + if (conn->_base.state == OR_CONN_STATE_TLS_HANDSHAKING) { + if (tor_tls_finish_handshake(conn->tls) < 0) { + log_warn(LD_OR, "Problem finishing handshake"); + connection_mark_for_close(TO_CONN(conn)); + 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 (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)); + return; + } else { + conn->_base.state = OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING; + tor_tls_unblock_renegotiation(conn->tls); + if (bufferevent_ssl_renegotiate(conn->_base.bufev)<0) { + log_warn(LD_OR, "Start_renegotiating went badly."); + connection_mark_for_close(TO_CONN(conn)); + } + tor_tls_unblock_renegotiation(conn->tls); + return; /* ???? */ + } + } + } else if (tor_tls_get_num_server_handshakes(conn->tls) == 1) { + /* v2 or v3 handshake, as a server. Only got one handshake, so + * wait for the next one. */ + tor_tls_set_renegotiate_callback(conn->tls, + connection_or_tls_renegotiated_cb, + conn); + conn->_base.state = OR_CONN_STATE_TLS_SERVER_RENEGOTIATING; + /* return 0; */ + return; /* ???? */ + } else { + const int handshakes = tor_tls_get_num_server_handshakes(conn->tls); + tor_assert(handshakes >= 2); + if (handshakes == 2) { + /* v2 handshake, as a server. Two handshakes happened already, + * so we treat renegotiation as done. + */ + connection_or_tls_renegotiated_cb(conn->tls, conn); + } else { + log_warn(LD_OR, "More than two handshakes done on connection. " + "Closing."); + connection_mark_for_close(TO_CONN(conn)); + } + 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)); /* ???? */ + return; + } + + if (event & BEV_EVENT_ERROR) { + unsigned long err; + while ((err = bufferevent_get_openssl_error(bufev))) { + tor_tls_log_one_error(conn->tls, err, LOG_WARN, LD_OR, + "handshaking (with bufferevent)"); + } + } + + connection_handle_event_cb(bufev, event, arg); +} +#endif + /** Return 1 if we initiated this connection, or 0 if it started * out as an incoming connection. */ @@ -984,6 +1320,29 @@ 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_env_t *identity_rcvd) +{ + const int started_here = connection_or_nonopen_was_started_here(conn); + crypto_pk_env_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. * @@ -1015,16 +1374,13 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, char *digest_rcvd_out) { crypto_pk_env_t *identity_rcvd=NULL; - or_options_t *options = get_options(); + 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); const char *conn_type = started_here ? "outgoing" : "incoming"; - crypto_pk_env_t *our_identity = - started_here ? get_tlsclient_identity_key() : - get_server_identity_key(); - int has_cert = 0, has_identity=0; + int has_cert = 0; check_no_tls_errors(); has_cert = tor_tls_peer_has_cert(conn->tls); @@ -1059,21 +1415,46 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, } if (identity_rcvd) { - has_identity = 1; crypto_pk_get_digest(identity_rcvd, digest_rcvd_out); - 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; - } - crypto_free_pk_env(identity_rcvd); } else { memset(digest_rcvd_out, 0, DIGEST_LEN); - conn->circ_id_type = CIRC_ID_TYPE_NEITHER; } - if (started_here && tor_digest_is_zero(conn->identity_digest)) { - connection_or_set_identity_digest(conn, digest_rcvd_out); + connection_or_set_circid_type(conn, identity_rcvd); + crypto_free_pk_env(identity_rcvd); + + if (started_here) + return connection_or_client_learned_peer_id(conn, + (const uint8_t*)digest_rcvd_out); + + return 0; +} + +/** Called when we (as a connection initiator) have definitively, + * authenticatedly, learned that ID of the Tor instance on the other + * side of <b>conn</b> is <b>peer_id</b>. For v1 and v2 handshakes, + * this is right after we get a certificate chain in a TLS handshake + * or renegotiation. For v3 handshakes, this is right after we get a + * certificate chain in a CERT cell. + * + * If we want any particular ID before, record the one we got. + * + * If we wanted an ID, but we didn't get it, log a warning and return -1. + * + * If we're testing reachability, remember what we learned. + * + * Return 0 on success, -1 on failure. + */ +int +connection_or_client_learned_peer_id(or_connection_t *conn, + const uint8_t *peer_id) +{ + int as_expected = 1; + const or_options_t *options = get_options(); + int severity = server_mode(options) ? LOG_PROTOCOL_WARN : LOG_WARN; + + if (tor_digest_is_zero(conn->identity_digest)) { + connection_or_set_identity_digest(conn, (const char*)peer_id); tor_free(conn->nickname); conn->nickname = tor_malloc(HEX_DIGEST_LEN+2); conn->nickname[0] = '$'; @@ -1085,43 +1466,39 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, /* if it's a bridge and we didn't know its identity fingerprint, now * we do -- remember it for future attempts. */ learned_router_identity(&conn->_base.addr, conn->_base.port, - digest_rcvd_out); + (const char*)peer_id); } - if (started_here) { - int as_advertised = 1; - tor_assert(has_cert); - tor_assert(has_identity); - if (tor_memneq(digest_rcvd_out, conn->identity_digest, DIGEST_LEN)) { - /* I was aiming for a particular digest. I didn't get it! */ - char seen[HEX_DIGEST_LEN+1]; - char expected[HEX_DIGEST_LEN+1]; - base16_encode(seen, sizeof(seen), digest_rcvd_out, DIGEST_LEN); - base16_encode(expected, sizeof(expected), conn->identity_digest, - DIGEST_LEN); - log_fn(severity, LD_HANDSHAKE, - "Tried connecting to router at %s:%d, but identity key was not " - "as expected: wanted %s but got %s.", - conn->_base.address, conn->_base.port, expected, seen); - entry_guard_register_connect_status(conn->identity_digest, 0, 1, - time(NULL)); - control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, - END_OR_CONN_REASON_OR_IDENTITY); - if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem("foo", END_OR_CONN_REASON_OR_IDENTITY); - as_advertised = 0; - } - if (authdir_mode_tests_reachability(options)) { - dirserv_orconn_tls_done(conn->_base.address, conn->_base.port, - digest_rcvd_out, as_advertised); - } - if (!as_advertised) - return -1; + if (tor_memneq(peer_id, conn->identity_digest, DIGEST_LEN)) { + /* I was aiming for a particular digest. I didn't get it! */ + char seen[HEX_DIGEST_LEN+1]; + char expected[HEX_DIGEST_LEN+1]; + base16_encode(seen, sizeof(seen), (const char*)peer_id, DIGEST_LEN); + base16_encode(expected, sizeof(expected), conn->identity_digest, + DIGEST_LEN); + log_fn(severity, LD_HANDSHAKE, + "Tried connecting to router at %s:%d, but identity key was not " + "as expected: wanted %s but got %s.", + conn->_base.address, conn->_base.port, expected, seen); + entry_guard_register_connect_status(conn->identity_digest, 0, 1, + time(NULL)); + control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, + END_OR_CONN_REASON_OR_IDENTITY); + if (!authdir_mode_tests_reachability(options)) + control_event_bootstrap_problem("foo", END_OR_CONN_REASON_OR_IDENTITY); + as_expected = 0; + } + if (authdir_mode_tests_reachability(options)) { + dirserv_orconn_tls_done(conn->_base.address, conn->_base.port, + (const char*)peer_id, as_expected); } + if (!as_expected) + return -1; + return 0; } -/** The tls handshake is finished. +/** The v1/v2 TLS handshake is finished. * * Make sure we are happy with the person we just handshaked with. * @@ -1131,6 +1508,8 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, * If all is successful, call circuit_n_conn_done() to handle events * that have been pending on the <tls handshake completion. Also set the * directory to be dirty (only matters if I'm an authdirserver). + * + * If this is a v2 TLS handshake, send a versions cell. */ static int connection_tls_finish_handshake(or_connection_t *conn) @@ -1138,7 +1517,9 @@ 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,"tls handshake with %s done. verifying.", + log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done. verifying.", + started_here?"outgoing":"incoming", + conn, safe_str_client(conn->_base.address)); directory_set_dirty(); @@ -1158,25 +1539,48 @@ connection_tls_finish_handshake(or_connection_t *conn) tor_tls_block_renegotiation(conn->tls); return connection_or_set_state_open(conn); } else { - conn->_base.state = OR_CONN_STATE_OR_HANDSHAKING; + conn->_base.state = 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); } - return connection_or_send_versions(conn); + return connection_or_send_versions(conn, 0); } } +/** + * Called as client when initial TLS handshake is done, and we notice + * that we got a v3-handshake signalling certificate from the server. + * Set up structures, do bookkeeping, and send the versions cell. + * Return 0 on success and -1 on failure. + */ +static int +connection_or_launch_v3_or_handshake(or_connection_t *conn) +{ + tor_assert(connection_or_nonopen_was_started_here(conn)); + tor_assert(tor_tls_received_v3_certificate(conn->tls)); + + circuit_build_times_network_is_live(&circ_times); + + conn->_base.state = OR_CONN_STATE_OR_HANDSHAKING_V3; + if (connection_init_or_handshake_state(conn, 1) < 0) + return -1; + + return connection_or_send_versions(conn, 1); +} + /** Allocate a new connection handshake state for the connection * <b>conn</b>. Return 0 on success, -1 on failure. */ -static int +int connection_init_or_handshake_state(or_connection_t *conn, int started_here) { or_handshake_state_t *s; s = conn->handshake_state = tor_malloc_zero(sizeof(or_handshake_state_t)); s->started_here = started_here ? 1 : 0; + s->digest_sent_data = 1; + s->digest_received_data = 1; return 0; } @@ -1186,10 +1590,89 @@ or_handshake_state_free(or_handshake_state_t *state) { if (!state) return; + crypto_free_digest_env(state->digest_sent); + crypto_free_digest_env(state->digest_received); + tor_cert_free(state->auth_cert); + tor_cert_free(state->id_cert); memset(state, 0xBE, sizeof(or_handshake_state_t)); tor_free(state); } +/** + * Remember that <b>cell</b> has been transmitted (if <b>incoming</b> is + * false) or received (if <b>incoming is true) during a V3 handshake using + * <b>state</b>. + * + * (We don't record the cell, but we keep a digest of everything sent or + * received during the v3 handshake, and the client signs it in an + * authenticate cell.) + */ +void +or_handshake_state_record_cell(or_handshake_state_t *state, + const cell_t *cell, + int incoming) +{ + crypto_digest_env_t *d, **dptr; + packed_cell_t packed; + if (incoming) { + if (!state->digest_received_data) + return; + } else { + if (!state->digest_sent_data) + return; + } + if (!incoming) { + log_warn(LD_BUG, "We shouldn't be sending any non-variable-length cells " + "while making a handshake digest. But we think we are sending " + "one with type %d.", (int)cell->command); + } + dptr = incoming ? &state->digest_received : &state->digest_sent; + if (! *dptr) + *dptr = crypto_new_digest256_env(DIGEST_SHA256); + + d = *dptr; + /* Re-packing like this is a little inefficient, but we don't have to do + this very often at all. */ + cell_pack(&packed, cell); + crypto_digest_add_bytes(d, packed.body, sizeof(packed.body)); + memset(&packed, 0, sizeof(packed)); +} + +/** Remember that a variable-length <b>cell</b> has been transmitted (if + * <b>incoming</b> is false) or received (if <b>incoming is true) during a V3 + * handshake using <b>state</b>. + * + * (We don't record the cell, but we keep a digest of everything sent or + * received during the v3 handshake, and the client signs it in an + * authenticate cell.) + */ +void +or_handshake_state_record_var_cell(or_handshake_state_t *state, + const var_cell_t *cell, + int incoming) +{ + crypto_digest_env_t *d, **dptr; + char buf[VAR_CELL_HEADER_SIZE]; + if (incoming) { + if (!state->digest_received_data) + return; + } else { + if (!state->digest_sent_data) + return; + } + dptr = incoming ? &state->digest_received : &state->digest_sent; + if (! *dptr) + *dptr = crypto_new_digest256_env(DIGEST_SHA256); + + d = *dptr; + + var_cell_pack_header(cell, buf); + crypto_digest_add_bytes(d, buf, sizeof(buf)); + crypto_digest_add_bytes(d, (const char *)cell->payload, cell->payload_len); + + memset(buf, 0, sizeof(buf)); +} + /** Set <b>conn</b>'s state to OR_CONN_STATE_OPEN, and tell other subsystems * as appropriate. Called when we are done with all TLS and OR handshaking. */ @@ -1219,7 +1702,7 @@ connection_or_set_state_open(or_connection_t *conn) 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_digest(conn->identity_digest)) { + if (!router_get_by_id_digest(conn->identity_digest)) { if (tor_addr_family(&TO_CONN(conn)->addr) == AF_INET) { /*XXXX IP6 support ipv6 geoip.*/ uint32_t a = tor_addr_to_ipv4h(&TO_CONN(conn)->addr); @@ -1230,8 +1713,12 @@ connection_or_set_state_open(or_connection_t *conn) or_handshake_state_free(conn->handshake_state); conn->handshake_state = NULL; + IF_HAS_BUFFEREVENT(TO_CONN(conn), { + connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); + }) ELSE_IF_NO_BUFFEREVENT { + connection_start_reading(TO_CONN(conn)); + } - connection_start_reading(TO_CONN(conn)); circuit_n_conn_done(conn, 1); /* send the pending creates, if any. */ return 0; @@ -1253,6 +1740,9 @@ 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) + or_handshake_state_record_cell(conn->handshake_state, cell, 0); + if (cell->command != CELL_PADDING) conn->timestamp_last_added_nonpadding = approx_time(); } @@ -1272,16 +1762,24 @@ 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) + or_handshake_state_record_var_cell(conn->handshake_state, cell, 0); if (cell->command != CELL_PADDING) conn->timestamp_last_added_nonpadding = approx_time(); } -/** See whether there's a variable-length cell waiting on <b>conn</b>'s +/** See whether there's a variable-length cell waiting on <b>or_conn</b>'s * inbuf. Return values as for fetch_var_cell_from_buf(). */ static int -connection_fetch_var_cell_from_buf(or_connection_t *conn, var_cell_t **out) +connection_fetch_var_cell_from_buf(or_connection_t *or_conn, var_cell_t **out) { - return fetch_var_cell_from_buf(conn->_base.inbuf, out, conn->link_proto); + connection_t *conn = TO_CONN(or_conn); + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return fetch_var_cell_from_evbuffer(input, out, or_conn->link_proto); + }) ELSE_IF_NO_BUFFEREVENT { + return fetch_var_cell_from_buf(conn->inbuf, out, or_conn->link_proto); + } } /** Process cells from <b>conn</b>'s inbuf. @@ -1299,7 +1797,7 @@ 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)buf_datalen(conn->_base.inbuf), + 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) @@ -1310,8 +1808,8 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) } else { char buf[CELL_NETWORK_SIZE]; cell_t cell; - if (buf_datalen(conn->_base.inbuf) < CELL_NETWORK_SIZE) /* whole response - available? */ + if (connection_get_inbuf_len(TO_CONN(conn)) + < CELL_NETWORK_SIZE) /* whole response available? */ return 0; /* not yet */ circuit_build_times_network_is_live(&circ_times); @@ -1350,7 +1848,7 @@ connection_or_send_destroy(circid_t circ_id, or_connection_t *conn, int reason) } /** Array of recognized link protocol versions. */ -static const uint16_t or_protocol_versions[] = { 1, 2 }; +static const uint16_t or_protocol_versions[] = { 1, 2, 3 }; /** Number of versions in <b>or_protocol_versions</b>. */ static const int n_or_protocol_versions = (int)( sizeof(or_protocol_versions)/sizeof(uint16_t) ); @@ -1369,20 +1867,33 @@ is_or_protocol_version_known(uint16_t v) } /** Send a VERSIONS cell on <b>conn</b>, telling the other host about the - * link protocol versions that this Tor can support. */ -static int -connection_or_send_versions(or_connection_t *conn) + * link protocol versions that this Tor can support. + * + * If <b>v3_plus</b>, this is part of a V3 protocol handshake, so only + * allow protocol version v3 or later. If not <b>v3_plus</b>, this is + * not part of a v3 protocol handshake, so don't allow protocol v3 or + * later. + **/ +int +connection_or_send_versions(or_connection_t *conn, int v3_plus) { var_cell_t *cell; int i; + int n_versions = 0; + const int min_version = v3_plus ? 3 : 0; + const int max_version = v3_plus ? UINT16_MAX : 2; tor_assert(conn->handshake_state && !conn->handshake_state->sent_versions_at); cell = var_cell_new(n_or_protocol_versions * 2); cell->command = CELL_VERSIONS; for (i = 0; i < n_or_protocol_versions; ++i) { uint16_t v = or_protocol_versions[i]; - set_uint16(cell->payload+(2*i), htons(v)); + if (v < min_version || v > max_version) + continue; + set_uint16(cell->payload+(2*n_versions), htons(v)); + ++n_versions; } + cell->payload_len = n_versions * 2; connection_or_write_var_cell_to_buf(cell, conn); conn->handshake_state->sent_versions_at = time(NULL); @@ -1398,10 +1909,12 @@ connection_or_send_netinfo(or_connection_t *conn) { cell_t cell; time_t now = time(NULL); - routerinfo_t *me; + const routerinfo_t *me; int len; uint8_t *out; + tor_assert(conn->handshake_state); + memset(&cell, 0, sizeof(cell_t)); cell.command = CELL_NETINFO; @@ -1428,8 +1941,285 @@ connection_or_send_netinfo(or_connection_t *conn) *out = 0; } + conn->handshake_state->digest_sent_data = 0; connection_or_write_cell_to_buf(&cell, conn); return 0; } +/** Send a CERT cell on the connection <b>conn</b>. Return 0 on success, -1 + * on failure. */ +int +connection_or_send_cert_cell(or_connection_t *conn) +{ + const tor_cert_t *link_cert = NULL, *id_cert = NULL; + const uint8_t *link_encoded = NULL, *id_encoded = NULL; + size_t link_len, id_len; + var_cell_t *cell; + size_t cell_len; + ssize_t pos; + int server_mode; + + tor_assert(conn->_base.state == OR_CONN_STATE_OR_HANDSHAKING_V3); + + if (! conn->handshake_state) + return -1; + server_mode = ! conn->handshake_state->started_here; + if (tor_tls_get_my_certs(server_mode, &link_cert, &id_cert) < 0) + return -1; + tor_cert_get_der(link_cert, &link_encoded, &link_len); + tor_cert_get_der(id_cert, &id_encoded, &id_len); + + cell_len = 1 /* 1 byte: num certs in cell */ + + 2 * ( 1 + 2 ) /* For each cert: 1 byte for type, 2 for length */ + + link_len + id_len; + cell = var_cell_new(cell_len); + cell->command = CELL_CERT; + cell->payload[0] = 2; + pos = 1; + + if (server_mode) + cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */ + else + cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */ + set_uint16(&cell->payload[pos+1], htons(link_len)); + memcpy(&cell->payload[pos+3], link_encoded, link_len); + pos += 3 + link_len; + + cell->payload[pos] = OR_CERT_TYPE_ID_1024; /* ID cert */ + set_uint16(&cell->payload[pos+1], htons(id_len)); + memcpy(&cell->payload[pos+3], id_encoded, id_len); + pos += 3 + id_len; + + tor_assert(pos == (int)cell_len); /* Otherwise we just smashed the heap */ + + connection_or_write_var_cell_to_buf(cell, conn); + var_cell_free(cell); + + return 0; +} + +/** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0 + * on success, -1 on failure. */ +int +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); + + if (! conn->handshake_state) + return -1; + + if (crypto_rand((char*)challenge, OR_AUTH_CHALLENGE_LEN) < 0) + return -1; + cell = var_cell_new(OR_AUTH_CHALLENGE_LEN + 4); + cell->command = CELL_AUTH_CHALLENGE; + memcpy(cell->payload, challenge, OR_AUTH_CHALLENGE_LEN); + cp = cell->payload + OR_AUTH_CHALLENGE_LEN; + set_uint16(cp, htons(1)); /* We recognize one authentication type. */ + set_uint16(cp+2, htons(AUTHTYPE_RSA_SHA256_TLSSECRET)); + + connection_or_write_var_cell_to_buf(cell, conn); + var_cell_free(cell); + memset(challenge, 0, sizeof(challenge)); + + return 0; +} + +/** Compute the main body of an AUTHENTICATE cell that a client can use + * to authenticate itself on a v3 handshake for <b>conn</b>. Write it to the + * <b>outlen</b>-byte buffer at <b>out</b>. + * + * If <b>server</b> is true, only calculate the first + * V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's + * determined by the rest of the handshake, and which match the provided value + * exactly. + * + * If <b>server</b> is false and <b>signing_key</b> is NULL, calculate the + * first V3_AUTH_BODY_LEN bytes of the authenticator (that is, everything + * that should be signed), but don't actually sign it. + * + * If <b>server</b> is false and <b>signing_key</b> is provided, calculate the + * entire authenticator, signed with <b>signing_key</b>. + * DOCDOC return value + */ +int +connection_or_compute_authenticate_cell_body(or_connection_t *conn, + uint8_t *out, size_t outlen, + crypto_pk_env_t *signing_key, + int server) +{ + uint8_t *ptr; + + /* assert state is reasonable XXXX */ + + if (outlen < V3_AUTH_FIXED_PART_LEN || + (!server && outlen < V3_AUTH_BODY_LEN)) + return -1; + + ptr = out; + + /* Type: 8 bytes. */ + memcpy(ptr, "AUTH0001", 8); + ptr += 8; + + { + const tor_cert_t *id_cert=NULL, *link_cert=NULL; + const digests_t *my_digests, *their_digests; + const uint8_t *my_id, *their_id, *client_id, *server_id; + if (tor_tls_get_my_certs(0, &link_cert, &id_cert)) + return -1; + my_digests = tor_cert_get_id_digests(id_cert); + their_digests = tor_cert_get_id_digests(conn->handshake_state->id_cert); + tor_assert(my_digests); + tor_assert(their_digests); + my_id = (uint8_t*)my_digests->d[DIGEST_SHA256]; + their_id = (uint8_t*)their_digests->d[DIGEST_SHA256]; + + client_id = server ? their_id : my_id; + server_id = server ? my_id : their_id; + + /* Client ID digest: 32 octets. */ + memcpy(ptr, client_id, 32); + ptr += 32; + + /* Server ID digest: 32 octets. */ + memcpy(ptr, server_id, 32); + ptr += 32; + } + + { + crypto_digest_env_t *server_d, *client_d; + if (server) { + server_d = conn->handshake_state->digest_sent; + client_d = conn->handshake_state->digest_received; + } else { + client_d = conn->handshake_state->digest_sent; + server_d = conn->handshake_state->digest_received; + } + + /* Server log digest : 32 octets */ + crypto_digest_get_digest(server_d, (char*)ptr, 32); + ptr += 32; + + /* Client log digest : 32 octets */ + crypto_digest_get_digest(client_d, (char*)ptr, 32); + ptr += 32; + } + + { + /* Digest of cert used on TLS link : 32 octets. */ + const tor_cert_t *cert = NULL; + tor_cert_t *freecert = NULL; + if (server) { + tor_tls_get_my_certs(1, &cert, NULL); + } else { + freecert = tor_tls_get_peer_cert(conn->tls); + cert = freecert; + } + if (!cert) + return -1; + memcpy(ptr, tor_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32); + + if (freecert) + tor_cert_free(freecert); + ptr += 32; + } + + /* HMAC of clientrandom and serverrandom using master key : 32 octets */ + tor_tls_get_tlssecrets(conn->tls, ptr); + ptr += 32; + + tor_assert(ptr - out == V3_AUTH_FIXED_PART_LEN); + + if (server) + return V3_AUTH_FIXED_PART_LEN; // ptr-out + + /* Time: 8 octets. */ + { + uint64_t now = time(NULL); + if ((time_t)now < 0) + return -1; + set_uint32(ptr, htonl((uint32_t)(now>>32))); + set_uint32(ptr+4, htonl((uint32_t)now)); + ptr += 8; + } + + /* Nonce: 16 octets. */ + crypto_rand((char*)ptr, 16); + ptr += 16; + + tor_assert(ptr - out == V3_AUTH_BODY_LEN); + + if (!signing_key) + return V3_AUTH_BODY_LEN; // ptr - out + + { + int siglen; + char d[32]; + crypto_digest256(d, (char*)out, ptr-out, DIGEST_SHA256); + siglen = crypto_pk_private_sign(signing_key, + (char*)ptr, outlen - (ptr-out), + d, 32); + if (siglen < 0) + return -1; + + ptr += siglen; + tor_assert(ptr <= out+outlen); + return (int)(ptr - out); + } +} + +/** Send an AUTHENTICATE cell on the connection <b>conn</b>. Return 0 on + * success, -1 on failure */ +int +connection_or_send_authenticate_cell(or_connection_t *conn, int authtype) +{ + var_cell_t *cell; + crypto_pk_env_t *pk = tor_tls_get_my_client_auth_key(); + int authlen; + size_t cell_maxlen; + /* XXXX make sure we're actually supposed to send this! */ + + if (!pk) { + log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key"); + return -1; + } + if (authtype != AUTHTYPE_RSA_SHA256_TLSSECRET) { + log_warn(LD_BUG, "Tried to send authenticate cell with unknown " + "authentication type %d", authtype); + return -1; + } + + cell_maxlen = 4 + /* overhead */ + V3_AUTH_BODY_LEN + /* Authentication body */ + crypto_pk_keysize(pk) + /* Max signature length */ + 16 /* add a few extra bytes just in case. */; + + cell = var_cell_new(cell_maxlen); + cell->command = CELL_AUTHENTICATE; + set_uint16(cell->payload, htons(AUTHTYPE_RSA_SHA256_TLSSECRET)); + /* skip over length ; we don't know that yet. */ + + authlen = connection_or_compute_authenticate_cell_body(conn, + cell->payload+4, + cell_maxlen-4, + pk, + 0 /* not server */); + if (authlen < 0) { + log_warn(LD_BUG, "Unable to compute authenticate cell!"); + var_cell_free(cell); + return -1; + } + tor_assert(authlen + 4 <= cell->payload_len); + set_uint16(cell->payload+2, htons(authlen)); + cell->payload_len = authlen + 4; + + connection_or_write_var_cell_to_buf(cell, conn); + var_cell_free(cell); + + return 0; +} + diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 70ef96a335..df009ab39e 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -14,6 +14,7 @@ void connection_or_remove_from_identity_map(or_connection_t *conn); void connection_or_clear_identity_map(void); +void clear_broken_connection_map(int disable); or_connection_t *connection_or_get_for_extend(const char *digest, const tor_addr_t *target_addr, const char **msg_out, @@ -25,19 +26,40 @@ int connection_or_process_inbuf(or_connection_t *conn); int connection_or_flushed_some(or_connection_t *conn); int connection_or_finished_flushing(or_connection_t *conn); int connection_or_finished_connecting(or_connection_t *conn); +void connection_or_about_to_close(or_connection_t *conn); int connection_or_digest_is_known_relay(const char *id_digest); void connection_or_update_token_buckets(smartlist_t *conns, - or_options_t *options); + const or_options_t *options); void connection_or_connect_failed(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); +void connection_or_report_broken_states(int severity, int domain); + int connection_tls_start_handshake(or_connection_t *conn, int receiving); int connection_tls_continue_handshake(or_connection_t *conn); +int connection_init_or_handshake_state(or_connection_t *conn, + int started_here); +void connection_or_init_conn_from_address(or_connection_t *conn, + const tor_addr_t *addr, + uint16_t port, + const char *id_digest, + 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_env_t *identity_rcvd); 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, + int incoming); +void or_handshake_state_record_var_cell(or_handshake_state_t *state, + const var_cell_t *cell, + int incoming); + int connection_or_set_state_open(or_connection_t *conn); void connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn); @@ -45,7 +67,16 @@ 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_cert_cell(or_connection_t *conn); +int connection_or_send_auth_challenge_cell(or_connection_t *conn); +int connection_or_compute_authenticate_cell_body(or_connection_t *conn, + uint8_t *out, size_t outlen, + crypto_pk_env_t *signing_key, + int server); +int connection_or_send_authenticate_cell(or_connection_t *conn, int type); + int is_or_protocol_version_known(uint16_t version); void cell_pack(packed_cell_t *dest, const cell_t *src); diff --git a/src/or/control.c b/src/or/control.c index 1e411ec9c1..1fdbac281c 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -18,6 +18,7 @@ #include "config.h" #include "connection.h" #include "connection_edge.h" +#include "connection_or.h" #include "control.h" #include "directory.h" #include "dirserv.h" @@ -26,12 +27,18 @@ #include "hibernate.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "router.h" #include "routerlist.h" #include "routerparse.h" +#ifndef MS_WINDOWS +#include <pwd.h> +#include <sys/resource.h> +#endif + #include "procmon.h" /** Yield true iff <b>s</b> is the state of a control_connection_t that has @@ -66,7 +73,9 @@ #define EVENT_CLIENTS_SEEN 0x0015 #define EVENT_NEWCONSENSUS 0x0016 #define EVENT_BUILDTIMEOUT_SET 0x0017 -#define _EVENT_MAX 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. */ /** Bitfield: The bit 1<<e is set if <b>any</b> open control @@ -168,7 +177,7 @@ static int handle_control_resolve(control_connection_t *conn, uint32_t len, static int handle_control_usefeature(control_connection_t *conn, uint32_t len, const char *body); -static int write_stream_target_to_buf(edge_connection_t *conn, char *buf, +static int write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len); static void orconn_target_get_name(char *buf, size_t len, or_connection_t *conn); @@ -494,8 +503,8 @@ connection_printf_to_buf(control_connection_t *conn, const char *format, ...) va_end(ap); if (len < 0) { - log_warn(LD_BUG, "Unable to format string for controller."); - return; + log_err(LD_BUG, "Unable to format string for controller."); + tor_assert(0); } connection_write_to_buf(buf, (size_t)len, TO_CONN(conn)); @@ -509,7 +518,7 @@ control_ports_write_to_file(void) { smartlist_t *lines; char *joined = NULL; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!options->ControlPortWriteToFile) return; @@ -593,7 +602,7 @@ send_control_event_string(uint16_t event, event_format_t which, else if (event == EVENT_STATUS_SERVER) is_err = !strcmpstart(msg, "STATUS_SERVER ERR "); if (is_err) - connection_handle_write(TO_CONN(control_conn), 1); + connection_flush(TO_CONN(control_conn)); } } } SMARTLIST_FOREACH_END(conn); @@ -647,7 +656,7 @@ get_circ(const char *id) } /** Given a text stream <b>id</b>, return the corresponding AP connection. */ -static edge_connection_t * +static entry_connection_t * get_stream(const char *id) { uint64_t n_id; @@ -659,7 +668,7 @@ get_stream(const char *id) conn = connection_get_by_global_id(n_id); if (!conn || conn->type != CONN_TYPE_AP || conn->marked_for_close) return NULL; - return TO_EDGE_CONN(conn); + return TO_ENTRY_CONN(conn); } /** Helper for setconf and resetconf. Acts like setconf, except @@ -798,7 +807,7 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, smartlist_t *unrecognized = smartlist_create(); char *msg = NULL; size_t msg_len; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int i, len; (void) body_len; /* body is NUL-terminated; so we can ignore len. */ @@ -910,13 +919,45 @@ handle_control_loadconf(control_connection_t *conn, uint32_t len, return 0; } +struct control_event_t { + uint16_t event_code; + const char *event_name; +}; +static const struct control_event_t control_event_table[] = { + { EVENT_CIRCUIT_STATUS, "CIRC" }, + { EVENT_STREAM_STATUS, "STREAM" }, + { EVENT_OR_CONN_STATUS, "ORCONN" }, + { EVENT_BANDWIDTH_USED, "BW" }, + { EVENT_DEBUG_MSG, "DEBUG" }, + { EVENT_INFO_MSG, "INFO" }, + { EVENT_NOTICE_MSG, "NOTICE" }, + { EVENT_WARN_MSG, "WARN" }, + { EVENT_ERR_MSG, "ERR" }, + { EVENT_NEW_DESC, "NEWDESC" }, + { EVENT_ADDRMAP, "ADDRMAP" }, + { EVENT_AUTHDIR_NEWDESCS, "AUTHDIR_NEWDESCS" }, + { EVENT_DESCCHANGED, "DESCCHANGED" }, + { EVENT_NS, "NS" }, + { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, + { EVENT_STATUS_CLIENT, "STATUS_CLIENT" }, + { EVENT_STATUS_SERVER, "STATUS_SERVER" }, + { EVENT_GUARD, "GUARD" }, + { EVENT_STREAM_BANDWIDTH_USED, "STREAM_BW" }, + { EVENT_CLIENTS_SEEN, "CLIENTS_SEEN" }, + { EVENT_NEWCONSENSUS, "NEWCONSENSUS" }, + { EVENT_BUILDTIMEOUT_SET, "BUILDTIMEOUT_SET" }, + { EVENT_SIGNAL, "SIGNAL" }, + { EVENT_CONF_CHANGED, "CONF_CHANGED"}, + { 0, NULL }, +}; + /** Called when we get a SETEVENTS message: update conn->event_mask, * and reply with DONE or ERROR. */ static int handle_control_setevents(control_connection_t *conn, uint32_t len, const char *body) { - uint16_t event_code; + int event_code = -1; uint32_t event_mask = 0; smartlist_t *events = smartlist_create(); @@ -928,56 +969,22 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, { if (!strcasecmp(ev, "EXTENDED")) { continue; - } else if (!strcasecmp(ev, "CIRC")) - event_code = EVENT_CIRCUIT_STATUS; - else if (!strcasecmp(ev, "STREAM")) - event_code = EVENT_STREAM_STATUS; - else if (!strcasecmp(ev, "ORCONN")) - event_code = EVENT_OR_CONN_STATUS; - else if (!strcasecmp(ev, "BW")) - event_code = EVENT_BANDWIDTH_USED; - else if (!strcasecmp(ev, "DEBUG")) - event_code = EVENT_DEBUG_MSG; - else if (!strcasecmp(ev, "INFO")) - event_code = EVENT_INFO_MSG; - else if (!strcasecmp(ev, "NOTICE")) - event_code = EVENT_NOTICE_MSG; - else if (!strcasecmp(ev, "WARN")) - event_code = EVENT_WARN_MSG; - else if (!strcasecmp(ev, "ERR")) - event_code = EVENT_ERR_MSG; - else if (!strcasecmp(ev, "NEWDESC")) - event_code = EVENT_NEW_DESC; - else if (!strcasecmp(ev, "ADDRMAP")) - event_code = EVENT_ADDRMAP; - else if (!strcasecmp(ev, "AUTHDIR_NEWDESCS")) - event_code = EVENT_AUTHDIR_NEWDESCS; - else if (!strcasecmp(ev, "DESCCHANGED")) - event_code = EVENT_DESCCHANGED; - else if (!strcasecmp(ev, "NS")) - event_code = EVENT_NS; - else if (!strcasecmp(ev, "STATUS_GENERAL")) - event_code = EVENT_STATUS_GENERAL; - else if (!strcasecmp(ev, "STATUS_CLIENT")) - event_code = EVENT_STATUS_CLIENT; - else if (!strcasecmp(ev, "STATUS_SERVER")) - event_code = EVENT_STATUS_SERVER; - else if (!strcasecmp(ev, "GUARD")) - event_code = EVENT_GUARD; - else if (!strcasecmp(ev, "STREAM_BW")) - event_code = EVENT_STREAM_BANDWIDTH_USED; - else if (!strcasecmp(ev, "CLIENTS_SEEN")) - event_code = EVENT_CLIENTS_SEEN; - else if (!strcasecmp(ev, "NEWCONSENSUS")) - event_code = EVENT_NEWCONSENSUS; - else if (!strcasecmp(ev, "BUILDTIMEOUT_SET")) - event_code = EVENT_BUILDTIMEOUT_SET; - else { - connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", - ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); - return 0; + } else { + int i; + for (i = 0; control_event_table[i].event_name != NULL; ++i) { + if (!strcasecmp(ev, control_event_table[i].event_name)) { + event_code = control_event_table[i].event_code; + break; + } + } + + if (event_code == -1) { + connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", + ev); + SMARTLIST_FOREACH(events, char *, e, tor_free(e)); + smartlist_free(events); + return 0; + } } event_mask |= (1 << event_code); } @@ -1039,7 +1046,7 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, const char *body) { int used_quoted_string = 0; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); const char *errstr = NULL; char *password; size_t password_len; @@ -1248,7 +1255,7 @@ handle_control_signal(control_connection_t *conn, uint32_t len, send_control_done(conn); /* Flush the "done" first if the signal might make us shut down. */ if (sig == SIGTERM || sig == SIGINT) - connection_handle_write(TO_CONN(conn), 1); + connection_flush(TO_CONN(conn)); process_signal(sig); @@ -1377,11 +1384,16 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, } else if (!strcmp(question, "info/names")) { *answer = list_getinfo_options(); } else if (!strcmp(question, "events/names")) { - *answer = tor_strdup("CIRC STREAM ORCONN BW DEBUG INFO NOTICE WARN ERR " - "NEWDESC ADDRMAP AUTHDIR_NEWDESCS DESCCHANGED " - "NS STATUS_GENERAL STATUS_CLIENT STATUS_SERVER " - "GUARD STREAM_BW CLIENTS_SEEN NEWCONSENSUS " - "BUILDTIMEOUT_SET"); + int i; + smartlist_t *event_names = smartlist_create(); + + for (i = 0; control_event_table[i].event_name != NULL; ++i) { + smartlist_add(event_names, (char *)control_event_table[i].event_name); + } + + *answer = smartlist_join_strings(event_names, " ", 0, NULL); + + smartlist_free(event_names); } else if (!strcmp(question, "features/names")) { *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS"); } else if (!strcmp(question, "address")) { @@ -1391,6 +1403,61 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, return -1; } *answer = tor_dup_ip(addr); + } else if (!strcmp(question, "traffic/read")) { + tor_asprintf(answer, U64_FORMAT, U64_PRINTF_ARG(get_bytes_read())); + } else if (!strcmp(question, "traffic/written")) { + tor_asprintf(answer, U64_FORMAT, U64_PRINTF_ARG(get_bytes_written())); + } else if (!strcmp(question, "process/pid")) { + int myPid = -1; + + #ifdef MS_WINDOWS + myPid = _getpid(); + #else + myPid = getpid(); + #endif + + tor_asprintf(answer, "%d", myPid); + } else if (!strcmp(question, "process/uid")) { + #ifdef MS_WINDOWS + *answer = tor_strdup("-1"); + #else + int myUid = geteuid(); + tor_asprintf(answer, "%d", myUid); + #endif + } else if (!strcmp(question, "process/user")) { + #ifdef MS_WINDOWS + *answer = tor_strdup(""); + #else + int myUid = geteuid(); + struct passwd *myPwEntry = getpwuid(myUid); + + if (myPwEntry) { + *answer = tor_strdup(myPwEntry->pw_name); + } else { + *answer = tor_strdup(""); + } + #endif + } else if (!strcmp(question, "process/descriptor-limit")) { + /** platform specifc limits are from the set_max_file_descriptors function + * of src/common/compat.c */ + /* XXXX023 This is duplicated code from compat.c; it should turn into a + * function. */ + #ifdef HAVE_GETRLIMIT + struct rlimit descriptorLimit; + + if (getrlimit(RLIMIT_NOFILE, &descriptorLimit) == 0) { + tor_asprintf(answer, U64_FORMAT, + U64_PRINTF_ARG(descriptorLimit.rlim_max)); + } else { + *answer = tor_strdup("-1"); + } + #elif defined(CYGWIN) || defined(__CYGWIN__) + *answer = tor_strdup("3200"); + #elif defined(MS_WINDOWS) + *answer = tor_strdup("15000"); + #else + *answer = tor_strdup("15000"); + #endif } else if (!strcmp(question, "dir-usage")) { *answer = directory_dump_request_log(); } else if (!strcmp(question, "fingerprint")) { @@ -1416,8 +1483,9 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, * NOTE: <b>ri_body</b> is as returned by signed_descriptor_get_body: it might * not be NUL-terminated. */ static char * -munge_extrainfo_into_routerinfo(const char *ri_body, signed_descriptor_t *ri, - signed_descriptor_t *ei) +munge_extrainfo_into_routerinfo(const char *ri_body, + const signed_descriptor_t *ri, + const signed_descriptor_t *ei) { char *out = NULL, *outp; int i; @@ -1519,16 +1587,17 @@ getinfo_helper_dir(control_connection_t *control_conn, const char *question, char **answer, const char **errmsg) { + const routerinfo_t *ri; (void) control_conn; if (!strcmpstart(question, "desc/id/")) { - routerinfo_t *ri = router_get_by_hexdigest(question+strlen("desc/id/")); + ri = router_get_by_hexdigest(question+strlen("desc/id/")); if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); } } else if (!strcmpstart(question, "desc/name/")) { - routerinfo_t *ri = router_get_by_nickname(question+strlen("desc/name/"),1); + ri = router_get_by_nickname(question+strlen("desc/name/"),1); if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1538,7 +1607,7 @@ getinfo_helper_dir(control_connection_t *control_conn, routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_create(); if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, + SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) @@ -1554,7 +1623,7 @@ getinfo_helper_dir(control_connection_t *control_conn, routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_create(); if (routerlist && routerlist->routers) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, + SMARTLIST_FOREACH(routerlist->routers, const routerinfo_t *, ri, { const char *body = signed_descriptor_get_body(&ri->cache_info); signed_descriptor_t *ei = extrainfo_get_by_descriptor_digest( @@ -1572,8 +1641,8 @@ getinfo_helper_dir(control_connection_t *control_conn, SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); } else if (!strcmpstart(question, "desc-annotations/id/")) { - routerinfo_t *ri = router_get_by_hexdigest(question+ - strlen("desc-annotations/id/")); + ri = router_get_by_hexdigest(question+ + strlen("desc-annotations/id/")); if (ri) { const char *annotations = signed_descriptor_get_annotations(&ri->cache_info); @@ -1733,7 +1802,7 @@ getinfo_helper_events(control_connection_t *control_conn, char buf[256]; SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { const char *state; - edge_connection_t *conn; + entry_connection_t *conn; char *s; size_t slen; circuit_t *circ; @@ -1743,8 +1812,8 @@ getinfo_helper_events(control_connection_t *control_conn, base_conn->state == AP_CONN_STATE_SOCKS_WAIT || base_conn->state == AP_CONN_STATE_NATD_WAIT) continue; - conn = TO_EDGE_CONN(base_conn); - switch (conn->_base.state) + conn = TO_ENTRY_CONN(base_conn); + switch (base_conn->state) { case AP_CONN_STATE_CONTROLLER_WAIT: case AP_CONN_STATE_CIRCUIT_WAIT: @@ -1763,17 +1832,17 @@ getinfo_helper_events(control_connection_t *control_conn, state = "SUCCEEDED"; break; default: log_warn(LD_BUG, "Asked for stream in unknown state %d", - conn->_base.state); + base_conn->state); continue; } - circ = circuit_get_by_edge_conn(conn); + circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); if (circ && CIRCUIT_IS_ORIGIN(circ)) origin_circ = TO_ORIGIN_CIRCUIT(circ); write_stream_target_to_buf(conn, buf, sizeof(buf)); slen = strlen(buf)+strlen(state)+32; s = tor_malloc(slen+1); tor_snprintf(s, slen, "%lu %s %lu %s", - (unsigned long) conn->_base.global_identifier,state, + (unsigned long) base_conn->global_identifier,state, origin_circ? (unsigned long)origin_circ->global_identifier : 0ul, buf); @@ -1886,7 +1955,7 @@ getinfo_helper_events(control_connection_t *control_conn, } else if (!strcmp(question, "status/version/num-versioning") || !strcmp(question, "status/version/num-concurring")) { char s[33]; - tor_snprintf(s, sizeof(s), "%d", get_n_authorities(V3_AUTHORITY)); + tor_snprintf(s, sizeof(s), "%d", get_n_authorities(V3_DIRINFO)); *answer = tor_strdup(s); log_warn(LD_GENERAL, "%s is deprecated; it no longer gives useful " "information", question); @@ -2004,6 +2073,14 @@ static const getinfo_item_t getinfo_items[] = { "Number of versioning authorities agreeing on the status of the " "current version"), ITEM("address", misc, "IP address of this Tor host, if we can guess it."), + ITEM("traffic/read", misc,"Bytes read since the process was started."), + ITEM("traffic/written", misc, + "Bytes written since the process was started."), + ITEM("process/pid", misc, "Process id belonging to the main tor process."), + ITEM("process/uid", misc, "User id running the tor process."), + ITEM("process/user", misc, + "Username under which the tor process is running."), + ITEM("process/descriptor-limit", misc, "File descriptor limit."), ITEM("dir-usage", misc, "Breakdown of bytes transferred over DirPort."), PREFIX("desc-annotations/id/", dir, "Router annotations by hexdigest."), PREFIX("dir/server/", dir,"Router descriptors as retrieved from a DirPort."), @@ -2213,7 +2290,7 @@ static int handle_control_extendcircuit(control_connection_t *conn, uint32_t len, const char *body) { - smartlist_t *router_nicknames=NULL, *routers=NULL; + smartlist_t *router_nicknames=NULL, *nodes=NULL; origin_circuit_t *circ = NULL; int zero_circ; uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; @@ -2244,8 +2321,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, if ((smartlist_len(args) == 1) || (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" - circ = circuit_launch_by_router(intended_purpose, NULL, - CIRCLAUNCH_NEED_CAPACITY); + circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); if (!circ) { connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); } else { @@ -2273,17 +2349,21 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); smartlist_free(args); - routers = smartlist_create(); + nodes = smartlist_create(); SMARTLIST_FOREACH(router_nicknames, const char *, n, { - routerinfo_t *r = router_get_by_nickname(n, 1); - if (!r) { + const node_t *node = node_get_by_nickname(n, 1); + if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; } - smartlist_add(routers, r); + if (!node_has_descriptor(node)) { + connection_printf_to_buf(conn, "552 descriptor for \"%s\"\r\n", n); + goto done; + } + smartlist_add(nodes, (void*)node); }); - if (!smartlist_len(routers)) { + if (!smartlist_len(nodes)) { connection_write_str_to_buf("512 No router names provided\r\n", conn); goto done; } @@ -2294,9 +2374,10 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, } /* now circ refers to something that is ready to be extended */ - SMARTLIST_FOREACH(routers, routerinfo_t *, r, + SMARTLIST_FOREACH(nodes, const node_t *, node, { - extend_info_t *info = extend_info_from_router(r); + extend_info_t *info = extend_info_from_node(node); + tor_assert(info); /* True, since node_has_descriptor(node) == true */ circuit_append_new_exit(circ, info); extend_info_free(info); }); @@ -2330,7 +2411,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, done: SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); smartlist_free(router_nicknames); - smartlist_free(routers); + smartlist_free(nodes); return 0; } @@ -2381,7 +2462,7 @@ static int handle_control_attachstream(control_connection_t *conn, uint32_t len, const char *body) { - edge_connection_t *ap_conn = NULL; + entry_connection_t *ap_conn = NULL; origin_circuit_t *circ = NULL; int zero_circ; smartlist_t *args; @@ -2417,9 +2498,9 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, if (!ap_conn || (!zero_circ && !circ) || !hop_line_ok) return 0; - if (ap_conn->_base.state != AP_CONN_STATE_CONTROLLER_WAIT && - ap_conn->_base.state != AP_CONN_STATE_CONNECT_WAIT && - ap_conn->_base.state != AP_CONN_STATE_RESOLVE_WAIT) { + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && + ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_RESOLVE_WAIT) { connection_write_str_to_buf( "555 Connection is not managed by controller.\r\n", conn); @@ -2427,15 +2508,16 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, } /* Do we need to detach it first? */ - if (ap_conn->_base.state != AP_CONN_STATE_CONTROLLER_WAIT) { - circuit_t *tmpcirc = circuit_get_by_edge_conn(ap_conn); - connection_edge_end(ap_conn, END_STREAM_REASON_TIMEOUT); + if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); + circuit_t *tmpcirc = circuit_get_by_edge_conn(edge_conn); + connection_edge_end(edge_conn, END_STREAM_REASON_TIMEOUT); /* Un-mark it as ending, since we're going to reuse it. */ - ap_conn->edge_has_sent_end = 0; - ap_conn->end_reason = 0; + edge_conn->edge_has_sent_end = 0; + edge_conn->end_reason = 0; if (tmpcirc) - circuit_detach_stream(tmpcirc,ap_conn); - ap_conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT; + circuit_detach_stream(tmpcirc, edge_conn); + TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; } if (circ && (circ->_base.state != CIRCUIT_STATE_OPEN)) { @@ -2446,16 +2528,17 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, } /* Is this a single hop circuit? */ if (circ && (circuit_get_cpath_len(circ)<2 || hop==1)) { - routerinfo_t *r = NULL; - char* exit_digest; + const node_t *node = NULL; + char *exit_digest; if (circ->build_state && circ->build_state->chosen_exit && !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) { exit_digest = circ->build_state->chosen_exit->identity_digest; - r = router_get_by_digest(exit_digest); + node = node_get_by_id(exit_digest); } /* Do both the client and relay allow one-hop exit circuits? */ - if (!r || !r->allow_single_hop_exits || + if (!node || + !node_allows_single_hop_exits(node) || !get_options()->AllowSingleHopCircuits) { connection_write_str_to_buf( "551 Can't attach stream to this one-hop circuit.\r\n", conn); @@ -2556,7 +2639,7 @@ static int handle_control_redirectstream(control_connection_t *conn, uint32_t len, const char *body) { - edge_connection_t *ap_conn = NULL; + entry_connection_t *ap_conn = NULL; char *new_addr = NULL; uint16_t new_port = 0; smartlist_t *args; @@ -2604,7 +2687,7 @@ static int handle_control_closestream(control_connection_t *conn, uint32_t len, const char *body) { - edge_connection_t *ap_conn=NULL; + entry_connection_t *ap_conn=NULL; uint8_t reason=0; smartlist_t *args; int ok; @@ -2749,7 +2832,7 @@ handle_control_protocolinfo(control_connection_t *conn, uint32_t len, connection_mark_for_close(TO_CONN(conn)); goto done; } else { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int cookies = options->CookieAuthentication; char *cfile = get_cookie_file(); char *esc_cfile = esc_for_log(cfile); @@ -2827,8 +2910,6 @@ int connection_control_finished_flushing(control_connection_t *conn) { tor_assert(conn); - - connection_stop_writing(TO_CONN(conn)); return 0; } @@ -2900,6 +2981,17 @@ is_valid_initial_command(control_connection_t *conn, const char *cmd) * interfaces is broken. */ #define MAX_COMMAND_LINE_LENGTH (1024*1024) +static int +peek_connection_has_control0_command(connection_t *conn) +{ + IF_HAS_BUFFEREVENT(conn, { + struct evbuffer *input = bufferevent_get_input(conn->bufev); + return peek_evbuffer_has_control0_command(input); + }) ELSE_IF_NO_BUFFEREVENT { + return peek_buf_has_control0_command(conn->inbuf); + } +} + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -2922,7 +3014,7 @@ connection_control_process_inbuf(control_connection_t *conn) } if (conn->_base.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_buf_has_control0_command(conn->_base.inbuf)) { + peek_connection_has_control0_command(TO_CONN(conn))) { /* Detect v0 commands and send a "no more v0" message. */ size_t body_len; char buf[128]; @@ -2934,8 +3026,8 @@ connection_control_process_inbuf(control_connection_t *conn) body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + + connection_mark_and_flush(TO_CONN(conn)); return 0; } @@ -2946,7 +3038,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = fetch_from_buf_line(conn->_base.inbuf, + r = connection_fetch_from_buf_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -2956,8 +3048,7 @@ connection_control_process_inbuf(control_connection_t *conn) if (data_len + conn->incoming_cmd_cur_len > MAX_COMMAND_LINE_LENGTH) { connection_write_str_to_buf("500 Line too long.\r\n", conn); connection_stop_reading(TO_CONN(conn)); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } while (conn->incoming_cmd_len < data_len+conn->incoming_cmd_cur_len) conn->incoming_cmd_len *= 2; @@ -3017,8 +3108,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* Otherwise, Quit is always valid. */ if (!strcasecmp(conn->incoming_cmd, "QUIT")) { connection_write_str_to_buf("250 closing connection\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); return 0; } @@ -3177,7 +3267,7 @@ control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp, * <b>conn</b>, and write it to <b>buf</b>. Return 0 on success, -1 on * failure. */ static int -write_stream_target_to_buf(edge_connection_t *conn, char *buf, size_t len) +write_stream_target_to_buf(entry_connection_t *conn, char *buf, size_t len) { char buf2[256]; if (conn->chosen_exit_name) @@ -3188,8 +3278,8 @@ write_stream_target_to_buf(edge_connection_t *conn, char *buf, size_t len) if (tor_snprintf(buf, len, "%s%s%s:%d", conn->socks_request->address, conn->chosen_exit_name ? buf2 : "", - !conn->chosen_exit_name && - connection_edge_is_rendezvous_stream(conn) ? ".onion" : "", + !conn->chosen_exit_name && connection_edge_is_rendezvous_stream( + ENTRY_TO_EDGE_CONN(conn)) ? ".onion" : "", conn->socks_request->port)<0) return -1; return 0; @@ -3198,7 +3288,7 @@ write_stream_target_to_buf(edge_connection_t *conn, char *buf, size_t len) /** Something has happened to the stream associated with AP connection * <b>conn</b>: tell any interested control connections. */ int -control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, +control_event_stream_status(entry_connection_t *conn, stream_status_event_t tp, int reason_code) { char reason_buf[64]; @@ -3270,7 +3360,7 @@ control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, if (tp == STREAM_EVENT_NEW) { tor_snprintf(addrport_buf,sizeof(addrport_buf), " SOURCE_ADDR=%s:%d", - TO_CONN(conn)->address, TO_CONN(conn)->port ); + ENTRY_TO_CONN(conn)->address, ENTRY_TO_CONN(conn)->port); } else { addrport_buf[0] = '\0'; } @@ -3278,12 +3368,12 @@ control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, if (tp == STREAM_EVENT_NEW_RESOLVE) { purpose = " PURPOSE=DNS_REQUEST"; } else if (tp == STREAM_EVENT_NEW) { - if (conn->is_dns_request || + if (ENTRY_TO_EDGE_CONN(conn)->is_dns_request || (conn->socks_request && SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command))) purpose = " PURPOSE=DNS_REQUEST"; else if (conn->use_begindir) { - connection_t *linked = TO_CONN(conn)->linked_conn; + connection_t *linked = ENTRY_TO_CONN(conn)->linked_conn; int linked_dir_purpose = -1; if (linked && linked->type == CONN_TYPE_DIR) linked_dir_purpose = linked->purpose; @@ -3295,12 +3385,13 @@ control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, purpose = " PURPOSE=USER"; } - circ = circuit_get_by_edge_conn(conn); + circ = circuit_get_by_edge_conn(ENTRY_TO_EDGE_CONN(conn)); if (circ && CIRCUIT_IS_ORIGIN(circ)) origin_circ = TO_ORIGIN_CIRCUIT(circ); send_control_event(EVENT_STREAM_STATUS, ALL_FORMATS, "650 STREAM "U64_FORMAT" %s %lu %s%s%s%s\r\n", - U64_PRINTF_ARG(conn->_base.global_identifier), status, + U64_PRINTF_ARG(ENTRY_TO_CONN(conn)->global_identifier), + status, origin_circ? (unsigned long)origin_circ->global_identifier : 0ul, buf, reason_buf, addrport_buf, purpose); @@ -3316,10 +3407,10 @@ control_event_stream_status(edge_connection_t *conn, stream_status_event_t tp, static void orconn_target_get_name(char *name, size_t len, or_connection_t *conn) { - routerinfo_t *ri = router_get_by_digest(conn->identity_digest); - if (ri) { + const node_t *node = node_get_by_id(conn->identity_digest); + if (node) { tor_assert(len > MAX_VERBOSE_NICKNAME_LEN); - router_get_verbose_nickname(name, ri); + node_get_verbose_nickname(node, name); } else if (! tor_digest_is_zero(conn->identity_digest)) { name[0] = '$'; base16_encode(name+1, len-1, conn->identity_digest, @@ -3636,7 +3727,7 @@ control_event_networkstatus_changed_helper(smartlist_t *statuses, smartlist_add(strs, tor_strdup("650+")); smartlist_add(strs, tor_strdup(event_string)); smartlist_add(strs, tor_strdup("\r\n")); - SMARTLIST_FOREACH(statuses, routerstatus_t *, rs, + SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs, { s = networkstatus_getinfo_helper_single(rs); if (!s) continue; @@ -3724,10 +3815,46 @@ control_event_buildtimeout_set(const circuit_build_times_t *cbt, return 0; } +/** Called when a signal has been processed from signal_callback */ +int +control_event_signal(uintptr_t signal) +{ + const char *signal_string = NULL; + + if (!control_event_is_interesting(EVENT_SIGNAL)) + return 0; + + switch (signal) { + case SIGHUP: + signal_string = "RELOAD"; + break; + case SIGUSR1: + signal_string = "DUMP"; + break; + case SIGUSR2: + signal_string = "DEBUG"; + break; + case SIGNEWNYM: + signal_string = "NEWNYM"; + break; + case SIGCLEARDNSCACHE: + signal_string = "CLEARDNSCACHE"; + break; + default: + log_warn(LD_BUG, "Unrecognized signal %lu in control_event_signal", + (unsigned long)signal); + return -1; + } + + send_control_event(EVENT_SIGNAL, ALL_FORMATS, "650 SIGNAL %s\r\n", + signal_string); + return 0; +} + /** Called when a single local_routerstatus_t has changed: Sends an NS event * to any controller that cares. */ int -control_event_networkstatus_changed_single(routerstatus_t *rs) +control_event_networkstatus_changed_single(const routerstatus_t *rs) { smartlist_t *statuses; int r; @@ -3736,7 +3863,7 @@ control_event_networkstatus_changed_single(routerstatus_t *rs) return 0; statuses = smartlist_create(); - smartlist_add(statuses, rs); + smartlist_add(statuses, (void*)rs); r = control_event_networkstatus_changed(statuses); smartlist_free(statuses); return r; @@ -3861,9 +3988,9 @@ control_event_guard(const char *nickname, const char *digest, { char buf[MAX_VERBOSE_NICKNAME_LEN+1]; - routerinfo_t *ri = router_get_by_digest(digest); - if (ri) { - router_get_verbose_nickname(buf, ri); + const node_t *node = node_get_by_id(digest); + if (node) { + node_get_verbose_nickname(node, buf); } else { tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname); } @@ -3873,12 +4000,45 @@ control_event_guard(const char *nickname, const char *digest, return 0; } +/** Called when a configuration option changes. This is generally triggered + * by SETCONF requests and RELOAD/SIGHUP signals. The <b>elements</b> is + * a smartlist_t containing (key, value, ...) pairs in sequence. + * <b>value</b> can be NULL. */ +int +control_event_conf_changed(smartlist_t *elements) +{ + int i; + char *result; + smartlist_t *lines; + if (!EVENT_IS_INTERESTING(EVENT_CONF_CHANGED) || + smartlist_len(elements) == 0) { + return 0; + } + lines = smartlist_create(); + for (i = 0; i < smartlist_len(elements); i += 2) { + char *k = smartlist_get(elements, i); + char *v = smartlist_get(elements, i+1); + if (v == NULL) { + smartlist_asprintf_add(lines, "650-%s", k); + } else { + smartlist_asprintf_add(lines, "650-%s=%s", k, v); + } + } + result = smartlist_join_strings(lines, "\r\n", 0, NULL); + send_control_event(EVENT_CONF_CHANGED, 0, + "650-CONF_CHANGED\r\n%s\r\n650 OK\r\n", result); + tor_free(result); + SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); + smartlist_free(lines); + return 0; +} + /** Helper: Return a newly allocated string containing a path to the * file where we store our authentication cookie. */ static char * get_cookie_file(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->CookieAuthFile && strlen(options->CookieAuthFile)) { return tor_strdup(options->CookieAuthFile); } else { @@ -4146,6 +4306,7 @@ control_event_bootstrap_problem(const char *warn, int reason) const char *tag, *summary; char buf[BOOTSTRAP_MSG_LEN]; const char *recommendation = "ignore"; + int severity; /* bootstrap_percent must not be in "undefined" state here. */ tor_assert(status >= 0); @@ -4170,12 +4331,17 @@ control_event_bootstrap_problem(const char *warn, int reason) status--; /* find a recognized status string based on current progress */ status = bootstrap_percent; /* set status back to the actual number */ - log_fn(!strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO, + severity = !strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO; + + log_fn(severity, LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " "count %d; recommendation %s)", status, summary, warn, orconn_end_reason_to_control_string(reason), bootstrap_problems, recommendation); + + connection_or_report_broken_states(severity, LD_HANDSHAKE); + tor_snprintf(buf, sizeof(buf), "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " "COUNT=%d RECOMMENDATION=%s", diff --git a/src/or/control.h b/src/or/control.h index ddea4cd548..0d9acd26ef 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -37,7 +37,7 @@ int control_event_is_interesting(int event); int control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t e, int reason); -int control_event_stream_status(edge_connection_t *conn, +int control_event_stream_status(entry_connection_t *conn, stream_status_event_t e, int reason); int control_event_or_conn_status(or_connection_t *conn, @@ -57,7 +57,7 @@ int control_event_my_descriptor_changed(void); int control_event_networkstatus_changed(smartlist_t *statuses); int control_event_newconsensus(const networkstatus_t *consensus); -int control_event_networkstatus_changed_single(routerstatus_t *rs); +int control_event_networkstatus_changed_single(const routerstatus_t *rs); int control_event_general_status(int severity, const char *format, ...) CHECK_PRINTF(2,3); int control_event_client_status(int severity, const char *format, ...) @@ -66,8 +66,10 @@ int control_event_server_status(int severity, const char *format, ...) CHECK_PRINTF(2,3); int control_event_guard(const char *nickname, const char *digest, const char *status); +int control_event_conf_changed(smartlist_t *elements); int control_event_buildtimeout_set(const circuit_build_times_t *cbt, buildtimeout_set_event_t type); +int control_event_signal(uintptr_t signal); int init_cookie_authentication(int enabled); smartlist_t *decode_hashed_passwords(config_line_t *passwords); diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index c5e4863f7f..bf8964c29c 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -62,7 +62,6 @@ connection_cpu_finished_flushing(connection_t *conn) { tor_assert(conn); tor_assert(conn->type == CONN_TYPE_CPUWORKER); - connection_stop_writing(conn); return 0; } @@ -141,13 +140,13 @@ connection_cpu_process_inbuf(connection_t *conn) tor_assert(conn); tor_assert(conn->type == CONN_TYPE_CPUWORKER); - if (!buf_datalen(conn->inbuf)) + if (!connection_get_inbuf_len(conn)) return 0; if (conn->state == CPUWORKER_STATE_BUSY_ONION) { - if (buf_datalen(conn->inbuf) < LEN_ONION_RESPONSE) /* answer available? */ + if (connection_get_inbuf_len(conn) < LEN_ONION_RESPONSE) return 0; /* not yet */ - tor_assert(buf_datalen(conn->inbuf) == LEN_ONION_RESPONSE); + tor_assert(connection_get_inbuf_len(conn) == LEN_ONION_RESPONSE); connection_fetch_from_buf(&success,1,conn); connection_fetch_from_buf(buf,LEN_ONION_RESPONSE-1,conn); @@ -367,7 +366,7 @@ spawn_cpuworker(void) static void spawn_enough_cpuworkers(void) { - int num_cpuworkers_needed = get_options()->NumCPUs; + int num_cpuworkers_needed = get_num_cpus(get_options()); if (num_cpuworkers_needed < MIN_CPUWORKERS) num_cpuworkers_needed = MIN_CPUWORKERS; diff --git a/src/or/directory.c b/src/or/directory.c index e3cc70f91f..776b7a25f9 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -15,7 +15,9 @@ #include "dirvote.h" #include "geoip.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "rendclient.h" #include "rendcommon.h" @@ -78,6 +80,8 @@ static void dir_routerdesc_download_failed(smartlist_t *failed, int router_purpose, int was_extrainfo, int was_descriptor_digests); +static void dir_microdesc_download_failed(smartlist_t *failed, + int status_code); static void note_client_request(int purpose, int compressed, size_t bytes); static int client_likes_consensus(networkstatus_t *v, const char *want_url); @@ -137,26 +141,28 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS || dir_purpose == DIR_PURPOSE_FETCH_CERTIFICATE || dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC || - dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO) + dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO || + dir_purpose == DIR_PURPOSE_FETCH_MICRODESC) return 0; return 1; } -/** Return a newly allocated string describing <b>auth</b>. */ -char * -authority_type_to_string(authority_type_t auth) +/** Return a newly allocated string describing <b>auth</b>. Only describes + * authority features. */ +static char * +authdir_type_to_string(dirinfo_type_t auth) { char *result; smartlist_t *lst = smartlist_create(); - if (auth & V1_AUTHORITY) + if (auth & V1_DIRINFO) smartlist_add(lst, (void*)"V1"); - if (auth & V2_AUTHORITY) + if (auth & V2_DIRINFO) smartlist_add(lst, (void*)"V2"); - if (auth & V3_AUTHORITY) + if (auth & V3_DIRINFO) smartlist_add(lst, (void*)"V3"); - if (auth & BRIDGE_AUTHORITY) + if (auth & BRIDGE_DIRINFO) smartlist_add(lst, (void*)"Bridge"); - if (auth & HIDSERV_AUTHORITY) + if (auth & HIDSERV_DIRINFO) smartlist_add(lst, (void*)"Hidden service"); if (smartlist_len(lst)) { result = smartlist_join_strings(lst, ", ", 0, NULL); @@ -201,6 +207,8 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_MICRODESC: + return "microdescriptor fetch"; } log_warn(LD_BUG, "Called with unknown purpose %d", purpose); @@ -213,17 +221,19 @@ dir_conn_purpose_to_string(int purpose) int router_supports_extrainfo(const char *identity_digest, int is_authority) { - routerinfo_t *ri = router_get_by_digest(identity_digest); + const node_t *node = node_get_by_id(identity_digest); - if (ri) { - if (ri->caches_extra_info) + if (node && node->ri) { + if (node->ri->caches_extra_info) return 1; - if (is_authority && ri->platform && - tor_version_as_new_as(ri->platform, "Tor 0.2.0.0-alpha-dev (r10070)")) + 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) { - routerstatus_t *rs = router_get_consensus_status_by_id(identity_digest); + const routerstatus_t *rs = + router_get_consensus_status_by_id(identity_digest); if (rs && rs->version_supports_extrainfo_upload) return 1; } @@ -242,7 +252,7 @@ int directories_have_accepted_server_descriptor(void) { smartlist_t *servers = router_get_trusted_dir_servers(); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); SMARTLIST_FOREACH(servers, trusted_dir_server_t *, d, { if ((d->type & options->_PublishServerDescriptor) && d->has_accepted_serverdesc) { @@ -271,11 +281,11 @@ directories_have_accepted_server_descriptor(void) */ void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, - authority_type_t type, + dirinfo_type_t type, const char *payload, size_t payload_len, size_t extrainfo_len) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int post_via_tor; smartlist_t *dirservers = router_get_trusted_dir_servers(); int found = 0; @@ -296,8 +306,8 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, if (exclude_self && router_digest_is_me(ds->digest)) continue; - if (options->ExcludeNodes && options->StrictNodes && - routerset_contains_routerstatus(options->ExcludeNodes, rs)) { + if (options->StrictNodes && + routerset_contains_routerstatus(options->ExcludeNodes, rs, -1)) { log_warn(LD_DIR, "Wanted to contact authority '%s' for %s, but " "it's in our ExcludedNodes list and StrictNodes is set. " "Skipping.", @@ -324,7 +334,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, NULL, payload, upload_len, 0); } SMARTLIST_FOREACH_END(ds); if (!found) { - char *s = authority_type_to_string(type); + char *s = authdir_type_to_string(type); log_warn(LD_DIR, "Publishing server descriptor to directory authorities " "of type '%s', but no authorities of that type listed!", s); tor_free(s); @@ -341,39 +351,44 @@ void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, const char *resource, int pds_flags) { - routerstatus_t *rs = NULL; - or_options_t *options = get_options(); + const routerstatus_t *rs = NULL; + const or_options_t *options = get_options(); int prefer_authority = directory_fetches_from_authorities(options); int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); - authority_type_t type; + dirinfo_type_t type; time_t if_modified_since = 0; /* FFFF we could break this switch into its own function, and call * it elsewhere in directory.c. -RD */ switch (dir_purpose) { case DIR_PURPOSE_FETCH_EXTRAINFO: - type = EXTRAINFO_CACHE | - (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_AUTHORITY : - V3_AUTHORITY); + type = EXTRAINFO_DIRINFO | + (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_DIRINFO : + V3_DIRINFO); break; case DIR_PURPOSE_FETCH_V2_NETWORKSTATUS: - type = V2_AUTHORITY; + type = V2_DIRINFO; prefer_authority = 1; /* Only v2 authorities have these anyway. */ break; case DIR_PURPOSE_FETCH_SERVERDESC: - type = (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_AUTHORITY : - V3_AUTHORITY); + type = (router_purpose == ROUTER_PURPOSE_BRIDGE ? BRIDGE_DIRINFO : + V3_DIRINFO); break; case DIR_PURPOSE_FETCH_RENDDESC: - type = HIDSERV_AUTHORITY; + type = HIDSERV_DIRINFO; break; case DIR_PURPOSE_FETCH_STATUS_VOTE: case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: - type = V3_AUTHORITY; + case DIR_PURPOSE_FETCH_CERTIFICATE: + type = V3_DIRINFO; break; case DIR_PURPOSE_FETCH_CONSENSUS: - case DIR_PURPOSE_FETCH_CERTIFICATE: - type = V3_AUTHORITY; + type = V3_DIRINFO; + if (resource && !strcmp(resource,"microdesc")) + type |= MICRODESC_DIRINFO; + break; + case DIR_PURPOSE_FETCH_MICRODESC: + type = MICRODESC_DIRINFO; break; default: log_warn(LD_BUG, "Unexpected purpose %d", (int)dir_purpose); @@ -381,25 +396,42 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, } if (dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS) { - networkstatus_t *v = networkstatus_get_latest_consensus(); - if (v) - if_modified_since = v->valid_after + 180; + int flav = FLAV_NS; + networkstatus_t *v; + if (resource) + flav = networkstatus_parse_flavor_name(resource); + + if (flav != -1) { + /* IF we have a parsed consensus of this type, we can do an + * if-modified-time based on it. */ + v = networkstatus_get_latest_consensus_by_flavor(flav); + if (v) + if_modified_since = v->valid_after + 180; + } else { + /* Otherwise it might be a consensus we don't parse, but which we + * do cache. Look at the cached copy, perhaps. */ + cached_dir_t *cd = dirserv_get_consensus(resource ? resource : "ns"); + if (cd) + if_modified_since = cd->published + 180; + } } - if (!options->FetchServerDescriptors && type != HIDSERV_AUTHORITY) + if (!options->FetchServerDescriptors && type != HIDSERV_DIRINFO) return; if (!get_via_tor) { - if (options->UseBridges && type != BRIDGE_AUTHORITY) { + if (options->UseBridges && type != BRIDGE_DIRINFO) { /* want to ask a running bridge for which we have a descriptor. */ /* XXX023 we assume that all of our bridges can answer any * possible directory question. This won't be true forever. -RD */ /* It certainly is not true with conditional consensus downloading, * so, for now, never assume the server supports that. */ - routerinfo_t *ri = choose_random_entry(NULL); - if (ri) { + const node_t *node = choose_random_entry(NULL); + if (node && node->ri) { + /* every bridge has a routerinfo. */ tor_addr_t addr; - tor_addr_from_ipv4h(&addr, ri->addr); + 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 */ @@ -412,10 +444,11 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, "nodes are available yet."); return; } else { - if (prefer_authority || type == BRIDGE_AUTHORITY) { + if (prefer_authority || type == BRIDGE_DIRINFO) { /* only ask authdirservers, and don't ask myself */ rs = router_pick_trusteddirserver(type, pds_flags); - if (rs == NULL && (pds_flags & PDS_NO_EXISTING_SERVERDESC_FETCH)) { + if (rs == NULL && (pds_flags & (PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH))) { /* We don't want to fetch from any authorities that we're currently * fetching server descriptors from, and we got no match. Did we * get no match because all the authorities have connections @@ -423,7 +456,8 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, * return,) or because all the authorities are down or on fire or * unreachable or something (in which case we should go on with * our fallback code)? */ - pds_flags &= ~PDS_NO_EXISTING_SERVERDESC_FETCH; + pds_flags &= ~(PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH); rs = router_pick_trusteddirserver(type, pds_flags); if (rs) { log_debug(LD_DIR, "Deferring serverdesc fetch: all authorities " @@ -432,7 +466,7 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, } } } - if (!rs && type != BRIDGE_AUTHORITY) { + if (!rs && type != BRIDGE_DIRINFO) { /* anybody with a non-zero dirport will do */ rs = router_pick_directory_server(type, pds_flags); if (!rs) { @@ -449,7 +483,7 @@ directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, if (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC) { /* only ask hidserv authorities, any of them will do */ pds_flags |= PDS_IGNORE_FASCISTFIREWALL|PDS_ALLOW_SELF; - rs = router_pick_trusteddirserver(HIDSERV_AUTHORITY, pds_flags); + rs = router_pick_trusteddirserver(HIDSERV_DIRINFO, pds_flags); } else { /* anybody with a non-zero dirport will do. Disregard firewalls. */ pds_flags |= PDS_IGNORE_FASCISTFIREWALL; @@ -495,7 +529,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose, routerstatus_t *rs; if (router_digest_is_me(ds->digest)) continue; - if (!(ds->type & V3_AUTHORITY)) + if (!(ds->type & V3_DIRINFO)) continue; rs = &ds->fake_status; directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, @@ -506,7 +540,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose, /** Same as directory_initiate_command_routerstatus(), but accepts * rendezvous data to fetch a hidden service descriptor. */ void -directory_initiate_command_routerstatus_rend(routerstatus_t *status, +directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -516,21 +550,22 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, time_t if_modified_since, const rend_data_t *rend_query) { - or_options_t *options = get_options(); - routerinfo_t *router; + const or_options_t *options = get_options(); + const node_t *node; char address_buf[INET_NTOA_BUF_LEN+1]; struct in_addr in; const char *address; tor_addr_t addr; - router = router_get_by_digest(status->identity_digest); + node = node_get_by_id(status->identity_digest); - if (!router && anonymized_connection) { - log_info(LD_DIR, "Not sending anonymized request to directory %s; we " + if (!node && anonymized_connection) { + log_info(LD_DIR, "Not sending anonymized request to directory '%s'; we " "don't have its router descriptor.", routerstatus_describe(status)); return; - } else if (router) { - address = router->address; + } else if (node) { + node_get_address_string(node, address_buf, sizeof(address_buf)); + address = address_buf; } else { in.s_addr = htonl(status->addr); tor_inet_ntoa(&in, address_buf, sizeof(address_buf)); @@ -539,7 +574,7 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, tor_addr_from_ipv4h(&addr, status->addr); if (options->ExcludeNodes && options->StrictNodes && - routerset_contains_routerstatus(options->ExcludeNodes, status)) { + routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) { log_warn(LD_DIR, "Wanted to contact directory mirror %s for %s, but " "it's in our ExcludedNodes list and StrictNodes is set. " "Skipping. This choice might make your Tor not work.", @@ -574,7 +609,7 @@ directory_initiate_command_routerstatus_rend(routerstatus_t *status, * want to fetch. */ void -directory_initiate_command_routerstatus(routerstatus_t *status, +directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -598,7 +633,7 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn) { if (conn->requested_resource && !strcmpstart(conn->requested_resource,"authority")) { - routerinfo_t *me = router_get_my_routerinfo(); + 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*/ @@ -612,7 +647,7 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn) * server due to a network error: Mark the router as down and try again if * possible. */ -void +static void connection_dir_request_failed(dir_connection_t *conn) { if (directory_conn_is_self_reachability_test(conn)) { @@ -626,15 +661,18 @@ connection_dir_request_failed(dir_connection_t *conn) connection_dir_download_v2_networkstatus_failed(conn, -1); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_SERVERDESC || conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) { - log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", + log_info(LD_DIR, "Giving up on serverdesc/extrainfo fetch from " + "directory server at '%s'; retrying", 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) { - networkstatus_consensus_download_failed(0); + if (conn->requested_resource) + networkstatus_consensus_download_failed(0, conn->requested_resource); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CERTIFICATE) { - log_info(LD_DIR, "Giving up on directory server at '%s'; retrying", + log_info(LD_DIR, "Giving up on certificate fetch from directory server " + "at '%s'; retrying", conn->_base.address); connection_dir_download_cert_failed(conn, 0); } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) { @@ -643,6 +681,10 @@ connection_dir_request_failed(dir_connection_t *conn) } 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) { + log_info(LD_DIR, "Giving up on downloading microdescriptors from " + " directory server at '%s'; will retry", conn->_base.address); + connection_dir_download_routerdesc_failed(conn); } } @@ -713,7 +755,8 @@ 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_EXTRAINFO || + conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); (void) conn; } @@ -776,7 +819,7 @@ connection_dir_download_cert_failed(dir_connection_t *conn, int status) * 3) Else yes. */ static int -directory_command_should_use_begindir(or_options_t *options, +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) @@ -816,6 +859,20 @@ directory_initiate_command(const char *address, const tor_addr_t *_addr, if_modified_since, NULL); } +/** Return non-zero iff a directory connection with purpose + * <b>dir_purpose</b> reveals sensitive information about a Tor + * instance's client activities. (Such connections must be performed + * through normal three-hop Tor circuits.) */ +static int +is_sensitive_dir_purpose(uint8_t dir_purpose) +{ + return ((dir_purpose == DIR_PURPOSE_FETCH_RENDDESC) || + (dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC) || + (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC) || + (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) || + (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)); +} + /** Same as directory_initiate_command(), but accepts rendezvous data to * fetch a hidden service descriptor. */ static void @@ -831,7 +888,7 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, const rend_data_t *rend_query) { dir_connection_t *conn; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int socket_error = 0; int use_begindir = supports_begindir && directory_command_should_use_begindir(options, _addr, @@ -850,6 +907,9 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose)); + tor_assert(!(is_sensitive_dir_purpose(dir_purpose) && + !anonymized_connection)); + /* ensure that we don't make direct connections when a SOCKS server is * configured. */ if (!anonymized_connection && !use_begindir && !options->HTTPProxy && @@ -911,7 +971,11 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, error indicates broken link in windowsland. */ } } else { /* we want to connect via a tor connection */ - edge_connection_t *linked_conn; + entry_connection_t *linked_conn; + /* Anonymized tunneled connections can never share a circuit. + * One-hop directory connections can share circuits with each other + * but nothing else. */ + int iso_flags = anonymized_connection ? ISO_STREAM : ISO_SESSIONGRP; /* If it's an anonymized connection, remember the fact that we * wanted it for later: maybe we'll want it again soon. */ @@ -925,14 +989,16 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, * hook up both sides */ linked_conn = - connection_ap_make_link(conn->_base.address, conn->_base.port, - digest, use_begindir, conn->dirconn_direct); + connection_ap_make_link(TO_CONN(conn), + conn->_base.address, conn->_base.port, + digest, + SESSION_GROUP_DIRCONN, iso_flags, + use_begindir, conn->dirconn_direct); if (!linked_conn) { log_warn(LD_NET,"Making tunnel to dirserver failed."); connection_mark_for_close(TO_CONN(conn)); return; } - connection_link_connections(TO_CONN(conn), TO_CONN(linked_conn)); if (connection_add(TO_CONN(conn)) < 0) { log_warn(LD_NET,"Unable to add connection for link to dirserver."); @@ -945,8 +1011,13 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr, payload, payload_len, supports_conditional_consensus, if_modified_since); + connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT); - connection_start_reading(TO_CONN(linked_conn)); + IF_HAS_BUFFEREVENT(ENTRY_TO_CONN(linked_conn), { + connection_watch_events(ENTRY_TO_CONN(linked_conn), + READ_EVENT|WRITE_EVENT); + }) ELSE_IF_NO_BUFFEREVENT + connection_start_reading(ENTRY_TO_CONN(linked_conn)); } } @@ -984,12 +1055,22 @@ _compare_strs(const void **a, const void **b) * This url depends on whether or not the server we go to * is sufficiently new to support conditional consensus downloading, * i.e. GET .../consensus/<b>fpr</b>+<b>fpr</b>+<b>fpr</b> + * + * If 'resource' is provided, it is the name of a consensus flavor to request. */ static char * -directory_get_consensus_url(int supports_conditional_consensus) +directory_get_consensus_url(int supports_conditional_consensus, + const char *resource) { - char *url; - size_t len; + char *url = NULL; + const char *hyphen, *flavor; + if (resource==NULL || strcmp(resource, "ns")==0) { + flavor = ""; /* Request ns consensuses as "", so older servers will work*/ + hyphen = ""; + } else { + flavor = resource; + hyphen = "-"; + } if (supports_conditional_consensus) { char *authority_id_list; @@ -999,7 +1080,7 @@ directory_get_consensus_url(int supports_conditional_consensus) trusted_dir_server_t *, ds, { char *hex; - if (!(ds->type & V3_AUTHORITY)) + if (!(ds->type & V3_DIRINFO)) continue; hex = tor_malloc(2*CONDITIONAL_CONSENSUS_FPR_LEN+1); @@ -1011,16 +1092,15 @@ directory_get_consensus_url(int supports_conditional_consensus) authority_id_list = smartlist_join_strings(authority_digests, "+", 0, NULL); - len = strlen(authority_id_list)+64; - url = tor_malloc(len); - tor_snprintf(url, len, "/tor/status-vote/current/consensus/%s.z", - authority_id_list); + tor_asprintf(&url, "/tor/status-vote/current/consensus%s%s/%s.z", + hyphen, flavor, authority_id_list); SMARTLIST_FOREACH(authority_digests, char *, cp, tor_free(cp)); smartlist_free(authority_digests); tor_free(authority_id_list); } else { - url = tor_strdup("/tor/status-vote/current/consensus.z"); + tor_asprintf(&url, "/tor/status-vote/current/consensus%s%s.z", + hyphen, flavor); } return url; } @@ -1036,11 +1116,11 @@ directory_send_command(dir_connection_t *conn, time_t if_modified_since) { char proxystring[256]; - char proxyauthstring[256]; char hoststring[128]; - char imsstring[RFC1123_TIME_LEN+32]; + smartlist_t *headers = smartlist_create(); char *url; char request[8192]; + char *header; const char *httpcommand = NULL; size_t len; @@ -1060,12 +1140,11 @@ directory_send_command(dir_connection_t *conn, } /* Format if-modified-since */ - if (!if_modified_since) { - imsstring[0] = '\0'; - } else { + if (if_modified_since) { char b[RFC1123_TIME_LEN+1]; format_rfc1123_time(b, if_modified_since); - tor_snprintf(imsstring, sizeof(imsstring), "\r\nIf-Modified-Since: %s", b); + tor_asprintf(&header, "If-Modified-Since: %s\r\n", b); + smartlist_add(headers, header); } /* come up with some proxy lines, if we're using one. */ @@ -1080,16 +1159,14 @@ directory_send_command(dir_connection_t *conn, log_warn(LD_BUG, "Encoding http authenticator failed"); } if (base64_authenticator) { - tor_snprintf(proxyauthstring, sizeof(proxyauthstring), - "\r\nProxy-Authorization: Basic %s", + tor_asprintf(&header, + "Proxy-Authorization: Basic %s\r\n", base64_authenticator); tor_free(base64_authenticator); - } else { - proxyauthstring[0] = 0; + smartlist_add(headers, header); } } else { proxystring[0] = 0; - proxyauthstring[0] = 0; } switch (purpose) { @@ -1101,10 +1178,11 @@ directory_send_command(dir_connection_t *conn, tor_snprintf(url, len, "/tor/status/%s", resource); break; case DIR_PURPOSE_FETCH_CONSENSUS: - tor_assert(!resource); + /* resource is optional. If present, it's a flavor name */ tor_assert(!payload); httpcommand = "GET"; - url = directory_get_consensus_url(supports_conditional_consensus); + url = directory_get_consensus_url(supports_conditional_consensus, + resource); log_info(LD_DIR, "Downloading consensus from %s using %s", hoststring, url); break; @@ -1144,12 +1222,23 @@ directory_send_command(dir_connection_t *conn, url = tor_malloc(len); tor_snprintf(url, len, "/tor/extra/%s", resource); break; - case DIR_PURPOSE_UPLOAD_DIR: + case DIR_PURPOSE_FETCH_MICRODESC: + tor_assert(resource); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/micro/%s", resource); + break; + case DIR_PURPOSE_UPLOAD_DIR: { + const char *why = router_get_descriptor_gen_reason(); tor_assert(!resource); tor_assert(payload); httpcommand = "POST"; url = tor_strdup("/tor/"); + if (why) { + tor_asprintf(&header, "X-Desc-Gen-Reason: %s\r\n", why); + smartlist_add(headers, header); + } break; + } case DIR_PURPOSE_UPLOAD_VOTE: tor_assert(!resource); tor_assert(payload); @@ -1200,26 +1289,26 @@ directory_send_command(dir_connection_t *conn, connection_write_to_buf(url, strlen(url), TO_CONN(conn)); tor_free(url); - if (!strcmp(httpcommand, "GET") && !payload) { - tor_snprintf(request, sizeof(request), - " HTTP/1.0\r\nHost: %s%s%s\r\n\r\n", - hoststring, - imsstring, - proxyauthstring); - } else { - tor_snprintf(request, sizeof(request), - " HTTP/1.0\r\nContent-Length: %lu\r\nHost: %s%s%s\r\n\r\n", - payload ? (unsigned long)payload_len : 0, - hoststring, - imsstring, - proxyauthstring); + if (!strcmp(httpcommand, "POST") || payload) { + tor_asprintf(&header, "Content-Length: %lu\r\n", + payload ? (unsigned long)payload_len : 0); + smartlist_add(headers, header); } + + header = smartlist_join_strings(headers, "", 0, NULL); + tor_snprintf(request, sizeof(request), " HTTP/1.0\r\nHost: %s\r\n%s\r\n", + hoststring, header); + tor_free(header); + connection_write_to_buf(request, strlen(request), TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ connection_write_to_buf(payload, payload_len, TO_CONN(conn)); } + + SMARTLIST_FOREACH(headers, char *, h, tor_free(h)); + smartlist_free(headers); } /** Parse an HTTP request string <b>headers</b> of the form @@ -1419,6 +1508,9 @@ body_is_plausible(const char *body, size_t len, int purpose) return 1; /* empty bodies don't need decompression */ if (len < 32) return 0; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + return (!strcmpstart(body,"onion-key")); + } if (purpose != DIR_PURPOSE_FETCH_RENDDESC) { if (!strcmpstart(body,"router") || !strcmpstart(body,"signed-directory") || @@ -1495,11 +1587,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) 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_EXTRAINFO || + conn->_base.purpose == DIR_PURPOSE_FETCH_MICRODESC); int was_compressed=0; time_t now = time(NULL); - switch (fetch_from_buf_http(conn->_base.inbuf, + switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, &body, &body_len, MAX_DIR_DL_SIZE, allow_partial)) { @@ -1574,13 +1667,14 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (status_code == 503) { routerstatus_t *rs; trusted_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); - if ((rs = router_get_consensus_status_by_id(conn->identity_digest))) + if ((rs = router_get_mutable_consensus_status_by_id(id_digest))) rs->last_dir_503_at = now; - if ((ds = router_get_trusteddirserver_by_digest(conn->identity_digest))) + if ((ds = router_get_trusteddirserver_by_digest(id_digest))) ds->fake_status.last_dir_503_at = now; tor_free(body); tor_free(headers); tor_free(reason); @@ -1719,6 +1813,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) { int r; + const char *flavname = conn->requested_resource; if (status_code != 200) { int severity = (status_code == 304) ? LOG_INFO : LOG_WARN; log(severity, LD_DIR, @@ -1727,22 +1822,24 @@ connection_dir_client_reached_eof(dir_connection_t *conn) 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); + 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); - if ((r=networkstatus_set_current_consensus(body, "ns", 0))<0) { + if ((r=networkstatus_set_current_consensus(body, flavname, 0))<0) { log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR, - "Unable to load consensus directory downloaded from " + "Unable to load %s consensus directory downloaded from " "server '%s:%d'. I'll try again soon.", - 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); + networkstatus_consensus_download_failed(0, flavname); return -1; } /* launches router downloads as needed */ routers_update_all_from_networkstatus(now, 3); + update_microdescs_from_networkstatus(now); + update_microdesc_downloads(now); directory_info_has_arrived(now, 0); log_info(LD_DIR, "Successfully loaded consensus."); } @@ -1892,6 +1989,43 @@ 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) { + smartlist_t *which = NULL; + log_info(LD_DIR,"Received answer to microdescriptor request (status %d, " + "size %d) from server '%s:%d'", + status_code, (int)body_len, conn->_base.address, + conn->_base.port); + tor_assert(conn->requested_resource && + !strcmpstart(conn->requested_resource, "d/")); + which = smartlist_create(); + dir_split_resource_into_fingerprints(conn->requested_resource+2, + which, NULL, + DSR_DIGEST256|DSR_BASE64); + if (status_code != 200) { + log_info(LD_DIR, "Received status code %d (%s) from server " + "'%s:%d' while fetching \"/tor/micro/%s\". I'll try again " + "soon.", + status_code, escaped(reason), conn->_base.address, + (int)conn->_base.port, conn->requested_resource); + dir_microdesc_download_failed(which, status_code); + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + tor_free(body); tor_free(headers); tor_free(reason); + return 0; + } else { + smartlist_t *mds; + mds = microdescs_add_to_cache(get_microdesc_cache(), + body, body+body_len, SAVED_NOWHERE, 0, + now, which); + if (smartlist_len(which)) { + /* Mark remaining ones as failed. */ + dir_microdesc_download_failed(which, status_code); + } + SMARTLIST_FOREACH(which, char *, cp, tor_free(cp)); + smartlist_free(which); + smartlist_free(mds); + } + } if (conn->_base.purpose == DIR_PURPOSE_UPLOAD_DIR) { switch (status_code) { @@ -2166,7 +2300,7 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } - if (buf_datalen(conn->_base.inbuf) > MAX_DIRECTORY_OBJECT_SIZE) { + if (connection_get_inbuf_len(TO_CONN(conn)) > MAX_DIRECTORY_OBJECT_SIZE) { log_warn(LD_HTTP, "Too much data received from directory connection: " "denial of service attempt, or you need to upgrade?"); connection_mark_for_close(TO_CONN(conn)); @@ -2178,6 +2312,28 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } +/** Called when we're about to finally unlink and free a directory connection: + * perform necessary accounting and cleanup */ +void +connection_dir_about_to_close(dir_connection_t *dir_conn) +{ + connection_t *conn = TO_CONN(dir_conn); + + if (conn->state < DIR_CONN_STATE_CLIENT_FINISHED) { + /* It's a directory connection and connecting or fetching + * failed: forget about this router, and maybe try again. */ + connection_dir_request_failed(dir_conn); + } + /* If we were trying to fetch a v2 rend desc and did not succeed, + * retry as needed. (If a fetch is successful, the connection state + * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that + * refetching is unnecessary.) */ + if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && + dir_conn->rend_data && + strlen(dir_conn->rend_data->onion_address) == REND_SERVICE_ID_LEN_BASE32) + rend_client_refetch_v2_renddesc(dir_conn->rend_data); +} + /** Create an http response for the client <b>conn</b> out of * <b>status</b> and <b>reason_phrase</b>. Write it to <b>conn</b>. */ @@ -2469,18 +2625,18 @@ client_likes_consensus(networkstatus_t *v, const char *want_url) * Always return 0. */ static int directory_handle_command_get(dir_connection_t *conn, const char *headers, - const char *body, size_t body_len) + const char *req_body, size_t req_body_len) { size_t dlen; char *url, *url_mem, *header; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); time_t if_modified_since = 0; int compressed; size_t url_len; /* We ignore the body of a GET request. */ - (void)body; - (void)body_len; + (void)req_body; + (void)req_body_len; log_debug(LD_DIRSERV,"Received GET command."); @@ -3189,7 +3345,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, const char *body, size_t body_len) { char *url = NULL; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); log_debug(LD_DIRSERV,"Received POST command."); @@ -3333,7 +3489,7 @@ directory_handle_command(dir_connection_t *conn) tor_assert(conn); tor_assert(conn->_base.type == CONN_TYPE_DIR); - switch (fetch_from_buf_http(conn->_base.inbuf, + switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, &body, &body_len, MAX_DIR_UL_SIZE, 0)) { case -1: /* overflow */ @@ -3386,14 +3542,27 @@ connection_dir_finished_flushing(dir_connection_t *conn) DIRREQ_DIRECT, DIRREQ_FLUSHING_DIR_CONN_FINISHED); 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; - connection_stop_writing(TO_CONN(conn)); return 0; case DIR_CONN_STATE_SERVER_WRITING: - log_debug(LD_DIRSERV,"Finished writing server response. Closing."); - connection_mark_for_close(TO_CONN(conn)); + if (conn->dir_spool_src != DIR_SPOOL_NONE) { +#ifdef USE_BUFFEREVENTS + /* This can happen with paired bufferevents, since a paired connection + * can flush immediately when you write to it, making the subsequent + * check in connection_handle_write_cb() decide that the connection + * is flushed. */ + log_debug(LD_DIRSERV, "Emptied a dirserv buffer, but still spooling."); +#else + log_warn(LD_BUG, "Emptied a dirserv buffer, but it's still spooling!"); + connection_mark_for_close(TO_CONN(conn)); +#endif + } else { + log_debug(LD_DIRSERV, "Finished writing server response. Closing."); + connection_mark_for_close(TO_CONN(conn)); + } return 0; default: log_warn(LD_BUG,"called in unexpected state %d.", @@ -3619,6 +3788,36 @@ dir_routerdesc_download_failed(smartlist_t *failed, int status_code, * every 10 or 60 seconds (FOO_DESCRIPTOR_RETRY_INTERVAL) in main.c. */ } +/* DOCDOC NM */ +static void +dir_microdesc_download_failed(smartlist_t *failed, + int status_code) +{ + networkstatus_t *consensus + = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + routerstatus_t *rs; + download_status_t *dls; + time_t now = time(NULL); + int server = directory_fetches_from_authorities(get_options()); + + if (! consensus) + return; + SMARTLIST_FOREACH_BEGIN(failed, const char *, d) { + rs = router_get_mutable_consensus_status_by_descriptor_digest(consensus,d); + if (!rs) + continue; + dls = &rs->dl_status; + if (dls->n_download_failures >= MAX_MICRODESC_DOWNLOAD_FAILURES) + continue; + { + char buf[BASE64_DIGEST256_LEN+1]; + digest256_to_base64(buf, d); + download_status_increment_failure(dls, status_code, buf, + server, now); + } + } SMARTLIST_FOREACH_END(d); +} + /** Helper. Compare two fp_pair_t objects, and return negative, 0, or * positive as appropriate. */ static int diff --git a/src/or/directory.h b/src/or/directory.h index 94dfb17644..8c63bb5dfd 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -13,9 +13,8 @@ #define _TOR_DIRECTORY_H int directories_have_accepted_server_descriptor(void); -char *authority_type_to_string(authority_type_t auth); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, - authority_type_t type, const char *payload, + dirinfo_type_t type, const char *payload, size_t payload_len, size_t extrainfo_len); void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, const char *resource, @@ -23,7 +22,7 @@ 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); -void directory_initiate_command_routerstatus(routerstatus_t *status, +void directory_initiate_command_routerstatus(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -31,7 +30,7 @@ void directory_initiate_command_routerstatus(routerstatus_t *status, const char *payload, size_t payload_len, time_t if_modified_since); -void directory_initiate_command_routerstatus_rend(routerstatus_t *status, +void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, int anonymized_connection, @@ -49,7 +48,7 @@ int connection_dir_reached_eof(dir_connection_t *conn); int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); int connection_dir_finished_connecting(dir_connection_t *conn); -void connection_dir_request_failed(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, diff --git a/src/or/dirserv.c b/src/or/dirserv.c index c427fe2ef3..288fca99b8 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -16,6 +16,7 @@ #include "hibernate.h" #include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "rephist.h" #include "router.h" @@ -67,8 +68,6 @@ static char *format_versions_list(config_line_t *ln); struct authdir_config_t; static int add_fingerprint_to_dir(const char *nickname, const char *fp, struct authdir_config_t *list); -static uint32_t dirserv_router_get_status(const routerinfo_t *router, - const char **msg); static uint32_t dirserv_get_status_impl(const char *fp, const char *nickname, const char *address, @@ -76,7 +75,8 @@ dirserv_get_status_impl(const char *fp, const char *nickname, const char *platform, const char *contact, const char **msg, int should_log); static void clear_cached_dir(cached_dir_t *d); -static signed_descriptor_t *get_signed_descriptor_by_fp(const char *fp, +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); @@ -212,7 +212,7 @@ dirserv_load_fingerprint_file(void) authdir_config_t *fingerprint_list_new; int result; config_line_t *front=NULL, *list; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); fname = get_datadir_fname("approved-routers"); log_info(LD_GENERAL, @@ -305,7 +305,7 @@ dirserv_load_fingerprint_file(void) * * If the status is 'FP_REJECT' and <b>msg</b> is provided, set * *<b>msg</b> to an explanation of why. */ -static uint32_t +uint32_t dirserv_router_get_status(const routerinfo_t *router, const char **msg) { char d[DIGEST_LEN]; @@ -327,7 +327,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg) /** Return true if there is no point in downloading the router described by * <b>rs</b> because this directory would reject it. */ int -dirserv_would_reject_router(routerstatus_t *rs) +dirserv_would_reject_router(const routerstatus_t *rs) { uint32_t res; @@ -362,7 +362,7 @@ dirserv_get_name_status(const char *id_digest, const char *nickname) return 0; } -/** Helper: As dirserv_get_router_status, but takes the router fingerprint +/** Helper: As dirserv_router_get_status, but takes the router fingerprint * (hex, no spaces), nickname, address (used for logging only), IP address, OR * port, platform (logging only) and contact info (logging only) as arguments. * @@ -377,7 +377,7 @@ dirserv_get_status_impl(const char *id_digest, const char *nickname, const char **msg, int should_log) { int reject_unlisted = get_options()->AuthDirRejectUnlisted; - uint32_t result = 0; + uint32_t result; router_status_t *status_by_digest; if (!fingerprint_list) @@ -542,7 +542,7 @@ dirserv_router_has_valid_address(routerinfo_t *ri) */ int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, - int complain) + int complain, int *valid_out) { /* Okay. Now check whether the fingerprint is recognized. */ uint32_t status = dirserv_router_get_status(ri, msg); @@ -586,15 +586,24 @@ authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, *msg = "Rejected: Address is not an IP, or IP is a private address."; return -1; } - /* Okay, looks like we're willing to accept this one. */ - ri->is_named = (status & FP_NAMED) ? 1 : 0; - ri->is_valid = (status & FP_INVALID) ? 0 : 1; - ri->is_bad_directory = (status & FP_BADDIR) ? 1 : 0; - ri->is_bad_exit = (status & FP_BADEXIT) ? 1 : 0; + + *valid_out = ! (status & FP_INVALID); return 0; } +/** Update the relevant flags of <b>node</b> based on our opinion as a + * directory authority in <b>authstatus</b>, as returned by + * dirserv_router_get_status or equivalent. */ +void +dirserv_set_node_flags_from_authoritative_status(node_t *node, + uint32_t authstatus) +{ + node->is_valid = (authstatus & FP_INVALID) ? 0 : 1; + node->is_bad_directory = (authstatus & FP_BADDIR) ? 1 : 0; + node->is_bad_exit = (authstatus & FP_BADEXIT) ? 1 : 0; +} + /** True iff <b>a</b> is more severe than <b>b</b>. */ static int WRA_MORE_SEVERE(was_router_added_t a, was_router_added_t b) @@ -719,7 +728,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) * from this server. (We do this here and not in router_add_to_routerlist * because we want to be able to accept the newest router descriptor that * another authority has, so we all converge on the same one.) */ - ri_old = router_get_by_digest(ri->cache_info.identity_digest); + ri_old = router_get_mutable_by_digest(ri->cache_info.identity_digest); if (ri_old && ri_old->cache_info.published_on < ri->cache_info.published_on && router_differences_are_cosmetic(ri_old, ri) && !router_is_me(ri)) { @@ -763,8 +772,7 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) routerlist_descriptors_added(changed, 0); smartlist_free(changed); if (!*msg) { - *msg = ri->is_valid ? "Descriptor for valid server accepted" : - "Descriptor for invalid server accepted"; + *msg = "Descriptor accepted"; } log_info(LD_DIRSERV, "Added descriptor from '%s' (source: %s): %s.", @@ -779,12 +787,12 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) static was_router_added_t dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) { - routerinfo_t *ri; + const routerinfo_t *ri; int r; tor_assert(msg); *msg = NULL; - ri = router_get_by_digest(ei->cache_info.identity_digest); + ri = router_get_by_id_digest(ei->cache_info.identity_digest); if (!ri) { *msg = "No corresponding router descriptor for extra-info descriptor"; extrainfo_free(ei); @@ -819,56 +827,67 @@ dirserv_add_extrainfo(extrainfo_t *ei, const char **msg) static void directory_remove_invalid(void) { - int i; int changed = 0; routerlist_t *rl = router_get_routerlist(); + smartlist_t *nodes = smartlist_create(); + smartlist_add_all(nodes, nodelist_get_list()); - routerlist_assert_ok(rl); - - for (i = 0; i < smartlist_len(rl->routers); ++i) { + SMARTLIST_FOREACH_BEGIN(nodes, node_t *, node) { const char *msg; - routerinfo_t *ent = smartlist_get(rl->routers, i); + routerinfo_t *ent = node->ri; char description[NODE_DESC_BUF_LEN]; - uint32_t r = dirserv_router_get_status(ent, &msg); + uint32_t r; + if (!ent) + continue; + r = dirserv_router_get_status(ent, &msg); router_get_description(description, ent); if (r & FP_REJECT) { log_info(LD_DIRSERV, "Router %s is now rejected: %s", description, msg?msg:""); routerlist_remove(rl, ent, 0, time(NULL)); - i--; changed = 1; continue; } - if (bool_neq((r & FP_NAMED), ent->is_named)) { +#if 0 + if (bool_neq((r & FP_NAMED), ent->auth_says_is_named)) { log_info(LD_DIRSERV, "Router %s is now %snamed.", description, (r&FP_NAMED)?"":"un"); ent->is_named = (r&FP_NAMED)?1:0; changed = 1; } - if (bool_neq((r & FP_INVALID), !ent->is_valid)) { + if (bool_neq((r & FP_UNNAMED), ent->auth_says_is_unnamed)) { + log_info(LD_DIRSERV, + "Router '%s' is now %snamed. (FP_UNNAMED)", description, + (r&FP_NAMED)?"":"un"); + ent->is_named = (r&FP_NUNAMED)?0:1; + changed = 1; + } +#endif + if (bool_neq((r & FP_INVALID), !node->is_valid)) { log_info(LD_DIRSERV, "Router '%s' is now %svalid.", description, (r&FP_INVALID) ? "in" : ""); - ent->is_valid = (r&FP_INVALID)?0:1; + node->is_valid = (r&FP_INVALID)?0:1; changed = 1; } - if (bool_neq((r & FP_BADDIR), ent->is_bad_directory)) { + if (bool_neq((r & FP_BADDIR), node->is_bad_directory)) { log_info(LD_DIRSERV, "Router '%s' is now a %s directory", description, (r & FP_BADDIR) ? "bad" : "good"); - ent->is_bad_directory = (r&FP_BADDIR) ? 1: 0; + node->is_bad_directory = (r&FP_BADDIR) ? 1: 0; changed = 1; } - if (bool_neq((r & FP_BADEXIT), ent->is_bad_exit)) { + if (bool_neq((r & FP_BADEXIT), node->is_bad_exit)) { log_info(LD_DIRSERV, "Router '%s' is now a %s exit", description, (r & FP_BADEXIT) ? "bad" : "good"); - ent->is_bad_exit = (r&FP_BADEXIT) ? 1: 0; + node->is_bad_exit = (r&FP_BADEXIT) ? 1: 0; changed = 1; } - } + } SMARTLIST_FOREACH_END(node); if (changed) directory_set_dirty(); routerlist_assert_ok(rl); + smartlist_free(nodes); } /** Mark the directory as <b>dirty</b> -- when we're next asked for a @@ -907,10 +926,11 @@ directory_set_dirty(void) * as running iff <b>is_live</b> is true. */ static char * -list_single_server_status(routerinfo_t *desc, int is_live) +list_single_server_status(const routerinfo_t *desc, int is_live) { char buf[MAX_NICKNAME_LEN+HEX_DIGEST_LEN+4]; /* !nickname=$hexdigest\0 */ char *cp; + const node_t *node; tor_assert(desc); @@ -918,7 +938,8 @@ list_single_server_status(routerinfo_t *desc, int is_live) if (!is_live) { *cp++ = '!'; } - if (desc->is_valid) { + node = node_get_by_id(desc->cache_info.identity_digest); + if (node && node->is_valid) { strlcpy(cp, desc->nickname, sizeof(buf)-(cp-buf)); cp += strlen(cp); *cp++ = '='; @@ -957,6 +978,8 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) unreachable. */ int answer; + node_t *node = node_get_mutable_by_id(router->cache_info.identity_digest); + tor_assert(node); if (router_is_me(router)) { /* We always know if we are down ourselves. */ @@ -991,7 +1014,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now) rep_hist_note_router_unreachable(router->cache_info.identity_digest, when); } - router->is_running = answer; + node->is_running = answer; } /** Based on the routerinfo_ts in <b>routers</b>, allocate the @@ -1009,7 +1032,7 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, smartlist_t *rs_entries; time_t now = time(NULL); time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); /* We include v2 dir auths here too, because they need to answer * controllers. Eventually we'll deprecate this whole function; * see also networkstatus_getinfo_by_purpose(). */ @@ -1019,6 +1042,8 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, rs_entries = smartlist_create(); SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { + const node_t *node = node_get_by_id(ri->cache_info.identity_digest); + tor_assert(node); if (authdir) { /* Update router status in routerinfo_t. */ dirserv_set_router_is_running(ri, now); @@ -1026,12 +1051,13 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, if (for_controller) { char name_buf[MAX_VERBOSE_NICKNAME_LEN+2]; char *cp = name_buf; - if (!ri->is_running) + if (!node->is_running) *cp++ = '!'; router_get_verbose_nickname(cp, ri); smartlist_add(rs_entries, tor_strdup(name_buf)); } else if (ri->cache_info.published_on >= cutoff) { - smartlist_add(rs_entries, list_single_server_status(ri, ri->is_running)); + smartlist_add(rs_entries, list_single_server_status(ri, + node->is_running)); } } SMARTLIST_FOREACH_END(ri); @@ -1069,12 +1095,12 @@ format_versions_list(config_line_t *ln) * not hibernating, and not too old. Else return 0. */ static int -router_is_active(routerinfo_t *ri, time_t now) +router_is_active(const routerinfo_t *ri, const node_t *node, time_t now) { time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; if (ri->cache_info.published_on < cutoff) return 0; - if (!ri->is_running || !ri->is_valid || ri->is_hibernating) + if (!node->is_running || !node->is_valid || ri->is_hibernating) return 0; return 1; } @@ -1173,9 +1199,9 @@ dirserv_dump_directory_to_string(char **dir_out, /** Return 1 if we fetch our directory material directly from the * authorities, rather than from a mirror. */ int -directory_fetches_from_authorities(or_options_t *options) +directory_fetches_from_authorities(const or_options_t *options) { - routerinfo_t *me; + const routerinfo_t *me; uint32_t addr; int refuseunknown; if (options->FetchDirInfoEarly) @@ -1200,7 +1226,7 @@ directory_fetches_from_authorities(or_options_t *options) * on the "mirror" schedule rather than the "client" schedule. */ int -directory_fetches_dir_info_early(or_options_t *options) +directory_fetches_dir_info_early(const or_options_t *options) { return directory_fetches_from_authorities(options); } @@ -1212,7 +1238,7 @@ directory_fetches_dir_info_early(or_options_t *options) * client as a directory guard. */ int -directory_fetches_dir_info_later(or_options_t *options) +directory_fetches_dir_info_later(const or_options_t *options) { return options->UseBridges != 0; } @@ -1220,7 +1246,7 @@ directory_fetches_dir_info_later(or_options_t *options) /** Return 1 if we want to cache v2 dir info (each status file). */ int -directory_caches_v2_dir_info(or_options_t *options) +directory_caches_v2_dir_info(const or_options_t *options) { return options->DirPort != 0; } @@ -1229,7 +1255,7 @@ directory_caches_v2_dir_info(or_options_t *options) * and we're willing to serve them to others. Else return 0. */ int -directory_caches_dir_info(or_options_t *options) +directory_caches_dir_info(const or_options_t *options) { if (options->BridgeRelay || options->DirPort) return 1; @@ -1245,7 +1271,7 @@ directory_caches_dir_info(or_options_t *options) * requests via the "begin_dir" interface, which doesn't require * having any separate port open. */ int -directory_permits_begindir_requests(or_options_t *options) +directory_permits_begindir_requests(const or_options_t *options) { return options->BridgeRelay != 0 || options->DirPort != 0; } @@ -1254,7 +1280,7 @@ directory_permits_begindir_requests(or_options_t *options) * requests via the controller interface, which doesn't require * having any separate port open. */ int -directory_permits_controller_requests(or_options_t *options) +directory_permits_controller_requests(const or_options_t *options) { return options->DirPort != 0; } @@ -1264,7 +1290,8 @@ directory_permits_controller_requests(or_options_t *options) * lately. */ int -directory_too_idle_to_fetch_descriptors(or_options_t *options, time_t now) +directory_too_idle_to_fetch_descriptors(const or_options_t *options, + time_t now) { return !directory_caches_dir_info(options) && !options->FetchUselessDescriptors && @@ -1287,7 +1314,8 @@ static cached_dir_t cached_runningrouters; * cached_dir_t. */ static digestmap_t *cached_v2_networkstatus = NULL; -/** Map from flavor name to the v3 consensuses that we're currently serving. */ +/** Map from flavor name to the cached_dir_t for the v3 consensuses that we're + * currently serving. */ static strmap_t *cached_consensuses = NULL; /** Possibly replace the contents of <b>d</b> with the value of @@ -1531,11 +1559,11 @@ dirserv_pick_cached_dir_obj(cached_dir_t *cache_src, cached_dir_t *auth_src, time_t dirty, cached_dir_t *(*regenerate)(void), const char *name, - authority_type_t auth_type) + dirinfo_type_t auth_type) { - or_options_t *options = get_options(); - int authority = (auth_type == V1_AUTHORITY && authdir_mode_v1(options)) || - (auth_type == V2_AUTHORITY && authdir_mode_v2(options)); + const or_options_t *options = get_options(); + int authority = (auth_type == V1_DIRINFO && authdir_mode_v1(options)) || + (auth_type == V2_DIRINFO && authdir_mode_v2(options)); if (!authority || authdir_mode_bridge(options)) { return cache_src; @@ -1564,7 +1592,7 @@ dirserv_get_directory(void) return dirserv_pick_cached_dir_obj(cached_directory, the_directory, the_directory_is_dirty, dirserv_regenerate_directory, - "v1 server directory", V1_AUTHORITY); + "v1 server directory", V1_DIRINFO); } /** Only called by v1 auth dirservers. @@ -1657,7 +1685,7 @@ dirserv_get_runningrouters(void) &cached_runningrouters, &the_runningrouters, runningrouters_is_dirty, generate_runningrouters, - "v1 network status list", V1_AUTHORITY); + "v1 network status list", V1_DIRINFO); } /** Return the latest downloaded consensus networkstatus in encoded, signed, @@ -1740,7 +1768,7 @@ static uint64_t total_exit_bandwidth = 0; /** Helper: estimate the uptime of a router given its stated uptime and the * amount of time since it last stated its stated uptime. */ static INLINE long -real_uptime(routerinfo_t *router, time_t now) +real_uptime(const routerinfo_t *router, time_t now) { if (now < router->cache_info.published_on) return router->uptime; @@ -1794,7 +1822,8 @@ dirserv_thinks_router_is_unreliable(time_t now, * been set. */ static int -dirserv_thinks_router_is_hs_dir(routerinfo_t *router, time_t now) +dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, + const node_t *node, time_t now) { long uptime; @@ -1822,7 +1851,7 @@ dirserv_thinks_router_is_hs_dir(routerinfo_t *router, time_t now) * to fix the bug was 0.2.2.25-alpha. */ return (router->wants_to_be_hs_dir && router->dir_port && uptime > get_options()->MinUptimeHidServDirectoryV2 && - router->is_running); + node->is_running); } /** Look through the routerlist, the Mean Time Between Failure history, and @@ -1870,19 +1899,22 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) /* Weighted fractional uptime for each active router. */ wfus = tor_malloc(sizeof(double)*smartlist_len(rl->routers)); + nodelist_assert_ok(); + /* Now, fill in the arrays. */ - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { - if (router_is_active(ri, now)) { + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + routerinfo_t *ri = node->ri; + if (ri && router_is_active(ri, node, now)) { const char *id = ri->cache_info.identity_digest; uint32_t bw; - ri->is_exit = (!router_exit_policy_rejects_all(ri) && - exit_policy_is_general_exit(ri->exit_policy)); + node->is_exit = (!router_exit_policy_rejects_all(ri) && + exit_policy_is_general_exit(ri->exit_policy)); uptimes[n_active] = (uint32_t)real_uptime(ri, now); mtbfs[n_active] = rep_hist_get_stability(id, now); tks [n_active] = rep_hist_get_weighted_time_known(id, now); bandwidths[n_active] = bw = router_get_advertised_bandwidth(ri); total_bandwidth += bw; - if (ri->is_exit && !ri->is_bad_exit) { + if (node->is_exit && !node->is_bad_exit) { total_exit_bandwidth += bw; } else { bandwidths_excluding_exits[n_active_nonexit] = bw; @@ -1890,7 +1922,7 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) } ++n_active; } - }); + } SMARTLIST_FOREACH_END(node); /* Now, compute thresholds. */ if (n_active) { @@ -1916,15 +1948,17 @@ dirserv_compute_performance_thresholds(routerlist_t *rl) /* Now that we have a time-known that 7/8 routers are known longer than, * fill wfus with the wfu of every such "familiar" router. */ n_familiar = 0; - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { - if (router_is_active(ri, now)) { + + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + routerinfo_t *ri = node->ri; + if (ri && router_is_active(ri, node, now)) { const char *id = ri->cache_info.identity_digest; long tk = rep_hist_get_weighted_time_known(id, now); if (tk < guard_tk) continue; wfus[n_familiar++] = rep_hist_get_weighted_fractional_uptime(id, now); } - }); + } SMARTLIST_FOREACH_END(node); if (n_familiar) guard_wfu = median_double(wfus, n_familiar); if (guard_wfu > WFU_TO_GUARANTEE_GUARD) @@ -1995,24 +2029,20 @@ version_from_platform(const char *platform) */ int routerstatus_format_entry(char *buf, size_t buf_len, - routerstatus_t *rs, const char *version, + const routerstatus_t *rs, const char *version, routerstatus_format_type_t format) { int r; - struct in_addr in; char *cp; char *summary; char published[ISO_TIME_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char identity64[BASE64_DIGEST_LEN+1]; char digest64[BASE64_DIGEST_LEN+1]; format_iso_time(published, rs->published_on); digest_to_base64(identity64, rs->identity_digest); digest_to_base64(digest64, rs->descriptor_digest); - in.s_addr = htonl(rs->addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); r = tor_snprintf(buf, buf_len, "r %s %s %s%s%s %s %d %d\n", @@ -2021,7 +2051,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64, (format==NS_V3_CONSENSUS_MICRODESC)?"":" ", published, - ipaddr, + fmt_addr32(rs->addr), (int)rs->or_port, (int)rs->dir_port); if (r<0) { @@ -2049,7 +2079,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, rs->is_possible_guard?" Guard":"", rs->is_hs_dir?" HSDir":"", rs->is_named?" Named":"", - rs->is_running?" Running":"", + rs->is_flagged_running?" Running":"", rs->is_stable?" Stable":"", rs->is_unnamed?" Unnamed":"", rs->is_v2_dir?" V2Dir":"", @@ -2071,7 +2101,7 @@ routerstatus_format_entry(char *buf, size_t buf_len, } if (format != NS_V2) { - routerinfo_t* desc = router_get_by_digest(rs->identity_digest); + const routerinfo_t* desc = router_get_by_id_digest(rs->identity_digest); uint32_t bw; if (format != NS_CONTROL_PORT) { @@ -2167,6 +2197,8 @@ _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; uint32_t bw_first, bw_second; + const node_t *node_first, *node_second; + int first_is_running, second_is_running; /* we return -1 if first should appear before second... that is, * if first is a better router. */ @@ -2189,9 +2221,14 @@ _compare_routerinfo_by_ip_and_bw(const void **a, const void **b) else if (!first_is_auth && second_is_auth) return 1; - else if (first->is_running && !second->is_running) + node_first = node_get_by_id(first->cache_info.identity_digest); + node_second = node_get_by_id(second->cache_info.identity_digest); + first_is_running = node_first && node_first->is_running; + second_is_running = node_second && node_second->is_running; + + if (first_is_running && !second_is_running) return -1; - else if (!first->is_running && second->is_running) + else if (!first_is_running && second_is_running) return 1; bw_first = router_get_advertised_bandwidth(first); @@ -2215,7 +2252,7 @@ _compare_routerinfo_by_ip_and_bw(const void **a, const void **b) static digestmap_t * get_possible_sybil_list(const smartlist_t *routers) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); digestmap_t *omit_as_sybil; smartlist_t *routers_by_ip = smartlist_create(); uint32_t last_addr; @@ -2328,7 +2365,9 @@ is_router_version_good_for_possible_guard(const char *platform) */ void set_routerstatus_from_routerinfo(routerstatus_t *rs, - routerinfo_t *ri, time_t now, + node_t *node, + routerinfo_t *ri, + time_t now, int naming, int listbadexits, int listbaddirs, int vote_on_hsdirs) { @@ -2341,25 +2380,25 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, router_digest_is_trusted_dir(ri->cache_info.identity_digest); /* Already set by compute_performance_thresholds. */ - rs->is_exit = ri->is_exit; - rs->is_stable = ri->is_stable = - router_is_active(ri, now) && + 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; - rs->is_fast = ri->is_fast = - router_is_active(ri, now) && + rs->is_fast = node->is_fast = + router_is_active(ri, node, now) && !dirserv_thinks_router_is_unreliable(now, ri, 0, 1); - rs->is_running = ri->is_running; /* computed above */ + rs->is_flagged_running = node->is_running; /* computed above */ if (naming) { uint32_t name_status = dirserv_get_name_status( - ri->cache_info.identity_digest, ri->nickname); + node->identity, ri->nickname); rs->is_named = (naming && (name_status & FP_NAMED)) ? 1 : 0; rs->is_unnamed = (naming && (name_status & FP_UNNAMED)) ? 1 : 0; } - rs->is_valid = ri->is_valid; + rs->is_valid = node->is_valid; - if (rs->is_fast && + if (node->is_fast && (router_get_advertised_bandwidth(ri) >= BANDWIDTH_TO_GUARANTEE_GUARD || router_get_advertised_bandwidth(ri) >= MIN(guard_bandwidth_including_exits, @@ -2367,24 +2406,25 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, (options->GiveGuardFlagTo_CVE_2011_2768_VulnerableRelays || is_router_version_good_for_possible_guard(ri->platform))) { long tk = rep_hist_get_weighted_time_known( - ri->cache_info.identity_digest, now); + node->identity, now); double wfu = rep_hist_get_weighted_fractional_uptime( - ri->cache_info.identity_digest, now); + node->identity, now); rs->is_possible_guard = (wfu >= guard_wfu && tk >= guard_tk) ? 1 : 0; } else { rs->is_possible_guard = 0; } - rs->is_bad_directory = listbaddirs && ri->is_bad_directory; - rs->is_bad_exit = listbadexits && ri->is_bad_exit; - ri->is_hs_dir = dirserv_thinks_router_is_hs_dir(ri, now); - rs->is_hs_dir = vote_on_hsdirs && ri->is_hs_dir; + + rs->is_bad_directory = listbaddirs && node->is_bad_directory; + rs->is_bad_exit = listbadexits && node->is_bad_exit; + node->is_hs_dir = dirserv_thinks_router_is_hs_dir(ri, node, now); + rs->is_hs_dir = vote_on_hsdirs && node->is_hs_dir; rs->is_v2_dir = ri->dir_port != 0; if (!strcasecmp(ri->nickname, UNNAMED_ROUTER_NICKNAME)) rs->is_named = rs->is_unnamed = 0; rs->published_on = ri->cache_info.published_on; - memcpy(rs->identity_digest, ri->cache_info.identity_digest, DIGEST_LEN); + memcpy(rs->identity_digest, node->identity, DIGEST_LEN); memcpy(rs->descriptor_digest, ri->cache_info.signed_descriptor_digest, DIGEST_LEN); rs->addr = ri->addr; @@ -2401,7 +2441,7 @@ static void clear_status_flags_on_sybil(routerstatus_t *rs) { rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_running = rs->is_named = rs->is_valid = rs->is_v2_dir = + rs->is_flagged_running = rs->is_named = rs->is_valid = rs->is_v2_dir = rs->is_hs_dir = rs->is_possible_guard = rs->is_bad_exit = rs->is_bad_directory = 0; /* FFFF we might want some mechanism to check later on if we @@ -2409,18 +2449,6 @@ clear_status_flags_on_sybil(routerstatus_t *rs) * forget to add it to this clause. */ } -/** Clear all the status flags in routerinfo <b>router</b>. We put this - * function here because it's eerily similar to - * clear_status_flags_on_sybil() above. One day we should merge them. */ -void -router_clear_status_flags(routerinfo_t *router) -{ - router->is_valid = router->is_running = router->is_hs_dir = - router->is_fast = router->is_stable = - router->is_possible_guard = router->is_exit = - router->is_bad_exit = router->is_bad_directory = 0; -} - /** * Helper function to parse out a line in the measured bandwidth file * into a measured_bw_line_t output structure. Returns -1 on failure @@ -2538,7 +2566,7 @@ dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses) { char line[256]; - FILE *fp = fopen(from_file, "r"); + FILE *fp = tor_fopen_cloexec(from_file, "r"); int applied_lines = 0; time_t file_time; int ok; @@ -2598,7 +2626,7 @@ networkstatus_t * dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key, authority_cert_t *cert) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); networkstatus_t *v3_out = NULL; uint32_t addr; char *hostname = NULL, *client_versions = NULL, *server_versions = NULL; @@ -2669,10 +2697,13 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key, routerstatus_t *rs; vote_routerstatus_t *vrs; microdesc_t *md; + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) + continue; vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); rs = &vrs->status; - set_routerstatus_from_routerinfo(rs, ri, now, + set_routerstatus_from_routerinfo(rs, node, ri, now, naming, listbadexits, listbaddirs, vote_on_hsdirs); @@ -2680,7 +2711,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_env_t *private_key, clear_status_flags_on_sybil(rs); if (!vote_on_reachability) - rs->is_running = 0; + rs->is_flagged_running = 0; vrs->version = version_from_platform(ri->platform); md = dirvote_create_microdescriptor(ri); @@ -2814,12 +2845,10 @@ generate_v2_networkstatus_opinion(void) char *status = NULL, *client_versions = NULL, *server_versions = NULL, *identity_pkey = NULL, *hostname = NULL; char *outp, *endp; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); char fingerprint[FINGERPRINT_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char published[ISO_TIME_LEN+1]; char digest[DIGEST_LEN]; - struct in_addr in; uint32_t addr; crypto_pk_env_t *private_key; routerlist_t *rl = router_get_routerlist(); @@ -2841,8 +2870,6 @@ generate_v2_networkstatus_opinion(void) log_warn(LD_NET, "Couldn't resolve my hostname"); goto done; } - in.s_addr = htonl(addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); format_iso_time(published, now); @@ -2888,7 +2915,7 @@ generate_v2_networkstatus_opinion(void) "dir-options%s%s%s%s\n" "%s" /* client version line, server version line. */ "dir-signing-key\n%s", - hostname, ipaddr, + hostname, fmt_addr32(addr), (int)router_get_advertised_dir_port(options, 0), fingerprint, contact, @@ -2920,8 +2947,12 @@ generate_v2_networkstatus_opinion(void) if (ri->cache_info.published_on >= cutoff) { routerstatus_t rs; char *version = version_from_platform(ri->platform); - - set_routerstatus_from_routerinfo(&rs, ri, now, + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) { + tor_free(version); + continue; + } + set_routerstatus_from_routerinfo(&rs, node, ri, now, naming, listbadexits, listbaddirs, vote_on_hsdirs); @@ -3005,7 +3036,7 @@ dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, if (!strcmp(key,"authority")) { if (authdir_mode_v2(get_options())) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (me) smartlist_add(result, tor_memdup(me->cache_info.identity_digest, DIGEST_LEN)); @@ -3024,7 +3055,7 @@ dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result, } else { SMARTLIST_FOREACH(router_get_trusted_dir_servers(), trusted_dir_server_t *, ds, - if (ds->type & V2_AUTHORITY) + if (ds->type & V2_DIRINFO) smartlist_add(result, tor_memdup(ds->digest, DIGEST_LEN))); } smartlist_sort_digests(result); @@ -3093,7 +3124,7 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, /* Treat "all" requests as if they were unencrypted */ for_unencrypted_conn = 1; } else if (!strcmp(key, "authority")) { - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) smartlist_add(fps_out, tor_memdup(ri->cache_info.identity_digest, DIGEST_LEN)); @@ -3113,8 +3144,8 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, if (for_unencrypted_conn) { /* Remove anything that insists it not be sent unencrypted. */ - SMARTLIST_FOREACH(fps_out, char *, cp, { - signed_descriptor_t *sd; + SMARTLIST_FOREACH_BEGIN(fps_out, char *, cp) { + const signed_descriptor_t *sd; if (by_id) sd = get_signed_descriptor_by_fp(cp,is_extrainfo,0); else if (is_extrainfo) @@ -3125,7 +3156,7 @@ dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key, tor_free(cp); SMARTLIST_DEL_CURRENT(fps_out, cp); } - }); + } SMARTLIST_FOREACH_END(cp); } if (!smartlist_len(fps_out)) { @@ -3164,9 +3195,9 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, smartlist_add(descs_out, &(r->cache_info))); } else if (!strcmp(key, "/tor/server/authority")) { - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } else if (!strcmpstart(key, "/tor/server/d/")) { smartlist_t *digests = smartlist_create(); key += strlen("/tor/server/d/"); @@ -3190,17 +3221,17 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key, { if (router_digest_is_me(d)) { /* make sure desc_routerinfo exists */ - routerinfo_t *ri = router_get_my_routerinfo(); + const routerinfo_t *ri = router_get_my_routerinfo(); if (ri) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } else { - routerinfo_t *ri = router_get_by_digest(d); + const routerinfo_t *ri = router_get_by_id_digest(d); /* Don't actually serve a descriptor that everyone will think is * expired. This is an (ugly) workaround to keep buggy 0.1.1.10 * Tors from downloading descriptors that they will throw away. */ if (ri && ri->cache_info.published_on > cutoff) - smartlist_add(descs_out, &(ri->cache_info)); + smartlist_add(descs_out, (void*) &(ri->cache_info)); } }); SMARTLIST_FOREACH(digests, char *, d, tor_free(d)); @@ -3249,7 +3280,7 @@ dirserv_orconn_tls_done(const char *address, log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.", router_describe(ri), address, ri->or_port); - if (tor_addr_from_str(&addr, ri->address) != -1) + if (tor_addr_parse(&addr, ri->address) != -1) addrp = &addr; else log_warn(LD_BUG, "Couldn't parse IP address \"%s\"", ri->address); @@ -3267,7 +3298,8 @@ dirserv_orconn_tls_done(const char *address, * an upload or a download. Used to decide whether to relaunch reachability * testing for the server. */ int -dirserv_should_launch_reachability_test(routerinfo_t *ri, routerinfo_t *ri_old) +dirserv_should_launch_reachability_test(const routerinfo_t *ri, + const routerinfo_t *ri_old) { if (!authdir_mode_handles_descs(get_options(), ri->purpose)) return 0; @@ -3391,7 +3423,7 @@ dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff) * its extra-info document if <b>extrainfo</b> is true. Return * NULL if not found or if the descriptor is older than * <b>publish_cutoff</b>. */ -static signed_descriptor_t * +static const signed_descriptor_t * get_signed_descriptor_by_fp(const char *fp, int extrainfo, time_t publish_cutoff) { @@ -3401,7 +3433,7 @@ get_signed_descriptor_by_fp(const char *fp, int extrainfo, else return &(router_get_my_routerinfo()->cache_info); } else { - routerinfo_t *ri = router_get_by_digest(fp); + const routerinfo_t *ri = router_get_by_id_digest(fp); if (ri && ri->cache_info.published_on > publish_cutoff) { if (extrainfo) @@ -3469,7 +3501,7 @@ dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, tor_assert(fps); if (is_serverdescs) { int n = smartlist_len(fps); - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); result = (me?me->cache_info.signed_descriptor_len:2048) * n; if (compressed) result /= 2; /* observed compressibility is between 35 and 55%. */ @@ -3523,20 +3555,19 @@ connection_dirserv_finish_spooling(dir_connection_t *conn) static int connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) { -#ifdef TRACK_SERVED_TIME - time_t now = time(NULL); -#endif int by_fp = (conn->dir_spool_src == DIR_SPOOL_SERVER_BY_FP || conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP); int extra = (conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_FP || conn->dir_spool_src == DIR_SPOOL_EXTRA_BY_DIGEST); time_t publish_cutoff = time(NULL)-ROUTER_MAX_AGE_TO_PUBLISH; + const or_options_t *options = get_options(); + while (smartlist_len(conn->fingerprint_stack) && - buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { const char *body; char *fp = smartlist_pop_last(conn->fingerprint_stack); - signed_descriptor_t *sd = NULL; + const signed_descriptor_t *sd = NULL; if (by_fp) { sd = get_signed_descriptor_by_fp(fp, extra, publish_cutoff); } else { @@ -3553,9 +3584,16 @@ connection_dirserv_add_servers_to_outbuf(dir_connection_t *conn) * unknown bridge descriptor has shown up between then and now. */ continue; } -#ifdef TRACK_SERVED_TIME - sd->last_served_at = now; -#endif + + /** If we are the bridge authority and the descriptor is a bridge + * descriptor, remember that we served this descriptor for desc stats. */ + if (options->BridgeAuthoritativeDir && by_fp) { + const routerinfo_t *router = + router_get_by_id_digest(sd->identity_digest); + tor_assert(router); + if (router->purpose == ROUTER_PURPOSE_BRIDGE) + rep_hist_note_desc_served(sd->identity_digest); + } body = signed_descriptor_get_body(sd); if (conn->zlib_state) { /* XXXX022 This 'last' business should actually happen on the last @@ -3594,7 +3632,7 @@ connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn) { microdesc_cache_t *cache = get_microdesc_cache(); while (smartlist_len(conn->fingerprint_stack) && - buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { char *fp256 = smartlist_pop_last(conn->fingerprint_stack); microdesc_t *md = microdesc_cache_lookup_by_digest256(cache, fp256); tor_free(fp256); @@ -3633,7 +3671,7 @@ connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn) ssize_t bytes; int64_t remaining; - bytes = DIRSERV_BUFFER_MIN - buf_datalen(conn->_base.outbuf); + bytes = DIRSERV_BUFFER_MIN - connection_get_outbuf_len(TO_CONN(conn)); tor_assert(bytes > 0); tor_assert(conn->cached_dir); if (bytes < 8192) @@ -3672,7 +3710,7 @@ static int connection_dirserv_add_networkstatus_bytes_to_outbuf(dir_connection_t *conn) { - while (buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) { + while (connection_get_outbuf_len(TO_CONN(conn)) < DIRSERV_BUFFER_MIN) { if (conn->cached_dir) { int uncompressing = (conn->zlib_state != NULL); int r = connection_dirserv_add_dir_bytes_to_outbuf(conn); @@ -3718,7 +3756,7 @@ connection_dirserv_flushed_some(dir_connection_t *conn) { tor_assert(conn->_base.state == DIR_CONN_STATE_SERVER_WRITING); - if (buf_datalen(conn->_base.outbuf) >= DIRSERV_BUFFER_MIN) + if (connection_get_outbuf_len(TO_CONN(conn)) >= DIRSERV_BUFFER_MIN) return 0; switch (conn->dir_spool_src) { diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 569abfca2e..d3fd90ceb5 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -52,8 +52,6 @@ MAX_V_LINE_LEN \ ) -#define UNNAMED_ROUTER_NICKNAME "Unnamed" - int connection_dirserv_flushed_some(dir_connection_t *conn); int dirserv_add_own_fingerprint(const char *nickname, crypto_pk_env_t *pk); @@ -73,15 +71,16 @@ int list_server_status_v1(smartlist_t *routers, char **router_status_out, int dirserv_dump_directory_to_string(char **dir_out, crypto_pk_env_t *private_key); -int directory_fetches_from_authorities(or_options_t *options); -int directory_fetches_dir_info_early(or_options_t *options); -int directory_fetches_dir_info_later(or_options_t *options); -int directory_caches_v2_dir_info(or_options_t *options); +int directory_fetches_from_authorities(const or_options_t *options); +int directory_fetches_dir_info_early(const or_options_t *options); +int directory_fetches_dir_info_later(const or_options_t *options); +int directory_caches_v2_dir_info(const or_options_t *options); #define directory_caches_v1_dir_info(o) directory_caches_v2_dir_info(o) -int directory_caches_dir_info(or_options_t *options); -int directory_permits_begindir_requests(or_options_t *options); -int directory_permits_controller_requests(or_options_t *options); -int directory_too_idle_to_fetch_descriptors(or_options_t *options, time_t now); +int directory_caches_dir_info(const or_options_t *options); +int directory_permits_begindir_requests(const or_options_t *options); +int directory_permits_controller_requests(const or_options_t *options); +int directory_too_idle_to_fetch_descriptors(const or_options_t *options, + time_t now); void directory_set_dirty(void); cached_dir_t *dirserv_get_directory(void); @@ -111,13 +110,19 @@ void dirserv_orconn_tls_done(const char *address, uint16_t or_port, const char *digest_rcvd, int as_advertised); -int dirserv_should_launch_reachability_test(routerinfo_t *ri, - routerinfo_t *ri_old); +int dirserv_should_launch_reachability_test(const routerinfo_t *ri, + const routerinfo_t *ri_old); void dirserv_single_reachability_test(time_t now, routerinfo_t *router); void dirserv_test_reachability(time_t now); int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg, - int complain); -int dirserv_would_reject_router(routerstatus_t *rs); + int complain, + int *valid_out); +uint32_t dirserv_router_get_status(const routerinfo_t *router, + const char **msg); +void dirserv_set_node_flags_from_authoritative_status(node_t *node, + uint32_t authstatus); + +int dirserv_would_reject_router(const routerstatus_t *rs); int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff); int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src); int dirserv_have_any_microdesc(const smartlist_t *fps); @@ -126,7 +131,7 @@ size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs, size_t dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed); int routerstatus_format_entry(char *buf, size_t buf_len, - routerstatus_t *rs, const char *platform, + const routerstatus_t *rs, const char *platform, routerstatus_format_type_t format); void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index c6ce9f6776..bf34c62af3 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -83,9 +83,7 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, const char *client_versions = NULL, *server_versions = NULL; char *outp, *endp; char fingerprint[FINGERPRINT_LEN+1]; - char ipaddr[INET_NTOA_BUF_LEN]; char digest[DIGEST_LEN]; - struct in_addr in; uint32_t addr; routerlist_t *rl = router_get_routerlist(); char *version_lines = NULL; @@ -98,8 +96,6 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, voter = smartlist_get(v3_ns->voters, 0); addr = voter->addr; - in.s_addr = htonl(addr); - tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr)); base16_encode(fingerprint, sizeof(fingerprint), v3_ns->cert->cache_info.identity_digest, DIGEST_LEN); @@ -186,7 +182,8 @@ format_networkstatus_vote(crypto_pk_env_t *private_signing_key, flags, params, voter->nickname, fingerprint, voter->address, - ipaddr, voter->dir_port, voter->or_port, voter->contact); + fmt_addr32(addr), voter->dir_port, voter->or_port, + voter->contact); if (r < 0) { log_err(LD_BUG, "Insufficient memory for network status line"); @@ -1530,8 +1527,6 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id); SMARTLIST_FOREACH_BEGIN(dir_sources, const dir_src_ent_t *, e) { - struct in_addr in; - char ip[INET_NTOA_BUF_LEN]; char fingerprint[HEX_DIGEST_LEN+1]; char votedigest[HEX_DIGEST_LEN+1]; networkstatus_t *v = e->v; @@ -1541,8 +1536,6 @@ networkstatus_compute_consensus(smartlist_t *votes, if (e->is_legacy) tor_assert(consensus_method >= 2); - in.s_addr = htonl(voter->addr); - tor_inet_ntoa(&in, ip, sizeof(ip)); base16_encode(fingerprint, sizeof(fingerprint), e->digest, DIGEST_LEN); base16_encode(votedigest, sizeof(votedigest), voter->vote_digest, DIGEST_LEN); @@ -1550,7 +1543,7 @@ networkstatus_compute_consensus(smartlist_t *votes, tor_asprintf(&buf, "dir-source %s%s %s %s %s %d %d\n", voter->nickname, e->is_legacy ? "-legacy" : "", - fingerprint, voter->address, ip, + fingerprint, voter->address, fmt_addr32(voter->addr), voter->dir_port, voter->or_port); smartlist_add(chunks, buf); @@ -2511,7 +2504,7 @@ authority_cert_dup(authority_cert_t *cert) void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); tor_assert(timing_out); @@ -2585,7 +2578,7 @@ static struct { /** Set voting_schedule to hold the timing for the next vote we should be * doing. */ void -dirvote_recalculate_timing(or_options_t *options, time_t now) +dirvote_recalculate_timing(const or_options_t *options, time_t now) { int interval, vote_delay, dist_delay; time_t start; @@ -2636,7 +2629,7 @@ dirvote_recalculate_timing(or_options_t *options, time_t now) /** Entry point: Take whatever voting actions are pending as of <b>now</b>. */ void -dirvote_act(or_options_t *options, time_t now) +dirvote_act(const or_options_t *options, time_t now) { if (!authdir_mode_v3(options)) return; @@ -2751,7 +2744,7 @@ dirvote_perform_vote(void) directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_VOTE, ROUTER_PURPOSE_GENERAL, - V3_AUTHORITY, + V3_DIRINFO, pending_vote->vote_body->dir, pending_vote->vote_body->dir_len, 0); log_notice(LD_DIR, "Vote posted."); @@ -2770,7 +2763,7 @@ dirvote_fetch_missing_votes(void) SMARTLIST_FOREACH(router_get_trusted_dir_servers(), trusted_dir_server_t *, ds, { - if (!(ds->type & V3_AUTHORITY)) + if (!(ds->type & V3_DIRINFO)) continue; if (!dirvote_get_vote(ds->v3_identity_digest, DGV_BY_ID|DGV_INCLUDE_PENDING)) { @@ -2883,7 +2876,7 @@ list_v3_auth_ids(void) char *keys; SMARTLIST_FOREACH(router_get_trusted_dir_servers(), trusted_dir_server_t *, ds, - if ((ds->type & V3_AUTHORITY) && + if ((ds->type & V3_DIRINFO) && !tor_digest_is_zero(ds->v3_identity_digest)) smartlist_add(known_v3_keys, tor_strdup(hex_str(ds->v3_identity_digest, DIGEST_LEN)))); @@ -3078,7 +3071,7 @@ dirvote_compute_consensuses(void) if (!pending_vote_list) pending_vote_list = smartlist_create(); - n_voters = get_n_authorities(V3_AUTHORITY); + n_voters = get_n_authorities(V3_DIRINFO); n_votes = smartlist_len(pending_vote_list); if (n_votes <= n_voters/2) { log_warn(LD_DIR, "We don't have enough votes to generate a consensus: " @@ -3217,7 +3210,7 @@ dirvote_compute_consensuses(void) directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_SIGNATURES, ROUTER_PURPOSE_GENERAL, - V3_AUTHORITY, + V3_DIRINFO, pending_consensus_signatures, strlen(pending_consensus_signatures), 0); log_notice(LD_DIR, "Signature(s) posted."); diff --git a/src/or/dirvote.h b/src/or/dirvote.h index de11fcf997..d19635173f 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -41,8 +41,8 @@ authority_cert_t *authority_cert_dup(authority_cert_t *cert); /* vote scheduling */ void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); time_t dirvote_get_start_of_next_interval(time_t now, int interval); -void dirvote_recalculate_timing(or_options_t *options, time_t now); -void dirvote_act(or_options_t *options, time_t now); +void dirvote_recalculate_timing(const or_options_t *options, time_t now); +void dirvote_act(const or_options_t *options, time_t now); /* invoked on timers and by outside triggers. */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, @@ -60,6 +60,7 @@ const char *dirvote_get_pending_detached_signatures(void); #define DGV_INCLUDE_PREVIOUS 4 const cached_dir_t *dirvote_get_vote(const char *fp, int flags); void set_routerstatus_from_routerinfo(routerstatus_t *rs, + node_t *node, routerinfo_t *ri, time_t now, int naming, int listbadexits, int listbaddirs, int vote_on_hsdirs); diff --git a/src/or/dns.c b/src/or/dns.c index 9b6b98afaf..8ed9536903 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -276,7 +276,7 @@ dns_init(void) int dns_reset(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (! server_mode(options)) { if (!the_evdns_base) { @@ -675,7 +675,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, cached_resolve_t *resolve; cached_resolve_t search; pending_connection_t *pending_connection; - routerinfo_t *me; + const routerinfo_t *me; tor_addr_t addr; time_t now = time(NULL); uint8_t is_reverse = 0; @@ -687,7 +687,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, /* first check if exitconn->_base.address is an IP. If so, we already * know the answer. */ - if (tor_addr_from_str(&addr, exitconn->_base.address) >= 0) { + if (tor_addr_parse(&addr, exitconn->_base.address) >= 0) { if (tor_addr_family(&addr) == AF_INET) { tor_addr_copy(&exitconn->_base.addr, &addr); exitconn->address_ttl = DEFAULT_DNS_TTL; @@ -721,7 +721,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, * .in-addr.arpa address but this isn't a resolve request, kill the * connection. */ - if ((r = tor_addr_parse_reverse_lookup_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; @@ -1026,7 +1026,7 @@ add_answer_to_cache(const char *address, uint8_t is_reverse, uint32_t addr, static INLINE int is_test_address(const char *address) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); return options->ServerDNSTestAddresses && smartlist_string_isin_case(options->ServerDNSTestAddresses, address); } @@ -1177,7 +1177,7 @@ evdns_err_is_transient(int err) static int configure_nameservers(int force) { - or_options_t *options; + const or_options_t *options; const char *conf_fname; struct stat st; int r; @@ -1198,7 +1198,7 @@ configure_nameservers(int force) #ifdef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS if (options->OutboundBindAddress) { tor_addr_t addr; - if (tor_addr_from_str(&addr, options->OutboundBindAddress) < 0) { + if (tor_addr_parse(&addr, options->OutboundBindAddress) < 0) { log_warn(LD_CONFIG,"Outbound bind address '%s' didn't parse. Ignoring.", options->OutboundBindAddress); } else { @@ -1404,7 +1404,7 @@ launch_resolve(edge_connection_t *exitconn) } } - r = tor_addr_parse_reverse_lookup_name( + r = tor_addr_parse_PTR_name( &a, exitconn->_base.address, AF_UNSPEC, 0); tor_assert(the_evdns_base); @@ -1595,7 +1595,7 @@ launch_wildcard_check(int min_len, int max_len, const char *suffix) static void launch_test_addresses(int fd, short event, void *args) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); struct evdns_request *req; (void)fd; (void)event; diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index f2c473dfc5..7f519398fa 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -29,8 +29,10 @@ * DNSPort. We need to eventually answer the request <b>req</b>. */ static void -evdns_server_callback(struct evdns_server_request *req, void *_data) +evdns_server_callback(struct evdns_server_request *req, void *data_) { + const listener_connection_t *listener = data_; + entry_connection_t *entry_conn; edge_connection_t *conn; int i = 0; struct evdns_server_question *q = NULL; @@ -43,7 +45,7 @@ evdns_server_callback(struct evdns_server_request *req, void *_data) char *q_name; tor_assert(req); - tor_assert(_data == NULL); + log_info(LD_APP, "Got a new DNS request!"); req->flags |= 0x80; /* set RA */ @@ -114,8 +116,9 @@ evdns_server_callback(struct evdns_server_request *req, void *_data) } /* Make a new dummy AP connection, and attach the request to it. */ - conn = edge_connection_new(CONN_TYPE_AP, AF_INET); - conn->_base.state = AP_CONN_STATE_RESOLVE_WAIT; + entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); + conn = ENTRY_TO_EDGE_CONN(entry_conn); + TO_CONN(conn)->state = AP_CONN_STATE_RESOLVE_WAIT; conn->is_dns_request = 1; tor_addr_copy(&TO_CONN(conn)->addr, &tor_addr); @@ -123,23 +126,27 @@ evdns_server_callback(struct evdns_server_request *req, void *_data) TO_CONN(conn)->address = tor_dup_addr(&tor_addr); if (q->type == EVDNS_TYPE_A) - conn->socks_request->command = SOCKS_COMMAND_RESOLVE; + entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE; else - conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; + entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; - strlcpy(conn->socks_request->address, q->name, - sizeof(conn->socks_request->address)); + strlcpy(entry_conn->socks_request->address, q->name, + sizeof(entry_conn->socks_request->address)); - conn->dns_server_request = req; + 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; + entry_conn->nym_epoch = get_signewnym_epoch(); - if (connection_add(TO_CONN(conn)) < 0) { + if (connection_add(ENTRY_TO_CONN(entry_conn)) < 0) { log_warn(LD_APP, "Couldn't register dummy connection for DNS request"); evdns_server_request_respond(req, DNS_ERR_SERVERFAILED); - connection_free(TO_CONN(conn)); + connection_free(ENTRY_TO_CONN(entry_conn)); return; } - control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + control_event_stream_status(entry_conn, STREAM_EVENT_NEW, 0); /* Now, unless a controller asked us to leave streams unattached, * throw the connection over to get rewritten (which will @@ -148,7 +155,7 @@ evdns_server_callback(struct evdns_server_request *req, void *_data) log_info(LD_APP, "Passing request for %s to rewrite_and_attach.", escaped_safe_str_client(q->name)); q_name = tor_strdup(q->name); /* q could be freed in rewrite_and_attach */ - connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + connection_ap_rewrite_and_attach_if_allowed(entry_conn, NULL, NULL); /* Now, the connection is marked if it was bad. */ log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.", @@ -164,22 +171,30 @@ evdns_server_callback(struct evdns_server_request *req, void *_data) int dnsserv_launch_request(const char *name, int reverse) { + entry_connection_t *entry_conn; edge_connection_t *conn; char *q_name; /* Make a new dummy AP connection, and attach the request to it. */ - conn = edge_connection_new(CONN_TYPE_AP, AF_INET); + 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; if (reverse) - conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; + entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; else - conn->socks_request->command = SOCKS_COMMAND_RESOLVE; + entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE; conn->is_dns_request = 1; - strlcpy(conn->socks_request->address, name, - sizeof(conn->socks_request->address)); + strlcpy(entry_conn->socks_request->address, name, + sizeof(entry_conn->socks_request->address)); + + entry_conn->socks_request->listener_type = CONN_TYPE_CONTROL_LISTENER; + entry_conn->original_dest_address = tor_strdup(name); + entry_conn->session_group = SESSION_GROUP_CONTROL_RESOLVE; + entry_conn->nym_epoch = get_signewnym_epoch(); + entry_conn->isolation_flags = ISO_DEFAULT; if (connection_add(TO_CONN(conn))<0) { log_warn(LD_APP, "Couldn't register dummy connection for RESOLVE request"); @@ -194,7 +209,7 @@ dnsserv_launch_request(const char *name, int reverse) log_info(LD_APP, "Passing request for %s to rewrite_and_attach.", escaped_safe_str_client(name)); q_name = tor_strdup(name); /* q could be freed in rewrite_and_attach */ - connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + connection_ap_rewrite_and_attach_if_allowed(entry_conn, NULL, NULL); /* Now, the connection is marked if it was bad. */ log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.", @@ -206,7 +221,7 @@ dnsserv_launch_request(const char *name, int reverse) /** If there is a pending request on <b>conn</b> that's waiting for an answer, * send back an error and free the request. */ void -dnsserv_reject_request(edge_connection_t *conn) +dnsserv_reject_request(entry_connection_t *conn) { if (conn->dns_server_request) { evdns_server_request_respond(conn->dns_server_request, @@ -252,7 +267,7 @@ evdns_get_orig_address(const struct evdns_server_request *req, * <b>answer_len</b>, in <b>answer</b>, with TTL <b>ttl</b>. Doesn't do * any caching; that's handled elsewhere. */ void -dnsserv_resolved(edge_connection_t *conn, +dnsserv_resolved(entry_connection_t *conn, int answer_type, size_t answer_len, const char *answer, @@ -305,12 +320,15 @@ dnsserv_resolved(edge_connection_t *conn, void dnsserv_configure_listener(connection_t *conn) { + listener_connection_t *listener_conn; tor_assert(conn); tor_assert(SOCKET_OK(conn->s)); tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER); - conn->dns_server_port = - tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL); + listener_conn = TO_LISTENER_CONN(conn); + listener_conn->dns_server_port = + tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, + listener_conn); } /** Free the evdns server port for <b>conn</b>, which must be an @@ -318,12 +336,15 @@ dnsserv_configure_listener(connection_t *conn) void dnsserv_close_listener(connection_t *conn) { + listener_connection_t *listener_conn; tor_assert(conn); tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER); - if (conn->dns_server_port) { - evdns_close_server_port(conn->dns_server_port); - conn->dns_server_port = NULL; + listener_conn = TO_LISTENER_CONN(conn); + + if (listener_conn->dns_server_port) { + evdns_close_server_port(listener_conn->dns_server_port); + listener_conn->dns_server_port = NULL; } } diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index fcca868885..73ec365647 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -14,12 +14,12 @@ void dnsserv_configure_listener(connection_t *conn); void dnsserv_close_listener(connection_t *conn); -void dnsserv_resolved(edge_connection_t *conn, +void dnsserv_resolved(entry_connection_t *conn, int answer_type, size_t answer_len, const char *answer, int ttl); -void dnsserv_reject_request(edge_connection_t *conn); +void dnsserv_reject_request(entry_connection_t *conn); int dnsserv_launch_request(const char *name, int is_reverse); #endif diff --git a/src/or/eventdns.c b/src/or/eventdns.c index 42e16aec7a..7fe376baff 100644 --- a/src/or/eventdns.c +++ b/src/or/eventdns.c @@ -1831,8 +1831,8 @@ evdns_server_request_respond(struct evdns_server_request *_req, int err) r = sendto(port->socket, req->response, req->response_len, 0, (struct sockaddr*) &req->addr, req->addrlen); if (r<0) { - int err = last_error(port->socket); - if (! error_is_eagain(err)) + int error = last_error(port->socket); + if (! error_is_eagain(error)) return -1; if (port->pending_replies) { @@ -2291,7 +2291,7 @@ _evdns_nameserver_add_impl(const struct sockaddr *address, evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns); - ns->socket = socket(address->sa_family, SOCK_DGRAM, 0); + ns->socket = tor_open_socket(address->sa_family, SOCK_DGRAM, 0); if (ns->socket < 0) { err = 1; goto out1; } #ifdef WIN32 { @@ -3040,7 +3040,7 @@ evdns_resolv_conf_parse(int flags, const char *const filename) { log(EVDNS_LOG_DEBUG, "Parsing resolv.conf file %s", filename); - fd = open(filename, O_RDONLY); + fd = tor_open_cloexec(filename, O_RDONLY, 0); if (fd < 0) { evdns_resolv_set_defaults(flags); return 1; @@ -3460,7 +3460,7 @@ main(int c, char **v) { if (servertest) { int sock; struct sockaddr_in my_addr; - sock = socket(PF_INET, SOCK_DGRAM, 0); + 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); diff --git a/src/or/geoip.c b/src/or/geoip.c index c51142c82e..73194ae9c6 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -44,6 +44,9 @@ 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; +/** SHA1 digest of the GeoIP file to include in extra-info descriptors. */ +static char geoip_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. */ @@ -113,10 +116,10 @@ geoip_parse_entry(const char *line) ++line; if (*line == '#') return 0; - if (sscanf(line,"%u,%u,%2s", &low, &high, b) == 3) { + if (tor_sscanf(line,"%u,%u,%2s", &low, &high, b) == 3) { geoip_add_entry(low, high, b); return 0; - } else if (sscanf(line,"\"%u\",\"%u\",\"%2s\",", &low, &high, b) == 3) { + } else if (tor_sscanf(line,"\"%u\",\"%u\",\"%2s\",", &low, &high, b) == 3) { geoip_add_entry(low, high, b); return 0; } else { @@ -159,7 +162,7 @@ _geoip_compare_key_to_entry(const void *_key, const void **_member) /** Return 1 if we should collect geoip stats on bridge users, and * include them in our extrainfo descriptor. Else return 0. */ int -should_record_bridge_info(or_options_t *options) +should_record_bridge_info(const or_options_t *options) { return options->BridgeRelay && options->BridgeRecordUsageByCountry; } @@ -196,13 +199,14 @@ init_geoip_countries(void) * with '#' (comments). */ int -geoip_load_file(const char *filename, or_options_t *options) +geoip_load_file(const char *filename, const or_options_t *options) { FILE *f; const char *msg = ""; int severity = options_need_geoip_info(options, &msg) ? LOG_WARN : LOG_INFO; + crypto_digest_env_t *geoip_digest_env = NULL; clear_geoip_db(); - if (!(f = fopen(filename, "r"))) { + if (!(f = tor_fopen_cloexec(filename, "r"))) { log_fn(severity, LD_GENERAL, "Failed to open GEOIP file %s. %s", filename, msg); return -1; @@ -214,11 +218,13 @@ geoip_load_file(const char *filename, or_options_t *options) smartlist_free(geoip_entries); } geoip_entries = smartlist_create(); + geoip_digest_env = crypto_new_digest_env(); log_notice(LD_GENERAL, "Parsing GEOIP file %s.", 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); } @@ -231,6 +237,11 @@ geoip_load_file(const char *filename, or_options_t *options) * 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); + crypto_free_digest_env(geoip_digest_env); + return 0; } @@ -278,6 +289,15 @@ geoip_is_loaded(void) return geoip_countries != NULL && geoip_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) +{ + return hex_str(geoip_digest, DIGEST_LEN); +} + /** Entry in a map from IP address to the last time we've seen an incoming * connection from that IP address. Used by bridges only, to track which * countries have them blocked. */ @@ -404,7 +424,7 @@ void geoip_note_client_seen(geoip_client_action_t action, uint32_t addr, time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); clientmap_entry_t lookup, *ent; if (action == GEOIP_CLIENT_CONNECT) { /* Only remember statistics as entry guard or as bridge. */ @@ -910,10 +930,9 @@ geoip_dirreq_stats_init(time_t now) start_of_dirreq_stats_interval = now; } -/** Stop collecting directory request stats in a way that we can re-start - * doing so in geoip_dirreq_stats_init(). */ +/** Reset counters for dirreq stats. */ void -geoip_dirreq_stats_term(void) +geoip_reset_dirreq_stats(time_t now) { SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, { c->n_v2_ns_requests = c->n_v3_ns_requests = 0; @@ -945,59 +964,44 @@ geoip_dirreq_stats_term(void) tor_free(this); } } - start_of_dirreq_stats_interval = 0; + start_of_dirreq_stats_interval = now; } -/** Write dirreq statistics to $DATADIR/stats/dirreq-stats and return when - * we would next want to write. */ -time_t -geoip_dirreq_stats_write(time_t now) +/** Stop collecting directory request stats in a way that we can re-start + * doing so in geoip_dirreq_stats_init(). */ +void +geoip_dirreq_stats_term(void) { - char *statsdir = NULL, *filename = NULL; - char *data_v2 = NULL, *data_v3 = NULL; - char written[ISO_TIME_LEN+1]; - open_file_t *open_file = NULL; + geoip_reset_dirreq_stats(0); +} + +/** Return a newly allocated string containing the dirreq statistics + * until <b>now</b>, or NULL if we're not collecting dirreq stats. Caller + * must ensure start_of_dirreq_stats_interval is in the past. */ +char * +geoip_format_dirreq_stats(time_t now) +{ + char t[ISO_TIME_LEN+1]; double v2_share = 0.0, v3_share = 0.0; - FILE *out; int i; + char *v3_ips_string, *v2_ips_string, *v3_reqs_string, *v2_reqs_string, + *v2_share_string = NULL, *v3_share_string = NULL, + *v3_direct_dl_string, *v2_direct_dl_string, + *v3_tunneled_dl_string, *v2_tunneled_dl_string; + char *result; if (!start_of_dirreq_stats_interval) - return 0; /* Not initialized. */ - if (start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL > now) - goto done; /* Not ready to write. */ + return NULL; /* Not initialized. */ - /* Discard all items in the client history that are too old. */ - geoip_remove_old_clients(start_of_dirreq_stats_interval); + tor_assert(now >= start_of_dirreq_stats_interval); - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) - goto done; - filename = get_datadir_fname2("stats", "dirreq-stats"); - data_v2 = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS_V2); - data_v3 = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS); - format_iso_time(written, now); - out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND | O_TEXT, - 0600, &open_file); - if (!out) - goto done; - if (fprintf(out, "dirreq-stats-end %s (%d s)\ndirreq-v3-ips %s\n" - "dirreq-v2-ips %s\n", written, - (unsigned) (now - start_of_dirreq_stats_interval), - data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0) - goto done; - tor_free(data_v2); - tor_free(data_v3); + 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); + v2_reqs_string = geoip_get_request_history( + GEOIP_CLIENT_NETWORKSTATUS_V2); + v3_reqs_string = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS); - data_v2 = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS_V2); - data_v3 = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS); - if (fprintf(out, "dirreq-v3-reqs %s\ndirreq-v2-reqs %s\n", - data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0) - goto done; - tor_free(data_v2); - tor_free(data_v3); - SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, { - c->n_v2_ns_requests = c->n_v3_ns_requests = 0; - }); #define RESPONSE_GRANULARITY 8 for (i = 0; i < GEOIP_NS_RESPONSE_NUM; i++) { ns_v2_responses[i] = round_uint32_to_next_multiple_of( @@ -1006,61 +1010,117 @@ geoip_dirreq_stats_write(time_t now) ns_v3_responses[i], RESPONSE_GRANULARITY); } #undef RESPONSE_GRANULARITY - if (fprintf(out, "dirreq-v3-resp ok=%u,not-enough-sigs=%u,unavailable=%u," - "not-found=%u,not-modified=%u,busy=%u\n", - ns_v3_responses[GEOIP_SUCCESS], - ns_v3_responses[GEOIP_REJECT_NOT_ENOUGH_SIGS], - ns_v3_responses[GEOIP_REJECT_UNAVAILABLE], - ns_v3_responses[GEOIP_REJECT_NOT_FOUND], - ns_v3_responses[GEOIP_REJECT_NOT_MODIFIED], - ns_v3_responses[GEOIP_REJECT_BUSY]) < 0) - goto done; - if (fprintf(out, "dirreq-v2-resp ok=%u,unavailable=%u," - "not-found=%u,not-modified=%u,busy=%u\n", - ns_v2_responses[GEOIP_SUCCESS], - ns_v2_responses[GEOIP_REJECT_UNAVAILABLE], - ns_v2_responses[GEOIP_REJECT_NOT_FOUND], - ns_v2_responses[GEOIP_REJECT_NOT_MODIFIED], - ns_v2_responses[GEOIP_REJECT_BUSY]) < 0) - goto done; - memset(ns_v2_responses, 0, sizeof(ns_v2_responses)); - memset(ns_v3_responses, 0, sizeof(ns_v3_responses)); + if (!geoip_get_mean_shares(now, &v2_share, &v3_share)) { - if (fprintf(out, "dirreq-v2-share %0.2lf%%\n", v2_share*100) < 0) - goto done; - if (fprintf(out, "dirreq-v3-share %0.2lf%%\n", v3_share*100) < 0) - goto done; + tor_asprintf(&v2_share_string, "dirreq-v2-share %0.2lf%%\n", + v2_share*100); + tor_asprintf(&v3_share_string, "dirreq-v3-share %0.2lf%%\n", + v3_share*100); } - data_v2 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS_V2, - DIRREQ_DIRECT); - data_v3 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS, - DIRREQ_DIRECT); - if (fprintf(out, "dirreq-v3-direct-dl %s\ndirreq-v2-direct-dl %s\n", - data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0) - goto done; - tor_free(data_v2); - tor_free(data_v3); - data_v2 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS_V2, - DIRREQ_TUNNELED); - data_v3 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS, - DIRREQ_TUNNELED); - if (fprintf(out, "dirreq-v3-tunneled-dl %s\ndirreq-v2-tunneled-dl %s\n", - data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0) - goto done; + v2_direct_dl_string = geoip_get_dirreq_history( + GEOIP_CLIENT_NETWORKSTATUS_V2, DIRREQ_DIRECT); + v3_direct_dl_string = geoip_get_dirreq_history( + GEOIP_CLIENT_NETWORKSTATUS, DIRREQ_DIRECT); + + v2_tunneled_dl_string = geoip_get_dirreq_history( + GEOIP_CLIENT_NETWORKSTATUS_V2, DIRREQ_TUNNELED); + v3_tunneled_dl_string = geoip_get_dirreq_history( + GEOIP_CLIENT_NETWORKSTATUS, DIRREQ_TUNNELED); + + /* Put everything together into a single string. */ + tor_asprintf(&result, "dirreq-stats-end %s (%d s)\n" + "dirreq-v3-ips %s\n" + "dirreq-v2-ips %s\n" + "dirreq-v3-reqs %s\n" + "dirreq-v2-reqs %s\n" + "dirreq-v3-resp ok=%u,not-enough-sigs=%u,unavailable=%u," + "not-found=%u,not-modified=%u,busy=%u\n" + "dirreq-v2-resp ok=%u,unavailable=%u," + "not-found=%u,not-modified=%u,busy=%u\n" + "%s" + "%s" + "dirreq-v3-direct-dl %s\n" + "dirreq-v2-direct-dl %s\n" + "dirreq-v3-tunneled-dl %s\n" + "dirreq-v2-tunneled-dl %s\n", + t, + (unsigned) (now - start_of_dirreq_stats_interval), + v3_ips_string ? v3_ips_string : "", + v2_ips_string ? v2_ips_string : "", + v3_reqs_string ? v3_reqs_string : "", + v2_reqs_string ? v2_reqs_string : "", + ns_v3_responses[GEOIP_SUCCESS], + ns_v3_responses[GEOIP_REJECT_NOT_ENOUGH_SIGS], + ns_v3_responses[GEOIP_REJECT_UNAVAILABLE], + ns_v3_responses[GEOIP_REJECT_NOT_FOUND], + ns_v3_responses[GEOIP_REJECT_NOT_MODIFIED], + ns_v3_responses[GEOIP_REJECT_BUSY], + ns_v2_responses[GEOIP_SUCCESS], + ns_v2_responses[GEOIP_REJECT_UNAVAILABLE], + ns_v2_responses[GEOIP_REJECT_NOT_FOUND], + ns_v2_responses[GEOIP_REJECT_NOT_MODIFIED], + ns_v2_responses[GEOIP_REJECT_BUSY], + v2_share_string ? v2_share_string : "", + v3_share_string ? v3_share_string : "", + v3_direct_dl_string ? v3_direct_dl_string : "", + v2_direct_dl_string ? v2_direct_dl_string : "", + v3_tunneled_dl_string ? v3_tunneled_dl_string : "", + v2_tunneled_dl_string ? v2_tunneled_dl_string : ""); + + /* Free partial strings. */ + tor_free(v3_ips_string); + tor_free(v2_ips_string); + tor_free(v3_reqs_string); + tor_free(v2_reqs_string); + tor_free(v2_share_string); + tor_free(v3_share_string); + tor_free(v3_direct_dl_string); + tor_free(v2_direct_dl_string); + tor_free(v3_tunneled_dl_string); + tor_free(v2_tunneled_dl_string); - finish_writing_to_file(open_file); - open_file = NULL; + return result; +} - start_of_dirreq_stats_interval = now; +/** If 24 hours have passed since the beginning of the current dirreq + * stats period, write dirreq stats to $DATADIR/stats/dirreq-stats + * (possibly overwriting an existing file) and reset counters. Return + * when we would next want to write dirreq stats or 0 if we never want to + * write. */ +time_t +geoip_dirreq_stats_write(time_t now) +{ + char *statsdir = NULL, *filename = NULL, *str = NULL; + + if (!start_of_dirreq_stats_interval) + return 0; /* Not initialized. */ + if (start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL > now) + goto done; /* Not ready to write. */ + + /* Discard all items in the client history that are too old. */ + geoip_remove_old_clients(start_of_dirreq_stats_interval); + + /* Generate history string .*/ + str = geoip_format_dirreq_stats(now); + + /* Write dirreq-stats string to disk. */ + statsdir = get_datadir_fname("stats"); + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); + goto done; + } + filename = get_datadir_fname2("stats", "dirreq-stats"); + if (write_str_to_file(filename, str, 0) < 0) + log_warn(LD_HIST, "Unable to write dirreq statistics to disk!"); + + /* Reset measurement interval start. */ + geoip_reset_dirreq_stats(now); done: - if (open_file) - abort_writing_to_file(open_file); - tor_free(filename); tor_free(statsdir); - tor_free(data_v2); - tor_free(data_v3); + tor_free(filename); + tor_free(str); return start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL; } @@ -1140,8 +1200,8 @@ static char *bridge_stats_extrainfo = NULL; /** Return a newly allocated string holding our bridge usage stats by country * in a format suitable for inclusion in an extrainfo document. Return NULL on * failure. */ -static char * -format_bridge_stats_extrainfo(time_t now) +char * +geoip_format_bridge_stats(time_t now) { char *out = NULL, *data = NULL; long duration = now - start_of_bridge_stats_interval; @@ -1149,6 +1209,8 @@ format_bridge_stats_extrainfo(time_t now) if (duration < 0) return NULL; + if (!start_of_bridge_stats_interval) + return NULL; /* Not initialized. */ format_iso_time(written, now); data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); @@ -1198,7 +1260,7 @@ geoip_bridge_stats_write(time_t now) geoip_remove_old_clients(start_of_bridge_stats_interval); /* Generate formatted string */ - val = format_bridge_stats_extrainfo(now); + val = geoip_format_bridge_stats(now); if (val == NULL) goto done; @@ -1275,25 +1337,54 @@ geoip_entry_stats_init(time_t now) start_of_entry_stats_interval = now; } +/** Reset counters for entry stats. */ +void +geoip_reset_entry_stats(time_t now) +{ + client_history_clear(); + start_of_entry_stats_interval = now; +} + /** Stop collecting entry stats in a way that we can re-start doing so in * geoip_entry_stats_init(). */ void geoip_entry_stats_term(void) { - client_history_clear(); - start_of_entry_stats_interval = 0; + geoip_reset_entry_stats(0); } -/** Write entry statistics to $DATADIR/stats/entry-stats and return time - * when we would next want to write. */ +/** Return a newly allocated string containing the entry statistics + * until <b>now</b>, or NULL if we're not collecting entry stats. Caller + * must ensure start_of_entry_stats_interval lies in the past. */ +char * +geoip_format_entry_stats(time_t now) +{ + char t[ISO_TIME_LEN+1]; + char *data = NULL; + char *result; + + if (!start_of_entry_stats_interval) + return NULL; /* Not initialized. */ + + tor_assert(now >= start_of_entry_stats_interval); + + data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); + 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_free(data); + return result; +} + +/** If 24 hours have passed since the beginning of the current entry stats + * period, write entry stats to $DATADIR/stats/entry-stats (possibly + * overwriting an existing file) and reset counters. Return when we would + * next want to write entry stats or 0 if we never want to write. */ time_t geoip_entry_stats_write(time_t now) { - char *statsdir = NULL, *filename = NULL; - char *data = NULL; - char written[ISO_TIME_LEN+1]; - open_file_t *open_file = NULL; - FILE *out; + char *statsdir = NULL, *filename = NULL, *str = NULL; if (!start_of_entry_stats_interval) return 0; /* Not initialized. */ @@ -1303,31 +1394,26 @@ geoip_entry_stats_write(time_t now) /* Discard all items in the client history that are too old. */ geoip_remove_old_clients(start_of_entry_stats_interval); + /* Generate history string .*/ + str = geoip_format_entry_stats(now); + + /* Write entry-stats string to disk. */ statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); goto done; + } filename = get_datadir_fname2("stats", "entry-stats"); - data = geoip_get_client_history(GEOIP_CLIENT_CONNECT); - format_iso_time(written, now); - out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND | O_TEXT, - 0600, &open_file); - if (!out) - goto done; - if (fprintf(out, "entry-stats-end %s (%u s)\nentry-ips %s\n", - written, (unsigned) (now - start_of_entry_stats_interval), - data ? data : "") < 0) - goto done; + if (write_str_to_file(filename, str, 0) < 0) + log_warn(LD_HIST, "Unable to write entry statistics to disk!"); - start_of_entry_stats_interval = now; + /* Reset measurement interval start. */ + geoip_reset_entry_stats(now); - finish_writing_to_file(open_file); - open_file = NULL; done: - if (open_file) - abort_writing_to_file(open_file); - tor_free(filename); tor_free(statsdir); - tor_free(data); + tor_free(filename); + tor_free(str); return start_of_entry_stats_interval + WRITE_STATS_INTERVAL; } diff --git a/src/or/geoip.h b/src/or/geoip.h index 24f7c5b931..ce3841967f 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -15,12 +15,13 @@ #ifdef GEOIP_PRIVATE int geoip_parse_entry(const char *line); #endif -int should_record_bridge_info(or_options_t *options); -int geoip_load_file(const char *filename, or_options_t *options); +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_get_n_countries(void); const char *geoip_get_country_name(country_t num); int geoip_is_loaded(void); +const char *geoip_db_digest(void); country_t geoip_get_country(const char *countrycode); void geoip_note_client_seen(geoip_client_action_t action, @@ -42,12 +43,17 @@ void geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type, dirreq_state_t new_state); void geoip_dirreq_stats_init(time_t now); +void geoip_reset_dirreq_stats(time_t now); +char *geoip_format_dirreq_stats(time_t now); time_t geoip_dirreq_stats_write(time_t now); void geoip_dirreq_stats_term(void); void geoip_entry_stats_init(time_t now); time_t geoip_entry_stats_write(time_t now); void geoip_entry_stats_term(void); +void geoip_reset_entry_stats(time_t now); +char *geoip_format_entry_stats(time_t now); void geoip_bridge_stats_init(time_t now); +char *geoip_format_bridge_stats(time_t now); time_t geoip_bridge_stats_write(time_t now); void geoip_bridge_stats_term(void); const char *geoip_get_bridge_stats_extrainfo(time_t); diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 2f7170fa24..6fd2b4f197 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -21,6 +21,7 @@ hibernating, phase 2: - close all OR/AP/exit conns) */ +#define HIBERNATE_PRIVATE #include "or.h" #include "config.h" #include "connection.h" @@ -29,26 +30,11 @@ hibernating, phase 2: #include "main.h" #include "router.h" -/** Possible values of hibernate_state */ -typedef enum { - /** We are running normally. */ - HIBERNATE_STATE_LIVE=1, - /** We're trying to shut down cleanly, and we'll kill all active connections - * at shutdown_time. */ - HIBERNATE_STATE_EXITING=2, - /** We're running low on allocated bandwidth for this period, so we won't - * accept any new connections. */ - HIBERNATE_STATE_LOWBANDWIDTH=3, - /** We are hibernating, and we won't wake up till there's more bandwidth to - * use. */ - HIBERNATE_STATE_DORMANT=4 -} hibernate_state_t; - extern long stats_n_seconds_working; /* published uptime */ /** Are we currently awake, asleep, running out of bandwidth, or shutting * down? */ -static hibernate_state_t hibernate_state = HIBERNATE_STATE_LIVE; +static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL; /** If are hibernating, when do we plan to wake up? Set to 0 if we * aren't hibernating. */ static time_t hibernate_end_time = 0; @@ -134,7 +120,7 @@ static void accounting_set_wakeup_time(void); * options->AccountingStart. Return 0 on success, -1 on failure. If * <b>validate_only</b> is true, do not change the current settings. */ int -accounting_parse_options(or_options_t *options, int validate_only) +accounting_parse_options(const or_options_t *options, int validate_only) { time_unit_t unit; int ok, idx; @@ -249,7 +235,7 @@ accounting_parse_options(or_options_t *options, int validate_only) * hibernate, return 1, else return 0. */ int -accounting_is_enabled(or_options_t *options) +accounting_is_enabled(const or_options_t *options) { if (options->AccountingMax) return 1; @@ -411,7 +397,7 @@ static void update_expected_bandwidth(void) { uint64_t expected; - or_options_t *options= get_options(); + const or_options_t *options= get_options(); uint64_t max_configured = (options->RelayBandwidthRate > 0 ? options->RelayBandwidthRate : options->BandwidthRate) * 60; @@ -750,7 +736,7 @@ static void hibernate_begin(hibernate_state_t new_state, time_t now) { connection_t *conn; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (new_state == HIBERNATE_STATE_EXITING && hibernate_state != HIBERNATE_STATE_LIVE) { @@ -804,10 +790,12 @@ static void hibernate_end(hibernate_state_t new_state) { tor_assert(hibernate_state == HIBERNATE_STATE_LOWBANDWIDTH || - hibernate_state == HIBERNATE_STATE_DORMANT); + hibernate_state == HIBERNATE_STATE_DORMANT || + hibernate_state == HIBERNATE_STATE_INITIAL); /* listeners will be relaunched in run_scheduled_events() in main.c */ - log_notice(LD_ACCT,"Hibernation period ended. Resuming normal activity."); + if (hibernate_state != HIBERNATE_STATE_INITIAL) + log_notice(LD_ACCT,"Hibernation period ended. Resuming normal activity."); hibernate_state = new_state; hibernate_end_time = 0; /* no longer hibernating */ @@ -856,7 +844,7 @@ hibernate_go_dormant(time_t now) connection_edge_end(TO_EDGE_CONN(conn), END_STREAM_REASON_HIBERNATING); log_info(LD_NET,"Closing conn type %d", conn->type); if (conn->type == CONN_TYPE_AP) /* send socks failure if needed */ - connection_mark_unattached_ap(TO_EDGE_CONN(conn), + connection_mark_unattached_ap(TO_ENTRY_CONN(conn), END_STREAM_REASON_HIBERNATING); else connection_mark_for_close(conn); @@ -939,7 +927,8 @@ consider_hibernation(time_t now) /* Else, we aren't hibernating. See if it's time to start hibernating, or to * go dormant. */ - if (hibernate_state == HIBERNATE_STATE_LIVE) { + if (hibernate_state == HIBERNATE_STATE_LIVE || + hibernate_state == HIBERNATE_STATE_INITIAL) { if (hibernate_soft_limit_reached()) { log_notice(LD_ACCT, "Bandwidth soft limit reached; commencing hibernation. " @@ -951,6 +940,8 @@ consider_hibernation(time_t now) "Commencing hibernation. We will wake up at %s local time.", buf); hibernate_go_dormant(now); + } else if (hibernate_state == HIBERNATE_STATE_INITIAL) { + hibernate_end(HIBERNATE_STATE_LIVE); } } @@ -1017,3 +1008,13 @@ getinfo_helper_accounting(control_connection_t *conn, return 0; } +/** + * Manually change the hibernation state. Private; used only by the unit + * tests. + */ +void +hibernate_set_state_for_testing_(hibernate_state_t newstate) +{ + hibernate_state = newstate; +} + diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 2aea0fab0c..78e7bb75e9 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -12,8 +12,8 @@ #ifndef _TOR_HIBERNATE_H #define _TOR_HIBERNATE_H -int accounting_parse_options(or_options_t *options, int validate_only); -int accounting_is_enabled(or_options_t *options); +int accounting_parse_options(const or_options_t *options, int validate_only); +int accounting_is_enabled(const or_options_t *options); void configure_accounting(time_t now); void accounting_run_housekeeping(time_t now); void accounting_add_bytes(size_t n_read, size_t n_written, int seconds); @@ -25,5 +25,27 @@ int getinfo_helper_accounting(control_connection_t *conn, const char *question, char **answer, const char **errmsg); +#ifdef HIBERNATE_PRIVATE +/** Possible values of hibernate_state */ +typedef enum { + /** We are running normally. */ + HIBERNATE_STATE_LIVE=1, + /** We're trying to shut down cleanly, and we'll kill all active connections + * at shutdown_time. */ + HIBERNATE_STATE_EXITING=2, + /** We're running low on allocated bandwidth for this period, so we won't + * accept any new connections. */ + HIBERNATE_STATE_LOWBANDWIDTH=3, + /** We are hibernating, and we won't wake up till there's more bandwidth to + * use. */ + HIBERNATE_STATE_DORMANT=4, + /** We start out in state default, which means we havent decided which state + * we're in. */ + HIBERNATE_STATE_INITIAL=5 +} hibernate_state_t; + +void hibernate_set_state_for_testing_(hibernate_state_t newstate); +#endif + #endif diff --git a/src/or/main.c b/src/or/main.c index b1159746a2..c1a7015e68 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -33,9 +33,11 @@ #include "main.h" #include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "ntmain.h" #include "onion.h" #include "policies.h" +#include "transports.h" #include "relay.h" #include "rendclient.h" #include "rendcommon.h" @@ -44,6 +46,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "status.h" #ifdef USE_DMALLOC #include <dmalloc.h> #include <openssl/crypto.h> @@ -56,6 +59,10 @@ #include <event.h> #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#endif + void evdns_shutdown(int); /********* PROTOTYPES **********/ @@ -71,6 +78,7 @@ static int connection_should_read_from_linked_conn(connection_t *conn); /********* START VARIABLES **********/ +#ifndef USE_BUFFEREVENTS int global_read_bucket; /**< Max number of bytes I can read this second. */ int global_write_bucket; /**< Max number of bytes I can write this second. */ @@ -78,13 +86,17 @@ int global_write_bucket; /**< Max number of bytes I can write this second. */ int global_relayed_read_bucket; /** Max number of relayed (bandwidth class 1) bytes I can write this second. */ int global_relayed_write_bucket; - /** What was the read bucket before the last second_elapsed_callback() call? * (used to determine how many bytes we've read). */ static int stats_prev_global_read_bucket; /** What was the write bucket before the last second_elapsed_callback() call? * (used to determine how many bytes we've written). */ static int stats_prev_global_write_bucket; +#endif + +static uint64_t stats_prev_n_read = 0; +static uint64_t stats_prev_n_written = 0; + /* XXX we might want to keep stats about global_relayed_*_bucket too. Or not.*/ /** How many bytes have we read since we started the process? */ static uint64_t stats_n_bytes_read = 0; @@ -103,6 +115,8 @@ static time_t time_to_check_for_correct_dns = 0; static time_t time_of_last_signewnym = 0; /** Is there a signewnym request we're currently waiting to handle? */ static int signewnym_is_pending = 0; +/** How many times have we called newnym? */ +static unsigned newnym_epoch = 0; /** Smartlist of all open connections. */ static smartlist_t *connection_array = NULL; @@ -141,6 +155,12 @@ int can_complete_circuit=0; * they are obsolete? */ #define TLS_HANDSHAKE_TIMEOUT (60) +/** Decides our behavior when no logs are configured/before any + * logs have been configured. For 0, we log notice to stdout as normal. + * For 1, we log warnings only. For 2, we log nothing. + */ +int quiet_level = 0; + /********* END VARIABLES ************/ /**************************************************************************** @@ -150,12 +170,38 @@ int can_complete_circuit=0; * ****************************************************************************/ +#if 0 && defined(USE_BUFFEREVENTS) +static void +free_old_inbuf(connection_t *conn) +{ + if (! conn->inbuf) + return; + + tor_assert(conn->outbuf); + tor_assert(buf_datalen(conn->inbuf) == 0); + tor_assert(buf_datalen(conn->outbuf) == 0); + buf_free(conn->inbuf); + buf_free(conn->outbuf); + conn->inbuf = conn->outbuf = NULL; + + if (conn->read_event) { + event_del(conn->read_event); + tor_event_free(conn->read_event); + } + if (conn->write_event) { + event_del(conn->read_event); + tor_event_free(conn->write_event); + } + conn->read_event = conn->write_event = NULL; +} +#endif + /** Add <b>conn</b> to the array of connections that we can poll on. The * connection's socket must be set; the connection starts out * non-reading and non-writing. */ int -connection_add(connection_t *conn) +connection_add_impl(connection_t *conn, int is_connecting) { tor_assert(conn); tor_assert(SOCKET_OK(conn->s) || @@ -167,11 +213,63 @@ connection_add(connection_t *conn) conn->conn_array_index = smartlist_len(connection_array); smartlist_add(connection_array, conn); - if (SOCKET_OK(conn->s) || conn->linked) { +#ifdef USE_BUFFEREVENTS + if (connection_type_uses_bufferevent(conn)) { + if (SOCKET_OK(conn->s) && !conn->linked) { + conn->bufev = bufferevent_socket_new( + tor_libevent_get_base(), + conn->s, + BEV_OPT_DEFER_CALLBACKS); + if (!conn->bufev) { + log_warn(LD_BUG, "Unable to create socket bufferevent"); + smartlist_del(connection_array, conn->conn_array_index); + conn->conn_array_index = -1; + return -1; + } + if (is_connecting) { + /* Put the bufferevent into a "connecting" state so that we'll get + * a "connected" event callback on successful write. */ + bufferevent_socket_connect(conn->bufev, NULL, 0); + } + connection_configure_bufferevent_callbacks(conn); + } else if (conn->linked && conn->linked_conn && + connection_type_uses_bufferevent(conn->linked_conn)) { + tor_assert(!(SOCKET_OK(conn->s))); + if (!conn->bufev) { + struct bufferevent *pair[2] = { NULL, NULL }; + if (bufferevent_pair_new(tor_libevent_get_base(), + BEV_OPT_DEFER_CALLBACKS, + pair) < 0) { + log_warn(LD_BUG, "Unable to create bufferevent pair"); + smartlist_del(connection_array, conn->conn_array_index); + conn->conn_array_index = -1; + return -1; + } + tor_assert(pair[0]); + conn->bufev = pair[0]; + conn->linked_conn->bufev = pair[1]; + } /* else the other side already was added, and got a bufferevent_pair */ + connection_configure_bufferevent_callbacks(conn); + } else { + tor_assert(!conn->linked); + } + + if (conn->bufev) + tor_assert(conn->inbuf == NULL); + + if (conn->linked_conn && conn->linked_conn->bufev) + tor_assert(conn->linked_conn->inbuf == NULL); + } +#else + (void) is_connecting; +#endif + + if (!HAS_BUFFEREVENT(conn) && (SOCKET_OK(conn->s) || conn->linked)) { conn->read_event = tor_event_new(tor_libevent_get_base(), conn->s, EV_READ|EV_PERSIST, conn_read_callback, conn); conn->write_event = tor_event_new(tor_libevent_get_base(), conn->s, EV_WRITE|EV_PERSIST, conn_write_callback, conn); + /* XXXX CHECK FOR NULL RETURN! */ } log_debug(LD_NET,"new conn type %s, socket %d, address %s, n_conns %d.", @@ -195,7 +293,13 @@ connection_unregister_events(connection_t *conn) log_warn(LD_BUG, "Error removing write event for %d", (int)conn->s); tor_free(conn->write_event); } - if (conn->dns_server_port) { +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + bufferevent_free(conn->bufev); + conn->bufev = NULL; + } +#endif + if (conn->type == CONN_TYPE_AP_DNS_LISTENER) { dnsserv_close_listener(conn); } } @@ -303,12 +407,37 @@ get_connection_array(void) return connection_array; } +/** Provides the traffic read and written over the life of the process. */ + +uint64_t +get_bytes_read(void) +{ + return stats_n_bytes_read; +} + +uint64_t +get_bytes_written(void) +{ + return stats_n_bytes_written; +} + /** Set the event mask on <b>conn</b> to <b>events</b>. (The event * mask is a bitmask whose bits are READ_EVENT and WRITE_EVENT) */ void connection_watch_events(connection_t *conn, watchable_events_t events) { + IF_HAS_BUFFEREVENT(conn, { + short ev = ((short)events) & (EV_READ|EV_WRITE); + short old_ev = bufferevent_get_enabled(conn->bufev); + if ((ev & ~old_ev) != 0) { + bufferevent_enable(conn->bufev, ev); + } + if ((old_ev & ~ev) != 0) { + bufferevent_disable(conn->bufev, old_ev & ~ev); + } + return; + }); if (events & READ_EVENT) connection_start_reading(conn); else @@ -326,6 +455,9 @@ connection_is_reading(connection_t *conn) { tor_assert(conn); + IF_HAS_BUFFEREVENT(conn, + return (bufferevent_get_enabled(conn->bufev) & EV_READ) != 0; + ); return conn->reading_from_linked_conn || (conn->read_event && event_pending(conn->read_event, EV_READ, NULL)); } @@ -335,6 +467,12 @@ void connection_stop_reading(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_disable(conn->bufev, EV_READ); + return; + }); + tor_assert(conn->read_event); if (conn->linked) { @@ -354,6 +492,12 @@ void connection_start_reading(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_enable(conn->bufev, EV_READ); + return; + }); + tor_assert(conn->read_event); if (conn->linked) { @@ -375,6 +519,10 @@ connection_is_writing(connection_t *conn) { tor_assert(conn); + IF_HAS_BUFFEREVENT(conn, + return (bufferevent_get_enabled(conn->bufev) & EV_WRITE) != 0; + ); + return conn->writing_to_linked_conn || (conn->write_event && event_pending(conn->write_event, EV_WRITE, NULL)); } @@ -384,6 +532,12 @@ void connection_stop_writing(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_disable(conn->bufev, EV_WRITE); + return; + }); + tor_assert(conn->write_event); if (conn->linked) { @@ -404,6 +558,12 @@ void connection_start_writing(connection_t *conn) { tor_assert(conn); + + IF_HAS_BUFFEREVENT(conn, { + bufferevent_enable(conn->bufev, EV_WRITE); + return; + }); + tor_assert(conn->write_event); if (conn->linked) { @@ -590,9 +750,32 @@ conn_close_if_marked(int i) assert_connection_ok(conn, now); /* assert_all_pending_dns_resolves_ok(); */ - log_debug(LD_NET,"Cleaning up connection (fd %d).",(int)conn->s); - if ((SOCKET_OK(conn->s) || conn->linked_conn) - && connection_wants_to_flush(conn)) { +#ifdef USE_BUFFEREVENTS + if (conn->bufev) { + if (conn->hold_open_until_flushed && + evbuffer_get_length(bufferevent_get_output(conn->bufev))) { + /* don't close yet. */ + return 0; + } + if (conn->linked_conn && ! conn->linked_conn->marked_for_close) { + /* We need to do this explicitly so that the linked connection + * notices that there was an EOF. */ + bufferevent_flush(conn->bufev, EV_WRITE, BEV_FINISHED); + } + } +#endif + + log_debug(LD_NET,"Cleaning up connection (fd %d).",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 + proxy. We should warn the user about this. */ + if (conn->proxy_state == PROXY_INFANT) + log_failed_proxy_connection(conn); + + IF_HAS_BUFFEREVENT(conn, goto unlink); + if ((SOCKET_OK(conn->s) || conn->linked_conn) && + connection_wants_to_flush(conn)) { /* s == -1 means it's an incomplete edge connection, or that the socket * has already been closed as unflushable. */ ssize_t sz = connection_bucket_write_limit(conn, now); @@ -614,8 +797,8 @@ conn_close_if_marked(int i) } log_debug(LD_GENERAL, "Flushed last %d bytes from a linked conn; " "%d left; flushlen %d; wants-to-flush==%d", retval, - (int)buf_datalen(conn->outbuf), - (int)conn->outbuf_flushlen, + (int)connection_get_outbuf_len(conn), + (int)conn->outbuf_flushlen, connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { @@ -652,13 +835,17 @@ conn_close_if_marked(int i) "something is wrong with your network connection, or " "something is wrong with theirs. " "(fd %d, type %s, state %d, marked at %s:%d).", - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), escaped_safe_str_client(conn->address), (int)conn->s, conn_type_to_string(conn->type), conn->state, conn->marked_for_close_file, conn->marked_for_close); } } + +#ifdef USE_BUFFEREVENTS + unlink: +#endif connection_unlink(conn); /* unlink, remove, free */ return 1; } @@ -679,13 +866,13 @@ directory_all_unreachable(time_t now) while ((conn = connection_get_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT))) { - edge_connection_t *edge_conn = TO_EDGE_CONN(conn); + entry_connection_t *entry_conn = TO_ENTRY_CONN(conn); log_notice(LD_NET, "Is your network connection down? " "Failing connection to '%s:%d'.", - safe_str_client(edge_conn->socks_request->address), - edge_conn->socks_request->port); - connection_mark_unattached_ap(edge_conn, + safe_str_client(entry_conn->socks_request->address), + entry_conn->socks_request->port); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_NET_UNREACHABLE); } control_event_general_status(LOG_ERR, "DIR_ALL_UNREACHABLE"); @@ -696,18 +883,19 @@ directory_all_unreachable(time_t now) void directory_info_has_arrived(time_t now, int from_cache) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!router_have_minimum_dir_info()) { int quiet = 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()); - update_router_descriptor_downloads(now); + update_all_descriptor_downloads(now); return; } else { - if (directory_fetches_from_authorities(options)) - update_router_descriptor_downloads(now); + if (directory_fetches_from_authorities(options)) { + update_all_descriptor_downloads(now); + } /* if we have enough dir info, then update our guard status with * whatever we just learned. */ @@ -740,12 +928,13 @@ run_connection_housekeeping(int i, time_t now) { cell_t cell; connection_t *conn = smartlist_get(connection_array, i); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); or_connection_t *or_conn; int past_keepalive = now >= conn->timestamp_lastwritten + options->KeepalivePeriod; - if (conn->outbuf && !buf_datalen(conn->outbuf) && conn->type == CONN_TYPE_OR) + if (conn->outbuf && !connection_get_outbuf_len(conn) && + conn->type == CONN_TYPE_OR) TO_OR_CONN(conn)->timestamp_lastempty = now; if (conn->marked_for_close) { @@ -765,7 +954,7 @@ run_connection_housekeeping(int i, time_t now) /* This check is temporary; it's to let us know whether we should consider * parsing partial serverdesc responses. */ if (conn->purpose == DIR_PURPOSE_FETCH_SERVERDESC && - buf_datalen(conn->inbuf)>=1024) { + connection_get_inbuf_len(conn) >= 1024) { log_info(LD_DIR,"Trying to extract information from wedged server desc " "download."); connection_dir_reached_eof(TO_DIR_CONN(conn)); @@ -782,7 +971,11 @@ run_connection_housekeeping(int i, time_t now) the connection or send a keepalive, depending. */ or_conn = TO_OR_CONN(conn); +#ifdef USE_BUFFEREVENTS + tor_assert(conn->bufev); +#else tor_assert(conn->outbuf); +#endif if (or_conn->is_bad_for_new_circs && !or_conn->n_circuits) { /* It's bad for new circuits, and has no unmarked circuits on it: @@ -794,8 +987,7 @@ 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_for_close(conn); - conn->hold_open_until_flushed = 1; + connection_mark_and_flush(conn); } else if (!connection_state_is_open(conn)) { if (past_keepalive) { /* We never managed to actually get this connection open and happy. */ @@ -804,13 +996,12 @@ run_connection_housekeeping(int i, time_t now) connection_mark_for_close(conn); } } else if (we_are_hibernating() && !or_conn->n_circuits && - !buf_datalen(conn->outbuf)) { + !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_for_close(conn); - conn->hold_open_until_flushed = 1; + connection_mark_and_flush(conn); } else if (!or_conn->n_circuits && now >= or_conn->timestamp_last_added_nonpadding + IDLE_OR_CONN_TIMEOUT) { @@ -818,7 +1009,6 @@ run_connection_housekeeping(int i, time_t now) "[idle %d].", (int)conn->s,conn->address, conn->port, (int)(now - or_conn->timestamp_last_added_nonpadding)); connection_mark_for_close(conn); - conn->hold_open_until_flushed = 1; } else if ( now >= or_conn->timestamp_lastempty + options->KeepalivePeriod*10 && now >= conn->timestamp_lastwritten + options->KeepalivePeriod*10) { @@ -826,10 +1016,10 @@ run_connection_housekeeping(int i, time_t now) "Expiring stuck OR connection to fd %d (%s:%d). (%d bytes to " "flush; %d seconds since last write)", (int)conn->s, conn->address, conn->port, - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), (int)(now-conn->timestamp_lastwritten)); connection_mark_for_close(conn); - } else if (past_keepalive && !buf_datalen(conn->outbuf)) { + } else if (past_keepalive && !connection_get_outbuf_len(conn)) { /* send a padding cell */ log_fn(LOG_DEBUG,LD_OR,"Sending keepalive to (%s:%d)", conn->address, conn->port); @@ -844,7 +1034,7 @@ run_connection_housekeeping(int i, time_t now) static void signewnym_impl(time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!proxy_mode(options)) { log_info(LD_CONTROL, "Ignoring SIGNAL NEWNYM because client functionality " "is disabled."); @@ -856,6 +1046,17 @@ signewnym_impl(time_t now) rend_client_purge_state(); time_of_last_signewnym = now; signewnym_is_pending = 0; + + ++newnym_epoch; + + control_event_signal(SIGNEWNYM); +} + +/** Return the number of times that signewnym has been called. */ +unsigned +get_signewnym_epoch(void) +{ + return newnym_epoch; } /** Perform regular maintenance tasks. This function gets run once per @@ -881,10 +1082,14 @@ run_scheduled_events(time_t now) static time_t time_to_check_for_expired_networkstatus = 0; static time_t time_to_write_stats_files = 0; static time_t time_to_write_bridge_stats = 0; + static time_t time_to_check_port_forwarding = 0; static time_t time_to_launch_reachability_tests = 0; static int should_init_bridge_stats = 1; static time_t time_to_retry_dns_init = 0; - or_options_t *options = get_options(); + static time_t time_to_next_heartbeat = 0; + static int has_validated_pt = 0; + const or_options_t *options = get_options(); + int is_server = server_mode(options); int i; int have_dir_info; @@ -895,6 +1100,16 @@ run_scheduled_events(time_t now) */ consider_hibernation(now); +#if 0 + { + static time_t nl_check_time = 0; + if (nl_check_time <= now) { + nodelist_assert_ok(); + nl_check_time = now + 30; + } + } +#endif + /* 0b. If we've deferred a signewnym, make sure it gets handled * eventually. */ if (signewnym_is_pending && @@ -923,7 +1138,7 @@ run_scheduled_events(time_t now) } if (time_to_try_getting_descriptors < now) { - update_router_descriptor_downloads(now); + update_all_descriptor_downloads(now); update_extrainfo_downloads(now); if (router_have_minimum_dir_info()) time_to_try_getting_descriptors = now + LAZY_DESCRIPTOR_RETRY_INTERVAL; @@ -1048,6 +1263,16 @@ run_scheduled_events(time_t now) if (next_write && next_write < next_time_to_write_stats_files) next_time_to_write_stats_files = next_write; } + if (options->ConnDirectionStatistics) { + time_t next_write = rep_hist_conn_stats_write(time_to_write_stats_files); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->BridgeAuthoritativeDir) { + time_t next_write = rep_hist_desc_stats_write(time_to_write_stats_files); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } time_to_write_stats_files = next_time_to_write_stats_files; } @@ -1076,10 +1301,9 @@ run_scheduled_events(time_t now) /* Remove old information from rephist and the rend cache. */ if (time_to_clean_caches < now) { rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(); - rend_cache_clean_v2_descs_as_dir(); - if (authdir_mode_v3(options)) - microdesc_cache_rebuild(NULL, 0); + rend_cache_clean(now); + rend_cache_clean_v2_descs_as_dir(now); + microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) time_to_clean_caches = now + CLEAN_CACHES_INTERVAL; } @@ -1088,7 +1312,7 @@ run_scheduled_events(time_t now) /* If we're a server and initializing dns failed, retry periodically. */ if (time_to_retry_dns_init < now) { time_to_retry_dns_init = now + RETRY_DNS_INTERVAL; - if (server_mode(options) && has_dns_init_failed()) + if (is_server && has_dns_init_failed()) dns_init(); } @@ -1111,15 +1335,11 @@ run_scheduled_events(time_t now) time_to_check_ipaddress = now + CHECK_IPADDRESS_INTERVAL; check_descriptor_ipaddress_changed(now); } -/** If our router descriptor ever goes this long without being regenerated - * because something changed, we force an immediate regenerate-and-upload. */ -#define FORCE_REGENERATE_DESCRIPTOR_INTERVAL (18*60*60) - mark_my_descriptor_dirty_if_older_than( - now - FORCE_REGENERATE_DESCRIPTOR_INTERVAL); + mark_my_descriptor_dirty_if_too_old(now); consider_publishable_server(0); /* also, check religiously for reachability, if it's within the first * 20 minutes of our uptime. */ - if (server_mode(options) && + if (is_server && (can_complete_circuit || !any_predicted_circuits(now)) && !we_are_hibernating()) { if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { @@ -1130,7 +1350,7 @@ run_scheduled_events(time_t now) /* If we haven't checked for 12 hours and our bandwidth estimate is * low, do another bandwidth test. This is especially important for * bridges, since they might go long periods without much use. */ - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (time_to_recheck_bandwidth && me && me->bandwidthcapacity < me->bandwidthrate && me->bandwidthcapacity < 51200) { @@ -1251,13 +1471,44 @@ run_scheduled_events(time_t now) } } - /** 10b. write bridge networkstatus file to disk */ + /** 10. write bridge networkstatus file to disk */ if (options->BridgeAuthoritativeDir && time_to_write_bridge_status_file < now) { networkstatus_dump_bridge_status_to_file(now); #define BRIDGE_STATUSFILE_INTERVAL (30*60) time_to_write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL; } + + /** 11. check the port forwarding app */ + if (time_to_check_port_forwarding < now && + options->PortForwarding && + is_server) { +#define PORT_FORWARDING_CHECK_INTERVAL 5 + tor_check_port_forwarding(options->PortForwardingHelper, + options->DirPort, + options->ORPort, + now); + time_to_check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; + } + + /** 11b. check pending unconfigured managed proxies */ + if (pt_proxies_configuration_pending()) + pt_configure_remaining_proxies(); + + /** 11c. validate pluggable transports configuration if we need to */ + if (!has_validated_pt && + (options->Bridges || options->ClientTransportPlugin)) { + if (validate_pluggable_transports_config() == 0) { + has_validated_pt = 1; + } + } + + /** 12. write the heartbeat message */ + if (options->HeartbeatPeriod && + time_to_next_heartbeat < now) { + log_heartbeat(now); + time_to_next_heartbeat = now+options->HeartbeatPeriod; + } } /** Timer: used to invoke second_elapsed_callback() once per second. */ @@ -1277,7 +1528,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) size_t bytes_written; size_t bytes_read; int seconds_elapsed; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); (void)timer; (void)arg; @@ -1288,21 +1539,30 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) update_approx_time(now); /* the second has rolled over. check more stuff. */ - bytes_written = stats_prev_global_write_bucket - global_write_bucket; - bytes_read = stats_prev_global_read_bucket - global_read_bucket; seconds_elapsed = current_second ? (int)(now - current_second) : 0; - stats_n_bytes_read += bytes_read; - stats_n_bytes_written += bytes_written; - if (accounting_is_enabled(options) && seconds_elapsed >= 0) - accounting_add_bytes(bytes_read, bytes_written, seconds_elapsed); +#ifdef USE_BUFFEREVENTS + { + uint64_t cur_read,cur_written; + connection_get_rate_limit_totals(&cur_read, &cur_written); + bytes_written = (size_t)(cur_written - stats_prev_n_written); + bytes_read = (size_t)(cur_read - stats_prev_n_read); + stats_n_bytes_read += bytes_read; + stats_n_bytes_written += bytes_written; + if (accounting_is_enabled(options) && seconds_elapsed >= 0) + accounting_add_bytes(bytes_read, bytes_written, seconds_elapsed); + stats_prev_n_written = cur_written; + stats_prev_n_read = cur_read; + } +#else + bytes_read = (size_t)(stats_n_bytes_read - stats_prev_n_read); + bytes_written = (size_t)(stats_n_bytes_written - stats_prev_n_written); + stats_prev_n_read = stats_n_bytes_read; + stats_prev_n_written = stats_n_bytes_written; +#endif + control_event_bandwidth_used((uint32_t)bytes_read,(uint32_t)bytes_written); control_event_stream_bandwidth_used(); - if (seconds_elapsed > 0) - connection_bucket_refill(seconds_elapsed, now); - stats_prev_global_read_bucket = global_read_bucket; - stats_prev_global_write_bucket = global_write_bucket; - if (server_mode(options) && !we_are_hibernating() && seconds_elapsed > 0 && @@ -1311,7 +1571,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) (stats_n_seconds_working+seconds_elapsed) / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { /* every 20 minutes, check and complain if necessary */ - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (me && !check_whether_orport_reachable()) { log_warn(LD_CONFIG,"Your server (%s:%d) has not managed to confirm that " "its ORPort is reachable. Please check your firewalls, ports, " @@ -1350,6 +1610,57 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) current_second = now; /* remember which second it is, for next time */ } +#ifndef USE_BUFFEREVENTS +/** Timer: used to invoke refill_callback(). */ +static periodic_timer_t *refill_timer = NULL; + +/** Libevent callback: invoked periodically to refill token buckets + * and count r/w bytes. It is only used when bufferevents are disabled. */ +static void +refill_callback(periodic_timer_t *timer, void *arg) +{ + static struct timeval current_millisecond; + struct timeval now; + + size_t bytes_written; + size_t bytes_read; + int milliseconds_elapsed = 0; + int seconds_rolled_over = 0; + + const or_options_t *options = get_options(); + + (void)timer; + (void)arg; + + tor_gettimeofday(&now); + + /* If this is our first time, no time has passed. */ + if (current_millisecond.tv_sec) { + long mdiff = tv_mdiff(¤t_millisecond, &now); + if (mdiff > INT_MAX) + mdiff = INT_MAX; + milliseconds_elapsed = (int)mdiff; + seconds_rolled_over = (int)(now.tv_sec - current_millisecond.tv_sec); + } + + bytes_written = stats_prev_global_write_bucket - global_write_bucket; + bytes_read = stats_prev_global_read_bucket - global_read_bucket; + + stats_n_bytes_read += bytes_read; + stats_n_bytes_written += bytes_written; + if (accounting_is_enabled(options) && milliseconds_elapsed >= 0) + accounting_add_bytes(bytes_read, bytes_written, seconds_rolled_over); + + if (milliseconds_elapsed > 0) + connection_bucket_refill(milliseconds_elapsed, now.tv_sec); + + stats_prev_global_read_bucket = global_read_bucket; + stats_prev_global_write_bucket = global_write_bucket; + + current_millisecond = now; /* remember what time it is, for next time */ +} +#endif + #ifndef MS_WINDOWS /** Called when a possibly ignorable libevent error occurs; ensures that we * don't get into an infinite loop by ignoring too many errors from @@ -1410,7 +1721,7 @@ dns_servers_relaunch_checks(void) static int do_hup(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); #ifdef USE_DMALLOC dmalloc_log_stats(); @@ -1502,8 +1813,10 @@ do_main_loop(void) /* Set up our buckets */ connection_bucket_init(); +#ifndef USE_BUFFEREVENTS stats_prev_global_read_bucket = global_read_bucket; stats_prev_global_write_bucket = global_write_bucket; +#endif /* initialize the bootstrap status events to know we're starting up */ control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); @@ -1546,6 +1859,22 @@ do_main_loop(void) tor_assert(second_timer); } +#ifndef USE_BUFFEREVENTS + if (!refill_timer) { + struct timeval refill_interval; + int msecs = get_options()->TokenBucketRefillInterval; + + refill_interval.tv_sec = msecs/1000; + refill_interval.tv_usec = (msecs%1000)*1000; + + refill_timer = periodic_timer_new(tor_libevent_get_base(), + &refill_interval, + refill_callback, + NULL); + tor_assert(refill_timer); + } +#endif + for (;;) { if (nt_service_is_stopping()) return 0; @@ -1634,11 +1963,13 @@ process_signal(uintptr_t sig) case SIGUSR1: /* prefer to log it at INFO, but make sure we always see it */ dumpstats(get_min_log_level()<LOG_INFO ? get_min_log_level() : LOG_INFO); + control_event_signal(sig); break; case SIGUSR2: switch_logs_debug(); log_debug(LD_GENERAL,"Caught USR2, going to loglevel debug. " "Send HUP to change back."); + control_event_signal(sig); break; case SIGHUP: if (do_hup() < 0) { @@ -1646,6 +1977,7 @@ process_signal(uintptr_t sig) tor_cleanup(); exit(1); } + control_event_signal(sig); break; #ifdef SIGCHLD case SIGCHLD: @@ -1667,10 +1999,18 @@ process_signal(uintptr_t sig) } case SIGCLEARDNSCACHE: addressmap_clear_transient(); + control_event_signal(sig); break; } } +/** Returns Tor's uptime. */ +long +get_uptime(void) +{ + return stats_n_seconds_working; +} + extern uint64_t rephist_total_alloc; extern uint32_t rephist_total_num; @@ -1717,13 +2057,13 @@ dumpstats(int severity) log(severity,LD_GENERAL, "Conn %d: %d bytes waiting on inbuf (len %d, last read %d secs ago)", i, - (int)buf_datalen(conn->inbuf), + (int)connection_get_inbuf_len(conn), (int)buf_allocation(conn->inbuf), (int)(now - conn->timestamp_lastread)); log(severity,LD_GENERAL, "Conn %d: %d bytes waiting on outbuf " "(len %d, last written %d secs ago)",i, - (int)buf_datalen(conn->outbuf), + (int)connection_get_outbuf_len(conn), (int)buf_allocation(conn->outbuf), (int)(now - conn->timestamp_lastwritten)); if (conn->type == CONN_TYPE_OR) { @@ -1895,9 +2235,15 @@ tor_init(int argc, char *argv[]) default: add_temp_log(LOG_NOTICE); } + quiet_level = quiet; - log(LOG_NOTICE, LD_GENERAL, "Tor v%s. This is experimental software. " - "Do not rely on it for strong anonymity. (Running on %s)",get_version(), + log(LOG_NOTICE, LD_GENERAL, "Tor v%s%s. This is experimental software. " + "Do not rely on it for strong anonymity. (Running on %s)", get_version(), +#ifdef USE_BUFFEREVENTS + " (with bufferevents)", +#else + "", +#endif get_uname()); if (network_init()<0) { @@ -1938,7 +2284,7 @@ static tor_lockfile_t *lockfile = NULL; * return -1 if we can't get the lockfile. Return 0 on success. */ int -try_locking(or_options_t *options, int err_if_locked) +try_locking(const or_options_t *options, int err_if_locked) { if (lockfile) return 0; @@ -2017,9 +2363,11 @@ tor_free_all(int postfork) clear_pending_onions(); circuit_free_all(); entry_guards_free_all(); + pt_free_all(); connection_free_all(); buf_shrink_freelists(1); memarea_clear_freelist(); + nodelist_free_all(); microdesc_free_all(); if (!postfork) { config_free_all(); @@ -2051,7 +2399,7 @@ tor_free_all(int postfork) void tor_cleanup(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->command == CMD_RUN_TOR) { time_t now = time(NULL); /* Remove our pid file. We don't care if there was an error when we diff --git a/src/or/main.h b/src/or/main.h index 0551f7aaf9..c8903642de 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -14,7 +14,9 @@ extern int can_complete_circuit; -int connection_add(connection_t *conn); +int connection_add_impl(connection_t *conn, int is_connecting); +#define connection_add(conn) connection_add_impl((conn), 0) +#define connection_add_connecting(conn) connection_add_impl((conn), 1) int connection_remove(connection_t *conn); void connection_unregister_events(connection_t *conn); int connection_in_array(connection_t *conn); @@ -22,10 +24,13 @@ void add_connection_to_closeable_list(connection_t *conn); int connection_is_on_closeable_list(connection_t *conn); smartlist_t *get_connection_array(void); +uint64_t get_bytes_read(void); +uint64_t get_bytes_written(void); /** Bitmask for events that we can turn on and off with * connection_watch_events. */ typedef enum watchable_events { + /* Yes, it is intentional that these match Libevent's EV_READ and EV_WRITE */ READ_EVENT=0x02, /**< We want to know when a connection is readable */ WRITE_EVENT=0x04 /**< We want to know when a connection is writable */ } watchable_events_t; @@ -46,10 +51,13 @@ void directory_info_has_arrived(time_t now, int from_cache); void ip_address_changed(int at_interface); void dns_servers_relaunch_checks(void); +long get_uptime(void); +unsigned get_signewnym_epoch(void); + void handle_signals(int is_parent); void process_signal(uintptr_t sig); -int try_locking(or_options_t *options, int err_if_locked); +int try_locking(const or_options_t *options, int err_if_locked); int have_lockfile(void); void release_lockfile(void); diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 7c67d51448..92f5c03585 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -3,7 +3,14 @@ #include "or.h" #include "config.h" +#include "directory.h" +#include "dirserv.h" #include "microdesc.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerlist.h" #include "routerparse.h" /** A data structure to hold a bunch of cached microdescriptors. There are @@ -121,15 +128,19 @@ get_microdesc_cache(void) * 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. Return a list of the added - * microdescriptors. */ + * <b>SAVED_NOWHERE</b>, do not allow annotations. If listed_at is positive, + * set the last_listed field of every microdesc to listed_at. If + * requested_digests is non-null, then it contains a list of digests we mean + * to allow, so we should reject any non-requested microdesc with a different + * digest, and alter the list to contain only the digests of those microdescs + * we didn't find. + * Return a newly allocated list of the added microdescriptors, or NULL */ smartlist_t * microdescs_add_to_cache(microdesc_cache_t *cache, const char *s, const char *eos, saved_location_t where, - int no_save) + int no_save, time_t listed_at, + smartlist_t *requested_digests256) { - /*XXXX need an argument that sets last_listed as appropriate. */ - smartlist_t *descriptors, *added; const int allow_annotations = (where != SAVED_NOWHERE); const int copy_body = (where != SAVED_IN_CACHE); @@ -137,6 +148,33 @@ microdescs_add_to_cache(microdesc_cache_t *cache, descriptors = microdescs_parse_from_string(s, eos, allow_annotations, copy_body); + if (listed_at > 0) { + SMARTLIST_FOREACH(descriptors, microdesc_t *, md, + md->last_listed = listed_at); + } + if (requested_digests256) { + digestmap_t *requested; /* XXXX actuqlly we should just use a + digest256map */ + requested = digestmap_new(); + SMARTLIST_FOREACH(requested_digests256, const char *, cp, + digestmap_set(requested, cp, (void*)1)); + SMARTLIST_FOREACH_BEGIN(descriptors, microdesc_t *, md) { + 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"); + microdesc_free(md); + SMARTLIST_DEL_CURRENT(descriptors, md); + } + } SMARTLIST_FOREACH_END(md); + SMARTLIST_FOREACH_BEGIN(requested_digests256, char *, cp) { + if (digestmap_get(requested, cp) == (void*)2) { + tor_free(cp); + SMARTLIST_DEL_CURRENT(requested_digests256, cp); + } + } SMARTLIST_FOREACH_END(cp); + digestmap_free(requested, NULL); + } added = microdescs_add_list_to_cache(cache, descriptors, where, no_save); smartlist_free(descriptors); @@ -144,7 +182,7 @@ microdescs_add_to_cache(microdesc_cache_t *cache, } /* As microdescs_add_to_cache, but takes a list of micrdescriptors instead of - * a string to encode. Frees any members of <b>descriptors</b> that it does + * a string to decode. Frees any members of <b>descriptors</b> that it does * not add. */ smartlist_t * microdescs_add_list_to_cache(microdesc_cache_t *cache, @@ -200,6 +238,7 @@ microdescs_add_list_to_cache(microdesc_cache_t *cache, md->no_save = no_save; HT_INSERT(microdesc_map, &cache->map, md); + md->held_in_map = 1; smartlist_add(added, md); ++cache->n_seen; cache->total_len_seen += md->bodylen; @@ -208,6 +247,15 @@ microdescs_add_list_to_cache(microdesc_cache_t *cache, if (f) finish_writing_to_file(open_file); /*XXX Check me.*/ + { + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && ns->flavor == FLAV_MICRODESC) + SMARTLIST_FOREACH(added, microdesc_t *, md, nodelist_add_microdesc(md)); + } + + if (smartlist_len(added)) + router_dir_info_changed(); + return added; } @@ -219,6 +267,7 @@ microdesc_cache_clear(microdesc_cache_t *cache) for (entry = HT_START(microdesc_map, &cache->map); entry; entry = next) { microdesc_t *md = *entry; next = HT_NEXT_RMV(microdesc_map, &cache->map, entry); + md->held_in_map = 0; microdesc_free(md); } HT_CLEAR(microdesc_map, &cache->map); @@ -247,7 +296,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) mm = cache->cache_content = tor_mmap_file(cache->cache_fname); if (mm) { added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size, - SAVED_IN_CACHE, 0); + SAVED_IN_CACHE, 0, -1, NULL); if (added) { total += smartlist_len(added); smartlist_free(added); @@ -260,7 +309,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) cache->journal_len = (size_t) st.st_size; added = microdescs_add_to_cache(cache, journal_content, journal_content+st.st_size, - SAVED_IN_JOURNAL, 0); + SAVED_IN_JOURNAL, 0, -1, NULL); if (added) { total += smartlist_len(added); smartlist_free(added); @@ -293,9 +342,11 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) size_t bytes_dropped = 0; time_t now = time(NULL); - (void) force; - /* In 0.2.2, we let this proceed unconditionally: only authorities have - * microdesc caches. */ + /* If we don't know a live consensus, don't believe last_listed values: we + * might be starting up after being down for a while. */ + if (! force && + ! networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC)) + return; if (cutoff <= 0) cutoff = now - TOLERATE_MICRODESC_AGE; @@ -305,6 +356,7 @@ microdesc_cache_clean(microdesc_cache_t *cache, time_t cutoff, int force) ++dropped; victim = *mdp; mdp = HT_NEXT_RMV(microdesc_map, &cache->map, mdp); + victim->held_in_map = 0; bytes_dropped += victim->bodylen; microdesc_free(victim); } else { @@ -390,6 +442,7 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) /* log? return -1? die? coredump the universe? */ continue; } + tor_assert(((size_t)size) == annotation_len + md->bodylen); md->off = off + annotation_len; off += size; if (md->saved_location != SAVED_IN_CACHE) { @@ -415,7 +468,21 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) SMARTLIST_FOREACH_BEGIN(wrote, microdesc_t *, md) { tor_assert(md->saved_location == SAVED_IN_CACHE); md->body = (char*)cache->cache_content->data + md->off; - tor_assert(fast_memeq(md->body, "onion-key", 9)); + if (PREDICT_UNLIKELY( + md->bodylen < 9 || fast_memneq(md->body, "onion-key", 9) != 0)) { + /* XXXX023 once bug 2022 is solved, we can kill this block and turn it + * into just the tor_assert(!memcmp) */ + off_t avail = cache->cache_content->size - md->off; + char *bad_str; + tor_assert(avail >= 0); + bad_str = tor_strndup(md->body, MIN(128, (size_t)avail)); + log_err(LD_BUG, "After rebuilding microdesc cache, offsets seem wrong. " + " At offset %d, I expected to find a microdescriptor starting " + " with \"onion-key\". Instead I got %s.", + (int)md->off, escaped(bad_str)); + tor_free(bad_str); + tor_assert(fast_memeq(md->body, "onion-key", 9)); + } } SMARTLIST_FOREACH_END(md); smartlist_free(wrote); @@ -432,6 +499,28 @@ microdesc_cache_rebuild(microdesc_cache_t *cache, int force) return 0; } +/** Make sure that the reference count of every microdescriptor in cache is + * accurate. */ +void +microdesc_check_counts(void) +{ + microdesc_t **mdp; + if (!the_microdesc_cache) + return; + + HT_FOREACH(mdp, microdesc_map, &the_microdesc_cache->map) { + microdesc_t *md = *mdp; + unsigned int found=0; + const smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, node_t *, node, { + if (node->md == md) { + ++found; + } + }); + tor_assert(found == md->held_by_nodes); + } +} + /** Deallocate a single microdescriptor. Note: the microdescriptor MUST have * previously been removed from the cache if it had ever been inserted. */ void @@ -439,7 +528,43 @@ microdesc_free(microdesc_t *md) { if (!md) return; - /* Must be removed from hash table! */ + + /* Make sure that the microdesc was really removed from the appropriate data + structures. */ + if (md->held_in_map) { + microdesc_cache_t *cache = get_microdesc_cache(); + microdesc_t *md2 = HT_FIND(microdesc_map, &cache->map, md); + if (md2 == md) { + log_warn(LD_BUG, "microdesc_free() called, but md was still in " + "microdesc_map"); + HT_REMOVE(microdesc_map, &cache->map, md); + } else { + log_warn(LD_BUG, "microdesc_free() called with held_in_map set, but " + "microdesc was not in the map."); + } + tor_fragile_assert(); + } + if (md->held_by_nodes) { + int found=0; + const smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, node_t *, node, { + if (node->md == md) { + ++found; + node->md = NULL; + } + }); + if (found) { + log_warn(LD_BUG, "microdesc_free() called, but md was still referenced " + "%d node(s); held_by_nodes == %u", found, md->held_by_nodes); + } else { + log_warn(LD_BUG, "microdesc_free() called with held_by_nodes set to %u, " + "but md was not referenced by any nodes", md->held_by_nodes); + } + tor_fragile_assert(); + } + //tor_assert(md->held_in_map == 0); + //tor_assert(md->held_by_nodes == 0); + if (md->onion_pkey) crypto_free_pk_env(md->onion_pkey); if (md->body && md->saved_location != SAVED_IN_CACHE) @@ -449,7 +574,7 @@ microdesc_free(microdesc_t *md) SMARTLIST_FOREACH(md->family, char *, cp, tor_free(cp)); smartlist_free(md->family); } - tor_free(md->exitsummary); + short_policy_free(md->exit_policy); tor_free(md); } @@ -491,3 +616,147 @@ microdesc_average_size(microdesc_cache_t *cache) return (size_t)(cache->total_len_seen / cache->n_seen); } +/** Return a smartlist of all the sha256 digest of the microdescriptors that + * are listed in <b>ns</b> but not present in <b>cache</b>. Returns pointers + * to internals of <b>ns</b>; you should not free the members of the resulting + * smartlist. Omit all microdescriptors whose digest appear in <b>skip</b>. */ +smartlist_t * +microdesc_list_missing_digest256(networkstatus_t *ns, microdesc_cache_t *cache, + int downloadable_only, digestmap_t *skip) +{ + smartlist_t *result = smartlist_create(); + time_t now = time(NULL); + tor_assert(ns->flavor == FLAV_MICRODESC); + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + if (microdesc_cache_lookup_by_digest256(cache, rs->descriptor_digest)) + continue; + if (downloadable_only && + !download_status_is_ready(&rs->dl_status, now, + MAX_MICRODESC_DOWNLOAD_FAILURES)) + continue; + if (skip && digestmap_get(skip, rs->descriptor_digest)) + continue; + if (tor_mem_is_zero(rs->descriptor_digest, DIGEST256_LEN)) + continue; /* This indicates a bug somewhere XXXX023*/ + /* XXXX Also skip if we're a noncache and wouldn't use this router. + * XXXX NM Microdesc + */ + smartlist_add(result, rs->descriptor_digest); + } SMARTLIST_FOREACH_END(rs); + return result; +} + +/** Launch download requests for mircodescriptors as appropriate. + * + * Specifically, we should launch download requests if we are configured to + * download mirodescriptors, and there are some microdescriptors listed the + * current microdesc consensus that we don't have, and either we never asked + * for them, or we failed to download them but we're willing to retry. + */ +void +update_microdesc_downloads(time_t now) +{ + const or_options_t *options = get_options(); + networkstatus_t *consensus; + smartlist_t *missing; + digestmap_t *pending; + + if (should_delay_dir_fetches(options)) + return; + if (directory_too_idle_to_fetch_descriptors(options, now)) + return; + + consensus = networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC); + if (!consensus) + return; + + if (!we_fetch_microdescriptors(options)) + return; + + pending = digestmap_new(); + list_pending_microdesc_downloads(pending); + + missing = microdesc_list_missing_digest256(consensus, + get_microdesc_cache(), + 1, + pending); + digestmap_free(pending, NULL); + + launch_descriptor_downloads(DIR_PURPOSE_FETCH_MICRODESC, + missing, NULL, now); + + smartlist_free(missing); +} + +/** For every microdescriptor listed in the current microdecriptor consensus, + * update its last_listed field to be at least as recent as the publication + * time of the current microdescriptor consensus. + */ +void +update_microdescs_from_networkstatus(time_t now) +{ + microdesc_cache_t *cache = get_microdesc_cache(); + microdesc_t *md; + networkstatus_t *ns = + networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC); + + if (! ns) + return; + + tor_assert(ns->flavor == FLAV_MICRODESC); + + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + md = microdesc_cache_lookup_by_digest256(cache, rs->descriptor_digest); + if (md && ns->valid_after > md->last_listed) + md->last_listed = ns->valid_after; + } SMARTLIST_FOREACH_END(rs); +} + +/** Return true iff we should prefer to use microdescriptors rather than + * routerdescs for building circuits. */ +int +we_use_microdescriptors_for_circuits(const or_options_t *options) +{ + int ret = options->UseMicrodescriptors; + if (ret == -1) { + /* UseMicrodescriptors is "auto"; we need to decide: */ + /* So we decide that we'll use microdescriptors iff we are not a server, + * and we're not autofetching everything. */ + ret = !server_mode(options) && !options->FetchUselessDescriptors; + } + return ret; +} + +/** Return true iff we should try to download microdescriptors at all. */ +int +we_fetch_microdescriptors(const or_options_t *options) +{ + if (directory_caches_dir_info(options)) + return 1; + if (options->FetchUselessDescriptors) + return 1; + return we_use_microdescriptors_for_circuits(options); +} + +/** Return true iff we should try to download router descriptors at all. */ +int +we_fetch_router_descriptors(const or_options_t *options) +{ + if (directory_caches_dir_info(options)) + return 1; + if (options->FetchUselessDescriptors) + return 1; + return ! we_use_microdescriptors_for_circuits(options); +} + +/** Return the consensus flavor we actually want to use to build circuits. */ +int +usable_consensus_flavor(void) +{ + if (we_use_microdescriptors_for_circuits(get_options())) { + return FLAV_MICRODESC; + } else { + return FLAV_NS; + } +} + diff --git a/src/or/microdesc.h b/src/or/microdesc.h index 77ce8536bc..4564132810 100644 --- a/src/or/microdesc.h +++ b/src/or/microdesc.h @@ -14,9 +14,12 @@ microdesc_cache_t *get_microdesc_cache(void); +void microdesc_check_counts(void); + smartlist_t *microdescs_add_to_cache(microdesc_cache_t *cache, const char *s, const char *eos, saved_location_t where, - int no_save); + int no_save, time_t listed_at, + smartlist_t *requested_digests256); smartlist_t *microdescs_add_list_to_cache(microdesc_cache_t *cache, smartlist_t *descriptors, saved_location_t where, int no_save); @@ -31,8 +34,21 @@ microdesc_t *microdesc_cache_lookup_by_digest256(microdesc_cache_t *cache, size_t microdesc_average_size(microdesc_cache_t *cache); +smartlist_t *microdesc_list_missing_digest256(networkstatus_t *ns, + microdesc_cache_t *cache, + int downloadable_only, + digestmap_t *skip); + void microdesc_free(microdesc_t *md); void microdesc_free_all(void); +void update_microdesc_downloads(time_t now); +void update_microdescs_from_networkstatus(time_t now); + +int usable_consensus_flavor(void); +int we_fetch_microdescriptors(const or_options_t *options); +int we_fetch_router_descriptors(const or_options_t *options); +int we_use_microdescriptors_for_circuits(const or_options_t *options); + #endif diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index b0ef74b02e..7cd9d02c3f 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -20,7 +20,9 @@ #include "dirserv.h" #include "dirvote.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "relay.h" #include "router.h" #include "routerlist.h" @@ -44,8 +46,21 @@ static strmap_t *named_server_map = NULL; * as unnamed for some server in the consensus. */ static strmap_t *unnamed_server_map = NULL; -/** Most recently received and validated v3 consensus network status. */ -static networkstatus_t *current_consensus = NULL; +/** Most recently received and validated v3 consensus network status, + * of whichever type we are using for our own circuits. This will be the same + * as one of current_ns_consensus or current_md_consensus. + */ +#define current_consensus \ + (we_use_microdescriptors_for_circuits(get_options()) ? \ + current_md_consensus : current_ns_consensus) + +/** Most recently received and validated v3 "ns"-flavored consensus network + * status. */ +static networkstatus_t *current_ns_consensus = NULL; + +/** Most recently received and validated v3 "microdec"-flavored consensus + * network status. */ +static networkstatus_t *current_md_consensus = NULL; /** A v3 consensus networkstatus that we've received, but which we don't * have enough certificates to be happy about. */ @@ -94,9 +109,8 @@ void networkstatus_reset_warnings(void) { if (current_consensus) { - SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, rs, - rs->name_lookup_warned = 0); + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + node->name_lookup_warned = 0); } have_warned_about_old_version = 0; @@ -199,7 +213,7 @@ router_reload_consensus_networkstatus(void) char *filename; char *s; struct stat st; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS; int flav; @@ -271,6 +285,7 @@ router_reload_consensus_networkstatus(void) update_certificate_downloads(time(NULL)); routers_update_all_from_networkstatus(time(NULL), 3); + update_microdescs_from_networkstatus(time(NULL)); return 0; } @@ -469,7 +484,7 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus, int n_bad = 0; int n_unknown = 0; int n_no_signature = 0; - int n_v3_authorities = get_n_authorities(V3_AUTHORITY); + int n_v3_authorities = get_n_authorities(V3_DIRINFO); int n_required = n_v3_authorities/2 + 1; smartlist_t *need_certs_from = smartlist_create(); smartlist_t *unrecognized = smartlist_create(); @@ -540,7 +555,7 @@ networkstatus_check_consensus_signature(networkstatus_t *consensus, SMARTLIST_FOREACH(router_get_trusted_dir_servers(), trusted_dir_server_t *, ds, { - if ((ds->type & V3_AUTHORITY) && + if ((ds->type & V3_DIRINFO) && !networkstatus_get_voter_by_id(consensus, ds->v3_identity_digest)) smartlist_add(missing_authorities, ds); }); @@ -723,7 +738,7 @@ router_set_networkstatus_v2(const char *s, time_t arrived_at, base16_encode(fp, HEX_DIGEST_LEN+1, ns->identity_digest, DIGEST_LEN); if (!(trusted_dir = router_get_trusteddirserver_by_digest(ns->identity_digest)) || - !(trusted_dir->type & V2_AUTHORITY)) { + !(trusted_dir->type & V2_DIRINFO)) { log_info(LD_DIR, "Network status was signed, but not by an authoritative " "directory we recognize."); source_desc = fp; @@ -927,10 +942,9 @@ compare_digest_to_routerstatus_entry(const void *_key, const void **_member) return tor_memcmp(key, rs->identity_digest, DIGEST_LEN); } -/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or - * NULL if none was found. */ +/** As networkstatus_v2_find_entry, but do not return a const pointer */ routerstatus_t * -networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) +networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, const char *digest) { return smartlist_bsearch(ns->entries, digest, compare_digest_to_routerstatus_entry); @@ -938,14 +952,29 @@ networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) /** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or * NULL if none was found. */ +const routerstatus_t * +networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest) +{ + return networkstatus_v2_find_mutable_entry(ns, digest); +} + +/** As networkstatus_find_entry, but do not return a const pointer */ routerstatus_t * -networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest) +networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest) { return smartlist_bsearch(ns->routerstatus_list, digest, compare_digest_to_routerstatus_entry); } -/*XXXX make this static once functions are moved into this file. */ +/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or + * NULL if none was found. */ +const routerstatus_t * +networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest) +{ + return networkstatus_vote_find_mutable_entry(ns, digest); +} + +/*XXXX MOVE make this static once functions are moved into this file. */ /** Search the routerstatuses in <b>ns</b> for one whose identity digest is * <b>digest</b>. Return value and set *<b>found_out</b> as for * smartlist_bsearch_idx(). */ @@ -967,22 +996,37 @@ networkstatus_get_v2_list(void) return networkstatus_v2_list; } -/** Return the consensus view of the status of the router whose current - * <i>descriptor</i> digest is <b>digest</b>, or NULL if no such router is - * known. */ +/* As router_get_consensus_status_by_descriptor_digest, but does not return + * a const pointer */ routerstatus_t * -router_get_consensus_status_by_descriptor_digest(const char *digest) +router_get_mutable_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest) { - if (!current_consensus) return NULL; - if (!current_consensus->desc_digest_map) { - digestmap_t * m = current_consensus->desc_digest_map = digestmap_new(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, + if (!consensus) + consensus = current_consensus; + if (!consensus) + return NULL; + if (!consensus->desc_digest_map) { + digestmap_t *m = consensus->desc_digest_map = digestmap_new(); + SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs, { digestmap_set(m, rs->descriptor_digest, rs); }); } - return digestmap_get(current_consensus->desc_digest_map, digest); + return digestmap_get(consensus->desc_digest_map, digest); +} + +/** Return the consensus view of the status of the router whose current + * <i>descriptor</i> digest in <b>consensus</b> is <b>digest</b>, or NULL if + * no such router is known. */ +const routerstatus_t * +router_get_consensus_status_by_descriptor_digest(networkstatus_t *consensus, + const char *digest) +{ + return router_get_mutable_consensus_status_by_descriptor_digest( + consensus, digest); } /** Given the digest of a router descriptor, return its current download @@ -991,7 +1035,10 @@ download_status_t * router_get_dl_status_by_descriptor_digest(const char *d) { routerstatus_t *rs; - if ((rs = router_get_consensus_status_by_descriptor_digest(d))) + if (!current_ns_consensus) + return NULL; + if ((rs = router_get_mutable_consensus_status_by_descriptor_digest( + current_ns_consensus, d))) return &rs->dl_status; if (v2_download_status_map) return digestmap_get(v2_download_status_map, d); @@ -999,10 +1046,9 @@ router_get_dl_status_by_descriptor_digest(const char *d) return NULL; } -/** Return the consensus view of the status of the router whose identity - * digest is <b>digest</b>, or NULL if we don't know about any such router. */ +/** As router_get_consensus_status_by_id, but do not return a const pointer */ routerstatus_t * -router_get_consensus_status_by_id(const char *digest) +router_get_mutable_consensus_status_by_id(const char *digest) { if (!current_consensus) return NULL; @@ -1010,100 +1056,27 @@ router_get_consensus_status_by_id(const char *digest) compare_digest_to_routerstatus_entry); } +/** Return the consensus view of the status of the router whose identity + * digest is <b>digest</b>, or NULL if we don't know about any such router. */ +const routerstatus_t * +router_get_consensus_status_by_id(const char *digest) +{ + return router_get_mutable_consensus_status_by_id(digest); +} + /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return * the corresponding routerstatus_t, or NULL if none exists. Warn the * user if <b>warn_if_unnamed</b> is set, and they have specified a router by * nickname, but the Named flag isn't set for that router. */ -routerstatus_t * +const routerstatus_t * router_get_consensus_status_by_nickname(const char *nickname, int warn_if_unnamed) { - char digest[DIGEST_LEN]; - routerstatus_t *best=NULL; - smartlist_t *matches=NULL; - const char *named_id=NULL; - - if (!current_consensus || !nickname) - return NULL; - - /* Is this name really a hexadecimal identity digest? */ - if (nickname[0] == '$') { - if (base16_decode(digest, DIGEST_LEN, nickname+1, strlen(nickname+1))<0) - return NULL; - return networkstatus_vote_find_entry(current_consensus, digest); - } else if (strlen(nickname) == HEX_DIGEST_LEN && - (base16_decode(digest, DIGEST_LEN, nickname, strlen(nickname))==0)) { - return networkstatus_vote_find_entry(current_consensus, digest); - } - - /* Is there a server that is Named with this name? */ - if (named_server_map) - named_id = strmap_get_lc(named_server_map, nickname); - if (named_id) - return networkstatus_vote_find_entry(current_consensus, named_id); - - /* Okay; is this name listed as Unnamed? */ - if (unnamed_server_map && - strmap_get_lc(unnamed_server_map, nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed; it is not the " - "canonical name of any server we know.", escaped(nickname)); + const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); + if (node) + return node->rs; + else return NULL; - } - - /* This name is not canonical for any server; go through the list and - * see who it matches. */ - /*XXXX This is inefficient; optimize it if it matters. */ - matches = smartlist_create(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, lrs, - { - if (!strcasecmp(lrs->nickname, nickname)) { - if (lrs->is_named) { - tor_fragile_assert() /* This should never happen. */ - smartlist_free(matches); - return lrs; - } else { - if (lrs->is_unnamed) { - tor_fragile_assert(); /* nor should this. */ - smartlist_clear(matches); - best=NULL; - break; - } - smartlist_add(matches, lrs); - best = lrs; - } - } - }); - - if (smartlist_len(matches)>1 && warn_if_unnamed) { - int any_unwarned=0; - SMARTLIST_FOREACH(matches, routerstatus_t *, lrs, - { - if (! lrs->name_lookup_warned) { - lrs->name_lookup_warned=1; - any_unwarned=1; - } - }); - if (any_unwarned) { - 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.", nickname); - } - } else if (warn_if_unnamed && best && !best->name_lookup_warned) { - char fp[HEX_DIGEST_LEN+1]; - base16_encode(fp, sizeof(fp), - best->identity_digest, DIGEST_LEN); - log_warn(LD_CONFIG, - "When looking up a status, you specified a server \"%s\" by name, " - "but the directory authorities do not have any key registered for " - "this nickname -- so it could be used by any server, " - "not just the one you meant. " - "To make sure you get the same server in the future, refer to " - "it by key, as \"$%s\".", nickname, fp); - best->name_lookup_warned = 1; - } - smartlist_free(matches); - return best; } /** Return the identity digest that's mapped to officially by @@ -1159,7 +1132,7 @@ update_v2_networkstatus_cache_downloads(time_t now) { char resource[HEX_DIGEST_LEN+6]; /* fp/hexdigit.z\0 */ tor_addr_t addr; - if (!(ds->type & V2_AUTHORITY)) + if (!(ds->type & V2_DIRINFO)) continue; if (router_digest_is_me(ds->digest)) continue; @@ -1200,6 +1173,29 @@ update_v2_networkstatus_cache_downloads(time_t now) } } +/** DOCDOC */ +static int +we_want_to_fetch_flavor(const or_options_t *options, int flavor) +{ + if (flavor < 0 || flavor > N_CONSENSUS_FLAVORS) { + /* This flavor is crazy; we don't want it */ + /*XXXX handle unrecognized flavors later */ + return 0; + } + if (authdir_mode_v3(options) || directory_caches_dir_info(options)) { + /* We want to serve all flavors to others, regardless if we would use + * it ourselves. */ + return 1; + } + if (options->FetchUselessDescriptors) { + /* In order to get all descriptors, we need to fetch all consensuses. */ + return 1; + } + /* Otherwise, we want the flavor only if we want to use it to build + * circuits. */ + return flavor == usable_consensus_flavor(); +} + /** How many times will we try to fetch a consensus before we give up? */ #define CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES 8 /** How long will we hang onto a possibly live consensus for which we're @@ -1212,48 +1208,65 @@ static void update_consensus_networkstatus_downloads(time_t now) { int i; + const or_options_t *options = get_options(); + if (!networkstatus_get_live_consensus(now)) time_to_download_next_consensus = now; /* No live consensus? Get one now!*/ if (time_to_download_next_consensus > now) return; /* Wait until the current consensus is older. */ - /* XXXXNM Microdescs: may need to download more types. */ - if (!download_status_is_ready(&consensus_dl_status[FLAV_NS], now, - CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) - return; /* We failed downloading a consensus too recently. */ - if (connection_get_by_type_purpose(CONN_TYPE_DIR, - DIR_PURPOSE_FETCH_CONSENSUS)) - return; /* There's an in-progress download.*/ for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { - consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i]; + /* XXXX need some way to download unknown flavors if we are caching. */ + const char *resource; + consensus_waiting_for_certs_t *waiting; + + if (! we_want_to_fetch_flavor(options, i)) + continue; + + resource = networkstatus_get_flavor_name(i); + + if (!download_status_is_ready(&consensus_dl_status[i], now, + CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES)) + continue; /* We failed downloading a consensus too recently. */ + if (connection_dir_get_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, resource)) + continue; /* There's an in-progress download.*/ + + waiting = &consensus_waiting_for_certs[i]; if (waiting->consensus) { /* XXXX make sure this doesn't delay sane downloads. */ - if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) - return; /* We're still getting certs for this one. */ - else { + if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { + continue; /* We're still getting certs for this one. */ + } else { if (!waiting->dl_failed) { - download_status_failed(&consensus_dl_status[FLAV_NS], 0); + download_status_failed(&consensus_dl_status[i], 0); waiting->dl_failed=1; } } } - } - log_info(LD_DIR, "Launching networkstatus consensus download."); - directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, - ROUTER_PURPOSE_GENERAL, NULL, - PDS_RETRY_IF_NO_SERVERS); + log_info(LD_DIR, "Launching %s networkstatus consensus download.", + networkstatus_get_flavor_name(i)); + + directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, + ROUTER_PURPOSE_GENERAL, resource, + PDS_RETRY_IF_NO_SERVERS); + } } /** Called when an attempt to download a consensus fails: note that the * failure occurred, and possibly retry. */ void -networkstatus_consensus_download_failed(int status_code) +networkstatus_consensus_download_failed(int status_code, const char *flavname) { - /* XXXXNM Microdescs: may need to handle more types. */ - download_status_failed(&consensus_dl_status[FLAV_NS], status_code); - /* Retry immediately, if appropriate. */ - update_consensus_networkstatus_downloads(time(NULL)); + int flav = networkstatus_parse_flavor_name(flavname); + if (flav >= 0) { + tor_assert(flav < N_CONSENSUS_FLAVORS); + /* XXXX handle unrecognized flavors */ + download_status_failed(&consensus_dl_status[flav], status_code); + /* Retry immediately, if appropriate. */ + update_consensus_networkstatus_downloads(time(NULL)); + } } /** How long do we (as a cache) wait after a consensus becomes non-fresh @@ -1265,7 +1278,7 @@ networkstatus_consensus_download_failed(int status_code) void update_consensus_networkstatus_fetch_time(time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); networkstatus_t *c = networkstatus_get_live_consensus(now); if (c) { long dl_interval; @@ -1339,7 +1352,7 @@ update_consensus_networkstatus_fetch_time(time_t now) * fetches yet (e.g. we demand bridges and none are yet known). * Else return 0. */ int -should_delay_dir_fetches(or_options_t *options) +should_delay_dir_fetches(const or_options_t *options) { if (options->UseBridges && !any_bridge_descriptors_known()) { log_info(LD_DIR, "delaying dir fetches (no running bridges known)"); @@ -1353,7 +1366,7 @@ should_delay_dir_fetches(or_options_t *options) void update_networkstatus_downloads(time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (should_delay_dir_fetches(options)) return; if (authdir_mode_any_main(options) || options->FetchV2Networkstatus) @@ -1374,7 +1387,10 @@ update_certificate_downloads(time_t now) now); } - authority_certs_fetch_missing(current_consensus, now); + if (current_ns_consensus) + authority_certs_fetch_missing(current_ns_consensus, now); + if (current_md_consensus) + authority_certs_fetch_missing(current_md_consensus, now); } /** Return 1 if we have a consensus but we don't have enough certificates @@ -1382,7 +1398,7 @@ update_certificate_downloads(time_t now) int consensus_is_waiting_for_certs(void) { - return consensus_waiting_for_certs[USABLE_CONSENSUS_FLAVOR].consensus + return consensus_waiting_for_certs[usable_consensus_flavor()].consensus ? 1 : 0; } @@ -1406,6 +1422,18 @@ networkstatus_get_latest_consensus(void) return current_consensus; } +/** DOCDOC */ +networkstatus_t * +networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) +{ + if (f == FLAV_NS) + return current_ns_consensus; + else if (f == FLAV_MICRODESC) + return current_md_consensus; + else + tor_assert(0); +} + /** Return the most recent consensus that we have downloaded, or NULL if it is * no longer live. */ networkstatus_t * @@ -1425,13 +1453,15 @@ networkstatus_get_live_consensus(time_t now) /** As networkstatus_get_live_consensus(), but is way more tolerant of expired * consensuses. */ networkstatus_t * -networkstatus_get_reasonably_live_consensus(time_t now) +networkstatus_get_reasonably_live_consensus(time_t now, int flavor) { #define REASONABLY_LIVE_TIME (24*60*60) - if (current_consensus && - current_consensus->valid_after <= now && - now <= current_consensus->valid_until+REASONABLY_LIVE_TIME) - return current_consensus; + networkstatus_t *consensus = + networkstatus_get_latest_consensus_by_flavor(flavor); + if (consensus && + consensus->valid_after <= now && + now <= consensus->valid_until+REASONABLY_LIVE_TIME) + return consensus; else return NULL; } @@ -1452,7 +1482,7 @@ routerstatus_has_changed(const routerstatus_t *a, const routerstatus_t *b) a->is_exit != b->is_exit || a->is_stable != b->is_stable || a->is_fast != b->is_fast || - a->is_running != b->is_running || + a->is_flagged_running != b->is_flagged_running || a->is_named != b->is_named || a->is_unnamed != b->is_unnamed || a->is_valid != b->is_valid || @@ -1493,13 +1523,14 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, } changed = smartlist_create(); - SMARTLIST_FOREACH_JOIN(old_c->routerstatus_list, routerstatus_t *, rs_old, - new_c->routerstatus_list, routerstatus_t *, rs_new, - tor_memcmp(rs_old->identity_digest, - rs_new->identity_digest, DIGEST_LEN), - smartlist_add(changed, rs_new)) { + SMARTLIST_FOREACH_JOIN( + old_c->routerstatus_list, const routerstatus_t *, rs_old, + new_c->routerstatus_list, const routerstatus_t *, rs_new, + tor_memcmp(rs_old->identity_digest, + rs_new->identity_digest, DIGEST_LEN), + smartlist_add(changed, (void*) rs_new)) { if (routerstatus_has_changed(rs_old, rs_new)) - smartlist_add(changed, rs_new); + smartlist_add(changed, (void*)rs_new); } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new); control_event_networkstatus_changed(changed); @@ -1523,7 +1554,6 @@ networkstatus_copy_old_consensus_info(networkstatus_t *new_c, rs_new->identity_digest, DIGEST_LEN), STMT_NIL) { /* Okay, so we're looking at the same identity. */ - rs_new->name_lookup_warned = rs_old->name_lookup_warned; rs_new->last_dir_503_at = rs_old->last_dir_503_at; if (tor_memeq(rs_old->descriptor_digest, rs_new->descriptor_digest, @@ -1559,7 +1589,7 @@ networkstatus_set_current_consensus(const char *consensus, networkstatus_t *c=NULL; int r, result = -1; time_t now = time(NULL); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); char *unverified_fname = NULL, *consensus_fname = NULL; int flav = networkstatus_parse_flavor_name(flavor); const unsigned from_cache = flags & NSSET_FROM_CACHE; @@ -1597,7 +1627,7 @@ networkstatus_set_current_consensus(const char *consensus, flavor = networkstatus_get_flavor_name(flav); } - if (flav != USABLE_CONSENSUS_FLAVOR && + if (flav != usable_consensus_flavor() && !directory_caches_dir_info(options)) { /* This consensus is totally boring to us: we won't use it, and we won't * serve it. Drop it. */ @@ -1616,9 +1646,16 @@ networkstatus_set_current_consensus(const char *consensus, if (!strcmp(flavor, "ns")) { consensus_fname = get_datadir_fname("cached-consensus"); unverified_fname = get_datadir_fname("unverified-consensus"); - if (current_consensus) { - current_digests = ¤t_consensus->digests; - current_valid_after = current_consensus->valid_after; + if (current_ns_consensus) { + current_digests = ¤t_ns_consensus->digests; + current_valid_after = current_ns_consensus->valid_after; + } + } else if (!strcmp(flavor, "microdesc")) { + consensus_fname = get_datadir_fname("cached-microdesc-consensus"); + unverified_fname = get_datadir_fname("unverified-microdesc-consensus"); + if (current_md_consensus) { + current_digests = ¤t_md_consensus->digests; + current_valid_after = current_md_consensus->valid_after; } } else { cached_dir_t *cur; @@ -1695,24 +1732,36 @@ networkstatus_set_current_consensus(const char *consensus, } } - if (!from_cache && flav == USABLE_CONSENSUS_FLAVOR) + if (!from_cache && flav == usable_consensus_flavor()) control_event_client_status(LOG_NOTICE, "CONSENSUS_ARRIVED"); /* Are we missing any certificates at all? */ if (r != 1 && dl_certs) authority_certs_fetch_missing(c, now); - if (flav == USABLE_CONSENSUS_FLAVOR) { + if (flav == usable_consensus_flavor()) { notify_control_networkstatus_changed(current_consensus, c); - - if (current_consensus) { - networkstatus_copy_old_consensus_info(c, current_consensus); - networkstatus_vote_free(current_consensus); + } + if (flav == FLAV_NS) { + if (current_ns_consensus) { + networkstatus_copy_old_consensus_info(c, current_ns_consensus); + networkstatus_vote_free(current_ns_consensus); /* Defensive programming : we should set current_consensus very soon, * but we're about to call some stuff in the meantime, and leaving this * dangling pointer around has proven to be trouble. */ - current_consensus = NULL; + current_ns_consensus = NULL; + } + current_ns_consensus = c; + free_consensus = 0; /* avoid free */ + } else if (flav == FLAV_MICRODESC) { + if (current_md_consensus) { + networkstatus_copy_old_consensus_info(c, current_md_consensus); + networkstatus_vote_free(current_md_consensus); + /* more defensive programming */ + current_md_consensus = NULL; } + current_md_consensus = c; + free_consensus = 0; /* avoid free */ } waiting = &consensus_waiting_for_certs[flav]; @@ -1737,12 +1786,12 @@ networkstatus_set_current_consensus(const char *consensus, download_status_failed(&consensus_dl_status[flav], 0); } - if (flav == USABLE_CONSENSUS_FLAVOR) { - current_consensus = c; - free_consensus = 0; /* Prevent free. */ - - /* XXXXNM Microdescs: needs a non-ns variant. */ + if (flav == usable_consensus_flavor()) { + /* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/ update_consensus_networkstatus_fetch_time(now); + + nodelist_set_consensus(current_consensus); + dirvote_recalculate_timing(options, now); routerstatus_list_update_named_server_map(); cell_ewma_set_scale_factor(options, current_consensus); @@ -1769,11 +1818,11 @@ networkstatus_set_current_consensus(const char *consensus, * valid-after time, declare that our clock is skewed. */ #define EARLY_CONSENSUS_NOTICE_SKEW 60 - if (now < current_consensus->valid_after - EARLY_CONSENSUS_NOTICE_SKEW) { + if (now < c->valid_after - EARLY_CONSENSUS_NOTICE_SKEW) { char tbuf[ISO_TIME_LEN+1]; char dbuf[64]; - long delta = now - current_consensus->valid_after; - format_iso_time(tbuf, current_consensus->valid_after); + long delta = now - c->valid_after; + 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 " @@ -1824,7 +1873,8 @@ void routers_update_all_from_networkstatus(time_t now, int dir_version) { routerlist_t *rl = router_get_routerlist(); - networkstatus_t *consensus = networkstatus_get_live_consensus(now); + networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now, + FLAV_NS); if (networkstatus_v2_list_has_changed) download_status_map_update_from_v2_networkstatus(); @@ -1896,7 +1946,7 @@ download_status_map_update_from_v2_networkstatus(void) dl_status = digestmap_new(); SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) { - SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) { + SMARTLIST_FOREACH_BEGIN(ns->entries, const routerstatus_t *, rs) { const char *d = rs->descriptor_digest; download_status_t *s; if (digestmap_get(dl_status, d)) @@ -1924,7 +1974,8 @@ routerstatus_list_update_named_server_map(void) named_server_map = strmap_new(); strmap_free(unnamed_server_map, NULL); unnamed_server_map = strmap_new(); - SMARTLIST_FOREACH(current_consensus->routerstatus_list, routerstatus_t *, rs, + SMARTLIST_FOREACH(current_consensus->routerstatus_list, + const routerstatus_t *, rs, { if (rs->is_named) { strmap_set_lc(named_server_map, rs->nickname, @@ -1944,9 +1995,8 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, int reset_failures) { trusted_dir_server_t *ds; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); - int namingdir = authdir && options->NamingAuthoritativeDir; networkstatus_t *ns = current_consensus; if (!ns || !smartlist_len(ns->routerstatus_list)) return; @@ -1960,25 +2010,12 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, tor_memcmp(rs->identity_digest, router->cache_info.identity_digest, DIGEST_LEN), { - /* We have no routerstatus for this router. Clear flags and skip it. */ - if (!namingdir) - router->is_named = 0; - if (!authdir) { - if (router->purpose == ROUTER_PURPOSE_GENERAL) - router_clear_status_flags(router); - } }) { /* We have a routerstatus for this router. */ const char *digest = router->cache_info.identity_digest; ds = router_get_trusteddirserver_by_digest(digest); - if (!namingdir) { - if (rs->is_named && !strcasecmp(router->nickname, rs->nickname)) - router->is_named = 1; - else - router->is_named = 0; - } /* Is it the same descriptor, or only the same identity? */ if (tor_memeq(router->cache_info.signed_descriptor_digest, rs->descriptor_digest, DIGEST_LEN)) { @@ -1986,28 +2023,17 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, router->cache_info.last_listed_as_valid_until = ns->valid_until; } - if (!authdir) { - /* If we're not an authdir, believe others. */ - router->is_valid = rs->is_valid; - router->is_running = rs->is_running; - router->is_fast = rs->is_fast; - router->is_stable = rs->is_stable; - router->is_possible_guard = rs->is_possible_guard; - router->is_exit = rs->is_exit; - router->is_bad_directory = rs->is_bad_directory; - router->is_bad_exit = rs->is_bad_exit; - router->is_hs_dir = rs->is_hs_dir; - } else { + if (authdir) { /* If we _are_ an authority, we should check whether this router * is one that will cause us to need a reachability test. */ routerinfo_t *old_router = - router_get_by_digest(router->cache_info.identity_digest); + router_get_mutable_by_digest(router->cache_info.identity_digest); if (old_router != router) { router->needs_retest_if_added = dirserv_should_launch_reachability_test(router, old_router); } } - if (router->is_running && ds) { + if (rs->is_flagged_running && ds) { download_status_reset(&ds->v2_ns_dl_status); } if (reset_failures) { @@ -2016,10 +2042,9 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, } SMARTLIST_FOREACH_JOIN_END(rs, router); /* Now update last_listed_as_valid_until from v2 networkstatuses. */ - /* XXXX If this is slow, we need to rethink the code. */ SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, { time_t live_until = ns->published_on + V2_NETWORKSTATUS_ROUTER_LIFETIME; - SMARTLIST_FOREACH_JOIN(ns->entries, routerstatus_t *, rs, + SMARTLIST_FOREACH_JOIN(ns->entries, const routerstatus_t *, rs, routers, routerinfo_t *, ri, tor_memcmp(rs->identity_digest, ri->cache_info.identity_digest, DIGEST_LEN), @@ -2040,7 +2065,7 @@ routers_update_status_from_consensus_networkstatus(smartlist_t *routers, void signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) { - networkstatus_t *ns = current_consensus; + networkstatus_t *ns = current_ns_consensus; if (!ns) return; @@ -2048,11 +2073,11 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) char dummy[DIGEST_LEN]; /* instantiates the digest map. */ memset(dummy, 0, sizeof(dummy)); - router_get_consensus_status_by_descriptor_digest(dummy); + router_get_consensus_status_by_descriptor_digest(ns, dummy); } SMARTLIST_FOREACH(descs, signed_descriptor_t *, d, { - routerstatus_t *rs = digestmap_get(ns->desc_digest_map, + const routerstatus_t *rs = digestmap_get(ns->desc_digest_map, d->signed_descriptor_digest); if (rs) { if (ns->valid_until > d->last_listed_as_valid_until) @@ -2065,7 +2090,7 @@ signed_descs_update_status_from_consensus_networkstatus(smartlist_t *descs) * return the result in a newly allocated string. Used only by controller * interface (for now.) */ char * -networkstatus_getinfo_helper_single(routerstatus_t *rs) +networkstatus_getinfo_helper_single(const routerstatus_t *rs) { char buf[RS_ENTRY_LEN+1]; routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT); @@ -2098,6 +2123,9 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) statuses = smartlist_create(); SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, { + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (!node) + continue; if (ri->cache_info.published_on < cutoff) continue; if (ri->purpose != purpose) @@ -2105,7 +2133,7 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE) dirserv_set_router_is_running(ri, now); /* then generate and write out status lines for each of them */ - set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0, 0); + set_routerstatus_from_routerinfo(&rs, node, ri, now, 0, 0, 0, 0); smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs)); }); @@ -2120,7 +2148,7 @@ void networkstatus_dump_bridge_status_to_file(time_t now) { char *status = networkstatus_getinfo_by_purpose("bridge", now); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); size_t len = strlen(options->DataDirectory) + 32; char *fname = tor_malloc(len); tor_snprintf(fname, len, "%s"PATH_SEPARATOR"networkstatus-bridges", @@ -2174,7 +2202,7 @@ get_net_param_from_list(smartlist_t *net_params, const char *param_name, * <b>min_val</b> and at most <b>max_val</b> and raise/cap the parsed value * if necessary. */ int32_t -networkstatus_get_param(networkstatus_t *ns, const char *param_name, +networkstatus_get_param(const networkstatus_t *ns, const char *param_name, int32_t default_val, int32_t min_val, int32_t max_val) { if (!ns) /* if they pass in null, go find it ourselves */ @@ -2253,7 +2281,7 @@ getinfo_helper_networkstatus(control_connection_t *conn, const char *question, char **answer, const char **errmsg) { - routerstatus_t *status; + const routerstatus_t *status; (void) conn; if (!current_consensus) { @@ -2264,7 +2292,7 @@ getinfo_helper_networkstatus(control_connection_t *conn, if (!strcmp(question, "ns/all")) { smartlist_t *statuses = smartlist_create(); SMARTLIST_FOREACH(current_consensus->routerstatus_list, - routerstatus_t *, rs, + const routerstatus_t *, rs, { smartlist_add(statuses, networkstatus_getinfo_helper_single(rs)); }); @@ -2308,8 +2336,9 @@ networkstatus_free_all(void) digestmap_free(v2_download_status_map, _tor_free); v2_download_status_map = NULL; - networkstatus_vote_free(current_consensus); - current_consensus = NULL; + networkstatus_vote_free(current_ns_consensus); + networkstatus_vote_free(current_md_consensus); + current_md_consensus = current_ns_consensus = NULL; for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i]; diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index ec2e8f884d..1b10f27388 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -38,31 +38,46 @@ int router_set_networkstatus_v2(const char *s, time_t arrived_at, void networkstatus_v2_list_clean(time_t now); int compare_digest_to_routerstatus_entry(const void *_key, const void **_member); -routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, +const routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest); -routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, +const routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns, + const char *digest); +routerstatus_t *networkstatus_v2_find_mutable_entry(networkstatus_v2_t *ns, + const char *digest); +routerstatus_t *networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest); int networkstatus_vote_find_entry_idx(networkstatus_t *ns, const char *digest, int *found_out); const smartlist_t *networkstatus_get_v2_list(void); download_status_t *router_get_dl_status_by_descriptor_digest(const char *d); -routerstatus_t *router_get_consensus_status_by_id(const char *digest); -routerstatus_t *router_get_consensus_status_by_descriptor_digest( - const char *digest); -routerstatus_t *router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed); +const routerstatus_t *router_get_consensus_status_by_id(const char *digest); +routerstatus_t *router_get_mutable_consensus_status_by_id( + const char *digest); +const routerstatus_t *router_get_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest); +routerstatus_t *router_get_mutable_consensus_status_by_descriptor_digest( + networkstatus_t *consensus, + const char *digest); +const routerstatus_t *router_get_consensus_status_by_nickname( + const char *nickname, + int warn_if_unnamed); const char *networkstatus_get_router_digest_by_nickname(const char *nickname); int networkstatus_nickname_is_unnamed(const char *nickname); -void networkstatus_consensus_download_failed(int status_code); +void networkstatus_consensus_download_failed(int status_code, + const char *flavname); void update_consensus_networkstatus_fetch_time(time_t now); -int should_delay_dir_fetches(or_options_t *options); +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); 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( + consensus_flavor_t f); networkstatus_t *networkstatus_get_live_consensus(time_t now); -networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now); +networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, + int flavor); #define NSSET_FROM_CACHE 1 #define NSSET_WAS_WAITING_FOR_CERTS 2 #define NSSET_DONT_DOWNLOAD_CERTS 4 @@ -78,10 +93,11 @@ void routers_update_status_from_consensus_networkstatus(smartlist_t *routers, void signed_descs_update_status_from_consensus_networkstatus( smartlist_t *descs); -char *networkstatus_getinfo_helper_single(routerstatus_t *rs); +char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); -int32_t networkstatus_get_param(networkstatus_t *ns, const char *param_name, +int32_t networkstatus_get_param(const networkstatus_t *ns, + const char *param_name, int32_t default_val, int32_t min_val, int32_t max_val); int getinfo_helper_networkstatus(control_connection_t *conn, diff --git a/src/or/nodelist.c b/src/or/nodelist.c new file mode 100644 index 0000000000..b93b919c13 --- /dev/null +++ b/src/or/nodelist.c @@ -0,0 +1,758 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "config.h" +#include "dirserv.h" +#include "microdesc.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "policies.h" +#include "router.h" +#include "routerlist.h" + +#include <string.h> + +static void nodelist_drop_node(node_t *node, int remove_from_ht); +static void node_free(node_t *node); + +/** 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 + * is currently in the routerlist, or currently in the consensus we're using. + */ +typedef struct nodelist_t { + /* A list of all the nodes. */ + smartlist_t *nodes; + /* Hash table to map from node ID digest to node. */ + HT_HEAD(nodelist_map, node_t) nodes_by_id; + +} nodelist_t; + +static INLINE unsigned int +node_id_hash(const node_t *node) +{ +#if SIZEOF_INT == 4 + const uint32_t *p = (const uint32_t*)node->identity; + return p[0] ^ p[1] ^ p[2] ^ p[3] ^ p[4]; +#elif SIZEOF_INT == 8 + const uint64_t *p = (const uint32_t*)node->identity; + const uint32_t *p32 = (const uint32_t*)node->identity; + return p[0] ^ p[1] ^ p32[4]; +#endif +} + +static INLINE unsigned int +node_id_eq(const node_t *node1, const node_t *node2) +{ + return tor_memeq(node1->identity, node2->identity, DIGEST_LEN); +} + +HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq); +HT_GENERATE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, + 0.6, malloc, realloc, free); + +/** The global nodelist. */ +static nodelist_t *the_nodelist=NULL; + +/** Create an empty nodelist if we haven't done so already. */ +static void +init_nodelist(void) +{ + if (PREDICT_UNLIKELY(the_nodelist == NULL)) { + the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); + HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + the_nodelist->nodes = smartlist_create(); + } +} + +/** As node_get_by_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_id(const char *identity_digest) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + + memcpy(&search.identity, identity_digest, DIGEST_LEN); + node = HT_FIND(nodelist_map, &the_nodelist->nodes_by_id, &search); + return node; +} + +/** Return the node_t whose identity is <b>identity_digest</b>, or NULL + * if no such node exists. */ +const node_t * +node_get_by_id(const char *identity_digest) +{ + return node_get_mutable_by_id(identity_digest); +} + +/** Internal: return the node_t whose identity_digest is + * <b>identity_digest</b>. If none exists, create a new one, add it to the + * nodelist, and return it. + * + * Requires that the nodelist be initialized. + */ +static node_t * +node_get_or_create(const char *identity_digest) +{ + node_t *node; + + if ((node = node_get_mutable_by_id(identity_digest))) + return node; + + node = tor_malloc_zero(sizeof(node_t)); + memcpy(node->identity, identity_digest, DIGEST_LEN); + HT_INSERT(nodelist_map, &the_nodelist->nodes_by_id, node); + + smartlist_add(the_nodelist->nodes, node); + node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + + node->country = -1; + + return node; +} + +/** Add <b>ri</b> to the nodelist. */ +node_t * +nodelist_add_routerinfo(routerinfo_t *ri) +{ + node_t *node; + init_nodelist(); + node = node_get_or_create(ri->cache_info.identity_digest); + node->ri = ri; + + if (node->country == -1) + node_set_country(node); + + if (authdir_mode(get_options())) { + const char *discard=NULL; + uint32_t status = dirserv_router_get_status(ri, &discard); + dirserv_set_node_flags_from_authoritative_status(node, status); + } + + return node; +} + +/** Set the appropriate node_t to use <b>md</b> as its microdescriptor. + * + * Called when a new microdesc has arrived and the usable consensus flavor + * is "microdesc". + **/ +node_t * +nodelist_add_microdesc(microdesc_t *md) +{ + networkstatus_t *ns = + networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + const routerstatus_t *rs; + node_t *node; + if (ns == NULL) + return NULL; + init_nodelist(); + + /* Microdescriptors don't carry an identity digest, so we need to figure + * it out by looking up the routerstatus. */ + rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); + if (rs == NULL) + return NULL; + node = node_get_mutable_by_id(rs->identity_digest); + if (node) { + if (node->md) + node->md->held_by_nodes--; + node->md = md; + md->held_by_nodes++; + } + return node; +} + +/** Tell the nodelist that the current usable consensus to <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. + */ +void +nodelist_set_consensus(networkstatus_t *ns) +{ + const or_options_t *options = get_options(); + int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + + init_nodelist(); + if (ns->flavor == FLAV_MICRODESC) + (void) get_microdesc_cache(); /* Make sure it exists first. */ + + SMARTLIST_FOREACH(the_nodelist->nodes, node_t *, node, + node->rs = NULL); + + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + node_t *node = node_get_or_create(rs->identity_digest); + node->rs = rs; + if (ns->flavor == FLAV_MICRODESC) { + if (node->md == NULL || + tor_memneq(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + if (node->md) + node->md->held_by_nodes--; + node->md = microdesc_cache_lookup_by_digest256(NULL, + rs->descriptor_digest); + if (node->md) + node->md->held_by_nodes++; + } + } + + node_set_country(node); + + /* If we're not an authdir, believe others. */ + if (!authdir) { + node->is_valid = rs->is_valid; + node->is_running = rs->is_flagged_running; + node->is_fast = rs->is_fast; + node->is_stable = rs->is_stable; + node->is_possible_guard = rs->is_possible_guard; + node->is_exit = rs->is_exit; + node->is_bad_directory = rs->is_bad_directory; + node->is_bad_exit = rs->is_bad_exit; + node->is_hs_dir = rs->is_hs_dir; + } + + } SMARTLIST_FOREACH_END(rs); + + nodelist_purge(); + + if (! authdir) { + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + /* We have no routerstatus for this router. Clear flags so we can skip + * it, maybe.*/ + if (!node->rs) { + tor_assert(node->ri); /* if it had only an md, or nothing, purge + * would have removed it. */ + if (node->ri->purpose == ROUTER_PURPOSE_GENERAL) { + /* Clear all flags. */ + 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; + } + } + } SMARTLIST_FOREACH_END(node); + } +} + +/** Helper: return true iff a node has a usable amount of information*/ +static INLINE int +node_is_usable(const node_t *node) +{ + return (node->rs) || (node->ri); +} + +/** Tell the nodelist that <b>md</b> is no longer a microdescriptor for the + * node with <b>identity_digest</b>. */ +void +nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) +{ + node_t *node = node_get_mutable_by_id(identity_digest); + if (node && node->md == md) { + node->md = NULL; + md->held_by_nodes--; + } +} + +/** Tell the nodelist that <b>ri</b> is no longer in the routerlist. */ +void +nodelist_remove_routerinfo(routerinfo_t *ri) +{ + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + if (node && node->ri == ri) { + node->ri = NULL; + if (! node_is_usable(node)) { + nodelist_drop_node(node, 1); + node_free(node); + } + } +} + +/** Remove <b>node</b> from the nodelist. (Asserts that it was there to begin + * with.) */ +static void +nodelist_drop_node(node_t *node, int remove_from_ht) +{ + node_t *tmp; + int idx; + if (remove_from_ht) { + tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); + tor_assert(tmp == node); + } + + idx = node->nodelist_idx; + tor_assert(idx >= 0); + + tor_assert(node == smartlist_get(the_nodelist->nodes, idx)); + smartlist_del(the_nodelist->nodes, idx); + if (idx < smartlist_len(the_nodelist->nodes)) { + tmp = smartlist_get(the_nodelist->nodes, idx); + tmp->nodelist_idx = idx; + } + node->nodelist_idx = -1; +} + +/** Release storage held by <b>node</b> */ +static void +node_free(node_t *node) +{ + if (!node) + return; + if (node->md) + node->md->held_by_nodes--; + tor_assert(node->nodelist_idx == -1); + tor_free(node); +} + +/** Remove all entries from the nodelist that don't have enough info to be + * usable for anything. */ +void +nodelist_purge(void) +{ + node_t **iter; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return; + + /* Remove the non-usable nodes. */ + for (iter = HT_START(nodelist_map, &the_nodelist->nodes_by_id); iter; ) { + node_t *node = *iter; + + if (node->md && !node->rs) { + /* An md is only useful if there is an rs. */ + node->md->held_by_nodes--; + node->md = NULL; + } + + if (node_is_usable(node)) { + iter = HT_NEXT(nodelist_map, &the_nodelist->nodes_by_id, iter); + } else { + iter = HT_NEXT_RMV(nodelist_map, &the_nodelist->nodes_by_id, iter); + nodelist_drop_node(node, 0); + node_free(node); + } + } + nodelist_assert_ok(); +} + +/** Release all storage held by the nodelist. */ +void +nodelist_free_all(void) +{ + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return; + + HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node->nodelist_idx = -1; + node_free(node); + } SMARTLIST_FOREACH_END(node); + + smartlist_free(the_nodelist->nodes); + + tor_free(the_nodelist); +} + +/** Check that the nodelist is internally consistent, and consistent with + * the directory info it's derived from. + */ +void +nodelist_assert_ok(void) +{ + routerlist_t *rl = router_get_routerlist(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + digestmap_t *dm; + + if (!the_nodelist) + return; + + dm = digestmap_new(); + + /* every routerinfo in rl->routers should be in the nodelist. */ + if (rl) { + SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, ri) { + const node_t *node = node_get_by_id(ri->cache_info.identity_digest); + tor_assert(node && node->ri == ri); + tor_assert(fast_memeq(ri->cache_info.identity_digest, + node->identity, DIGEST_LEN)); + tor_assert(! digestmap_get(dm, node->identity)); + digestmap_set(dm, node->identity, (void*)node); + } SMARTLIST_FOREACH_END(ri); + } + + /* every routerstatus in ns should be in the nodelist */ + if (ns) { + SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { + const node_t *node = node_get_by_id(rs->identity_digest); + tor_assert(node && node->rs == rs); + tor_assert(fast_memeq(rs->identity_digest, node->identity, DIGEST_LEN)); + digestmap_set(dm, node->identity, (void*)node); + if (ns->flavor == FLAV_MICRODESC) { + /* If it's a microdesc consensus, every entry that has a + * microdescriptor should be in the nodelist. + */ + microdesc_t *md = + microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); + tor_assert(md == node->md); + if (md) + tor_assert(md->held_by_nodes >= 1); + } + } SMARTLIST_FOREACH_END(rs); + } + + /* The nodelist should have no other entries, and its entries should be + * well-formed. */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + tor_assert(digestmap_get(dm, node->identity) != NULL); + tor_assert(node_sl_idx == node->nodelist_idx); + } SMARTLIST_FOREACH_END(node); + + tor_assert((long)smartlist_len(the_nodelist->nodes) == + (long)HT_SIZE(&the_nodelist->nodes_by_id)); + + digestmap_free(dm, NULL); +} + +/** Return a list of a node_t * for every node we know about. The caller + * MUST NOT modify the list. (You can set and clear flags in the nodes if + * you must, but you must not add or remove nodes.) */ +smartlist_t * +nodelist_get_list(void) +{ + init_nodelist(); + return the_nodelist->nodes; +} + +/** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, + * or $DIGEST~name, return the node with the matching identity digest and + * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> + * is not well-formed. */ +const node_t * +node_get_by_hex_id(const char *hex_id) +{ + char digest_buf[DIGEST_LEN]; + char nn_buf[MAX_NICKNAME_LEN+1]; + char nn_char='\0'; + + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { + const node_t *node = node_get_by_id(digest_buf); + if (!node) + return NULL; + if (nn_char) { + const char *real_name = node_get_nickname(node); + if (!real_name || strcasecmp(real_name, nn_buf)) + return NULL; + if (nn_char == '=') { + const char *named_id = + networkstatus_get_router_digest_by_nickname(nn_buf); + if (!named_id || tor_memneq(named_id, digest_buf, DIGEST_LEN)) + return NULL; + } + } + return node; + } + + return NULL; +} + +/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return + * the corresponding node_t, or NULL if none exists. Warn the user if + * <b>warn_if_unnamed</b> is set, and they have specified a router by + * nickname, but the Named flag isn't set for that router. */ +const node_t * +node_get_by_nickname(const char *nickname, int warn_if_unnamed) +{ + const node_t *node; + if (!the_nodelist) + return NULL; + + /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ + if ((node = node_get_by_hex_id(nickname)) != NULL) + return node; + + if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) + return NULL; + + /* Okay, so if we get here, the nickname is just a nickname. Is there + * a binding for it in the consensus? */ + { + const char *named_id = + networkstatus_get_router_digest_by_nickname(nickname); + if (named_id) + return node_get_by_id(named_id); + } + + /* Is it marked as owned-by-someone-else? */ + if (networkstatus_nickname_is_unnamed(nickname)) { + log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " + "router that holds it, but not one listed in the current " + "consensus.", escaped(nickname)); + return NULL; + } + + /* Okay, so the name is not canonical for anybody. */ + { + smartlist_t *matches = smartlist_create(); + const node_t *choice = NULL; + + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!strcasecmp(node_get_nickname(node), nickname)) + smartlist_add(matches, node); + } SMARTLIST_FOREACH_END(node); + + if (smartlist_len(matches)>1 && warn_if_unnamed) { + int any_unwarned = 0; + SMARTLIST_FOREACH_BEGIN(matches, node_t *, node) { + if (!node->name_lookup_warned) { + node->name_lookup_warned = 1; + any_unwarned = 1; + } + } SMARTLIST_FOREACH_END(node); + + if (any_unwarned) { + log_warn(LD_CONFIG, "There are multiple matches for the name %s, " + "but none is listed as Named in the directory consensus. " + "Choosing one arbitrarily.", nickname); + } + } else if (smartlist_len(matches)>1 && warn_if_unnamed) { + char fp[HEX_DIGEST_LEN+1]; + node_t *node = smartlist_get(matches, 0); + if (node->name_lookup_warned) { + base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); + log_warn(LD_CONFIG, + "You specified a server \"%s\" by name, but the directory " + "authorities do not have any key registered for this " + "nickname -- so it could be used by any server, not just " + "the one you meant. " + "To make sure you get the same server in the future, refer " + "to it by key, as \"$%s\".", nickname, fp); + node->name_lookup_warned = 1; + } + } + + if (smartlist_len(matches)) + choice = smartlist_get(matches, 0); + + smartlist_free(matches); + return choice; + } +} + +/** Return the nickname of <b>node</b>, or NULL if we can't find one. */ +const char * +node_get_nickname(const node_t *node) +{ + tor_assert(node); + if (node->rs) + return node->rs->nickname; + else if (node->ri) + return node->ri->nickname; + else + return NULL; +} + +/** Return true iff the nickname of <b>node</b> is canonical, based on the + * latest consensus. */ +int +node_is_named(const node_t *node) +{ + const char *named_id; + const char *nickname = node_get_nickname(node); + if (!nickname) + return 0; + named_id = networkstatus_get_router_digest_by_nickname(nickname); + if (!named_id) + return 0; + return tor_memeq(named_id, node->identity, DIGEST_LEN); +} + +/** Return true iff <b>node</b> appears to be a directory authority or + * directory cache */ +int +node_is_dir(const node_t *node) +{ + if (node->rs) + return node->rs->dir_port != 0; + else if (node->ri) + return node->ri->dir_port != 0; + else + return 0; +} + +/** Return true iff <b>node</b> has either kind of usable descriptor -- that + * is, a routerdecriptor or a microdescriptor. */ +int +node_has_descriptor(const node_t *node) +{ + return (node->ri || + (node->rs && node->md)); +} + +/** Return the router_purpose of <b>node</b>. */ +int +node_get_purpose(const node_t *node) +{ + if (node->ri) + return node->ri->purpose; + else + return ROUTER_PURPOSE_GENERAL; +} + +/** Compute the verbose ("extended") nickname of <b>node</b> and store it + * into the MAX_VERBOSE_NICKNAME_LEN+1 character buffer at + * <b>verbose_nickname_out</b> */ +void +node_get_verbose_nickname(const node_t *node, + char *verbose_name_out) +{ + const char *nickname = node_get_nickname(node); + int is_named = node_is_named(node); + verbose_name_out[0] = '$'; + base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, + DIGEST_LEN); + if (!nickname) + return; + verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); +} + +/** Return true iff it seems that <b>node</b> allows circuits to exit + * through it directlry from the client. */ +int +node_allows_single_hop_exits(const node_t *node) +{ + if (node && node->ri) + return node->ri->allow_single_hop_exits; + else + return 0; +} + +/** Return true iff it seems that <b>node</b> has an exit policy that doesn't + * actually permit anything to exit, or we don't know its exit policy */ +int +node_exit_policy_rejects_all(const node_t *node) +{ + if (node->rejects_all) + return 1; + + if (node->ri) + return node->ri->policy_is_reject_star; + else if (node->md) + return node->md->exit_policy == NULL || + short_policy_is_reject_star(node->md->exit_policy); + else + return 1; +} + +/** Copy the address for <b>node</b> into *<b>addr_out</b>. */ +int +node_get_addr(const node_t *node, tor_addr_t *addr_out) +{ + if (node->ri) { + tor_addr_from_ipv4h(addr_out, node->ri->addr); + return 0; + } else if (node->rs) { + tor_addr_from_ipv4h(addr_out, node->rs->addr); + return 0; + } + return -1; +} + +/** Return the host-order IPv4 address for <b>node</b>, or 0 if it doesn't + * seem to have one. */ +uint32_t +node_get_addr_ipv4h(const node_t *node) +{ + if (node->ri) { + return node->ri->addr; + } else if (node->rs) { + return node->rs->addr; + } + return 0; +} + +/** Copy a string representation of the IP address for <b>node</b> into the + * <b>len</b>-byte buffer at <b>buf</b>. + */ +void +node_get_address_string(const node_t *node, char *buf, size_t len) +{ + if (node->ri) { + strlcpy(buf, node->ri->address, len); + } else if (node->rs) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, node->rs->addr); + tor_addr_to_str(buf, &addr, len, 0); + } else { + buf[0] = '\0'; + } +} + +/** Return <b>node</b>'s declared uptime, or -1 if it doesn't seem to have + * one. */ +long +node_get_declared_uptime(const node_t *node) +{ + if (node->ri) + return node->ri->uptime; + else + return -1; +} + +/** Return <b>node</b>'s declared or_port */ +uint16_t +node_get_orport(const node_t *node) +{ + if (node->ri) + return node->ri->or_port; + else if (node->rs) + return node->rs->or_port; + else + return 0; +} + +/** Return <b>node</b>'s platform string, or NULL if we don't know it. */ +const char * +node_get_platform(const node_t *node) +{ + /* If we wanted, we could record the version in the routerstatus_t, since + * the consensus lists it. We don't, though, so this function just won't + * work with microdescriptors. */ + if (node->ri) + return node->ri->platform; + else + return NULL; +} + +/** Return <b>node</b>'s time of publication, or 0 if we don't have one. */ +time_t +node_get_published_on(const node_t *node) +{ + if (node->ri) + return node->ri->cache_info.published_on; + else + return 0; +} + +/** Return true iff <b>node</b> is one representing this router. */ +int +node_is_me(const node_t *node) +{ + return router_digest_is_me(node->identity); +} + +/** Return <b>node</b> declared family (as a list of names), or NULL if + * the node didn't declare a family. */ +const smartlist_t * +node_get_declared_family(const node_t *node) +{ + if (node->ri && node->ri->declared_family) + return node->ri->declared_family; + else if (node->md && node->md->family) + return node->md->family; + else + return NULL; +} + diff --git a/src/or/nodelist.h b/src/or/nodelist.h new file mode 100644 index 0000000000..bd2e63953c --- /dev/null +++ b/src/or/nodelist.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-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file microdesc.h + * \brief Header file for microdesc.c. + **/ + +#ifndef _TOR_NODELIST_H +#define _TOR_NODELIST_H + +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_add_microdesc(microdesc_t *md); +void nodelist_set_consensus(networkstatus_t *ns); + +void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); +void nodelist_remove_routerinfo(routerinfo_t *ri); +void nodelist_purge(void); + +void nodelist_free_all(void); +void nodelist_assert_ok(void); + +const node_t *node_get_by_nickname(const char *nickname, int warn_if_unnamed); +void node_get_verbose_nickname(const node_t *node, + char *verbose_name_out); +int node_is_named(const node_t *node); +int node_is_dir(const node_t *node); +int node_has_descriptor(const node_t *node); +int node_get_purpose(const node_t *node); +#define node_is_bridge(node) \ + (node_get_purpose((node)) == ROUTER_PURPOSE_BRIDGE) +int node_is_me(const node_t *node); +int node_exit_policy_rejects_all(const node_t *node); +int node_get_addr(const node_t *node, tor_addr_t *addr_out); +uint32_t node_get_addr_ipv4h(const node_t *node); +int node_allows_single_hop_exits(const node_t *node); +uint16_t node_get_orport(const node_t *node); +const char *node_get_nickname(const node_t *node); +const char *node_get_platform(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); + +smartlist_t *nodelist_get_list(void); + +/* 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); + +#endif + diff --git a/src/or/ntmain.c b/src/or/ntmain.c index b2fee648cc..4eb487e976 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -193,7 +193,6 @@ nt_service_loadlibrary(void) */ int nt_service_is_stopping(void) -/* XXXX this function would probably _love_ to be inline, in 0.2.0. */ { /* If we haven't loaded the function pointers, we can't possibly be an NT * service trying to shut down. */ diff --git a/src/or/or.h b/src/or/or.h index 7d50e1f505..e4f9b9b2b6 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -23,8 +23,12 @@ #endif #ifdef MS_WINDOWS +#ifndef WIN32_WINNT #define WIN32_WINNT 0x400 +#endif +#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x400 +#endif #define WIN32_LEAN_AND_MEAN #endif @@ -83,6 +87,13 @@ #define snprintf _snprintf #endif +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#include <event2/buffer.h> +#include <event2/util.h> +#endif + +#include "crypto.h" #include "tortls.h" #include "../common/torlog.h" #include "container.h" @@ -225,15 +236,30 @@ typedef enum { #define PROXY_CONNECT 1 #define PROXY_SOCKS4 2 #define PROXY_SOCKS5 3 +/* !!!! If there is ever a PROXY_* type over 2, we must grow the proxy_type + * field in or_connection_t */ +/* pluggable transports proxy type */ +#define PROXY_PLUGGABLE 4 /* Proxy client handshake states */ -#define PROXY_HTTPS_WANT_CONNECT_OK 1 -#define PROXY_SOCKS4_WANT_CONNECT_OK 2 -#define PROXY_SOCKS5_WANT_AUTH_METHOD_NONE 3 -#define PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929 4 -#define PROXY_SOCKS5_WANT_AUTH_RFC1929_OK 5 -#define PROXY_SOCKS5_WANT_CONNECT_OK 6 -#define PROXY_CONNECTED 7 +/* We use a proxy but we haven't even connected to it yet. */ +#define PROXY_INFANT 1 +/* We use an HTTP proxy and we've sent the CONNECT command. */ +#define PROXY_HTTPS_WANT_CONNECT_OK 2 +/* We use a SOCKS4 proxy and we've sent the CONNECT command. */ +#define PROXY_SOCKS4_WANT_CONNECT_OK 3 +/* We use a SOCKS5 proxy and we try to negotiate without + any authentication . */ +#define PROXY_SOCKS5_WANT_AUTH_METHOD_NONE 4 +/* We use a SOCKS5 proxy and we try to negotiate with + Username/Password authentication . */ +#define PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929 5 +/* We use a SOCKS5 proxy and we just sent our credentials. */ +#define PROXY_SOCKS5_WANT_AUTH_RFC1929_OK 6 +/* We use a SOCKS5 proxy and we just sent our CONNECT command. */ +#define PROXY_SOCKS5_WANT_CONNECT_OK 7 +/* We use a proxy and we CONNECTed successfully!. */ +#define PROXY_CONNECTED 8 /** True iff <b>x</b> is an edge connection. */ #define CONN_IS_EDGE(x) \ @@ -257,22 +283,27 @@ typedef enum { #define OR_CONN_STATE_CONNECTING 1 /** State for a connection to an OR: waiting for proxy handshake to complete */ #define OR_CONN_STATE_PROXY_HANDSHAKING 2 -/** State for a connection to an OR or client: SSL is handshaking, not done +/** State for an OR connection client: SSL is handshaking, not done * yet. */ #define OR_CONN_STATE_TLS_HANDSHAKING 3 /** State for a connection to an OR: We're doing a second SSL handshake for - * renegotiation purposes. */ + * renegotiation purposes. (V2 handshake only.) */ #define OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING 4 /** State for a connection at an OR: We're waiting for the client to - * renegotiate. */ + * renegotiate (to indicate a v2 handshake) or send a versions cell (to + * indicate a v3 handshake) */ #define OR_CONN_STATE_TLS_SERVER_RENEGOTIATING 5 -/** State for a connection to an OR: We're done with our SSL handshake, but we - * haven't yet negotiated link protocol versions and sent a netinfo cell. - */ -#define OR_CONN_STATE_OR_HANDSHAKING 6 -/** State for a connection to an OR: Ready to send/receive cells. */ -#define OR_CONN_STATE_OPEN 7 -#define _OR_CONN_STATE_MAX 7 +/** State for an OR connection: We're done with our SSL handshake, we've done + * renegotiation, but we haven't yet negotiated link protocol versions and + * sent a netinfo cell. */ +#define OR_CONN_STATE_OR_HANDSHAKING_V2 6 +/** State for an OR connection: We're done with our SSL handshake, but we + * haven't yet negotiated link protocol versions, done a V3 handshake, and + * sent a netinfo cell. */ +#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 _EXIT_CONN_STATE_MIN 1 /** State for an exit connection: waiting for response from DNS farm. */ @@ -386,7 +417,9 @@ typedef enum { /** A connection to a hidden service directory server: download a v2 rendezvous * descriptor. */ #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 -#define _DIR_PURPOSE_MAX 18 +/** A connection to a directory server: download a microdescriptor. */ +#define DIR_PURPOSE_FETCH_MICRODESC 19 +#define _DIR_PURPOSE_MAX 19 /** True iff <b>p</b> is a purpose corresponding to uploading data to a * directory server. */ @@ -792,9 +825,10 @@ typedef enum { #define CELL_NETINFO 8 #define CELL_RELAY_EARLY 9 -/** True iff the cell command <b>x</b> is one that implies a variable-length - * cell. */ -#define CELL_COMMAND_IS_VAR_LENGTH(x) ((x) == CELL_VERSIONS) +#define CELL_VPADDING 128 +#define CELL_CERT 129 +#define CELL_AUTH_CHALLENGE 130 +#define CELL_AUTHENTICATE 131 /** How long to test reachability before complaining to the user. */ #define TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT (20*60) @@ -807,6 +841,9 @@ typedef enum { * Tor 0.1.2.x is obsolete, we can remove this. */ #define DEFAULT_CLIENT_NICKNAME "client" +/** Name chosen by routers that don't configure nicknames */ +#define UNNAMED_ROUTER_NICKNAME "Unnamed" + /** Number of bytes in a SOCKS4 header. */ #define SOCKS4_NETWORK_LEN 8 @@ -857,7 +894,7 @@ typedef struct var_cell_t { /** Number of bytes actually stored in <b>payload</b> */ uint16_t payload_len; /** Payload of this cell */ - uint8_t payload[1]; + uint8_t payload[FLEXIBLE_ARRAY_MEMBER]; } var_cell_t; /** A cell as packed for writing to the network. */ @@ -901,14 +938,21 @@ typedef struct { typedef struct buf_t buf_t; typedef struct socks_request_t socks_request_t; +#ifdef USE_BUFFEREVENTS +#define generic_buffer_t struct evbuffer +#else +#define generic_buffer_t buf_t +#endif /* Values for connection_t.magic: used to make sure that downcasts (casts from * connection_t to foo_connection_t) are safe. */ #define BASE_CONNECTION_MAGIC 0x7C3C304Eu #define OR_CONNECTION_MAGIC 0x7D31FF03u #define EDGE_CONNECTION_MAGIC 0xF0374013u +#define ENTRY_CONNECTION_MAGIC 0xbb4a5703 #define DIR_CONNECTION_MAGIC 0x9988ffeeu #define CONTROL_CONNECTION_MAGIC 0x8abc765du +#define LISTENER_CONNECTION_MAGIC 0x1a1ac741u /** Description of a connection to another host or process, and associated * data. @@ -974,6 +1018,7 @@ typedef struct connection_t { /** Our socket; -1 if this connection is closed, or has no socket. */ tor_socket_t s; int conn_array_index; /**< Index into the global connection array. */ + struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ buf_t *inbuf; /**< Buffer holding data read over this connection. */ @@ -984,6 +1029,11 @@ typedef struct connection_t { * read? */ time_t timestamp_lastwritten; /**< When was the last time libevent said we * could write? */ + +#ifdef USE_BUFFEREVENTS + struct bufferevent *bufev; /**< A Libevent buffered IO structure. */ +#endif + time_t timestamp_created; /**< When was this connection_t created? */ /* XXXX_IP6 make this IPv6-capable */ @@ -1008,17 +1058,73 @@ typedef struct connection_t { /** Unique identifier for this connection on this Tor instance. */ uint64_t global_identifier; - /* XXXX023 move this field, and all the listener-only fields (just - socket_family, I think), into a new listener_connection_t subtype. */ + /** Unique ID for measuring tunneled network status requests. */ + uint64_t dirreq_id; +} connection_t; + +typedef struct listener_connection_t { + 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. */ struct evdns_server_port *dns_server_port; - /** Unique ID for measuring tunneled network status requests. */ - uint64_t dirreq_id; -} connection_t; + /** @name Isolation parameters + * + * For an AP listener, these fields describe how to isolate streams that + * arrive on the listener. + * + * @{ + */ + /** The session group for this listener. */ + int session_group; + /** One or more ISO_ flags to describe how to isolate streams. */ + uint8_t isolation_flags; + /**@}*/ + +} listener_connection_t; -/** Stores flags and information related to the portion of a v2 Tor OR +/** Minimum length of the random part of an AUTH_CHALLENGE cell. */ +#define OR_AUTH_CHALLENGE_LEN 32 + +/** + * @name Certificate types for CERT cells. + * + * These values are defined by the protocol, and affect how an X509 + * certificate in a CERT cell is interpreted and used. + * + * @{ */ +/** A certificate that authenticates a TLS link key. The subject key + * must match the key used in the TLS handshake; it must be signed by + * the identity key. */ +#define OR_CERT_TYPE_TLS_LINK 1 +/** A self-signed identity certificate. The subject key must be a + * 1024-bit RSA key. */ +#define OR_CERT_TYPE_ID_1024 2 +/** A certificate that authenticates a key used in an AUTHENTICATE cell + * in the v3 handshake. The subject key must be a 1024-bit RSA key; it + * must be signed by the identity key */ +#define OR_CERT_TYPE_AUTH_1024 3 +/**@}*/ + +/** The one currently supported type of AUTHENTICATE cell. It contains + * a bunch of structures signed with an RSA1024 key. The signed + * structures include a HMAC using negotiated TLS secrets, and a digest + * of all cells sent or received before the AUTHENTICATE cell (including + * the random server-generated AUTH_CHALLENGE cell). + */ +#define AUTHTYPE_RSA_SHA256_TLSSECRET 1 + +/** The length of the part of the AUTHENTICATE cell body that the client and + * server can generate independently (when using RSA_SHA256_TLSSECRET). It + * contains everything except the client's timestamp, the client's randomly + * generated nonce, and the signature. */ +#define V3_AUTH_FIXED_PART_LEN (8+(32*6)) +/** The length of the part of the AUTHENTICATE cell body that the client + * signs. */ +#define V3_AUTH_BODY_LEN (V3_AUTH_FIXED_PART_LEN + 8 + 16) + +/** Stores flags and information related to the portion of a v2/v3 Tor OR * connection handshake that happens after the TLS handshake is finished. */ typedef struct or_handshake_state_t { @@ -1029,6 +1135,52 @@ typedef struct or_handshake_state_t { unsigned int started_here : 1; /** True iff we have received and processed a VERSIONS cell. */ unsigned int received_versions : 1; + /** True iff we have received and processed an AUTH_CHALLENGE cell */ + unsigned int received_auth_challenge : 1; + /** True iff we have received and processed a CERT cell. */ + unsigned int received_cert_cell : 1; + /** True iff we have received and processed an AUTHENTICATE cell */ + unsigned int received_authenticate : 1; + + /* True iff we've received valid authentication to some identity. */ + unsigned int authenticated : 1; + + /** True iff we should feed outgoing cells into digest_sent and + * digest_received respectively. + * + * From the server's side of the v3 handshake, we want to capture everything + * from the VERSIONS cell through and including the AUTH_CHALLENGE cell. + * From the client's, we want to capture everything from the VERSIONS cell + * through but *not* including the AUTHENTICATE cell. + * + * @{ */ + unsigned int digest_sent_data : 1; + unsigned int digest_received_data : 1; + /**@}*/ + + /** Identity digest that we have received and authenticated for our peer + * on this connection. */ + uint8_t authenticated_peer_id[DIGEST_LEN]; + + /** Digests of the cells that we have sent or received as part of a V3 + * handshake. Used for making and checking AUTHENTICATE cells. + * + * @{ + */ + crypto_digest_env_t *digest_sent; + crypto_digest_env_t *digest_received; + /** @} */ + + /** Certificates that a connection initiator sent us in a CERT cell; we're + * holding on to them until we get an AUTHENTICATE cell. + * + * @{ + */ + /** The cert for the key that's supposed to sign the AUTHENTICATE cell */ + tor_cert_t *auth_cert; + /** A self-signed identity certificate */ + tor_cert_t *id_cert; + /**@}*/ } or_handshake_state_t; /** Subtype of connection_t for an "OR connection" -- that is, one that speaks @@ -1074,6 +1226,7 @@ typedef struct or_connection_t { unsigned int is_connection_with_client:1; /** True iff this is an outgoing connection. */ unsigned int is_outgoing:1; + unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */ 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 @@ -1089,10 +1242,16 @@ typedef struct or_connection_t { /* bandwidth* and *_bucket only used by ORs in OPEN state: */ int bandwidthrate; /**< Bytes/s added to the bucket. (OPEN ORs only.) */ int bandwidthburst; /**< Max bucket size for this conn. (OPEN ORs only.) */ +#ifndef USE_BUFFEREVENTS int read_bucket; /**< When this hits 0, stop receiving. Every second we * add 'bandwidthrate' to this, capping it at * bandwidthburst. (OPEN ORs only) */ int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */ +#else + /** DOCDOC */ + /* 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 ? */ @@ -1115,27 +1274,27 @@ typedef struct or_connection_t { * identity digest as this one. */ } or_connection_t; -/** Subtype of connection_t for an "edge connection" -- that is, a socks (ap) +/** 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; struct edge_connection_t *next_stream; /**< Points to the next stream at this * edge, if any */ - struct crypt_path_t *cpath_layer; /**< A pointer to which node in the circ - * this conn exits at. */ int package_window; /**< How many more relay cells can I send into the * circuit? */ int deliver_window; /**< How many more relay cells can end at me? */ - /** Nickname of planned exit node -- used with .exit support. */ - char *chosen_exit_name; - - socks_request_t *socks_request; /**< SOCKS structure describing request (AP - * only.) */ struct circuit_t *on_circuit; /**< The circuit (if any) that this edge * connection is using. */ + /** A pointer to which node in the circ this conn exits at. Set for AP + * connections and for hidden service exit connections. */ + struct crypt_path_t *cpath_layer; + /** What rendezvous service are we querying for (if an AP) or providing (if + * an exit)? */ + rend_data_t *rend_data; + uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit * connection. Exit connections only. */ @@ -1151,14 +1310,62 @@ typedef struct edge_connection_t { /** Bytes written since last call to control_event_stream_bandwidth_used() */ uint32_t n_written; - /** What rendezvous service are we querying for? (AP only) */ - rend_data_t *rend_data; + /** True iff this connection is for a DNS request only. */ + unsigned int is_dns_request:1; + + unsigned int edge_has_sent_end:1; /**< For debugging; only used on edge + * connections. Set once we've set the stream end, + * and check in connection_about_to_close_connection(). + */ + /** True iff we've blocked reading until the circuit has fewer queued + * cells. */ + unsigned int edge_blocked_on_circ:1; + +} 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; + + /** Nickname of planned exit node -- used with .exit support. */ + char *chosen_exit_name; + + socks_request_t *socks_request; /**< SOCKS structure describing request (AP + * only.) */ + + /* === Isolation related, AP only. === */ + /** AP only: based on which factors do we isolate this stream? */ + uint8_t isolation_flags; + /** AP only: what session group is this stream in? */ + int session_group; + /** AP only: The newnym epoch in which we created this connection. */ + unsigned nym_epoch; + /** AP only: The original requested address before we rewrote it. */ + char *original_dest_address; + /* Other fields to isolate on already exist. The ClientAddr is addr. The + ClientProtocol is a combination of type and socks_request-> + socks_version. SocksAuth is socks_request->username/password. + DestAddr is in socks_request->address. */ /** Number of times we've reassigned this application connection to * a new circuit. We keep track because the timeout is longer if we've * already retried several times. */ uint8_t num_socks_retries; + /** For AP connections only: buffer for data that we have sent + * optimistically, which we might need to re-send if we have to + * retry this connection. */ + generic_buffer_t *pending_optimistic_data; + /* For AP connections only: buffer for data that we previously sent + * optimistically which we are currently re-sending as we retry this + * connection. */ + generic_buffer_t *sending_optimistic_data; + + /** If this is a DNSPort connection, this field holds the pending DNS + * request that we're going to try to answer. */ + struct evdns_server_request *dns_server_request; + #define NUM_CIRCUITS_LAUNCHED_THRESHOLD 10 /** Number of times we've launched a circuit to handle this stream. If * it gets too high, that could indicate an inconsistency between our @@ -1166,9 +1373,6 @@ typedef struct edge_connection_t { * stream to one of the available circuits" logic. */ unsigned int num_circuits_launched:4; - /** True iff this connection is for a DNS request only. */ - unsigned int is_dns_request:1; - /** True iff this stream must attach to a one-hop circuit (e.g. for * begin_dir). */ unsigned int want_onehop:1; @@ -1176,13 +1380,6 @@ typedef struct edge_connection_t { * itself rather than BEGIN (either via onehop or via a whole circuit). */ unsigned int use_begindir:1; - unsigned int edge_has_sent_end:1; /**< For debugging; only used on edge - * connections. Set once we've set the stream end, - * and check in connection_about_to_close_connection(). - */ - /** True iff we've blocked reading until the circuit has fewer queued - * cells. */ - unsigned int edge_blocked_on_circ:1; /** For AP connections only. If 1, and we fail to reach the chosen exit, * stop requiring it. */ unsigned int chosen_exit_optional:1; @@ -1196,19 +1393,26 @@ typedef struct edge_connection_t { * NATd connection */ unsigned int is_transparent_ap:1; - /** If this is a DNSPort connection, this field holds the pending DNS - * request that we're going to try to answer. */ - struct evdns_server_request *dns_server_request; + /** For AP connections only: Set if this connection's target exit node + * allows optimistic data (that is, data sent on this stream before + * the exit has sent a CONNECTED cell) and we have chosen to use it. + */ + unsigned int may_use_optimistic_data : 1; -} edge_connection_t; +} 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; - char *requested_resource; /**< Which 'resource' did we ask the directory - * for? */ + /** Which 'resource' did we ask the directory for? This is typically the part + * of the URL string that defines, relative to the directory conn purpose, + * what thing we want. For example, in router descriptor downloads by + * descriptor digest, it contains "d/", then one ore more +-separated + * fingerprints. + **/ + char *requested_resource; unsigned int dirconn_direct:1; /**< Is this dirconn direct, or via Tor? */ /* Used only for server sides of some dir connections, to implement @@ -1268,6 +1472,11 @@ typedef struct control_connection_t { /** 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)) +/** Cast a entry_connection_t subtype pointer to a connection_t **/ +#define ENTRY_TO_CONN(c) (TO_CONN(ENTRY_TO_EDGE_CONN(c))) + /** Convert a connection_t* to an or_connection_t*; assert if the cast is * invalid. */ static or_connection_t *TO_OR_CONN(connection_t *); @@ -1277,9 +1486,18 @@ static dir_connection_t *TO_DIR_CONN(connection_t *); /** Convert a connection_t* to an edge_connection_t*; assert if the cast is * invalid. */ static edge_connection_t *TO_EDGE_CONN(connection_t *); +/** Convert a connection_t* to an entry_connection_t*; assert if the cast is + * invalid. */ +static entry_connection_t *TO_ENTRY_CONN(connection_t *); +/** Convert a edge_connection_t* to an entry_connection_t*; assert if the cast + * is invalid. */ +static entry_connection_t *EDGE_TO_ENTRY_CONN(edge_connection_t *); /** Convert a connection_t* to an control_connection_t*; assert if the cast is * invalid. */ static control_connection_t *TO_CONTROL_CONN(connection_t *); +/** Convert a connection_t* to an listener_connection_t*; assert if the cast is + * invalid. */ +static listener_connection_t *TO_LISTENER_CONN(connection_t *); static INLINE or_connection_t *TO_OR_CONN(connection_t *c) { @@ -1293,14 +1511,75 @@ static INLINE dir_connection_t *TO_DIR_CONN(connection_t *c) } static INLINE edge_connection_t *TO_EDGE_CONN(connection_t *c) { - tor_assert(c->magic == EDGE_CONNECTION_MAGIC); + tor_assert(c->magic == EDGE_CONNECTION_MAGIC || + c->magic == ENTRY_CONNECTION_MAGIC); return DOWNCAST(edge_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); +} +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); +} static INLINE control_connection_t *TO_CONTROL_CONN(connection_t *c) { tor_assert(c->magic == CONTROL_CONNECTION_MAGIC); return DOWNCAST(control_connection_t, c); } +static INLINE listener_connection_t *TO_LISTENER_CONN(connection_t *c) +{ + tor_assert(c->magic == LISTENER_CONNECTION_MAGIC); + return DOWNCAST(listener_connection_t, c); +} + +/* Conditional macros to help write code that works whether bufferevents are + disabled or not. + + We can't just write: + if (conn->bufev) { + do bufferevent stuff; + } else { + do other stuff; + } + because the bufferevent stuff won't even compile unless we have a fairly + new version of Libevent. Instead, we say: + IF_HAS_BUFFEREVENT(conn, { do_bufferevent_stuff } ); + or: + IF_HAS_BUFFEREVENT(conn, { + do bufferevent stuff; + }) ELSE_IF_NO_BUFFEREVENT { + do non-bufferevent stuff; + } + If we're compiling with bufferevent support, then the macros expand more or + less to: + if (conn->bufev) { + do_bufferevent_stuff; + } else { + do non-bufferevent stuff; + } + and if we aren't using bufferevents, they expand more or less to: + { do non-bufferevent stuff; } +*/ +#ifdef USE_BUFFEREVENTS +#define HAS_BUFFEREVENT(c) (((c)->bufev) != NULL) +#define IF_HAS_BUFFEREVENT(c, stmt) \ + if ((c)->bufev) do { \ + stmt ; \ + } while (0) +#define ELSE_IF_NO_BUFFEREVENT ; else +#define IF_HAS_NO_BUFFEREVENT(c) \ + if (NULL == (c)->bufev) +#else +#define HAS_BUFFEREVENT(c) (0) +#define IF_HAS_BUFFEREVENT(c, stmt) (void)0 +#define ELSE_IF_NO_BUFFEREVENT ; +#define IF_HAS_NO_BUFFEREVENT(c) \ + if (1) +#endif /** What action type does an address policy indicate: accept or reject? */ typedef enum { @@ -1417,11 +1696,6 @@ typedef struct signed_descriptor_t { * networkstatus that listed it. 0 for "never listed in a consensus or * status, so far as we know." */ time_t last_listed_as_valid_until; -#ifdef TRACK_SERVED_TIME - /** The last time we served anybody this descriptor. Used for internal - * testing to see whether we're holding on to descriptors too long. */ - time_t last_served_at; /*XXXX remove if not useful. */ -#endif /* If true, we do not ever try to save this object in the cache. */ unsigned int do_not_cache : 1; /* If true, this item is meant to represent an extrainfo. */ @@ -1465,59 +1739,49 @@ typedef struct { char *contact_info; /**< Declared contact info for this router. */ unsigned int is_hibernating:1; /**< Whether the router claims to be * hibernating */ - unsigned int has_old_dnsworkers:1; /**< Whether the router is using - * dnsworker code. */ - unsigned int caches_extra_info:1; /**< Whether the router caches and serves - * extrainfo documents. */ - unsigned int allow_single_hop_exits:1; /**< Whether the router allows - * single hop exits. */ - - /* local info */ - 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?) - */ - unsigned int is_named:1; /**< Do we believe the nickname that this OR gives - * us? */ - 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? */ - unsigned int is_exit:1; /**< Do we think this is an OK exit? */ - unsigned int is_bad_exit:1; /**< Do we think this exit is censored, borked, - * or otherwise nasty? */ - unsigned int is_bad_directory:1; /**< Do we think this directory is junky, - * underpowered, or otherwise useless? */ + unsigned int caches_extra_info:1; /**< Whether the router says it caches and + * serves extrainfo documents. */ + unsigned int allow_single_hop_exits:1; /**< Whether the router says + * it allows single hop exits. */ + unsigned int wants_to_be_hs_dir:1; /**< True iff this router claims to be * a hidden service directory. */ - unsigned int is_hs_dir:1; /**< True iff this router is a hidden service - * directory according to the authorities. */ unsigned int policy_is_reject_star:1; /**< True iff the exit policy for this * router rejects everything. */ /** True if, after we have added this router, we should re-launch * tests for it. */ unsigned int needs_retest_if_added:1; -/** Tor can use this router for general positions in circuits. */ +/** 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 + * uploaded it. */ #define ROUTER_PURPOSE_GENERAL 0 -/** Tor should avoid using this router for circuit-building. */ +/** Tor should avoid using this router for circuit-building: we got it + * from a crontroller. If the controller wants to use it, it'll have to + * ask for it by identity. */ #define ROUTER_PURPOSE_CONTROLLER 1 -/** Tor should use this router only for bridge positions in circuits. */ +/** Tor should use this router only for bridge positions in circuits: we got + * it via a directory request from the bridge itself, or a bridge + * authority. x*/ #define ROUTER_PURPOSE_BRIDGE 2 /** Tor should not use this router; it was marked in cached-descriptors with * a purpose we didn't recognize. */ #define ROUTER_PURPOSE_UNKNOWN 255 - uint8_t purpose; /** What positions in a circuit is this router good for? */ + /* In what way did we find out about this router? One of ROUTER_PURPOSE_*. + * Routers of different purposes are kept segregated and used for different + * 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; - /** According to the geoip db what country is this router in? */ - country_t country; + } routerinfo_t; /** Information needed to keep and cache a signed extra-info document. */ @@ -1543,8 +1807,9 @@ typedef struct routerstatus_t { * has. */ char identity_digest[DIGEST_LEN]; /**< Digest of the router's identity * key. */ - char descriptor_digest[DIGEST_LEN]; /**< Digest of the router's most recent - * descriptor. */ + /** Digest of the router's most recent descriptor or microdescriptor. + * If it's a descriptor, we only use the first DIGEST_LEN bytes. */ + char descriptor_digest[DIGEST256_LEN]; uint32_t addr; /**< IPv4 address for this router. */ uint16_t or_port; /**< OR port for this router. */ uint16_t dir_port; /**< Directory port for this router. */ @@ -1552,7 +1817,11 @@ typedef struct routerstatus_t { 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. */ unsigned int is_fast:1; /**< True iff this router has good bandwidth. */ - unsigned int is_running:1; /**< True iff this router is up. */ + /** True iff this router is called 'running' in the consensus. We give it + * this funny name so that we don't accidentally use this bit as a view of + * whether we think the router is *currently* running. If that's what you + * want to know, look at is_running in node_t. */ + unsigned int is_flagged_running:1; unsigned int is_named:1; /**< True iff "nickname" belongs to this router. */ unsigned int is_unnamed:1; /**< True iff "nickname" belongs to another * router. */ @@ -1583,6 +1852,12 @@ typedef struct routerstatus_t { /** 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; unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ @@ -1604,15 +1879,31 @@ typedef struct routerstatus_t { * from this authority.) Applies in v2 networkstatus document only. */ unsigned int need_to_mirror:1; - unsigned int name_lookup_warned:1; /**< Have we warned the user for referring - * to this (unnamed) router by nickname? - */ time_t last_dir_503_at; /**< When did this router last tell us that it * was too busy to serve directory info? */ download_status_t dl_status; } routerstatus_t; +/** A single entry in a parsed policy summary, describing a range of ports. */ +typedef struct short_policy_entry_t { + uint16_t min_port, max_port; +} short_policy_entry_t; + +/** A short_poliy_t is the parsed version of a policy summary. */ +typedef struct short_policy_t { + /** True if the members of 'entries' are port ranges to accept; false if + * they are port ranges to reject */ + unsigned int is_accept : 1; + /** The actual number of values in 'entries'. */ + unsigned int n_entries : 31; + /** An array of 0 or more short_policy_entry_t values, each describing a + * range of ports that this policy accepts or rejects (depending on the + * value of is_accept). + */ + short_policy_entry_t entries[FLEXIBLE_ARRAY_MEMBER]; +} short_policy_t; + /** A microdescriptor is the smallest amount of information needed to build a * circuit through a router. They are generated by the directory authorities, * using information from the uploaded routerinfo documents. They are not @@ -1632,6 +1923,11 @@ typedef struct microdesc_t { saved_location_t saved_location : 3; /** If true, do not attempt to cache this microdescriptor on disk. */ unsigned int no_save : 1; + /** If true, this microdesc has an entry in the microdesc_map */ + unsigned int held_in_map : 1; + /** Reference count: how many node_ts have a reference to this microdesc? */ + unsigned int held_by_nodes; + /** If saved_location == SAVED_IN_CACHE, this field holds the offset of the * microdescriptor in the cache. */ off_t off; @@ -1654,15 +1950,83 @@ typedef struct microdesc_t { crypto_pk_env_t *onion_pkey; /** As routerinfo_t.family */ smartlist_t *family; - /** Encoded exit policy summary */ - char *exitsummary; /**< exit policy summary - - * XXX this probably should not stay a string. */ + /** Exit policy summary */ + short_policy_t *exit_policy; } microdesc_t; +/** A node_t represents a Tor router. + * + * Specifically, a node_t is a Tor router as we are using it: a router that + * we are considering for circuits, connections, and so on. A node_t is a + * thin wrapper around the routerstatus, routerinfo, and microdesc for a + * single wrapper, and provides a consistent interface for all of them. + * + * Also, a node_t has mutable state. While a routerinfo, a routerstatus, + * and a microdesc have[*] only the information read from a router + * descriptor, a consensus entry, and a microdescriptor (respectively)... + * a node_t has flags based on *our own current opinion* of the node. + * + * [*] Actually, there is some leftover information in each that is mutable. + * We should try to excise that. + */ +typedef struct node_t { + /* Indexing information */ + + /** Used to look up the node_t by its identity digest. */ + HT_ENTRY(node_t) ht_ent; + /** Position of the node within the list of nodes */ + int nodelist_idx; + + /** The identity digest of this node_t. No more than one node_t per + * identity may exist at a time. */ + char identity[DIGEST_LEN]; + + microdesc_t *md; + routerinfo_t *ri; + routerstatus_t *rs; + + /* local info: copied from routerstatus, then possibly frobbed based + * on experience. Authorities set this stuff directly. */ + + 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?) + */ + 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? */ + unsigned int is_exit:1; /**< Do we think this is an OK exit? */ + unsigned int is_bad_exit:1; /**< Do we think this exit is censored, borked, + * or otherwise nasty? */ + unsigned int is_bad_directory:1; /**< Do we think this directory is junky, + * underpowered, or otherwise useless? */ + unsigned int is_hs_dir:1; /**< True iff this router is a hidden service + * directory according to the authorities. */ + + /* Local info: warning state. */ + + unsigned int name_lookup_warned:1; /**< Have we warned the user for referring + * to this (unnamed) router by nickname? + */ + + /** Local info: we treat this node as if it rejects everything */ + unsigned int rejects_all:1; + + /* Local info: derived. */ + + /** According to the geoip db what country is this router in? */ + country_t country; +} node_t; + /** How many times will we try to download a router's descriptor before giving * up? */ #define MAX_ROUTERDESC_DOWNLOAD_FAILURES 8 +/** How many times will we try to download a microdescriptor before giving + * up? */ +#define MAX_MICRODESC_DOWNLOAD_FAILURES 8 + /** Contents of a v2 (non-consensus, non-vote) network status object. */ typedef struct networkstatus_v2_t { /** When did we receive the network-status document? */ @@ -1776,9 +2140,6 @@ typedef enum { FLAV_MICRODESC = 1, } consensus_flavor_t; -/** Which consensus flavor do we actually want to use to build circuits? */ -#define USABLE_CONSENSUS_FLAVOR FLAV_NS - /** How many different consensus flavors are there? */ #define N_CONSENSUS_FLAVORS ((int)(FLAV_MICRODESC)+1) @@ -1948,24 +2309,33 @@ typedef struct authority_cert_t { uint8_t is_cross_certified; } authority_cert_t; -/** Bitfield enum type listing types of directory authority/directory - * server. */ +/** Bitfield enum type listing types of information that directory authorities + * can be authoritative about, and that directory caches may or may not cache. + * + * Note that the granularity here is based on authority granularity and on + * cache capabilities. Thus, one particular bit may correspond in practice to + * a few types of directory info, so long as every authority that pronounces + * officially about one of the types prounounces officially about all of them, + * and so long as every cache that caches one of them caches all of them. + */ typedef enum { - NO_AUTHORITY = 0, + NO_DIRINFO = 0, /** Serves/signs v1 directory information: Big lists of routers, and short * routerstatus documents. */ - V1_AUTHORITY = 1 << 0, + V1_DIRINFO = 1 << 0, /** Serves/signs v2 directory information: i.e. v2 networkstatus documents */ - V2_AUTHORITY = 1 << 1, + V2_DIRINFO = 1 << 1, /** Serves/signs v3 directory information: votes, consensuses, certs */ - V3_AUTHORITY = 1 << 2, + V3_DIRINFO = 1 << 2, /** Serves hidden service descriptors. */ - HIDSERV_AUTHORITY = 1 << 3, + HIDSERV_DIRINFO = 1 << 3, /** Serves bridge descriptors. */ - BRIDGE_AUTHORITY = 1 << 4, - /** Serves extrainfo documents. (XXX Not precisely an authority type)*/ - EXTRAINFO_CACHE = 1 << 5, -} authority_type_t; + BRIDGE_DIRINFO = 1 << 4, + /** Serves extrainfo documents. */ + EXTRAINFO_DIRINFO=1 << 5, + /** Serves microdescriptors. */ + MICRODESC_DIRINFO=1 << 6, +} dirinfo_type_t; #define CRYPT_PATH_MAGIC 0x70127012u @@ -2038,15 +2408,15 @@ typedef struct { /** How to extend to the planned exit node. */ extend_info_t *chosen_exit; /** Whether every node in the circ must have adequate uptime. */ - int need_uptime; + unsigned int need_uptime : 1; /** Whether every node in the circ must have adequate capacity. */ - int need_capacity; + unsigned int need_capacity : 1; /** Whether the last hop was picked with exiting in mind. */ - int is_internal; - /** Did we pick this as a one-hop tunnel (not safe for other conns)? - * These are for encrypted connections that exit to this router, not + unsigned int is_internal : 1; + /** Did we pick this as a one-hop tunnel (not safe for other streams)? + * These are for encrypted dir conns that exit to this router, not * for arbitrary exits from the circuit. */ - int onehop_tunnel; + unsigned int onehop_tunnel : 1; /** The crypt_path_t to append after rendezvous: used for rendezvous. */ crypt_path_t *pending_final_cpath; /** How many times has building a circuit for this task failed? */ @@ -2144,7 +2514,10 @@ typedef struct circuit_t { * length ONIONSKIN_CHALLENGE_LEN. */ char *n_conn_onionskin; - struct timeval timestamp_created; /**< When was the circuit created? */ + /** 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. */ + 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 @@ -2242,6 +2615,55 @@ typedef struct origin_circuit_t { /* XXXX NM This can get re-used after 2**32 circuits. */ uint32_t global_identifier; + /** True if we have associated one stream to this circuit, thereby setting + * the isolation paramaters for this circuit. Note that this doesn't + * necessarily mean that we've <em>attached</em> any streams to the circuit: + * we may only have marked up this circuit during the launch process. + */ + unsigned int isolation_values_set : 1; + /** True iff any stream has <em>ever</em> been attached to this circuit. + * + * In a better world we could use timestamp_dirty for this, but + * timestamp_dirty is far too overloaded at the moment. + */ + unsigned int isolation_any_streams_attached : 1; + + /** A bitfield of ISO_* flags for every isolation field such that this + * circuit has had streams with more than one value for that field + * attached to it. */ + uint8_t isolation_flags_mixed; + + /** @name Isolation parameters + * + * If any streams have been associated with this circ (isolation_values_set + * == 1), and all streams associated with the circuit have had the same + * value for some field ((isolation_flags_mixed & ISO_FOO) == 0), then these + * elements hold the value for that field. + * + * Note again that "associated" is not the same as "attached": we + * preliminarily associate streams with a circuit while the circuit is being + * launched, so that we can tell whether we need to launch more circuits. + * + * @{ + */ + uint8_t client_proto_type; + uint8_t client_proto_socksver; + uint16_t dest_port; + tor_addr_t client_addr; + char *dest_address; + int session_group; + unsigned nym_epoch; + size_t socks_username_len; + uint8_t socks_password_len; + /* Note that the next two values are NOT NUL-terminated; see + socks_username_len and socks_password_len for their lengths. */ + char *socks_username; + char *socks_password; + /** Global identifier for the first stream attached here; used by + * ISO_STREAM. */ + uint64_t associated_isolated_stream_global_id; + /**@}*/ + } origin_circuit_t; /** An or_circuit_t holds information needed to implement a circuit at an @@ -2360,6 +2782,60 @@ typedef enum invalid_router_usage_t { #define MIN_CONSTRAINED_TCP_BUFFER 2048 #define MAX_CONSTRAINED_TCP_BUFFER 262144 /* 256k */ +/** @name Isolation flags + + Ways to isolate client streams + + @{ +*/ +/** Isolate based on destination port */ +#define ISO_DESTPORT (1u<<0) +/** Isolate based on destination address */ +#define ISO_DESTADDR (1u<<1) +/** Isolate based on SOCKS authentication */ +#define ISO_SOCKSAUTH (1u<<2) +/** Isolate based on client protocol choice */ +#define ISO_CLIENTPROTO (1u<<3) +/** Isolate based on client address */ +#define ISO_CLIENTADDR (1u<<4) +/** Isolate based on session group (always on). */ +#define ISO_SESSIONGRP (1u<<5) +/** Isolate based on newnym epoch (always on). */ +#define ISO_NYM_EPOCH (1u<<6) +/** Isolate all streams (Internal only). */ +#define ISO_STREAM (1u<<7) +/**@}*/ + +/** Default isolation level for ports. */ +#define ISO_DEFAULT (ISO_CLIENTADDR|ISO_SOCKSAUTH|ISO_SESSIONGRP|ISO_NYM_EPOCH) + +/** Indicates that we haven't yet set a session group on a port_cfg_t. */ +#define SESSION_GROUP_UNSET -1 +/** Session group reserved for directory connections */ +#define SESSION_GROUP_DIRCONN -2 +/** Session group reserved for resolve requests launched by a controller */ +#define SESSION_GROUP_CONTROL_RESOLVE -3 +/** First automatically allocated session group number */ +#define SESSION_GROUP_FIRST_AUTO -4 + +/** Configuration for a single port that we're listening on. */ +typedef struct port_cfg_t { + tor_addr_t addr; /**< The actual IP to listen on, if !is_unix_addr. */ + int port; /**< The configured port, or CFG_AUTO_PORT to tell Tor to pick its + * own port. */ + uint8_t type; /**< One of CONN_TYPE_*_LISTENER */ + unsigned is_unix_addr : 1; /**< True iff this is an AF_UNIX address. */ + + /* Client port types (socks, dns, trans, natd) only: */ + uint8_t isolation_flags; /**< Zero or more isolation flags */ + int session_group; /**< A session group, or -1 if this port is not in a + * session group. */ + + /* Unix sockets only: */ + /** Path for an AF_UNIX address */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; +} port_cfg_t; + /** A linked list of lines in a config file. */ typedef struct config_line_t { char *key; @@ -2386,6 +2862,7 @@ typedef struct { config_line_t *Logs; /**< New-style list of configuration lines * for logs */ + int LogTimeGranularity; /**< Log resolution in milliseconds. */ int LogMessageDomains; /**< Boolean: Should we log the domain(s) in which * each log message occurs? */ @@ -2454,16 +2931,17 @@ typedef struct { char *User; /**< Name of user to run Tor as. */ char *Group; /**< Name of group to run Tor as. */ int ORPort; /**< Port to listen on for OR connections. */ - int SocksPort; /**< Port to listen on for SOCKS connections. */ - /** Port to listen on for transparent pf/netfilter connections. */ - int TransPort; - int NATDPort; /**< Port to listen on for transparent natd connections. */ + config_line_t *SocksPort; /**< Ports to listen on for SOCKS connections. */ + /** Ports to listen on for transparent pf/netfilter connections. */ + config_line_t *TransPort; + config_line_t *NATDPort; /**< Ports to listen on for transparent natd + * connections. */ int ControlPort; /**< Port to listen on for control connections. */ config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on * for control connections. */ int ControlSocketsGroupWritable; /**< Boolean: Are control sockets g+rw? */ int DirPort; /**< Port to listen on for directory connections. */ - int DNSPort; /**< Port to listen on for DNS requests. */ + config_line_t *DNSPort; /**< Port to listen on for DNS requests. */ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ int AuthoritativeDir; /**< Boolean: is this an authoritative directory? */ int V1AuthoritativeDir; /**< Boolean: is this an authoritative directory @@ -2491,6 +2969,12 @@ typedef struct { int UseBridges; /**< Boolean: should we start all circuits with a bridge? */ config_line_t *Bridges; /**< List of bootstrap bridge addresses. */ + config_line_t *ClientTransportPlugin; /**< List of client + transport plugins. */ + + config_line_t *ServerTransportPlugin; /**< List of client + transport plugins. */ + int BridgeRelay; /**< Boolean: are we acting as a bridge relay? We make * this explicit so we can change how we behave in the * future. */ @@ -2505,8 +2989,8 @@ typedef struct { /** To what authority types do we publish our descriptor? Choices are * "v1", "v2", "v3", "bridge", or "". */ smartlist_t *PublishServerDescriptor; - /** An authority type, derived from PublishServerDescriptor. */ - authority_type_t _PublishServerDescriptor; + /** A bitfield of authority types, derived from 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? */ @@ -2538,12 +3022,10 @@ typedef struct { uint64_t ConstrainedSockSize; /**< Size of constrained buffers. */ /** Whether we should drop exit streams from Tors that we don't know are - * relays. One of "0" (never refuse), "1" (always refuse), or "auto" (do + * relays. One of "0" (never refuse), "1" (always refuse), or "-1" (do * what the consensus says, defaulting to 'refuse' if the consensus says * nothing). */ - char *RefuseUnknownExits; - /** Parsed version of RefuseUnknownExits. -1 for auto. */ - int RefuseUnknownExits_; + int RefuseUnknownExits; /** Application ports that require all nodes in circ to have sufficient * uptime. */ @@ -2614,6 +3096,9 @@ typedef struct { * authorizations for hidden services */ char *ContactInfo; /**< Contact info to be published in the directory. */ + int HeartbeatPeriod; /**< Log heartbeat messages after this many seconds + * have passed. */ + char *HTTPProxy; /**< hostname[:port] to use as http proxy, if any. */ tor_addr_t HTTPProxyAddr; /**< Parsed IPv4 addr for http proxy, if any. */ uint16_t HTTPProxyPort; /**< Parsed port for http proxy, if any. */ @@ -2651,7 +3136,8 @@ typedef struct { char *MyFamily; /**< Declared family for this OR. */ config_line_t *NodeFamilies; /**< List of config lines for - * node families */ + * node families */ + smartlist_t *NodeFamilySets; /**< List of parsed NodeFamilies values. */ config_line_t *AuthDirBadDir; /**< Address policy for descriptors to * mark as bad dir mirrors. */ config_line_t *AuthDirBadExit; /**< Address policy for descriptors to @@ -2721,6 +3207,8 @@ typedef struct { * log whether it was DNS-leaking or not? */ int HardwareAccel; /**< Boolean: Should we enable OpenSSL hardware * acceleration where available? */ + /** Token Bucket Refill resolution in milliseconds. */ + int TokenBucketRefillInterval; char *AccelName; /**< Optional hardware acceleration engine name. */ char *AccelDir; /**< Optional hardware acceleration engine search dir. */ int UseEntryGuards; /**< Boolean: Do we try to enter from a smallish number @@ -2755,7 +3243,9 @@ typedef struct { /** Boolean: if set, we start even if our resolv.conf file is missing * or broken. */ int ServerDNSAllowBrokenConfig; - + /** Boolean: if set, then even connections to private addresses will get + * rate-limited. */ + int CountPrivateBandwidth; smartlist_t *ServerDNSTestAddresses; /**< A list of addresses that definitely * should be resolvable. Used for * testing our DNS server. */ @@ -2765,6 +3255,10 @@ typedef struct { * possible. */ int PreferTunneledDirConns; /**< If true, avoid dirservers that don't * support BEGIN_DIR, when possible. */ + int PortForwarding; /**< If true, use NAT-PMP or UPnP to automatically + * forward the DirPort and ORPort on the NAT device */ + char *PortForwardingHelper; /** < Filename or full path of the port + forwarding helper executable */ int AllowNonRFC953Hostnames; /**< If true, we allow connections to hostnames * with weird characters. */ /** If true, we try resolving hostnames with weird characters. */ @@ -2802,6 +3296,9 @@ typedef struct { /** If true, the user wants us to collect statistics on port usage. */ int ExitPortStatistics; + /** If true, the user wants us to collect connection statistics. */ + int ConnDirectionStatistics; + /** If true, the user wants us to collect cell statistics. */ int CellStatistics; @@ -2898,16 +3395,35 @@ typedef struct { */ double CircuitPriorityHalflife; + /** If true, do not enable IOCP on windows with bufferevents, even if + * we think we could. */ + int DisableIOCP; + /** For testing only: will go away in 0.2.3.x. */ + 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; + /** If 1, we try to use microdescriptors to build circuits. If 0, we don't. + * If -1, Tor decides. */ + int UseMicrodescriptors; + /** File where we should write the ControlPort. */ char *ControlPortWriteToFile; /** Should that file be group-readable? */ int ControlPortFileGroupReadable; +#define MAX_MAX_CLIENT_CIRCUITS_PENDING 1024 + /** Maximum number of non-open general-purpose origin circuits to allow at + * once. */ + int MaxClientCircuitsPending; + + /** If 1, we always send optimistic data when it's supported. If 0, we + * never use it. If -1, we do what the consensus says. */ + int OptimisticData; + } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -2934,6 +3450,8 @@ typedef struct { /** A list of Entry Guard-related configuration lines. */ config_line_t *EntryGuards; + config_line_t *TransportProxies; + /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the * bandwidth usage. The "Interval" fields hold the granularity, in seconds, @@ -2986,6 +3504,8 @@ static INLINE void or_state_mark_dirty(or_state_t *state, time_t when) #define MAX_SOCKS_REPLY_LEN 1024 #define MAX_SOCKS_ADDR_LEN 256 +#define SOCKS_NO_AUTH 0x00 +#define SOCKS_USER_PASS 0x02 /** Please open a TCP connection to this addr:port. */ #define SOCKS_COMMAND_CONNECT 0x01 @@ -3005,10 +3525,17 @@ struct socks_request_t { /** Which version of SOCKS did the client use? One of "0, 4, 5" -- where * 0 means that no socks handshake ever took place, and this is just a * stub connection (e.g. see connection_ap_make_link()). */ - char socks_version; - int command; /**< What is this stream's goal? One from the above list. */ + uint8_t socks_version; + /** If using socks5 authentication, which authentication type did we + * negotiate? currently we support 0 (no authentication) and 2 + * (username/password). */ + uint8_t auth_type; + /** What is this stream's goal? One of the SOCKS_COMMAND_* values */ + uint8_t command; + /** Which kind of listener created this stream? */ + uint8_t listener_type; size_t replylen; /**< Length of <b>reply</b>. */ - char reply[MAX_SOCKS_REPLY_LEN]; /**< Write an entry into this string if + uint8_t reply[MAX_SOCKS_REPLY_LEN]; /**< Write an entry into this string if * we want to specify our own socks reply, * rather than using the default socks4 or * socks5 socks reply. We use this for the @@ -3020,10 +3547,21 @@ struct socks_request_t { unsigned int has_finished : 1; /**< Has the SOCKS handshake finished? Used to * make sure we send back a socks reply for * every connection. */ + unsigned int got_auth : 1; /**< Have we received any authentication data? */ + + /** Number of bytes in username; 0 if username is NULL */ + size_t usernamelen; + /** Number of bytes in password; 0 if password is NULL */ + uint8_t passwordlen; + /** The negotiated username value if any (for socks5), or the entire + * authentication string (for socks4). This value is NOT nul-terminated; + * see usernamelen for its length. */ + char *username; + /** The negotiated password value if any (for socks5). This value is NOT + * nul-terminated; see passwordlen for its length. */ + char *password; }; -/* all the function prototypes go here */ - /********************************* circuitbuild.c **********************/ /** How many hops does a general-purpose circuit have by default? */ @@ -3436,7 +3974,7 @@ typedef enum { ADDR_POLICY_PROBABLY_ACCEPTED=1, /** Part of the address was unknown, but as far as we can tell, it was * rejected. */ - ADDR_POLICY_PROBABLY_REJECTED=2 + ADDR_POLICY_PROBABLY_REJECTED=2, } addr_policy_result_t; /********************************* rephist.c ***************************/ @@ -3466,6 +4004,11 @@ typedef struct rend_encoded_v2_service_descriptor_t { char *desc_str; /**< Descriptor string. */ } rend_encoded_v2_service_descriptor_t; +/** The maximum number of non-circuit-build-timeout failures a hidden + * service client will tolerate while trying to build a circuit to an + * introduction point. See also rend_intro_point_t.unreachable_count. */ +#define MAX_INTRO_POINT_REACHABILITY_FAILURES 5 + /** Introduction point information. Used both in rend_service_t (on * the service side) and in rend_service_descriptor_t (on both the * client and service side). */ @@ -3473,6 +4016,18 @@ typedef struct rend_intro_point_t { extend_info_t *extend_info; /**< Extend info of this introduction point. */ crypto_pk_env_t *intro_key; /**< Introduction key that replaces the service * key, if this descriptor is V2. */ + + /** (Client side only) Flag indicating that a timeout has occurred + * after sending an INTRODUCE cell to this intro point. After a + * timeout, an intro point should not be tried again during the same + * hidden service connection attempt, but it may be tried again + * during a future connection attempt. */ + unsigned int timed_out : 1; + + /** (Client side only) The number of times we have failed to build a + * circuit to this intro point for some reason other than our + * circuit-build timeout. See also MAX_INTRO_POINT_REACHABILITY_FAILURES. */ + unsigned int unreachable_count : 3; } rend_intro_point_t; /** Information used to connect to a hidden service. Used on both the @@ -3524,7 +4079,7 @@ typedef struct trusted_dir_server_t { unsigned int has_accepted_serverdesc:1; /** What kind of authority is this? (Bitfield.) */ - authority_type_t type; + dirinfo_type_t type; download_status_t v2_ns_dl_status; /**< Status of downloading this server's * v2 network status. */ @@ -3570,6 +4125,8 @@ typedef struct trusted_dir_server_t { * fetches to _any_ single directory server.] */ #define PDS_NO_EXISTING_SERVERDESC_FETCH (1<<3) +#define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4) + #define _PDS_PREFER_TUNNELED_DIR_CONNS (1<<16) /** Possible ways to weight routers when choosing one randomly. See @@ -3587,7 +4144,8 @@ typedef enum { CRN_NEED_GUARD = 1<<2, CRN_ALLOW_INVALID = 1<<3, /* XXXX not used, apparently. */ - CRN_WEIGHT_AS_EXIT = 1<<5 + CRN_WEIGHT_AS_EXIT = 1<<5, + CRN_NEED_DESC = 1<<6 } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ diff --git a/src/or/policies.c b/src/or/policies.c index c87036013d..40e5277478 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -11,6 +11,7 @@ #include "or.h" #include "config.h" #include "dirserv.h" +#include "nodelist.h" #include "policies.h" #include "routerparse.h" #include "ht.h" @@ -82,15 +83,15 @@ policy_expand_private(smartlist_t **policy) continue; } for (i = 0; private_nets[i]; ++i) { - addr_policy_t policy; - memcpy(&policy, p, sizeof(addr_policy_t)); - policy.is_private = 0; - policy.is_canonical = 0; - if (tor_addr_parse_mask_ports(private_nets[i], &policy.addr, - &policy.maskbits, &port_min, &port_max)<0) { + addr_policy_t newpolicy; + 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, + &newpolicy.maskbits, &port_min, &port_max)<0) { tor_assert(0); } - smartlist_add(tmp, addr_policy_get_canonical_entry(&policy)); + smartlist_add(tmp, addr_policy_get_canonical_entry(&newpolicy)); } addr_policy_free(p); }); @@ -163,7 +164,7 @@ parse_addr_policy(config_line_t *cfg, smartlist_t **dest, static int parse_reachable_addresses(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int ret = 0; if (options->ReachableDirAddresses && @@ -261,7 +262,7 @@ fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port) /** Return true iff we think our firewall will let us make an OR connection to * <b>ri</b>. */ int -fascist_firewall_allows_or(routerinfo_t *ri) +fascist_firewall_allows_or(const routerinfo_t *ri) { /* XXXX proposal 118 */ tor_addr_t addr; @@ -269,6 +270,22 @@ fascist_firewall_allows_or(routerinfo_t *ri) return fascist_firewall_allows_address_or(&addr, ri->or_port); } +/** Return true iff we think our firewall will let us make an OR connection to + * <b>node</b>. */ +int +fascist_firewall_allows_node(const node_t *node) +{ + if (node->ri) { + return fascist_firewall_allows_or(node->ri); + } else if (node->rs) { + tor_addr_t addr; + tor_addr_from_ipv4h(&addr, node->rs->addr); + return fascist_firewall_allows_address_or(&addr, node->rs->or_port); + } else { + return 1; + } +} + /** Return true iff we think our firewall will let us make a directory * connection to addr:port. */ int @@ -339,7 +356,7 @@ authdir_policy_badexit_address(uint32_t addr, uint16_t port) * options in <b>options</b>, return -1 and set <b>msg</b> to a newly * allocated description of the error. Else return 0. */ int -validate_addr_policies(or_options_t *options, char **msg) +validate_addr_policies(const or_options_t *options, char **msg) { /* XXXX Maybe merge this into parse_policies_from_options, to make sure * that the two can't go out of sync. */ @@ -423,7 +440,7 @@ load_policy_from_option(config_line_t *config, smartlist_t **policy, /** Set all policies based on <b>options</b>, which should have been validated * first by validate_addr_policies. */ int -policies_parse_from_options(or_options_t *options) +policies_parse_from_options(const or_options_t *options) { int ret = 0; if (load_policy_from_option(options->SocksPolicy, &socks_policy, -1) < 0) @@ -553,18 +570,6 @@ addr_policy_get_canonical_entry(addr_policy_t *e) return found->policy; } -/** As compare_tor_addr_to_addr_policy, but instead of a tor_addr_t, takes - * in host order. */ -addr_policy_result_t -compare_addr_to_addr_policy(uint32_t addr, uint16_t port, - const smartlist_t *policy) -{ - /*XXXX deprecate this function when possible. */ - tor_addr_t a; - tor_addr_from_ipv4h(&a, addr); - return compare_tor_addr_to_addr_policy(&a, port, policy); -} - /** Helper for compare_tor_addr_to_addr_policy. Implements the case where * addr and port are both known. */ static addr_policy_result_t @@ -684,7 +689,7 @@ compare_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, if (!policy) { /* no policy? accept all. */ return ADDR_POLICY_ACCEPTED; - } else if (tor_addr_is_null(addr)) { + } else if (addr == NULL || tor_addr_is_null(addr)) { tor_assert(port != 0); return compare_unknown_tor_addr_to_addr_policy(port, policy); } else if (port == 0) { @@ -866,15 +871,11 @@ policies_exit_policy_append_reject_star(smartlist_t **dest) append_exit_policy_string(dest, "reject *:*"); } -/** Replace the exit policy of <b>r</b> with reject *:*. */ +/** Replace the exit policy of <b>node</b> with reject *:* */ void -policies_set_router_exitpolicy_to_reject_all(routerinfo_t *r) +policies_set_node_exitpolicy_to_reject_all(node_t *node) { - addr_policy_t *item; - addr_policy_list_free(r->exit_policy); - r->exit_policy = smartlist_create(); - item = router_parse_addr_policy_item_from_string("reject *:*", -1); - smartlist_add(r->exit_policy, item); + node->rejects_all = 1; } /** Return 1 if there is at least one /8 subnet in <b>policy</b> that @@ -1085,7 +1086,7 @@ policy_summary_split(smartlist_t *summary, int start_at_index; int i = 0; - /* XXXX Do a binary search if run time matters */ + while (AT(i)->prt_max < prt_min) i++; if (AT(i)->prt_min != prt_min) { @@ -1298,6 +1299,186 @@ policy_summarize(smartlist_t *policy) return result; } +/** Convert a summarized policy string into a short_policy_t. Return NULL + * if the string is not well-formed. */ +short_policy_t * +parse_short_policy(const char *summary) +{ + const char *orig_summary = summary; + short_policy_t *result; + int is_accept; + int n_entries; + short_policy_entry_t entries[MAX_EXITPOLICY_SUMMARY_LEN]; /* overkill */ + const char *next; + + if (!strcmpstart(summary, "accept ")) { + is_accept = 1; + summary += strlen("accept "); + } else if (!strcmpstart(summary, "reject ")) { + is_accept = 0; + summary += strlen("reject "); + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Unrecognized policy summary keyword"); + return NULL; + } + + n_entries = 0; + for ( ; *summary; summary = next) { + const char *comma = strchr(summary, ','); + unsigned low, high; + char dummy; + char ent_buf[32]; + + next = comma ? comma+1 : strchr(summary, '\0'); + + if (n_entries == MAX_EXITPOLICY_SUMMARY_LEN) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Impossibly long policy summary %s", + escaped(orig_summary)); + return NULL; + } + + if (! TOR_ISDIGIT(*summary) || next-summary > (int)(sizeof(ent_buf)-1)) { + /* unrecognized entry format. skip it. */ + continue; + } + if (next-summary < 2) { + /* empty; skip it. */ + continue; + } + + memcpy(ent_buf, summary, next-summary-1); + ent_buf[next-summary-1] = '\0'; + + if (tor_sscanf(ent_buf, "%u-%u%c", &low, &high, &dummy) == 2) { + if (low<1 || low>65535 || high<1 || high>65535) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found bad entry in policy summary %s", escaped(orig_summary)); + return NULL; + } + } else if (tor_sscanf(ent_buf, "%u%c", &low, &dummy) == 1) { + if (low<1 || low>65535) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found bad entry in policy summary %s", escaped(orig_summary)); + return NULL; + } + high = low; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR,"Found bad entry in policy summary %s", + escaped(orig_summary)); + return NULL; + } + + entries[n_entries].min_port = low; + entries[n_entries].max_port = high; + n_entries++; + } + + if (n_entries == 0) { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Found no port-range entries in summary %s", escaped(orig_summary)); + return NULL; + } + + { + size_t size = STRUCT_OFFSET(short_policy_t, entries) + + sizeof(short_policy_entry_t)*(n_entries); + result = tor_malloc_zero(size); + + tor_assert( (char*)&result->entries[n_entries-1] < ((char*)result)+size); + } + + result->is_accept = is_accept; + result->n_entries = n_entries; + memcpy(result->entries, entries, sizeof(short_policy_entry_t)*n_entries); + return result; +} + +/** Release all storage held in <b>policy</b>. */ +void +short_policy_free(short_policy_t *policy) +{ + tor_free(policy); +} + +/** See whether the <b>addr</b>:<b>port</b> address is likely to be accepted + * or rejected by the summarized policy <b>policy</b>. Return values are as + * for compare_tor_addr_to_addr_policy. Unlike the regular addr_policy + * functions, requires the <b>port</b> be specified. */ +addr_policy_result_t +compare_tor_addr_to_short_policy(const tor_addr_t *addr, uint16_t port, + const short_policy_t *policy) +{ + int i; + int found_match = 0; + int accept; + (void)addr; + + tor_assert(port != 0); + + if (addr && tor_addr_is_null(addr)) + addr = NULL; /* Unspec means 'no address at all,' in this context. */ + + if (addr && (tor_addr_is_internal(addr, 0) || + tor_addr_is_loopback(addr))) + return ADDR_POLICY_REJECTED; + + for (i=0; i < policy->n_entries; ++i) { + const short_policy_entry_t *e = &policy->entries[i]; + if (e->min_port <= port && port <= e->max_port) { + found_match = 1; + break; + } + } + + if (found_match) + accept = policy->is_accept; + else + accept = ! policy->is_accept; + + /* ???? are these right? */ + if (accept) + return ADDR_POLICY_PROBABLY_ACCEPTED; + else + return ADDR_POLICY_REJECTED; +} + +/** Return true iff <b>policy</b> seems reject all ports */ +int +short_policy_is_reject_star(const short_policy_t *policy) +{ + /* This doesn't need to be as much on the lookout as policy_is_reject_star, + * since policy summaries are from the consensus or from consensus + * microdescs. + */ + tor_assert(policy); + /* Check for an exact match of "reject 1-65535". */ + return (policy->is_accept == 0 && policy->n_entries == 1 && + policy->entries[0].min_port == 1 && + policy->entries[0].max_port == 65535); +} + +/** Decides whether addr:port is probably or definitely accepted or rejcted by + * <b>node</b>. See compare_tor_addr_to_addr_policy for details on addr/port + * interpretation. */ +addr_policy_result_t +compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, + const node_t *node) +{ + if (node->rejects_all) + return ADDR_POLICY_REJECTED; + + if (node->ri) + return compare_tor_addr_to_addr_policy(addr, port, node->ri->exit_policy); + 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 + return ADDR_POLICY_PROBABLY_REJECTED; +} + /** Implementation for GETINFO control command: knows the answer for questions * about "exit-policy/..." */ int diff --git a/src/or/policies.h b/src/or/policies.h index b2947c67e7..51716ab0a7 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -19,7 +19,8 @@ int firewall_is_fascist_or(void); int fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port); -int fascist_firewall_allows_or(routerinfo_t *ri); +int fascist_firewall_allows_or(const routerinfo_t *ri); +int fascist_firewall_allows_node(const node_t *node); int fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port); int dir_policy_permits_address(const tor_addr_t *addr); int socks_policy_permits_address(const tor_addr_t *addr); @@ -28,21 +29,23 @@ int authdir_policy_valid_address(uint32_t addr, uint16_t port); int authdir_policy_baddir_address(uint32_t addr, uint16_t port); int authdir_policy_badexit_address(uint32_t addr, uint16_t port); -int validate_addr_policies(or_options_t *options, char **msg); +int validate_addr_policies(const or_options_t *options, char **msg); void policy_expand_private(smartlist_t **policy); -int policies_parse_from_options(or_options_t *options); +int policies_parse_from_options(const or_options_t *options); addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent); int cmp_addr_policies(smartlist_t *a, smartlist_t *b); addr_policy_result_t compare_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, const smartlist_t *policy); -addr_policy_result_t compare_addr_to_addr_policy(uint32_t addr, - uint16_t port, const smartlist_t *policy); + +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 rejectprivate, const char *local_address, int add_default_policy); void policies_exit_policy_append_reject_star(smartlist_t **dest); -void policies_set_router_exitpolicy_to_reject_all(routerinfo_t *exitrouter); +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 getinfo_helper_policies(control_connection_t *conn, @@ -57,5 +60,12 @@ void policies_free_all(void); char *policy_summarize(smartlist_t *policy); +short_policy_t *parse_short_policy(const char *summary); +void short_policy_free(short_policy_t *policy); +int short_policy_is_reject_star(const short_policy_t *policy); +addr_policy_result_t compare_tor_addr_to_short_policy( + const tor_addr_t *addr, uint16_t port, + const short_policy_t *policy); + #endif diff --git a/src/or/relay.c b/src/or/relay.c index d9b9d0c486..51a29a20ee 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -24,6 +24,7 @@ #include "main.h" #include "mempool.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -648,6 +649,7 @@ connection_edge_send_command(edge_connection_t *fromconn, { /* XXXX NM Split this function into a separate versions per circuit type? */ circuit_t *circ; + crypt_path_t *cpath_layer = fromconn->cpath_layer; tor_assert(fromconn); circ = fromconn->on_circuit; @@ -662,7 +664,8 @@ connection_edge_send_command(edge_connection_t *fromconn, if (!circ) { if (fromconn->_base.type == CONN_TYPE_AP) { log_info(LD_APP,"no circ. Closing conn."); - connection_mark_unattached_ap(fromconn, END_STREAM_REASON_INTERNAL); + connection_mark_unattached_ap(EDGE_TO_ENTRY_CONN(fromconn), + END_STREAM_REASON_INTERNAL); } else { log_info(LD_EXIT,"no circ. Closing conn."); fromconn->edge_has_sent_end = 1; /* no circ to send to */ @@ -674,7 +677,7 @@ connection_edge_send_command(edge_connection_t *fromconn, return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, - payload_len, fromconn->cpath_layer); + payload_len, cpath_layer); } /** How many times will I retry a stream that fails due to DNS @@ -702,22 +705,24 @@ edge_reason_is_retriable(int reason) static int connection_ap_process_end_not_open( relay_header_t *rh, cell_t *cell, origin_circuit_t *circ, - edge_connection_t *conn, crypt_path_t *layer_hint) + entry_connection_t *conn, crypt_path_t *layer_hint) { struct in_addr in; - routerinfo_t *exitrouter; + node_t *exitrouter; int reason = *(cell->payload+RELAY_HEADER_SIZE); int control_reason = reason | END_STREAM_REASON_FLAG_REMOTE; + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); (void) layer_hint; /* unused */ if (rh->length > 0 && edge_reason_is_retriable(reason) && - !connection_edge_is_rendezvous_stream(conn) /* avoid retry if rend */ - ) { + /* avoid retry if rend */ + !connection_edge_is_rendezvous_stream(edge_conn)) { + const char *chosen_exit_digest = + circ->build_state->chosen_exit->identity_digest; log_info(LD_APP,"Address '%s' refused due to '%s'. Considering retrying.", safe_str(conn->socks_request->address), stream_end_reason_to_string(reason)); - exitrouter = - router_get_by_digest(circ->build_state->chosen_exit->identity_digest); + exitrouter = node_get_mutable_by_id(chosen_exit_digest); switch (reason) { case END_STREAM_REASON_EXITPOLICY: if (rh->length >= 5) { @@ -752,8 +757,8 @@ connection_ap_process_end_not_open( log_info(LD_APP, "Exitrouter %s seems to be more restrictive than its exit " "policy. Not using this router as exit for now.", - router_describe(exitrouter)); - policies_set_router_exitpolicy_to_reject_all(exitrouter); + 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, @@ -818,7 +823,7 @@ connection_ap_process_end_not_open( case END_STREAM_REASON_HIBERNATING: case END_STREAM_REASON_RESOURCELIMIT: if (exitrouter) { - policies_set_router_exitpolicy_to_reject_all(exitrouter); + policies_set_node_exitpolicy_to_reject_all(exitrouter); } if (conn->chosen_exit_optional) { /* stop wanting a specific exit */ @@ -838,7 +843,7 @@ connection_ap_process_end_not_open( stream_end_reason_to_string(rh->length > 0 ? reason : -1)); circuit_log_path(LOG_INFO,LD_APP,circ); /* need to test because of detach_retriable */ - if (!conn->_base.marked_for_close) + if (!ENTRY_TO_CONN(conn)->marked_for_close) connection_mark_unattached_ap(conn, control_reason); return 0; } @@ -847,7 +852,7 @@ connection_ap_process_end_not_open( * dotted-quad representation of <b>new_addr</b> (given in host order), * and send an appropriate REMAP event. */ static void -remap_event_helper(edge_connection_t *conn, uint32_t new_addr) +remap_event_helper(entry_connection_t *conn, uint32_t new_addr) { struct in_addr in; @@ -873,7 +878,8 @@ connection_edge_process_relay_cell_not_open( if (rh->command == RELAY_COMMAND_END) { if (CIRCUIT_IS_ORIGIN(circ) && conn->_base.type == CONN_TYPE_AP) { return connection_ap_process_end_not_open(rh, cell, - TO_ORIGIN_CIRCUIT(circ), conn, + TO_ORIGIN_CIRCUIT(circ), + EDGE_TO_ENTRY_CONN(conn), layer_hint); } else { /* we just got an 'end', don't need to send one */ @@ -887,6 +893,7 @@ connection_edge_process_relay_cell_not_open( if (conn->_base.type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_CONNECTED) { + 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) { log_fn(LOG_PROTOCOL_WARN, LD_APP, @@ -901,29 +908,26 @@ connection_edge_process_relay_cell_not_open( int ttl; if (!addr || (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0))) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP, - "...but it claims the IP address was %s. Closing.", buf); + log_info(LD_APP, "...but it claims the IP address was %s. Closing.", + fmt_addr32(addr)); connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); - connection_mark_unattached_ap(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(conn->socks_request->address, addr, - conn->chosen_exit_name, ttl); + client_dns_set_addressmap(entry_conn->socks_request->address, addr, + entry_conn->chosen_exit_name, ttl); - remap_event_helper(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 */ - if (!conn->socks_request->has_finished) - connection_ap_handshake_socks_reply(conn, NULL, 0, 0); + if (!entry_conn->socks_request->has_finished) + connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -946,6 +950,12 @@ connection_edge_process_relay_cell_not_open( break; } } + /* This is definitely a success, so forget about any pending data we + * had sent. */ + if (entry_conn->pending_optimistic_data) { + generic_buffer_free(entry_conn->pending_optimistic_data); + entry_conn->pending_optimistic_data = NULL; + } /* handle anything that might have queued */ if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) { @@ -960,17 +970,18 @@ connection_edge_process_relay_cell_not_open( 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) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a 'resolved' cell while " "not in state resolve_wait. Dropping."); return 0; } - tor_assert(SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)); + tor_assert(SOCKS_COMMAND_IS_RESOLVE(entry_conn->socks_request->command)); answer_len = cell->payload[RELAY_HEADER_SIZE+1]; if (rh->length < 2 || answer_len+2>rh->length) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Dropping malformed 'resolved' cell"); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); return 0; } answer_type = cell->payload[RELAY_HEADER_SIZE]; @@ -983,19 +994,17 @@ connection_edge_process_relay_cell_not_open( uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); if (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0)) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", buf); - connection_ap_handshake_socks_resolved(conn, + log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", + fmt_addr32(addr)); + connection_ap_handshake_socks_resolved(entry_conn, RESOLVED_TYPE_ERROR_TRANSIENT, 0, NULL, 0, TIME_MAX); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_TORPROTOCOL); return 0; } } - connection_ap_handshake_socks_resolved(conn, + connection_ap_handshake_socks_resolved(entry_conn, answer_type, cell->payload[RELAY_HEADER_SIZE+1], /*answer_len*/ cell->payload+RELAY_HEADER_SIZE+2, /*answer*/ @@ -1003,9 +1012,9 @@ connection_edge_process_relay_cell_not_open( -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(conn, addr); + remap_event_helper(entry_conn, addr); } - connection_mark_unattached_ap(conn, + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_DONE | END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); return 0; @@ -1039,6 +1048,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, relay_header_t rh; unsigned domain = layer_hint?LD_APP:LD_EXIT; int reason; + int optimistic_data = 0; /* Set to 1 if we receive data on a stream + * that's in the EXIT_CONN_STATE_RESOLVING + * or EXIT_CONN_STATE_CONNECTING states. */ tor_assert(cell); tor_assert(circ); @@ -1058,9 +1070,20 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, /* either conn is NULL, in which case we've got a control cell, or else * conn points to the recognized stream. */ - if (conn && !connection_state_is_open(TO_CONN(conn))) - return connection_edge_process_relay_cell_not_open( - &rh, cell, circ, conn, layer_hint); + 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) && + 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. + * This speeds up HTTP, for example. */ + optimistic_data = 1; + } else { + return connection_edge_process_relay_cell_not_open( + &rh, cell, circ, conn, layer_hint); + } + } switch (rh.command) { case RELAY_COMMAND_DROP: @@ -1127,7 +1150,14 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, stats_n_data_bytes_received += rh.length; connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); - connection_edge_consider_sending_sendme(conn); + + if (!optimistic_data) { + /* Only send a SENDME if we're not getting optimistic data; otherwise + * a SENDME could arrive before the CONNECTED. + */ + connection_edge_consider_sending_sendme(conn); + } + return 0; case RELAY_COMMAND_END: reason = rh.length > 0 ? @@ -1142,9 +1172,13 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, conn->_base.s, stream_end_reason_to_string(reason), conn->stream_id); - if (conn->socks_request && !conn->socks_request->has_finished) - log_warn(LD_BUG, - "open stream hasn't sent socks answer yet? Closing."); + 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) + log_warn(LD_BUG, + "open stream hasn't sent socks answer yet? Closing."); + } /* We just *got* an end; no reason to send one. */ conn->edge_has_sent_end = 1; if (!conn->end_reason) @@ -1152,8 +1186,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, 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_for_close(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } return 0; case RELAY_COMMAND_EXTEND: @@ -1324,10 +1357,17 @@ int connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, int *max_cells) { - size_t amount_to_process, length; + size_t bytes_to_process, length; char payload[CELL_PAYLOAD_SIZE]; circuit_t *circ; - unsigned domain = conn->cpath_layer ? 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; + entry_connection_t *entry_conn = + conn->_base.type == CONN_TYPE_AP ? EDGE_TO_ENTRY_CONN(conn) : NULL; + crypt_path_t *cpath_layer = conn->cpath_layer; tor_assert(conn); @@ -1350,7 +1390,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, return -1; } - if (circuit_consider_stop_edge_reading(circ, conn->cpath_layer)) + if (circuit_consider_stop_edge_reading(circ, cpath_layer)) return 0; if (conn->package_window <= 0) { @@ -1360,44 +1400,75 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, return 0; } - amount_to_process = buf_datalen(conn->_base.inbuf); + sending_from_optimistic = entry_conn && + entry_conn->sending_optimistic_data != NULL; - if (!amount_to_process) + if (PREDICT_UNLIKELY(sending_from_optimistic)) { + bytes_to_process = generic_buffer_len(entry_conn->sending_optimistic_data); + if (PREDICT_UNLIKELY(!bytes_to_process)) { + log_warn(LD_BUG, "sending_optimistic_data was non-NULL but empty"); + bytes_to_process = connection_get_inbuf_len(TO_CONN(conn)); + sending_from_optimistic = 0; + } + } else { + bytes_to_process = connection_get_inbuf_len(TO_CONN(conn)); + } + + if (!bytes_to_process) return 0; - if (!package_partial && amount_to_process < RELAY_PAYLOAD_SIZE) + if (!package_partial && bytes_to_process < RELAY_PAYLOAD_SIZE) return 0; - if (amount_to_process > RELAY_PAYLOAD_SIZE) { + if (bytes_to_process > RELAY_PAYLOAD_SIZE) { length = RELAY_PAYLOAD_SIZE; } else { - length = amount_to_process; + length = bytes_to_process; } stats_n_data_bytes_packaged += length; stats_n_data_cells_packaged += 1; - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + if (PREDICT_UNLIKELY(sending_from_optimistic)) { + /* XXX023 We could be more efficient here by sometimes packing + * previously-sent optimistic data in the same cell with data + * from the inbuf. */ + generic_buffer_get(entry_conn->sending_optimistic_data, payload, length); + if (!generic_buffer_len(entry_conn->sending_optimistic_data)) { + generic_buffer_free(entry_conn->sending_optimistic_data); + entry_conn->sending_optimistic_data = NULL; + } + } else { + connection_fetch_from_buf(payload, length, TO_CONN(conn)); + } log_debug(domain,"(%d) Packaging %d bytes (%d waiting).", conn->_base.s, - (int)length, (int)buf_datalen(conn->_base.inbuf)); + (int)length, (int)connection_get_inbuf_len(TO_CONN(conn))); + + if (sending_optimistically && !sending_from_optimistic) { + /* This is new optimistic data; remember it in case we need to detach and + retry */ + if (!entry_conn->pending_optimistic_data) + entry_conn->pending_optimistic_data = generic_buffer_new(); + generic_buffer_add(entry_conn->pending_optimistic_data, payload, length); + } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, payload, length) < 0 ) /* circuit got marked for close, don't continue, don't need to mark conn */ return 0; - if (!conn->cpath_layer) { /* non-rendezvous exit */ + if (!cpath_layer) { /* non-rendezvous exit */ tor_assert(circ->package_window > 0); circ->package_window--; } else { /* we're an AP, or an exit on a rendezvous circ */ - tor_assert(conn->cpath_layer->package_window > 0); - conn->cpath_layer->package_window--; + tor_assert(cpath_layer->package_window > 0); + cpath_layer->package_window--; } if (--conn->package_window <= 0) { /* is it 0 after decrement? */ connection_stop_reading(TO_CONN(conn)); log_debug(domain,"conn->package_window reached 0."); - circuit_consider_stop_edge_reading(circ, conn->cpath_layer); + circuit_consider_stop_edge_reading(circ, cpath_layer); return 0; /* don't process the inbuf any more */ } log_debug(domain,"conn->package_window is now %d",conn->package_window); @@ -1436,7 +1507,7 @@ connection_edge_consider_sending_sendme(edge_connection_t *conn) } while (conn->deliver_window <= STREAMWINDOW_START - STREAMWINDOW_INCREMENT) { - log_debug(conn->cpath_layer?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); conn->deliver_window += STREAMWINDOW_INCREMENT; @@ -1532,7 +1603,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1543,7 +1614,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1582,7 +1653,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, } /* If there's still data to read, we'll be coming back to this stream. */ - if (buf_datalen(conn->_base.inbuf)) + if (connection_get_inbuf_len(TO_CONN(conn))) ++n_streams_left; /* If the circuit won't accept any more data, return without looking @@ -1638,9 +1709,10 @@ circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) if (layer_hint->package_window <= 0) { log_debug(domain,"yes, at-origin. stopped."); for (conn = TO_ORIGIN_CIRCUIT(circ)->p_streams; conn; - conn=conn->next_stream) + conn=conn->next_stream) { if (conn->cpath_layer == layer_hint) connection_stop_reading(TO_CONN(conn)); + } return 1; } return 0; @@ -1995,7 +2067,8 @@ static int ewma_enabled = 0; /** Adjust the global cell scale factor based on <b>options</b> */ void -cell_ewma_set_scale_factor(or_options_t *options, networkstatus_t *consensus) +cell_ewma_set_scale_factor(const or_options_t *options, + const networkstatus_t *consensus) { int32_t halflife_ms; double halflife; @@ -2246,7 +2319,7 @@ set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn, edge->edge_blocked_on_circ = block; } - if (!conn->read_event) { + if (!conn->read_event && !HAS_BUFFEREVENT(conn)) { /* This connection is a placeholder for something; probably a DNS * request. It can't actually stop or start reading.*/ continue; @@ -2321,13 +2394,13 @@ connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, /* Calculate the exact time that this cell has spent in the queue. */ if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { - struct timeval now; + struct timeval tvnow; uint32_t flushed; uint32_t cell_waiting_time; insertion_time_queue_t *it_queue = queue->insertion_times; - tor_gettimeofday_cached(&now); - flushed = (uint32_t)((now.tv_sec % SECONDS_IN_A_DAY) * 100L + - (uint32_t)now.tv_usec / (uint32_t)10000L); + tor_gettimeofday_cached(&tvnow); + flushed = (uint32_t)((tvnow.tv_sec % SECONDS_IN_A_DAY) * 100L + + (uint32_t)tvnow.tv_usec / (uint32_t)10000L); if (!it_queue || !it_queue->first) { log_info(LD_GENERAL, "Cannot determine insertion time of cell. " "Looks like the CellStatistics option was " @@ -2447,7 +2520,7 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, make_circuit_active_on_conn(circ, orconn); } - if (! buf_datalen(orconn->_base.outbuf)) { + if (! connection_get_outbuf_len(TO_CONN(orconn))) { /* 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. diff --git a/src/or/relay.h b/src/or/relay.h index f64752da5d..7fce8edcaf 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -60,8 +60,8 @@ 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(or_options_t *options, - networkstatus_t *consensus); +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 tor_gettimeofday_cache_clear(void); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index f84475acbc..1038378de1 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -16,6 +16,7 @@ #include "connection_edge.h" #include "directory.h" #include "main.h" +#include "nodelist.h" #include "relay.h" #include "rendclient.h" #include "rendcommon.h" @@ -138,6 +139,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, tor_assert(rendcirc->rend_data); tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address, rendcirc->rend_data->onion_address)); + tor_assert(!(introcirc->build_state->onehop_tunnel)); + tor_assert(!(rendcirc->build_state->onehop_tunnel)); if (rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1, &entry) < 1) { @@ -328,6 +331,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, } tor_assert(circ->build_state->chosen_exit); + tor_assert(!(circ->build_state->onehop_tunnel)); tor_assert(circ->rend_data); if (request_len == 0) { @@ -339,6 +343,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, rendcirc = circuit_get_by_rend_query_and_purpose( circ->rend_data->onion_address, CIRCUIT_PURPOSE_C_REND_READY); if (rendcirc) { /* remember the ack */ + tor_assert(!(rendcirc->build_state->onehop_tunnel)); rendcirc->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED; /* Set timestamp_dirty, because circuit_expire_building expects * it to specify when a circuit entered the @@ -360,8 +365,9 @@ rend_client_introduction_acked(origin_circuit_t *circ, log_info(LD_REND, "Got nack for %s from %s...", safe_str_client(circ->rend_data->onion_address), safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); - if (rend_client_remove_intro_point(circ->build_state->chosen_exit, - circ->rend_data) > 0) { + if (rend_client_report_intro_point_failure(circ->build_state->chosen_exit, + circ->rend_data, + INTRO_POINT_FAILURE_GENERIC)>0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ int result = rend_client_reextend_intro_circuit(circ); @@ -378,9 +384,12 @@ rend_client_introduction_acked(origin_circuit_t *circ, #define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) /** Contains the last request times to hidden service directories for - * certain queries; keys are strings consisting of base32-encoded - * hidden service directory identities and base32-encoded descriptor IDs; - * values are pointers to timestamps of the last requests. */ + * certain queries; each key is a string consisting of the + * concatenation of a base32-encoded HS directory identity digest, a + * base32-encoded HS descriptor ID, and a hidden service address + * (without the ".onion" part); each value is a pointer to a time_t + * holding the time of the last request for that descriptor ID to that + * HS directory. */ static strmap_t *last_hid_serv_requests_ = NULL; /** Returns last_hid_serv_requests_, initializing it to a new strmap if @@ -393,23 +402,34 @@ get_last_hid_serv_requests(void) return last_hid_serv_requests_; } +#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ + REND_DESC_ID_V2_LEN_BASE32 + \ + REND_SERVICE_ID_LEN_BASE32) + /** Look up the last request time to hidden service directory <b>hs_dir</b> - * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero, + * for descriptor ID <b>desc_id_base32</b> for the service specified in + * <b>rend_query</b>. If <b>set</b> is non-zero, * assign the current time <b>now</b> and return that. Otherwise, return * the most recent request time, or 0 if no such request has been sent * before. */ static time_t lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, time_t now, int set) + const char *desc_id_base32, + const rend_data_t *rend_query, + time_t now, int set) { char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[2 * REND_DESC_ID_V2_LEN_BASE32 + 1]; + char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; time_t *last_request_ptr; strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, desc_id_base32); + tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s%s", + hsdir_id_base32, + desc_id_base32, + rend_query->onion_address); + /* XXX023 tor_assert(strlen(hsdir_desc_comb_id) == + LAST_HID_SERV_REQUEST_KEY_LEN); */ if (set) { time_t *oldptr; last_request_ptr = tor_malloc_zero(sizeof(time_t)); @@ -427,10 +447,10 @@ lookup_last_hid_serv_request(routerstatus_t *hs_dir, * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD * seconds any more. */ static void -directory_clean_last_hid_serv_requests(void) +directory_clean_last_hid_serv_requests(time_t now) { strmap_iter_t *iter; - time_t cutoff = time(NULL) - REND_HID_SERV_DIR_REQUERY_PERIOD; + time_t cutoff = now - REND_HID_SERV_DIR_REQUERY_PERIOD; strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); for (iter = strmap_iter_init(last_hid_serv_requests); !strmap_iter_done(iter); ) { @@ -448,6 +468,33 @@ directory_clean_last_hid_serv_requests(void) } } +/** Remove all requests related to the hidden service named + * <b>onion_address</b> from the history of times of requests to + * hidden service directories. */ +static void +purge_hid_serv_from_last_hid_serv_requests(const char *onion_address) +{ + strmap_iter_t *iter; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + /* XXX023 tor_assert(strlen(onion_address) == REND_SERVICE_ID_LEN_BASE32); */ + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + strmap_iter_get(iter, &key, &val); + /* XXX023 tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ + if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - + REND_SERVICE_ID_LEN_BASE32, + onion_address, + REND_SERVICE_ID_LEN_BASE32)) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(val); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + /** Purge the history of request times to hidden service directories, * so that future lookups of an HS descriptor will not fail because we * accessed all of the HSDir relays responsible for the descriptor @@ -469,12 +516,11 @@ rend_client_purge_last_hid_serv_requests(void) } /** Determine the responsible hidden service directories for <b>desc_id</b> - * and fetch the descriptor belonging to that ID from one of them. Only - * send a request to hidden service directories that we did not try within - * the last REND_HID_SERV_DIR_REQUERY_PERIOD seconds; on success, return 1, + * and fetch the descriptor with that ID from one of them. Only + * send a request to a hidden service directory that we have not yet tried + * during this attempt to connect to this hidden service; on success, return 1, * in the case that no hidden service directory is left to ask for the - * descriptor, return 0, and in case of a failure -1. <b>query</b> is only - * passed for pretty log statements. */ + * descriptor, return 0, and in case of a failure -1. */ static int directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) { @@ -494,12 +540,16 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) /* Only select those hidden service directories to which we did not send * a request recently and for which we have a router descriptor here. */ - directory_clean_last_hid_serv_requests(); /* Clean request history first. */ + + /* Clean request history first. */ + directory_clean_last_hid_serv_requests(now); SMARTLIST_FOREACH(responsible_dirs, routerstatus_t *, dir, { - if (lookup_last_hid_serv_request(dir, desc_id_base32, 0, 0) + - REND_HID_SERV_DIR_REQUERY_PERIOD >= now || - !router_get_by_digest(dir->identity_digest)) + time_t last = lookup_last_hid_serv_request( + dir, desc_id_base32, rend_query, 0, 0); + const node_t *node = node_get_by_id(dir->identity_digest); + if (last + REND_HID_SERV_DIR_REQUERY_PERIOD >= now || + !node || !node_has_descriptor(node)) SMARTLIST_DEL_CURRENT(responsible_dirs, dir); }); @@ -512,9 +562,9 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query) return 0; } - /* Remember, that we are requesting a descriptor from this hidden service + /* Remember that we are requesting a descriptor from this hidden service * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); + lookup_last_hid_serv_request(hs_dir, desc_id_base32, rend_query, now, 1); /* Encode descriptor cookie for logging purposes. */ if (rend_query->auth_type != REND_NO_AUTH) { @@ -568,10 +618,11 @@ rend_client_refetch_v2_renddesc(const rend_data_t *rend_query) "service descriptor, but are not fetching service descriptors."); return; } - /* Before fetching, check if we already have the descriptor here. */ - if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0) { + /* Before fetching, check if we already have a usable descriptor here. */ + if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0 && + rend_client_any_intro_points_usable(e)) { log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we " - "already have that descriptor here. Not fetching."); + "already have a usable descriptor here. Not fetching."); return; } log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s", @@ -641,16 +692,31 @@ rend_client_cancel_descriptor_fetches(void) } SMARTLIST_FOREACH_END(conn); } -/** Remove failed_intro from ent. If ent now has no intro points, or - * service is unrecognized, then launch a new renddesc fetch. - +/** Mark <b>failed_intro</b> as a failed introduction point for the + * hidden service specified by <b>rend_query</b>. If the HS now has no + * usable intro points, or we do not have an HS descriptor for it, + * then launch a new renddesc fetch. * - * Return -1 if error, 0 if no intro points remain or service + * If <b>failure_type</b> is INTRO_POINT_FAILURE_GENERIC, remove the + * intro point from (our parsed copy of) the HS descriptor. + * + * If <b>failure_type</b> is INTRO_POINT_FAILURE_TIMEOUT, mark the + * intro point as 'timed out'; it will not be retried until the + * current hidden service connection attempt has ended or it has + * appeared in a newly fetched rendezvous descriptor. + * + * If <b>failure_type</b> is INTRO_POINT_FAILURE_UNREACHABLE, + * increment the intro point's reachability-failure count; if it has + * now failed MAX_INTRO_POINT_REACHABILITY_FAILURES or more times, + * remove the intro point from (our parsed copy of) the HS descriptor. + * + * Return -1 if error, 0 if no usable intro points remain or service * unrecognized, 1 if recognized and some intro points remain. */ int -rend_client_remove_intro_point(extend_info_t *failed_intro, - const rend_data_t *rend_query) +rend_client_report_intro_point_failure(extend_info_t *failed_intro, + const rend_data_t *rend_query, + unsigned int failure_type) { int i, r; rend_cache_entry_t *ent; @@ -673,8 +739,34 @@ rend_client_remove_intro_point(extend_info_t *failed_intro, rend_intro_point_t *intro = smartlist_get(ent->parsed->intro_nodes, i); if (tor_memeq(failed_intro->identity_digest, intro->extend_info->identity_digest, DIGEST_LEN)) { - rend_intro_point_free(intro); - smartlist_del(ent->parsed->intro_nodes, i); + switch (failure_type) { + default: + log_warn(LD_BUG, "Unknown failure type %u. Removing intro point.", + failure_type); + tor_fragile_assert(); + /* fall through */ + case INTRO_POINT_FAILURE_GENERIC: + rend_intro_point_free(intro); + smartlist_del(ent->parsed->intro_nodes, i); + break; + case INTRO_POINT_FAILURE_TIMEOUT: + intro->timed_out = 1; + break; + case INTRO_POINT_FAILURE_UNREACHABLE: + ++(intro->unreachable_count); + { + int zap_intro_point = + intro->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES; + log_info(LD_REND, "Failed to reach this intro point %u times.%s", + intro->unreachable_count, + zap_intro_point ? " Removing from descriptor.": ""); + if (zap_intro_point) { + rend_intro_point_free(intro); + smartlist_del(ent->parsed->intro_nodes, i); + } + } + break; + } break; } } @@ -810,40 +902,42 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, void rend_client_desc_trynow(const char *query) { - edge_connection_t *conn; + entry_connection_t *conn; rend_cache_entry_t *entry; + const rend_data_t *rend_data; time_t now = time(NULL); smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, _conn) { - if (_conn->type != CONN_TYPE_AP || - _conn->state != AP_CONN_STATE_RENDDESC_WAIT || - _conn->marked_for_close) + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + if (base_conn->type != CONN_TYPE_AP || + base_conn->state != AP_CONN_STATE_RENDDESC_WAIT || + base_conn->marked_for_close) continue; - conn = TO_EDGE_CONN(_conn); - if (!conn->rend_data) + conn = TO_ENTRY_CONN(base_conn); + rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; + if (!rend_data) continue; - if (rend_cmp_service_ids(query, conn->rend_data->onion_address)) + if (rend_cmp_service_ids(query, rend_data->onion_address)) continue; - assert_connection_ok(TO_CONN(conn), now); - if (rend_cache_lookup_entry(conn->rend_data->onion_address, -1, + assert_connection_ok(base_conn, now); + if (rend_cache_lookup_entry(rend_data->onion_address, -1, &entry) == 1 && rend_client_any_intro_points_usable(entry)) { /* either this fetch worked, or it failed but there was a * valid entry from before which we should reuse */ log_info(LD_REND,"Rend desc is usable. Launching circuits."); - conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT; + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; /* restart their timeout values, so they get a fair shake at * connecting to the hidden service. */ - conn->_base.timestamp_created = now; - conn->_base.timestamp_lastread = now; - conn->_base.timestamp_lastwritten = now; + base_conn->timestamp_created = now; + base_conn->timestamp_lastread = now; + base_conn->timestamp_lastwritten = now; if (connection_ap_handshake_attach_circuit(conn) < 0) { /* it will never work */ log_warn(LD_REND,"Rendezvous attempt failed. Closing."); - if (!conn->_base.marked_for_close) + if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); } } else { /* 404, or fetch didn't get that far */ @@ -851,8 +945,34 @@ rend_client_desc_trynow(const char *query) "unavailable (try again later).", safe_str_client(query)); connection_mark_unattached_ap(conn, END_STREAM_REASON_RESOLVEFAILED); + rend_client_note_connection_attempt_ended(query); } - } SMARTLIST_FOREACH_END(_conn); + } SMARTLIST_FOREACH_END(base_conn); +} + +/** Clear temporary state used only during an attempt to connect to + * the hidden service named <b>onion_address</b>. Called when a + * connection attempt has ended; may be called occasionally at other + * times, and should be reasonably harmless. */ +void +rend_client_note_connection_attempt_ended(const char *onion_address) +{ + rend_cache_entry_t *cache_entry = NULL; + rend_cache_lookup_entry(onion_address, -1, &cache_entry); + + log_info(LD_REND, "Connection attempt for %s has ended; " + "cleaning up temporary state.", + safe_str_client(onion_address)); + + /* Clear the timed_out flag on all remaining intro points for this HS. */ + if (cache_entry != NULL) { + SMARTLIST_FOREACH(cache_entry->parsed->intro_nodes, + rend_intro_point_t *, ip, + ip->timed_out = 0; ); + } + + /* Remove the HS's entries in last_hid_serv_requests. */ + purge_hid_serv_from_last_hid_serv_requests(onion_address); } /** Return a newly allocated extend_info_t* for a randomly chosen introduction @@ -894,8 +1014,7 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, int i; rend_intro_point_t *intro; - routerinfo_t *router; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); smartlist_t *usable_nodes; int n_excluded = 0; @@ -904,6 +1023,13 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, usable_nodes = smartlist_create(); smartlist_add_all(usable_nodes, entry->parsed->intro_nodes); + /* Remove the intro points that have timed out during this HS + * connection attempt from our list of usable nodes. */ + SMARTLIST_FOREACH(usable_nodes, rend_intro_point_t *, ip, + if (ip->timed_out) { + SMARTLIST_DEL_CURRENT(usable_nodes, ip); + }); + again: if (smartlist_len(usable_nodes) == 0) { if (n_excluded && get_options()->StrictNodes && warnings) { @@ -921,21 +1047,22 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, intro = smartlist_get(usable_nodes, i); /* Do we need to look up the router or is the extend info complete? */ if (!intro->extend_info->onion_key) { + const node_t *node; if (tor_digest_is_zero(intro->extend_info->identity_digest)) - router = router_get_by_hexdigest(intro->extend_info->nickname); + node = node_get_by_hex_id(intro->extend_info->nickname); else - router = router_get_by_digest(intro->extend_info->identity_digest); - if (!router) { + node = node_get_by_id(intro->extend_info->identity_digest); + if (!node) { log_info(LD_REND, "Unknown router with nickname '%s'; trying another.", intro->extend_info->nickname); smartlist_del(usable_nodes, i); goto again; } extend_info_free(intro->extend_info); - intro->extend_info = extend_info_from_router(router); + intro->extend_info = extend_info_from_node(node); } /* Check if we should refuse to talk to this router. */ - if (options->ExcludeNodes && strict && + if (strict && routerset_contains_extendinfo(options->ExcludeNodes, intro->extend_info)) { n_excluded++; @@ -1001,7 +1128,8 @@ rend_service_authorization_free_all(void) * service and add it to the local map of hidden service authorizations. * Return 0 for success and -1 for failure. */ int -rend_parse_service_authorization(or_options_t *options, int validate_only) +rend_parse_service_authorization(const or_options_t *options, + int validate_only) { config_line_t *line; int res = -1; diff --git a/src/or/rendclient.h b/src/or/rendclient.h index c6cf82b3dd..89da47789a 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -22,8 +22,15 @@ int rend_client_introduction_acked(origin_circuit_t *circ, void rend_client_refetch_v2_renddesc(const rend_data_t *rend_query); void rend_client_cancel_descriptor_fetches(void); void rend_client_purge_last_hid_serv_requests(void); -int rend_client_remove_intro_point(extend_info_t *failed_intro, - const rend_data_t *rend_query); + +#define INTRO_POINT_FAILURE_GENERIC 0 +#define INTRO_POINT_FAILURE_TIMEOUT 1 +#define INTRO_POINT_FAILURE_UNREACHABLE 2 + +int rend_client_report_intro_point_failure(extend_info_t *failed_intro, + const rend_data_t *rend_query, + unsigned int failure_type); + int rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -32,12 +39,14 @@ int rend_client_receive_rendezvous(origin_circuit_t *circ, size_t request_len); void rend_client_desc_trynow(const char *query); +void rend_client_note_connection_attempt_ended(const char *onion_address); + extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query); int rend_client_any_intro_points_usable(const rend_cache_entry_t *entry); int rend_client_send_introduction(origin_circuit_t *introcirc, origin_circuit_t *rendcirc); -int rend_parse_service_authorization(or_options_t *options, +int rend_parse_service_authorization(const or_options_t *options, int validate_only); rend_service_authorization_t *rend_client_lookup_service_authorization( const char *onion_address); diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index c5bf88163d..94bb002210 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -814,14 +814,13 @@ rend_cache_free_all(void) /** Removes all old entries from the service descriptor cache. */ void -rend_cache_clean(void) +rend_cache_clean(time_t now) { strmap_iter_t *iter; const char *key; void *val; rend_cache_entry_t *ent; - time_t cutoff; - cutoff = time(NULL) - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; + time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; for (iter = strmap_iter_init(rend_cache); !strmap_iter_done(iter); ) { strmap_iter_get(iter, &key, &val); ent = (rend_cache_entry_t*)val; @@ -849,10 +848,10 @@ rend_cache_purge(void) /** Remove all old v2 descriptors and those for which this hidden service * directory is not responsible for any more. */ void -rend_cache_clean_v2_descs_as_dir(void) +rend_cache_clean_v2_descs_as_dir(time_t now) { digestmap_iter_t *iter; - time_t cutoff = time(NULL) - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; + time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; for (iter = digestmap_iter_init(rend_cache_v2_dir); !digestmap_iter_done(iter); ) { const char *key; diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index c51039f1f2..0d64466dbe 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -34,8 +34,8 @@ void rend_encoded_v2_service_descriptor_free( void rend_intro_point_free(rend_intro_point_t *intro); void rend_cache_init(void); -void rend_cache_clean(void); -void rend_cache_clean_v2_descs_as_dir(void); +void rend_cache_clean(time_t now); +void rend_cache_clean_v2_descs_as_dir(time_t now); void rend_cache_purge(void); void rend_cache_free_all(void); int rend_valid_service_id(const char *query); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 0f5731912d..d582d71f6c 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -14,6 +14,7 @@ #include "config.h" #include "directory.h" #include "networkstatus.h" +#include "nodelist.h" #include "rendclient.h" #include "rendcommon.h" #include "rendservice.h" @@ -257,7 +258,7 @@ parse_port_config(const char *string) } else { addrport = smartlist_get(sl,1); if (strchr(addrport, ':') || strchr(addrport, '.')) { - if (tor_addr_port_parse(addrport, &addr, &p)<0) { + if (tor_addr_port_lookup(addrport, &addr, &p)<0) { log_warn(LD_CONFIG,"Unparseable address in hidden service port " "configuration."); goto err; @@ -291,7 +292,7 @@ parse_port_config(const char *string) * normal, but don't actually change the configured services.) */ int -rend_config_services(or_options_t *options, int validate_only) +rend_config_services(const or_options_t *options, int validate_only) { config_line_t *line; rend_service_t *service = NULL; @@ -904,8 +905,9 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, time_t now = time(NULL); char diffie_hellman_hash[DIGEST_LEN]; time_t *access_time; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); + tor_assert(!(circuit->build_state->onehop_tunnel)); tor_assert(circuit->rend_data); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, @@ -1058,7 +1060,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, } else { char *rp_nickname; size_t nickname_field_len; - routerinfo_t *router; + const node_t *node; int version; if (*buf == 1) { rp_nickname = buf+1; @@ -1085,8 +1087,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, len -= nickname_field_len; len -= rp_nickname - buf; /* also remove header space used by version, if * any */ - router = router_get_by_nickname(rp_nickname, 0); - if (!router) { + 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? */ @@ -1094,7 +1096,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, goto err; } - extend_info = extend_info_from_router(router); + extend_info = extend_info_from_node(node); } if (len != REND_COOKIE_LEN+DH_KEY_LEN) { @@ -1104,7 +1106,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request, } /* Check if we'd refuse to talk to this router */ - if (options->ExcludeNodes && options->StrictNodes && + if (options->StrictNodes && routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) { log_warn(LD_REND, "Client asked to rendezvous at a relay that we " "exclude, and StrictNodes is set. Refusing service."); @@ -1389,6 +1391,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) crypto_pk_env_t *intro_key; tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + tor_assert(!(circuit->build_state->onehop_tunnel)); tor_assert(circuit->cpath); tor_assert(circuit->rend_data); @@ -1407,7 +1410,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /* If we already have enough introduction circuits for this service, * redefine this one as a general circuit or close it, depending. */ if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->ExcludeNodes) { /* XXXX in some future version, we can test whether the transition is allowed or not given the actual nodes in the circuit. But for now, @@ -1544,6 +1547,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); tor_assert(circuit->cpath); tor_assert(circuit->build_state); + tor_assert(!(circuit->build_state->onehop_tunnel)); tor_assert(circuit->rend_data); hop = circuit->build_state->pending_final_cpath; tor_assert(hop); @@ -1672,12 +1676,14 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, for (j = 0; j < smartlist_len(responsible_dirs); j++) { char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; char *hs_dir_ip; + const node_t *node; hs_dir = smartlist_get(responsible_dirs, j); if (smartlist_digest_isin(renddesc->successful_uploads, hs_dir->identity_digest)) /* Don't upload descriptor if we succeeded in doing so last time. */ continue; - if (!router_get_by_digest(hs_dir->identity_digest)) { + node = node_get_by_id(hs_dir->identity_digest); + if (!node || !node_has_descriptor(node)) { log_info(LD_REND, "Not sending publish request for v2 descriptor to " "hidden service directory %s; we don't have its " "router descriptor. Queuing for later upload.", @@ -1854,19 +1860,19 @@ void rend_services_introduce(void) { int i,j,r; - routerinfo_t *router; + const node_t *node; rend_service_t *service; rend_intro_point_t *intro; int changed, prev_intro_nodes; - smartlist_t *intro_routers; + smartlist_t *intro_nodes; time_t now; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); - intro_routers = smartlist_create(); + intro_nodes = smartlist_create(); now = time(NULL); for (i=0; i < smartlist_len(rend_service_list); ++i) { - smartlist_clear(intro_routers); + smartlist_clear(intro_nodes); service = smartlist_get(rend_service_list, i); tor_assert(service); @@ -1886,8 +1892,8 @@ rend_services_introduce(void) service. */ for (j=0; j < smartlist_len(service->intro_nodes); ++j) { intro = smartlist_get(service->intro_nodes, j); - router = router_get_by_digest(intro->extend_info->identity_digest); - if (!router || !find_intro_circuit(intro, service->pk_digest)) { + node = node_get_by_id(intro->extend_info->identity_digest); + if (!node || !find_intro_circuit(intro, service->pk_digest)) { log_info(LD_REND,"Giving up on %s as intro point for %s.", safe_str_client(extend_info_describe(intro->extend_info)), safe_str_client(service->service_id)); @@ -1907,8 +1913,8 @@ rend_services_introduce(void) smartlist_del(service->intro_nodes,j--); changed = 1; } - if (router) - smartlist_add(intro_routers, router); + if (node) + smartlist_add(intro_nodes, (void*)node); } /* We have enough intro points, and the intro points we thought we had were @@ -1937,26 +1943,26 @@ rend_services_introduce(void) #define NUM_INTRO_POINTS_INIT (NUM_INTRO_POINTS + 2) for (j=prev_intro_nodes; j < (prev_intro_nodes == 0 ? NUM_INTRO_POINTS_INIT : NUM_INTRO_POINTS); ++j) { - router_crn_flags_t flags = CRN_NEED_UPTIME; + router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION) flags |= CRN_ALLOW_INVALID; - router = router_choose_random_node(intro_routers, - options->ExcludeNodes, flags); - if (!router) { + node = router_choose_random_node(intro_nodes, + options->ExcludeNodes, flags); + if (!node) { log_warn(LD_REND, "Could only establish %d introduction points for %s.", smartlist_len(service->intro_nodes), service->service_id); break; } changed = 1; - smartlist_add(intro_routers, router); + smartlist_add(intro_nodes, (void*)node); intro = tor_malloc_zero(sizeof(rend_intro_point_t)); - intro->extend_info = extend_info_from_router(router); + intro->extend_info = extend_info_from_node(node); intro->intro_key = crypto_new_pk_env(); tor_assert(!crypto_pk_generate_key(intro->intro_key)); smartlist_add(service->intro_nodes, intro); log_info(LD_REND, "Picked router %s as an intro point for %s.", - safe_str_client(router_describe(router)), + safe_str_client(node_describe(node)), safe_str_client(service->service_id)); } @@ -1975,7 +1981,7 @@ rend_services_introduce(void) } } } - smartlist_free(intro_routers); + smartlist_free(intro_nodes); } /** Regenerate and upload rendezvous service descriptors for all diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 70389afe9a..8a2994c4c0 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -13,7 +13,7 @@ #define _TOR_RENDSERVICE_H int num_rend_services(void); -int rend_config_services(or_options_t *options, int validate_only); +int rend_config_services(const or_options_t *options, int validate_only); int rend_service_load_keys(void); void rend_services_introduce(void); void rend_consider_services_upload(time_t now); diff --git a/src/or/rephist.c b/src/or/rephist.c index 1dd3d94fd0..6bbb93b821 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -7,7 +7,7 @@ * \brief Basic history and "reputation" functionality to remember * which servers have worked in the past, how much bandwidth we've * been using, which ports we tend to want, and so on; further, - * exit port statistics and cell statistics. + * exit port statistics, cell statistics, and connection statistics. **/ #include "or.h" @@ -15,6 +15,7 @@ #include "circuituse.h" #include "config.h" #include "networkstatus.h" +#include "nodelist.h" #include "rephist.h" #include "router.h" #include "routerlist.h" @@ -643,7 +644,7 @@ rep_hist_dump_stats(time_t now, int severity) size_t len; int ret; unsigned long upt, downt; - routerinfo_t *r; + const node_t *node; rep_history_clean(now - get_options()->RephistTrackTime); @@ -657,8 +658,8 @@ rep_hist_dump_stats(time_t now, int severity) digestmap_iter_get(orhist_it, &digest1, &or_history_p); or_history = (or_history_t*) or_history_p; - if ((r = router_get_by_digest(digest1))) - name1 = r->nickname; + if ((node = node_get_by_id(digest1)) && node_get_nickname(node)) + name1 = node_get_nickname(node); else name1 = "(unknown)"; base16_encode(hexdigest1, sizeof(hexdigest1), digest1, DIGEST_LEN); @@ -688,8 +689,8 @@ rep_hist_dump_stats(time_t now, int severity) lhist_it = digestmap_iter_next(or_history->link_history_map, lhist_it)) { digestmap_iter_get(lhist_it, &digest2, &link_history_p); - if ((r = router_get_by_digest(digest2))) - name2 = r->nickname; + if ((node = node_get_by_id(digest2)) && node_get_nickname(node)) + name2 = node_get_nickname(node); else name2 = "(unknown)"; @@ -823,7 +824,7 @@ rep_hist_record_mtbf_data(time_t now, int missing_means_down) base16_encode(dbuf, sizeof(dbuf), digest, DIGEST_LEN); if (missing_means_down && hist->start_of_run && - !router_get_by_digest(digest)) { + !router_get_by_id_digest(digest)) { /* We think this relay is running, but it's not listed in our * routerlist. Somehow it fell out without telling us it went * down. Complain and also correct it. */ @@ -938,28 +939,32 @@ rep_hist_get_router_stability_doc(time_t now) } DIGESTMAP_FOREACH(history_map, id, or_history_t *, hist) { - routerinfo_t *ri; + const node_t *node; char dbuf[BASE64_DIGEST_LEN+1]; char header_buf[512]; char *info; digest_to_base64(dbuf, id); - ri = router_get_by_digest(id); - if (ri) { - char *ip = tor_dup_ip(ri->addr); + node = node_get_by_id(id); + if (node) { + char ip[INET_NTOA_BUF_LEN+1]; char tbuf[ISO_TIME_LEN+1]; - format_iso_time(tbuf, ri->cache_info.published_on); + time_t published = node_get_published_on(node); + node_get_address_string(node,ip,sizeof(ip)); + if (published > 0) + format_iso_time(tbuf, published); + else + strlcpy(tbuf, "???", sizeof(tbuf)); tor_snprintf(header_buf, sizeof(header_buf), "router %s %s %s\n" "published %s\n" "relevant-flags %s%s%s\n" "declared-uptime %ld\n", - dbuf, ri->nickname, ip, + dbuf, node_get_nickname(node), ip, tbuf, - ri->is_running ? "Running " : "", - ri->is_valid ? "Valid " : "", - ri->is_hibernating ? "Hibernating " : "", - ri->uptime); - tor_free(ip); + node->is_running ? "Running " : "", + node->is_valid ? "Valid " : "", + node->ri && node->ri->is_hibernating ? "Hibernating " : "", + node_get_declared_uptime(node)); } else { tor_snprintf(header_buf, sizeof(header_buf), "router %s {no descriptor}\n", dbuf); @@ -1474,7 +1479,7 @@ rep_hist_fill_bandwidth_history(char *buf, size_t len, const bw_array_t *b) { char *cp = buf; int i, n; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); uint64_t cutoff; if (b->num_maxes_set <= b->next_max_idx) { @@ -1770,10 +1775,13 @@ rep_hist_load_state(or_state_t *state, char **err) /*********************************************************************/ +typedef struct predicted_port_t { + uint16_t port; + time_t time; +} predicted_port_t; + /** A list of port numbers that have been used recently. */ static smartlist_t *predicted_ports_list=NULL; -/** The corresponding most recently used time for each port. */ -static smartlist_t *predicted_ports_times=NULL; /** We just got an application request for a connection with * port <b>port</b>. Remember it for the future, so we can keep @@ -1782,14 +1790,11 @@ static smartlist_t *predicted_ports_times=NULL; static void add_predicted_port(time_t now, uint16_t port) { - /* XXXX we could just use uintptr_t here, I think. -NM */ - uint16_t *tmp_port = tor_malloc(sizeof(uint16_t)); - time_t *tmp_time = tor_malloc(sizeof(time_t)); - *tmp_port = port; - *tmp_time = now; - rephist_total_alloc += sizeof(uint16_t) + sizeof(time_t); - smartlist_add(predicted_ports_list, tmp_port); - smartlist_add(predicted_ports_times, tmp_time); + predicted_port_t *pp = tor_malloc(sizeof(predicted_port_t)); + pp->port = port; + pp->time = now; + rephist_total_alloc += sizeof(*pp); + smartlist_add(predicted_ports_list, pp); } /** Initialize whatever memory and structs are needed for predicting @@ -1800,7 +1805,6 @@ static void predicted_ports_init(void) { predicted_ports_list = smartlist_create(); - predicted_ports_times = smartlist_create(); add_predicted_port(time(NULL), 80); /* add one to kickstart us */ } @@ -1810,12 +1814,11 @@ predicted_ports_init(void) static void predicted_ports_free(void) { - rephist_total_alloc -= smartlist_len(predicted_ports_list)*sizeof(uint16_t); - SMARTLIST_FOREACH(predicted_ports_list, char *, cp, tor_free(cp)); + rephist_total_alloc -= + smartlist_len(predicted_ports_list)*sizeof(predicted_port_t); + SMARTLIST_FOREACH(predicted_ports_list, predicted_port_t *, + pp, tor_free(pp)); smartlist_free(predicted_ports_list); - rephist_total_alloc -= smartlist_len(predicted_ports_times)*sizeof(time_t); - SMARTLIST_FOREACH(predicted_ports_times, char *, cp, tor_free(cp)); - smartlist_free(predicted_ports_times); } /** Remember that <b>port</b> has been asked for as of time <b>now</b>. @@ -1825,24 +1828,17 @@ predicted_ports_free(void) void rep_hist_note_used_port(time_t now, uint16_t port) { - int i; - uint16_t *tmp_port; - time_t *tmp_time; - tor_assert(predicted_ports_list); - tor_assert(predicted_ports_times); if (!port) /* record nothing */ return; - for (i = 0; i < smartlist_len(predicted_ports_list); ++i) { - tmp_port = smartlist_get(predicted_ports_list, i); - tmp_time = smartlist_get(predicted_ports_times, i); - if (*tmp_port == port) { - *tmp_time = now; + SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { + if (pp->port == port) { + pp->time = now; return; } - } + } SMARTLIST_FOREACH_END(pp); /* it's not there yet; we need to add it */ add_predicted_port(now, port); } @@ -1851,36 +1847,28 @@ rep_hist_note_used_port(time_t now, uint16_t port) * we'll want to make connections to the same port in the future. */ #define PREDICTED_CIRCS_RELEVANCE_TIME (60*60) -/** Return a pointer to the list of port numbers that +/** Return a newly allocated pointer to a list of uint16_t * for ports that * are likely to be asked for in the near future. - * - * The caller promises not to mess with it. */ smartlist_t * rep_hist_get_predicted_ports(time_t now) { - int i; - uint16_t *tmp_port; - time_t *tmp_time; - + smartlist_t *out = smartlist_create(); tor_assert(predicted_ports_list); - tor_assert(predicted_ports_times); /* clean out obsolete entries */ - for (i = 0; i < smartlist_len(predicted_ports_list); ++i) { - tmp_time = smartlist_get(predicted_ports_times, i); - if (*tmp_time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { - tmp_port = smartlist_get(predicted_ports_list, i); - log_debug(LD_CIRC, "Expiring predicted port %d", *tmp_port); - smartlist_del(predicted_ports_list, i); - smartlist_del(predicted_ports_times, i); - rephist_total_alloc -= sizeof(uint16_t)+sizeof(time_t); - tor_free(tmp_port); - tor_free(tmp_time); - i--; + SMARTLIST_FOREACH_BEGIN(predicted_ports_list, predicted_port_t *, pp) { + if (pp->time + PREDICTED_CIRCS_RELEVANCE_TIME < now) { + log_debug(LD_CIRC, "Expiring predicted port %d", pp->port); + + rephist_total_alloc -= sizeof(predicted_port_t); + tor_free(pp); + SMARTLIST_DEL_CURRENT(predicted_ports_list, pp); + } else { + smartlist_add(out, tor_memdup(&pp->port, sizeof(uint16_t))); } - } - return predicted_ports_list; + } SMARTLIST_FOREACH_END(pp); + return out; } /** The user asked us to do a resolve. Rather than keeping track of @@ -2116,7 +2104,9 @@ rep_hist_exit_stats_term(void) tor_free(exit_streams); } -/** Helper for qsort: compare two ints. */ +/** Helper for qsort: compare two ints. Does not handle overflow properly, + * 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) { @@ -2124,7 +2114,8 @@ _compare_int(const void *x, const void *y) } /** Return a newly allocated string containing the exit port statistics - * until <b>now</b>, or NULL if we're not collecting exit stats. */ + * until <b>now</b>, or NULL if we're not collecting exit stats. Caller + * must ensure start_of_exit_stats_interval is in the past. */ char * rep_hist_format_exit_stats(time_t now) { @@ -2143,6 +2134,8 @@ rep_hist_format_exit_stats(time_t now) if (!start_of_exit_stats_interval) return NULL; /* Not initialized. */ + tor_assert(now >= start_of_exit_stats_interval); + /* Go through all ports to find the n ports that saw most written and * read bytes. * @@ -2374,41 +2367,60 @@ typedef struct circ_buffer_stats_t { /** List of circ_buffer_stats_t. */ static smartlist_t *circuits_for_buffer_stats = NULL; +/** Remember cell statistics <b>mean_num_cells_in_queue</b>, + * <b>mean_time_cells_in_queue</b>, and <b>processed_cells</b> of a + * circuit. */ +void +rep_hist_add_buffer_stats(double mean_num_cells_in_queue, + double mean_time_cells_in_queue, uint32_t processed_cells) +{ + circ_buffer_stats_t *stat; + if (!start_of_buffer_stats_interval) + return; /* Not initialized. */ + stat = tor_malloc_zero(sizeof(circ_buffer_stats_t)); + stat->mean_num_cells_in_queue = mean_num_cells_in_queue; + stat->mean_time_cells_in_queue = mean_time_cells_in_queue; + stat->processed_cells = processed_cells; + if (!circuits_for_buffer_stats) + circuits_for_buffer_stats = smartlist_create(); + smartlist_add(circuits_for_buffer_stats, stat); +} + /** Remember cell statistics for circuit <b>circ</b> at time * <b>end_of_interval</b> and reset cell counters in case the circuit * remains open in the next measurement interval. */ void rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval) { - circ_buffer_stats_t *stat; time_t start_of_interval; int interval_length; or_circuit_t *orcirc; + double mean_num_cells_in_queue, mean_time_cells_in_queue; + uint32_t processed_cells; if (CIRCUIT_IS_ORIGIN(circ)) return; orcirc = TO_OR_CIRCUIT(circ); if (!orcirc->processed_cells) return; - if (!circuits_for_buffer_stats) - circuits_for_buffer_stats = smartlist_create(); - start_of_interval = circ->timestamp_created.tv_sec > - start_of_buffer_stats_interval ? + start_of_interval = (circ->timestamp_created.tv_sec > + start_of_buffer_stats_interval) ? circ->timestamp_created.tv_sec : start_of_buffer_stats_interval; interval_length = (int) (end_of_interval - start_of_interval); if (interval_length <= 0) return; - stat = tor_malloc_zero(sizeof(circ_buffer_stats_t)); - stat->processed_cells = orcirc->processed_cells; + processed_cells = orcirc->processed_cells; /* 1000.0 for s -> ms; 2.0 because of app-ward and exit-ward queues */ - stat->mean_num_cells_in_queue = (double) orcirc->total_cell_waiting_time / + mean_num_cells_in_queue = (double) orcirc->total_cell_waiting_time / (double) interval_length / 1000.0 / 2.0; - stat->mean_time_cells_in_queue = + mean_time_cells_in_queue = (double) orcirc->total_cell_waiting_time / (double) orcirc->processed_cells; - smartlist_add(circuits_for_buffer_stats, stat); orcirc->total_cell_waiting_time = 0; orcirc->processed_cells = 0; + rep_hist_add_buffer_stats(mean_num_cells_in_queue, + mean_time_cells_in_queue, + processed_cells); } /** Sorting helper: return -1, 1, or 0 based on comparison of two @@ -2430,136 +2442,536 @@ _buffer_stats_compare_entries(const void **_a, const void **_b) void rep_hist_buffer_stats_term(void) { - start_of_buffer_stats_interval = 0; + rep_hist_reset_buffer_stats(0); +} + +/** Clear history of circuit statistics and set the measurement interval + * start to <b>now</b>. */ +void +rep_hist_reset_buffer_stats(time_t now) +{ if (!circuits_for_buffer_stats) circuits_for_buffer_stats = smartlist_create(); SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *, stat, tor_free(stat)); smartlist_clear(circuits_for_buffer_stats); + start_of_buffer_stats_interval = now; } -/** Write buffer statistics to $DATADIR/stats/buffer-stats and return when - * we would next want to write exit stats. */ -time_t -rep_hist_buffer_stats_write(time_t now) +/** Return a newly allocated string containing the buffer statistics until + * <b>now</b>, or NULL if we're not collecting buffer stats. Caller must + * ensure start_of_buffer_stats_interval is in the past. */ +char * +rep_hist_format_buffer_stats(time_t now) { - char *statsdir = NULL, *filename = NULL; - char written[ISO_TIME_LEN+1]; - open_file_t *open_file = NULL; - FILE *out; #define SHARES 10 int processed_cells[SHARES], circs_in_share[SHARES], number_of_circuits, i; double queued_cells[SHARES], time_in_queue[SHARES]; - smartlist_t *str_build = NULL; - char *str = NULL, *buf = NULL; - circuit_t *circ; + char *buf = NULL; + smartlist_t *processed_cells_strings, *queued_cells_strings, + *time_in_queue_strings; + char *processed_cells_string, *queued_cells_string, + *time_in_queue_string; + char t[ISO_TIME_LEN+1]; + char *result; if (!start_of_buffer_stats_interval) - return 0; /* Not initialized. */ - if (start_of_buffer_stats_interval + WRITE_STATS_INTERVAL > now) - goto done; /* Not ready to write */ + return NULL; /* Not initialized. */ - str_build = smartlist_create(); + tor_assert(now >= start_of_buffer_stats_interval); - /* add current circuits to stats */ - for (circ = _circuit_get_global_list(); circ; circ = circ->next) - rep_hist_buffer_stats_add_circ(circ, now); - /* calculate deciles */ + /* Calculate deciles if we saw at least one circuit. */ memset(processed_cells, 0, SHARES * sizeof(int)); memset(circs_in_share, 0, SHARES * sizeof(int)); memset(queued_cells, 0, SHARES * sizeof(double)); memset(time_in_queue, 0, SHARES * sizeof(double)); if (!circuits_for_buffer_stats) circuits_for_buffer_stats = smartlist_create(); - smartlist_sort(circuits_for_buffer_stats, - _buffer_stats_compare_entries); number_of_circuits = smartlist_len(circuits_for_buffer_stats); - if (number_of_circuits < 1) { - log_info(LD_HIST, "Attempt to write cell statistics to disk failed. " - "We haven't seen a single circuit to report about."); - goto done; + if (number_of_circuits > 0) { + smartlist_sort(circuits_for_buffer_stats, + _buffer_stats_compare_entries); + i = 0; + SMARTLIST_FOREACH_BEGIN(circuits_for_buffer_stats, + circ_buffer_stats_t *, stat) + { + int share = i++ * SHARES / number_of_circuits; + processed_cells[share] += stat->processed_cells; + queued_cells[share] += stat->mean_num_cells_in_queue; + time_in_queue[share] += stat->mean_time_cells_in_queue; + circs_in_share[share]++; + } + SMARTLIST_FOREACH_END(stat); } - i = 0; - SMARTLIST_FOREACH_BEGIN(circuits_for_buffer_stats, - circ_buffer_stats_t *, stat) - { - int share = i++ * SHARES / number_of_circuits; - processed_cells[share] += stat->processed_cells; - queued_cells[share] += stat->mean_num_cells_in_queue; - time_in_queue[share] += stat->mean_time_cells_in_queue; - circs_in_share[share]++; - } - SMARTLIST_FOREACH_END(stat); - /* clear buffer stats history */ - SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *, - stat, tor_free(stat)); - smartlist_clear(circuits_for_buffer_stats); - /* write to file */ - statsdir = get_datadir_fname("stats"); - if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) - goto done; - filename = get_datadir_fname2("stats", "buffer-stats"); - out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND | O_TEXT, - 0600, &open_file); - if (!out) - goto done; - format_iso_time(written, now); - if (fprintf(out, "cell-stats-end %s (%d s)\n", written, - (unsigned) (now - start_of_buffer_stats_interval)) < 0) - goto done; + + /* Write deciles to strings. */ + processed_cells_strings = smartlist_create(); + queued_cells_strings = smartlist_create(); + time_in_queue_strings = smartlist_create(); for (i = 0; i < SHARES; i++) { tor_asprintf(&buf,"%d", !circs_in_share[i] ? 0 : processed_cells[i] / circs_in_share[i]); - smartlist_add(str_build, buf); + smartlist_add(processed_cells_strings, buf); } - str = smartlist_join_strings(str_build, ",", 0, NULL); - if (fprintf(out, "cell-processed-cells %s\n", str) < 0) - goto done; - tor_free(str); - SMARTLIST_FOREACH(str_build, char *, c, tor_free(c)); - smartlist_clear(str_build); for (i = 0; i < SHARES; i++) { tor_asprintf(&buf, "%.2f", circs_in_share[i] == 0 ? 0.0 : queued_cells[i] / (double) circs_in_share[i]); - smartlist_add(str_build, buf); + smartlist_add(queued_cells_strings, buf); } - str = smartlist_join_strings(str_build, ",", 0, NULL); - if (fprintf(out, "cell-queued-cells %s\n", str) < 0) - goto done; - tor_free(str); - SMARTLIST_FOREACH(str_build, char *, c, tor_free(c)); - smartlist_clear(str_build); for (i = 0; i < SHARES; i++) { tor_asprintf(&buf, "%.0f", circs_in_share[i] == 0 ? 0.0 : time_in_queue[i] / (double) circs_in_share[i]); - smartlist_add(str_build, buf); + smartlist_add(time_in_queue_strings, buf); } - str = smartlist_join_strings(str_build, ",", 0, NULL); - if (fprintf(out, "cell-time-in-queue %s\n", str) < 0) - goto done; - tor_free(str); - SMARTLIST_FOREACH(str_build, char *, c, tor_free(c)); - smartlist_free(str_build); - str_build = NULL; - if (fprintf(out, "cell-circuits-per-decile %d\n", - (number_of_circuits + SHARES - 1) / SHARES) < 0) + + /* Join all observations in single strings. */ + processed_cells_string = smartlist_join_strings(processed_cells_strings, + ",", 0, NULL); + queued_cells_string = smartlist_join_strings(queued_cells_strings, + ",", 0, NULL); + time_in_queue_string = smartlist_join_strings(time_in_queue_strings, + ",", 0, NULL); + SMARTLIST_FOREACH(processed_cells_strings, char *, cp, tor_free(cp)); + SMARTLIST_FOREACH(queued_cells_strings, char *, cp, tor_free(cp)); + SMARTLIST_FOREACH(time_in_queue_strings, char *, cp, tor_free(cp)); + smartlist_free(processed_cells_strings); + smartlist_free(queued_cells_strings); + smartlist_free(time_in_queue_strings); + + /* Put everything together. */ + format_iso_time(t, now); + tor_asprintf(&result, "cell-stats-end %s (%d s)\n" + "cell-processed-cells %s\n" + "cell-queued-cells %s\n" + "cell-time-in-queue %s\n" + "cell-circuits-per-decile %d\n", + t, (unsigned) (now - start_of_buffer_stats_interval), + processed_cells_string, + queued_cells_string, + time_in_queue_string, + (number_of_circuits + SHARES - 1) / SHARES); + tor_free(processed_cells_string); + tor_free(queued_cells_string); + tor_free(time_in_queue_string); + return result; +#undef SHARES +} + +/** If 24 hours have passed since the beginning of the current buffer + * stats period, write buffer stats to $DATADIR/stats/buffer-stats + * (possibly overwriting an existing file) and reset counters. Return + * when we would next want to write buffer stats or 0 if we never want to + * write. */ +time_t +rep_hist_buffer_stats_write(time_t now) +{ + circuit_t *circ; + char *statsdir = NULL, *filename = NULL, *str = NULL; + + if (!start_of_buffer_stats_interval) + return 0; /* Not initialized. */ + if (start_of_buffer_stats_interval + WRITE_STATS_INTERVAL > now) + goto done; /* Not ready to write */ + + /* Add open circuits to the history. */ + for (circ = _circuit_get_global_list(); circ; circ = circ->next) { + rep_hist_buffer_stats_add_circ(circ, now); + } + + /* Generate history string. */ + str = rep_hist_format_buffer_stats(now); + + /* Reset both buffer history and counters of open circuits. */ + rep_hist_reset_buffer_stats(now); + + /* Try to write to disk. */ + statsdir = get_datadir_fname("stats"); + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); goto done; - finish_writing_to_file(open_file); - open_file = NULL; - start_of_buffer_stats_interval = now; + } + filename = get_datadir_fname2("stats", "buffer-stats"); + if (write_str_to_file(filename, str, 0) < 0) + log_warn(LD_HIST, "Unable to write buffer stats to disk!"); + done: - if (open_file) - abort_writing_to_file(open_file); + tor_free(str); tor_free(filename); tor_free(statsdir); - if (str_build) { - SMARTLIST_FOREACH(str_build, char *, c, tor_free(c)); - smartlist_free(str_build); + return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL; +} + +/*** Descriptor serving statistics ***/ + +/** Digestmap to track which descriptors were downloaded this stats + * collection interval. It maps descriptor digest to pointers to 1, + * effectively turning this into a list. */ +static digestmap_t *served_descs = NULL; + +/** Number of how many descriptors were downloaded in total during this + * interval. */ +static unsigned long total_descriptor_downloads; + +/** Start time of served descs stats or 0 if we're not collecting those. */ +static time_t start_of_served_descs_stats_interval; + +/** Initialize descriptor stats. */ +void +rep_hist_desc_stats_init(time_t now) +{ + if (served_descs) { + log_warn(LD_BUG, "Called rep_hist_desc_stats_init() when desc stats were " + "already initialized. This is probably harmless."); + return; // Already initialized } + served_descs = digestmap_new(); + total_descriptor_downloads = 0; + start_of_served_descs_stats_interval = now; +} + +/** Reset served descs stats to empty, starting a new interval <b>now</b>. */ +static void +rep_hist_reset_desc_stats(time_t now) +{ + rep_hist_desc_stats_term(); + rep_hist_desc_stats_init(now); +} + +/** Stop collecting served descs stats, so that rep_hist_desc_stats_init() is + * safe to be called again. */ +void +rep_hist_desc_stats_term(void) +{ + digestmap_free(served_descs, NULL); + served_descs = NULL; + start_of_served_descs_stats_interval = 0; + total_descriptor_downloads = 0; +} + +/** Helper for rep_hist_desc_stats_write(). Return a newly allocated string + * containing the served desc statistics until now, or NULL if we're not + * collecting served desc stats. Caller must ensure that now is not before + * start_of_served_descs_stats_interval. */ +static char * +rep_hist_format_desc_stats(time_t now) +{ + char t[ISO_TIME_LEN+1]; + char *result; + + digestmap_iter_t *iter; + const char *key; + void *val; + unsigned size; + int *vals; + int n = 0; + + if (!start_of_served_descs_stats_interval) + return NULL; + + size = digestmap_size(served_descs); + vals = tor_malloc(size * sizeof(int)); + + for (iter = digestmap_iter_init(served_descs); !digestmap_iter_done(iter); + iter = digestmap_iter_next(served_descs, iter) ) { + uintptr_t count; + digestmap_iter_get(iter, &key, &val); + count = (uintptr_t)val; + vals[n++] = (int)count; + (void)key; + } + + format_iso_time(t, now); + + tor_asprintf(&result, + "served-descs-stats-end %s (%d s) total=%lu unique=%u " + "max=%d q3=%d md=%d q1=%d min=%d\n", + t, + (unsigned) (now - start_of_served_descs_stats_interval), + total_descriptor_downloads, + size, + find_nth_int(vals, size, size-1), + find_nth_int(vals, size, (3*size-1)/4), + find_nth_int(vals, size, (size-1)/2), + find_nth_int(vals, size, (size-1)/4), + find_nth_int(vals, size, 0)); + + tor_free(vals); + return result; +} + +/** If WRITE_STATS_INTERVAL seconds have passed since the beginning of + * the current served desc stats interval, write the stats to + * $DATADIR/stats/served-desc-stats (possibly appending to an existing file) + * and reset the state for the next interval. Return when we would next want + * to write served desc stats or 0 if we won't want to write. */ +time_t +rep_hist_desc_stats_write(time_t now) +{ + char *statsdir = NULL, *filename = NULL, *str = NULL; + + if (!start_of_served_descs_stats_interval) + return 0; /* We're not collecting stats. */ + if (start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL > now) + return start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL; + + str = rep_hist_format_desc_stats(now); + + statsdir = get_datadir_fname("stats"); + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); + goto done; + } + filename = get_datadir_fname2("stats", "served-desc-stats"); + if (append_bytes_to_file(filename, str, strlen(str), 0) < 0) + log_warn(LD_HIST, "Unable to write served descs statistics to disk!"); + + rep_hist_reset_desc_stats(now); + + done: + tor_free(statsdir); + tor_free(filename); tor_free(str); -#undef SHARES - return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL; + return start_of_served_descs_stats_interval + WRITE_STATS_INTERVAL; +} + +void +rep_hist_note_desc_served(const char * desc) +{ + void *val; + uintptr_t count; + if (!served_descs) + return; // We're not collecting stats + val = digestmap_get(served_descs, desc); + count = (uintptr_t)val; + if (count != INT_MAX) + ++count; + digestmap_set(served_descs, desc, (void*)count); + total_descriptor_downloads++; +} + +/*** Connection statistics ***/ + +/** Start of the current connection stats interval or 0 if we're not + * collecting connection statistics. */ +static time_t start_of_conn_stats_interval; + +/** Initialize connection stats. */ +void +rep_hist_conn_stats_init(time_t now) +{ + start_of_conn_stats_interval = now; +} + +/* Count connections that we read and wrote less than these many bytes + * from/to as below threshold. */ +#define BIDI_THRESHOLD 20480 + +/* Count connections that we read or wrote at least this factor as many + * bytes from/to than we wrote or read to/from as mostly reading or + * writing. */ +#define BIDI_FACTOR 10 + +/* Interval length in seconds for considering read and written bytes for + * connection stats. */ +#define BIDI_INTERVAL 10 + +/* Start of next BIDI_INTERVAL second interval. */ +static time_t bidi_next_interval = 0; + +/* Number of connections that we read and wrote less than BIDI_THRESHOLD + * bytes from/to in BIDI_INTERVAL seconds. */ +static uint32_t below_threshold = 0; + +/* Number of connections that we read at least BIDI_FACTOR times more + * bytes from than we wrote to in BIDI_INTERVAL seconds. */ +static uint32_t mostly_read = 0; + +/* Number of connections that we wrote at least BIDI_FACTOR times more + * bytes to than we read from in BIDI_INTERVAL seconds. */ +static uint32_t mostly_written = 0; + +/* Number of connections that we read and wrote at least BIDI_THRESHOLD + * bytes from/to, but not BIDI_FACTOR times more in either direction in + * BIDI_INTERVAL seconds. */ +static uint32_t both_read_and_written = 0; + +/* Entry in a map from connection ID to the number of read and written + * bytes on this connection in a BIDI_INTERVAL second interval. */ +typedef struct bidi_map_entry_t { + HT_ENTRY(bidi_map_entry_t) node; + uint64_t conn_id; /**< Connection ID */ + size_t read; /**< Number of read bytes */ + size_t written; /**< Number of written bytes */ +} bidi_map_entry_t; + +/** Map of OR connections together with the number of read and written + * bytes in the current BIDI_INTERVAL second interval. */ +static HT_HEAD(bidimap, bidi_map_entry_t) bidi_map = + HT_INITIALIZER(); + +static int +bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b) +{ + return a->conn_id == b->conn_id; +} + +static unsigned +bidi_map_ent_hash(const bidi_map_entry_t *entry) +{ + return (unsigned) entry->conn_id; +} + +HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, + bidi_map_ent_eq); +HT_GENERATE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, + bidi_map_ent_eq, 0.6, malloc, realloc, free); + +static void +bidi_map_free(void) +{ + bidi_map_entry_t **ptr, **next, *ent; + for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { + ent = *ptr; + next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); + tor_free(ent); + } + HT_CLEAR(bidimap, &bidi_map); +} + +/** Reset counters for conn statistics. */ +void +rep_hist_reset_conn_stats(time_t now) +{ + start_of_conn_stats_interval = now; + below_threshold = 0; + mostly_read = 0; + mostly_written = 0; + both_read_and_written = 0; + bidi_map_free(); +} + +/** Stop collecting connection stats in a way that we can re-start doing + * so in rep_hist_conn_stats_init(). */ +void +rep_hist_conn_stats_term(void) +{ + rep_hist_reset_conn_stats(0); +} + +/** We read <b>num_read</b> bytes and wrote <b>num_written</b> from/to OR + * connection <b>conn_id</b> in second <b>when</b>. If this is the first + * observation in a new interval, sum up the last observations. Add bytes + * for this connection. */ +void +rep_hist_note_or_conn_bytes(uint64_t conn_id, size_t num_read, + size_t num_written, time_t when) +{ + if (!start_of_conn_stats_interval) + return; + /* Initialize */ + if (bidi_next_interval == 0) + bidi_next_interval = when + BIDI_INTERVAL; + /* Sum up last period's statistics */ + if (when >= bidi_next_interval) { + bidi_map_entry_t **ptr, **next, *ent; + for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) { + ent = *ptr; + if (ent->read + ent->written < BIDI_THRESHOLD) + below_threshold++; + else if (ent->read >= ent->written * BIDI_FACTOR) + mostly_read++; + else if (ent->written >= ent->read * BIDI_FACTOR) + mostly_written++; + else + both_read_and_written++; + next = HT_NEXT_RMV(bidimap, &bidi_map, ptr); + tor_free(ent); + } + while (when >= bidi_next_interval) + bidi_next_interval += BIDI_INTERVAL; + log_info(LD_GENERAL, "%d below threshold, %d mostly read, " + "%d mostly written, %d both read and written.", + below_threshold, mostly_read, mostly_written, + both_read_and_written); + } + /* Add this connection's bytes. */ + if (num_read > 0 || num_written > 0) { + bidi_map_entry_t *entry, lookup; + lookup.conn_id = conn_id; + entry = HT_FIND(bidimap, &bidi_map, &lookup); + if (entry) { + entry->written += num_written; + entry->read += num_read; + } else { + entry = tor_malloc_zero(sizeof(bidi_map_entry_t)); + entry->conn_id = conn_id; + entry->written = num_written; + entry->read = num_read; + HT_INSERT(bidimap, &bidi_map, entry); + } + } +} + +/** Return a newly allocated string containing the connection statistics + * until <b>now</b>, or NULL if we're not collecting conn stats. Caller must + * ensure start_of_conn_stats_interval is in the past. */ +char * +rep_hist_format_conn_stats(time_t now) +{ + char *result, written[ISO_TIME_LEN+1]; + + if (!start_of_conn_stats_interval) + return NULL; /* Not initialized. */ + + tor_assert(now >= start_of_conn_stats_interval); + + format_iso_time(written, now); + tor_asprintf(&result, "conn-bi-direct %s (%d s) %d,%d,%d,%d\n", + written, + (unsigned) (now - start_of_conn_stats_interval), + below_threshold, + mostly_read, + mostly_written, + both_read_and_written); + return result; +} + +/** If 24 hours have passed since the beginning of the current conn stats + * period, write conn stats to $DATADIR/stats/conn-stats (possibly + * overwriting an existing file) and reset counters. Return when we would + * next want to write conn stats or 0 if we never want to write. */ +time_t +rep_hist_conn_stats_write(time_t now) +{ + char *statsdir = NULL, *filename = NULL, *str = NULL; + + if (!start_of_conn_stats_interval) + return 0; /* Not initialized. */ + if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now) + goto done; /* Not ready to write */ + + /* Generate history string. */ + str = rep_hist_format_conn_stats(now); + + /* Reset counters. */ + rep_hist_reset_conn_stats(now); + + /* Try to write to disk. */ + statsdir = get_datadir_fname("stats"); + if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) { + log_warn(LD_HIST, "Unable to create stats/ directory!"); + goto done; + } + filename = get_datadir_fname2("stats", "conn-stats"); + if (write_str_to_file(filename, str, 0) < 0) + log_warn(LD_HIST, "Unable to write conn stats to disk!"); + + done: + tor_free(str); + tor_free(filename); + tor_free(statsdir); + return start_of_conn_stats_interval + WRITE_STATS_INTERVAL; } /** Free all storage held by the OR/link history caches, by the @@ -2576,11 +2988,15 @@ rep_hist_free_all(void) tor_free(exit_streams); built_last_stability_doc_at = 0; predicted_ports_free(); + bidi_map_free(); + if (circuits_for_buffer_stats) { SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *, s, tor_free(s)); smartlist_free(circuits_for_buffer_stats); circuits_for_buffer_stats = NULL; } + rep_hist_desc_stats_term(); + total_descriptor_downloads = 0; } diff --git a/src/or/rephist.h b/src/or/rephist.h index b06a39ed59..0a3e46ae1a 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -77,6 +77,23 @@ void rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval); time_t rep_hist_buffer_stats_write(time_t now); void rep_hist_buffer_stats_term(void); +void rep_hist_add_buffer_stats(double mean_num_cells_in_queue, + double mean_time_cells_in_queue, uint32_t processed_cells); +char *rep_hist_format_buffer_stats(time_t now); +void rep_hist_reset_buffer_stats(time_t now); + +void rep_hist_desc_stats_init(time_t now); +void rep_hist_note_desc_served(const char * desc); +void rep_hist_desc_stats_term(void); +time_t rep_hist_desc_stats_write(time_t now); + +void rep_hist_conn_stats_init(time_t now); +void rep_hist_note_or_conn_bytes(uint64_t conn_id, size_t num_read, + size_t num_written, time_t when); +void rep_hist_reset_conn_stats(time_t now); +char *rep_hist_format_conn_stats(time_t now); +time_t rep_hist_conn_stats_write(time_t now); +void rep_hist_conn_stats_term(void); #endif diff --git a/src/or/router.c b/src/or/router.c index 365e888af9..c9f141b7f0 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -7,6 +7,7 @@ #define ROUTER_PRIVATE #include "or.h" +#include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" @@ -19,6 +20,7 @@ #include "hibernate.h" #include "main.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "relay.h" #include "rephist.h" @@ -82,6 +84,11 @@ static authority_cert_t *legacy_key_certificate = NULL; static void set_onion_key(crypto_pk_env_t *k) { + if (onionkey && !crypto_pk_cmp_keys(onionkey, k)) { + /* k is already our onion key; free it and return */ + crypto_free_pk_env(k); + return; + } tor_mutex_acquire(key_lock); crypto_free_pk_env(onionkey); onionkey = k; @@ -149,8 +156,8 @@ assert_identity_keys_ok(void) } 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(0!=crypto_pk_cmp_keys(client_identitykey, + server_identitykey)); } } @@ -493,8 +500,8 @@ init_keys(void) char digest[DIGEST_LEN]; char v3_digest[DIGEST_LEN]; char *cp; - or_options_t *options = get_options(); - authority_type_t type; + const or_options_t *options = get_options(); + dirinfo_type_t type; time_t now = time(NULL); trusted_dir_server_t *ds; int v3_digest_set = 0; @@ -695,11 +702,12 @@ init_keys(void) } /* 6b. [authdirserver only] add own key to approved directories. */ crypto_pk_get_digest(get_server_identity_key(), digest); - type = ((options->V1AuthoritativeDir ? V1_AUTHORITY : NO_AUTHORITY) | - (options->V2AuthoritativeDir ? V2_AUTHORITY : NO_AUTHORITY) | - (options->V3AuthoritativeDir ? V3_AUTHORITY : NO_AUTHORITY) | - (options->BridgeAuthoritativeDir ? BRIDGE_AUTHORITY : NO_AUTHORITY) | - (options->HSAuthoritativeDir ? HIDSERV_AUTHORITY : NO_AUTHORITY)); + type = ((options->V1AuthoritativeDir ? V1_DIRINFO : NO_DIRINFO) | + (options->V2AuthoritativeDir ? V2_DIRINFO : NO_DIRINFO) | + (options->V3AuthoritativeDir ? + (V3_DIRINFO|MICRODESC_DIRINFO|EXTRAINFO_DIRINFO) : NO_DIRINFO) | + (options->BridgeAuthoritativeDir ? BRIDGE_DIRINFO : NO_DIRINFO) | + (options->HSAuthoritativeDir ? HIDSERV_DIRINFO : NO_DIRINFO)); ds = router_get_trusteddirserver_by_digest(digest); if (!ds) { @@ -721,7 +729,7 @@ init_keys(void) type, ds->type); ds->type = type; } - if (v3_digest_set && (ds->type & V3_AUTHORITY) && + if (v3_digest_set && (ds->type & V3_DIRINFO) && tor_memneq(v3_digest, ds->v3_identity_digest, DIGEST_LEN)) { log_warn(LD_DIR, "V3 identity key does not match identity declared in " "DirServer line. Adjusting."); @@ -760,7 +768,7 @@ router_reset_reachability(void) int check_whether_orport_reachable(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); return options->AssumeReachable || can_reach_or_port; } @@ -769,7 +777,7 @@ check_whether_orport_reachable(void) int check_whether_dirport_reachable(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); return !options->DirPort || options->AssumeReachable || we_are_hibernating() || @@ -784,7 +792,7 @@ check_whether_dirport_reachable(void) * a DirPort. */ static int -decide_to_advertise_dirport(or_options_t *options, uint16_t dir_port) +decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) { static int advertising=1; /* start out assuming we will advertise */ int new_choice=1; @@ -849,14 +857,14 @@ decide_to_advertise_dirport(or_options_t *options, uint16_t dir_port) void consider_testing_reachability(int test_or, int test_dir) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); int orport_reachable = check_whether_orport_reachable(); tor_addr_t addr; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!me) return; - if (routerset_contains_router(options->ExcludeNodes, me) && + if (routerset_contains_router(options->ExcludeNodes, me, -1) && options->StrictNodes) { /* If we've excluded ourself, and StrictNodes is set, we can't test * ourself. */ @@ -876,11 +884,14 @@ consider_testing_reachability(int test_or, int test_dir) } if (test_or && (!orport_reachable || !circuit_enough_testing_circs())) { + extend_info_t *ei; log_info(LD_CIRC, "Testing %s of my ORPort: %s:%d.", !orport_reachable ? "reachability" : "bandwidth", me->address, me->or_port); - circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, - CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + ei = extend_info_from_router(me); + circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei, + CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); + extend_info_free(ei); } tor_addr_from_ipv4h(&addr, me->addr); @@ -903,11 +914,11 @@ consider_testing_reachability(int test_or, int test_dir) void router_orport_found_reachable(void) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); 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_AUTHORITY ? + get_options()->_PublishServerDescriptor != NO_DIRINFO ? " Publishing server descriptor." : ""); can_reach_or_port = 1; mark_my_descriptor_dirty("ORPort found reachable"); @@ -921,7 +932,7 @@ router_orport_found_reachable(void) void router_dirport_found_reachable(void) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!can_reach_dir_port && me) { log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable " "from the outside. Excellent."); @@ -967,7 +978,7 @@ router_perform_bandwidth_test(int num_circs, time_t now) * directory server. */ int -authdir_mode(or_options_t *options) +authdir_mode(const or_options_t *options) { return options->AuthoritativeDir != 0; } @@ -975,7 +986,7 @@ authdir_mode(or_options_t *options) * directory server. */ int -authdir_mode_v1(or_options_t *options) +authdir_mode_v1(const or_options_t *options) { return authdir_mode(options) && options->V1AuthoritativeDir != 0; } @@ -983,7 +994,7 @@ authdir_mode_v1(or_options_t *options) * directory server. */ int -authdir_mode_v2(or_options_t *options) +authdir_mode_v2(const or_options_t *options) { return authdir_mode(options) && options->V2AuthoritativeDir != 0; } @@ -991,13 +1002,13 @@ authdir_mode_v2(or_options_t *options) * directory server. */ int -authdir_mode_v3(or_options_t *options) +authdir_mode_v3(const or_options_t *options) { return authdir_mode(options) && options->V3AuthoritativeDir != 0; } /** Return true iff we are a v1, v2, or v3 directory authority. */ int -authdir_mode_any_main(or_options_t *options) +authdir_mode_any_main(const or_options_t *options) { return options->V1AuthoritativeDir || options->V2AuthoritativeDir || @@ -1006,7 +1017,7 @@ authdir_mode_any_main(or_options_t *options) /** Return true if we believe ourselves to be any kind of * authoritative directory beyond just a hidserv authority. */ int -authdir_mode_any_nonhidserv(or_options_t *options) +authdir_mode_any_nonhidserv(const or_options_t *options) { return options->BridgeAuthoritativeDir || authdir_mode_any_main(options); @@ -1015,7 +1026,7 @@ authdir_mode_any_nonhidserv(or_options_t *options) * authoritative about receiving and serving descriptors of type * <b>purpose</b> its dirport. Use -1 for "any purpose". */ int -authdir_mode_handles_descs(or_options_t *options, int purpose) +authdir_mode_handles_descs(const or_options_t *options, int purpose) { if (purpose < 0) return authdir_mode_any_nonhidserv(options); @@ -1030,7 +1041,7 @@ authdir_mode_handles_descs(or_options_t *options, int purpose) * publishes its own network statuses. */ int -authdir_mode_publishes_statuses(or_options_t *options) +authdir_mode_publishes_statuses(const or_options_t *options) { if (authdir_mode_bridge(options)) return 0; @@ -1040,7 +1051,7 @@ authdir_mode_publishes_statuses(or_options_t *options) * tests reachability of the descriptors it learns about. */ int -authdir_mode_tests_reachability(or_options_t *options) +authdir_mode_tests_reachability(const or_options_t *options) { return authdir_mode_handles_descs(options, -1); } @@ -1048,7 +1059,7 @@ authdir_mode_tests_reachability(or_options_t *options) * directory server. */ int -authdir_mode_bridge(or_options_t *options) +authdir_mode_bridge(const or_options_t *options) { return authdir_mode(options) && options->BridgeAuthoritativeDir != 0; } @@ -1056,7 +1067,7 @@ authdir_mode_bridge(or_options_t *options) /** Return true iff we are trying to be a server. */ int -server_mode(or_options_t *options) +server_mode(const or_options_t *options) { if (options->ClientOnly) return 0; return (options->ORPort != 0 || options->ORListenAddress); @@ -1065,7 +1076,7 @@ server_mode(or_options_t *options) /** Return true iff we are trying to be a non-bridge server. */ int -public_server_mode(or_options_t *options) +public_server_mode(const or_options_t *options) { if (!server_mode(options)) return 0; return (!options->BridgeRelay); @@ -1075,10 +1086,10 @@ public_server_mode(or_options_t *options) * in the consensus mean that we don't want to allow exits from circuits * we got from addresses not known to be servers. */ int -should_refuse_unknown_exits(or_options_t *options) +should_refuse_unknown_exits(const or_options_t *options) { - if (options->RefuseUnknownExits_ != -1) { - return options->RefuseUnknownExits_; + if (options->RefuseUnknownExits != -1) { + return options->RefuseUnknownExits; } else { return networkstatus_get_param(NULL, "refuseunknownexits", 1, 0, 1); } @@ -1105,14 +1116,12 @@ set_server_advertised(int s) server_is_advertised = s; } -/** Return true iff we are trying to be a socks proxy. */ +/** Return true iff we are trying to proxy client connections. */ int -proxy_mode(or_options_t *options) +proxy_mode(const or_options_t *options) { - return (options->SocksPort != 0 || - options->TransPort != 0 || - options->NATDPort != 0 || - options->DNSPort != 0); + (void)options; + return smartlist_len(get_configured_client_ports()) > 0; } /** Decide if we're a publishable server. We are a publishable server if: @@ -1128,11 +1137,11 @@ proxy_mode(or_options_t *options) static int decide_if_publishable_server(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->ClientOnly) return 0; - if (options->_PublishServerDescriptor == NO_AUTHORITY) + if (options->_PublishServerDescriptor == NO_DIRINFO) return 0; if (!server_mode(options)) return 0; @@ -1173,7 +1182,7 @@ consider_publishable_server(int force) * the one configured in the ORPort option, or the one we actually bound to * if ORPort is "auto". */ uint16_t -router_get_advertised_or_port(or_options_t *options) +router_get_advertised_or_port(const or_options_t *options) { if (options->ORPort == CFG_AUTO_PORT) { connection_t *c = connection_get_by_type(CONN_TYPE_OR_LISTENER); @@ -1190,7 +1199,7 @@ router_get_advertised_or_port(or_options_t *options) * the one configured in the DirPort option, * or the one we actually bound to if DirPort is "auto". */ uint16_t -router_get_advertised_dir_port(or_options_t *options, uint16_t dirport) +router_get_advertised_dir_port(const or_options_t *options, uint16_t dirport) { if (!options->DirPort) return dirport; @@ -1211,9 +1220,14 @@ router_get_advertised_dir_port(or_options_t *options, uint16_t dirport) static routerinfo_t *desc_routerinfo = NULL; /** My extrainfo */ static extrainfo_t *desc_extrainfo = NULL; +/** Why did we most recently decide to regenerate our descriptor? Used to + * tell the authorities why we're sending it to them. */ +static const char *desc_gen_reason = NULL; /** Since when has our descriptor been "clean"? 0 if we need to regenerate it * now. */ static time_t desc_clean_since = 0; +/** Why did we mark the descriptor dirty? */ +static const char *desc_dirty_reason = NULL; /** Boolean: do we need to regenerate the above? */ static int desc_needs_upload = 0; @@ -1224,11 +1238,11 @@ static int desc_needs_upload = 0; void router_upload_dir_desc_to_dirservers(int force) { - routerinfo_t *ri; + const routerinfo_t *ri; extrainfo_t *ei; char *msg; size_t desc_len, extra_len = 0, total_len; - authority_type_t auth = get_options()->_PublishServerDescriptor; + dirinfo_type_t auth = get_options()->_PublishServerDescriptor; ri = router_get_my_routerinfo(); if (!ri) { @@ -1236,7 +1250,7 @@ router_upload_dir_desc_to_dirservers(int force) return; } ei = router_get_my_extrainfo(); - if (auth == NO_AUTHORITY) + if (auth == NO_DIRINFO) return; if (!force && !desc_needs_upload) return; @@ -1257,7 +1271,7 @@ router_upload_dir_desc_to_dirservers(int force) msg[desc_len+extra_len] = 0; directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_DIR, - (auth & BRIDGE_AUTHORITY) ? + (auth & BRIDGE_DIRINFO) ? ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL, auth, msg, desc_len, extra_len); @@ -1322,7 +1336,7 @@ router_extrainfo_digest_is_me(const char *digest) /** A wrapper around router_digest_is_me(). */ int -router_is_me(routerinfo_t *router) +router_is_me(const routerinfo_t *router) { return router_digest_is_me(router->cache_info.identity_digest); } @@ -1341,7 +1355,7 @@ router_fingerprint_is_me(const char *fp) /** Return a routerinfo for this OR, rebuilding a fresh one if * necessary. Return NULL on error, or if called on an OP. */ -routerinfo_t * +const routerinfo_t * router_get_my_routerinfo(void) { if (!server_mode(get_options())) @@ -1380,6 +1394,14 @@ router_get_my_extrainfo(void) return desc_extrainfo; } +/** Return a human-readable string describing what triggered us to generate + * our current descriptor, or NULL if we don't know. */ +const char * +router_get_descriptor_gen_reason(void) +{ + return desc_gen_reason; +} + /** A list of nicknames that we've warned about including in our family * declaration verbatim rather than as digests. */ static smartlist_t *warned_nonexistent_family = NULL; @@ -1391,10 +1413,8 @@ static int router_guess_address_from_dir_headers(uint32_t *guess); * dirserver headers. Place the answer in *<b>addr</b> and return * 0 on success, else return -1 if we have no guess. */ int -router_pick_published_address(or_options_t *options, uint32_t *addr) +router_pick_published_address(const or_options_t *options, uint32_t *addr) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; if (resolve_my_address(LOG_INFO, options, addr, NULL) < 0) { log_info(LD_CONFIG, "Could not determine our address locally. " "Checking if directory headers provide any hints."); @@ -1404,9 +1424,7 @@ router_pick_published_address(or_options_t *options, uint32_t *addr) return -1; } } - a.s_addr = htonl(*addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_CONFIG,"Success: chose address '%s'.", buf); + log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr)); return 0; } @@ -1422,7 +1440,7 @@ router_rebuild_descriptor(int force) uint32_t addr; char platform[256]; int hibernating = we_are_hibernating(); - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (desc_clean_since && !force) return 0; @@ -1476,13 +1494,12 @@ router_rebuild_descriptor(int force) ri->policy_is_reject_star = policy_is_reject_star(ri->exit_policy); - if (desc_routerinfo) { /* inherit values */ - ri->is_valid = desc_routerinfo->is_valid; - ri->is_running = desc_routerinfo->is_running; - ri->is_named = desc_routerinfo->is_named; - } +#if 0 + /* XXXX NM NM I belive this is safe to remove */ if (authdir_mode(options)) ri->is_valid = ri->is_named = 1; /* believe in yourself */ +#endif + if (options->MyFamily) { smartlist_t *family; if (!warned_nonexistent_family) @@ -1491,13 +1508,12 @@ router_rebuild_descriptor(int force) ri->declared_family = smartlist_create(); smartlist_split_string(family, options->MyFamily, ",", SPLIT_SKIP_SPACE|SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH(family, char *, name, - { - routerinfo_t *member; + SMARTLIST_FOREACH_BEGIN(family, char *, name) { + const node_t *member; if (!strcasecmp(name, options->Nickname)) - member = ri; + goto skip; /* Don't list ourself, that's redundant */ else - member = router_get_by_nickname(name, 1); + member = node_get_by_nickname(name, 1); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_string_isin(warned_nonexistent_family, name) && @@ -1517,19 +1533,21 @@ router_rebuild_descriptor(int force) smartlist_add(ri->declared_family, name); name = NULL; } - } else if (router_is_me(member)) { + } else if (router_digest_is_me(member->identity)) { /* Don't list ourself in our own family; that's redundant */ + /* XXX shouldn't be possible */ } else { char *fp = tor_malloc(HEX_DIGEST_LEN+2); fp[0] = '$'; base16_encode(fp+1,HEX_DIGEST_LEN+1, - member->cache_info.identity_digest, DIGEST_LEN); + member->identity, DIGEST_LEN); smartlist_add(ri->declared_family, fp); if (smartlist_string_isin(warned_nonexistent_family, name)) smartlist_string_remove(warned_nonexistent_family, name); } + skip: tor_free(name); - }); + } SMARTLIST_FOREACH_END(name); /* remove duplicates from the list */ smartlist_sort_strings(ri->declared_family); @@ -1590,8 +1608,6 @@ router_rebuild_descriptor(int force) strlen(ri->cache_info.signed_descriptor_body), ri->cache_info.signed_descriptor_digest); - routerinfo_set_country(ri); - if (ei) { tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL)); } @@ -1603,16 +1619,56 @@ router_rebuild_descriptor(int force) desc_clean_since = time(NULL); desc_needs_upload = 1; + desc_gen_reason = desc_dirty_reason; + desc_dirty_reason = NULL; control_event_my_descriptor_changed(); return 0; } -/** Mark descriptor out of date if it's older than <b>when</b> */ +/** If our router descriptor ever goes this long without being regenerated + * because something changed, we force an immediate regenerate-and-upload. */ +#define FORCE_REGENERATE_DESCRIPTOR_INTERVAL (18*60*60) + +/** If our router descriptor seems to be missing or unacceptable according + * to the authorities, regenerate and reupload it _this_ often. */ +#define FAST_RETRY_DESCRIPTOR_INTERVAL (90*60) + +/** Mark descriptor out of date if it's been "too long" since we last tried + * to upload one. */ void -mark_my_descriptor_dirty_if_older_than(time_t when) +mark_my_descriptor_dirty_if_too_old(time_t now) { - if (desc_clean_since < when) + networkstatus_t *ns; + const routerstatus_t *rs; + const char *retry_fast_reason = NULL; /* Set if we should retry frequently */ + const time_t slow_cutoff = now - FORCE_REGENERATE_DESCRIPTOR_INTERVAL; + const time_t fast_cutoff = now - FAST_RETRY_DESCRIPTOR_INTERVAL; + + /* If it's already dirty, don't mark it. */ + if (! desc_clean_since) + return; + + /* If it's older than FORCE_REGENERATE_DESCRIPTOR_INTERVAL, it's always + * time to rebuild it. */ + if (desc_clean_since < slow_cutoff) { mark_my_descriptor_dirty("time for new descriptor"); + return; + } + /* Now we see whether we want to be retrying frequently or no. The + * rule here is that we'll retry frequently if we aren't listed in the + * live consensus we have, or if the publication time of the + * descriptor listed for us in the consensus is very old. */ + ns = networkstatus_get_live_consensus(now); + if (ns) { + rs = networkstatus_vote_find_entry(ns, server_identitykey_digest); + if (rs == NULL) + retry_fast_reason = "not listed in consensus"; + else if (rs->published_on < slow_cutoff) + retry_fast_reason = "version listed in consensus is quite old"; + } + + if (retry_fast_reason && desc_clean_since < fast_cutoff) + mark_my_descriptor_dirty(retry_fast_reason); } /** Call when the current descriptor is out of date. */ @@ -1621,6 +1677,8 @@ mark_my_descriptor_dirty(const char *reason) { desc_clean_since = 0; log_info(LD_OR, "Decided to publish new relay descriptor: %s", reason); + if (!desc_dirty_reason) + desc_dirty_reason = reason; } /** How frequently will we republish our descriptor because of large (factor @@ -1686,7 +1744,7 @@ void check_descriptor_ipaddress_changed(time_t now) { uint32_t prev, cur; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); (void) now; if (!desc_routerinfo) @@ -1718,7 +1776,7 @@ router_new_address_suggestion(const char *suggestion, { uint32_t addr, cur = 0; struct in_addr in; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); /* first, learn what the IP address actually is */ if (!tor_inet_aton(suggestion, &in)) { @@ -1817,7 +1875,7 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, int result=0; addr_policy_t *tmpe; char *family_line; - or_options_t *options = get_options(); + 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)) { @@ -2059,7 +2117,7 @@ int extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, crypto_pk_env_t *ident_key) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); char identity[HEX_DIGEST_LEN+1]; char published[ISO_TIME_LEN+1]; char digest[DIGEST_LEN]; @@ -2083,6 +2141,12 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, tor_free(bandwidth_usage); smartlist_add(chunks, pre); + if (geoip_is_loaded()) { + char *chunk=NULL; + tor_asprintf(&chunk, "geoip-db-digest %s\n", geoip_db_digest()); + smartlist_add(chunks, chunk); + } + if (options->ExtraInfoStatistics && write_stats_to_extrainfo) { log_info(LD_GENERAL, "Adding stats to extra-info descriptor."); if (options->DirReqStatistics && @@ -2105,6 +2169,11 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, "exit-stats-end", now, &contents) > 0) { smartlist_add(chunks, contents); } + if (options->ConnDirectionStatistics && + load_stats_file("stats"PATH_SEPARATOR"conn-stats", + "conn-bi-direct", now, &contents) > 0) { + smartlist_add(chunks, contents); + } } if (should_record_bridge_info(options) && write_stats_to_extrainfo) { @@ -2291,13 +2360,45 @@ router_get_description(char *buf, const routerinfo_t *ri) return "<null>"; return format_node_description(buf, ri->cache_info.identity_digest, - ri->is_named, + router_is_named(ri), ri->nickname, NULL, ri->addr); } /** Use <b>buf</b> (which must be at least NODE_DESC_BUF_LEN bytes long) to + * hold a human-readable description of <b>node</b>. + * + * Return a pointer to the front of <b>buf</b>. + */ +const char * +node_get_description(char *buf, const node_t *node) +{ + const char *nickname = NULL; + uint32_t addr32h = 0; + int is_named = 0; + + if (!node) + return "<null>"; + + if (node->rs) { + nickname = node->rs->nickname; + is_named = node->rs->is_named; + addr32h = node->rs->addr; + } else if (node->ri) { + nickname = node->ri->nickname; + addr32h = node->ri->addr; + } + + return format_node_description(buf, + node->identity, + is_named, + nickname, + NULL, + addr32h); +} + +/** Use <b>buf</b> (which must be at least NODE_DESC_BUF_LEN bytes long) to * hold a human-readable description of <b>rs</b>. * * Return a pointer to the front of <b>buf</b>. @@ -2345,6 +2446,18 @@ router_describe(const routerinfo_t *ri) return router_get_description(buf, ri); } +/** Return a human-readable description of the node_t <b>node</b>. + * + * This function is not thread-safe. Each call to this function invalidates + * previous values returned by this function. + */ +const char * +node_describe(const node_t *node) +{ + static char buf[NODE_DESC_BUF_LEN]; + return node_get_description(buf, node); +} + /** Return a human-readable description of the routerstatus_t <b>rs</b>. * * This function is not thread-safe. Each call to this function invalidates @@ -2379,10 +2492,15 @@ extend_info_describe(const extend_info_t *ei) void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { + const char *good_digest = networkstatus_get_router_digest_by_nickname( + router->nickname); + int is_named = good_digest && tor_memeq(good_digest, + router->cache_info.identity_digest, + DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = router->is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/router.h b/src/or/router.h index 3733099f93..f9d156cb09 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -39,30 +39,30 @@ void router_orport_found_reachable(void); void router_dirport_found_reachable(void); void router_perform_bandwidth_test(int num_circs, time_t now); -int authdir_mode(or_options_t *options); -int authdir_mode_v1(or_options_t *options); -int authdir_mode_v2(or_options_t *options); -int authdir_mode_v3(or_options_t *options); -int authdir_mode_any_main(or_options_t *options); -int authdir_mode_any_nonhidserv(or_options_t *options); -int authdir_mode_handles_descs(or_options_t *options, int purpose); -int authdir_mode_publishes_statuses(or_options_t *options); -int authdir_mode_tests_reachability(or_options_t *options); -int authdir_mode_bridge(or_options_t *options); +int authdir_mode(const or_options_t *options); +int authdir_mode_v1(const or_options_t *options); +int authdir_mode_v2(const or_options_t *options); +int authdir_mode_v3(const or_options_t *options); +int authdir_mode_any_main(const or_options_t *options); +int authdir_mode_any_nonhidserv(const or_options_t *options); +int authdir_mode_handles_descs(const or_options_t *options, int purpose); +int authdir_mode_publishes_statuses(const or_options_t *options); +int authdir_mode_tests_reachability(const or_options_t *options); +int authdir_mode_bridge(const or_options_t *options); -uint16_t router_get_advertised_or_port(or_options_t *options); -uint16_t router_get_advertised_dir_port(or_options_t *options, +uint16_t router_get_advertised_or_port(const or_options_t *options); +uint16_t router_get_advertised_dir_port(const or_options_t *options, uint16_t dirport); -int server_mode(or_options_t *options); -int public_server_mode(or_options_t *options); +int server_mode(const or_options_t *options); +int public_server_mode(const or_options_t *options); int advertised_server_mode(void); -int proxy_mode(or_options_t *options); +int proxy_mode(const or_options_t *options); void consider_publishable_server(int force); -int should_refuse_unknown_exits(or_options_t *options); +int should_refuse_unknown_exits(const or_options_t *options); void router_upload_dir_desc_to_dirservers(int force); -void mark_my_descriptor_dirty_if_older_than(time_t when); +void mark_my_descriptor_dirty_if_too_old(time_t now); void mark_my_descriptor_dirty(const char *reason); void check_descriptor_bandwidth_changed(time_t now); void check_descriptor_ipaddress_changed(time_t now); @@ -70,14 +70,15 @@ 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_my_exit_policy_is_reject_star(void); -routerinfo_t *router_get_my_routerinfo(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); int router_extrainfo_digest_is_me(const char *digest); -int router_is_me(routerinfo_t *router); +int router_is_me(const routerinfo_t *router); int router_fingerprint_is_me(const char *fp); -int router_pick_published_address(or_options_t *options, uint32_t *addr); +int router_pick_published_address(const or_options_t *options, uint32_t *addr); int router_rebuild_descriptor(int force); int router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, crypto_pk_env_t *ident_key); @@ -102,9 +103,11 @@ const char *format_node_description(char *buf, const tor_addr_t *addr, uint32_t addr32h); const char *router_get_description(char *buf, const routerinfo_t *ri); +const char *node_get_description(char *buf, const node_t *node); const char *routerstatus_get_description(char *buf, const routerstatus_t *rs); const char *extend_info_get_description(char *buf, const extend_info_t *ei); const char *router_describe(const routerinfo_t *ri); +const char *node_describe(const node_t *node); const char *routerstatus_describe(const routerstatus_t *ri); const char *extend_info_describe(const extend_info_t *ei); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index f8df089a8f..d97b978f43 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -22,7 +22,9 @@ #include "geoip.h" #include "hibernate.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "rendcommon.h" @@ -37,22 +39,25 @@ /****************************************************************************/ /* static function prototypes */ -static routerstatus_t *router_pick_directory_server_impl( - authority_type_t auth, int flags); -static routerstatus_t *router_pick_trusteddirserver_impl( - authority_type_t auth, int flags, int *n_busy_out); +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); -static int router_nickname_matches(routerinfo_t *router, const char *nickname); +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 launch_router_descriptor_downloads(smartlist_t *downloadable, - routerstatus_t *source, - time_t now); 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(signed_descriptor_t *desc, - int with_annotations); +static const char *signed_descriptor_get_body_impl( + const signed_descriptor_t *desc, + int with_annotations); static void list_pending_downloads(digestmap_t *result, int purpose, const char *prefix); +static void launch_dummy_descriptor_download_as_needed(time_t now, + const or_options_t *options); DECLARE_TYPED_DIGESTMAP_FNS(sdmap_, digest_sd_map_t, signed_descriptor_t) DECLARE_TYPED_DIGESTMAP_FNS(rimap_, digest_ri_map_t, routerinfo_t) @@ -94,7 +99,7 @@ static smartlist_t *warned_nicknames = NULL; /** The last time we tried to download any routerdesc, or 0 for "never". We * use this to rate-limit download attempts when the number of routerdescs to * download is low. */ -static time_t last_routerdesc_download_attempted = 0; +static time_t last_descriptor_download_attempted = 0; /** When we last computed the weights to use for bandwidths on directory * requests, what were the total weighted bandwidth, and our share of that @@ -106,7 +111,7 @@ static uint64_t sl_last_total_weighted_bw = 0, /** Return the number of directory authorities whose type matches some bit set * in <b>type</b> */ int -get_n_authorities(authority_type_t type) +get_n_authorities(dirinfo_type_t type) { int n = 0; if (!trusted_dir_servers) @@ -117,7 +122,7 @@ get_n_authorities(authority_type_t type) return n; } -#define get_n_v2_authorities() get_n_authorities(V2_AUTHORITY) +#define get_n_v2_authorities() get_n_authorities(V2_DIRINFO) /** Helper: Return the cert_list_t for an authority whose authority ID is * <b>id_digest</b>, allocating a new list if necessary. */ @@ -312,6 +317,7 @@ trusted_dirs_remove_old_certs(void) time_t now = time(NULL); #define DEAD_CERT_LIFETIME (2*24*60*60) #define OLD_CERT_LIFETIME (7*24*60*60) +#define CERT_EXPIRY_SKEW (60*60) if (!trusted_dir_certs) return; @@ -514,7 +520,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) } SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) { int found = 0; - if (!(ds->type & V3_AUTHORITY)) + if (!(ds->type & V3_DIRINFO)) continue; if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest)) continue; @@ -600,7 +606,7 @@ router_should_rebuild_store(desc_store_t *store) /** Return the desc_store_t in <b>rl</b> that should be used to store * <b>sd</b>. */ static INLINE desc_store_t * -desc_get_store(routerlist_t *rl, signed_descriptor_t *sd) +desc_get_store(routerlist_t *rl, const signed_descriptor_t *sd) { if (sd->is_extrainfo) return &rl->extrainfo_store; @@ -926,10 +932,10 @@ router_get_trusted_dir_servers(void) * Don't pick an authority if any non-authority is viable; try to avoid using * servers that have returned 503 recently. */ -routerstatus_t * -router_pick_directory_server(authority_type_t type, int flags) +const routerstatus_t * +router_pick_directory_server(dirinfo_type_t type, int flags) { - routerstatus_t *choice; + const routerstatus_t *choice; if (get_options()->PreferTunneledDirConns) flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; @@ -958,8 +964,8 @@ int router_get_my_share_of_directory_requests(double *v2_share_out, double *v3_share_out) { - routerinfo_t *me = router_get_my_routerinfo(); - routerstatus_t *rs; + const routerinfo_t *me = router_get_my_routerinfo(); + const routerstatus_t *rs; const int pds_flags = PDS_ALLOW_SELF|PDS_IGNORE_FASCISTFIREWALL; *v2_share_out = *v3_share_out = 0.0; if (!me) @@ -972,7 +978,7 @@ router_get_my_share_of_directory_requests(double *v2_share_out, /* XXXX This is a bit of a kludge */ if (rs->is_v2_dir) { sl_last_total_weighted_bw = 0; - router_pick_directory_server(V2_AUTHORITY, pds_flags); + router_pick_directory_server(V2_DIRINFO, pds_flags); if (sl_last_total_weighted_bw != 0) { *v2_share_out = U64_TO_DBL(sl_last_weighted_bw_of_me) / U64_TO_DBL(sl_last_total_weighted_bw); @@ -981,7 +987,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_AUTHORITY, pds_flags); + router_pick_directory_server(V3_DIRINFO, pds_flags); if (sl_last_total_weighted_bw != 0) { *v3_share_out = U64_TO_DBL(sl_last_weighted_bw_of_me) / U64_TO_DBL(sl_last_total_weighted_bw); @@ -1022,7 +1028,7 @@ trusteddirserver_get_by_v3_auth_digest(const char *digest) SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds, { if (tor_memeq(ds->v3_identity_digest, digest, DIGEST_LEN) && - (ds->type & V3_AUTHORITY)) + (ds->type & V3_DIRINFO)) return ds; }); @@ -1032,10 +1038,10 @@ trusteddirserver_get_by_v3_auth_digest(const char *digest) /** Try to find a running trusted dirserver. Flags are as for * router_pick_directory_server. */ -routerstatus_t * -router_pick_trusteddirserver(authority_type_t type, int flags) +const routerstatus_t * +router_pick_trusteddirserver(dirinfo_type_t type, int flags) { - routerstatus_t *choice; + const routerstatus_t *choice; int busy = 0; if (get_options()->PreferTunneledDirConns) flags |= _PDS_PREFER_TUNNELED_DIR_CONNS; @@ -1047,7 +1053,8 @@ router_pick_trusteddirserver(authority_type_t type, int flags) /* If the reason that we got no server is that servers are "busy", * we must be excluding good servers because we already have serverdesc * fetches with them. Do not mark down servers up because of this. */ - tor_assert((flags & PDS_NO_EXISTING_SERVERDESC_FETCH)); + tor_assert((flags & (PDS_NO_EXISTING_SERVERDESC_FETCH| + PDS_NO_EXISTING_MICRODESC_FETCH))); return NULL; } @@ -1067,11 +1074,11 @@ router_pick_trusteddirserver(authority_type_t type, int flags) * If the _PDS_PREFER_TUNNELED_DIR_CONNS flag is set, prefer directory servers * that we can use with BEGINDIR. */ -static routerstatus_t * -router_pick_directory_server_impl(authority_type_t type, int flags) +static const routerstatus_t * +router_pick_directory_server_impl(dirinfo_type_t type, int flags) { - or_options_t *options = get_options(); - routerstatus_t *result; + const or_options_t *options = get_options(); + const node_t *result; smartlist_t *direct, *tunnel; smartlist_t *trusted_direct, *trusted_tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; @@ -1095,54 +1102,64 @@ router_pick_directory_server_impl(authority_type_t type, int flags) overloaded_tunnel = smartlist_create(); /* Find all the running dirservers we know about. */ - SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, - status) { + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { int is_trusted; - int is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; + int is_overloaded; tor_addr_t addr; - if (!status->is_running || !status->dir_port || !status->is_valid) + const routerstatus_t *status = node->rs; + const country_t country = node->country; + if (!status) continue; - if (status->is_bad_directory) + + if (!node->is_running || !status->dir_port || !node->is_valid) + continue; + if (node->is_bad_directory) continue; - if (requireother && router_digest_is_me(status->identity_digest)) + if (requireother && router_digest_is_me(node->identity)) continue; - if (type & V3_AUTHORITY) { + if (type & V3_DIRINFO) { if (!(status->version_supports_v3_dir || - router_digest_is_trusted_dir_type(status->identity_digest, - V3_AUTHORITY))) + router_digest_is_trusted_dir_type(node->identity, + V3_DIRINFO))) continue; } - is_trusted = router_digest_is_trusted_dir(status->identity_digest); - if ((type & V2_AUTHORITY) && !(status->is_v2_dir || is_trusted)) + is_trusted = router_digest_is_trusted_dir(node->identity); + if ((type & V2_DIRINFO) && !(node->rs->is_v2_dir || is_trusted)) + continue; + if ((type & EXTRAINFO_DIRINFO) && + !router_supports_extrainfo(node->identity, 0)) continue; - if ((type & EXTRAINFO_CACHE) && - !router_supports_extrainfo(status->identity_digest, 0)) + if ((type & MICRODESC_DIRINFO) && !is_trusted && + !node->rs->version_supports_microdesc_cache) continue; - if (try_excluding && options->ExcludeNodes && - routerset_contains_routerstatus(options->ExcludeNodes, status)) { + if (try_excluding && + routerset_contains_routerstatus(options->ExcludeNodes, status, + country)) { ++n_excluded; continue; } /* XXXX IP6 proposal 118 */ - tor_addr_from_ipv4h(&addr, status->addr); + tor_addr_from_ipv4h(&addr, node->rs->addr); + + 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 : - is_overloaded ? overloaded_tunnel : tunnel, status); + is_overloaded ? overloaded_tunnel : tunnel, (void*)node); else if (!fascistfirewall || fascist_firewall_allows_address_dir(&addr, status->dir_port)) smartlist_add(is_trusted ? trusted_direct : - is_overloaded ? overloaded_direct : direct, status); - } SMARTLIST_FOREACH_END(status); + is_overloaded ? overloaded_direct : direct, (void*)node); + } SMARTLIST_FOREACH_END(node); if (smartlist_len(tunnel)) { - result = routerstatus_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR); } else if (smartlist_len(overloaded_tunnel)) { - result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel, + result = node_sl_choose_by_bandwidth(overloaded_tunnel, WEIGHT_FOR_DIR); } else if (smartlist_len(trusted_tunnel)) { /* FFFF We don't distinguish between trusteds and overloaded trusteds @@ -1151,10 +1168,10 @@ router_pick_directory_server_impl(authority_type_t type, int flags) * is a feature, but it could easily be a bug. -RD */ result = smartlist_choose(trusted_tunnel); } else if (smartlist_len(direct)) { - result = routerstatus_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR); } else if (smartlist_len(overloaded_direct)) { - result = routerstatus_sl_choose_by_bandwidth(overloaded_direct, - WEIGHT_FOR_DIR); + result = node_sl_choose_by_bandwidth(overloaded_direct, + WEIGHT_FOR_DIR); } else { result = smartlist_choose(trusted_direct); } @@ -1173,26 +1190,27 @@ router_pick_directory_server_impl(authority_type_t type, int flags) goto retry_without_exclude; } - return result; + return result ? result->rs : NULL; } /** Choose randomly from among the trusted dirservers that are up. Flags * are as for router_pick_directory_server_impl(). */ -static routerstatus_t * -router_pick_trusteddirserver_impl(authority_type_t type, int flags, +static const routerstatus_t * +router_pick_trusteddirserver_impl(dirinfo_type_t type, int flags, int *n_busy_out) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); smartlist_t *direct, *tunnel; smartlist_t *overloaded_direct, *overloaded_tunnel; - routerinfo_t *me = router_get_my_routerinfo(); - routerstatus_t *result; + const routerinfo_t *me = router_get_my_routerinfo(); + const routerstatus_t *result; 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 no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH); + const int no_microdesc_fetching =(flags & PDS_NO_EXISTING_MICRODESC_FETCH); int n_busy = 0; int try_excluding = 1, n_excluded = 0; @@ -1214,14 +1232,14 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, if (!d->is_running) continue; if ((type & d->type) == 0) continue; - if ((type & EXTRAINFO_CACHE) && + if ((type & EXTRAINFO_DIRINFO) && !router_supports_extrainfo(d->digest, 1)) continue; if (requireother && me && router_digest_is_me(d->digest)) continue; - if (try_excluding && options->ExcludeNodes && + if (try_excluding && routerset_contains_routerstatus(options->ExcludeNodes, - &d->fake_status)) { + &d->fake_status, -1)) { ++n_excluded; continue; } @@ -1240,6 +1258,13 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, continue; } } + if (no_microdesc_fetching) { + if (connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &addr, d->dir_port, DIR_PURPOSE_FETCH_MICRODESC)) { + ++n_busy; + continue; + } + } if (prefer_tunnel && d->or_port && @@ -1287,22 +1312,18 @@ router_pick_trusteddirserver_impl(authority_type_t type, int flags, static void mark_all_trusteddirservers_up(void) { - if (routerlist) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - if (router_digest_is_trusted_dir(router->cache_info.identity_digest) && - router->dir_port > 0) { - router->is_running = 1; - }); - } + 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(trusted_dir_servers, trusted_dir_server_t *, dir, { routerstatus_t *rs; dir->is_running = 1; download_status_reset(&dir->v2_ns_dl_status); - rs = router_get_consensus_status_by_id(dir->digest); - if (rs && !rs->is_running) { - rs->is_running = 1; + rs = router_get_mutable_consensus_status_by_id(dir->digest); + if (rs) { rs->last_dir_503_at = 0; control_event_networkstatus_changed_single(rs); } @@ -1326,148 +1347,160 @@ router_reset_status_download_failures(void) mark_all_trusteddirservers_up(); } -/** Return true iff router1 and router2 have the same /16 network. */ +/** 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 -routers_in_same_network_family(routerinfo_t *r1, routerinfo_t *r2) +addrs_in_same_network_family(const tor_addr_t *a1, + const tor_addr_t *a2) { - return (r1->addr & 0xffff0000) == (r2->addr & 0xffff0000); + /* XXXX MOVE ? */ + return 0 == tor_addr_compare_masked(a1, a2, 16, CMP_SEMANTIC); } -/** Look through the routerlist and identify routers that - * advertise the same /16 network address as <b>router</b>. - * Add each of them to <b>sl</b>. +/** + * 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. */ -static void -routerlist_add_network_family(smartlist_t *sl, routerinfo_t *router) +void +nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) { - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, r, + /* 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. */ { - if (router != r && routers_in_same_network_family(router, r)) - smartlist_add(sl, r); - }); -} + const node_t *real_node = node_get_by_id(node->identity); + if (real_node) + smartlist_add(sl, (node_t*)real_node); + } -/** Add all the family of <b>router</b> 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. - */ -void -routerlist_add_family(smartlist_t *sl, routerinfo_t *router) -{ - routerinfo_t *r; - config_line_t *cl; - or_options_t *options = get_options(); + /* First, add any nodes with similar network addresses. */ + if (options->EnforceDistinctSubnets) { + tor_addr_t node_addr; + node_get_addr(node, &node_addr); - /* First, add any routers with similar network addresses. */ - if (options->EnforceDistinctSubnets) - routerlist_add_network_family(sl, router); + 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); + } - if (router->declared_family) { - /* Add every r such that router declares familyness with r, and r + /* 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(router->declared_family, const char *, n, - { - if (!(r = router_get_by_nickname(n, 0))) - continue; - if (!r->declared_family) - continue; - SMARTLIST_FOREACH(r->declared_family, const char *, n2, - { - if (router_nickname_matches(router, n2)) - smartlist_add(sl, r); - }); - }); + 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. */ - for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (router_nickname_is_in_list(router, cl->value)) { - add_nickname_list_to_smartlist(sl, cl->value, 0); - } + 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); + } + }); } } -/** Return true iff r is named by some nickname in <b>lst</b>. */ +/** Given a <b>router</b>, add every node_t in its family (including the + * node itself</b>) to <b>sl</b>. + * + * Note the type mismatch: This function takes a routerinfo, but adds nodes + * to the smartlist! + */ +static void +routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) +{ + /* XXXX MOVE ? */ + node_t fake_node; + const node_t *node = node_get_by_id(router->cache_info.identity_digest);; + if (node == NULL) { + memset(&fake_node, 0, sizeof(fake_node)); + fake_node.ri = (routerinfo_t *)router; + memcpy(fake_node.identity, router->cache_info.identity_digest, DIGEST_LEN); + node = &fake_node; + } + 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 -router_in_nickname_smartlist(smartlist_t *lst, routerinfo_t *r) +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 (router_nickname_matches(r, name)) - return 1;); + 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 -routers_in_same_family(routerinfo_t *r1, routerinfo_t *r2) +nodes_in_same_family(const node_t *node1, const node_t *node2) { - or_options_t *options = get_options(); - config_line_t *cl; - - if (options->EnforceDistinctSubnets && routers_in_same_network_family(r1,r2)) - return 1; - - if (router_in_nickname_smartlist(r1->declared_family, r2) && - router_in_nickname_smartlist(r2->declared_family, r1)) - return 1; + /* XXXX MOVE */ + const or_options_t *options = get_options(); - for (cl = options->NodeFamilies; cl; cl = cl->next) { - if (router_nickname_is_in_list(r1, cl->value) && - router_nickname_is_in_list(r2, cl->value)) + /* 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; } - return 0; -} - -/** Given a (possibly NULL) comma-and-whitespace separated list of nicknames, - * see which nicknames in <b>list</b> name routers in our routerlist, and add - * the routerinfos for those routers to <b>sl</b>. If <b>must_be_running</b>, - * only include routers that we think are running. - * Warn if any non-Named routers are specified by nickname. - */ -void -add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, - int must_be_running) -{ - routerinfo_t *router; - smartlist_t *nickname_list; - int have_dir_info = router_have_minimum_dir_info(); - if (!list) - return; /* nothing to do */ - tor_assert(sl); - - nickname_list = smartlist_create(); - if (!warned_nicknames) - warned_nicknames = smartlist_create(); + /* 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; + } - smartlist_split_string(nickname_list, list, ",", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + /* 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; + }); + } - SMARTLIST_FOREACH(nickname_list, const char *, nick, { - int warned; - if (!is_legal_nickname_or_hexdigest(nick)) { - log_warn(LD_CONFIG, "Nickname '%s' is misformed; skipping", nick); - continue; - } - router = router_get_by_nickname(nick, 1); - warned = smartlist_string_isin(warned_nicknames, nick); - if (router) { - if (!must_be_running || router->is_running) { - smartlist_add(sl,router); - } - } else if (!router_get_consensus_status_by_nickname(nick,1)) { - if (!warned) { - log_fn(have_dir_info ? LOG_WARN : LOG_INFO, LD_CONFIG, - "Nickname list includes '%s' which isn't a known router.",nick); - smartlist_add(warned_nicknames, tor_strdup(nick)); - } - } - }); - SMARTLIST_FOREACH(nickname_list, char *, nick, tor_free(nick)); - smartlist_free(nickname_list); + return 0; } /** Return 1 iff any member of the (possibly NULL) comma-separated list @@ -1475,7 +1508,7 @@ add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, * return 0. */ int -router_nickname_is_in_list(routerinfo_t *router, const char *list) +router_nickname_is_in_list(const routerinfo_t *router, const char *list) { smartlist_t *nickname_list; int v = 0; @@ -1494,34 +1527,32 @@ router_nickname_is_in_list(routerinfo_t *router, const char *list) return v; } -/** Add every suitable router from our routerlist to <b>sl</b>, so that +/** Add every suitable node from our nodelist to <b>sl</b>, so that * we can pick a node for a circuit. */ static void -router_add_running_routers_to_smartlist(smartlist_t *sl, int allow_invalid, - int need_uptime, int need_capacity, - int need_guard) -{ - if (!routerlist) - return; +router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, + int need_uptime, int need_capacity, + int need_guard, int need_desc) +{ /* XXXX MOVE */ + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { + if (!node->is_running || + (!node->is_valid && !allow_invalid)) + continue; + if (need_desc && !(node->ri || (node->rs && node->md))) + continue; + if (node->ri && node->ri->purpose != ROUTER_PURPOSE_GENERAL) + continue; + if (node_is_unreliable(node, need_uptime, need_capacity, need_guard)) + continue; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->is_running && - router->purpose == ROUTER_PURPOSE_GENERAL && - (router->is_valid || allow_invalid) && - !router_is_unreliable(router, need_uptime, - need_capacity, need_guard)) { - /* If it's running, and it's suitable according to the - * other flags we had in mind */ - smartlist_add(sl, router); - } - }); + smartlist_add(sl, (void *)node); + } SMARTLIST_FOREACH_END(node); } /** Look through the routerlist until we find a router that has my key. Return it. */ -routerinfo_t * +const routerinfo_t * routerlist_find_my_routerinfo(void) { if (!routerlist) @@ -1541,13 +1572,13 @@ routerlist_find_my_routerinfo(void) * Don't exit enclave to excluded relays -- it wouldn't actually * hurt anything, but this way there are fewer confused users. */ -routerinfo_t * +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; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (!tor_inet_aton(address, &in)) return NULL; /* it's not an IP already */ @@ -1555,14 +1586,13 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) tor_addr_from_ipv4h(&a, addr); - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->addr == addr && - router->is_running && - compare_tor_addr_to_addr_policy(&a, port, router->exit_policy) == + 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_router(options->_ExcludeExitNodesUnion, router)) - return router; + !routerset_contains_node(options->_ExcludeExitNodesUnion, node)) + return node; }); return NULL; } @@ -1574,14 +1604,14 @@ router_find_exact_exit_enclave(const char *address, uint16_t port) * If <b>need_guard</b>, we require that the router is a possible entry guard. */ int -router_is_unreliable(routerinfo_t *router, int need_uptime, - int need_capacity, int need_guard) +node_is_unreliable(const node_t *node, int need_uptime, + int need_capacity, int need_guard) { - if (need_uptime && !router->is_stable) + if (need_uptime && !node->is_stable) return 1; - if (need_capacity && !router->is_fast) + if (need_capacity && !node->is_fast) return 1; - if (need_guard && !router->is_possible_guard) + if (need_guard && !node->is_possible_guard) return 1; return 0; } @@ -1589,7 +1619,7 @@ router_is_unreliable(routerinfo_t *router, int need_uptime, /** Return the smaller of the router's configured BandwidthRate * and its advertised capacity. */ uint32_t -router_get_advertised_bandwidth(routerinfo_t *router) +router_get_advertised_bandwidth(const routerinfo_t *router) { if (router->bandwidthcapacity < router->bandwidthrate) return router->bandwidthcapacity; @@ -1603,7 +1633,7 @@ router_get_advertised_bandwidth(routerinfo_t *router) /** Return the smaller of the router's configured BandwidthRate * and its advertised capacity, capped by max-believe-bw. */ uint32_t -router_get_advertised_bandwidth_capped(routerinfo_t *router) +router_get_advertised_bandwidth_capped(const routerinfo_t *router) { uint32_t result = router->bandwidthcapacity; if (result > router->bandwidthrate) @@ -1645,13 +1675,10 @@ kb_to_bytes(uint32_t bw) } /** Helper function: - * choose a random element of smartlist <b>sl</b>, weighted by + * choose a random element of smartlist <b>sl</b> of nodes, weighted by * the advertised bandwidth of each element using the consensus * bandwidth weights. * - * If <b>statuses</b> is zero, then <b>sl</b> is a list of - * routerinfo_t's. Otherwise it's a list of routerstatus_t's. - * * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all * nodes' bandwidth equally regardless of their Exit status, since there may * be some in the list because they exit to obscure ports. If @@ -1661,10 +1688,9 @@ kb_to_bytes(uint32_t bw) * guard node: consider all guard's bandwidth equally. Otherwise, weight * guards proportionally less. */ -static void * -smartlist_choose_by_bandwidth_weights(smartlist_t *sl, - bandwidth_weight_rule_t rule, - int statuses) +static const node_t * +smartlist_choose_node_by_bandwidth_weights(smartlist_t *sl, + bandwidth_weight_rule_t rule) { int64_t weight_scale; int64_t rand_bw; @@ -1758,15 +1784,14 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, bandwidths = tor_malloc_zero(sizeof(double)*smartlist_len(sl)); // Cycle through smartlist and total the bandwidth. - for (i = 0; i < (unsigned)smartlist_len(sl); ++i) { + SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0, is_me = 0; double weight = 1; - if (statuses) { - routerstatus_t *status = smartlist_get(sl, i); - is_exit = status->is_exit && !status->is_bad_exit; - is_guard = status->is_possible_guard; - is_dir = (status->dir_port != 0); - if (!status->has_bandwidth) { + is_exit = node->is_exit && ! node->is_bad_exit; + is_guard = node->is_possible_guard; + is_dir = node_is_dir(node); + if (node->rs) { + if (!node->rs->has_bandwidth) { tor_free(bandwidths); /* This should never happen, unless all the authorites downgrade * to 0.2.0 or rogue routerstatuses get inserted into our consensus. */ @@ -1775,26 +1800,17 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, "old router selection algorithm."); return NULL; } - this_bw = kb_to_bytes(status->bandwidth); - if (router_digest_is_me(status->identity_digest)) - is_me = 1; + this_bw = kb_to_bytes(node->rs->bandwidth); + } 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 { - routerstatus_t *rs; - routerinfo_t *router = smartlist_get(sl, i); - rs = router_get_consensus_status_by_id( - router->cache_info.identity_digest); - is_exit = router->is_exit && !router->is_bad_exit; - is_guard = router->is_possible_guard; - is_dir = (router->dir_port != 0); - if (rs && rs->has_bandwidth) { - this_bw = kb_to_bytes(rs->bandwidth); - } else { /* bridge or other descriptor not in our consensus */ - this_bw = bridge_get_advertised_bandwidth_bounded(router); - have_unknown = 1; - } - if (router_digest_is_me(router->cache_info.identity_digest)) - is_me = 1; + /* We can't use this one. */ + continue; } + is_me = router_digest_is_me(node->identity); + if (is_guard && is_exit) { weight = (is_dir ? Wdb*Wd : Wd); } else if (is_guard) { @@ -1805,11 +1821,11 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, weight = (is_dir ? Wmb*Wm : Wm); } - bandwidths[i] = weight*this_bw; + bandwidths[node_sl_idx] = weight*this_bw; weighted_bw += weight*this_bw; if (is_me) sl_last_weighted_bw_of_me = weight*this_bw; - } + } SMARTLIST_FOREACH_END(node); /* XXXX023 this is a kludge to expose these values. */ sl_last_total_weighted_bw = weighted_bw; @@ -1857,12 +1873,9 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, } /** Helper function: - * choose a random element of smartlist <b>sl</b>, weighted by + * choose a random node_t element of smartlist <b>sl</b>, weighted by * the advertised bandwidth of each element. * - * If <b>statuses</b> is zero, then <b>sl</b> is a list of - * routerinfo_t's. Otherwise it's a list of routerstatus_t's. - * * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all * nodes' bandwidth equally regardless of their Exit status, since there may * be some in the list because they exit to obscure ports. If @@ -1872,13 +1885,11 @@ smartlist_choose_by_bandwidth_weights(smartlist_t *sl, * guard node: consider all guard's bandwidth equally. Otherwise, weight * guards proportionally less. */ -static void * -smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, - int statuses) +static const node_t * +smartlist_choose_node_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule) { - unsigned int i; - routerinfo_t *router; - routerstatus_t *status=NULL; + unsigned i; int32_t *bandwidths; int is_exit; int is_guard; @@ -1919,49 +1930,34 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, guard_bits = bitarray_init_zero(smartlist_len(sl)); /* Iterate over all the routerinfo_t or routerstatus_t, and */ - for (i = 0; i < (unsigned)smartlist_len(sl); ++i) { + 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; - if (statuses) { - status = smartlist_get(sl, i); - if (router_digest_is_me(status->identity_digest)) - me_idx = i; - router = router_get_by_digest(status->identity_digest); - is_exit = status->is_exit; - is_guard = status->is_possible_guard; - if (status->has_bandwidth) { - this_bw = kb_to_bytes(status->bandwidth); + i = node_sl_idx; + + if (router_digest_is_me(node->identity)) + me_idx = node_sl_idx; + + is_exit = node->is_exit; + is_guard = node->is_possible_guard; + if (node->rs) { + if (node->rs->has_bandwidth) { + this_bw = kb_to_bytes(node->rs->bandwidth); } else { /* guess */ /* XXX023 once consensuses always list bandwidths, we can take * this guessing business out. -RD */ is_known = 0; - flags = status->is_fast ? 1 : 0; + flags = node->rs->is_fast ? 1 : 0; flags |= is_exit ? 2 : 0; flags |= is_guard ? 4 : 0; } - } else { - routerstatus_t *rs; - router = smartlist_get(sl, i); - rs = router_get_consensus_status_by_id( - router->cache_info.identity_digest); - if (router_digest_is_me(router->cache_info.identity_digest)) - me_idx = i; - is_exit = router->is_exit; - is_guard = router->is_possible_guard; - if (rs && rs->has_bandwidth) { - this_bw = kb_to_bytes(rs->bandwidth); - } else if (rs) { /* guess; don't trust the descriptor */ - /* XXX023 once consensuses always list bandwidths, we can take - * this guessing business out. -RD */ - is_known = 0; - flags = router->is_fast ? 1 : 0; - flags |= is_exit ? 2 : 0; - flags |= is_guard ? 4 : 0; - } else /* bridge or other descriptor not in our consensus */ - this_bw = bridge_get_advertised_bandwidth_bounded(router); + } else if (node->ri) { + /* Must be a bridge if we're willing to use it */ + this_bw = bridge_get_advertised_bandwidth_bounded(node->ri); } + if (is_exit) bitarray_set(exit_bits, i); if (is_guard) @@ -1981,9 +1977,9 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, total_nonexit_bw += this_bw; } else { ++n_unknown; - bandwidths[i] = -flags; + bandwidths[node_sl_idx] = -flags; } - } + } SMARTLIST_FOREACH_END(node); /* Now, fill in the unknown values. */ if (n_unknown) { @@ -2125,40 +2121,23 @@ smartlist_choose_by_bandwidth(smartlist_t *sl, bandwidth_weight_rule_t rule, return smartlist_get(sl, i); } -/** Choose a random element of router list <b>sl</b>, weighted by - * the advertised bandwidth of each router. - */ -routerinfo_t * -routerlist_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule) -{ - routerinfo_t *ret; - if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 0))) { - return ret; - } else { - return smartlist_choose_by_bandwidth(sl, rule, 0); - } -} - /** Choose a random element of status list <b>sl</b>, weighted by - * the advertised bandwidth of each status. - */ -routerstatus_t * -routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule) -{ - /* We are choosing neither exit nor guard here. Weight accordingly. */ - routerstatus_t *ret; - if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 1))) { + * the advertised bandwidth of each node */ +const node_t * +node_sl_choose_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule) +{ /*XXXX MOVE */ + const node_t *ret; + if ((ret = smartlist_choose_node_by_bandwidth_weights(sl, rule))) { return ret; } else { - return smartlist_choose_by_bandwidth(sl, rule, 1); + return smartlist_choose_node_by_bandwidth(sl, rule); } } -/** Return a random running router from the routerlist. Never - * pick a node whose routerinfo is in - * <b>excludedsmartlist</b>, or whose routerinfo matches <b>excludedset</b>, +/** Return a random running node from the nodelist. Never + * pick a node that is in + * <b>excludedsmartlist</b>, or which matches <b>excludedset</b>, * even if they are the only nodes available. * If <b>CRN_NEED_UPTIME</b> is set in flags and any router has more than * a minimum uptime, return one of those. @@ -2170,21 +2149,26 @@ routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, * If <b>CRN_WEIGHT_AS_EXIT</b> is set in flags, we weight bandwidths as if * picking an exit node, otherwise we weight bandwidths for picking a relay * node (that is, possibly discounting exit nodes). + * If <b>CRN_NEED_DESC</b> is set in flags, we only consider nodes that + * have a routerinfo or microdescriptor -- that is, enough info to be + * used to build a circuit. */ -routerinfo_t * +const node_t * router_choose_random_node(smartlist_t *excludedsmartlist, routerset_t *excludedset, router_crn_flags_t flags) -{ +{ /* XXXX MOVE */ const int need_uptime = (flags & CRN_NEED_UPTIME) != 0; const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0; const int need_guard = (flags & CRN_NEED_GUARD) != 0; const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0; + const int need_desc = (flags & CRN_NEED_DESC) != 0; smartlist_t *sl=smartlist_create(), - *excludednodes=smartlist_create(); - routerinfo_t *choice = NULL, *r; + *excludednodes=smartlist_create(); + const node_t *choice = NULL; + const routerinfo_t *r; bandwidth_weight_rule_t rule; tor_assert(!(weight_for_exit && need_guard)); @@ -2194,29 +2178,26 @@ router_choose_random_node(smartlist_t *excludedsmartlist, /* Exclude relays that allow single hop exit circuits, if the user * wants to (such relays might be risky) */ if (get_options()->ExcludeSingleHopRelays) { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, r, - if (r->allow_single_hop_exits) { - smartlist_add(excludednodes, r); + SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + if (node_allows_single_hop_exits(node)) { + smartlist_add(excludednodes, node); }); } - if ((r = routerlist_find_my_routerinfo())) { - smartlist_add(excludednodes, r); - routerlist_add_family(excludednodes, r); - } + if ((r = routerlist_find_my_routerinfo())) + routerlist_add_node_and_family(excludednodes, r); - router_add_running_routers_to_smartlist(sl, allow_invalid, - need_uptime, need_capacity, - need_guard); + router_add_running_nodes_to_smartlist(sl, allow_invalid, + need_uptime, need_capacity, + need_guard, need_desc); smartlist_subtract(sl,excludednodes); if (excludedsmartlist) smartlist_subtract(sl,excludedsmartlist); if (excludedset) - routerset_subtract_routers(sl,excludedset); + routerset_subtract_nodes(sl,excludedset); // Always weight by bandwidth - choice = routerlist_sl_choose_by_bandwidth(sl, rule); + choice = node_sl_choose_by_bandwidth(sl, rule); smartlist_free(sl); if (!choice && (need_uptime || need_capacity || need_guard)) { @@ -2239,35 +2220,90 @@ router_choose_random_node(smartlist_t *excludedsmartlist, return choice; } -/** Helper: Return true iff the <b>identity_digest</b> and <b>nickname</b> - * 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 INLINE int -hex_digest_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) +/** Helper: given an extended nickname in <b>hexdigest</b> try to decode it. + * Return 0 on success, -1 on failure. Store the result into the + * DIGEST_LEN-byte buffer at <b>digest_out</b>, the single character at + * <b>nickname_qualifier_char_out</b>, and the MAXNICKNAME_LEN+1-byte buffer + * at <b>nickname_out</b>. + * + * The recognized format is: + * HexName = Dollar? HexDigest NamePart? + * Dollar = '?' + * HexDigest = HexChar*20 + * HexChar = 'a'..'f' | 'A'..'F' | '0'..'9' + * NamePart = QualChar Name + * QualChar = '=' | '~' + * Name = NameChar*(1..MAX_NICKNAME_LEN) + * NameChar = Any ASCII alphanumeric character + */ +int +hex_digest_nickname_decode(const char *hexdigest, + char *digest_out, + char *nickname_qualifier_char_out, + char *nickname_out) { - char digest[DIGEST_LEN]; size_t len; + tor_assert(hexdigest); if (hexdigest[0] == '$') ++hexdigest; len = strlen(hexdigest); - if (len < HEX_DIGEST_LEN) + if (len < HEX_DIGEST_LEN) { + return -1; + } else if (len > HEX_DIGEST_LEN && (hexdigest[HEX_DIGEST_LEN] == '=' || + hexdigest[HEX_DIGEST_LEN] == '~') && + len <= HEX_DIGEST_LEN+1+MAX_NICKNAME_LEN) { + *nickname_qualifier_char_out = hexdigest[HEX_DIGEST_LEN]; + strlcpy(nickname_out, hexdigest+HEX_DIGEST_LEN+1 , MAX_NICKNAME_LEN+1); + } else if (len == HEX_DIGEST_LEN) { + ; + } else { + return -1; + } + + if (base16_decode(digest_out, DIGEST_LEN, hexdigest, HEX_DIGEST_LEN)<0) + return -1; + return 0; +} + +/** Helper: Return true iff the <b>identity_digest</b> and <b>nickname</b> + * 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 +hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, + const char *nickname, int is_named) +{ + char digest[DIGEST_LEN]; + char nn_char='\0'; + char nn_buf[MAX_NICKNAME_LEN+1]; + + if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - else if (len > HEX_DIGEST_LEN && - (hexdigest[HEX_DIGEST_LEN] == '=' || - hexdigest[HEX_DIGEST_LEN] == '~')) { - if (strcasecmp(hexdigest+HEX_DIGEST_LEN+1, nickname)) + + if (nn_char == '=' || nn_char == '~') { + if (!nickname) return 0; - if (hexdigest[HEX_DIGEST_LEN] == '=' && !is_named) + if (strcasecmp(nn_buf, nickname)) + return 0; + if (nn_char == '=' && !is_named) return 0; } - if (base16_decode(digest, DIGEST_LEN, hexdigest, HEX_DIGEST_LEN)<0) - return 0; - return (tor_memeq(digest, identity_digest, DIGEST_LEN)); + return tor_memeq(digest, identity_digest, DIGEST_LEN); +} + +/* Return true iff <b>router</b> is listed as named in the current + * consensus. */ +int +router_is_named(const routerinfo_t *router) +{ + const char *digest = + networkstatus_get_router_digest_by_nickname(router->nickname); + + return (digest && + tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); } /** Return true iff the digest of <b>router</b>'s identity key, @@ -2275,10 +2311,12 @@ hex_digest_matches(const char *hexdigest, const char *identity_digest, * optionally prefixed with a single dollar sign). Return false if * <b>hexdigest</b> is malformed, or it doesn't match. */ static INLINE int -router_hex_digest_matches(routerinfo_t *router, const char *hexdigest) +router_hex_digest_matches(const routerinfo_t *router, const char *hexdigest) { - return hex_digest_matches(hexdigest, router->cache_info.identity_digest, - router->nickname, router->is_named); + return hex_digest_nickname_matches(hexdigest, + router->cache_info.identity_digest, + router->nickname, + router_is_named(router)); } /** Return true if <b>router</b>'s nickname matches <b>nickname</b> @@ -2286,20 +2324,43 @@ router_hex_digest_matches(routerinfo_t *router, const char *hexdigest) * matches a hexadecimal value stored in <b>nickname</b>. Return * false otherwise. */ static int -router_nickname_matches(routerinfo_t *router, const char *nickname) +router_nickname_matches(const routerinfo_t *router, const char *nickname) { if (nickname[0]!='$' && !strcasecmp(router->nickname, nickname)) return 1; 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. */ -routerinfo_t * +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; @@ -2345,15 +2406,14 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) if (warn_if_unnamed && n_matches > 1) { smartlist_t *fps = smartlist_create(); int any_unwarned = 0; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { + SMARTLIST_FOREACH_BEGIN(routerlist->routers, routerinfo_t *, router) { routerstatus_t *rs; char *desc; size_t dlen; char fp[HEX_DIGEST_LEN+1]; if (strcasecmp(router->nickname, nickname)) continue; - rs = router_get_consensus_status_by_id( + rs = router_get_mutable_consensus_status_by_id( router->cache_info.identity_digest); if (rs && !rs->name_lookup_warned) { rs->name_lookup_warned = 1; @@ -2366,7 +2426,7 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) tor_snprintf(desc, dlen, "\"$%s\" for the one at %s:%d", fp, router->address, router->or_port); smartlist_add(fps, desc); - }); + } SMARTLIST_FOREACH_END(router); if (any_unwarned) { char *alternatives = smartlist_join_strings(fps, "; ",0,NULL); log_warn(LD_CONFIG, @@ -2379,7 +2439,7 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) SMARTLIST_FOREACH(fps, char *, cp, tor_free(cp)); smartlist_free(fps); } else if (warn_if_unnamed) { - routerstatus_t *rs = router_get_consensus_status_by_id( + 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]; @@ -2395,27 +2455,15 @@ router_get_by_nickname(const char *nickname, int warn_if_unnamed) } return best_match; } - return NULL; -} - -/** Try to find a routerinfo for <b>digest</b>. If we don't have one, - * return 1. If we do, ask tor_version_as_new_as() for the answer. - */ -int -router_digest_version_as_new_as(const char *digest, const char *cutoff) -{ - routerinfo_t *router = router_get_by_digest(digest); - if (!router) - return 1; - return tor_version_as_new_as(router->platform, cutoff); +#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. */ int -router_digest_is_trusted_dir_type(const char *digest, authority_type_t type) +router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type) { if (!trusted_dir_servers) return 0; @@ -2459,44 +2507,20 @@ hexdigest_to_digest(const char *hexdigest, char *digest) /** Return the router in our routerlist whose hexadecimal key digest * is <b>hexdigest</b>. Return NULL if no such router is known. */ -routerinfo_t * +const routerinfo_t * router_get_by_hexdigest(const char *hexdigest) { - char digest[DIGEST_LEN]; - size_t len; - routerinfo_t *ri; - - tor_assert(hexdigest); - if (!routerlist) - return NULL; - if (hexdigest[0]=='$') - ++hexdigest; - len = strlen(hexdigest); - if (hexdigest_to_digest(hexdigest, digest) < 0) + if (is_legal_nickname(hexdigest)) return NULL; - ri = router_get_by_digest(digest); - - if (ri && len > HEX_DIGEST_LEN) { - if (hexdigest[HEX_DIGEST_LEN] == '=') { - if (strcasecmp(ri->nickname, hexdigest+HEX_DIGEST_LEN+1) || - !ri->is_named) - return NULL; - } else if (hexdigest[HEX_DIGEST_LEN] == '~') { - if (strcasecmp(ri->nickname, hexdigest+HEX_DIGEST_LEN+1)) - return NULL; - } else { - return NULL; - } - } - - return ri; + /* It's not a legal nickname, so it must be a hexdigest or nothing. */ + return router_get_by_nickname(hexdigest, 1); } -/** Return the router in our routerlist whose 20-byte key digest - * is <b>digest</b>. Return NULL if no such router is known. */ +/** As router_get_by_id_digest,but return a pointer that you're allowed to + * modify */ routerinfo_t * -router_get_by_digest(const char *digest) +router_get_mutable_by_digest(const char *digest) { tor_assert(digest); @@ -2507,6 +2531,14 @@ router_get_by_digest(const char *digest) return rimap_get(routerlist->identity_map, digest); } +/** Return the router in our routerlist whose 20-byte key digest + * is <b>digest</b>. Return NULL if no such router is known. */ +const routerinfo_t * +router_get_by_id_digest(const char *digest) +{ + return router_get_mutable_by_digest(digest); +} + /** Return the router in our routerlist whose 20-byte descriptor * is <b>digest</b>. Return NULL if no such router is known. */ signed_descriptor_t * @@ -2557,7 +2589,7 @@ extrainfo_get_by_descriptor_digest(const char *digest) * The caller must not free the string returned. */ static const char * -signed_descriptor_get_body_impl(signed_descriptor_t *desc, +signed_descriptor_get_body_impl(const signed_descriptor_t *desc, int with_annotations) { const char *r = NULL; @@ -2606,7 +2638,7 @@ signed_descriptor_get_body_impl(signed_descriptor_t *desc, * The caller must not free the string returned. */ const char * -signed_descriptor_get_body(signed_descriptor_t *desc) +signed_descriptor_get_body(const signed_descriptor_t *desc) { return signed_descriptor_get_body_impl(desc, 0); } @@ -2614,7 +2646,7 @@ signed_descriptor_get_body(signed_descriptor_t *desc) /** As signed_descriptor_get_body(), but points to the beginning of the * annotations section rather than the beginning of the descriptor. */ const char * -signed_descriptor_get_annotations(signed_descriptor_t *desc) +signed_descriptor_get_annotations(const signed_descriptor_t *desc) { return signed_descriptor_get_body_impl(desc, 1); } @@ -2667,7 +2699,6 @@ routerinfo_free(routerinfo_t *router) } addr_policy_list_free(router->exit_policy); - /* XXXX Remove if this turns out to affect performance. */ memset(router, 77, sizeof(routerinfo_t)); tor_free(router); @@ -2682,7 +2713,6 @@ extrainfo_free(extrainfo_t *extrainfo) tor_free(extrainfo->cache_info.signed_descriptor_body); tor_free(extrainfo->pending_sig); - /* XXXX remove this if it turns out to slow us down. */ memset(extrainfo, 88, sizeof(extrainfo_t)); /* debug bad memory usage */ tor_free(extrainfo); } @@ -2696,7 +2726,6 @@ signed_descriptor_free(signed_descriptor_t *sd) tor_free(sd->signed_descriptor_body); - /* XXXX remove this once more bugs go away. */ memset(sd, 99, sizeof(signed_descriptor_t)); /* Debug bad mem usage */ tor_free(sd); } @@ -2802,8 +2831,7 @@ routerlist_insert(routerlist_t *rl, routerinfo_t *ri) routerinfo_t *ri_old; signed_descriptor_t *sd_old; { - /* XXXX Remove if this slows us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri); } tor_assert(ri->cache_info.routerlist_index == -1); @@ -2825,6 +2853,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); router_dir_info_changed(); #ifdef DEBUG_ROUTERLIST routerlist_assert_ok(rl); @@ -2845,7 +2874,6 @@ extrainfo_insert(routerlist_t *rl, extrainfo_t *ei) extrainfo_t *ei_tmp; { - /* XXXX remove this code if it slows us down. */ extrainfo_t *ei_generated = router_get_my_extrainfo(); tor_assert(ei_generated != ei); } @@ -2891,8 +2919,7 @@ static void routerlist_insert_old(routerlist_t *rl, routerinfo_t *ri) { { - /* XXXX remove this code if it slows us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri); } tor_assert(ri->cache_info.routerlist_index == -1); @@ -2932,6 +2959,8 @@ routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old, time_t now) tor_assert(0 <= idx && idx < smartlist_len(rl->routers)); tor_assert(smartlist_get(rl->routers, idx) == ri); + nodelist_remove_routerinfo(ri); + /* make sure the rephist module knows that it's not running */ rep_hist_note_router_unreachable(ri->cache_info.identity_digest, now); @@ -3043,8 +3072,7 @@ routerlist_replace(routerlist_t *rl, routerinfo_t *ri_old, routerinfo_t *ri_tmp; extrainfo_t *ei_tmp; { - /* XXXX Remove this if it turns out to slow us down. */ - routerinfo_t *ri_generated = router_get_my_routerinfo(); + const routerinfo_t *ri_generated = router_get_my_routerinfo(); tor_assert(ri_generated != ri_new); } tor_assert(ri_old != ri_new); @@ -3054,6 +3082,9 @@ 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); + router_dir_info_changed(); if (idx >= 0) { smartlist_set(rl->routers, idx, ri_new); @@ -3200,28 +3231,25 @@ routerlist_reset_warnings(void) void router_set_status(const char *digest, int up) { - routerinfo_t *router; - routerstatus_t *status; + 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); - router = router_get_by_digest(digest); - if (router) { + node = node_get_mutable_by_id(digest); + if (node) { +#if 0 log_debug(LD_DIR,"Marking router %s as %s.", - router_describe(router), up ? "up" : "down"); - if (!up && router_is_me(router) && !we_are_hibernating()) + node_describe(node), up ? "up" : "down"); +#endif + if (!up && node_is_me(node) && !we_are_hibernating()) log_warn(LD_NET, "We just marked ourself as down. Are your external " "addresses reachable?"); - router->is_running = up; - } - status = router_get_consensus_status_by_id(digest); - if (status && status->is_running != up) { - status->is_running = up; - control_event_networkstatus_changed_single(status); + node->is_running = up; } + router_dir_info_changed(); } @@ -3250,11 +3278,12 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, int from_cache, int from_fetch) { const char *id_digest; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); int authdir = authdir_mode_handles_descs(options, router->purpose); int authdir_believes_valid = 0; routerinfo_t *old_router; - networkstatus_t *consensus = networkstatus_get_latest_consensus(); + networkstatus_t *consensus = + networkstatus_get_latest_consensus_by_flavor(FLAV_NS); const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); int in_consensus = 0; @@ -3265,7 +3294,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, id_digest = router->cache_info.identity_digest; - old_router = router_get_by_digest(id_digest); + old_router = router_get_mutable_by_digest(id_digest); /* Make sure that we haven't already got this exact descriptor. */ if (sdmap_get(routerlist->desc_digest_map, @@ -3297,12 +3326,12 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, if (authdir) { if (authdir_wants_to_reject_router(router, msg, - !from_cache && !from_fetch)) { + !from_cache && !from_fetch, + &authdir_believes_valid)) { tor_assert(*msg); routerinfo_free(router); return ROUTER_AUTHDIR_REJECTS; } - authdir_believes_valid = router->is_valid; } else if (from_fetch) { /* Only check the descriptor digest against the network statuses when * we are receiving in response to a fetch. */ @@ -3329,14 +3358,15 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns, { routerstatus_t *rs = - networkstatus_v2_find_entry(ns, id_digest); + networkstatus_v2_find_mutable_entry(ns, id_digest); if (rs && tor_memeq(rs->descriptor_digest, router->cache_info.signed_descriptor_digest, DIGEST_LEN)) rs->need_to_mirror = 0; }); if (consensus) { - routerstatus_t *rs = networkstatus_vote_find_entry(consensus, id_digest); + routerstatus_t *rs = networkstatus_vote_find_mutable_entry( + consensus, id_digest); if (rs && tor_memeq(rs->descriptor_digest, router->cache_info.signed_descriptor_digest, DIGEST_LEN)) { @@ -3950,7 +3980,7 @@ router_load_extrainfo_from_string(const char *s, const char *eos, static int signed_desc_digest_is_recognized(signed_descriptor_t *desc) { - routerstatus_t *rs; + const routerstatus_t *rs; networkstatus_t *consensus = networkstatus_get_latest_consensus(); int caches = directory_caches_dir_info(get_options()); const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); @@ -3974,6 +4004,16 @@ signed_desc_digest_is_recognized(signed_descriptor_t *desc) return 0; } +/** Update downloads for router descriptors and/or microdescriptors as + * appropriate. */ +void +update_all_descriptor_downloads(time_t now) +{ + update_router_descriptor_downloads(now); + update_microdesc_downloads(now); + launch_dummy_descriptor_download_as_needed(now, get_options()); +} + /** Clear all our timeouts for fetching v2 and v3 directory stuff, and then * give it all a try again. */ void @@ -3982,34 +4022,34 @@ routerlist_retry_directory_downloads(time_t now) router_reset_status_download_failures(); router_reset_descriptor_download_failures(); update_networkstatus_downloads(now); - update_router_descriptor_downloads(now); + update_all_descriptor_downloads(now); } -/** Return 1 if all running sufficiently-stable routers will reject +/** 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_routers_reject(uint32_t addr, uint16_t port, - int need_uptime) -{ +router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port, + int need_uptime) +{ /* XXXX MOVE */ addr_policy_result_t r; - if (!routerlist) return 1; - SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, router, - { - if (router->is_running && - !router_is_unreliable(router, need_uptime, 0, 0)) { - r = compare_addr_to_addr_policy(addr, port, router->exit_policy); + 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 -router_exit_policy_rejects_all(routerinfo_t *router) +router_exit_policy_rejects_all(const routerinfo_t *router) { return router->policy_is_reject_star; } @@ -4022,7 +4062,7 @@ 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, - authority_type_t type) + dirinfo_type_t type) { trusted_dir_server_t *ent; uint32_t a; @@ -4057,7 +4097,7 @@ add_trusted_dir_server(const char *nickname, const char *address, ent->is_running = 1; ent->type = type; memcpy(ent->digest, digest, DIGEST_LEN); - if (v3_auth_digest && (type & V3_AUTHORITY)) + if (v3_auth_digest && (type & V3_DIRINFO)) memcpy(ent->v3_identity_digest, v3_auth_digest, DIGEST_LEN); dlen = 64 + strlen(hostname) + (nickname?strlen(nickname):0); @@ -4136,7 +4176,7 @@ int any_trusted_dir_is_v1_authority(void) { if (trusted_dir_servers) - return get_n_authorities(V1_AUTHORITY) > 0; + return get_n_authorities(V1_DIRINFO) > 0; return 0; } @@ -4144,7 +4184,9 @@ any_trusted_dir_is_v1_authority(void) /** For every current directory connection whose purpose is <b>purpose</b>, * and where the resource being downloaded begins with <b>prefix</b>, split * rest of the resource into base16 fingerprints, decode them, and set the - * corresponding elements of <b>result</b> to a nonzero value. */ + * corresponding elements of <b>result</b> to a nonzero value. + * DOCDOC purpose==microdesc + */ static void list_pending_downloads(digestmap_t *result, int purpose, const char *prefix) @@ -4152,20 +4194,23 @@ list_pending_downloads(digestmap_t *result, const size_t p_len = strlen(prefix); smartlist_t *tmp = smartlist_create(); smartlist_t *conns = get_connection_array(); + int flags = DSR_HEX; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) + flags = DSR_DIGEST256|DSR_BASE64; tor_assert(result); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->type == CONN_TYPE_DIR && conn->purpose == purpose && !conn->marked_for_close) { const char *resource = TO_DIR_CONN(conn)->requested_resource; if (!strcmpstart(resource, prefix)) dir_split_resource_into_fingerprints(resource + p_len, - tmp, NULL, DSR_HEX); + tmp, NULL, flags); } - }); + } SMARTLIST_FOREACH_END(conn); + SMARTLIST_FOREACH(tmp, char *, d, { digestmap_set(result, d, (void*)1); @@ -4185,13 +4230,21 @@ list_pending_descriptor_downloads(digestmap_t *result, int extrainfo) list_pending_downloads(result, purpose, "d/"); } -/** Launch downloads for all the descriptors whose digests are listed - * as digests[i] for lo <= i < hi. (Lo and hi may be out of range.) - * If <b>source</b> is given, download from <b>source</b>; otherwise, - * download from an appropriate random directory server. +/** DOCDOC */ +/*XXXX NM should use digest256, if one comes into being. */ +void +list_pending_microdesc_downloads(digestmap_t *result) +{ + list_pending_downloads(result, DIR_PURPOSE_FETCH_MICRODESC, "d/"); +} + +/** Launch downloads for all the descriptors whose digests or digests256 + * are listed as digests[i] for lo <= i < hi. (Lo and hi may be out of + * range.) If <b>source</b> is given, download from <b>source</b>; + * otherwise, download from an appropriate random directory server. */ static void -initiate_descriptor_downloads(routerstatus_t *source, +initiate_descriptor_downloads(const routerstatus_t *source, int purpose, smartlist_t *digests, int lo, int hi, int pds_flags) @@ -4199,6 +4252,20 @@ initiate_descriptor_downloads(routerstatus_t *source, int i, n = hi-lo; char *resource, *cp; size_t r_len; + + int digest_len = DIGEST_LEN, enc_digest_len = HEX_DIGEST_LEN; + char sep = '+'; + int b64_256 = 0; + + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + /* Microdescriptors are downloaded by "-"-separated base64-encoded + * 256-bit digests. */ + digest_len = DIGEST256_LEN; + enc_digest_len = BASE64_DIGEST256_LEN; + sep = '-'; + b64_256 = 1; + } + if (n <= 0) return; if (lo < 0) @@ -4206,15 +4273,19 @@ initiate_descriptor_downloads(routerstatus_t *source, if (hi > smartlist_len(digests)) hi = smartlist_len(digests); - r_len = 8 + (HEX_DIGEST_LEN+1)*n; + r_len = 8 + (enc_digest_len+1)*n; cp = resource = tor_malloc(r_len); memcpy(cp, "d/", 2); cp += 2; for (i = lo; i < hi; ++i) { - base16_encode(cp, r_len-(cp-resource), - smartlist_get(digests,i), DIGEST_LEN); - cp += HEX_DIGEST_LEN; - *cp++ = '+'; + if (b64_256) { + digest256_to_base64(cp, smartlist_get(digests, i)); + } else { + base16_encode(cp, r_len-(cp-resource), + smartlist_get(digests,i), digest_len); + } + cp += enc_digest_len; + *cp++ = sep; } memcpy(cp-1, ".z", 3); @@ -4235,9 +4306,10 @@ initiate_descriptor_downloads(routerstatus_t *source, * 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(routerstatus_t *rs, time_t now, or_options_t *options) +client_would_use_router(const routerstatus_t *rs, time_t now, + const or_options_t *options) { - if (!rs->is_running && !options->FetchUselessDescriptors) { + 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; @@ -4261,6 +4333,7 @@ client_would_use_router(routerstatus_t *rs, time_t now, or_options_t *options) * So use 96 because it's a nice number. */ #define MAX_DL_PER_REQUEST 96 +#define MAX_MICRODESC_DL_PER_REQUEST 92 /** Don't split our requests so finely that we are requesting fewer than * this number per server. */ #define MIN_DL_PER_REQUEST 4 @@ -4275,35 +4348,49 @@ client_would_use_router(routerstatus_t *rs, time_t now, or_options_t *options) * them until they have more, or until this amount of time has passed. */ #define MAX_CLIENT_INTERVAL_WITHOUT_REQUEST (10*60) -/** Given a list of router descriptor digests in <b>downloadable</b>, decide - * whether to delay fetching until we have more. If we don't want to delay, - * launch one or more requests to the appropriate directory authorities. */ -static void -launch_router_descriptor_downloads(smartlist_t *downloadable, - routerstatus_t *source, time_t now) +/** Given a <b>purpose</b> (FETCH_MICRODESC or FETCH_SERVERDESC) and a list of + * router descriptor digests or microdescriptor digest256s in + * <b>downloadable</b>, decide whether to delay fetching until we have more. + * If we don't want to delay, launch one or more requests to the appropriate + * directory authorities. + */ +void +launch_descriptor_downloads(int purpose, + smartlist_t *downloadable, + const routerstatus_t *source, time_t now) { int should_delay = 0, n_downloadable; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); + const char *descname; + + tor_assert(purpose == DIR_PURPOSE_FETCH_SERVERDESC || + purpose == DIR_PURPOSE_FETCH_MICRODESC); + + descname = (purpose == DIR_PURPOSE_FETCH_SERVERDESC) ? + "routerdesc" : "microdesc"; n_downloadable = smartlist_len(downloadable); if (!directory_fetches_dir_info_early(options)) { if (n_downloadable >= MAX_DL_TO_DELAY) { log_debug(LD_DIR, - "There are enough downloadable routerdescs to launch requests."); + "There are enough downloadable %ss to launch requests.", + descname); should_delay = 0; } else { - should_delay = (last_routerdesc_download_attempted + + should_delay = (last_descriptor_download_attempted + MAX_CLIENT_INTERVAL_WITHOUT_REQUEST) > now; if (!should_delay && n_downloadable) { - if (last_routerdesc_download_attempted) { + if (last_descriptor_download_attempted) { log_info(LD_DIR, - "There are not many downloadable routerdescs, but we've " + "There are not many downloadable %ss, but we've " "been waiting long enough (%d seconds). Downloading.", - (int)(now-last_routerdesc_download_attempted)); + descname, + (int)(now-last_descriptor_download_attempted)); } else { log_info(LD_DIR, - "There are not many downloadable routerdescs, but we haven't " - "tried downloading descriptors recently. Downloading."); + "There are not many downloadable %ss, but we haven't " + "tried downloading descriptors recently. Downloading.", + descname); } } } @@ -4330,12 +4417,19 @@ launch_router_descriptor_downloads(smartlist_t *downloadable, * update_router_descriptor_downloads() later on, once the connections * have succeeded or failed. */ - pds_flags |= PDS_NO_EXISTING_SERVERDESC_FETCH; + pds_flags |= (purpose == DIR_PURPOSE_FETCH_MICRODESC) ? + PDS_NO_EXISTING_MICRODESC_FETCH : + PDS_NO_EXISTING_SERVERDESC_FETCH; } n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS); - if (n_per_request > MAX_DL_PER_REQUEST) - n_per_request = MAX_DL_PER_REQUEST; + if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { + if (n_per_request > MAX_MICRODESC_DL_PER_REQUEST) + n_per_request = MAX_MICRODESC_DL_PER_REQUEST; + } else { + if (n_per_request > MAX_DL_PER_REQUEST) + n_per_request = MAX_DL_PER_REQUEST; + } if (n_per_request < MIN_DL_PER_REQUEST) n_per_request = MIN_DL_PER_REQUEST; @@ -4350,11 +4444,11 @@ launch_router_descriptor_downloads(smartlist_t *downloadable, req_plural, n_downloadable, rtr_plural, n_per_request); smartlist_sort_digests(downloadable); for (i=0; i < n_downloadable; i += n_per_request) { - initiate_descriptor_downloads(source, DIR_PURPOSE_FETCH_SERVERDESC, + initiate_descriptor_downloads(source, purpose, downloadable, i, i+n_per_request, pds_flags); } - last_routerdesc_download_attempted = now; + last_descriptor_download_attempted = now; } } @@ -4370,7 +4464,7 @@ update_router_descriptor_cache_downloads_v2(time_t now) digestmap_t *map; /* Which descs are in progress, or assigned? */ int i, j, n; int n_download; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list(); if (! directory_fetches_dir_info_early(options)) { @@ -4512,7 +4606,7 @@ void update_consensus_router_descriptor_downloads(time_t now, int is_vote, networkstatus_t *consensus) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); digestmap_t *map = NULL; smartlist_t *no_longer_old = smartlist_create(); smartlist_t *downloadable = smartlist_create(); @@ -4540,15 +4634,14 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, map = digestmap_new(); list_pending_descriptor_downloads(map, 0); - SMARTLIST_FOREACH(consensus->routerstatus_list, void *, rsp, - { + SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, void *, rsp) { routerstatus_t *rs = is_vote ? &(((vote_routerstatus_t *)rsp)->status) : rsp; signed_descriptor_t *sd; if ((sd = router_get_by_descriptor_digest(rs->descriptor_digest))) { - routerinfo_t *ri; + const routerinfo_t *ri; ++n_have; - if (!(ri = router_get_by_digest(rs->identity_digest)) || + if (!(ri = router_get_by_id_digest(rs->identity_digest)) || tor_memneq(ri->cache_info.signed_descriptor_digest, sd->signed_descriptor_digest, DIGEST_LEN)) { /* We have a descriptor with this digest, but either there is no @@ -4581,7 +4674,8 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, if (is_vote && source) { char time_bufnew[ISO_TIME_LEN+1]; char time_bufold[ISO_TIME_LEN+1]; - routerinfo_t *oldrouter = router_get_by_digest(rs->identity_digest); + const routerinfo_t *oldrouter; + oldrouter = router_get_by_id_digest(rs->identity_digest); format_iso_time(time_bufnew, rs->published_on); if (oldrouter) format_iso_time(time_bufold, oldrouter->cache_info.published_on); @@ -4592,7 +4686,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, source->nickname, oldrouter ? "known" : "unknown"); } smartlist_add(downloadable, rs->descriptor_digest); - }); + } SMARTLIST_FOREACH_END(rsp); if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) && smartlist_len(no_longer_old)) { @@ -4624,7 +4718,8 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_len(downloadable), n_delayed, n_have, n_in_oldrouters, n_would_reject, n_wouldnt_use, n_inprogress); - launch_router_descriptor_downloads(downloadable, source, now); + launch_descriptor_downloads(DIR_PURPOSE_FETCH_SERVERDESC, + downloadable, source, now); digestmap_free(map, NULL); done: @@ -4638,27 +4733,20 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, * do this only when we aren't seeing incoming data. see bug 652. */ #define DUMMY_DOWNLOAD_INTERVAL (20*60) -/** Launch downloads for router status as needed. */ -void -update_router_descriptor_downloads(time_t now) +/** As needed, launch a dummy router descriptor fetch to see if our + * address has changed. */ +static void +launch_dummy_descriptor_download_as_needed(time_t now, + const or_options_t *options) { - or_options_t *options = get_options(); static time_t last_dummy_download = 0; - if (should_delay_dir_fetches(options)) - return; - if (directory_fetches_dir_info_early(options)) { - update_router_descriptor_cache_downloads_v2(now); - } - update_consensus_router_descriptor_downloads(now, 0, - networkstatus_get_reasonably_live_consensus(now)); - /* XXXX023 we could be smarter here; see notes on bug 652. */ /* If we're a server that doesn't have a configured address, we rely on * directory fetches to learn when our address changes. So if we haven't * tried to get any routerdescs in a long time, try a dummy fetch now. */ if (!options->Address && server_mode(options) && - last_routerdesc_download_attempted + DUMMY_DOWNLOAD_INTERVAL < now && + last_descriptor_download_attempted + DUMMY_DOWNLOAD_INTERVAL < now && last_dummy_download + DUMMY_DOWNLOAD_INTERVAL < now) { last_dummy_download = now; directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, @@ -4667,11 +4755,28 @@ update_router_descriptor_downloads(time_t now) } } +/** Launch downloads for router status as needed. */ +void +update_router_descriptor_downloads(time_t now) +{ + const or_options_t *options = get_options(); + if (should_delay_dir_fetches(options)) + return; + if (!we_fetch_router_descriptors(options)) + return; + if (directory_fetches_dir_info_early(options)) { + update_router_descriptor_cache_downloads_v2(now); + } + + update_consensus_router_descriptor_downloads(now, 0, + networkstatus_get_reasonably_live_consensus(now, FLAV_NS)); +} + /** Launch extrainfo downloads as needed. */ void update_extrainfo_downloads(time_t now) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); routerlist_t *rl; smartlist_t *wanted; digestmap_t *pending; @@ -4699,7 +4804,7 @@ update_extrainfo_downloads(time_t now) sd = &((routerinfo_t*)smartlist_get(lst, i))->cache_info; if (sd->is_extrainfo) continue; /* This should never happen. */ - if (old_routers && !router_get_by_digest(sd->identity_digest)) + if (old_routers && !router_get_by_id_digest(sd->identity_digest)) continue; /* Couldn't check the signature if we got it. */ if (sd->extrainfo_is_bogus) continue; @@ -4793,23 +4898,31 @@ get_dir_info_status_string(void) static void count_usable_descriptors(int *num_present, int *num_usable, const networkstatus_t *consensus, - or_options_t *options, time_t now, + const or_options_t *options, time_t now, routerset_t *in_set) { + const int md = (consensus->flavor == FLAV_MICRODESC); *num_present = 0, *num_usable=0; - SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs, - { - if (in_set && ! routerset_contains_routerstatus(in_set, rs)) + SMARTLIST_FOREACH_BEGIN(consensus->routerstatus_list, routerstatus_t *, rs) + { + 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 (router_get_by_descriptor_digest(rs->descriptor_digest)) { + 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); } @@ -4823,7 +4936,7 @@ 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); + networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); double fraction; if (!consensus) @@ -4851,16 +4964,17 @@ update_router_have_minimum_dir_info(void) int num_present = 0, num_usable=0; time_t now = time(NULL); int res; - or_options_t *options = get_options(); + const or_options_t *options = get_options(); const networkstatus_t *consensus = - networkstatus_get_reasonably_live_consensus(now); + 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 network-status consensus.", + strlcpy(dir_info_status, "We have no usable consensus.", sizeof(dir_info_status)); else - strlcpy(dir_info_status, "We have no recent network-status consensus.", + strlcpy(dir_info_status, "We have no recent usable consensus.", sizeof(dir_info_status)); res = 0; goto done; @@ -4874,19 +4988,22 @@ update_router_have_minimum_dir_info(void) goto done; } + using_md = consensus->flavor == FLAV_MICRODESC; + count_usable_descriptors(&num_present, &num_usable, consensus, options, now, NULL); if (num_present < num_usable/4) { tor_snprintf(dir_info_status, sizeof(dir_info_status), - "We have only %d/%d usable descriptors.", num_present, num_usable); + "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 descriptor%s here and believed reachable!", - num_present, num_present ? "" : "s"); + "Only %d %sdescriptor%s here and believed reachable!", + num_present, using_md ? "micro" : "", num_present ? "" : "s"); res = 0; goto done; } @@ -4898,8 +5015,8 @@ update_router_have_minimum_dir_info(void) if (!num_usable || !num_present) { tor_snprintf(dir_info_status, sizeof(dir_info_status), - "We have only %d/%d usable entry node descriptors.", - num_present, num_usable); + "We have only %d/%d usable entry node %sdescriptors.", + num_present, num_usable, using_md?"micro":""); res = 0; goto done; } @@ -4939,7 +5056,7 @@ void router_reset_descriptor_download_failures(void) { networkstatus_reset_download_failures(); - last_routerdesc_download_attempted = 0; + last_descriptor_download_attempted = 0; if (!routerlist) return; SMARTLIST_FOREACH(routerlist->routers, routerinfo_t *, ri, @@ -4963,7 +5080,7 @@ router_reset_descriptor_download_failures(void) * would not cause a recent (post 0.1.1.6) dirserver to republish. */ int -router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) +router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2) { time_t r1pub, r2pub; long time_difference; @@ -4971,7 +5088,7 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) /* r1 should be the one that was published first. */ if (r1->cache_info.published_on > r2->cache_info.published_on) { - routerinfo_t *ri_tmp = r2; + const routerinfo_t *ri_tmp = r2; r2 = r1; r1 = ri_tmp; } @@ -4990,7 +5107,6 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) (r1->contact_info && r2->contact_info && strcasecmp(r1->contact_info, r2->contact_info)) || r1->is_hibernating != r2->is_hibernating || - r1->has_old_dnsworkers != r2->has_old_dnsworkers || cmp_addr_policies(r1->exit_policy, r2->exit_policy)) return 0; if ((r1->declared_family == NULL) != (r2->declared_family == NULL)) @@ -5045,7 +5161,8 @@ router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2) * incompatibility (if any). **/ int -routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, +routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri, + extrainfo_t *ei, signed_descriptor_t *sd, const char **msg) { @@ -5053,7 +5170,7 @@ routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, tor_assert(ri); tor_assert(ei); if (!sd) - sd = &ri->cache_info; + sd = (signed_descriptor_t*)&ri->cache_info; if (ei->bad_sig) { if (msg) *msg = "Extrainfo signature was bad, or signed with wrong key."; @@ -5118,7 +5235,7 @@ routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, /** Assert that the internal representation of <b>rl</b> is * self-consistent. */ void -routerlist_assert_ok(routerlist_t *rl) +routerlist_assert_ok(const routerlist_t *rl) { routerinfo_t *r2; signed_descriptor_t *sd2; @@ -5208,7 +5325,7 @@ routerlist_assert_ok(routerlist_t *rl) * If <b>router</b> is NULL, it just frees its internal memory and returns. */ const char * -esc_router_info(routerinfo_t *router) +esc_router_info(const routerinfo_t *router) { static char *info=NULL; char *esc_contact, *esc_platform; @@ -5310,38 +5427,6 @@ routerset_get_countryname(const char *c) return country; } -#if 0 -/** Add the GeoIP database's integer index (+1) of a valid two-character - * country code to the routerset's <b>countries</b> bitarray. Return the - * integer index if the country code is valid, -1 otherwise.*/ -static int -routerset_add_country(const char *c) -{ - char country[3]; - country_t cc; - - /* XXXX: Country codes must be of the form \{[a-z\?]{2}\} but this accepts - \{[.]{2}\}. Do we need to be strict? -RH */ - /* Nope; if the country code is bad, we'll get 0 when we look it up. */ - - if (!geoip_is_loaded()) { - log(LOG_WARN, LD_CONFIG, "GeoIP database not loaded: Cannot add country" - "entry %s, ignoring.", c); - return -1; - } - - memcpy(country, c+1, 2); - country[2] = '\0'; - tor_strlower(country); - - if ((cc=geoip_get_country(country))==-1) { - log(LOG_WARN, LD_CONFIG, "Country code '%s' is not valid, ignoring.", - country); - } - return cc; -} -#endif - /** Update the routerset's <b>countries</b> bitarray_t. Called whenever * the GeoIP database is reloaded. */ @@ -5430,7 +5515,7 @@ routerset_parse(routerset_t *target, const char *s, const char *description) void refresh_all_country_info(void) { - or_options_t *options = get_options(); + const or_options_t *options = get_options(); if (options->EntryNodes) routerset_refresh_countries(options->EntryNodes); @@ -5443,7 +5528,7 @@ refresh_all_country_info(void) if (options->_ExcludeExitNodesUnion) routerset_refresh_countries(options->_ExcludeExitNodesUnion); - routerlist_refresh_countries(); + nodelist_refresh_countries(); } /** Add all members of the set <b>source</b> to <b>target</b>. */ @@ -5493,11 +5578,11 @@ routerset_is_empty(const routerset_t *set) static int routerset_contains(const routerset_t *set, const tor_addr_t *addr, uint16_t orport, - const char *nickname, const char *id_digest, int is_named, + const char *nickname, const char *id_digest, country_t country) { - if (!set || !set->list) return 0; - (void) is_named; /* not supported */ + 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)) @@ -5525,13 +5610,14 @@ routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei) ei->port, ei->nickname, ei->identity_digest, - -1, /*is_named*/ -1 /*country*/); } -/** Return true iff <b>ri</b> is in <b>set</b>. */ +/** 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, routerinfo_t *ri) +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); @@ -5540,13 +5626,15 @@ routerset_contains_router(const routerset_t *set, routerinfo_t *ri) ri->or_port, ri->nickname, ri->cache_info.identity_digest, - ri->is_named, - ri->country); + country); } -/** Return true iff <b>rs</b> is in <b>set</b>. */ +/** 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, routerstatus_t *rs) +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); @@ -5555,50 +5643,59 @@ routerset_contains_routerstatus(const routerset_t *set, routerstatus_t *rs) rs->or_port, rs->nickname, rs->identity_digest, - rs->is_named, - -1); + 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 routerinfo_t that is a member of <b>routerset</b> to +/** 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_routers(smartlist_t *out, const routerset_t *routerset, - const routerset_t *excludeset, int running_only) -{ +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 (!warned_nicknames) - warned_nicknames = smartlist_create(); - if (routerset_is_list(routerset)) { + 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, { - routerinfo_t *router = router_get_by_nickname(name, 1); - if (router) { - if (!running_only || router->is_running) - if (!routerset_contains_router(excludeset, router)) - smartlist_add(out, router); + 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. */ - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, router, { - if (running_only && !router->is_running) + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (running_only && !node->is_running) continue; - if (routerset_contains_router(routerset, router) && - !routerset_contains_router(excludeset, router)) - smartlist_add(out, router); + if (routerset_contains_node(routerset, node) && + !routerset_contains_node(excludeset, node)) + smartlist_add(out, (void*)node); }); } } #if 0 -/** Add to <b>target</b> every routerinfo_t from <b>source</b> except: +/** 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 @@ -5607,40 +5704,40 @@ routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, * 3) If <b>running_only</b>, don't add non-running routers. */ void -routersets_get_disjunction(smartlist_t *target, +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, routerinfo_t *, router, { + SMARTLIST_FOREACH(source, const node_t *, node, { int include_result; - if (running_only && !router->is_running) + if (running_only && !node->is_running) continue; if (!routerset_is_empty(include)) - include_result = routerset_contains_router(include, router); + include_result = routerset_contains_node(include, node); else include_result = 1; if (include_result) { - int exclude_result = routerset_contains_router(exclude, router); + int exclude_result = routerset_contains_node(exclude, node); if (include_result >= exclude_result) - smartlist_add(target, router); + smartlist_add(target, (void*)node); } }); } #endif -/** Remove every routerinfo_t from <b>lst</b> that is in <b>routerset</b>. */ +/** Remove every node_t from <b>lst</b> that is in <b>routerset</b>. */ void -routerset_subtract_routers(smartlist_t *lst, const routerset_t *routerset) -{ +routerset_subtract_nodes(smartlist_t *lst, const routerset_t *routerset) +{ /*XXXX MOVE ? */ tor_assert(lst); if (!routerset) return; - SMARTLIST_FOREACH(lst, routerinfo_t *, r, { - if (routerset_contains_router(routerset, r)) { + 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, r); + SMARTLIST_DEL_CURRENT(lst, node); } }); } @@ -5706,18 +5803,23 @@ routerset_free(routerset_t *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 -routerinfo_set_country(routerinfo_t *ri) +node_set_country(node_t *node) { - ri->country = geoip_get_country_by_ip(ri->addr); + 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 -routerlist_refresh_countries(void) +nodelist_refresh_countries(void) /* MOVE */ { - routerlist_t *rl = router_get_routerlist(); - SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, - routerinfo_set_country(ri)); + 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 @@ -5760,7 +5862,7 @@ hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, int hid_serv_acting_as_directory(void) { - routerinfo_t *me = router_get_my_routerinfo(); + const routerinfo_t *me = router_get_my_routerinfo(); if (!me) return 0; if (!get_options()->HidServDirectoryV2) { @@ -5776,7 +5878,7 @@ hid_serv_acting_as_directory(void) int hid_serv_responsible_for_desc_id(const char *query) { - routerinfo_t *me; + const routerinfo_t *me; routerstatus_t *last_rs; const char *my_id, *last_id; int result; diff --git a/src/or/routerlist.h b/src/or/routerlist.h index fec18705b3..cae8814333 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -11,7 +11,7 @@ #ifndef _TOR_ROUTERLIST_H #define _TOR_ROUTERLIST_H -int get_n_authorities(authority_type_t type); +int get_n_authorities(dirinfo_type_t type); int trusted_dirs_reload_certs(void); int trusted_dirs_load_certs_from_string(const char *contents, int from_store, int flush); @@ -27,53 +27,50 @@ int router_reload_router_list(void); int authority_cert_dl_looks_uncertain(const char *id_digest); smartlist_t *router_get_trusted_dir_servers(void); -routerstatus_t *router_pick_directory_server(authority_type_t type, int flags); +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); -routerstatus_t *router_pick_trusteddirserver(authority_type_t type, int flags); +const routerstatus_t *router_pick_trusteddirserver(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); -void routerlist_add_family(smartlist_t *sl, routerinfo_t *router); -int routers_in_same_family(routerinfo_t *r1, routerinfo_t *r2); int routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2); -void add_nickname_list_to_smartlist(smartlist_t *sl, const char *list, - int must_be_running); -int router_nickname_is_in_list(routerinfo_t *router, const char *list); -routerinfo_t *routerlist_find_my_routerinfo(void); -routerinfo_t *router_find_exact_exit_enclave(const char *address, +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 router_is_unreliable(routerinfo_t *router, int need_uptime, +int node_is_unreliable(const node_t *router, int need_uptime, int need_capacity, int need_guard); -uint32_t router_get_advertised_bandwidth(routerinfo_t *router); -uint32_t router_get_advertised_bandwidth_capped(routerinfo_t *router); +uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); +uint32_t router_get_advertised_bandwidth_capped(const routerinfo_t *router); -routerinfo_t *routerlist_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule); -routerstatus_t *routerstatus_sl_choose_by_bandwidth(smartlist_t *sl, - bandwidth_weight_rule_t rule); +const node_t *node_sl_choose_by_bandwidth(smartlist_t *sl, + bandwidth_weight_rule_t rule); -routerinfo_t *router_choose_random_node(smartlist_t *excludedsmartlist, +const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -routerinfo_t *router_get_by_nickname(const char *nickname, +const routerinfo_t *router_get_by_nickname(const char *nickname, int warn_if_unnamed); -int router_digest_version_as_new_as(const char *digest, const char *cutoff); +int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, - authority_type_t type); + dirinfo_type_t type); #define router_digest_is_trusted_dir(d) \ - router_digest_is_trusted_dir_type((d), NO_AUTHORITY) + router_digest_is_trusted_dir_type((d), NO_DIRINFO) int router_addr_is_trusted_dir(uint32_t addr); int hexdigest_to_digest(const char *hexdigest, char *digest); -routerinfo_t *router_get_by_hexdigest(const char *hexdigest); -routerinfo_t *router_get_by_digest(const 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); signed_descriptor_t *router_get_by_extrainfo_digest(const char *digest); signed_descriptor_t *extrainfo_get_by_descriptor_digest(const char *digest); -const char *signed_descriptor_get_body(signed_descriptor_t *desc); -const char *signed_descriptor_get_annotations(signed_descriptor_t *desc); +const char *signed_descriptor_get_body(const signed_descriptor_t *desc); +const char *signed_descriptor_get_annotations(const signed_descriptor_t *desc); routerlist_t *router_get_routerlist(void); void routerinfo_free(routerinfo_t *router); void extrainfo_free(extrainfo_t *extrainfo); @@ -132,63 +129,71 @@ 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_routers_reject(uint32_t addr, uint16_t port, - int need_uptime); -int router_exit_policy_rejects_all(routerinfo_t *router); +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, - authority_type_t type); + dirinfo_type_t type); void authority_cert_free(authority_cert_t *cert); void clear_trusted_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(routerinfo_t *r1, routerinfo_t *r2); -int routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei, +int router_differences_are_cosmetic(const routerinfo_t *r1, + const routerinfo_t *r2); +int routerinfo_incompatible_with_extrainfo(const routerinfo_t *ri, + extrainfo_t *ei, signed_descriptor_t *sd, const char **msg); -void routerlist_assert_ok(routerlist_t *rl); -const char *esc_router_info(routerinfo_t *router); +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, routerinfo_t *ri); +int routerset_contains_router(const routerset_t *set, const routerinfo_t *ri, + country_t country); int routerset_contains_routerstatus(const routerset_t *set, - routerstatus_t *rs); + const routerstatus_t *rs, + country_t country); int routerset_contains_extendinfo(const routerset_t *set, const extend_info_t *ei); -void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset, - const routerset_t *excludeset, - int running_only); + +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_disjunction(smartlist_t *target, const smartlist_t *source, +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_routers(smartlist_t *out, +void routerset_subtract_nodes(smartlist_t *out, const routerset_t *routerset); + char *routerset_to_string(const routerset_t *routerset); -void routerset_refresh_countries(routerset_t *target); int routerset_equal(const routerset_t *old, const routerset_t *new); void routerset_free(routerset_t *routerset); -void routerinfo_set_country(routerinfo_t *ri); -void routerlist_refresh_countries(void); void refresh_all_country_info(void); int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, @@ -196,5 +201,16 @@ int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, int hid_serv_acting_as_directory(void); int hid_serv_responsible_for_desc_id(const char *id); +void list_pending_microdesc_downloads(digestmap_t *result); +void launch_descriptor_downloads(int purpose, + smartlist_t *downloadable, + const routerstatus_t *source, + time_t now); + +int hex_digest_nickname_decode(const char *hexdigest, + char *digest_out, + char *nickname_qualifier_out, + char *nickname_out); + #endif diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 001c4d6c7f..e8b2dd7d2b 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -70,7 +70,6 @@ typedef enum { K_V, K_W, K_M, - K_EVENTDNS, K_EXTRA_INFO, K_EXTRA_INFO_DIGEST, K_CACHES_EXTRA_INFO, @@ -287,7 +286,6 @@ static token_rule_t routerdesc_token_table[] = { T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("caches-extra-info", K_CACHES_EXTRA_INFO, NO_ARGS, NO_OBJ ), - T01("eventdns", K_EVENTDNS, ARGS, NO_OBJ ), T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), T1( "bandwidth", K_BANDWIDTH, GE(3), NO_OBJ ), @@ -1357,7 +1355,6 @@ router_parse_entry_from_string(const char *s, const char *end, tor_assert(tok->n_args >= 5); router = tor_malloc_zero(sizeof(routerinfo_t)); - router->country = -1; router->cache_info.routerlist_index = -1; router->cache_info.annotations_len = s-start_of_annotations + prepend_len; router->cache_info.signed_descriptor_len = end-s; @@ -1503,13 +1500,6 @@ router_parse_entry_from_string(const char *s, const char *end, router->contact_info = tor_strdup(tok->args[0]); } - if ((tok = find_opt_by_keyword(tokens, K_EVENTDNS))) { - router->has_old_dnsworkers = tok->n_args && !strcmp(tok->args[0], "0"); - } else if (router->platform) { - if (! tor_version_as_new_as(router->platform, "0.1.2.2-alpha")) - router->has_old_dnsworkers = 1; - } - if (find_opt_by_keyword(tokens, K_REJECT6) || find_opt_by_keyword(tokens, K_ACCEPT6)) { log_warn(LD_DIR, "Rejecting router with reject6/accept6 line: they crash " @@ -1574,8 +1564,6 @@ router_parse_entry_from_string(const char *s, const char *end, "router descriptor") < 0) goto err; - routerinfo_set_country(router); - if (!router->or_port) { log_warn(LD_DIR,"or_port unreadable or 0. Failing."); goto err; @@ -1823,9 +1811,9 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) struct in_addr in; char *address = NULL; tor_assert(tok->n_args); - /* XXX023 use tor_addr_port_parse() below instead. -RD */ - if (parse_addr_port(LOG_WARN, tok->args[0], &address, NULL, - &cert->dir_port)<0 || + /* XXX023 use some tor_addr parse function below instead. -RD */ + if (tor_addr_port_split(LOG_WARN, tok->args[0], &address, + &cert->dir_port) < 0 || tor_inet_aton(address, &in) == 0) { log_warn(LD_DIR, "Couldn't parse dir-address in certificate"); tor_free(address); @@ -1975,6 +1963,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, if (!consensus_method) flav = FLAV_NS; + tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC); eos = find_start_of_next_routerstatus(*s); @@ -1987,15 +1976,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, goto err; } tok = find_by_keyword(tokens, K_R); - tor_assert(tok->n_args >= 7); + tor_assert(tok->n_args >= 7); /* guaranteed by GE(7) in K_R setup */ if (flav == FLAV_NS) { if (tok->n_args < 8) { log_warn(LD_DIR, "Too few arguments to r"); goto err; } - } else { - offset = -1; + } else if (flav == FLAV_MICRODESC) { + offset = -1; /* There is no identity digest */ } + if (vote_rs) { rs = &vote_rs->status; } else { @@ -2069,7 +2059,7 @@ routerstatus_parse_entry_from_string(memarea_t *area, else if (!strcmp(tok->args[i], "Fast")) rs->is_fast = 1; else if (!strcmp(tok->args[i], "Running")) - rs->is_running = 1; + rs->is_flagged_running = 1; else if (!strcmp(tok->args[i], "Named")) rs->is_named = 1; else if (!strcmp(tok->args[i], "Valid")) @@ -2100,6 +2090,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, 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"); @@ -2109,6 +2101,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, 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"); + /* XXXX023 NM microdescs: 0.2.3.1-alpha isn't widely used yet, but + * not all 0.2.3.0-alpha "versions" actually support microdesc cacheing + * right. There's a compromise here. Since this is 5 May, let's + * err on the side of having some possible caches to use. Once more + * caches are running 0.2.3.1-alpha, we can bump this version number. + */ + rs->version_supports_microdesc_cache = + tor_version_as_new_as(tok->args[0], "0.2.3.0-alpha"); + rs->version_supports_optimistic_data = + tor_version_as_new_as(tok->args[0], "0.2.3.1-alpha"); } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); @@ -2171,6 +2173,16 @@ routerstatus_parse_entry_from_string(memarea_t *area, vote_rs->microdesc = line; } } SMARTLIST_FOREACH_END(t); + } else if (flav == FLAV_MICRODESC) { + tok = find_opt_by_keyword(tokens, K_M); + if (tok) { + tor_assert(tok->n_args); + if (digest256_from_base64(rs->descriptor_digest, tok->args[0])) { + log_warn(LD_DIR, "Error decoding microdescriptor digest %s", + escaped(tok->args[0])); + goto err; + } + } } if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME)) @@ -3502,10 +3514,10 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos) siglist = detached_get_signatures(sigs, flavor); is_duplicate = 0; - SMARTLIST_FOREACH(siglist, document_signature_t *, s, { - if (s->alg == alg && - tor_memeq(id_digest, s->identity_digest, DIGEST_LEN) && - tor_memeq(sk_digest, s->signing_key_digest, DIGEST_LEN)) { + SMARTLIST_FOREACH(siglist, document_signature_t *, dsig, { + if (dsig->alg == alg && + tor_memeq(id_digest, dsig->identity_digest, DIGEST_LEN) && + tor_memeq(sk_digest, dsig->signing_key_digest, DIGEST_LEN)) { is_duplicate = 1; } }); @@ -4358,7 +4370,7 @@ microdescs_parse_from_string(const char *s, const char *eos, } if ((tok = find_opt_by_keyword(tokens, K_P))) { - md->exitsummary = tor_strdup(tok->args[0]); + md->exit_policy = parse_short_policy(tok->args[0]); } crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); @@ -4969,7 +4981,7 @@ rend_parse_introduction_points(rend_service_descriptor_t *parsed, info->identity_digest, DIGEST_LEN); /* Parse IP address. */ tok = find_by_keyword(tokens, R_IPO_IP_ADDRESS); - if (tor_addr_from_str(&info->addr, tok->args[0])<0) { + if (tor_addr_parse(&info->addr, tok->args[0])<0) { log_warn(LD_REND, "Could not parse introduction point address."); rend_intro_point_free(intro); goto err; diff --git a/src/or/status.c b/src/or/status.c new file mode 100644 index 0000000000..3e4cb779a3 --- /dev/null +++ b/src/or/status.c @@ -0,0 +1,115 @@ +/* Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file status.c + * \brief Keep status information and log the heartbeat messages. + **/ + +#include "or.h" +#include "config.h" +#include "status.h" +#include "nodelist.h" +#include "router.h" +#include "circuitlist.h" +#include "main.h" + +/** Return the total number of circuits. */ +static int +count_circuits(void) +{ + circuit_t *circ; + int nr=0; + + for (circ = _circuit_get_global_list(); circ; circ = circ->next) + nr++; + + return nr; +} + +/** Take seconds <b>secs</b> and return a newly allocated human-readable + * uptime string */ +static char * +secs_to_uptime(long secs) +{ + long int days = secs / 86400; + int hours = (int)((secs - (days * 86400)) / 3600); + int minutes = (int)((secs - (days * 86400) - (hours * 3600)) / 60); + char *uptime_string = NULL; + + switch (days) { + case 0: + tor_asprintf(&uptime_string, "%d:%02d hours", hours, minutes); + break; + case 1: + tor_asprintf(&uptime_string, "%ld day %d:%02d hours", + days, hours, minutes); + break; + default: + tor_asprintf(&uptime_string, "%ld days %d:%02d hours", + days, hours, minutes); + break; + } + + return uptime_string; +} + +/** Take <b>bytes</b> and returns a newly allocated human-readable usage + * string. */ +static char * +bytes_to_usage(uint64_t bytes) +{ + char *bw_string = NULL; + + if (bytes < (1<<20)) { /* Less than a megabyte. */ + tor_asprintf(&bw_string, U64_FORMAT" kB", U64_PRINTF_ARG(bytes>>10)); + } else if (bytes < (1<<30)) { /* Megabytes. Let's add some precision. */ + double bw = U64_TO_DBL(bytes); + tor_asprintf(&bw_string, "%.2f MB", bw/(1<<20)); + } else { /* Gigabytes. */ + double bw = U64_TO_DBL(bytes); + tor_asprintf(&bw_string, "%.2f GB", bw/(1<<30)); + } + + return bw_string; +} + +/** Log a "heartbeat" message describing Tor's status and history so that the + * user can know that there is indeed a running Tor. Return 0 on success and + * -1 on failure. */ +int +log_heartbeat(time_t now) +{ + char *bw_sent = NULL; + char *bw_rcvd = NULL; + char *uptime = NULL; + const routerinfo_t *me; + + const or_options_t *options = get_options(); + (void)now; + + if (public_server_mode(options)) { + /* Let's check if we are in the current cached consensus. */ + if (!(me = router_get_my_routerinfo())) + return -1; /* Something stinks, we won't even attempt this. */ + else + if (!node_get_by_id(me->cache_info.identity_digest)) + log_fn(LOG_NOTICE, LD_HEARTBEAT, "Heartbeat: It seems like we are not " + "in the cached consensus."); + } + + uptime = secs_to_uptime(get_uptime()); + bw_rcvd = bytes_to_usage(get_bytes_read()); + bw_sent = bytes_to_usage(get_bytes_written()); + + log_fn(LOG_NOTICE, LD_HEARTBEAT, "Heartbeat: Tor's uptime is %s, with %d " + "circuits open. I've sent %s and received %s.", + uptime, count_circuits(),bw_sent,bw_rcvd); + + tor_free(uptime); + tor_free(bw_sent); + tor_free(bw_rcvd); + + return 0; +} + diff --git a/src/or/status.h b/src/or/status.h new file mode 100644 index 0000000000..ac726a1d2d --- /dev/null +++ b/src/or/status.h @@ -0,0 +1,10 @@ +/* Copyright (c) 2010, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef _TOR_STATUS_H +#define _TOR_STATUS_H + +int log_heartbeat(time_t now); + +#endif + diff --git a/src/or/transports.c b/src/or/transports.c new file mode 100644 index 0000000000..6e8200f407 --- /dev/null +++ b/src/or/transports.c @@ -0,0 +1,1048 @@ +/* Copyright (c) 2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file transports.c + * \brief Pluggable Transports related code. + **/ + +#define PT_PRIVATE +#include "or.h" +#include "config.h" +#include "circuitbuild.h" +#include "transports.h" +#include "util.h" + +static void set_managed_proxy_environment(char ***envp, + const managed_proxy_t *mp); +static INLINE int proxy_configuration_finished(const managed_proxy_t *mp); + +static void managed_proxy_destroy(managed_proxy_t *mp); + +static void handle_finished_proxy(managed_proxy_t *mp); +static void configure_proxy(managed_proxy_t *mp); + +static void parse_method_error(const char *line, int is_server_method); +#define parse_server_method_error(l) parse_method_error(l, 1) +#define parse_client_method_error(l) parse_method_error(l, 0) + +static INLINE void free_execve_args(char **arg); + +/** Managed proxy protocol strings */ +#define PROTO_ENV_ERROR "ENV-ERROR" +#define PROTO_NEG_SUCCESS "VERSION" +#define PROTO_NEG_FAIL "VERSION-ERROR no-version" +#define PROTO_CMETHOD "CMETHOD" +#define PROTO_SMETHOD "SMETHOD" +#define PROTO_CMETHOD_ERROR "CMETHOD-ERROR" +#define PROTO_SMETHOD_ERROR "SMETHOD-ERROR" +#define PROTO_CMETHODS_DONE "CMETHODS DONE" +#define PROTO_SMETHODS_DONE "SMETHODS DONE" + +/* The smallest valid managed proxy protocol line that can + appear. It's the size of "VERSION 1" */ +#define SMALLEST_MANAGED_LINE_SIZE 9 + +/** Number of environment variables for managed proxy clients/servers. */ +#define ENVIRON_SIZE_CLIENT 5 +#define ENVIRON_SIZE_SERVER 8 + +/** The first and only supported - at the moment - configuration + protocol version. */ +#define PROTO_VERSION_ONE 1 + +/** List of unconfigured managed proxies. */ +static smartlist_t *managed_proxy_list = NULL; +/** Number of still unconfigured proxies. */ +static int unconfigured_proxies_n = 0; + +/** "The main idea is:" + + Each managed proxy is represented by a 'managed_proxy_t'. + Each managed proxy can support multiple transports. + Each managed proxy gets configured through a multistep process. + + 'managed_proxy_list' contains all the managed proxies this tor + instance is supporting. + In the 'managed_proxy_list' there are 'unconfigured_proxies_n' + managed proxies that are still unconfigured. + + In every run_scheduled_event() tick, we attempt to launch and then + configure the unconfiged managed proxies, using the configuration + protocol defined in the 180_pluggable_transport.txt proposal. A + managed proxy might need several ticks to get fully configured. + + When a managed proxy is fully configured, we register all its + transports to the circuitbuild.c subsystem. At that point the + transports are owned by the circuitbuild.c subsystem. + + When a managed proxy fails to follow the 180 configuration + protocol, it gets marked as broken and gets destroyed. + + "In a little more technical detail:" + + While we are serially parsing torrc, we store all the transports + that a proxy should spawn in its 'transports_to_launch' element. + + When we finish reading the torrc, we spawn the managed proxy and + expect {S,C}METHOD lines from its output. We add transports + described by METHOD lines to its 'transports' element, as + '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 + 'transport_t's can be transformed into dangling pointers at any + point by the circuitbuild.c subsystem, and so we replace all + 'transport_t's with strings describing the transport names. We + can still go from a transport name to a 'transport_t' using the + fact that transport names uniquely identify 'transport_t's. + + "In even more technical detail I shall describe what happens when + the SIGHUP bell tolls:" + + We immediately destroy all unconfigured proxies (We shouldn't have + unconfigured proxies in the first place, except when SIGHUP rings + immediately after tor is launched.). + + We mark all managed proxies and transports to signify that they + must be removed if they don't contribute by the new torrc + (marked_for_removal). + We also mark all managed proxies to signify that they might need + to be restarted so that they end up supporting all the transports + the new torrc wants them to support (got_hup). + We also clear their 'transports_to_launch' list so that we can put + there the transports we need to launch according to the new torrc. + + We then start parsing torrc again. + + Everytime we encounter a transport line using a known pre-SIGHUP + managed proxy, we cleanse that proxy from the removal mark. + + We also mark it as unconfigured so that on the next scheduled + events tick, we investigate whether we need to restart the proxy + so that it also spawns the new transports. + If the post-SIGHUP 'transports_to_launch' list is identical to the + pre-SIGHUP one, it means that no changes were introduced to this + proxy during the SIGHUP and no restart has to take place. + + During the post-SIGHUP torrc parsing, we unmark all transports + spawned by managed proxies that we find in our torrc. + We do that so that if we don't need to restart a managed proxy, we + can continue using its old transports normally. + If we end up restarting the proxy, we destroy and unregister all + old transports from the circuitbuild.c subsystem. +*/ + +/** Return true if there are still unconfigured managed proxies. */ +int +pt_proxies_configuration_pending(void) +{ + return !! unconfigured_proxies_n; +} + +/** Return true if <b>mp</b> has the same argv as <b>proxy_argv</b> */ +static int +managed_proxy_has_argv(const managed_proxy_t *mp, char **proxy_argv) +{ + char **tmp1=proxy_argv; + char **tmp2=mp->argv; + + tor_assert(tmp1); + tor_assert(tmp2); + + while (*tmp1 && *tmp2) { + if (strcmp(*tmp1++, *tmp2++)) + return 0; + } + + if (!*tmp1 && !*tmp2) + return 1; + + return 0; +} + +/** Return a managed proxy with the same argv as <b>proxy_argv</b>. + * If no such managed proxy exists, return NULL. */ +static managed_proxy_t * +get_managed_proxy_by_argv_and_type(char **proxy_argv, int is_server) +{ + if (!managed_proxy_list) + return NULL; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (managed_proxy_has_argv(mp, proxy_argv) && + mp->is_server == is_server) + return mp; + } SMARTLIST_FOREACH_END(mp); + + return NULL; +} + +/** Add <b>transport</b> to managed proxy <b>mp</b>. */ +static void +add_transport_to_proxy(const char *transport, managed_proxy_t *mp) +{ + tor_assert(mp->transports_to_launch); + if (!smartlist_string_isin(mp->transports_to_launch, transport)) + smartlist_add(mp->transports_to_launch, tor_strdup(transport)); +} + +/** Called when a SIGHUP occurs. Returns true if managed proxy + * <b>mp</b> needs to be restarted after the SIGHUP, based on the new + * torrc. */ +static int +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. + + If the two lists contain the same strings, we don't need to + restart the proxy, since it already does what we want. */ + + tor_assert(smartlist_len(mp->transports_to_launch) > 0); + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + 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)) + goto needs_restart; + + } SMARTLIST_FOREACH_END(t_t_l); + + return 0; + + needs_restart: + return 1; +} + +/** Managed proxy <b>mp</b> must be restarted. Do all the necessary + * 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; + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + tor_assert(mp->pid); + + /* kill the old obfsproxy process */ + tor_terminate_process(mp->pid); + mp->pid = 0; + fclose(mp->_stdout); + + /* 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); + if (t_tmp) + t_tmp->marked_for_removal = 1; + } SMARTLIST_FOREACH_END(t_name); + sweep_transport_list(); + + /* free the transport names in mp->transports */ + SMARTLIST_FOREACH(mp->transports, char *, t_name, tor_free(t_name)); + smartlist_clear(mp->transports); + + /* flag it as an infant proxy so that it gets launched on next tick */ + mp->conf_state = PT_PROTO_INFANT; +} + +/** Launch managed proxy <b>mp</b>. */ +static int +launch_managed_proxy(managed_proxy_t *mp) +{ + (void) mp; + (void) set_managed_proxy_environment; + return -1; +#if 0 + /* XXXX023 we must reenable this code for managed proxies to work. + * "All it needs" is revision to work with the new tor_spawn_background + * API. */ + char **envp=NULL; + int pid; + process_handle_t proc; + FILE *stdout_read = NULL; + int stdout_pipe=-1, stderr_pipe=-1; + + /* prepare the environment variables for the managed proxy */ + set_managed_proxy_environment(&envp, mp); + + pid = tor_spawn_background(mp->argv[0], (const char **)mp->argv, + (const char **)envp, &proc); + if (pid < 0) { + log_warn(LD_GENERAL, "Managed proxy at '%s' failed at launch.", + mp->argv[0]); + return -1; + } + + /* free the memory allocated by set_managed_proxy_environment(). */ + free_execve_args(envp); + + /* Set stdout/stderr pipes to be non-blocking */ +#ifdef _WIN32 + { + u_long nonblocking = 1; + ioctlsocket(stdout_pipe, FIONBIO, &nonblocking); + } +#else + fcntl(stdout_pipe, F_SETFL, O_NONBLOCK); +#endif + + /* Open the buffered IO streams */ + stdout_read = fdopen(stdout_pipe, "r"); + + log_info(LD_CONFIG, "Managed proxy has spawned at PID %d.", pid); + + mp->conf_state = PT_PROTO_LAUNCHED; + mp->_stdout = stdout_read; + mp->pid = pid; +#endif + return 0; +} + +/** Check if any of the managed proxies we are currently trying to + * configure have anything new to say. This is called from + * run_scheduled_events(). */ +void +pt_configure_remaining_proxies(void) +{ + log_debug(LD_CONFIG, "Configuring remaining managed proxies (%d)!", + unconfigured_proxies_n); + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + tor_assert(mp->conf_state != PT_PROTO_BROKEN); + + if (mp->got_hup) { + mp->got_hup = 0; + + /* This proxy is marked by a SIGHUP. Check whether we need to + restart it. */ + if (proxy_needs_restart(mp)) { + log_info(LD_GENERAL, "Preparing managed proxy for restart."); + proxy_prepare_for_restart(mp); + continue; + } else { /* it doesn't need to be restarted. */ + log_info(LD_GENERAL, "Nothing changed for managed proxy after HUP: " + "not restarting."); + unconfigured_proxies_n--; + tor_assert(unconfigured_proxies_n >= 0); + } + + continue; + } + + /* If the proxy is not fully configured, try to configure it + futher. */ + if (!proxy_configuration_finished(mp)) + configure_proxy(mp); + + } SMARTLIST_FOREACH_END(mp); +} + +/** 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) { + launch_managed_proxy(mp); + return; + } + + tor_assert(mp->conf_state != PT_PROTO_INFANT); + + while (1) { + r = get_string_from_pipe(mp->_stdout, 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_notice(LD_GENERAL, "Managed proxy stream closed. " + "Most probably application stopped running"); + mp->conf_state = PT_PROTO_BROKEN; + } else { /* unknown stream status */ + log_notice(LD_GENERAL, "Unknown stream status while configuring proxy."); + } + + /* if the proxy finished configuring, exit the loop. */ + if (proxy_configuration_finished(mp)) { + handle_finished_proxy(mp); + return; + } + } +} + +/** Register server managed proxy <b>mp</b> transports to state */ +static void +register_server_proxy(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_create(); + + 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); + smartlist_add(sm_tmp, tor_strdup(t->name)); + } SMARTLIST_FOREACH_END(t); + + /* Since server proxies don't register their transports in the + circuitbuild.c subsystem, it's our duty to free them when we + switch mp->transports to strings. */ + SMARTLIST_FOREACH(mp->transports, transport_t *, t, transport_free(t)); + smartlist_free(mp->transports); + + mp->transports = sm_tmp; +} + +/** Register all the transports supported by client managed proxy + * <b>mp</b> to the bridge subsystem. */ +static void +register_client_proxy(managed_proxy_t *mp) +{ + 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_create(); + + tor_assert(mp->conf_state != PT_PROTO_COMPLETED); + SMARTLIST_FOREACH_BEGIN(mp->transports, transport_t *, t) { + r = transport_add(t); + switch (r) { + case -1: + log_notice(LD_GENERAL, "Could not add transport %s. Skipping.", t->name); + transport_free(t); + 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); + 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) +{ + if (mp->is_server) + register_server_proxy(mp); + else + register_client_proxy(mp); +} + +/** Free memory allocated by managed proxy <b>mp</b>. */ +static void +managed_proxy_destroy(managed_proxy_t *mp) +{ + 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)); + + /* free the transports smartlist */ + smartlist_free(mp->transports); + + /* free the transports_to_launch smartlist */ + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_free(mp->transports_to_launch); + + /* remove it from the list of managed proxies */ + smartlist_remove(managed_proxy_list, mp); + + /* close its stdout stream */ + if (mp->_stdout) + fclose(mp->_stdout); + + /* free the argv */ + free_execve_args(mp->argv); + + if (mp->pid) + tor_terminate_process(mp->pid); + + tor_free(mp); +} + +/** Handle a configured or broken managed proxy <b>mp</b>. */ +static void +handle_finished_proxy(managed_proxy_t *mp) +{ + switch (mp->conf_state) { + case PT_PROTO_BROKEN: /* if broken: */ + managed_proxy_destroy(mp); /* annihilate it. */ + break; + case PT_PROTO_CONFIGURED: /* if configured correctly: */ + register_proxy(mp); /* register its transports */ + mp->conf_state = PT_PROTO_COMPLETED; /* and mark it as completed. */ + break; + case PT_PROTO_INFANT: + case PT_PROTO_LAUNCHED: + case PT_PROTO_ACCEPTING_METHODS: + case PT_PROTO_COMPLETED: + default: + log_warn(LD_CONFIG, "Unexpected managed proxy state in " + "handle_finished_proxy()."); + tor_assert(0); + } + + unconfigured_proxies_n--; + tor_assert(unconfigured_proxies_n >= 0); +} + +/** Return true if the configuration of the managed proxy <b>mp</b> is + finished. */ +static INLINE int +proxy_configuration_finished(const managed_proxy_t *mp) +{ + return (mp->conf_state == PT_PROTO_CONFIGURED || + mp->conf_state == PT_PROTO_BROKEN); +} + +/** This function is called when a proxy sends an {S,C}METHODS DONE message. */ +static void +handle_methods_done(const managed_proxy_t *mp) +{ + tor_assert(mp->transports); + + if (smartlist_len(mp->transports) == 0) + log_notice(LD_GENERAL, "Proxy was spawned successfully, " + "but it didn't laucn any pluggable transport listeners!"); + + log_info(LD_CONFIG, "%s managed proxy configuration completed!", + mp->is_server ? "Server" : "Client"); +} + +/** Handle a configuration protocol <b>line</b> received from a + * managed proxy <b>mp</b>. */ +void +handle_proxy_line(const char *line, managed_proxy_t *mp) +{ + log_debug(LD_GENERAL, "Got a line from managed proxy: %s\n", line); + + if (strlen(line) < SMALLEST_MANAGED_LINE_SIZE) { + log_warn(LD_GENERAL, "Managed proxy configuration line is too small. " + "Discarding"); + goto err; + } + + if (!strcmpstart(line, PROTO_ENV_ERROR)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + parse_env_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_FAIL)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + log_warn(LD_CONFIG, "Managed proxy could not pick a " + "configuration protocol version."); + goto err; + } else if (!strcmpstart(line, PROTO_NEG_SUCCESS)) { + if (mp->conf_state != PT_PROTO_LAUNCHED) + goto err; + + if (parse_version(line,mp) < 0) + goto err; + + tor_assert(mp->conf_protocol != 0); + mp->conf_state = PT_PROTO_ACCEPTING_METHODS; + return; + } else if (!strcmpstart(line, PROTO_CMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_SMETHODS_DONE)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + handle_methods_done(mp); + + mp->conf_state = PT_PROTO_CONFIGURED; + return; + } else if (!strcmpstart(line, PROTO_CMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_client_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_SMETHOD_ERROR)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + parse_server_method_error(line); + goto err; + } else if (!strcmpstart(line, PROTO_CMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_cmethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, PROTO_SMETHOD)) { + if (mp->conf_state != PT_PROTO_ACCEPTING_METHODS) + goto err; + + if (parse_smethod_line(line, mp) < 0) + goto err; + + return; + } else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) { + log_warn(LD_GENERAL, "Could not launch managed proxy executable!"); + goto err; + } + + log_warn(LD_CONFIG, "Unknown line received by managed proxy. (%s)", line); + + err: + mp->conf_state = PT_PROTO_BROKEN; + return; +} + +/** Parses an ENV-ERROR <b>line</b> and warns the user accordingly. */ +void +parse_env_error(const char *line) +{ + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(PROTO_ENV_ERROR) + 2)) + log_notice(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", PROTO_ENV_ERROR); + + log_warn(LD_CONFIG, "Managed proxy couldn't understand the " + "pluggable transport environment variables. (%s)", + line+strlen(PROTO_ENV_ERROR)+1); +} + +/** Handles a VERSION <b>line</b>. Updates the configuration protocol + * version in <b>mp</b>. */ +int +parse_version(const char *line, managed_proxy_t *mp) +{ + if (strlen(line) < (strlen(PROTO_NEG_SUCCESS) + 2)) { + log_warn(LD_CONFIG, "Managed proxy sent us malformed %s line.", + PROTO_NEG_SUCCESS); + return -1; + } + + if (strcmp("1", line+strlen(PROTO_NEG_SUCCESS)+1)) { /* hardcoded temp */ + log_warn(LD_CONFIG, "Managed proxy tried to negotiate on version '%s'. " + "We only support version '1'", line+strlen(PROTO_NEG_SUCCESS)+1); + return -1; + } + + mp->conf_protocol = PROTO_VERSION_ONE; /* temp. till more versions appear */ + return 0; +} + +/** Parses {C,S}METHOD-ERROR <b>line</b> and warns the user + * accordingly. If <b>is_server</b> it is an SMETHOD-ERROR, + * otherwise it is a CMETHOD-ERROR. */ +static void +parse_method_error(const char *line, int is_server) +{ + const char* error = is_server ? + PROTO_SMETHOD_ERROR : PROTO_CMETHOD_ERROR; + + /* (Length of the protocol string) plus (a space) and (the first char of + the error message) */ + if (strlen(line) < (strlen(error) + 2)) + log_warn(LD_CONFIG, "Managed proxy sent us an %s without an error " + "message.", error); + + log_warn(LD_CONFIG, "%s managed proxy encountered a method error. (%s)", + is_server ? "Server" : "Client", + line+strlen(error)+1); +} + +/** Parses an SMETHOD <b>line</b> and if well-formed it registers the + * new transport in <b>mp</b>. */ +int +parse_smethod_line(const char *line, managed_proxy_t *mp) +{ + int r; + smartlist_t *items = NULL; + + char *method_name=NULL; + + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + + transport_t *transport=NULL; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 3) { + log_warn(LD_CONFIG, "Server managed proxy sent us a SMETHOD line " + "with too few arguments."); + goto err; + } + + tor_assert(!strcmp(smartlist_get(items,0),PROTO_SMETHOD)); + + method_name = smartlist_get(items,1); + if (!string_is_C_identifier(method_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + method_name); + goto err; + } + + addrport = smartlist_get(items, 2); + if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + transport = transport_create(&addr, port, method_name, PROXY_NONE); + if (!transport) + goto err; + + smartlist_add(mp->transports, transport); + + /* For now, notify the user so that he knows where the server + transport is listening. */ + log_info(LD_CONFIG, "Server transport %s at %s:%d.", + method_name, fmt_addr(&addr), (int)port); + + r=0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + return r; +} + +/** Parses a CMETHOD <b>line</b>, and if well-formed it registers + * the new transport in <b>mp</b>. */ +int +parse_cmethod_line(const char *line, managed_proxy_t *mp) +{ + int r; + smartlist_t *items = NULL; + + char *method_name=NULL; + + char *socks_ver_str=NULL; + int socks_ver=PROXY_NONE; + + char *addrport=NULL; + tor_addr_t addr; + uint16_t port = 0; + + transport_t *transport=NULL; + + items = smartlist_create(); + smartlist_split_string(items, line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 4) { + log_warn(LD_CONFIG, "Client managed proxy sent us a CMETHOD line " + "with too few arguments."); + goto err; + } + + tor_assert(!strcmp(smartlist_get(items,0),PROTO_CMETHOD)); + + method_name = smartlist_get(items,1); + if (!string_is_C_identifier(method_name)) { + log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).", + method_name); + goto err; + } + + socks_ver_str = smartlist_get(items,2); + + if (!strcmp(socks_ver_str,"socks4")) { + socks_ver = PROXY_SOCKS4; + } else if (!strcmp(socks_ver_str,"socks5")) { + socks_ver = PROXY_SOCKS5; + } else { + log_warn(LD_CONFIG, "Client managed proxy sent us a proxy protocol " + "we don't recognize. (%s)", socks_ver_str); + goto err; + } + + addrport = smartlist_get(items, 3); + if (tor_addr_port_lookup(addrport, &addr, &port)<0) { + log_warn(LD_CONFIG, "Error parsing transport " + "address '%s'", addrport); + goto err; + } + + if (!port) { + log_warn(LD_CONFIG, + "Transport address '%s' has no port.", addrport); + goto err; + } + + transport = transport_create(&addr, port, method_name, socks_ver); + if (!transport) + goto err; + + smartlist_add(mp->transports, transport); + + log_info(LD_CONFIG, "Transport %s at %s:%d with SOCKS %d. " + "Attached to managed proxy.", + method_name, fmt_addr(&addr), (int)port, socks_ver); + + r=0; + goto done; + + err: + r = -1; + + done: + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + return r; +} + +/** Return a string containing the address:port that <b>transport</b> + * should use. It's the responsibility of the caller to free() the + * received string. */ +static char * +get_bindaddr_for_proxy(const managed_proxy_t *mp) +{ + char *bindaddr = NULL; + smartlist_t *string_tmp = smartlist_create(); + + tor_assert(mp->is_server); + + SMARTLIST_FOREACH_BEGIN(mp->transports_to_launch, char *, t) { + tor_asprintf(&bindaddr, "%s-%s", t, get_bindaddr_for_transport(t)); + smartlist_add(string_tmp, bindaddr); + } SMARTLIST_FOREACH_END(t); + + bindaddr = smartlist_join_strings(string_tmp, ",", 0, NULL); + + SMARTLIST_FOREACH(string_tmp, char *, t, tor_free(t)); + smartlist_free(string_tmp); + + return bindaddr; +} + +/** Prepare the <b>envp</b> of managed proxy <b>mp</b> */ +static void +set_managed_proxy_environment(char ***envp, const managed_proxy_t *mp) +{ + const or_options_t *options = get_options(); + char **tmp=NULL; + char *state_loc=NULL; + char *transports_to_launch=NULL; + char *bindaddr=NULL; + + int n_envs = mp->is_server ? ENVIRON_SIZE_SERVER : ENVIRON_SIZE_CLIENT; + + /* allocate enough space for our env. vars and a NULL pointer */ + *envp = tor_malloc(sizeof(char*)*(n_envs+1)); + tmp = *envp; + + state_loc = get_datadir_fname("pt_state/"); /* XXX temp */ + transports_to_launch = + smartlist_join_strings(mp->transports_to_launch, ",", 0, NULL); + + tor_asprintf(tmp++, "HOME=%s", getenv("HOME")); + tor_asprintf(tmp++, "PATH=%s", getenv("PATH")); + tor_asprintf(tmp++, "TOR_PT_STATE_LOCATION=%s", state_loc); + tor_asprintf(tmp++, "TOR_PT_MANAGED_TRANSPORT_VER=1"); /* temp */ + if (mp->is_server) { + bindaddr = get_bindaddr_for_proxy(mp); + + /* XXX temp */ + tor_asprintf(tmp++, "TOR_PT_ORPORT=127.0.0.1:%d", options->ORPort); + tor_asprintf(tmp++, "TOR_PT_SERVER_BINDADDR=%s", bindaddr); + tor_asprintf(tmp++, "TOR_PT_SERVER_TRANSPORTS=%s", transports_to_launch); + /* XXX temp*/ + tor_asprintf(tmp++, "TOR_PT_EXTENDED_SERVER_PORT=127.0.0.1:4200"); + } else { + tor_asprintf(tmp++, "TOR_PT_CLIENT_TRANSPORTS=%s", transports_to_launch); + } + *tmp = NULL; + + tor_free(state_loc); + tor_free(transports_to_launch); + tor_free(bindaddr); +} + +/** Create and return a new managed proxy for <b>transport</b> using + * <b>proxy_argv</b>. If <b>is_server</b> is true, it's a server + * managed proxy. */ +static managed_proxy_t * +managed_proxy_create(const smartlist_t *transport_list, + char **proxy_argv, int is_server) +{ + managed_proxy_t *mp = tor_malloc_zero(sizeof(managed_proxy_t)); + mp->conf_state = PT_PROTO_INFANT; + mp->is_server = is_server; + mp->argv = proxy_argv; + mp->transports = smartlist_create(); + + mp->transports_to_launch = smartlist_create(); + SMARTLIST_FOREACH(transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + + /* register the managed proxy */ + if (!managed_proxy_list) + managed_proxy_list = smartlist_create(); + smartlist_add(managed_proxy_list, mp); + unconfigured_proxies_n++; + + return mp; +} + +/** Register <b>transport</b> using proxy with <b>proxy_argv</b> to + * the managed proxy subsystem. + * If <b>is_server</b> is true, then the proxy is a server proxy. */ +void +pt_kickstart_proxy(const smartlist_t *transport_list, + char **proxy_argv, int is_server) +{ + managed_proxy_t *mp=NULL; + transport_t *old_transport = NULL; + + mp = get_managed_proxy_by_argv_and_type(proxy_argv, is_server); + + if (!mp) { /* we haven't seen this proxy before */ + managed_proxy_create(transport_list, proxy_argv, is_server); + + } else { /* known proxy. add its transport to its transport list */ + if (mp->got_hup) { + /* If the managed proxy we found is marked by a SIGHUP, it means + that it's not useless and should be kept. If it's marked for + removal, unmark it and increase the unconfigured proxies so + that we try to restart it if we need to. Afterwards, check if + a transport_t for 'transport' used to exist before the SIGHUP + and make sure it doesn't get deleted because we might reuse + it. */ + if (mp->marked_for_removal) { + mp->marked_for_removal = 0; + unconfigured_proxies_n++; + } + + SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport) { + old_transport = transport_get_by_name(transport); + if (old_transport) + old_transport->marked_for_removal = 0; + } SMARTLIST_FOREACH_END(transport); + } + + SMARTLIST_FOREACH(transport_list, const char *, transport, + add_transport_to_proxy(transport, mp)); + free_execve_args(proxy_argv); + } +} + +/** Frees the array of pointers in <b>arg</b> used as arguments to + execve(2). */ +static INLINE void +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(arg); +} + +/** Tor will read its config. + * Prepare the managed proxy list so that proxies not used in the new + * config will shutdown, and proxies that need to spawn different + * transports will do so. */ +void +pt_prepare_proxy_list_for_config_read(void) +{ + if (!managed_proxy_list) + return; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + /* Destroy unconfigured proxies. */ + if (mp->conf_state != PT_PROTO_COMPLETED) { + managed_proxy_destroy(mp); + unconfigured_proxies_n--; + continue; + } + + tor_assert(mp->conf_state == PT_PROTO_COMPLETED); + + mp->marked_for_removal = 1; + mp->got_hup = 1; + SMARTLIST_FOREACH(mp->transports_to_launch, char *, t, tor_free(t)); + smartlist_clear(mp->transports_to_launch); + } SMARTLIST_FOREACH_END(mp); + + tor_assert(unconfigured_proxies_n == 0); +} + +/** 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 + * config. */ +void +sweep_proxy_list(void) +{ + if (!managed_proxy_list) + return; + + SMARTLIST_FOREACH_BEGIN(managed_proxy_list, managed_proxy_t *, mp) { + if (mp->marked_for_removal) { + SMARTLIST_DEL_CURRENT(managed_proxy_list, mp); + managed_proxy_destroy(mp); + } + } SMARTLIST_FOREACH_END(mp); +} + +/** Release all storage held by the pluggable transports subsystem. */ +void +pt_free_all(void) +{ + 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 + free them. Otherwise, it hasn't registered its transports yet + and we should free them here. */ + SMARTLIST_FOREACH(managed_proxy_list, managed_proxy_t *, mp, + managed_proxy_destroy(mp)); + + smartlist_free(managed_proxy_list); + managed_proxy_list=NULL; + } +} + diff --git a/src/or/transports.h b/src/or/transports.h new file mode 100644 index 0000000000..4a93387596 --- /dev/null +++ b/src/or/transports.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file transports.h + * \brief Headers for transports.c + **/ + +#ifndef TOR_TRANSPORTS_H +#define TOR_TRANSPORTS_H + +void pt_kickstart_proxy(const smartlist_t *transport_list, char **proxy_argv, + int is_server); + +#define pt_kickstart_client_proxy(tl, pa) \ + pt_kickstart_proxy(tl, pa, 0) +#define pt_kickstart_server_proxy(tl, pa) \ + pt_kickstart_proxy(tl, pa, 1) + +void pt_configure_remaining_proxies(void); + +int pt_proxies_configuration_pending(void); + +void pt_free_all(void); + +void pt_prepare_proxy_list_for_config_read(void); +void sweep_proxy_list(void); + +#ifdef PT_PRIVATE +/** State of the managed proxy configuration protocol. */ +enum pt_proto_state { + PT_PROTO_INFANT, /* was just born */ + PT_PROTO_LAUNCHED, /* was just launched */ + PT_PROTO_ACCEPTING_METHODS, /* accepting methods */ + PT_PROTO_CONFIGURED, /* configured successfully */ + PT_PROTO_COMPLETED, /* configure and registered its transports */ + PT_PROTO_BROKEN +}; + +/** Structure containing information of a managed proxy. */ +typedef struct { + enum pt_proto_state conf_state; /* the current configuration state */ + char **argv; /* the cli arguments of this proxy */ + int conf_protocol; /* the configuration protocol version used */ + + int is_server; /* is it a server proxy? */ + + FILE *_stdout; /* a stream to its stdout + (closed in managed_proxy_destroy()) */ + + int pid; /* The Process ID this managed proxy is using. */ + + /** Boolean: We are re-parsing our config, and we are going to + * remove this managed proxy if we don't find it any transport + * plugins that use it. */ + unsigned int marked_for_removal : 1; + + /** Boolean: We got a SIGHUP while this proxy was running. We use + * this flag to signify that this proxy might need to be restarted + * so that it can listen for other transports according to the new + * torrc. */ + unsigned int got_hup : 1; + + /* transports to-be-launched by this proxy */ + 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 *). + */ + smartlist_t *transports; +} managed_proxy_t; + +int parse_cmethod_line(const char *line, managed_proxy_t *mp); +int parse_smethod_line(const char *line, managed_proxy_t *mp); + +int parse_version(const char *line, managed_proxy_t *mp); +void parse_env_error(const char *line); +void handle_proxy_line(const char *line, managed_proxy_t *mp); + +#endif + +#endif + |