[hackers] [quark] Refactor server- and connection-logic into separate components || Laslo Hunhold

From: <git_AT_suckless.org>
Date: Mon, 15 Feb 2021 18:32:58 +0100 (CET)

commit e2463e733e4880c1d8034e3b90072825509ceb69
Author: Laslo Hunhold <dev_AT_frign.de>
AuthorDate: Mon Feb 15 18:25:33 2021 +0100
Commit: Laslo Hunhold <dev_AT_frign.de>
CommitDate: Mon Feb 15 18:25:33 2021 +0100

    Refactor server- and connection-logic into separate components
    
    The server-part (creating a pool, launching workers, etc.) and methods
    concerning the handling of connections (accepting, serving, logging,
    etc.) were all in main.c, making it unnecessarily complicated on top of
    the fact that there is already a lot to do there.
    
    Now, these parts are moved into server.h/server.c and connection.h/
    connection.c respectively, while main() now just only has to call
    into server_init_thread_pool(). In other words, main() now only has to
    deal with the argument parsing and preparations, and the rest is
    done by other program parts.
    
    The methods are prefixed with server_* and connection_* respectively,
    making it much easier to see where each method is defined in the
    program.
    
    This refactoring also separates concerns well. The server-struct is
    now in server.h (and not in util.h, where it was always out of place)
    and spacetok() is moved into util.c.
    
    Signed-off-by: Laslo Hunhold <dev_AT_frign.de>

diff --git a/Makefile b/Makefile
index da0e458..4922621 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -4,15 +4,17 @@
 
 include config.mk
 
-COMPONENTS = data http queue sock util
+COMPONENTS = connection data http queue server sock util
 
 all: quark
 
-data.o: data.c data.h http.h util.h config.mk
-http.o: http.c config.h http.h util.h config.mk
-main.o: main.c arg.h data.h http.h queue.h sock.h util.h config.mk
-sock.o: sock.c sock.h util.h config.mk
-util.o: util.c util.h config.mk
+connection.o: connection.c config.h connection.h data.h http.h server.h sock.h util.h config.mk
+data.o: data.c config.h data.h http.h server.h util.h config.mk
+http.o: http.c config.h http.h server.h util.h config.mk
+main.o: main.c arg.h config.h server.h sock.h util.h config.mk
+server.o: server.c config.h connection.h http.h queue.h server.h util.h config.mk
+sock.o: sock.c config.h sock.h util.h config.mk
+util.o: util.c config.h util.h config.mk
 
 quark: config.h $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
         $(CC) -o $_AT_ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)
