mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-10-01 04:03:18 +03:00
976 lines
29 KiB
C
976 lines
29 KiB
C
/*
|
|
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/daemon_start.c
|
|
* @brief functions to start a daemon
|
|
* @author Christian Grothoff
|
|
*/
|
|
#include "internal.h"
|
|
#include "connection_cleanup.h"
|
|
#include "daemon_close_all_connections.h"
|
|
#include "daemon_select.h"
|
|
#include "daemon_poll.h"
|
|
#include "daemon_epoll.h"
|
|
#include "request_resume.h"
|
|
|
|
|
|
/**
|
|
* Set listen socket options to allow port rebinding (or not)
|
|
* depending on how MHD was configured.
|
|
*
|
|
* @param[in,out] daemon the daemon with the listen socket to configure
|
|
* @return #MHD_SC_OK on success (or non-fatal errors)
|
|
*/
|
|
static enum MHD_StatusCode
|
|
configure_listen_reuse (struct MHD_Daemon *daemon)
|
|
{
|
|
const MHD_SCKT_OPT_BOOL_ on = 1;
|
|
|
|
/* Apply the socket options according to
|
|
listening_address_reuse. */
|
|
if (daemon->allow_address_reuse)
|
|
{
|
|
/* User requested to allow reusing listening address:port. */
|
|
#ifndef MHD_WINSOCK_SOCKETS
|
|
/* Use SO_REUSEADDR on non-W32 platforms, and do not fail if
|
|
* it doesn't work. */
|
|
if (0 > setsockopt (daemon->listen_socket,
|
|
SOL_SOCKET,
|
|
SO_REUSEADDR,
|
|
(void *) &on,
|
|
sizeof (on)))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED,
|
|
_ ("setsockopt failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED;
|
|
}
|
|
return MHD_SC_OK;
|
|
#endif /* ! MHD_WINSOCK_SOCKETS */
|
|
/* Use SO_REUSEADDR on Windows and SO_REUSEPORT on most platforms.
|
|
* Fail if SO_REUSEPORT is not defined or setsockopt fails.
|
|
*/
|
|
/* SO_REUSEADDR on W32 has the same semantics
|
|
as SO_REUSEPORT on BSD/Linux */
|
|
#if defined(MHD_WINSOCK_SOCKETS) || defined(SO_REUSEPORT)
|
|
if (0 > setsockopt (daemon->listen_socket,
|
|
SOL_SOCKET,
|
|
#ifndef MHD_WINSOCK_SOCKETS
|
|
SO_REUSEPORT,
|
|
#else /* MHD_WINSOCK_SOCKETS */
|
|
SO_REUSEADDR,
|
|
#endif /* MHD_WINSOCK_SOCKETS */
|
|
(void *) &on,
|
|
sizeof (on)))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED,
|
|
_ ("setsockopt failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED;
|
|
}
|
|
return MHD_SC_OK;
|
|
#else /* !MHD_WINSOCK_SOCKETS && !SO_REUSEPORT */
|
|
/* we're supposed to allow address:port re-use, but
|
|
on this platform we cannot; fail hard */
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED,
|
|
_ (
|
|
"Cannot allow listening address reuse: SO_REUSEPORT not defined.\n"));
|
|
#endif
|
|
return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED;
|
|
#endif /* !MHD_WINSOCK_SOCKETS && !SO_REUSEPORT */
|
|
}
|
|
|
|
/* if (! daemon->allow_address_reuse) */
|
|
/* User requested to disallow reusing listening address:port.
|
|
* Do nothing except for Windows where SO_EXCLUSIVEADDRUSE
|
|
* is used and Solaris with SO_EXCLBIND.
|
|
* Fail if MHD was compiled for W32 without SO_EXCLUSIVEADDRUSE
|
|
* or setsockopt fails.
|
|
*/
|
|
#if (defined(MHD_WINSOCK_SOCKETS) && defined(SO_EXCLUSIVEADDRUSE)) || \
|
|
(defined(__sun) && defined(SO_EXCLBIND))
|
|
if (0 > setsockopt (daemon->listen_socket,
|
|
SOL_SOCKET,
|
|
#ifdef SO_EXCLUSIVEADDRUSE
|
|
SO_EXCLUSIVEADDRUSE,
|
|
#else /* SO_EXCLBIND */
|
|
SO_EXCLBIND,
|
|
#endif /* SO_EXCLBIND */
|
|
(void *) &on,
|
|
sizeof (on)))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_FAILED,
|
|
_ ("setsockopt failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_FAILED;
|
|
}
|
|
return MHD_SC_OK;
|
|
#elif defined(MHD_WINSOCK_SOCKETS) /* SO_EXCLUSIVEADDRUSE not defined on W32? */
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_NOT_SUPPORTED,
|
|
_ (
|
|
"Cannot disallow listening address reuse: SO_EXCLUSIVEADDRUSE not defined.\n"));
|
|
#endif
|
|
return MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_NOT_SUPPORTED;
|
|
#endif /* MHD_WINSOCK_SOCKETS */
|
|
/* Not on WINSOCK, simply doing nothing will do */
|
|
return MHD_SC_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Open, configure and bind the listen socket (if required).
|
|
*
|
|
* @param[in,out] daemon daemon to open the socket for
|
|
* @return #MHD_SC_OK on success
|
|
*/
|
|
static enum MHD_StatusCode
|
|
open_listen_socket (struct MHD_Daemon *daemon)
|
|
{
|
|
enum MHD_StatusCode sc;
|
|
socklen_t addrlen;
|
|
struct sockaddr_storage ss;
|
|
const struct sockaddr *sa;
|
|
int pf;
|
|
bool use_v6;
|
|
|
|
if (MHD_INVALID_SOCKET != daemon->listen_socket)
|
|
return MHD_SC_OK; /* application opened it for us! */
|
|
pf = -1;
|
|
/* Determine address family */
|
|
switch (daemon->listen_af)
|
|
{
|
|
case MHD_AF_NONE:
|
|
if (0 == daemon->listen_sa_len)
|
|
{
|
|
/* no listening desired, that's OK */
|
|
return MHD_SC_OK;
|
|
}
|
|
/* we have a listen address, get AF from there! */
|
|
switch (daemon->listen_sa.ss_family)
|
|
{
|
|
case AF_INET:
|
|
pf = PF_INET;
|
|
use_v6 = false;
|
|
break;
|
|
#ifdef AF_INET6
|
|
case AF_INET6:
|
|
pf = PF_INET6;
|
|
use_v6 = true;
|
|
break;
|
|
#endif
|
|
#ifdef AF_UNIX
|
|
case AF_UNIX:
|
|
pf = PF_UNIX;
|
|
use_v6 = false;
|
|
break;
|
|
#endif
|
|
default:
|
|
return MHD_SC_AF_NOT_SUPPORTED_BY_BUILD;
|
|
} /* switch on ss_family */
|
|
break; /* MHD_AF_NONE */
|
|
case MHD_AF_AUTO:
|
|
#ifdef HAVE_INET6
|
|
pf = PF_INET6;
|
|
use_v6 = true;
|
|
#else
|
|
pf = PF_INET;
|
|
use_v6 = false;
|
|
#endif
|
|
break;
|
|
case MHD_AF_INET4:
|
|
use_v6 = false;
|
|
pf = PF_INET;
|
|
break;
|
|
case MHD_AF_INET6:
|
|
case MHD_AF_DUAL:
|
|
#ifdef HAVE_INET6
|
|
pf = PF_INET6;
|
|
use_v6 = true;
|
|
break;
|
|
#else
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD,
|
|
_ ("IPv6 not supported by this build.\n"));
|
|
#endif
|
|
return MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD;
|
|
#endif
|
|
}
|
|
mhd_assert (-1 != pf);
|
|
/* try to open listen socket */
|
|
try_open_listen_socket:
|
|
daemon->listen_socket = MHD_socket_create_listen_ (pf);
|
|
if ( (MHD_INVALID_SOCKET == daemon->listen_socket) &&
|
|
(MHD_AF_AUTO == daemon->listen_af) &&
|
|
(use_v6) )
|
|
{
|
|
use_v6 = false;
|
|
pf = PF_INET;
|
|
goto try_open_listen_socket;
|
|
}
|
|
if (MHD_INVALID_SOCKET == daemon->listen_socket)
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET,
|
|
_ ("Failed to create socket for listening: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET;
|
|
}
|
|
|
|
if (MHD_SC_OK !=
|
|
(sc = configure_listen_reuse (daemon)))
|
|
return sc;
|
|
|
|
/* configure for dual stack (or not) */
|
|
if (use_v6)
|
|
{
|
|
#if defined IPPROTO_IPV6 && defined IPV6_V6ONLY
|
|
/* Note: "IPV6_V6ONLY" is declared by Windows Vista ff., see "IPPROTO_IPV6 Socket Options"
|
|
(http://msdn.microsoft.com/en-us/library/ms738574%28v=VS.85%29.aspx);
|
|
and may also be missing on older POSIX systems; good luck if you have any of those,
|
|
your IPv6 socket may then also bind against IPv4 anyway... */
|
|
const MHD_SCKT_OPT_BOOL_ v6_only =
|
|
(MHD_AF_INET6 == daemon->listen_af);
|
|
if (0 > setsockopt (daemon->listen_socket,
|
|
IPPROTO_IPV6,
|
|
IPV6_V6ONLY,
|
|
(const void *) &v6_only,
|
|
sizeof (v6_only)))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_FAILED,
|
|
_ ("setsockopt failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
}
|
|
#else
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_NOT_SUPPORTED,
|
|
_ (
|
|
"Cannot explicitly setup dual stack behavior on this platform.\n"));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/* Determine address to bind to */
|
|
if (0 != daemon->listen_sa_len)
|
|
{
|
|
/* Bind address explicitly given */
|
|
sa = (const struct sockaddr *) &daemon->listen_sa;
|
|
addrlen = daemon->listen_sa_len;
|
|
}
|
|
else
|
|
{
|
|
/* Compute bind address based on port and AF */
|
|
#ifdef HAVE_INET6
|
|
if (use_v6)
|
|
{
|
|
#ifdef IN6ADDR_ANY_INIT
|
|
static const struct in6_addr static_in6any = IN6ADDR_ANY_INIT;
|
|
#endif
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &ss;
|
|
|
|
addrlen = sizeof (struct sockaddr_in6);
|
|
memset (sin6,
|
|
0,
|
|
sizeof (struct sockaddr_in6));
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = htons (daemon->listen_port);
|
|
#ifdef IN6ADDR_ANY_INIT
|
|
sin6->sin6_addr = static_in6any;
|
|
#endif
|
|
#if HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
|
|
sin6->sin6_len = sizeof (struct sockaddr_in6);
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
struct sockaddr_in *sin4 = (struct sockaddr_in *) &ss;
|
|
|
|
addrlen = sizeof (struct sockaddr_in);
|
|
memset (sin4,
|
|
0,
|
|
sizeof (struct sockaddr_in));
|
|
sin4->sin_family = AF_INET;
|
|
sin4->sin_port = htons (daemon->listen_port);
|
|
if (0 != INADDR_ANY)
|
|
sin4->sin_addr.s_addr = htonl (INADDR_ANY);
|
|
#if HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
|
|
sin4->sin_len = sizeof (struct sockaddr_in);
|
|
#endif
|
|
}
|
|
sa = (const struct sockaddr *) &ss;
|
|
}
|
|
|
|
/* actually do the bind() */
|
|
if (-1 == bind (daemon->listen_socket,
|
|
sa,
|
|
addrlen))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
unsigned int port = 0;
|
|
|
|
switch (sa->sa_family)
|
|
{
|
|
case AF_INET:
|
|
if (addrlen == sizeof (struct sockaddr_in))
|
|
port = ntohs (((const struct sockaddr_in *) sa)->sin_port);
|
|
else
|
|
port = UINT16_MAX + 1; /* indicate size error */
|
|
break;
|
|
case AF_INET6:
|
|
if (addrlen == sizeof (struct sockaddr_in6))
|
|
port = ntohs (((const struct sockaddr_in6 *) sa)->sin6_port);
|
|
else
|
|
port = UINT16_MAX + 1; /* indicate size error */
|
|
break;
|
|
default:
|
|
port = UINT_MAX; /* AF_UNIX? */
|
|
break;
|
|
}
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_SOCKET_BIND_FAILED,
|
|
_ ("Failed to bind to port %u: %s\n"),
|
|
port,
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_LISTEN_SOCKET_BIND_FAILED;
|
|
}
|
|
|
|
/* setup TCP_FASTOPEN */
|
|
#ifdef TCP_FASTOPEN
|
|
if (MHD_FOM_DISABLE != daemon->fast_open_method)
|
|
{
|
|
if (0 != setsockopt (daemon->listen_socket,
|
|
IPPROTO_TCP,
|
|
TCP_FASTOPEN,
|
|
&daemon->fo_queue_length,
|
|
sizeof (daemon->fo_queue_length)))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_FAST_OPEN_FAILURE,
|
|
_ ("setsockopt failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
if (MHD_FOM_REQUIRE == daemon->fast_open_method)
|
|
return MHD_SC_FAST_OPEN_FAILURE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* setup listening */
|
|
if (0 > listen (daemon->listen_socket,
|
|
daemon->listen_backlog))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_FAILURE,
|
|
_ ("Failed to listen for connections: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_LISTEN_FAILURE;
|
|
}
|
|
return MHD_SC_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obtain the listen port number from the socket (if it
|
|
* was not explicitly set by us, i.e. if we were given
|
|
* a listen socket or if the port was 0 and the OS picked
|
|
* a free one).
|
|
*
|
|
* @param[in,out] daemon daemon to obtain the port number for
|
|
*/
|
|
static void
|
|
get_listen_port_number (struct MHD_Daemon *daemon)
|
|
{
|
|
struct sockaddr_storage servaddr;
|
|
socklen_t addrlen;
|
|
|
|
if ( (0 != daemon->listen_port) ||
|
|
(MHD_INVALID_SOCKET == daemon->listen_socket) )
|
|
return; /* nothing to be done */
|
|
|
|
memset (&servaddr,
|
|
0,
|
|
sizeof (struct sockaddr_storage));
|
|
addrlen = sizeof (servaddr);
|
|
if (0 != getsockname (daemon->listen_socket,
|
|
(struct sockaddr *) &servaddr,
|
|
&addrlen))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_PORT_INTROSPECTION_FAILURE,
|
|
_ ("Failed to get listen port number: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif /* HAVE_MESSAGES */
|
|
return;
|
|
}
|
|
#ifdef MHD_POSIX_SOCKETS
|
|
if (sizeof (servaddr) < addrlen)
|
|
{
|
|
/* should be impossible with `struct sockaddr_storage` */
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_PORT_INTROSPECTION_FAILURE,
|
|
_ (
|
|
"Failed to get listen port number (`struct sockaddr_storage` too small!?).\n"));
|
|
#endif /* HAVE_MESSAGES */
|
|
return;
|
|
}
|
|
#endif /* MHD_POSIX_SOCKETS */
|
|
switch (servaddr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *s4 = (struct sockaddr_in *) &servaddr;
|
|
|
|
daemon->listen_port = ntohs (s4->sin_port);
|
|
break;
|
|
}
|
|
#ifdef HAVE_INET6
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &servaddr;
|
|
|
|
daemon->listen_port = ntohs (s6->sin6_port);
|
|
break;
|
|
}
|
|
#endif /* HAVE_INET6 */
|
|
#ifdef AF_UNIX
|
|
case AF_UNIX:
|
|
daemon->listen_port = 0; /* special value for UNIX domain sockets */
|
|
break;
|
|
#endif
|
|
default:
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_PORT_INTROSPECTION_UNKNOWN_AF,
|
|
_ ("Unknown address family!\n"));
|
|
#endif
|
|
daemon->listen_port = 0; /* ugh */
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef EPOLL_SUPPORT
|
|
/**
|
|
* Setup file descriptor to be used for epoll() control.
|
|
*
|
|
* @param daemon the daemon to setup epoll FD for
|
|
* @return the epoll() fd to use
|
|
*/
|
|
static int
|
|
setup_epoll_fd (struct MHD_Daemon *daemon)
|
|
{
|
|
int fd;
|
|
|
|
#ifndef HAVE_MESSAGES
|
|
(void) daemon; /* Mute compiler warning. */
|
|
#endif /* ! HAVE_MESSAGES */
|
|
|
|
#ifdef USE_EPOLL_CREATE1
|
|
fd = epoll_create1 (EPOLL_CLOEXEC);
|
|
#else /* ! USE_EPOLL_CREATE1 */
|
|
fd = epoll_create (MAX_EVENTS);
|
|
#endif /* ! USE_EPOLL_CREATE1 */
|
|
if (MHD_INVALID_SOCKET == fd)
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_EPOLL_CTL_CREATE_FAILED,
|
|
_ ("Call to epoll_create1 failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_INVALID_SOCKET;
|
|
}
|
|
#if ! defined(USE_EPOLL_CREATE1)
|
|
if (! MHD_socket_noninheritable_ (fd))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_EPOLL_CTL_CONFIGURE_NOINHERIT_FAILED,
|
|
_ ("Failed to set noninheritable mode on epoll FD.\n"));
|
|
#endif
|
|
}
|
|
#endif /* ! USE_EPOLL_CREATE1 */
|
|
return fd;
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup epoll() FD for the daemon and initialize it to listen
|
|
* on the listen FD.
|
|
* @remark To be called only from thread that process
|
|
* daemon's select()/poll()/etc.
|
|
*
|
|
* @param daemon daemon to initialize for epoll()
|
|
* @return #MHD_SC_OK on success
|
|
*/
|
|
static enum MHD_StatusCode
|
|
setup_epoll_to_listen (struct MHD_Daemon *daemon)
|
|
{
|
|
struct epoll_event event;
|
|
MHD_socket ls;
|
|
|
|
/* FIXME: update function! */
|
|
daemon->epoll_fd = setup_epoll_fd (daemon);
|
|
if (-1 == daemon->epoll_fd)
|
|
return MHD_SC_EPOLL_CTL_CREATE_FAILED;
|
|
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
|
|
if (! daemon->disallow_upgrade)
|
|
{
|
|
daemon->epoll_upgrade_fd = setup_epoll_fd (daemon);
|
|
if (MHD_INVALID_SOCKET == daemon->epoll_upgrade_fd)
|
|
return MHD_SC_EPOLL_CTL_CREATE_FAILED;
|
|
}
|
|
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
|
|
if ( (MHD_INVALID_SOCKET == (ls = daemon->listen_socket)) ||
|
|
(daemon->was_quiesced) )
|
|
return MHD_SC_OK; /* non-listening daemon */
|
|
event.events = EPOLLIN;
|
|
event.data.ptr = daemon;
|
|
if (0 != epoll_ctl (daemon->epoll_fd,
|
|
EPOLL_CTL_ADD,
|
|
ls,
|
|
&event))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_EPOLL_CTL_ADD_FAILED,
|
|
_ ("Call to epoll_ctl failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_EPOLL_CTL_ADD_FAILED;
|
|
}
|
|
daemon->listen_socket_in_epoll = true;
|
|
if (MHD_ITC_IS_VALID_ (daemon->itc))
|
|
{
|
|
event.events = EPOLLIN;
|
|
event.data.ptr = (void *) daemon->epoll_itc_marker;
|
|
if (0 != epoll_ctl (daemon->epoll_fd,
|
|
EPOLL_CTL_ADD,
|
|
MHD_itc_r_fd_ (daemon->itc),
|
|
&event))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_EPOLL_CTL_ADD_FAILED,
|
|
_ ("Call to epoll_ctl failed: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
return MHD_SC_EPOLL_CTL_ADD_FAILED;
|
|
}
|
|
}
|
|
return MHD_SC_OK;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Thread that runs the polling loop until the daemon
|
|
* is explicitly shut down.
|
|
*
|
|
* @param cls `struct MHD_Deamon` to run select loop in a thread for
|
|
* @return always 0 (on shutdown)
|
|
*/
|
|
static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_
|
|
MHD_polling_thread (void *cls)
|
|
{
|
|
struct MHD_Daemon *daemon = cls;
|
|
|
|
MHD_thread_init_ (&daemon->pid);
|
|
while (! daemon->shutdown)
|
|
{
|
|
switch (daemon->event_loop_syscall)
|
|
{
|
|
case MHD_ELS_AUTO:
|
|
MHD_PANIC ("MHD_ELS_AUTO should have been mapped to preferred style.\n");
|
|
break;
|
|
case MHD_ELS_SELECT:
|
|
MHD_daemon_select_ (daemon,
|
|
MHD_YES);
|
|
break;
|
|
case MHD_ELS_POLL:
|
|
#ifdef HAVE_POLL
|
|
MHD_daemon_poll_ (daemon,
|
|
MHD_YES);
|
|
#else
|
|
MHD_PANIC ("MHD_ELS_POLL not supported, should have failed earlier.\n");
|
|
#endif
|
|
break;
|
|
case MHD_ELS_EPOLL:
|
|
#ifdef EPOLL_SUPPORT
|
|
MHD_daemon_epoll_ (daemon,
|
|
MHD_YES);
|
|
#else
|
|
MHD_PANIC ("MHD_ELS_EPOLL not supported, should have failed earlier.\n");
|
|
#endif
|
|
break;
|
|
}
|
|
MHD_connection_cleanup_ (daemon);
|
|
}
|
|
/* Resume any pending for resume connections, join
|
|
* all connection's threads (if any) and finally cleanup
|
|
* everything. */
|
|
if (! daemon->disallow_suspend_resume)
|
|
MHD_resume_suspended_connections_ (daemon);
|
|
MHD_daemon_close_all_connections_ (daemon);
|
|
|
|
return (MHD_THRD_RTRN_TYPE_) 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup the thread pool (if needed).
|
|
*
|
|
* @param[in,out] daemon daemon to setup thread pool for
|
|
* @return #MHD_SC_OK on success
|
|
*/
|
|
static enum MHD_StatusCode
|
|
setup_thread_pool (struct MHD_Daemon *daemon)
|
|
{
|
|
/* Coarse-grained count of connections per thread (note error
|
|
* due to integer division). Also keep track of how many
|
|
* connections are leftover after an equal split. */
|
|
unsigned int conns_per_thread = daemon->global_connection_limit
|
|
/ daemon->threading_mode;
|
|
unsigned int leftover_conns = daemon->global_connection_limit
|
|
% daemon->threading_mode;
|
|
int i;
|
|
enum MHD_StatusCode sc;
|
|
|
|
/* Allocate memory for pooled objects */
|
|
daemon->worker_pool = MHD_calloc_ (daemon->threading_mode,
|
|
sizeof (struct MHD_Daemon));
|
|
if (NULL == daemon->worker_pool)
|
|
return MHD_SC_THREAD_POOL_MALLOC_FAILURE;
|
|
|
|
/* Start the workers in the pool */
|
|
for (i = 0; i < daemon->threading_mode; i++)
|
|
{
|
|
/* Create copy of the Daemon object for each worker */
|
|
struct MHD_Daemon *d = &daemon->worker_pool[i];
|
|
|
|
memcpy (d,
|
|
daemon,
|
|
sizeof (struct MHD_Daemon));
|
|
/* Adjust pooling params for worker daemons; note that memcpy()
|
|
has already copied MHD_USE_INTERNAL_POLLING_THREAD thread mode into
|
|
the worker threads. */
|
|
d->master = daemon;
|
|
d->worker_pool_size = 0;
|
|
d->worker_pool = NULL;
|
|
/* Divide available connections evenly amongst the threads.
|
|
* Thread indexes in [0, leftover_conns) each get one of the
|
|
* leftover connections. */
|
|
d->global_connection_limit = conns_per_thread;
|
|
if (((unsigned int) i) < leftover_conns)
|
|
++d->global_connection_limit;
|
|
|
|
if (! daemon->disable_itc)
|
|
{
|
|
if (! MHD_itc_init_ (d->itc))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_ITC_INITIALIZATION_FAILED,
|
|
_ (
|
|
"Failed to create worker inter-thread communication channel: %s\n"),
|
|
MHD_itc_last_strerror_ () );
|
|
#endif
|
|
sc = MHD_SC_ITC_INITIALIZATION_FAILED;
|
|
goto thread_failed;
|
|
}
|
|
if ( (MHD_ELS_SELECT == daemon->event_loop_syscall) &&
|
|
(! MHD_SCKT_FD_FITS_FDSET_ (MHD_itc_r_fd_ (d->itc),
|
|
NULL)) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_ITC_DESCRIPTOR_TOO_LARGE,
|
|
_ (
|
|
"File descriptor for inter-thread communication channel exceeds maximum value.\n"));
|
|
#endif
|
|
MHD_itc_destroy_chk_ (d->itc);
|
|
sc = MHD_SC_ITC_DESCRIPTOR_TOO_LARGE;
|
|
goto thread_failed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MHD_itc_set_invalid_ (d->itc);
|
|
}
|
|
|
|
#ifdef EPOLL_SUPPORT
|
|
if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
|
|
(MHD_SC_OK != (sc = setup_epoll_to_listen (d))) )
|
|
goto thread_failed;
|
|
#endif
|
|
|
|
/* Must init cleanup connection mutex for each worker */
|
|
if (! MHD_mutex_init_ (&d->cleanup_connection_mutex))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_THREAD_POOL_CREATE_MUTEX_FAILURE,
|
|
_ ("MHD failed to initialize cleanup connection mutex.\n"));
|
|
#endif
|
|
if (! daemon->disable_itc)
|
|
MHD_itc_destroy_chk_ (d->itc);
|
|
sc = MHD_SC_THREAD_POOL_CREATE_MUTEX_FAILURE;
|
|
goto thread_failed;
|
|
}
|
|
|
|
/* Spawn the worker thread */
|
|
if (! MHD_create_named_thread_ (&d->pid,
|
|
"MHD-worker",
|
|
daemon->thread_stack_limit_b,
|
|
&MHD_polling_thread,
|
|
d))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_THREAD_POOL_LAUNCH_FAILURE,
|
|
_ ("Failed to create pool thread: %s\n"),
|
|
MHD_strerror_ (errno));
|
|
#endif
|
|
/* Free memory for this worker; cleanup below handles
|
|
* all previously-created workers. */
|
|
if (! daemon->disable_itc)
|
|
MHD_itc_destroy_chk_ (d->itc);
|
|
MHD_mutex_destroy_chk_ (&d->cleanup_connection_mutex);
|
|
sc = MHD_SC_THREAD_POOL_LAUNCH_FAILURE;
|
|
goto thread_failed;
|
|
}
|
|
} /* end for() */
|
|
return MHD_SC_OK;
|
|
|
|
thread_failed:
|
|
/* If no worker threads created, then shut down normally. Calling
|
|
MHD_stop_daemon (as we do below) doesn't work here since it
|
|
assumes a 0-sized thread pool means we had been in the default
|
|
MHD_USE_INTERNAL_POLLING_THREAD mode. */
|
|
if (0 == i)
|
|
{
|
|
if (NULL != daemon->worker_pool)
|
|
{
|
|
free (daemon->worker_pool);
|
|
daemon->worker_pool = NULL;
|
|
}
|
|
return MHD_SC_THREAD_LAUNCH_FAILURE;
|
|
}
|
|
/* Shutdown worker threads we've already created. Pretend
|
|
as though we had fully initialized our daemon, but
|
|
with a smaller number of threads than had been
|
|
requested. */
|
|
daemon->worker_pool_size = i;
|
|
daemon->listen_socket = MHD_daemon_quiesce (daemon);
|
|
return sc;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start a webserver.
|
|
*
|
|
* @param daemon daemon to start; you can no longer set
|
|
* options on this daemon after this call!
|
|
* @return #MHD_SC_OK on success
|
|
* @ingroup event
|
|
*/
|
|
enum MHD_StatusCode
|
|
MHD_daemon_start (struct MHD_Daemon *daemon)
|
|
{
|
|
enum MHD_StatusCode sc;
|
|
|
|
if (MHD_ELS_AUTO == daemon->event_loop_syscall)
|
|
{
|
|
#if EPOLL_SUPPORT
|
|
/* We do not support thread-per-connection in combination
|
|
with epoll, so use poll in this case, otherwise prefer
|
|
epoll. */
|
|
if (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode)
|
|
daemon->event_loop_syscall = MHD_ELS_POLL;
|
|
else
|
|
daemon->event_loop_syscall = MHD_ELS_EPOLL;
|
|
#elif defined(HAVE_POLL)
|
|
daemon->event_loop_syscall = MHD_ELS_POLL;
|
|
#else
|
|
daemon->event_loop_syscall = MHD_ELS_SELECT;
|
|
#endif
|
|
}
|
|
|
|
#ifdef EPOLL_SUPPORT
|
|
if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
|
|
(0 == daemon->worker_pool_size) &&
|
|
(MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_SYSCALL_THREAD_COMBINATION_INVALID,
|
|
_ (
|
|
"Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_EPOLL is not supported.\n"));
|
|
#endif
|
|
return MHD_SC_SYSCALL_THREAD_COMBINATION_INVALID;
|
|
}
|
|
#endif
|
|
|
|
/* Setup ITC */
|
|
if ( (! daemon->disable_itc) &&
|
|
(0 == daemon->worker_pool_size) )
|
|
{
|
|
if (! MHD_itc_init_ (daemon->itc))
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_ITC_INITIALIZATION_FAILED,
|
|
_ ("Failed to create inter-thread communication channel: %s\n"),
|
|
MHD_itc_last_strerror_ ());
|
|
#endif
|
|
return MHD_SC_ITC_INITIALIZATION_FAILED;
|
|
}
|
|
if ( (MHD_ELS_SELECT == daemon->event_loop_syscall) &&
|
|
(! MHD_SCKT_FD_FITS_FDSET_ (MHD_itc_r_fd_ (daemon->itc),
|
|
NULL)) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_ITC_DESCRIPTOR_TOO_LARGE,
|
|
_ (
|
|
"File descriptor for inter-thread communication channel exceeds maximum value.\n"));
|
|
#endif
|
|
return MHD_SC_ITC_DESCRIPTOR_TOO_LARGE;
|
|
}
|
|
}
|
|
|
|
if (MHD_SC_OK != (sc = open_listen_socket (daemon)))
|
|
return sc;
|
|
|
|
/* Check listen socket is in range (if we are limited) */
|
|
if ( (MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(MHD_ELS_SELECT == daemon->event_loop_syscall) &&
|
|
(! MHD_SCKT_FD_FITS_FDSET_ (daemon->listen_socket,
|
|
NULL)) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_SOCKET_TOO_LARGE,
|
|
_ ("Socket descriptor larger than FD_SETSIZE: %d > %d\n"),
|
|
daemon->listen_socket,
|
|
FD_SETSIZE);
|
|
#endif
|
|
return MHD_SC_LISTEN_SOCKET_TOO_LARGE;
|
|
}
|
|
|
|
/* set listen socket to non-blocking */
|
|
if ( (MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(! MHD_socket_nonblocking_ (daemon->listen_socket)) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE,
|
|
_ ("Failed to set nonblocking mode on listening socket: %s\n"),
|
|
MHD_socket_last_strerr_ ());
|
|
#endif
|
|
if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) ||
|
|
(daemon->worker_pool_size > 0) )
|
|
{
|
|
/* Accept must be non-blocking. Multiple children may wake
|
|
* up to handle a new connection, but only one will win the
|
|
* race. The others must immediately return. As this is
|
|
* not possible, we must fail hard here. */
|
|
return MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE;
|
|
}
|
|
}
|
|
|
|
#ifdef EPOLL_SUPPORT
|
|
/* Setup epoll */
|
|
if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
|
|
(0 == daemon->worker_pool_size) &&
|
|
(MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(MHD_SC_OK != (sc = setup_epoll_to_listen (daemon))) )
|
|
return sc;
|
|
#endif
|
|
|
|
/* Setup main listen thread (only if we have no thread pool or
|
|
external event loop and do have a listen socket) */
|
|
/* FIXME: why no worker thread if we have no listen socket? */
|
|
if ( ( (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode) ||
|
|
(1 == daemon->threading_mode) ) &&
|
|
(MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(! MHD_create_named_thread_ (&daemon->pid,
|
|
(MHD_TM_THREAD_PER_CONNECTION ==
|
|
daemon->threading_mode)
|
|
? "MHD-listen"
|
|
: "MHD-single",
|
|
daemon->thread_stack_limit_b,
|
|
&MHD_polling_thread,
|
|
daemon) ) )
|
|
{
|
|
#ifdef HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
MHD_SC_THREAD_MAIN_LAUNCH_FAILURE,
|
|
_ ("Failed to create listen thread: %s\n"),
|
|
MHD_strerror_ (errno));
|
|
#endif
|
|
return MHD_SC_THREAD_MAIN_LAUNCH_FAILURE;
|
|
}
|
|
|
|
/* Setup worker threads */
|
|
/* FIXME: why no thread pool if we have no listen socket? */
|
|
if ( (1 < daemon->threading_mode) &&
|
|
(MHD_INVALID_SOCKET != daemon->listen_socket) &&
|
|
(MHD_SC_OK != (sc = setup_thread_pool (daemon))) )
|
|
return sc;
|
|
|
|
/* make sure we know our listen port (if any) */
|
|
get_listen_port_number (daemon);
|
|
|
|
return MHD_SC_OK;
|
|
}
|
|
|
|
|
|
/* end of daemon_start.c */
|