r15689@catbus: nickm | 2007-10-11 16:40:25 -0400

Fix bug 451.  This was a nasty bug, so let's fix it twice: first, by banning recursive calls to connection_handle_write from connection_flushed_some; and second, by not calling connection_finished_flushing() on a closed connection.  Backport candidate.


svn:r11882
This commit is contained in:
Nick Mathewson 2007-10-11 20:45:26 +00:00
parent 375acaab26
commit b62d379f92
3 changed files with 55 additions and 10 deletions

View File

@ -35,6 +35,18 @@ Changes in version 0.2.0.8-alpha - 2007-10-12
string which usually wasn't there, once for every routerinfo string which usually wasn't there, once for every routerinfo
we read, just scan lines forward until we find one we like. we read, just scan lines forward until we find one we like.
Bugfix on 0.2.0.1. Bugfix on 0.2.0.1.
- When we add data to a write buffer in response to the data on that
write buffer getting low because of a flush, do not consider the newly
added data as a candidate for immediate flushing, but rather make it
wait until the next round of writing. Otherwise, we flush and refill
recursively, and a single greedy TLS connection can eat all of our
bandwidth. Bugfix on 0.1.2.7-alpha.
o Major bugfixes (crashes):
- If a connection is shut down abruptly because of something that
happened inside connection_flushed_some(), do not call
connection_finished_flushing(). Should fix bug 451. Bugfix on
0.1.2.7-alpha.
o Minor features (v3 authority system): o Minor features (v3 authority system):
- Add more ways for tools to download the votes that lead to the - Add more ways for tools to download the votes that lead to the

View File