diff --git a/connection.c b/connection.c
new file mode 100644
index 0000000..8aca2ab
--- /dev/null
+++ b/connection.c
_AT_@ -0,0 +1,314 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "connection.h"
+#include "data.h"
+#include "http.h"
+#include "server.h"
+#include "sock.h"
+#include "util.h"
+
+struct worker_data {
+ int insock;
+ size_t nslots;
+ const struct server *srv;
+};
+
+void
+connection_log(const struct connection *c)
+{
+ char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
+ char tstmp[21];
+
+ /* create timestamp */
+ if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
+ gmtime(&(time_t){time(NULL)}))) {
+ warn("strftime: Exceeded buffer capacity");
+ /* continue anyway (we accept the truncation) */
+ }
+
+ /* generate address-string */
+ if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
+ warn("sock_get_inaddr_str: Couldn't generate adress-string");
+ inaddr_str[0] = '\0';
+ }
+
+ printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n",
+ tstmp,
+ inaddr_str,
+ (c->res.status == 0) ? "dropped" : "",
+ (c->res.status == 0) ? 0 : 3,
+ c->res.status,
+ c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-",
+ c->req.path[0] ? c->req.path : "-",
+ c->req.query[0] ? "?" : "",
+ c->req.query,
+ c->req.fragment[0] ? "#" : "",
+ c->req.fragment);
+}
+
+void
+connection_reset(struct connection *c)
+{
+ if (c != NULL) {
+ shutdown(c->fd, SHUT_RDWR);
+ close(c->fd);
+ memset(c, 0, sizeof(*c));
+ }
+}
+
+void
+connection_serve(struct connection *c, const struct server *srv)
+{
+ enum status s;
+ int done;
+
+ switch (c->state) {
+ case C_VACANT:
+ /*
+ * we were passed a "fresh" connection which should now
+ * try to receive the header, reset buf beforehand
+ */
+ memset(&c->buf, 0, sizeof(c->buf));
+
+ c->state = C_RECV_HEADER;
+ /* fallthrough */
+ case C_RECV_HEADER:
+ /* receive header */
+ done = 0;
+ if ((s = http_recv_header(c->fd, &c->buf, &done))) {
+ http_prepare_error_response(&c->req, &c->res, s);
+ goto response;
+ }
+ if (!done) {
+ /* not done yet */
+ return;
+ }
+
+ /* parse header */
+ if ((s = http_parse_header(c->buf.data, &c->req))) {
+ http_prepare_error_response(&c->req, &c->res, s);
+ goto response;
+ }
+
+ /* prepare response struct */
+ http_prepare_response(&c->req, &c->res, srv);
+response:
+ /* generate response header */
+ if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
+ http_prepare_error_response(&c->req, &c->res, s);
+ if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
+ /* couldn't generate the header, we failed for good */
+ c->res.status = s;
+ goto err;
+ }
+ }
+
+ c->state = C_SEND_HEADER;
+ /* fallthrough */
+ case C_SEND_HEADER:
+ if ((s = http_send_buf(c->fd, &c->buf))) {
+ c->res.status = s;
+ goto err;
+ }
+ if (c->buf.len > 0) {
+ /* not done yet */
+ return;
+ }
+
+ c->state = C_SEND_BODY;
+ /* fallthrough */
+ case C_SEND_BODY:
+ if (c->req.method == M_GET) {
+ if (c->buf.len == 0) {
+ /* fill buffer with body data */
+ if ((s = data_fct[c->res.type](&c->res, &c->buf,
+ &c->progress))) {
+ /* too late to do any real error handling */
+ c->res.status = s;
+ goto err;
+ }
+
+ /* if the buffer remains empty, we are done */
+ if (c->buf.len == 0) {
+ break;
+ }
+ } else {
+ /* send buffer */
+ if ((s = http_send_buf(c->fd, &c->buf))) {
+ /* too late to do any real error handling */
+ c->res.status = s;
+ goto err;
+ }
+ }
+ return;
+ }
+ break;
+ default:
+ warn("serve: invalid connection state");
+ return;
+ }
+err:
+ connection_log(c);
+ connection_reset(c);
+}
+
+static struct connection *
+connection_get_drop_candidate(struct connection *connection, size_t nslots)
+{
+ struct connection *c, *minc;
+ size_t i, j, maxcnt, cnt;
+
+ /*
+ * determine the most-unimportant connection 'minc' of the in-address
+ * with most connections; this algorithm has a complexity of O(n²)
+ * in time but is O(1) in space; there are algorithms with O(n) in
+ * time and space, but this would require memory allocation,
+ * which we avoid. Given the simplicity of the inner loop and
+ * relatively small number of slots per thread, this is fine.
+ */
+ for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) {
+ /*
+ * we determine how many connections have the same
+ * in-address as connection[i], but also minimize over
+ * that set with other criteria, yielding a general
+ * minimizer c. We first set it to connection[i] and
+ * update it, if a better candidate shows up, in the inner
+ * loop
+ */
+ c = &connection[i];
+
+ for (j = 0, cnt = 0; j < nslots; j++) {
+ if (!sock_same_addr(&connection[i].ia,
+ &connection[j].ia)) {
+ continue;
+ }
+ cnt++;
+
+ /* minimize over state */
+ if (connection[j].state < c->state) {
+ c = &connection[j];
+ } else if (connection[j].state == c->state) {
+ /* minimize over progress */
+ if (c->state == C_SEND_BODY &&
+ connection[i].res.type != c->res.type) {
+ /*
+ * mixed response types; progress
+ * is not comparable
+ *
+ * the res-type-enum is ordered as
+ * DIRLISTING, ERROR, FILE, i.e.
+ * in rising priority, because a
+ * file transfer is most important,
+ * followed by error-messages.
+ * Dirlistings as an "interactive"
+ * feature (that take up lots of
+ * resources) have the lowest
+ * priority
+ */
+ if (connection[i].res.type <
+ c->res.type) {
+ c = &connection[j];
+ }
+ } else if (connection[j].progress <
+ c->progress) {
+ /*
+ * for C_SEND_BODY with same response
+ * type, C_RECV_HEADER and C_SEND_BODY
+ * it is sufficient to compare the
+ * raw progress
+ */
+ c = &connection[j];
+ }
+ }
+ }
+
+ if (cnt > maxcnt) {
+ /* this run yielded an even greedier in-address */
+ minc = c;
+ maxcnt = cnt;
+ }
+ }
+
+ return minc;
+}
+
+struct connection *
+connection_accept(int insock, struct connection *connection, size_t nslots)
+{
+ struct connection *c = NULL;
+ size_t i;
+
+ /* find vacant connection (i.e. one with no fd assigned to it) */
+ for (i = 0; i < nslots; i++) {
+ if (connection[i].fd == 0) {
+ c = &connection[i];
+ break;
+ }
+ }
+ if (i == nslots) {
+ /*
+ * all our connection-slots are occupied and the only
+ * way out is to drop another connection, because not
+ * accepting this connection just kicks this can further
+ * down the road (to the next queue_wait()) without
+ * solving anything.
+ *
+ * This may sound bad, but this case can only be hit
+ * either when there's a (D)DoS-attack or a massive
+ * influx of requests. The latter is impossible to solve
+ * at this moment without expanding resources, but the
+ * former has certain characteristics allowing us to
+ * handle this gracefully.
+ *
+ * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow
+ * Read or just plain flooding) we can not see who is
+ * waiting to be accept()ed.
+ * However, an attacker usually already has many
+ * connections open (while well-behaved clients could
+ * do everything with just one connection using
+ * keep-alive). Inferring a likely attacker-connection
+ * is an educated guess based on which in-address is
+ * occupying the most connection slots. Among those,
+ * connections in early stages (receiving or sending
+ * headers) are preferred over connections in late
+ * stages (sending body).
+ *
+ * This quantitative approach effectively drops malicious
+ * connections while preserving even long-running
+ * benevolent connections like downloads.
+ */
+ c = connection_get_drop_candidate(connection, nslots);
+ c->res.status = 0;
+ connection_log(c);
+ connection_reset(c);
+ }
+
+ /* accept connection */
+ if ((c->fd = accept(insock, (struct sockaddr *)&c->ia,
+ &(socklen_t){sizeof(c->ia)})) < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ /*
+ * this should not happen, as we received the
+ * event that there are pending connections here
+ */
+ warn("accept:");
+ }
+ return NULL;
+ }
+
+ /* set socket to non-blocking mode */
+ if (sock_set_nonblocking(c->fd)) {
+ /* we can't allow blocking sockets */
+ return NULL;
+ }
+
+ return c;
+}
diff --git a/connection.h b/connection.h
new file mode 100644
index 0000000..5b725c0
--- /dev/null
+++ b/connection.h
_AT_@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef CONNECTION_H
+#define CONNECTION_H
+
+#include "http.h"
+#include "server.h"
+#include "util.h"
+
+enum connection_state {
+ C_VACANT,
+ C_RECV_HEADER,
+ C_SEND_HEADER,
+ C_SEND_BODY,
+ NUM_CONN_STATES,
+};
+
+struct connection {
+ enum connection_state state;
+ int fd;
+ struct sockaddr_storage ia;
+ struct request req;
+ struct response res;
+ struct buffer buf;
+ size_t progress;
+};
+
+struct connection *connection_accept(int, struct connection *, size_t);
+void connection_log(const struct connection *);
+void connection_reset(struct connection *);
+void connection_serve(struct connection *, const struct server *);
+
+#endif /* CONNECTION_H */
diff --git a/http.h b/http.h
index 82e8e65..d08a3b4 100644
--- a/http.h
+++ b/http.h
_AT_@ -6,6 +6,7 @@
 #include <sys/socket.h>
 
 #include "config.h"
