From 1812f5541a1fffb720180f958cb36ae7a8fcab3d Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Fri, 29 Apr 2016 00:11:45 -0400 Subject: [PATCH] [core] lighttpd -1 handles single request on stdin socket (fixes #1584) (e.g. when called from xinetd) Note: lighttpd is designed as a high performance, long-running server, not a one-shot executable. This one-shot mode of operation has not been tuned for performance. lighttpd server start-up and initialization aims for correctness, not speed. If using this one-shot mode as part of fork and exec from xinetd, then performance is already not of high concern. x-ref: "support for xinetd" https://redmine.lighttpd.net/issues/1584 --- doc/lighttpd.8 | 3 ++ src/connections.c | 8 +++- src/connections.h | 1 + src/network.c | 10 +++++ src/server.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 151 insertions(+), 3 deletions(-) diff --git a/doc/lighttpd.8 b/doc/lighttpd.8 index 5c4211b2..e2eb10c6 100644 --- a/doc/lighttpd.8 +++ b/doc/lighttpd.8 @@ -42,6 +42,9 @@ Do not daemonize (go into background). The default is to daemonize. \fB\-i\ \fP \fIsecs\fP Trigger daemon graceful shutdown after \fIsecs\fP of inactivity. .TP 8 +\fB\-1\fP +Process single (one) request on stdin socket, then exit. +.TP 8 \fB\-v\fP Show version and exit. .TP 8 diff --git a/src/connections.c b/src/connections.c index d9aaaebf..7d5072b0 100644 --- a/src/connections.c +++ b/src/connections.c @@ -894,6 +894,11 @@ connection *connection_accept(server *srv, server_socket *srv_socket) { } return NULL; } else { + return connection_accepted(srv, srv_socket, &cnt_addr, cnt); + } +} + +connection *connection_accepted(server *srv, server_socket *srv_socket, sock_addr *cnt_addr, int cnt) { connection *con; srv->cur_fds++; @@ -917,7 +922,7 @@ connection *connection_accept(server *srv, server_socket *srv_socket) { connection_set_state(srv, con, CON_STATE_REQUEST_START); con->connection_start = srv->cur_ts; - con->dst_addr = cnt_addr; + con->dst_addr = *cnt_addr; buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr))); con->srv_socket = srv_socket; @@ -947,7 +952,6 @@ connection *connection_accept(server *srv, server_socket *srv_socket) { } #endif return con; - } } diff --git a/src/connections.h b/src/connections.h index 92cdc618..7a42b955 100644 --- a/src/connections.h +++ b/src/connections.h @@ -10,6 +10,7 @@ int connection_reset(server *srv, connection *con); void connections_free(server *srv); connection * connection_accept(server *srv, server_socket *srv_sock); +connection * connection_accepted(server *srv, server_socket *srv_socket, sock_addr *cnt_addr, int cnt); int connection_set_state(server *srv, connection *con, connection_state_t state); const char * connection_get_state(connection_state_t state); diff --git a/src/network.c b/src/network.c index 2c5c8fb5..5b64cdc0 100644 --- a/src/network.c +++ b/src/network.c @@ -334,6 +334,13 @@ static int network_server_init(server *srv, buffer *host_token, specific_config goto error_free_socket; } + if (srv->sockets_disabled) { /* lighttpd -1 (one-shot mode) */ +#ifdef USE_OPENSSL + if (s->ssl_enabled) srv_socket->ssl_ctx = s->ssl_ctx; +#endif + goto srv_sockets_append; + } + #ifdef HAVE_SYS_UN_H if (AF_UNIX == srv_socket->addr.plain.sa_family) { /* check if the socket exists and try to connect to it. */ @@ -455,6 +462,7 @@ static int network_server_init(server *srv, buffer *host_token, specific_config #endif } +srv_sockets_append: srv_socket->is_ssl = s->ssl_enabled; if (srv->srv_sockets.size == 0) { @@ -1026,6 +1034,8 @@ int network_register_fdevents(server *srv) { return -1; } + if (srv->sockets_disabled) return 0; /* lighttpd -1 (one-shot mode) */ + /* register fdevents after reset */ for (i = 0; i < srv->srv_sockets.used; i++) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; diff --git a/src/server.c b/src/server.c index 42e0ef04..1119efd0 100644 --- a/src/server.c +++ b/src/server.c @@ -424,6 +424,110 @@ static void remove_pid_file(server *srv, int *pid_fd) { } } + +static server_socket * server_oneshot_getsock(server *srv, sock_addr *cnt_addr) { + server_socket *srv_socket, *srv_socket_wild = NULL; + size_t i; + for (i = 0; i < srv->srv_sockets.used; ++i) { + srv_socket = srv->srv_sockets.ptr[i]; + if (cnt_addr->plain.sa_family != srv_socket->addr.plain.sa_family) continue; + switch (cnt_addr->plain.sa_family) { + case AF_INET: + if (srv_socket->addr.ipv4.sin_port != cnt_addr->ipv4.sin_port) continue; + if (srv_socket->addr.ipv4.sin_addr.s_addr == cnt_addr->ipv4.sin_addr.s_addr) { + return srv_socket; + } + if (srv_socket->addr.ipv4.sin_addr.s_addr == htonl(INADDR_ANY)) { + srv_socket_wild = srv_socket; + } + continue; + #ifdef HAVE_IPV6 + case AF_INET6: + if (srv_socket->addr.ipv6.sin6_port != cnt_addr->ipv6.sin6_port) continue; + if (0 == memcmp(&srv_socket->addr.ipv6.sin6_addr, &cnt_addr->ipv6.sin6_addr, sizeof(struct in6_addr))) { + return srv_socket; + } + if (0 == memcmp(&srv_socket->addr.ipv6.sin6_addr, &in6addr_any, sizeof(struct in6_addr))) { + srv_socket_wild = srv_socket; + } + continue; + #endif + #ifdef HAVE_SYS_UN_H + case AF_UNIX: + if (0 == strcmp(srv_socket->addr.un.sun_path, cnt_addr->un.sun_path)) { + return srv_socket; + } + continue; + #endif + default: continue; + } + } + + if (NULL != srv_socket_wild) { + return srv_socket_wild; + } else if (srv->srv_sockets.used) { + return srv->srv_sockets.ptr[0]; + } else { + log_error_write(srv, __FILE__, __LINE__, "s", "no sockets configured"); + return NULL; + } +} + + +static int server_oneshot_init(server *srv, int fd) { + /* Note: does not work with netcat due to requirement that fd be socket. + * STDOUT_FILENO was not saved earlier in startup, and that is to where + * netcat expects output to be sent. Since lighttpd expects connections + * to be sockets, con->fd is where output is sent; separate fds are not + * stored for input and output, but netcat has different fds for stdin + * and * stdout. To support netcat, would additionally need to avoid + * S_ISSOCK(), getsockname(), and getpeername() below, reconstructing + * addresses from environment variables: + * NCAT_LOCAL_ADDR NCAT_LOCAL_PORT + * NCAT_REMOTE_ADDR NCAT_REMOTE_PORT + * NCAT_PROTO + */ + connection *con; + server_socket *srv_socket; + sock_addr cnt_addr; + socklen_t cnt_len; + struct stat st; + + if (0 != fstat(fd, &st)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fstat:", strerror(errno)); + return 0; + } + + if (!S_ISSOCK(st.st_mode)) { + /* require that fd is a socket + * (modules might expect STDIN_FILENO and STDOUT_FILENO opened to /dev/null) */ + log_error_write(srv, __FILE__, __LINE__, "s", "lighttpd -1 stdin is not a socket"); + return 0; + } + + cnt_len = sizeof(cnt_addr); + if (0 != getsockname(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "getsockname:", strerror(errno)); + return 0; + } + + srv_socket = server_oneshot_getsock(srv, &cnt_addr); + if (NULL == srv_socket) return 0; + + cnt_len = sizeof(cnt_addr); + if (0 != getpeername(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) { + log_error_write(srv, __FILE__, __LINE__, "ss", "getpeername:", strerror(errno)); + return 0; + } + + con = connection_accepted(srv, srv_socket, &cnt_addr, fd); + if (NULL == con) return 0; + + connection_state_machine(srv, con); + return 1; +} + + static void show_version (void) { #ifdef USE_OPENSSL # define TEXT_SSL " (ssl)" @@ -600,6 +704,7 @@ static void show_help (void) { " -f filename of the config-file\n" \ " -m module directory (default: "LIBRARY_DIR")\n" \ " -i graceful shutdown after of inactivity\n" \ +" -1 process single (one) request on stdin socket, then exit\n" \ " -p print the parsed config-file in internal form, and exit\n" \ " -t test the config-file, and exit\n" \ " -D don't go to background (default: go to background)\n" \ @@ -633,6 +738,7 @@ int main (int argc, char **argv) { #ifdef HAVE_FORK int parent_pipe_fd = -1; #endif + int oneshot_fd = 0; #ifdef USE_ALARM struct itimerval interval; @@ -662,7 +768,7 @@ int main (int argc, char **argv) { srv->srvconf.dont_daemonize = 0; srv->srvconf.preflight_check = 0; - while(-1 != (o = getopt(argc, argv, "f:m:i:hvVDpt"))) { + while(-1 != (o = getopt(argc, argv, "f:m:i:hvVD1pt"))) { switch(o) { case 'f': if (srv->config_storage) { @@ -694,6 +800,7 @@ int main (int argc, char **argv) { } case 'p': print_config = 1; break; case 't': ++test_config; break; + case '1': oneshot_fd = dup(STDIN_FILENO); break; case 'D': srv->srvconf.dont_daemonize = 1; break; case 'v': show_version(); server_free(srv); return 0; case 'V': show_features(); server_free(srv); return 0; @@ -740,6 +847,24 @@ int main (int argc, char **argv) { return 0; } + if (oneshot_fd) { + if (oneshot_fd <= STDERR_FILENO) { + log_error_write(srv, __FILE__, __LINE__, "s", + "Invalid fds at startup with lighttpd -1"); + server_free(srv); + return -1; + } + graceful_shutdown = 1; + srv->sockets_disabled = 1; + srv->srvconf.dont_daemonize = 1; + buffer_reset(srv->srvconf.pid_file); + if (srv->srvconf.max_worker) { + srv->srvconf.max_worker = 0; + log_error_write(srv, __FILE__, __LINE__, "s", + "server one-shot command line option disables server.max-worker config file option."); + } + } + /* close stdin and stdout, as they are not needed */ openDevNull(STDIN_FILENO); openDevNull(STDOUT_FILENO); @@ -1344,12 +1469,17 @@ int main (int argc, char **argv) { for (i = 0; i < srv->srv_sockets.used; i++) { server_socket *srv_socket = srv->srv_sockets.ptr[i]; + if (srv->sockets_disabled) continue; /* lighttpd -1 (one-shot mode) */ if (-1 == fdevent_fcntl_set(srv->ev, srv_socket->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed:", strerror(errno)); return -1; } } + if (oneshot_fd && !server_oneshot_init(srv, oneshot_fd)) { + close(oneshot_fd); + } + /* main-loop */ while (!srv_shutdown) { int n; -- 2.11.4.GIT