@ -529,6 +529,11 @@ connection_about_to_close_connection(connection_t *conn)
} }
} }
/** Return true iff connection_close_immediate has been called on this
* connection */
#define CONN_IS_CLOSED(c) \
((c)->linked ? ((c)->linked_conn_is_closed) : ((c)->s < 0))
/** Close the underlying socket for <b>conn</b>, so we don't try to /** Close the underlying socket for <b>conn</b>, so we don't try to
* flush it. Must be used in conjunction with (right before) * flush it. Must be used in conjunction with (right before)
* connection_mark_for_close(). * connection_mark_for_close().
@ -537,7 +542,7 @@ void
connection_close_immediate(connection_t *conn) connection_close_immediate(connection_t *conn)
{ {
assert_connection_ok(conn,0); assert_connection_ok(conn,0);
if (conn->s < 0 && !conn->linked) { if (CONN_IS_CLOSED(conn)) {
log_err(LD_BUG,"Attempt to close already-closed connection."); log_err(LD_BUG,"Attempt to close already-closed connection.");
tor_fragile_assert(); tor_fragile_assert();
return; return;
@ -554,6 +559,8 @@ connection_close_immediate(connection_t *conn)
if (conn->s >= 0) if (conn->s >= 0)
tor_close_socket(conn->s); tor_close_socket(conn->s);
conn->s = -1; conn->s = -1;
if (conn->linked)
conn->linked_conn_is_closed = 1;
if (!connection_is_listener(conn)) { if (!connection_is_listener(conn)) {
buf_clear(conn->outbuf); buf_clear(conn->outbuf);
conn->outbuf_flushlen = 0; conn->outbuf_flushlen = 0;
@ -2046,6 +2053,11 @@ connection_handle_write(connection_t *conn, int force)
if (conn->marked_for_close || conn->s < 0) if (conn->marked_for_close || conn->s < 0)
return 0; /* do nothing */ return 0; /* do nothing */
if (conn->in_flushed_some) {
log_warn(LD_BUG, "called recursively from inside conn->in_flushed_some()");
return 0;
}
conn->timestamp_lastwritten = now; conn->timestamp_lastwritten = now;
/* Sometimes, "writable" means "connected". */ /* Sometimes, "writable" means "connected". */
@ -2207,6 +2219,7 @@ void
_connection_write_to_buf_impl(const char *string, size_t len, _connection_write_to_buf_impl(const char *string, size_t len,
connection_t *conn, int zlib) connection_t *conn, int zlib)
{ {
/* XXXX This function really needs to return -1 on failure. */
int r; int r;
size_t old_datalen; size_t old_datalen;
if (!len) if (!len)
@ -2248,9 +2261,18 @@ _connection_write_to_buf_impl(const char *string, size_t len,
int extra = 0; int extra = 0;
conn->outbuf_flushlen += len; conn->outbuf_flushlen += len;
/* Should we try flushing the outbuf now? */
if (conn->in_flushed_some) {
/* Don't flush the outbuf when the reason we're writing more stuff is
* _because_ we flushed the outbuf. That's unfair. */
return;
}
if (conn->type == CONN_TYPE_OR && if (conn->type == CONN_TYPE_OR &&
conn->outbuf_flushlen-len < MIN_TLS_FLUSHLEN && conn->outbuf_flushlen-len < MIN_TLS_FLUSHLEN &&
conn->outbuf_flushlen >= MIN_TLS_FLUSHLEN) { conn->outbuf_flushlen >= MIN_TLS_FLUSHLEN) {
/* We just pushed outbuf_flushelen to MIN_TLS_FLUSHLEN or above;
* we can send out a full TLS frame now if we like. */
extra = conn->outbuf_flushlen - MIN_TLS_FLUSHLEN; extra = conn->outbuf_flushlen - MIN_TLS_FLUSHLEN;
conn->outbuf_flushlen = MIN_TLS_FLUSHLEN; conn->outbuf_flushlen = MIN_TLS_FLUSHLEN;
} else if (conn->type == CONN_TYPE_CONTROL && } else if (conn->type == CONN_TYPE_CONTROL &&
@ -2625,13 +2647,17 @@ connection_process_inbuf(connection_t *conn, int package_partial)
static int static int
connection_flushed_some(connection_t *conn) connection_flushed_some(connection_t *conn)
{ {
int r = 0;
tor_assert(!conn->in_flushed_some);
conn->in_flushed_some = 1;
if (conn->type == CONN_TYPE_DIR && if (conn->type == CONN_TYPE_DIR &&
conn->state == DIR_CONN_STATE_SERVER_WRITING) conn->state == DIR_CONN_STATE_SERVER_WRITING) {
return connection_dirserv_flushed_some(TO_DIR_CONN(conn)); r = connection_dirserv_flushed_some(TO_DIR_CONN(conn));
else if (conn->type == CONN_TYPE_OR) } else if (conn->type == CONN_TYPE_OR) {
return connection_or_flushed_some(TO_OR_CONN(conn)); r = connection_or_flushed_some(TO_OR_CONN(conn));
else }
return 0; conn->in_flushed_some = 0;
return r;
} }
/** We just finished flushing bytes from conn-\>outbuf, and there /** We just finished flushing bytes from conn-\>outbuf, and there
@ -2645,6 +2671,10 @@ connection_finished_flushing(connection_t *conn)
{ {
tor_assert(conn); tor_assert(conn);
/* If the connection is don't try to do anything more here. */
if (CONN_IS_CLOSED(conn))
return 0;
// log_fn(LOG_DEBUG,"entered. Socket %u.", conn->s); // log_fn(LOG_DEBUG,"entered. Socket %u.", conn->s);
switch (conn->type) { switch (conn->type) {

View File

@ -744,9 +744,6 @@ typedef struct connection_t {
/* The next fields are all one-bit booleans. Some are only applicable to /* The next fields are all one-bit booleans. Some are only applicable to
* connection subtypes, but we hold them here anyway, to save space. * connection subtypes, but we hold them here anyway, to save space.
* (Currently, they all fit into a single byte. If they ever need more than
* one byte, we can shave some bits off type, state, and purpose above, none
* of which is ever over 31.)
*/ */
unsigned read_blocked_on_bw:1; /**< Boolean: should we start reading again unsigned read_blocked_on_bw:1; /**< Boolean: should we start reading again
* once the bandwidth throttler allows it? */ * once the bandwidth throttler allows it? */
@ -769,6 +766,9 @@ typedef struct connection_t {
/** For AP connections only. If 1, and we fail to reach the chosen exit, /** For AP connections only. If 1, and we fail to reach the chosen exit,
* stop requiring it. */ * stop requiring it. */
unsigned int chosen_exit_optional:1; unsigned int chosen_exit_optional:1;
/** Set to 1 when we're inside connection_flushed_some to keep us from
* calling connection_handle_write() recursively. */
unsigned int in_flushed_some:1;
/* For linked connections: /* For linked connections:
*/ */
@ -781,6 +781,9 @@ typedef struct connection_t {
/** True iff we're currently able to read on the linked conn, and our /** True iff we're currently able to read on the linked conn, and our
* read_event should be made active with libevent. */ * read_event should be made active with libevent. */
unsigned int active_on_link:1; unsigned int active_on_link:1;
/** True iff we've called connection_close_immediate on this linked
* connection */
unsigned int linked_conn_is_closed:1;
int s; /**< Our socket; -1 if this connection is closed, or has no int s; /**< Our socket; -1 if this connection is closed, or has no
* socket. */ * socket. */