+#include "server.h"
 #include "util.h"
 
 enum req_field {
_AT_@ -84,24 +85,6 @@ struct response {
         } file;
 };
 
-enum conn_state {
- C_VACANT,
- C_RECV_HEADER,
- C_SEND_HEADER,
- C_SEND_BODY,
- NUM_CONN_STATES,
-};
-
-struct connection {
- enum conn_state state;
- int fd;
- struct sockaddr_storage ia;
- struct request req;
- struct response res;
- struct buffer buf;
- size_t progress;
-};
-
 enum status http_prepare_header_buf(const struct response *, struct buffer *);
 enum status http_send_buf(int, struct buffer *);
 enum status http_recv_header(int, struct buffer *, int *);
diff --git a/main.c b/main.c
index ed650c4..dac8eb6 100644
--- a/main.c
+++ b/main.c
_AT_@ -2,494 +2,31 @@
 #include <errno.h>
 #include <grp.h>
 #include <limits.h>
-#include <netinet/in.h>
-#include <pthread.h>
 #include <pwd.h>
 #include <regex.h>
 #include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/resource.h>
-#include <sys/socket.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
 #include <unistd.h>
 
 #include "arg.h"
-#include "data.h"
-#include "http.h"
-#include "queue.h"
+#include "server.h"
 #include "sock.h"
 #include "util.h"
 
 static char *udsname;
 
