[PATCH] HTTP-Server DDos fix

Alexander Malysh <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Hi All,

attached you can find a patch that fixes DDos issue with our HTTP-Server implementation (consume all available file descriptors)
and also adds possibility to set keep alive timeout for each listen port.

This patch will limit maximum open client connections to 500. Later we can set this dynamically if this should be needed.

Please review this patch.

Thanks,
Alexander Malysh
http-server.patch (application/octet-stream, 12.3 KB)
diff --git a/gwlib/http.c b/gwlib/http.c
index f18afbe..ac1c440 100644
--- a/gwlib/http.c
+++ b/gwlib/http.c
@@ -92,6 +92,8 @@ static int http_client_timeout = 240;
 
 /* define http server connections timeout in seconds (set to -1 for disable) */
 #define HTTP_SERVER_TIMEOUT 60
+/* max accepted clients */
+#define HTTP_SERVER_MAX_ACTIVE_CONNECTIONS 500
 
 /***********************************************************************
  * Stuff used in several sub-modules.
@@ -1855,6 +1857,15 @@ struct HTTPClient {
 };
 
 
+/*
+ * Variables related to server side implementation.
+ */
+static Mutex *server_thread_lock = NULL;
+static volatile sig_atomic_t server_thread_is_running = 0;
+static long server_thread_id = -1;
+static List *new_server_sockets = NULL;
+static List *closed_server_sockets = NULL;
+static int keep_servers_open = 0;
 /* List with all active HTTPClient's */
 static List *active_connections;
 
@@ -1892,6 +1903,7 @@ static HTTPClient *client_create(int port, Connection *conn, Octstr *ip)
 static void client_destroy(void *client)
 {
     HTTPClient *p;
+    long a_len;
     
     if (client == NULL)
         return;
@@ -1902,7 +1914,13 @@ static void client_destroy(void *client)
     gwlist_lock(active_connections);
     if (gwlist_delete_equal(active_connections, p) != 1)
         panic(0, "HTTP: Race condition in client_destroy(%p) detected!", client);
+
+    /* signal server thread that client slot is free */
+    a_len = gwlist_len(active_connections);
     gwlist_unlock(active_connections);
+
+    if (a_len >= HTTP_SERVER_MAX_ACTIVE_CONNECTIONS - 1)
+        gwthread_wakeup(server_thread_id);
     
     debug("gwlib.http", 0, "HTTP: Destroying HTTPClient area %p.", p);
     gw_assert_allocated(p, __FILE__, __LINE__, __func__);
@@ -1962,8 +1980,12 @@ static int client_is_persistent(List *headers, int use_version_1_0)
  * Port specific lists of clients with requests.
  */
 struct port {
+    int fd;
+    int port;
+    int ssl;
     List *clients_with_requests;
     Counter *active_consumers;
+    FDSet *server_fdset;
 };
 
 
@@ -2000,7 +2022,7 @@ static Octstr *port_key(int port)
 }
 
 
-static void port_add(int port)
+static struct port *port_add(int port)
 {
     Octstr *key;
     struct port *p;
@@ -2012,12 +2034,15 @@ static void port_add(int port)
         p->clients_with_requests = gwlist_create();
         gwlist_add_producer(p->clients_with_requests);
         p->active_consumers = counter_create();
+        p->server_fdset = fdset_create_real(HTTP_SERVER_TIMEOUT);
         dict_put(port_collection, key, p);
     } else {
         warning(0, "HTTP: port_add called for existing port (%d)", port);
     }
     mutex_unlock(port_mutex);
     octstr_destroy(key);
+
+    return p;
 }
 
 
@@ -2042,9 +2067,9 @@ static void port_remove(int port)
     gwlist_remove_producer(p->clients_with_requests);
     while (counter_value(p->active_consumers) > 0)
        gwthread_sleep(0.1);    /* Reasonable use of busy waiting. */
+
     gwlist_destroy(p->clients_with_requests, client_destroy);
     counter_destroy(p->active_consumers);
-    gw_free(p);
 
     /*
      * In order to avoid race conditions with FDSet thread, we
@@ -2062,6 +2087,10 @@ static void port_remove(int port)
     gwlist_destroy(l, NULL);
     while((client = gwlist_search(active_connections, &port, port_match)) != NULL)
         client_destroy(client);
+
+    /* now destroy fdset */
+    fdset_destroy(p->server_fdset);
+    gw_free(p);
 }
 
 
@@ -2109,16 +2138,41 @@ static HTTPClient *port_get_request(int port)
 }
 
 
