Fix "broken pipe" problem for UDP inetd services

This patch fixes several issues related to the problem of launching
UDP inetd services:

- Make sure to disable O_NONBLOCK on the socket before passing it to
  the inetd service, that's what is expected.  This goes for both UDP
  and TCP (accepted) connections -- there is no guarantee even for TCP
  sockets that they do *not* inherit the parent socket's flags.
- Mark the UDP inetd parent's SVC as busy and stop its watcher before
  starting the child task to service the connection.  The watcher must
  be stopped or multiple child tasks may be spawned!
- Only copy the most relevant parts to the UDP task.  In particular
  must *not* copy the libuEv watcher data to the task!
- With the child task done and the parent inetd service unblocked we
  must restore O_NONBLOCK to the socket before restarting the watcher

When a UDP inetd service connection is handled we create a child task
to service the request, meanwhile we block the parent service.  There
was a problem with blocking the parent since we called inetd_stop(),
which both did a shutdown() and close() of the socket ... this patch
introduces separate handling of blocked (busy) inetd services in the
inetd_stop() function and also make sure to handle restarting the
parent watcher in inetd_start() when the child task is done.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson
2016-04-24 12:01:10 +02:00
parent 7e91cb4afc
commit efd265c9c8
+47 -12
View File
@@ -150,6 +150,15 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
return;
}
/*
* Make sure to disable O_NONBLOCK on the descriptor before
* passing it to the inetd service, that's what is expected.
*/
if (fcntl(stdin, F_SETFL, fcntl(stdin, F_GETFL, 0) & ~O_NONBLOCK) < 0) {
FLOG_ERROR("Failed disabling non-blocking on %s socket", svc->cmd);
return;
}
task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN);
if (!task) {
FLOG_ERROR("%s: Unable to allocate service for inetd client",
@@ -157,9 +166,19 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
return;
}
/* Copy inherited attributes from inetd */
if (!svc->inetd.forking) {
svc->block = SVC_BLOCK_INETD_BUSY;
service_step(svc);
}
/* Copy inherited attributes from inetd service's svc */
task->runlevels = svc->runlevels;
task->inetd = svc->inetd;
/* Only copy the most relevant parts of inetd, in particular we
* must *not* copy the watcher data to the clone! */
task->inetd.svc = svc;
task->inetd.type = svc->inetd.type;
memcpy(task->cond, svc->cond, sizeof(task->cond));
memcpy(task->username, svc->username, sizeof(task->username));
memcpy(task->group, svc->group, sizeof(task->group));
@@ -168,11 +187,6 @@ static void socket_cb(uev_t *w, void *arg, int UNUSED(events))
task->stdin = stdin;
service_step(task);
if (!svc->inetd.forking) {
svc->block = SVC_BLOCK_INETD_BUSY;
service_step(svc);
}
}
/* Launch Inet socket for service.
@@ -231,19 +245,40 @@ static int spawn_socket(inetd_t *inetd)
int inetd_start(inetd_t *inetd)
{
if (inetd->watcher.fd == -1)
int sd;
char buf[BUFSIZ];
ssize_t len;
sd = inetd->watcher.fd;
if (sd == -1)
return spawn_socket(inetd);
return -EEXIST;
/* Read anything lingering, or clean up socket after failure */
len = recv(sd, buf, sizeof(buf), MSG_DONTWAIT);
_d("Read %d lingering bytes from socket before restarting %s ...", len, inetd->svc->cmd);
/* Restore O_NONBLOCK for socket */
fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK);
_d("Re-starting %s socket watcher ...", inetd->svc->cmd);
uev_io_start(&inetd->watcher);
return 0;
}
void inetd_stop(inetd_t *inetd)
{
if (inetd->watcher.fd != -1) {
_d("Stopping %s socket watcher ...", inetd->svc->cmd);
uev_io_stop(&inetd->watcher);
shutdown(inetd->watcher.fd, SHUT_RDWR);
close(inetd->watcher.fd);
inetd->watcher.fd = -1;
/* For dgram inetd services we block the parent SVC
* and halt the watcher, so don't close the socket! */
if (inetd->svc->block != SVC_BLOCK_INETD_BUSY) {
shutdown(inetd->watcher.fd, SHUT_RDWR);
close(inetd->watcher.fd);
inetd->watcher.fd = -1;
}
}
}