-static void
-logmsg(const struct connection *c)
-{
- char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
- char tstmp[21];
-
- /* create timestamp */
- if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
- gmtime(&(time_t){time(NULL)}))) {
- warn("strftime: Exceeded buffer capacity");
- /* continue anyway (we accept the truncation) */
- }
-
- /* generate address-string */
- if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
- warn("sock_get_inaddr_str: Couldn't generate adress-string");
- inaddr_str[0] = '\0';
- }
-
- printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n",
- tstmp,
- inaddr_str,
- (c->res.status == 0) ? "dropped" : "",
- (c->res.status == 0) ? 0 : 3,
- c->res.status,
- c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-",
- c->req.path[0] ? c->req.path : "-",
- c->req.query[0] ? "?" : "",
- c->req.query,
- c->req.fragment[0] ? "#" : "",
- c->req.fragment);
-}
-
-static void
-reset_connection(struct connection *c)
-{
- if (c != NULL) {
- shutdown(c->fd, SHUT_RDWR);
- close(c->fd);
- memset(c, 0, sizeof(*c));
- }
-}
-
-static void
-serve_connection(struct connection *c, const struct server *srv)
-{
- enum status s;
- int done;
-
- switch (c->state) {
- case C_VACANT:
- /*
- * we were passed a "fresh" connection which should now
- * try to receive the header, reset buf beforehand
- */
- memset(&c->buf, 0, sizeof(c->buf));
-
- c->state = C_RECV_HEADER;
- /* fallthrough */
- case C_RECV_HEADER:
- /* receive header */
- done = 0;
- if ((s = http_recv_header(c->fd, &c->buf, &done))) {
- http_prepare_error_response(&c->req, &c->res, s);
- goto response;
- }
- if (!done) {
- /* not done yet */
- return;
- }
-
- /* parse header */
- if ((s = http_parse_header(c->buf.data, &c->req))) {
- http_prepare_error_response(&c->req, &c->res, s);
- goto response;
- }
-
- /* prepare response struct */
- http_prepare_response(&c->req, &c->res, srv);
-response:
- /* generate response header */
- if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
- http_prepare_error_response(&c->req, &c->res, s);
- if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
- /* couldn't generate the header, we failed for good */
- c->res.status = s;
- goto err;
- }
- }
-
- c->state = C_SEND_HEADER;
- /* fallthrough */
- case C_SEND_HEADER:
- if ((s = http_send_buf(c->fd, &c->buf))) {
- c->res.status = s;
- goto err;
- }
- if (c->buf.len > 0) {
- /* not done yet */
- return;
- }
-
- c->state = C_SEND_BODY;
- /* fallthrough */
- case C_SEND_BODY:
- if (c->req.method == M_GET) {
- if (c->buf.len == 0) {
- /* fill buffer with body data */
- if ((s = data_fct[c->res.type](&c->res, &c->buf,
- &c->progress))) {
- /* too late to do any real error handling */
- c->res.status = s;
- goto err;
- }
-
- /* if the buffer remains empty, we are done */
- if (c->buf.len == 0) {
- break;
- }
- } else {
- /* send buffer */
- if ((s = http_send_buf(c->fd, &c->buf))) {
- /* too late to do any real error handling */
- c->res.status = s;
- goto err;
- }
- }
- return;
- }
- break;
- default:
- warn("serve: invalid connection state");
- return;
- }
-err:
- logmsg(c);
- reset_connection(c);
-}
-
-static struct connection *
-get_connection_drop_candidate(struct connection *connection, size_t nslots)
-{
- struct connection *c, *minc;
- size_t i, j, maxcnt, cnt;
-
- /*
- * determine the most-unimportant connection 'minc' of the in-address
- * with most connections; this algorithm has a complexity of O(n²)
- * in time but is O(1) in space; there are algorithms with O(n) in
- * time and space, but this would require memory allocation,
- * which we avoid. Given the simplicity of the inner loop and
- * relatively small number of slots per thread, this is fine.
- */
- for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) {
- /*
- * we determine how many connections have the same
- * in-address as connection[i], but also minimize over
- * that set with other criteria, yielding a general
- * minimizer c. We first set it to connection[i] and
- * update it, if a better candidate shows up, in the inner
- * loop
- */
- c = &connection[i];
-
- for (j = 0, cnt = 0; j < nslots; j++) {
- if (!sock_same_addr(&connection[i].ia,
- &connection[j].ia)) {
- continue;
- }
- cnt++;
-
- /* minimize over state */
- if (connection[j].state < c->state) {
- c = &connection[j];
- } else if (connection[j].state == c->state) {
- /* minimize over progress */
- if (c->state == C_SEND_BODY &&
- connection[i].res.type != c->res.type) {
- /*
- * mixed response types; progress
- * is not comparable
- *
- * the res-type-enum is ordered as
- * DIRLISTING, ERROR, FILE, i.e.
- * in rising priority, because a
- * file transfer is most important,
- * followed by error-messages.
- * Dirlistings as an "interactive"
- * feature (that take up lots of
- * resources) have the lowest
- * priority
- */
- if (connection[i].res.type <
- c->res.type) {
- c = &connection[j];
- }
- } else if (connection[j].progress <
- c->progress) {
- /*
- * for C_SEND_BODY with same response
- * type, C_RECV_HEADER and C_SEND_BODY
- * it is sufficient to compare the
- * raw progress
- */
- c = &connection[j];
- }
- }
- }
-
- if (cnt > maxcnt) {
- /* this run yielded an even greedier in-address */
- minc = c;
- maxcnt = cnt;
- }
- }
-
- return minc;
-}
-
-struct connection *
-accept_connection(int insock, struct connection *connection,
- size_t nslots)
-{
- struct connection *c = NULL;
- size_t i;
-
- /* find vacant connection (i.e. one with no fd assigned to it) */
- for (i = 0; i < nslots; i++) {
- if (connection[i].fd == 0) {
- c = &connection[i];
- break;
- }
- }
- if (i == nslots) {
- /*
- * all our connection-slots are occupied and the only
- * way out is to drop another connection, because not
- * accepting this connection just kicks this can further
- * down the road (to the next queue_wait()) without
- * solving anything.
- *
- * This may sound bad, but this case can only be hit
- * either when there's a (D)DoS-attack or a massive
- * influx of requests. The latter is impossible to solve
- * at this moment without expanding resources, but the
- * former has certain characteristics allowing us to
- * handle this gracefully.
- *
- * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow
- * Read or just plain flooding) we can not see who is
- * waiting to be accept()ed.
- * However, an attacker usually already has many
- * connections open (while well-behaved clients could
- * do everything with just one connection using
- * keep-alive). Inferring a likely attacker-connection
- * is an educated guess based on which in-address is
- * occupying the most connection slots. Among those,
- * connections in early stages (receiving or sending
- * headers) are preferred over connections in late
- * stages (sending body).
- *
- * This quantitative approach effectively drops malicious
- * connections while preserving even long-running
- * benevolent connections like downloads.
- */
- c = get_connection_drop_candidate(connection, nslots);
- c->res.status = 0;
- logmsg(c);
- reset_connection(c);
- }
-
- /* accept connection */
- if ((c->fd = accept(insock, (struct sockaddr *)&c->ia,
- &(socklen_t){sizeof(c->ia)})) < 0) {
- if (errno != EAGAIN && errno != EWOULDBLOCK) {
- /*
- * this should not happen, as we received the
- * event that there are pending connections here
- */
- warn("accept:");
- }
- return NULL;
- }
-
- /* set socket to non-blocking mode */
- if (sock_set_nonblocking(c->fd)) {
- /* we can't allow blocking sockets */
- return NULL;
- }
-
- return c;
-}
-
-struct worker_data {
- int insock;
- size_t nslots;
- const struct server *srv;
-};
-
-static void *
-thread_method(void *data)
-{
- queue_event *event = NULL;
- struct connection *connection, *c, *newc;
- struct worker_data *d = (struct worker_data *)data;
- int qfd;
- ssize_t nready;
- size_t i;
-
- /* allocate connections */
- if (!(connection = calloc(d->nslots, sizeof(*connection)))) {
- die("calloc:");
- }
-
- /* create event queue */
- if ((qfd = queue_create()) < 0) {
- exit(1);
- }
-
- /* add insock to the interest list (with data=NULL) */
- if (queue_add_fd(qfd, d->insock, QUEUE_EVENT_IN, 1, NULL) < 0) {
- exit(1);
- }
-
- /* allocate event array */
- if (!(event = reallocarray(event, d->nslots, sizeof(*event)))) {
- die("reallocarray:");
- }
-
- for (;;) {
- /* wait for new activity */
- if ((nready = queue_wait(qfd, event, d->nslots)) < 0) {
- exit(1);
- }
-
- /* handle events */
- for (i = 0; i < (size_t)nready; i++) {
- c = queue_event_get_data(&event[i]);
-
- if (queue_event_is_error(&event[i])) {
- if (c != NULL) {
- queue_rem_fd(qfd, c->fd);
- c->res.status = 0;
- logmsg(c);
- reset_connection(c);
- }
-
- continue;
- }
-
- if (c == NULL) {
- /* add new connection to the interest list */
- if (!(newc = accept_connection(d->insock,
- connection,
- d->nslots))) {
- /*
- * the socket is either blocking
- * or something failed.
- * In both cases, we just carry on
- */
- continue;
- }
-
- /*
- * add event to the interest list
- * (we want IN, because we start
- * with receiving the header)
- */
- if (queue_add_fd(qfd, newc->fd,
- QUEUE_EVENT_IN,
- 0, newc) < 0) {
- /* not much we can do here */
- continue;
- }
- } else {
- /* serve existing connection */
- serve_connection(c, d->srv);
-
- if (c->fd == 0) {
- /* we are done */
- memset(c, 0, sizeof(struct connection));
- continue;
- }
-
- /*
- * rearm the event based on the state
- * we are "stuck" at
- */
- switch(c->state) {
- case C_RECV_HEADER:
- if (queue_mod_fd(qfd, c->fd,
- QUEUE_EVENT_IN,
- c) < 0) {
- reset_connection(c);
- break;
- }
- break;
- case C_SEND_HEADER:
- case C_SEND_BODY:
- if (queue_mod_fd(qfd, c->fd,
- QUEUE_EVENT_OUT,
- c) < 0) {
- reset_connection(c);
- break;
- }
- break;
- default:
- break;
- }
- }
- }
- }
-
- return NULL;
-}
-
-static void
-handle_connections(int *insock, size_t nthreads, size_t nslots,
- const struct server *srv)
-{
- pthread_t *thread = NULL;
- struct worker_data *d = NULL;
- size_t i;
-
- /* allocate worker_data structs */
- if (!(d = reallocarray(d, nthreads, sizeof(*d)))) {
- die("reallocarray:");
- }
- for (i = 0; i < nthreads; i++) {
- d[i].insock = insock[i];
- d[i].nslots = nslots;
- d[i].srv = srv;
- }
-
- /* allocate and initialize thread pool */
- if (!(thread = reallocarray(thread, nthreads, sizeof(*thread)))) {
- die("reallocarray:");
- }
- for (i = 0; i < nthreads; i++) {
- if (pthread_create(&thread[i], NULL, thread_method, &d[i]) != 0) {
- if (errno == EAGAIN) {
- die("You need to run as root or have "
- "CAP_SYS_RESOURCE set, or are trying "
- "to create more threads than the "
- "system can offer");
- } else {
- die("pthread_create:");
- }
- }
- }
-
- /* wait for threads */
- for (i = 0; i < nthreads; i++) {
- if ((errno = pthread_join(thread[i], NULL))) {
- warn("pthread_join:");
- }
- }
-}
-
 static void
 cleanup(void)
 {
- if (udsname)
- sock_rem_uds(udsname);
+ if (udsname) {
+ sock_rem_uds(udsname);
+ }
 }
 
 static void
