blob: 8875064ef69afd24d54f33de4acde46099b8c47b [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file lib/connection_add.c
* @brief functions to add connection to our active set
* @author Christian Grothoff
*/
#include "internal.h"
#include "connection_add.h"
#include "connection_call_handlers.h"
#include "connection_close.h"
#include "connection_finish_forward.h"
#include "connection_update_last_activity.h"
#include "daemon_ip_limit.h"
#include "daemon_select.h"
#include "daemon_poll.h"
#include "mhd_sockets.h"
#ifdef UPGRADE_SUPPORT
/**
* Main function of the thread that handles an individual connection
* after it was "upgraded" when #MHD_USE_THREAD_PER_CONNECTION is set.
* @remark To be called only from thread that process
* connection's recv(), send() and response.
*
* @param con the connection this thread will handle
*/
static void
thread_main_connection_upgrade (struct MHD_Connection *con)
{
#ifdef HTTPS_SUPPORT
struct MHD_Daemon *daemon = con->daemon;
/* Here, we need to bi-directionally forward
until the application tells us that it is done
with the socket; */
if ( (NULL != daemon->tls_api) &&
(MHD_ELS_POLL != daemon->event_loop_syscall) )
{
MHD_daemon_upgrade_connection_with_select_ (con);
}
#ifdef HAVE_POLL
else if (NULL != daemon->tls_api)
{
MHD_daemon_upgrade_connection_with_poll_ (con);
}
#endif
/* end HTTPS */
#endif /* HTTPS_SUPPORT */
/* TLS forwarding was finished. Cleanup socketpair. */
MHD_connection_finish_forward_ (con);
/* Do not set 'urh->clean_ready' yet as 'urh' will be used
* in connection thread for a little while. */
}
#endif /* UPGRADE_SUPPORT */
/**
* Main function of the thread that handles an individual
* connection when #MHD_USE_THREAD_PER_CONNECTION is set.
*
* @param data the `struct MHD_Connection` this thread will handle
* @return always 0
*/
static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_
thread_main_handle_connection (void *data)
{
struct MHD_Connection *con = data;
struct MHD_Daemon *daemon = con->daemon;
int num_ready;
fd_set rs;
fd_set ws;
fd_set es;
MHD_socket maxsock;
struct timeval tv;
struct timeval *tvp;
time_t now;
#if WINDOWS
#ifdef HAVE_POLL
int extra_slot;
#endif /* HAVE_POLL */
#define EXTRA_SLOTS 1
#else /* !WINDOWS */
#define EXTRA_SLOTS 0
#endif /* !WINDOWS */
#ifdef HAVE_POLL
struct pollfd p[1 + EXTRA_SLOTS];
#endif
#undef EXTRA_SLOTS
#ifdef HAVE_POLL
const bool use_poll = (MHD_ELS_POLL == daemon->event_loop_syscall);
#else /* ! HAVE_POLL */
const bool use_poll = false;
#endif /* ! HAVE_POLL */
bool was_suspended = false;
MHD_thread_init_ (&con->pid);
while ( (! daemon->shutdown) &&
(MHD_REQUEST_CLOSED != con->request.state) )
{
const time_t timeout = daemon->connection_default_timeout;
#ifdef UPGRADE_SUPPORT
struct MHD_UpgradeResponseHandle *const urh = con->request.urh;
#else /* ! UPGRADE_SUPPORT */
static const void *const urh = NULL;
#endif /* ! UPGRADE_SUPPORT */
if ( (con->suspended) &&
(NULL == urh) )
{
/* Connection was suspended, wait for resume. */
was_suspended = true;
if (! use_poll)
{
FD_ZERO (&rs);
if (! MHD_add_to_fd_set_ (MHD_itc_r_fd_ (daemon->itc),
&rs,
NULL,
FD_SETSIZE))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE,
_ ("Failed to add FD to fd_set.\n"));
#endif
goto exit;
}
if (0 > MHD_SYS_select_ (MHD_itc_r_fd_ (daemon->itc) + 1,
&rs,
NULL,
NULL,
NULL))
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EINTR_ (err))
continue;
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_UNEXPECTED_SELECT_ERROR,
_ ("Error during select (%d): `%s'\n"),
err,
MHD_socket_strerr_ (err));
#endif
break;
}
}
#ifdef HAVE_POLL
else /* use_poll */
{
p[0].events = POLLIN;
p[0].fd = MHD_itc_r_fd_ (daemon->itc);
p[0].revents = 0;
if (0 > MHD_sys_poll_ (p,
1,
-1))
{
if (MHD_SCKT_LAST_ERR_IS_ (MHD_SCKT_EINTR_))
continue;
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_UNEXPECTED_POLL_ERROR,
_ ("Error during poll: `%s'\n"),
MHD_socket_last_strerr_ ());
#endif
break;
}
}
#endif /* HAVE_POLL */
MHD_itc_clear_ (daemon->itc);
continue; /* Check again for resume. */
} /* End of "suspended" branch. */
if (was_suspended)
{
MHD_connection_update_last_activity_ (con); /* Reset timeout timer. */
/* Process response queued during suspend and update states. */
MHD_request_handle_idle_ (&con->request);
was_suspended = false;
}
tvp = NULL;
if ( (MHD_EVENT_LOOP_INFO_BLOCK == con->request.event_loop_info)
#ifdef HTTPS_SUPPORT
|| ( (con->tls_read_ready) &&
(MHD_EVENT_LOOP_INFO_READ == con->request.event_loop_info) )
#endif /* HTTPS_SUPPORT */
)
{
/* do not block: more data may be inside of TLS buffers waiting or
* application must provide response data */
tv.tv_sec = 0;
tv.tv_usec = 0;
tvp = &tv;
}
if ( (NULL == tvp) &&
(timeout > 0) )
{
now = MHD_monotonic_sec_counter ();
if (now - con->last_activity > timeout)
tv.tv_sec = 0;
else
{
const time_t seconds_left = timeout - (now - con->last_activity);
#if ! defined(_WIN32) || defined(__CYGWIN__)
tv.tv_sec = seconds_left;
#else /* _WIN32 && !__CYGWIN__ */
if (seconds_left > TIMEVAL_TV_SEC_MAX)
tv.tv_sec = TIMEVAL_TV_SEC_MAX;
else
tv.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) seconds_left;
#endif /* _WIN32 && ! __CYGWIN__ */
}
tv.tv_usec = 0;
tvp = &tv;
}
if (! use_poll)
{
/* use select */
bool err_state = false;
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
maxsock = MHD_INVALID_SOCKET;
switch (con->request.event_loop_info)
{
case MHD_EVENT_LOOP_INFO_READ:
if (! MHD_add_to_fd_set_ (con->socket_fd,
&rs,
&maxsock,
FD_SETSIZE))
err_state = true;
break;
case MHD_EVENT_LOOP_INFO_WRITE:
if (! MHD_add_to_fd_set_ (con->socket_fd,
&ws,
&maxsock,
FD_SETSIZE))
err_state = true;
break;
case MHD_EVENT_LOOP_INFO_BLOCK:
if (! MHD_add_to_fd_set_ (con->socket_fd,
&es,
&maxsock,
FD_SETSIZE))
err_state = true;
break;
case MHD_EVENT_LOOP_INFO_CLEANUP:
/* how did we get here!? */
goto exit;
}
#if WINDOWS
if (MHD_ITC_IS_VALID_ (daemon->itc) )
{
if (! MHD_add_to_fd_set_ (MHD_itc_r_fd_ (daemon->itc),
&rs,
&maxsock,
FD_SETSIZE))
err_state = 1;
}
#endif
if (err_state)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE,
_ ("Failed to add FD to fd_set.\n"));
#endif
goto exit;
}
num_ready = MHD_SYS_select_ (maxsock + 1,
&rs,
&ws,
&es,
tvp);
if (num_ready < 0)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EINTR_ (err))
continue;
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_UNEXPECTED_SELECT_ERROR,
_ ("Error during select (%d): `%s'\n"),
err,
MHD_socket_strerr_ (err));
#endif
break;
}
#if WINDOWS
/* Clear ITC before other processing so additional
* signals will trigger select() again */
if ( (MHD_ITC_IS_VALID_ (daemon->itc)) &&
(FD_ISSET (MHD_itc_r_fd_ (daemon->itc),
&rs)) )
MHD_itc_clear_ (daemon->itc);
#endif
if (MHD_NO ==
MHD_connection_call_handlers_ (con,
FD_ISSET (con->socket_fd,
&rs),
FD_ISSET (con->socket_fd,
&ws),
FD_ISSET (con->socket_fd,
&es)) )
goto exit;
}
#ifdef HAVE_POLL
else
{
/* use poll */
memset (&p,
0,
sizeof (p));
p[0].fd = con->socket_fd;
switch (con->request.event_loop_info)
{
case MHD_EVENT_LOOP_INFO_READ:
p[0].events |= POLLIN | MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_WRITE:
p[0].events |= POLLOUT | MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_BLOCK:
p[0].events |= MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_CLEANUP:
/* how did we get here!? */
goto exit;
}
#if WINDOWS
extra_slot = 0;
if (MHD_ITC_IS_VALID_ (daemon->itc))
{
p[1].events |= POLLIN;
p[1].fd = MHD_itc_r_fd_ (daemon->itc);
p[1].revents = 0;
extra_slot = 1;
}
#endif
if (MHD_sys_poll_ (p,
#if WINDOWS
1 + extra_slot,
#else
1,
#endif
(NULL == tvp) ? -1 : tv.tv_sec * 1000) < 0)
{
if (MHD_SCKT_LAST_ERR_IS_ (MHD_SCKT_EINTR_))
continue;
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_UNEXPECTED_POLL_ERROR,
_ ("Error during poll: `%s'\n"),
MHD_socket_last_strerr_ ());
#endif
break;
}
#if WINDOWS
/* Clear ITC before other processing so additional
* signals will trigger poll() again */
if ( (MHD_ITC_IS_VALID_ (daemon->itc)) &&
(0 != (p[1].revents & (POLLERR | POLLHUP | POLLIN))) )
MHD_itc_clear_ (daemon->itc);
#endif
if (MHD_NO ==
MHD_connection_call_handlers_ (con,
(0 != (p[0].revents & POLLIN)),
(0 != (p[0].revents & POLLOUT)),
(0 != (p[0].revents & (POLLERR
|
MHD_POLL_REVENTS_ERR_DISC))) ))
goto exit;
}
#endif
#ifdef UPGRADE_SUPPORT
if (MHD_REQUEST_UPGRADE == con->request.state)
{
/* Normal HTTP processing is finished,
* notify application. */
if (NULL != con->request.response->termination_cb)
con->request.response->termination_cb
(con->request.response->termination_cb_cls,
MHD_REQUEST_TERMINATED_COMPLETED_OK,
con->request.client_context);
thread_main_connection_upgrade (con);
/* MHD_connection_finish_forward_() was called by thread_main_connection_upgrade(). */
/* "Upgraded" data will not be used in this thread from this point. */
con->request.urh->clean_ready = true;
/* If 'urh->was_closed' set to true, connection will be
* moved immediately to cleanup list. Otherwise connection
* will stay in suspended list until 'urh' will be marked
* with 'was_closed' by application. */
MHD_request_resume (&con->request);
/* skip usual clean up */
return (MHD_THRD_RTRN_TYPE_) 0;
}
#endif /* UPGRADE_SUPPORT */
}
#if DEBUG_CLOSE
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_THREAD_TERMINATING,
_ ("Processing thread terminating. Closing connection.\n"));
#endif
#endif
if (MHD_REQUEST_CLOSED != con->request.state)
MHD_connection_close_ (con,
(daemon->shutdown) ?
MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN :
MHD_REQUEST_TERMINATED_WITH_ERROR);
MHD_request_handle_idle_ (&con->request);
exit:
if (NULL != con->request.response)
{
MHD_response_queue_for_destroy (con->request.response);
con->request.response = NULL;
}
if (MHD_INVALID_SOCKET != con->socket_fd)
{
shutdown (con->socket_fd,
SHUT_WR);
/* 'socket_fd' can be used in other thread to signal shutdown.
* To avoid data races, do not close socket here. Daemon will
* use more connections only after cleanup anyway. */
}
return (MHD_THRD_RTRN_TYPE_) 0;
}
/**
* Callback for receiving data from the socket.
*
* @param connection the MHD connection structure
* @param other where to write received data to
* @param i maximum size of other (in bytes)
* @return positive value for number of bytes actually received or
* negative value for error number MHD_ERR_xxx_
*/
static ssize_t
recv_param_adapter (struct MHD_Connection *connection,
void *other,
size_t i)
{
ssize_t ret;
if ( (MHD_INVALID_SOCKET == connection->socket_fd) ||
(MHD_REQUEST_CLOSED == connection->request.state) )
{
return MHD_ERR_NOTCONN_;
}
if (i > MHD_SCKT_SEND_MAX_SIZE_)
i = MHD_SCKT_SEND_MAX_SIZE_; /* return value limit */
ret = MHD_recv_ (connection->socket_fd,
other,
i);
if (0 > ret)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
{
#ifdef EPOLL_SUPPORT
/* Got EAGAIN --- no longer read-ready */
connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY;
#endif /* EPOLL_SUPPORT */
return MHD_ERR_AGAIN_;
}
if (MHD_SCKT_ERR_IS_EINTR_ (err))
return MHD_ERR_AGAIN_;
if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ECONNRESET_))
return MHD_ERR_CONNRESET_;
/* Treat any other error as hard error. */
return MHD_ERR_NOTCONN_;
}
#ifdef EPOLL_SUPPORT
else if (i > (size_t) ret)
connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY;
#endif /* EPOLL_SUPPORT */
return ret;
}
/**
* Callback for writing data to the socket.
*
* @param connection the MHD connection structure
* @param other data to write
* @param i number of bytes to write
* @return positive value for number of bytes actually sent or
* negative value for error number MHD_ERR_xxx_
*/
static ssize_t
send_param_adapter (struct MHD_Connection *connection,
const void *other,
size_t i)
{
ssize_t ret;
if ( (MHD_INVALID_SOCKET == connection->socket_fd) ||
(MHD_REQUEST_CLOSED == connection->request.state) )
{
return MHD_ERR_NOTCONN_;
}
if (i > MHD_SCKT_SEND_MAX_SIZE_)
i = MHD_SCKT_SEND_MAX_SIZE_; /* return value limit */
ret = MHD_send_ (connection->socket_fd,
other,
i);
if (0 > ret)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
{
#ifdef EPOLL_SUPPORT
/* EAGAIN --- no longer write-ready */
connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
#endif /* EPOLL_SUPPORT */
return MHD_ERR_AGAIN_;
}
if (MHD_SCKT_ERR_IS_EINTR_ (err))
return MHD_ERR_AGAIN_;
if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ECONNRESET_))
return MHD_ERR_CONNRESET_;
/* Treat any other error as hard error. */
return MHD_ERR_NOTCONN_;
}
#ifdef EPOLL_SUPPORT
else if (i > (size_t) ret)
connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
#endif /* EPOLL_SUPPORT */
return ret;
}
/**
* Add another client connection to the set of connections
* managed by MHD. This API is usually not needed (since
* MHD will accept inbound connections on the server socket).
* Use this API in special cases, for example if your HTTP
* server is behind NAT and needs to connect out to the
* HTTP client.
*
* The given client socket will be managed (and closed!) by MHD after
* this call and must no longer be used directly by the application
* afterwards.
*
* @param daemon daemon that manages the connection
* @param client_socket socket to manage (MHD will expect
* to receive an HTTP request from this socket next).
* @param addr IP address of the client
* @param addrlen number of bytes in @a addr
* @param external_add perform additional operations needed due
* to the application calling us directly
* @param non_blck indicate that socket in non-blocking mode
* @return #MHD_SC_OK on success
*/
static enum MHD_StatusCode
internal_add_connection (struct MHD_Daemon *daemon,
MHD_socket client_socket,
const struct sockaddr *addr,
socklen_t addrlen,
bool external_add,
bool non_blck)
{
enum MHD_StatusCode sc;
struct MHD_Connection *connection;
int eno = 0;
/* Direct add to master daemon could happen only with "external" add mode. */
mhd_assert ( (NULL == daemon->worker_pool) ||
(external_add) );
if ( (external_add) &&
(NULL != daemon->worker_pool) )
{
unsigned int i;
/* have a pool, try to find a pool with capacity; we use the
socket as the initial offset into the pool for load
balancing */
for (i = 0; i < daemon->worker_pool_size; ++i)
{
struct MHD_Daemon *const worker =
&daemon->worker_pool[(i + client_socket) % daemon->worker_pool_size];
if (worker->connections < worker->global_connection_limit)
return internal_add_connection (worker,
client_socket,
addr,
addrlen,
true,
non_blck);
}
/* all pools are at their connection limit, must refuse */
MHD_socket_close_chk_ (client_socket);
#if ENFILE
errno = ENFILE;
#endif
return MHD_SC_LIMIT_CONNECTIONS_REACHED;
}
if ( (! MHD_SCKT_FD_FITS_FDSET_ (client_socket,
NULL)) &&
(MHD_ELS_SELECT == daemon->event_loop_syscall) )
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE,
_ ("Socket descriptor larger than FD_SETSIZE: %d > %d\n"),
(int) client_socket,
(int) FD_SETSIZE);
#endif
MHD_socket_close_chk_ (client_socket);
#if EINVAL
errno = EINVAL;
#endif
return MHD_SC_SOCKET_OUTSIDE_OF_FDSET_RANGE;
}
#ifdef MHD_socket_nosignal_
if (! MHD_socket_nosignal_ (client_socket))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_CONFIGURE_NOSIGPIPE_FAILED,
_ ("Failed to set SO_NOSIGPIPE on accepted socket: %s\n"),
MHD_socket_last_strerr_ ());
#endif
#ifndef MSG_NOSIGNAL
/* Cannot use socket as it can produce SIGPIPE. */
#ifdef ENOTSOCK
errno = ENOTSOCK;
#endif /* ENOTSOCK */
return MHD_SC_ACCEPT_CONFIGURE_NOSIGPIPE_FAILED;
#endif /* ! MSG_NOSIGNAL */
}
#endif /* MHD_socket_nosignal_ */
#ifdef HAVE_MESSAGES
#if DEBUG_CONNECT
MHD_DLOG (daemon,
MHD_SC_CONNECTION_ACCEPTED,
_ ("Accepted connection on socket %d.\n"),
client_socket);
#endif
#endif
if ( (daemon->connections == daemon->global_connection_limit) ||
(MHD_NO == MHD_ip_limit_add (daemon,
addr,
addrlen)) )
{
/* above connection limit - reject */
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_LIMIT_CONNECTIONS_REACHED,
_ (
"Server reached connection limit. Closing inbound connection.\n"));
#endif
MHD_socket_close_chk_ (client_socket);
#if ENFILE
errno = ENFILE;
#endif
return MHD_SC_LIMIT_CONNECTIONS_REACHED;
}
/* apply connection acceptance policy if present */
if ( (NULL != daemon->accept_policy_cb) &&
(MHD_NO ==
daemon->accept_policy_cb (daemon->accept_policy_cb_cls,
addr,
addrlen)) )
{
#if DEBUG_CLOSE
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_POLICY_REJECTED,
_ ("Connection rejected by application. Closing connection.\n"));
#endif
#endif
MHD_socket_close_chk_ (client_socket);
MHD_ip_limit_del (daemon,
addr,
addrlen);
#if EACCESS
errno = EACCESS;
#endif
return MHD_SC_ACCEPT_POLICY_REJECTED;
}
if (NULL ==
(connection = MHD_calloc_ (1,
sizeof (struct MHD_Connection))))
{
eno = errno;
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_CONNECTION_MALLOC_FAILURE,
"Error allocating memory: %s\n",
MHD_strerror_ (errno));
#endif
MHD_socket_close_chk_ (client_socket);
MHD_ip_limit_del (daemon,
addr,
addrlen);
errno = eno;
return MHD_SC_CONNECTION_MALLOC_FAILURE;
}
connection->pool
= MHD_pool_create (daemon->connection_memory_limit_b);
if (NULL == connection->pool)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_POOL_MALLOC_FAILURE,
_ ("Error allocating memory: %s\n"),
MHD_strerror_ (errno));
#endif
MHD_socket_close_chk_ (client_socket);
MHD_ip_limit_del (daemon,
addr,
addrlen);
free (connection);
#if ENOMEM
errno = ENOMEM;
#endif
return MHD_SC_POOL_MALLOC_FAILURE;
}
connection->connection_timeout = daemon->connection_default_timeout;
memcpy (&connection->addr,
addr,
addrlen);
connection->addr_len = addrlen;
connection->socket_fd = client_socket;
connection->sk_nonblck = non_blck;
connection->daemon = daemon;
connection->last_activity = MHD_monotonic_sec_counter ();
#ifdef HTTPS_SUPPORT
if (NULL != daemon->tls_api)
{
connection->tls_cs
= daemon->tls_api->setup_connection (daemon->tls_api->cls,
NULL /* FIXME */);
if (NULL == connection->tls_cs)
{
eno = EINVAL;
sc = -1; // FIXME!
goto cleanup;
}
}
else
#endif /* ! HTTPS_SUPPORT */
{
/* set default connection handlers */
connection->recv_cls = &recv_param_adapter;
connection->send_cls = &send_param_adapter;
}
MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
/* Firm check under lock. */
if (daemon->connections >= daemon->global_connection_limit)
{
MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
/* above connection limit - reject */
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_LIMIT_CONNECTIONS_REACHED,
_ (
"Server reached connection limit. Closing inbound connection.\n"));
#endif
#if ENFILE
eno = ENFILE;
#endif
sc = MHD_SC_LIMIT_CONNECTIONS_REACHED;
goto cleanup;
}
daemon->connections++;
if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode)
{
XDLL_insert (daemon->normal_timeout_head,
daemon->normal_timeout_tail,
connection);
}
DLL_insert (daemon->connections_head,
daemon->connections_tail,
connection);
MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
if (NULL != daemon->notify_connection_cb)
daemon->notify_connection_cb (daemon->notify_connection_cb_cls,
connection,
MHD_CONNECTION_NOTIFY_STARTED);
/* attempt to create handler thread */
if (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode)
{
if (! MHD_create_named_thread_ (&connection->pid,
"MHD-connection",
daemon->thread_stack_limit_b,
&thread_main_handle_connection,
connection))
{
eno = errno;
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_THREAD_LAUNCH_FAILURE,
"Failed to create a thread: %s\n",
MHD_strerror_ (eno));
#endif
sc = MHD_SC_THREAD_LAUNCH_FAILURE;
goto cleanup;
}
}
else
{
connection->pid = daemon->pid;
}
#ifdef EPOLL_SUPPORT
if (MHD_ELS_EPOLL == daemon->event_loop_syscall)
{
if ( (! daemon->enable_turbo) ||
(external_add))
{ /* Do not manipulate EReady DL-list in 'external_add' mode. */
struct epoll_event event;
event.events = EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLET;
event.data.ptr = connection;
if (0 != epoll_ctl (daemon->epoll_fd,
EPOLL_CTL_ADD,
client_socket,
&event))
{
eno = errno;
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_EPOLL_CTL_ADD_FAILED,
_ ("Call to epoll_ctl failed: %s\n"),
MHD_socket_last_strerr_ ());
#endif
sc = MHD_SC_EPOLL_CTL_ADD_FAILED;
goto cleanup;
}
connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET;
}
else
{
connection->epoll_state |= MHD_EPOLL_STATE_READ_READY
| MHD_EPOLL_STATE_WRITE_READY
| MHD_EPOLL_STATE_IN_EREADY_EDLL;
EDLL_insert (daemon->eready_head,
daemon->eready_tail,
connection);
}
}
else /* This 'else' is combined with next 'if'. */
#endif
if ( (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) &&
(external_add) &&
(MHD_ITC_IS_VALID_ (daemon->itc)) &&
(! MHD_itc_activate_ (daemon->itc,
"n")) )
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ITC_USE_FAILED,
_ (
"Failed to signal new connection via inter-thread communication channel (not necessarily fatal, continuing anyway).\n"));
#endif
}
return MHD_SC_OK;
cleanup:
if (NULL != daemon->notify_connection_cb)
daemon->notify_connection_cb (daemon->notify_connection_cb_cls,
connection,
MHD_CONNECTION_NOTIFY_CLOSED);
#ifdef HTTPS_SUPPORT
if ( (NULL != daemon->tls_api) &&
(NULL != connection->tls_cs) )
daemon->tls_api->teardown_connection (daemon->tls_api->cls,
connection->tls_cs);
#endif /* HTTPS_SUPPORT */
MHD_socket_close_chk_ (client_socket);
MHD_ip_limit_del (daemon,
addr,
addrlen);
MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode)
{
XDLL_remove (daemon->normal_timeout_head,
daemon->normal_timeout_tail,
connection);
}
DLL_remove (daemon->connections_head,
daemon->connections_tail,
connection);
MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
MHD_pool_destroy (connection->pool);
free (connection);
if (0 != eno)
errno = eno;
else
errno = EINVAL;
return sc;
}
/**
* Add another client connection to the set of connections managed by
* MHD. This API is usually not needed (since MHD will accept inbound
* connections on the server socket). Use this API in special cases,
* for example if your HTTP server is behind NAT and needs to connect
* out to the HTTP client, or if you are building a proxy.
*
* If you use this API in conjunction with a internal select or a
* thread pool, you must set the option #MHD_USE_ITC to ensure that
* the freshly added connection is immediately processed by MHD.
*
* The given client socket will be managed (and closed!) by MHD after
* this call and must no longer be used directly by the application
* afterwards.
*
* @param daemon daemon that manages the connection
* @param client_socket socket to manage (MHD will expect
* to receive an HTTP request from this socket next).
* @param addr IP address of the client
* @param addrlen number of bytes in @a addr
* @return #MHD_YES on success, #MHD_NO if this daemon could
* not handle the connection (i.e. malloc() failed, etc).
* The socket will be closed in any case; `errno` is
* set to indicate further details about the error.
* @ingroup specialized
*/
enum MHD_StatusCode
MHD_daemon_add_connection (struct MHD_Daemon *daemon,
MHD_socket client_socket,
const struct sockaddr *addr,
socklen_t addrlen)
{
bool sk_nonbl;
if (! MHD_socket_nonblocking_ (client_socket))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED,
_ ("Failed to set nonblocking mode on new client socket: %s\n"),
MHD_socket_last_strerr_ ());
#endif
sk_nonbl = false;
}
else
{
sk_nonbl = true;
}
if ( (daemon->enable_turbo) &&
(! MHD_socket_noninheritable_ (client_socket)) )
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED,
_ ("Failed to set noninheritable mode on new client socket.\n"));
#endif
}
return internal_add_connection (daemon,
client_socket,
addr,
addrlen,
true,
sk_nonbl);
}
/**
* Accept an incoming connection and create the MHD_Connection object
* for it. This function also enforces policy by way of checking with
* the accept policy callback. @remark To be called only from thread
* that process daemon's select()/poll()/etc.
*
* @param daemon handle with the listen socket
* @return #MHD_SC_OK on success
*/
enum MHD_StatusCode
MHD_accept_connection_ (struct MHD_Daemon *daemon)
{
struct sockaddr_storage addrstorage;
struct sockaddr *addr = (struct sockaddr *) &addrstorage;
socklen_t addrlen;
MHD_socket s;
MHD_socket fd;
bool sk_nonbl;
addrlen = sizeof (addrstorage);
memset (addr,
0,
sizeof (addrstorage));
if ( (MHD_INVALID_SOCKET == (fd = daemon->listen_socket)) ||
(daemon->was_quiesced) )
return MHD_SC_DAEMON_ALREADY_QUIESCED;
#ifdef USE_ACCEPT4
s = accept4 (fd,
addr,
&addrlen,
MAYBE_SOCK_CLOEXEC | MAYBE_SOCK_NONBLOCK);
sk_nonbl = (0 != MAYBE_SOCK_NONBLOCK);
#else /* ! USE_ACCEPT4 */
s = accept (fd,
addr,
&addrlen);
sk_nonbl = false;
#endif /* ! USE_ACCEPT4 */
if ( (MHD_INVALID_SOCKET == s) ||
(addrlen <= 0) )
{
const int err = MHD_socket_get_error_ ();
/* This could be a common occurrence with multiple worker threads */
if (MHD_SCKT_ERR_IS_ (err,
MHD_SCKT_EINVAL_))
return MHD_SC_DAEMON_ALREADY_SHUTDOWN; /* can happen during shutdown, let's hope this is the cause... */
if (MHD_SCKT_ERR_IS_DISCNN_BEFORE_ACCEPT_ (err))
return MHD_SC_ACCEPT_FAST_DISCONNECT; /* do not print error if client just disconnected early */
if (MHD_SCKT_ERR_IS_EAGAIN_ (err) )
return MHD_SC_ACCEPT_FAILED_EAGAIN;
if (MHD_INVALID_SOCKET != s)
{
MHD_socket_close_chk_ (s);
}
if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err) )
{
/* system/process out of resources */
if (0 == daemon->connections)
{
#ifdef HAVE_MESSAGES
/* Not setting 'at_limit' flag, as there is no way it
would ever be cleared. Instead trying to produce
bit fat ugly warning. */
MHD_DLOG (daemon,
MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED_INSTANTLY,
_ (
"Hit process or system resource limit at FIRST connection. This is really bad as there is no sane way to proceed. Will try busy waiting for system resources to become magically available.\n"));
#endif
return MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED_INSTANTLY;
}
else
{
MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
daemon->at_limit = true;
MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED,
_ (
"Hit process or system resource limit at %u connections, temporarily suspending accept(). Consider setting a lower MHD_OPTION_CONNECTION_LIMIT.\n"),
(unsigned int) daemon->connections);
#endif
return MHD_SC_ACCEPT_SYSTEM_LIMIT_REACHED;
}
}
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_FAILED_UNEXPECTEDLY,
_ ("Error accepting connection: %s\n"),
MHD_socket_strerr_ (err));
#endif
return MHD_SC_ACCEPT_FAILED_UNEXPECTEDLY;
}
#if ! defined(USE_ACCEPT4) || ! defined(HAVE_SOCK_NONBLOCK)
if (! MHD_socket_nonblocking_ (s))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED,
_ (
"Failed to set nonblocking mode on incoming connection socket: %s\n"),
MHD_socket_last_strerr_ ());
#endif
}
else
sk_nonbl = true;
#endif /* !USE_ACCEPT4 || !HAVE_SOCK_NONBLOCK */
#if ! defined(USE_ACCEPT4) || ! defined(SOCK_CLOEXEC)
if (! MHD_socket_noninheritable_ (s))
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED,
_ (
"Failed to set noninheritable mode on incoming connection socket.\n"));
#endif
}
#endif /* !USE_ACCEPT4 || !SOCK_CLOEXEC */
#ifdef HAVE_MESSAGES
#if DEBUG_CONNECT
MHD_DLOG (daemon,
MHD_SC_CONNECTION_ACCEPTED,
_ ("Accepted connection on socket %d.\n"),
s);
#endif
#endif
return internal_add_connection (daemon,
s,
addr,
addrlen,
false,
sk_nonbl);
}
/* end of connection_add.c */