libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit dc6f6a872e5f3c18a230f44dfc6d31ab50a397a7
parent 42c501a9c40dca7df9752fa4d4ac4a7a1ce5e4a9
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 22 Oct 2012 11:07:36 +0000

Trying to fix issue reported by Matthieu:

>>
I face an issue while handling big https posts with libmicrohttpd 0.9.22.
I'm using an "external" select based on MHD_get_fdset and MHD_run.

Let's assume 16K of encrypted data arrive on the socket.
When the data arrives, my select correctly returns and MHD_run is called.

Do_read will first call gnu_tls to decrypt the data.
Apparently gnu_tls reads the full 16K so starting here, from the point of
view of socket/select, there is no more data to read.

Do_read however presents a buffer of only 9K to gnu_tls to retrieve the data
(9K as the result of a try_grow_read_buffer call).
So gnu_tls returns 9K of clear data and keeps the rest 7K in its own buffer
I think.
Do_read handles the 9K and then MHD_run returns;

Since from the socket point of view no data is pending read, select will
then wait until it finally timeout.
Only after this timeout, MHD_run is run again and the remaining 7K are
correctly processed.

I found two dirty workarounds for this issue :
1/ increase the MDH_BUF_INC_SIZE to an arbitrarily large value so it will
always be greater than gnu_tls own buffers
2/ run multiple MHD_run after each select (let's say 10 times)

What do you think of this issue ?
What could be a cleaner way of handling this ?
<<
Approach: detect that we *might* be in this situation because TLS fills
the entire buffer we give to gnuTLS.  If so, set timeout to 0 and read
again.


Diffstat:
MChangeLog | 5+++++
Msrc/daemon/daemon.c | 39+++++++++++++++++++++++++++++++++++----
Msrc/daemon/internal.h | 6++++++
3 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,8 @@ +Mon Oct 22 13:05:01 CEST 2012 + Immediately do a second read if we get a full buffer from + TLS as there might be more data in the TLS buffers even if + there is no activity on the socket. -CG + Tue Oct 16 01:33:55 CEST 2012 Consistently use "#ifdef" and "#ifndef" WINDOWS, and not sometimes "#if". -CG diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c @@ -386,6 +386,7 @@ recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i) { int res; + connection->tls_read_ready = MHD_NO; res = gnutls_record_recv (connection->tls_session, other, i); if ( (GNUTLS_E_AGAIN == res) || (GNUTLS_E_INTERRUPTED == res) ) @@ -401,6 +402,8 @@ recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i) errno = EPIPE; return res; } + if (res == i) + connection->tls_read_ready = MHD_YES; return res; } @@ -606,7 +609,15 @@ MHD_handle_connection (void *data) tv.tv_usec = 0; tvp = &tv; } - +#if HTTPS_SUPPORT + if (MHD_YES == con->tls_read_ready) + { + /* do not block (more data may be inside of TLS buffers waiting for us) */ + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + } +#endif if (0 == (con->daemon->options & MHD_USE_POLL)) { /* use select */ @@ -629,7 +640,11 @@ MHD_handle_connection (void *data) break; } /* call appropriate connection handler if necessary */ - if (FD_ISSET (con->socket_fd, &rs)) + if ( (FD_ISSET (con->socket_fd, &rs)) +#if HTTPS_SUPPORT + || (MHD_YES == con->tls_read_ready) +#endif + ) con->read_handler (con); if (FD_ISSET (con->socket_fd, &ws)) con->write_handler (con); @@ -660,7 +675,11 @@ MHD_handle_connection (void *data) #endif break; } - if (0 != (p[0].revents & POLLIN)) + if ( (0 != (p[0].revents & POLLIN)) +#if HTTPS_SUPPORT + || (MHD_YES == con->tls_read_ready) +#endif + ) con->read_handler (con); if (0 != (p[0].revents & POLLOUT)) con->write_handler (con); @@ -1271,6 +1290,14 @@ MHD_get_timeout (struct MHD_Daemon *daemon, have_timeout = MHD_NO; for (pos = daemon->connections_head; NULL != pos; pos = pos->next) { +#if HTTPS_SUPPORT + if (MHD_YES == pos->tls_read_ready) + { + earliest_deadline = 0; + have_timeout = MHD_YES; + break; + } +#endif if (0 != pos->connection_timeout) { if ( (! have_timeout) || @@ -1397,7 +1424,11 @@ MHD_select (struct MHD_Daemon *daemon, ds = pos->socket_fd; if (ds != -1) { - if (FD_ISSET (ds, &rs)) + if ( (FD_ISSET (ds, &rs)) +#if HTTPS_SUPPORT + || (MHD_YES == pos->tls_read_ready) +#endif + ) pos->read_handler (pos); if (FD_ISSET (ds, &ws)) pos->write_handler (pos); diff --git a/src/daemon/internal.h b/src/daemon/internal.h @@ -750,6 +750,12 @@ struct MHD_Connection */ int cipher; + /** + * Could it be that we are ready to read due to TLS buffers + * even though the socket is not? + */ + int tls_read_ready; + #endif };