Re: [hackers] [quark] Use epoll/kqueue and worker threads to handle connections || Laslo Hunhold

2021-01-17 Thread Laslo Hunhold
On Sun, 17 Jan 2021 12:48:38 +0100
Hiltjo Posthuma  wrote:

Dear Hiltjo,

> This does not work on OpenBSD and it does not compile.

thanks for letting me know! I didn't come around to testing it on
OpenBSD yet, but did it now and pushed a fix[0].

With best regards

Laslo

[0]:https://git.suckless.org/quark/commit/959c855734e3af12f35532d76deb1ab85474f8f4.html



Re: [hackers] [quark] Use epoll/kqueue and worker threads to handle connections || Laslo Hunhold

2021-01-17 Thread Hiltjo Posthuma
On Sun, Nov 01, 2020 at 12:34:52AM +0100, g...@suckless.org wrote:
> commit dff98c0bcaef7be220c563ebaebd66f8c6704197
> Author: Laslo Hunhold 
> AuthorDate: Sun Nov 1 00:27:46 2020 +0100
> Commit: Laslo Hunhold 
> CommitDate: Sun Nov 1 00:27:46 2020 +0100
> 
> Use epoll/kqueue and worker threads to handle connections
> 
> This adds quite a bit of code, but is the culmination of the previous
> restructurizations. Each worker thread has a connection pool and the
> interesting part is that it's 100% nonblocking. If reading or writing
> blocks at any point, the worker thread can just drop it and continue
> with something else. This is especially powerful against attacks like
> slow loris, which cannot be caught with a forking-model and could easily
> be used in a DoS against a quark instance.
> 
> There are no memory allocations at runtime, unless you use dirlistings,
> whose libc-allocations you can't work around.
> 
> In case the connection pool is exhausted due to a lot of slow lorises,
> we still hit a DoS, but at least it can now be possible to assess the
> connection pool and just drop another connection that can be
> heuristically assessed as a "malicious" one (e.g. many connections from
> one client, long time in one state or something using a monotonic
> clock).
> 
> Given we still sadly don't have kqueue in linux, which is 1000x times
> better than epoll, which is deeply flawed, I wrote a very thin wrapper
> in queue.{c,h} which exposes the necessary functions in a common
> interface.
> 
> Signed-off-by: Laslo Hunhold 
> 
> diff --git a/Makefile b/Makefile
> index 548e6aa..da0e458 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -4,13 +4,13 @@
>  
>  include config.mk
>  
> -COMPONENTS = data http sock util
> +COMPONENTS = data http queue 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 sock.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
>  
> diff --git a/config.mk b/config.mk
> index 7056241..cedb3e7 100644
> --- a/config.mk
> +++ b/config.mk
> @@ -10,7 +10,7 @@ MANPREFIX = $(PREFIX)/share/man
>  # flags
>  CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 
> -D_BSD_SOURCE
>  CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os
> -LDFLAGS  = -s
> +LDFLAGS  = -lpthread -s
>  
>  # compiler and linker
>  CC = cc
> diff --git a/main.c b/main.c
> index d64774b..e26cc77 100644
> --- a/main.c
> +++ b/main.c
> @@ -3,6 +3,7 @@
>  #include 
>  #include 
>  #include 
> +#include 
>  #include 
>  #include 
>  #include 
> @@ -19,6 +20,7 @@
>  #include "arg.h"
>  #include "data.h"
>  #include "http.h"
> +#include "queue.h"
>  #include "sock.h"
>  #include "util.h"
>  
> @@ -48,16 +50,20 @@ logmsg(const struct connection *c)
>  }
>  
>  static void
> -serve(struct connection *c, const struct server *srv)
> +close_connection(struct connection *c)
> +{
> + if (c != NULL) {
> + close(c->fd);
> + memset(c, 0, sizeof(*c));
> + }
> +}
> +
> +static void
> +serve_connection(struct connection *c, const struct server *srv)
>  {
>   enum status s;
>   int done;
>  
> - /* set connection timeout */
> - if (sock_set_timeout(c->fd, 30)) {
> - warn("sock_set_timeout: Failed");
> - }
> -
>   switch (c->state) {
>   case C_VACANT:
>   /*
> @@ -145,12 +151,212 @@ response:
>   }
>  err:
>   logmsg(c);
> + close_connection(c);
> +}
> +
> +struct connection *
> +accept_connection(int insock, struct connection *connection,
> +  size_t nslots)
> +{
> + struct connection *c = NULL;
> + size_t j;
> +
> + /* find vacant connection (i.e. one with no fd assigned to it) */
> + for (j = 0; j < nslots; j++) {
> + if (connection[j].fd == 0) {
> + c = [j];
> + break;
> + }
> + }
> + if (j == nslots) {
> + /* nothing available right now, return without accepting */
> +
> + /* 
> +  * NOTE: This is currently still not the best option, but
> +  * at least we now have control over it and can reap a
> +  * connection from our pool instead of previously when
> +  * we were forking and were more or less on our own in
> +  * each process
> +  */
> + return NULL;
> + }
> +
> + /* accept connection */
> + if ((c->fd = accept(insock, (struct sockaddr *)>ia,
> + &(socklen_t){sizeof(c->ia)})) < 0) {
> + if (errno != EAGAIN && errno != EWOULDBLOCK) {
> + /* not much we can do here */
> + warn("accept:");
> 

[hackers] [quark] Use epoll/kqueue and worker threads to handle connections || Laslo Hunhold

2020-10-31 Thread git
commit dff98c0bcaef7be220c563ebaebd66f8c6704197
Author: Laslo Hunhold 
AuthorDate: Sun Nov 1 00:27:46 2020 +0100
Commit: Laslo Hunhold 
CommitDate: Sun Nov 1 00:27:46 2020 +0100

Use epoll/kqueue and worker threads to handle connections

This adds quite a bit of code, but is the culmination of the previous
restructurizations. Each worker thread has a connection pool and the
interesting part is that it's 100% nonblocking. If reading or writing
blocks at any point, the worker thread can just drop it and continue
with something else. This is especially powerful against attacks like
slow loris, which cannot be caught with a forking-model and could easily
be used in a DoS against a quark instance.

There are no memory allocations at runtime, unless you use dirlistings,
whose libc-allocations you can't work around.

In case the connection pool is exhausted due to a lot of slow lorises,
we still hit a DoS, but at least it can now be possible to assess the
connection pool and just drop another connection that can be
heuristically assessed as a "malicious" one (e.g. many connections from
one client, long time in one state or something using a monotonic
clock).

Given we still sadly don't have kqueue in linux, which is 1000x times
better than epoll, which is deeply flawed, I wrote a very thin wrapper
in queue.{c,h} which exposes the necessary functions in a common
interface.

Signed-off-by: Laslo Hunhold 

diff --git a/Makefile b/Makefile
index 548e6aa..da0e458 100644
--- a/Makefile
+++ b/Makefile
@@ -4,13 +4,13 @@
 
 include config.mk
 
-COMPONENTS = data http sock util
+COMPONENTS = data http queue 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 sock.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
 
diff --git a/config.mk b/config.mk
index 7056241..cedb3e7 100644
--- a/config.mk
+++ b/config.mk
@@ -10,7 +10,7 @@ MANPREFIX = $(PREFIX)/share/man
 # flags
 CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 
-D_BSD_SOURCE
 CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os
-LDFLAGS  = -s
+LDFLAGS  = -lpthread -s
 
 # compiler and linker
 CC = cc
diff --git a/main.c b/main.c
index d64774b..e26cc77 100644
--- a/main.c
+++ b/main.c
@@ -3,6 +3,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -19,6 +20,7 @@
 #include "arg.h"
 #include "data.h"
 #include "http.h"
+#include "queue.h"
 #include "sock.h"
 #include "util.h"
 
@@ -48,16 +50,20 @@ logmsg(const struct connection *c)
 }
 
 static void
-serve(struct connection *c, const struct server *srv)
+close_connection(struct connection *c)
+{
+   if (c != NULL) {
+   close(c->fd);
+   memset(c, 0, sizeof(*c));
+   }
+}
+
+static void
+serve_connection(struct connection *c, const struct server *srv)
 {
enum status s;
int done;
 
-   /* set connection timeout */
-   if (sock_set_timeout(c->fd, 30)) {
-   warn("sock_set_timeout: Failed");
-   }
-
switch (c->state) {
case C_VACANT:
/*
@@ -145,12 +151,212 @@ response:
}
 err:
logmsg(c);
+   close_connection(c);
+}
+
+struct connection *
+accept_connection(int insock, struct connection *connection,
+  size_t nslots)
+{
+   struct connection *c = NULL;
+   size_t j;
+
+   /* find vacant connection (i.e. one with no fd assigned to it) */
+   for (j = 0; j < nslots; j++) {
+   if (connection[j].fd == 0) {
+   c = [j];
+   break;
+   }
+   }
+   if (j == nslots) {
+   /* nothing available right now, return without accepting */
+
+   /* 
+* NOTE: This is currently still not the best option, but
+* at least we now have control over it and can reap a
+* connection from our pool instead of previously when
+* we were forking and were more or less on our own in
+* each process
+*/
+   return NULL;
+   }
+
+   /* accept connection */
+   if ((c->fd = accept(insock, (struct sockaddr *)>ia,
+   &(socklen_t){sizeof(c->ia)})) < 0) {
+   if (errno != EAGAIN && errno != EWOULDBLOCK) {
+   /* not much we can do 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 {
+