stream-service: Avoid race condition when accepting sockets
Even if `poll()` indicates that the socket is ready it might block if
it's in blocking mode. This change avoids blocking in such cases (accept
will fail with EAGAIN/EWOULDBLOCK and `watch()` will return TRUE).
As the non-blocking mode is inherited on Windows (on Linux, the man page
documents the non-inheritance as a Linux specialty), we set the mode for
the accepted socket explicitly to blocking to match the expectations of
`stream_t`.
Fixes: daf1880b39 ("stream: add a stream service class abstracting services using BSD sockets")
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
typedef struct private_stream_service_t private_stream_service_t;
|
||||
|
||||
@@ -149,6 +150,22 @@ static void destroy_async_data(async_data_t *data)
|
||||
free(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable non-blocking mode on a socket.
|
||||
*/
|
||||
static bool set_blocking(int fd, bool block)
|
||||
{
|
||||
#ifdef WIN32
|
||||
u_long on = block ? 0 : 1;
|
||||
return ioctlsocket(fd, FIONBIO, &on) == 0;
|
||||
#else
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
return flags != -1 &&
|
||||
fcntl(fd, F_SETFL, block ? (flags & ~O_NONBLOCK)
|
||||
: (flags | O_NONBLOCK)) != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce running counter
|
||||
*/
|
||||
@@ -180,6 +197,14 @@ static job_requeue_t accept_async(async_data_t *data)
|
||||
this->running++;
|
||||
this->mutex->unlock(this->mutex);
|
||||
|
||||
/* accepted socket may inherit non-blocking mode from listening socket
|
||||
* depending on the implementation, so we normalize this to blocking mode */
|
||||
if (!set_blocking(data->fd, TRUE))
|
||||
{
|
||||
reduce_running(data);
|
||||
return JOB_REQUEUE_NONE;
|
||||
}
|
||||
|
||||
stream = stream_create_from_fd(data->fd);
|
||||
if (stream)
|
||||
{
|
||||
@@ -296,6 +321,12 @@ stream_service_t *stream_service_create_from_fd(int fd)
|
||||
{
|
||||
private_stream_service_t *this;
|
||||
|
||||
if (!set_blocking(fd, FALSE))
|
||||
{
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.on_accept = _on_accept,
|
||||
|
||||
@@ -76,10 +76,12 @@ struct stream_service_t {
|
||||
/**
|
||||
* Create a service from a file descriptor.
|
||||
*
|
||||
* The file descriptor MUST be a socket.
|
||||
* The file descriptor MUST be a socket. It is set to non-blocking mode to
|
||||
* safely accept() connections from the watcher thread. For consistency, the
|
||||
* accepted connection sockets are set to blocking mode on all platforms.
|
||||
*
|
||||
* @param fd file descriptor to wrap into a stream_service_t
|
||||
* @return stream_service instance
|
||||
* @param fd file descriptor to wrap (adopted, closed on failure)
|
||||
* @return service, NULL if fd can't be switched to non-blocking mode
|
||||
*/
|
||||
stream_service_t *stream_service_create_from_fd(int fd);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user