_AT_@ -514,77 +51,6 @@ handlesignals(void(*hdl)(int))
         sigaction(SIGQUIT, &sa, NULL);
 }
 
-static int
-spacetok(const char *s, char **t, size_t tlen)
-{
- const char *tok;
- size_t i, j, toki, spaces;
-
- /* fill token-array with NULL-pointers */
- for (i = 0; i < tlen; i++) {
- t[i] = NULL;
- }
- toki = 0;
-
- /* don't allow NULL string or leading spaces */
- if (!s || *s == ' ') {
- return 1;
- }
-start:
- /* skip spaces */
- for (; *s == ' '; s++)
- ;
-
- /* don't allow trailing spaces */
- if (*s == '\0') {
- goto err;
- }
-
- /* consume token */
- for (tok = s, spaces = 0; ; s++) {
- if (*s == '\\' && *(s + 1) == ' ') {
- spaces++;
- s++;
- continue;
- } else if (*s == ' ') {
- /* end of token */
- goto token;
- } else if (*s == '\0') {
- /* end of string */
- goto token;
- }
- }
-token:
- if (toki >= tlen) {
- goto err;
- }
- if (!(t[toki] = malloc(s - tok - spaces + 1))) {
- die("malloc:");
- }
- for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) {
- if (tok[i] == '\\' && tok[i + 1] == ' ') {
- i++;
- }
- t[toki][j] = tok[i];
- }
- t[toki][s - tok - spaces] = '\0';
- toki++;
-
- if (*s == ' ') {
- s++;
- goto start;
- }
-
- return 0;
-err:
- for (i = 0; i < tlen; i++) {
- free(t[i]);
- t[i] = NULL;
- }
-
- return 1;
-}
-
 static void
 usage(void)
 {
_AT_@ -864,7 +330,7 @@ main(int argc, char *argv[])
                 }
 
                 /* accept incoming connections */