-/*
- * Variables related to server side implementation.
- */
-static Mutex *server_thread_lock = NULL;
-static volatile sig_atomic_t server_thread_is_running = 0;
-static long server_thread_id = -1;
-static FDSet *server_fdset = NULL;
-static List *new_server_sockets = NULL;
-static List *closed_server_sockets = NULL;
-static int keep_servers_open = 0;
+static void port_set_timeout(int port, long timeout)
+{
+    Octstr *key;
+    struct port *p;
+
+    mutex_lock(port_mutex);
+    key = port_key(port);
+    p = dict_get(port_collection, key);
+    octstr_destroy(key);
+
+    if (p != NULL)
+        fdset_set_timeout(p->server_fdset, timeout);
+
+    mutex_unlock(port_mutex);
+}
+
+
+static FDSet *port_get_fdset(int port)
+{
+    Octstr *key;
+    struct port *p;
+    FDSet *ret = NULL;
+
+    mutex_lock(port_mutex);
+    key = port_key(port);
+    p = dict_get(port_collection, key);
+    octstr_destroy(key);
+
+    if (p != NULL)
+        ret = p->server_fdset;
+
+    mutex_unlock(port_mutex);
+
+    return ret;
+}
 
 
 static int parse_request_line(int *method, Octstr **url,
@@ -2260,28 +2314,21 @@ error:
 }
 
 
