summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-08-28 23:09:44 +0000
committerChristian Grothoff <christian@grothoff.org>2016-08-28 23:09:44 +0000
commitb6ac5b9f8e1a668a1a9026cfe7004dbdab4264da (patch)
tree316e16aabd97175dac09e06f38e05ec7990800a8
parentaf90b3cd5169fbc49d376f4b96df1f231a577fdc (diff)
-theoretically finishing epoll() + HTTPS-based connection upgrade logic
-rw-r--r--doc/libmicrohttpd.texi13
-rw-r--r--src/include/microhttpd.h19
-rw-r--r--src/microhttpd/connection.c19
-rw-r--r--src/microhttpd/daemon.c392
-rw-r--r--src/microhttpd/internal.h72
-rw-r--r--src/microhttpd/response.c144
6 files changed, 527 insertions, 132 deletions
diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi
index deee825e..2adfd51d 100644
--- a/doc/libmicrohttpd.texi
+++ b/doc/libmicrohttpd.texi
@@ -587,6 +587,19 @@ Enable TCP_FASTOPEN on the listen socket. TCP_FASTOPEN is currently
supported on Linux >= 3.6. On other systems using this option with
cause @code{MHD_start_daemon} to fail.
+@item MHD_USE_TLS_EPOLL_UPGRADE
+@cindex epoll
+@cindex upgrade
+@cindex https
+@cindex tls
+This option must be set if you want to use @code{epoll()} in
+combination with a server offering TLS and then upgrade connections
+(via ``101 Switching Protocols'' responses). This requires MHD to
+open up and process an extra socket, and hence we require this
+special flag in case this is really needed. Note that using
+this option automatically implies @code{MHD_USE_EPOLL},
+@code{MHD_USE_TLS} and @code{MHD_USE_SUSPEND_RESUME}.
+
@end table
@end deftp
diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
index 46a82625..7347447c 100644
--- a/src/include/microhttpd.h
+++ b/src/include/microhttpd.h
@@ -538,11 +538,16 @@ enum MHD_FLAG
MHD_USE_DEBUG = 1,
/**
- * Run in HTTPS mode.
+ * Run in HTTPS mode. Same as #MHD_USE_TLS, just using the historic name.
*/
MHD_USE_SSL = 2,
/**
+ * Run in HTTPS mode. The modern protocol is called TLS.
+ */
+ MHD_USE_TLS = 2,
+
+ /**
* Run using one thread per connection.
*/
MHD_USE_THREAD_PER_CONNECTION = 4,
@@ -672,7 +677,17 @@ enum MHD_FLAG
* kernel >= 3.6. On other systems, using this option cases #MHD_start_daemon
* to fail.
*/
- MHD_USE_TCP_FASTOPEN = 16384
+ MHD_USE_TCP_FASTOPEN = 16384,
+
+ /**
+ * You need to set this option if you want to use epoll() in
+ * combination with HTTPS connections and switching protocols via
+ * connection upgrades (via #MHD_create_response_for_upgrade()).
+ * This flag is required as under these circumstances we need to
+ * open up an extra file descriptor, which we do not want to do
+ * unless necessary.
+ */
+ MHD_USE_TLS_EPOLL_UPGRADE = 32768 | MHD_USE_SUSPEND_RESUME | MHD_USE_EPOLL | MHD_USE_TLS
};
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index 54556167..e3880e8a 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -3158,6 +3158,8 @@ MHD_queue_response (struct MHD_Connection *connection,
unsigned int status_code,
struct MHD_Response *response)
{
+ struct MHD_Daemon *daemon = connection->daemon;
+
if ( (NULL == connection) ||
(NULL == response) ||
(NULL != connection->response) ||
@@ -3168,20 +3170,31 @@ MHD_queue_response (struct MHD_Connection *connection,
(NULL != response->upgrade_handler) )
{
#ifdef HAVE_MESSAGES
- MHD_DLOG (connection->daemon,
+ MHD_DLOG (daemon,
"Application used invalid status code for 'upgrade' response!\n");
#endif
return MHD_NO;
}
if ( (NULL != response->upgrade_handler) &&
- (0 == (connection->daemon->options & MHD_USE_SUSPEND_RESUME)) )
+ (0 == (daemon->options & MHD_USE_SUSPEND_RESUME)) )
{
#ifdef HAVE_MESSAGES
- MHD_DLOG (connection->daemon,
+ MHD_DLOG (daemon,
"Application attempted 'upgrade' without setting MHD_USE_SUSPEND_RESUME!\n");
#endif
return MHD_NO;
}
+ if ( (NULL != response->upgrade_handler) &&
+ (0 != (MHD_USE_EPOLL & daemon->options)) &&
+ (0 != (MHD_USE_TLS & daemon->options)) &&
+ (MHD_USE_TLS_EPOLL_UPGRADE != (MHD_USE_TLS_EPOLL_UPGRADE & daemon->options)) )
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Application attempted 'upgrade' HTTPS connection in epoll mode without setting MHD_USE_HTTPS_EPOLL_UPGRADE!\n");
+#endif
+ return MHD_NO;
+ }
MHD_increment_response_rc (response);
connection->response = response;
connection->responseCode = status_code;
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
index a25fe358..2fb87ed4 100644
--- a/src/microhttpd/daemon.c
+++ b/src/microhttpd/daemon.c
@@ -626,11 +626,15 @@ MHD_get_fdset (struct MHD_Daemon *daemon,
fd_set *except_fd_set,
MHD_socket *max_fd)
{
- return MHD_get_fdset2(daemon, read_fd_set,
- write_fd_set, except_fd_set,
- max_fd, _MHD_SYS_DEFAULT_FD_SETSIZE);
+ return MHD_get_fdset2 (daemon,
+ read_fd_set,
+ write_fd_set,
+ except_fd_set,
+ max_fd,
+ _MHD_SYS_DEFAULT_FD_SETSIZE);
}
+
/**
* Obtain the `select()` sets for this daemon.
* Daemon's FDs will be added to fd_sets. To get only
@@ -729,25 +733,25 @@ MHD_get_fdset2 (struct MHD_Daemon *daemon,
}
for (urh = daemon->urh_head; NULL != urh; urh = urh->next)
{
- if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->celi_mhd)) &&
- (! MHD_add_to_fd_set_ (urh->mhd_socket,
+ if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
+ (! MHD_add_to_fd_set_ (urh->mhd.socket,
read_fd_set,
max_fd,
fd_setsize)) )
result = MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->celi_mhd)) &&
- (! MHD_add_to_fd_set_ (urh->mhd_socket,
+ if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
+ (! MHD_add_to_fd_set_ (urh->mhd.socket,
write_fd_set,
max_fd,
fd_setsize)) )
result = MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->celi_client)) &&
+ if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) &&
(! MHD_add_to_fd_set_ (urh->connection->socket_fd,
read_fd_set,
max_fd,
fd_setsize)) )
result = MHD_NO;
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->celi_client)) &&
+ if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
(! MHD_add_to_fd_set_ (urh->connection->socket_fd,
write_fd_set,
max_fd,
@@ -2175,7 +2179,7 @@ static void
process_urh (struct MHD_UpgradeResponseHandle *urh)
{
/* handle reading from TLS client and writing to application */
- if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->celi_client)) &&
+ if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) &&
(urh->in_buffer_off < urh->in_buffer_size) )
{
ssize_t res;
@@ -2186,25 +2190,25 @@ process_urh (struct MHD_UpgradeResponseHandle *urh)
if ( (GNUTLS_E_AGAIN == res) ||
(GNUTLS_E_INTERRUPTED == res) )
{
- urh->celi_client &= ~MHD_EPOLL_STATE_READ_READY;
+ urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
else if (res > 0)
{
urh->in_buffer_off += res;
}
}
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->celi_mhd)) &&
+ if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
(urh->in_buffer_off > 0) )
{
size_t res;
- res = write (urh->mhd_socket,
+ res = write (urh->mhd.socket,
urh->in_buffer,
urh->in_buffer_off);
if (-1 == res)
{
/* FIXME: differenciate by errno? */
- urh->celi_mhd &= ~MHD_EPOLL_STATE_WRITE_READY;
+ urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
}
else
{
@@ -2223,25 +2227,25 @@ process_urh (struct MHD_UpgradeResponseHandle *urh)
}
/* handle reading from application and writing to HTTPS client */
- if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->celi_mhd)) &&
+ if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
(urh->out_buffer_off < urh->out_buffer_size) )
{
size_t res;
- res = read (urh->mhd_socket,
+ res = read (urh->mhd.socket,
&urh->out_buffer[urh->out_buffer_off],
urh->out_buffer_size - urh->out_buffer_off);
if (-1 == res)
{
/* FIXME: differenciate by errno? */
- urh->celi_mhd &= ~MHD_EPOLL_STATE_READ_READY;
+ urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
else
{
urh->out_buffer_off += res;
}
}
- if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->celi_client)) &&
+ if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
(urh->out_buffer_off > 0) )
{
ssize_t res;
@@ -2252,7 +2256,7 @@ process_urh (struct MHD_UpgradeResponseHandle *urh)
if ( (GNUTLS_E_AGAIN == res) ||
(GNUTLS_E_INTERRUPTED == res) )
{
- urh->celi_client &= ~MHD_EPOLL_STATE_WRITE_READY;
+ urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
}
else if (res > 0)
{
@@ -2359,13 +2363,13 @@ MHD_run_from_select (struct MHD_Daemon *daemon,
{
/* update urh state based on select() output */
if (FD_ISSET (urh->connection->socket_fd, read_fd_set))
- urh->celi_client |= MHD_EPOLL_STATE_READ_READY;
+ urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
if (FD_ISSET (urh->connection->socket_fd, write_fd_set))
- urh->celi_client |= MHD_EPOLL_STATE_WRITE_READY;
- if (FD_ISSET (urh->mhd_socket, read_fd_set))
- urh->celi_mhd |= MHD_EPOLL_STATE_READ_READY;
- if (FD_ISSET (urh->mhd_socket, write_fd_set))
- urh->celi_mhd |= MHD_EPOLL_STATE_WRITE_READY;
+ urh->app.celi |= MHD_EPOLL_STATE_WRITE_READY;
+ if (FD_ISSET (urh->mhd.socket, read_fd_set))
+ urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
+ if (FD_ISSET (urh->mhd.socket, write_fd_set))
+ urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY;
/* call generic forwarding function for passing data */
process_urh (urh);
}
@@ -2632,15 +2636,15 @@ MHD_poll_all (struct MHD_Daemon *daemon,
for (urh = daemon->urh_head; NULL != urh; urh = urh->next)
{
p[poll_server+i].fd = urh->connection->socket_fd;
- if (0 == (MHD_EPOLL_STATE_READ_READY & urh->celi_client))
+ if (0 == (MHD_EPOLL_STATE_READ_READY & urh->app.celi))
p[poll_server+i].events |= POLLIN;
- if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->celi_client))
+ if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi))
p[poll_server+i].events |= POLLOUT;
i++;
- p[poll_server+i].fd = urh->mhd_socket;
- if (0 == (MHD_EPOLL_STATE_READ_READY & urh->celi_mhd))
+ p[poll_server+i].fd = urh->mhd.socket;
+ if (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi))
p[poll_server+i].events |= POLLIN;
- if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->celi_mhd))
+ if (0 == (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi))
p[poll_server+i].events |= POLLOUT;
i++;
}
@@ -2701,11 +2705,11 @@ MHD_poll_all (struct MHD_Daemon *daemon,
if (p[poll_server+i].fd != urh->connection->socket_fd)
continue; /* fd mismatch, something else happened, retry later ... */
if (0 != (p[poll_server+i].revents & POLLIN))
- urh->celi_client |= MHD_EPOLL_STATE_READ_READY;
+ urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
if (0 != (p[poll_server+i].revents & POLLOUT))
- urh->celi_client |= MHD_EPOLL_STATE_WRITE_READY;
+ urh->app.celi |= MHD_EPOLL_STATE_WRITE_READY;
i++;
- if (p[poll_server+i].fd != urh->mhd_socket)
+ if (p[poll_server+i].fd != urh->mhd.socket)
{
/* fd mismatch, something else happened, retry later ... */
/* may still be able to do something based on updates
@@ -2714,9 +2718,9 @@ MHD_poll_all (struct MHD_Daemon *daemon,
continue;
}
if (0 != (p[poll_server+i].revents & POLLIN))
- urh->celi_mhd |= MHD_EPOLL_STATE_READ_READY;
+ urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
if (0 != (p[poll_server+i].revents & POLLOUT))
- urh->celi_mhd |= MHD_EPOLL_STATE_WRITE_READY;
+ urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY;
i++;
process_urh (urh);
}
@@ -2832,6 +2836,136 @@ MHD_poll (struct MHD_Daemon *daemon,
#define MAX_EVENTS 128
+#if HTTPS_SUPPORT
+
+/**
+ * Do epoll()-based processing for TLS connections that have been
+ * upgraded. This requires a separate epoll() invocation as we
+ * cannot use the `struct MHD_Connection` data structures for
+ * the `union epoll_data` in this case.
+ */
+static int
+run_epoll_for_upgrade (struct MHD_Daemon *daemon)
+{
+ struct epoll_event events[MAX_EVENTS];
+ int num_events;
+ unsigned int i;
+
+ num_events = MAX_EVENTS;
+ while (MAX_EVENTS == num_events)
+ {
+ /* update event masks */
+ num_events = epoll_wait (daemon->epoll_upgrade_fd,
+ events,
+ MAX_EVENTS,
+ 0);
+ if (-1 == num_events)
+ {
+ const int err = MHD_socket_get_error_ ();
+ if (MHD_SCKT_ERR_IS_EINTR_ (err))
+ return MHD_YES;
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_wait failed: %s\n",
+ MHD_socket_strerr_ (err));
+#endif
+ return MHD_NO;
+ }
+ for (i=0;i<(unsigned int) num_events;i++)
+ {
+ struct UpgradeEpollHandle *ueh = events[i].data.ptr;
+ struct MHD_UpgradeResponseHandle *urh = ueh->urh;
+ struct epoll_event event;
+ int fd;
+
+ /* Update our state based on what is ready according to epoll() */
+ if (0 != (events[i].events & EPOLLIN))
+ ueh->celi |= MHD_EPOLL_STATE_READ_READY;
+ if (0 != (events[i].events & EPOLLOUT))
+ ueh->celi |= MHD_EPOLL_STATE_WRITE_READY;
+
+ /* shuffle data based on buffers and FD readyness */
+ process_urh (urh);
+
+ /* if we drained the IO buffer, re-add to epoll() to wait for more! */
+ if (0 == (ueh->celi & MHD_EPOLL_STATE_READ_READY))
+ {
+ event.events = EPOLLIN;
+ event.data.ptr = ueh;
+ fd = (ueh == &urh->mhd) ? ueh->socket : urh->connection->socket_fd;
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_ADD,
+ fd,
+ &event))
+ {
+ MHD_socket myfd;
+
+ /* Handle error by closing OUR socket; with some
+ luck, this should tricker the application to fail
+ to read, and then the application should close
+ the connection completely. */
+
+ /* epoll documentation suggests that closing a FD
+ automatically removes it from the epoll set;
+ however, this is not true as if we fail to do
+ manually remove it, we are still seeing an event
+ for this fd in epoll, causing grief
+ (use-after-free...) --- at least on my system. */
+ myfd = urh->mhd.socket;
+ if ( (fd != myfd) &&
+ (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_DEL,
+ urh->mhd.socket,
+ NULL)) )
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+ urh->mhd.socket = MHD_INVALID_SOCKET;
+ MHD_socket_close_ (myfd);
+ continue;
+ }
+ }
+ if (0 == (ueh->celi & MHD_EPOLL_STATE_WRITE_READY))
+ {
+ event.events = EPOLLOUT;
+ event.data.ptr = ueh;
+ fd = (ueh == &ueh->urh->mhd) ? ueh->socket : ueh->urh->connection->socket_fd;
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_ADD,
+ fd,
+ &event))
+ {
+ MHD_socket myfd;
+
+ /* Handle error by closing OUR socket; with some
+ luck, this should tricker the application to fail
+ to read, and then the application should close
+ the connection completely. */
+
+ /* epoll documentation suggests that closing a FD
+ automatically removes it from the epoll set;
+ however, this is not true as if we fail to do
+ manually remove it, we are still seeing an event
+ for this fd in epoll, causing grief
+ (use-after-free...) --- at least on my system. */
+ myfd = urh->mhd.socket;
+ if ( (fd != myfd) &&
+ (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_DEL,
+ urh->mhd.socket,
+ NULL)) )
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+
+ urh->mhd.socket = MHD_INVALID_SOCKET;
+ MHD_socket_close_ (myfd);
+ continue;
+ }
+ }
+ }
+ }
+ return MHD_YES;
+}
+#endif
+
+
/**
* Do epoll()-based processing (this function is allowed to
* block if @a may_block is set to #MHD_YES).
@@ -2844,6 +2978,9 @@ static int
MHD_epoll (struct MHD_Daemon *daemon,
int may_block)
{
+#if HTTPS_SUPPORT
+ static const char *upgrade_marker = "upgrade_ptr";
+#endif
struct MHD_Connection *pos;
struct MHD_Connection *next;
struct epoll_event events[MAX_EVENTS];
@@ -2879,6 +3016,27 @@ MHD_epoll (struct MHD_Daemon *daemon,
}
daemon->listen_socket_in_epoll = MHD_YES;
}
+#if HTTPS_SUPPORT
+ if ( (MHD_NO == daemon->upgrade_fd_in_epoll) &&
+ (MHD_INVALID_SOCKET != daemon->epoll_upgrade_fd) )
+ {
+ event.events = EPOLLIN | EPOLLOUT;
+ event.data.ptr = (void *) upgrade_marker;
+ if (0 != epoll_ctl (daemon->epoll_fd,
+ EPOLL_CTL_ADD,
+ daemon->epoll_upgrade_fd,
+ &event))
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ return MHD_NO;
+ }
+ daemon->upgrade_fd_in_epoll = MHD_YES;
+ }
+#endif
if ( ( (MHD_YES == daemon->listen_socket_in_epoll) &&
(daemon->connections == daemon->connection_limit) ) ||
(MHD_YES == daemon->at_limit) )
@@ -2917,7 +3075,9 @@ MHD_epoll (struct MHD_Daemon *daemon,
{
/* update event masks */
num_events = epoll_wait (daemon->epoll_fd,
- events, MAX_EVENTS, timeout_ms);
+ events,
+ MAX_EVENTS,
+ timeout_ms);
if (-1 == num_events)
{
const int err = MHD_socket_get_error_ ();
@@ -2932,8 +3092,25 @@ MHD_epoll (struct MHD_Daemon *daemon,
}
for (i=0;i<(unsigned int) num_events;i++)
{
+ /* First, check for the values of `ptr` that would indicate
+ that this event is not about a normal connection. */
if (NULL == events[i].data.ptr)
continue; /* shutdown signal! */
+#if HTTPS_SUPPORT
+ if (upgrade_marker == events[i].data.ptr)
+ {
+ /* activity on an upgraded connection, we process
+ those in a separate epoll() */
+ daemon->upgrade_fd_in_epoll = MHD_NO;
+ run_epoll_for_upgrade (daemon);
+ continue;
+ }
+#endif
+ /* UGH: we're storing pointers and fds in the same union
+ here; incredibly ugly and somewhat risky, even though a
+ pointer with the same numeric value as the wpipe[0] can
+ be expected to be rare... FIXME (a construction similar
+ to what we did with the `upgrade_marker` should do) */
if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) &&
(daemon->wpipe[0] == events[i].data.fd) )
{
@@ -2942,39 +3119,7 @@ MHD_epoll (struct MHD_Daemon *daemon,
MHD_pipe_drain_ (daemon->wpipe[0]);
continue;
}
- if (daemon != events[i].data.ptr)
- {
- /* this is an event relating to a 'normal' connection,
- remember the event and if appropriate mark the
- connection as 'eready'. */
- pos = events[i].data.ptr;
- if (0 != (events[i].events & EPOLLIN))
- {
- pos->epoll_state |= MHD_EPOLL_STATE_READ_READY;
- if ( ( (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) ||
- (pos->read_buffer_size > pos->read_buffer_offset) ) &&
- (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
- {
- EDLL_insert (daemon->eready_head,
- daemon->eready_tail,
- pos);
- pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
- }
- }
- if (0 != (events[i].events & EPOLLOUT))
- {
- pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY;
- if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) &&
- (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
- {
- EDLL_insert (daemon->eready_head,
- daemon->eready_tail,
- pos);
- pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
- }
- }
- }
- else /* must be listen socket */
+ if (daemon == events[i].data.ptr)
{
/* run 'accept' until it fails or we are not allowed to take
on more connections */
@@ -2984,8 +3129,39 @@ MHD_epoll (struct MHD_Daemon *daemon,
(series_length < 128) &&
(MHD_NO == daemon->at_limit) )
series_length++;
+ continue;
}
- }
+ /* this is an event relating to a 'normal' connection,
+ remember the event and if appropriate mark the
+ connection as 'eready'. */
+ pos = events[i].data.ptr;
+ /* normal processing: update read/write data */
+ if (0 != (events[i].events & EPOLLIN))
+ {
+ pos->epoll_state |= MHD_EPOLL_STATE_READ_READY;
+ if ( ( (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) ||
+ (pos->read_buffer_size > pos->read_buffer_offset) ) &&
+ (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ }
+ if (0 != (events[i].events & EPOLLOUT))
+ {
+ pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY;
+ if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) &&
+ (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) )
+ {
+ EDLL_insert (daemon->eready_head,
+ daemon->eready_tail,
+ pos);
+ pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL;
+ }
+ }
+ }
}
/* we handle resumes here because we may have ready connections
@@ -3426,8 +3602,8 @@ parse_options_va (struct MHD_Daemon *daemon,
if (gnutls_dh_params_init (&daemon->https_mem_dhparams) < 0)
{
#ifdef HAVE_MESSAGES
- MHD_DLOG(daemon,
- "Error initializing DH parameters\n");
+ MHD_DLOG (daemon,
+ "Error initializing DH parameters\n");
#endif
return MHD_NO;
}
@@ -3437,8 +3613,8 @@ parse_options_va (struct MHD_Daemon *daemon,
GNUTLS_X509_FMT_PEM) < 0)
{
#ifdef HAVE_MESSAGES
- MHD_DLOG(daemon,
- "Bad Diffie-Hellman parameters format\n");
+ MHD_DLOG (daemon,
+ "Bad Diffie-Hellman parameters format\n");
#endif
gnutls_dh_params_deinit (daemon->https_mem_dhparams);
return MHD_NO;
@@ -3652,34 +3828,27 @@ parse_options_va (struct MHD_Daemon *daemon,
#ifdef EPOLL_SUPPORT
-/**
- * Setup epoll() FD for the daemon and initialize it to listen
- * on the listen FD.
- *
- * @param daemon daemon to initialize for epoll()
- * @return #MHD_YES on success, #MHD_NO on failure
- */
static int
-setup_epoll_to_listen (struct MHD_Daemon *daemon)
+setup_epoll_fd (struct MHD_Daemon *daemon)
{
- struct epoll_event event;
+ int fd;
#ifdef USE_EPOLL_CREATE1
- daemon->epoll_fd = epoll_create1 (EPOLL_CLOEXEC);
+ fd = epoll_create1 (EPOLL_CLOEXEC);
#else /* ! USE_EPOLL_CREATE1 */
- daemon->epoll_fd = epoll_create (MAX_EVENTS);
+ fd = epoll_create (MAX_EVENTS);
#endif /* ! USE_EPOLL_CREATE1 */
- if (-1 == daemon->epoll_fd)
+ if (MHD_INVALID_SOCKET == fd)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
"Call to epoll_create1 failed: %s\n",
MHD_socket_last_strerr_ ());
#endif
- return MHD_NO;
+ return MHD_INVALID_SOCKET;
}
#if !defined(USE_EPOLL_CREATE1)
- if (!MHD_socket_noninheritable_ (daemon->epoll_fd))
+ if (! MHD_socket_noninheritable_ (fd))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
@@ -3687,6 +3856,33 @@ setup_epoll_to_listen (struct MHD_Daemon *daemon)
#endif
}
#endif /* ! USE_EPOLL_CREATE1 */
+ return fd;
+}
+
+
+/**
+ * Setup epoll() FD for the daemon and initialize it to listen
+ * on the listen FD.
+ *
+ * @param daemon daemon to initialize for epoll()
+ * @return #MHD_YES on success, #MHD_NO on failure
+ */
+static int
+setup_epoll_to_listen (struct MHD_Daemon *daemon)
+{
+ struct epoll_event event;
+
+ daemon->epoll_fd = setup_epoll_fd (daemon);
+ if (MHD_INVALID_SOCKET == daemon->epoll_fd)
+ return MHD_NO;
+#if HTTPS_SUPPORT
+ if (MHD_USE_TLS_EPOLL_UPGRADE == (MHD_USE_TLS_EPOLL_UPGRADE & daemon->options))
+ {
+ daemon->epoll_upgrade_fd = setup_epoll_fd (daemon);
+ if (MHD_INVALID_SOCKET == daemon->epoll_upgrade_fd)
+ return MHD_NO;
+ }
+#endif
if (MHD_INVALID_SOCKET == daemon->socket_fd)
return MHD_YES; /* non-listening daemon */
event.events = EPOLLIN;
@@ -3788,6 +3984,9 @@ MHD_start_daemon_va (unsigned int flags,
memset (daemon, 0, sizeof (struct MHD_Daemon));
#ifdef EPOLL_SUPPORT
daemon->epoll_fd = -1;
+#if HTTPS_SUPPORT
+ daemon->epoll_upgrade_fd = -1;
+#endif
#endif
/* try to open listen socket */
#if HTTPS_SUPPORT
@@ -3842,7 +4041,7 @@ MHD_start_daemon_va (unsigned int flags,
free (daemon);
return NULL;
}
- if (!MHD_itc_nonblocking_(daemon->wpipe[0]))
+ if (! MHD_itc_nonblocking_(daemon->wpipe[0]))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
@@ -4482,6 +4681,10 @@ thread_failed:
#ifdef EPOLL_SUPPORT
if (-1 != daemon->epoll_fd)
close (daemon->epoll_fd);
+#if HTTPS_SUPPORT
+ if (-1 != daemon->epoll_upgrade_fd)
+ close (daemon->epoll_upgrade_fd);
+#endif
#endif
#ifdef DAUTH_SUPPORT
free (daemon->nnc);
@@ -4710,6 +4913,11 @@ MHD_stop_daemon (struct MHD_Daemon *daemon)
if ( (-1 != daemon->worker_pool[i].epoll_fd) &&
(0 != MHD_socket_close_ (daemon->worker_pool[i].epoll_fd)) )
MHD_PANIC ("close failed\n");
+#if HTTPS_SUPPORT
+ if ( (-1 != daemon->worker_pool[i].epoll_upgrade_fd) &&
+ (0 != MHD_socket_close_ (daemon->worker_pool[i].epoll_upgrade_fd)) )
+ MHD_PANIC ("close failed\n");
+#endif
#endif
/* Individual pipes are always used */
if (1)
@@ -4762,6 +4970,12 @@ MHD_stop_daemon (struct MHD_Daemon *daemon)
(-1 != daemon->epoll_fd) &&
(0 != MHD_socket_close_ (daemon->epoll_fd)) )
MHD_PANIC ("close failed\n");
+#if HTTPS_SUPPORT
+ if ( (0 != (daemon->options & MHD_USE_EPOLL)) &&
+ (-1 != daemon->epoll_upgrade_fd) &&
+ (0 != MHD_socket_close_ (daemon->epoll_upgrade_fd)) )
+ MHD_PANIC ("close failed\n");
+#endif
#endif
#ifdef DAUTH_SUPPORT
diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h
index 9f0faba7..00843729 100644
--- a/src/microhttpd/internal.h
+++ b/src/microhttpd/internal.h
@@ -899,6 +899,45 @@ struct MHD_Connection
#define RESERVE_EBUF_SIZE 8
/**
+ * Context we pass to epoll() for each of the two sockets
+ * of a `struct MHD_UpgradeResponseHandle`. We need to do
+ * this so we can distinguish the two sockets when epoll()
+ * gives us event notifications.
+ */
+struct UpgradeEpollHandle
+{
+ /**
+ * Reference to the overall response handle this struct is
+ * included within.
+ */
+ struct MHD_UpgradeResponseHandle *urh;
+
+ /**
+ * The socket this event is kind-of about. Note that this is NOT
+ * necessarily the socket we are polling on, as for when we read
+ * from TLS, we epoll() on the connection's socket
+ * (`urh->connection->socket_fd`), while this then the application's
+ * socket (where the application will read from). Nevertheless, for
+ * the application to read, we need to first read from TLS, hence
+ * the two are related.
+ *
+ * Similarly, for writing to TLS, this epoll() will be on the
+ * connection's `socket_fd`, and this will merely be the FD which
+ * the applicatio would write to. Hence this struct must always be
+ * interpreted based on which field in `struct
+ * MHD_UpgradeResponseHandle` it is (`app` or `mhd`).
+ */
+ MHD_socket socket;
+
+ /**
+ * IO-state of the @e socket (or the connection's `socket_fd`).
+ */
+ enum MHD_EpollState celi;
+
+};
+
+
+/**
* Handle given to the application to manage special
* actions relating to MHD responses that "upgrade"
* the HTTP protocol (i.e. to WebSockets).
@@ -960,23 +999,13 @@ struct MHD_UpgradeResponseHandle
/**
* The socket we gave to the application (r/w).
*/
- MHD_socket app_socket;
+ struct UpgradeEpollHandle app;
/**
* If @a app_sock was a socketpair, our end of it, otherwise
* #MHD_INVALID_SOCKET; (r/w).
*/
- MHD_socket mhd_socket;
-
- /**
- * IO-state of the @e mhd_socket.
- */
- enum MHD_EpollState celi_mhd;
-
- /**
- * IO-state of the @e connection's socket.
- */
- enum MHD_EpollState celi_client;
+ struct UpgradeEpollHandle mhd;
/**
* Emergency IO buffer we use in case the memory pool has literally
@@ -1257,10 +1286,25 @@ struct MHD_Daemon
int epoll_fd;
/**
- * MHD_YES if the listen socket is in the 'epoll' set,
- * MHD_NO if not.
+ * #MHD_YES if the listen socket is in the 'epoll' set,
+ * #MHD_NO if not.
*/
int listen_socket_in_epoll;
+
+#if HTTPS_SUPPORT
+ /**
+ * File descriptor associated with the #run_epoll_for_upgrade() loop.
+ * Only available if #MHD_USE_HTTPS_EPOLL_UPGRADE is set.
+ */
+ int epoll_upgrade_fd;
+
+ /**
+ * #MHD_YES if @e epoll_upgrade_fd is in the 'epoll' set,
+ * #MHD_NO if not.
+ */
+ int upgrade_fd_in_epoll;
+#endif
+
#endif
/**
diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c
index e778ed7f..670be983 100644
--- a/src/microhttpd/response.c
+++ b/src/microhttpd/response.c
@@ -599,7 +599,8 @@ MHD_upgrade_action (struct MHD_UpgradeResponseHandle *urh,
enum MHD_UpgradeAction action,
...)
{
- struct MHD_Daemon *daemon = urh->connection->daemon;
+ struct MHD_Connection *connection = urh->connection;
+ struct MHD_Daemon *daemon = connection->daemon;
switch (action)
{
@@ -611,18 +612,46 @@ MHD_upgrade_action (struct MHD_UpgradeResponseHandle *urh,
DLL_remove (daemon->urh_head,
daemon->urh_tail,
urh);
- /* FIXME: if running in epoll()-mode, do we have
- to remove any of the FDs from any epoll-sets here? */
- if ( (MHD_INVALID_SOCKET != urh->app_socket) &&
- (0 != MHD_socket_close_ (urh->app_socket)) )
- MHD_PANIC ("close failed\n");
- if ( (MHD_INVALID_SOCKET != urh->mhd_socket) &&
- (0 != MHD_socket_close_ (urh->mhd_socket)) )
- MHD_PANIC ("close failed\n");
+ if (0 != (daemon->options & MHD_USE_EPOLL))
+ {
+ /* epoll documentation suggests that closing a FD
+ automatically removes it from the epoll set; however,
+ this is not true as if we fail to do manually remove it,
+ we are still seeing an event for this fd in epoll,
+ causing grief (use-after-free...) --- at least on my
+ system. */
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_DEL,
+ connection->socket_fd,
+ NULL))
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+ }
+ if (MHD_INVALID_SOCKET != urh->app.socket)
+ {
+ if (0 != MHD_socket_close_ (urh->app.socket))
+ MHD_PANIC ("close failed\n");
+ }
+ if (MHD_INVALID_SOCKET != urh->mhd.socket)
+ {
+ /* epoll documentation suggests that closing a FD
+ automatically removes it from the epoll set; however,
+ this is not true as if we fail to do manually remove it,
+ we are still seeing an event for this fd in epoll,
+ causing grief (use-after-free...) --- at least on my
+ system. */
+ if ( (0 != (daemon->options & MHD_USE_EPOLL)) &&
+ (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_DEL,
+ urh->mhd.socket,
+ NULL)) )
+ MHD_PANIC ("Failed to remove FD from epoll set\n");
+ if (0 != MHD_socket_close_ (urh->mhd.socket))
+ MHD_PANIC ("close failed\n");
+ }
}
#endif
- MHD_resume_connection (urh->connection);
- MHD_connection_close_ (urh->connection,
+ MHD_resume_connection (connection);
+ MHD_connection_close_ (connection,
MHD_REQUEST_TERMINATED_COMPLETED_OK);
free (urh);
return MHD_YES;
@@ -691,6 +720,15 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response,
free (urh);
return MHD_NO;
}
+ if ( (! MHD_itc_nonblocking_(sv[0])) ||
+ (! MHD_itc_nonblocking_(sv[1])) )
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Failed to make read side of inter-thread control channel non-blocking: %s\n",
+ MHD_pipe_last_strerror_ ());
+#endif
+ }
if ( (! MHD_SCKT_FD_FITS_FDSET_(sv[1], NULL)) &&
(0 == (daemon->options & (MHD_USE_POLL | MHD_USE_EPOLL))) )
{
@@ -707,8 +745,12 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response,
free (urh);
return MHD_NO;
}
- urh->app_socket = sv[0];
- urh->mhd_socket = sv[1];
+ urh->app.socket = sv[0];
+ urh->app.urh = urh;
+ urh->app.celi = MHD_EPOLL_STATE_UNREADY;
+ urh->mhd.socket = sv[1];
+ urh->mhd.urh = urh;
+ urh->mhd.celi = MHD_EPOLL_STATE_UNREADY;
pool = connection->pool;
avail = MHD_pool_get_free (pool);
if (avail < 8)
@@ -740,26 +782,80 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response,
connection->client_context,
connection->read_buffer,
rbo,
- urh->app_socket,
+ urh->app.socket,
urh);
/* As far as MHD is concerned, this connection is
suspended; it will be resumed once we are done
in the #MHD_upgrade_action() function */
MHD_suspend_connection (connection);
- urh->celi_mhd = MHD_EPOLL_STATE_UNREADY;
- urh->celi_client = MHD_EPOLL_STATE_UNREADY;
- /* FIXME: is it possible we did not fully drain the client
- socket yet and are thus read-ready already? This may
- matter if we are in epoll() edge triggered mode... */
+
/* Launch IO processing by the event loop */
- /* FIXME: this will not work (yet) for thread-per-connection processing */
- DLL_insert (connection->daemon->urh_head,
- connection->daemon->urh_tail,
+ if (0 != (daemon->options & MHD_USE_EPOLL))
+ {
+ /* We're running with epoll(), need to add the sockets
+ to the event set of the daemon's `epoll_upgrade_fd` */
+ struct epoll_event event;
+
+ EXTRA_CHECK (MHD_SOCKET_INVALID != daemon->epoll_upgrade_fd);
+ /* First, add network socket */
+ event.events = EPOLLIN | EPOLLOUT;
+ event.data.ptr = &urh->app;
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_ADD,
+ connection->socket_fd,
+ &event))
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ if (0 != MHD_socket_close_ (sv[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_socket_close_ (sv[1]))
+ MHD_PANIC ("close failed\n");
+ free (urh);
+ return MHD_NO;
+ }
+
+ /* Second, add our end of the UNIX socketpair() */
+ event.events = EPOLLIN | EPOLLOUT;
+ event.data.ptr = &urh->mhd;
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_ADD,
+ urh->mhd.socket,
+ &event))
+ {
+ event.events = EPOLLIN | EPOLLOUT;
+ event.data.ptr = &urh->app;
+ if (0 != epoll_ctl (daemon->epoll_upgrade_fd,
+ EPOLL_CTL_DEL,
+ connection->socket_fd,
+ &event))
+ MHD_PANIC ("Error cleaning up while handling epoll error");
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ "Call to epoll_ctl failed: %s\n",
+ MHD_socket_last_strerr_ ());
+#endif
+ if (0 != MHD_socket_close_ (sv[0]))
+ MHD_PANIC ("close failed\n");
+ if (0 != MHD_socket_close_ (sv[1]))
+ MHD_PANIC ("close failed\n");
+ free (urh);
+ return MHD_NO;
+ }
+ }
+
+ /* This takes care of most event loops: simply add to DLL */
+ DLL_insert (daemon->urh_head,
+ daemon->urh_tail,
urh);
+ /* FIXME: None of the above will not work (yet) for thread-per-connection processing */
return MHD_YES;
}
- urh->app_socket = MHD_INVALID_SOCKET;
- urh->mhd_socket = MHD_INVALID_SOCKET;
+ urh->app.socket = MHD_INVALID_SOCKET;
+ urh->mhd.socket = MHD_INVALID_SOCKET;
#endif
response->upgrade_handler (response->upgrade_handler_cls,
connection,