- handle_connections(insock, nthreads, nslots, &srv);
+ server_init_thread_pool(insock, nthreads, nslots, &srv);
 
                 exit(0);
         default:
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..9bede9c
--- /dev/null
+++ b/server.c
_AT_@ -0,0 +1,177 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "connection.h"
+#include "queue.h"
+#include "server.h"
+#include "util.h"
+
+struct worker_data {
+ int insock;
+ size_t nslots;
+ const struct server *srv;
+};
+
+static void *
+server_worker(void *data)
+{
+ queue_event *event = NULL;
+ struct connection *connection, *c, *newc;
+ struct worker_data *d = (struct worker_data *)data;
+ int qfd;
+ ssize_t nready;
+ size_t i;
+
+ /* allocate connections */
+ if (!(connection = calloc(d->nslots, sizeof(*connection)))) {
+ die("calloc:");
+ }
+
+ /* create event queue */
+ if ((qfd = queue_create()) < 0) {
+ exit(1);
+ }
+
+ /* add insock to the interest list (with data=NULL) */
+ if (queue_add_fd(qfd, d->insock, QUEUE_EVENT_IN, 1, NULL) < 0) {
+ exit(1);
+ }
+
+ /* allocate event array */
+ if (!(event = reallocarray(event, d->nslots, sizeof(*event)))) {
+ die("reallocarray:");
+ }
+
+ for (;;) {
+ /* wait for new activity */
+ if ((nready = queue_wait(qfd, event, d->nslots)) < 0) {
+ exit(1);
+ }
+
+ /* handle events */
+ for (i = 0; i < (size_t)nready; i++) {
+ c = queue_event_get_data(&event[i]);
+
+ if (queue_event_is_error(&event[i])) {
+ if (c != NULL) {
+ queue_rem_fd(qfd, c->fd);
+ c->res.status = 0;
+ connection_log(c);
+ connection_reset(c);
+ }
+
+ continue;
+ }
+
+ if (c == NULL) {
+ /* add new connection to the interest list */
+ if (!(newc = connection_accept(d->insock,
+ connection,
+ d->nslots))) {
+ /*
+ * the socket is either blocking
+ * or something failed.
+ * In both cases, we just carry on
+ */
+ continue;
+ }
+
+ /*
+ * add event to the interest list
+ * (we want IN, because we start
+ * with receiving the header)
+ */
+ if (queue_add_fd(qfd, newc->fd,
+ QUEUE_EVENT_IN,
+ 0, newc) < 0) {
+ /* not much we can do here */
+ continue;
+ }
+ } else {
+ /* serve existing connection */
+ connection_serve(c, d->srv);
+
+ if (c->fd == 0) {
+ /* we are done */
+ memset(c, 0, sizeof(struct connection));
+ continue;
+ }
+
+ /*
+ * rearm the event based on the state
+ * we are "stuck" at
+ */
+ switch(c->state) {
+ case C_RECV_HEADER:
+ if (queue_mod_fd(qfd, c->fd,
+ QUEUE_EVENT_IN,
+ c) < 0) {
+ connection_reset(c);
+ break;
+ }
+ break;
+ case C_SEND_HEADER:
+ case C_SEND_BODY:
+ if (queue_mod_fd(qfd, c->fd,
+ QUEUE_EVENT_OUT,
+ c) < 0) {
+ connection_reset(c);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void
+server_init_thread_pool(int *insock, size_t nthreads, size_t nslots,
+ const struct server *srv)
+{
+ pthread_t *thread = NULL;
+ struct worker_data *d = NULL;
+ size_t i;
+
+ /* allocate worker_data structs */
+ if (!(d = reallocarray(d, nthreads, sizeof(*d)))) {
+ die("reallocarray:");
+ }
+ for (i = 0; i < nthreads; i++) {
+ d[i].insock = insock[i];
+ d[i].nslots = nslots;
+ d[i].srv = srv;
+ }
+
+ /* allocate and initialize thread pool */
+ if (!(thread = reallocarray(thread, nthreads, sizeof(*thread)))) {
+ die("reallocarray:");
+ }
+ for (i = 0; i < nthreads; i++) {
+ if (pthread_create(&thread[i], NULL, server_worker, &d[i]) != 0) {
+ if (errno == EAGAIN) {
+ die("You need to run as root or have "
+ "CAP_SYS_RESOURCE set, or are trying "
+ "to create more threads than the "
+ "system can offer");
+ } else {
+ die("pthread_create:");
+ }
+ }
+ }
+
+ /* wait for threads */
+ for (i = 0; i < nthreads; i++) {
+ if ((errno = pthread_join(thread[i], NULL))) {
+ warn("pthread_join:");
+ }
+ }
+}
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..39232f8
--- /dev/null
+++ b/server.h
_AT_@ -0,0 +1,35 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <regex.h>
+#include <stddef.h>
+
+struct vhost {
+ char *chost;
+ char *regex;
+ char *dir;
+ char *prefix;
+ regex_t re;
+};
+
+struct map {
+ char *chost;
+ char *from;
+ char *to;
+};
+
+struct server {
+ char *host;
+ char *port;
+ char *docindex;
+ int listdirs;
+ struct vhost *vhost;
+ size_t vhost_len;
+ struct map *map;
+ size_t map_len;
+};
+
+void server_init_thread_pool(int *, size_t, size_t, const struct server *);
+
+#endif /* SERVER_H */
diff --git a/util.c b/util.c
index 764615b..f10e546 100644
--- a/util.c
+++ b/util.c
_AT_@ -123,6 +123,79 @@ prepend(char *str, size_t size, const char *prefix)
         return 0;
 }
 
+int
+spacetok(const char *s, char **t, size_t tlen)
+{
+ const char *tok;
+ size_t i, j, toki, spaces;
+
+ /* fill token-array with NULL-pointers */
+ for (i = 0; i < tlen; i++) {
+ t[i] = NULL;
+ }
+ toki = 0;
+
+ /* don't allow NULL string or leading spaces */
+ if (!s || *s == ' ') {
+ return 1;
+ }
+start:
+ /* skip spaces */
+ for (; *s == ' '; s++)
+ ;
+
+ /* don't allow trailing spaces */
+ if (*s == '\0') {
+ goto err;
+ }
+
+ /* consume token */
+ for (tok = s, spaces = 0; ; s++) {
+ if (*s == '\\' && *(s + 1) == ' ') {
+ spaces++;
+ s++;
+ continue;
+ } else if (*s == ' ') {
+ /* end of token */
+ goto token;
+ } else if (*s == '\0') {
+ /* end of string */
+ goto token;
+ }
+ }
+token:
+ if (toki >= tlen) {
+ goto err;
+ }
+ if (!(t[toki] = malloc(s - tok - spaces + 1))) {
+ die("malloc:");
+ }
+ for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) {
+ if (tok[i] == '\\' && tok[i + 1] == ' ') {
+ i++;
+ }
+ t[toki][j] = tok[i];
+ }
+ t[toki][s - tok - spaces] = '\0';
+ toki++;
+
+ if (*s == ' ') {
+ s++;
+ goto start;
+ }
+
+ return 0;
+err:
+ for (i = 0; i < tlen; i++) {
+ free(t[i]);
+ t[i] = NULL;
+ }
+
+ return 1;
+}
+
+
+
 #define INVALID 1
 #define TOOSMALL 2
 #define TOOLARGE 3
diff --git a/util.h b/util.h
index 983abd2..f327c13 100644
--- a/util.h
+++ b/util.h
_AT_@ -8,32 +8,6 @@
 
 #include "config.h"
 
-/* main server struct */
-struct vhost {
- char *chost;
- char *regex;
- char *dir;
- char *prefix;
- regex_t re;
-};
-
-struct map {
- char *chost;
- char *from;
- char *to;
-};
-
-struct server {
- char *host;
- char *port;
- char *docindex;
- int listdirs;
- struct vhost *vhost;
- size_t vhost_len;
- struct map *map;
- size_t map_len;
-};
-
 /* general purpose buffer */
 struct buffer {
         char data[BUFFER_SIZE];
_AT_@ -58,6 +32,7 @@ void eunveil(const char *, const char *);
 int timestamp(char *, size_t, time_t);
 int esnprintf(char *, size_t, const char *, ...);
 int prepend(char *, size_t, const char *);
+int spacetok(const char *, char **, size_t);
 
 void *reallocarray(void *, size_t, size_t);
 long long strtonum(const char *, long long, long long, const char **);
Received on Mon Feb 15 2021 - 18:32:58 CET

This archive was generated by hypermail 2.3.0 : Mon Feb 15 2021 - 18:36:59 CET