-struct server {
-    int fd;
-    int port;
-    int ssl;
-};
-
-
 static void server_thread(void *dummy)
 {
     struct pollfd *tab = NULL;
-    struct server **ports = NULL;
-    int tab_size = 0, n, i, fd, ret;
+    struct port **ports = NULL;
+    int tab_size = 0, n, i, fd, ret, max_clients_reached;
     struct sockaddr_in addr;
     socklen_t addrlen;
     HTTPClient *client;
     Connection *conn;
     int *portno;
 
-    n = 0;
+    n = max_clients_reached = 0;
     while (run_status == running && keep_servers_open) {
         while (n == 0 || gwlist_len(new_server_sockets) > 0) {
-            struct server *p = gwlist_consume(new_server_sockets);
+            struct port *p = gwlist_consume(new_server_sockets);
             if (p == NULL) {
                 debug("gwlib.http", 0, "HTTP: No new servers. Quitting.");
                 break;
@@ -2294,7 +2341,7 @@ static void server_thread(void *dummy)
                 ports = gw_realloc(ports, tab_size * sizeof(*ports));
                 if (tab == NULL || ports == NULL) {
                     tab_size--;
-                    gw_free(p);
+                    port_remove(p->port);
                     continue;
                 }
             }
@@ -2304,7 +2351,11 @@ static void server_thread(void *dummy)
             n++;
         }
 
-        if ((ret = gwthread_poll(tab, n, -1.0)) == -1) {
+        if (max_clients_reached && gwlist_len(active_connections) >= HTTP_SERVER_MAX_ACTIVE_CONNECTIONS) {
+            /* TODO start cleanup of stale connections */
+            /* wait for slots to become free */
+            gwthread_sleep(1.0);
+        } else if (!max_clients_reached && (ret = gwthread_poll(tab, n, -1.0)) == -1) {
             if (errno != EINTR) /* a signal was caught during poll() function */
                 warning(errno, "HTTP: gwthread_poll failed.");
             continue;
@@ -2312,6 +2363,14 @@ static void server_thread(void *dummy)
 
         for (i = 0; i < n; ++i) {
             if (tab[i].revents & POLLIN) {
+                /* check our limit */
+                if (gwlist_len(active_connections) >= HTTP_SERVER_MAX_ACTIVE_CONNECTIONS) {
+                    max_clients_reached = 1;
+                    break;
+                } else {
+                    max_clients_reached = 0;
+                }
+
                 addrlen = sizeof(addr);
                 fd = accept(tab[i].fd, (struct sockaddr *) &addr, &addrlen);
                 if (fd == -1) {
@@ -2325,7 +2384,7 @@ static void server_thread(void *dummy)
                      */             
                     if ((conn = conn_wrap_fd(fd, ports[i]->ssl))) {
                         client = client_create(ports[i]->port, conn, client_ip);
-                        conn_register(conn, server_fdset, receive_request, client);
+                        conn_register(conn, ports[i]->server_fdset, receive_request, client);
                     } else {
                         error(0, "HTTP: unsuccessful SSL handshake for client `%s'",
                         octstr_get_cstr(client_ip));
@@ -2342,7 +2401,6 @@ static void server_thread(void *dummy)
                     tab[i].fd = -1;
                     tab[i].events = 0;
                     port_remove(ports[i]->port);
-                    gw_free(ports[i]);
                     ports[i] = NULL;
                     n--;
                     
@@ -2362,7 +2420,6 @@ static void server_thread(void *dummy)
     for (i = 0; i < n; ++i) {
         (void) close(tab[i].fd);
         port_remove(ports[i]->port);
-        gw_free(ports[i]);
     }
     gw_free(tab);
     gw_free(ports);
@@ -2382,7 +2439,6 @@ static void start_server_thread(void)
          */
         mutex_lock(server_thread_lock);
         if (!server_thread_is_running) {
-            server_fdset = fdset_create_real(HTTP_SERVER_TIMEOUT);
             server_thread_id = gwthread_create(server_thread, NULL);
             server_thread_is_running = 1;
         }
@@ -2391,24 +2447,29 @@ static void start_server_thread(void)
 }
 
 
+void http_set_server_timeout(int port, long timeout)
+{
+    port_set_timeout(port, timeout);
+}
+
+
 int http_open_port_if(int port, int ssl, Octstr *interface)
 {
-    struct server *p;
+    struct port *p;
 
     if (ssl) 
         info(0, "HTTP: Opening SSL server at port %d.", port);
     else 
         info(0, "HTTP: Opening server at port %d.", port);
-    p = gw_malloc(sizeof(*p));
+    p = port_add(port);
     p->port = port;
     p->ssl = ssl;
     p->fd = make_server_socket(port, (interface ? octstr_get_cstr(interface) : NULL));
     if (p->fd == -1) {
-        gw_free(p);
+        port_remove(port);
     	return -1;
     }
     
-    port_add(port);
     gwlist_produce(new_server_sockets, p);
     keep_servers_open = 1;
     start_server_thread();
@@ -2442,8 +2503,6 @@ void http_close_all_ports(void)
         gwthread_wakeup(server_thread_id);
         gwthread_join_every(server_thread);
         server_thread_is_running = 0;
-        fdset_destroy(server_fdset);
-        server_fdset = NULL;
     }
 }
 
@@ -2642,13 +2701,13 @@ void http_send_reply(HTTPClient *client, int status, List *headers,
         } else {
             /* XXX mark this HTTPClient in the keep-alive cleaner thread */
             client_reset(client);
-            conn_register(client->conn, server_fdset, receive_request, client);
+            conn_register(client->conn, port_get_fdset(client->port), receive_request, client);
         }
     }
     /* queued for sending, we don't want to block */
     else if (ret == 1) {    
         client->state = sending_reply;
-        conn_register(client->conn, server_fdset, receive_request, client);
+        conn_register(client->conn, port_get_fdset(client->port), receive_request, client);
     }
     /* error while sending response */
     else {     
@@ -2678,11 +2737,11 @@ static void server_init(void)
 
 static void destroy_struct_server(void *p)
 {
-    struct server *pp;
+    struct port *pp;
     
     pp = p;
     (void) close(pp->fd);
-    gw_free(pp);
+    port_remove(pp->port);
 }
 
 
@@ -2702,8 +2761,6 @@ static void server_shutdown(void)
         server_thread_is_running = 0;
     }
     mutex_destroy(server_thread_lock);
-    fdset_destroy(server_fdset);
-    server_fdset = NULL;
     gwlist_destroy(new_server_sockets, destroy_struct_server);
     gwlist_destroy(closed_server_sockets, destroy_int_pointer);
 }
diff --git a/gwlib/http.h b/gwlib/http.h
index 479b0cb..a4e7b16 100644
--- a/gwlib/http.h
+++ b/gwlib/http.h
@@ -395,6 +395,12 @@ void *http_receive_result_real(HTTPCaller *caller, int *status, Octstr **final_u
 typedef struct HTTPClient HTTPClient;
 
 
+/**
+ * Define timeout in seconds for which HTTP server will wait for
+ * request. Set -1 to disable timeouts.
+ */
+void http_set_server_timeout(int port, long timeout);
+
 /*
  * Open an HTTP server at a given port. Return -1 for errors (invalid
  * port number, etc), 0 for OK. This will also start a background thread
diff --git a/test/test_http_server.c b/test/test_http_server.c
index 9fe2d6f..61bfbf6 100644
--- a/test/test_http_server.c
+++ b/test/test_http_server.c
@@ -73,8 +73,10 @@
 Octstr *whitelist, *blacklist;
 Octstr *reply_text = NULL;
 
-int verbose, run, port;
+int verbose, port;
 int ssl = 0;   /* indicate if SSL-enabled server should be used */
+static volatile sig_atomic_t run;
+
 
 static void client_thread(void *arg) 
 {
@@ -274,7 +276,6 @@ static void help(void) {
 
 static void sigterm(int signo) {
     run = 0;
-    http_close_all_ports();
     debug("test.gwlib", 0, "Signal %d received, quitting.", signo);
 }
 
@@ -300,6 +301,7 @@ int main(int argc, char **argv) {
     sigemptyset(&act.sa_mask);
     act.sa_flags = 0;
     sigaction(SIGTERM, &act, NULL);
+    sigaction(SIGINT, &act, NULL);
 
     port = 8080;
     use_threads = 1;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.