On Mon, 2009-10-19 at 10:40 +1100, Bojan Smojver wrote: > Maybe we could use SIGURG here. After all we do have an urgent message > for the socket :-)
Hmm, it's a real bummer we don't have more user defined signals. We could reach for RT stuff, but I'm not sure how portable that is. Anyhow, here is a version that uses the dreadful SIGINT a bit more smartly. It'll only close client socket if SIGINT came from the parent process (provided sigaction() is available). Otherwise, it'll exit the process, as usual. -- Bojan
--- httpd-2.2.14-v/server/mpm/prefork/prefork.c 2009-02-01 07:54:55.000000000 +1100 +++ httpd-2.2.14/server/mpm/prefork/prefork.c 2009-10-19 12:19:03.631423052 +1100 @@ -48,6 +48,7 @@ #include "ap_listen.h" #include "ap_mmn.h" #include "apr_poll.h" +#include "apr_md5.h" #ifdef HAVE_BSTRING_H #include <bstring.h> /* for IRIX, FD_SET calls bzero() */ @@ -336,6 +337,29 @@ die_now = 1; } +static int volatile client_socket = -1; + +#ifndef NO_USE_SIGACTION +static void close_client_socket(int sig, siginfo_t *info, void *context) +#else +static void close_client_socket(int sig) +#endif +{ + if (client_socket != -1) { +#ifndef NO_USE_SIGACTION + if (info->si_pid == getppid()) { +#endif + close(client_socket); + client_socket = -1; +#ifndef NO_USE_SIGACTION + } + else { + clean_child_exit(0); + } +#endif + } +} + /* volatile just in case */ static int volatile shutdown_pending; static int volatile restart_pending; @@ -659,8 +683,12 @@ current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, my_child_num, sbh, bucket_alloc); if (current_conn) { + apr_os_sock_get((apr_os_sock_t *)&client_socket, csd); + ap_process_connection(current_conn, csd); ap_lingering_close(current_conn); + + client_socket = -1; } /* Check the pod and the generation number after processing a @@ -733,6 +761,10 @@ } if (!pid) { +#ifndef NO_USE_SIGACTION + struct sigaction act; +#endif + #ifdef HAVE_BINDPROCESSOR /* by default AIX binds to a single processor * this bit unbinds children which will then bind to another cpu @@ -755,6 +787,19 @@ * The pod is used for signalling the graceful restart. */ apr_signal(AP_SIG_GRACEFUL, stop_listening); + + /* If the parent sends SIGINT to the child, we close the client + * socket, as we suspect that we are under DoS attack. + */ +#ifndef NO_USE_SIGACTION + memset(&act, 0, sizeof(act)); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = close_client_socket; + sigaction(SIGINT, &act, NULL); +#else + apr_signal(SIGINT, close_client_socket); +#endif + child_main(slot); } @@ -803,6 +848,8 @@ int free_slots[MAX_SPAWN_RATE]; int last_non_dead; int total_non_dead; + int status; + static apr_time_t maxed_out = 0; /* initialize the free_list */ free_length = 0; @@ -813,8 +860,6 @@ total_non_dead = 0; for (i = 0; i < ap_daemons_limit; ++i) { - int status; - if (i >= ap_max_daemons_limit && free_length == idle_spawn_rate) break; ws = &ap_scoreboard_image->servers[i][0]; @@ -856,12 +901,17 @@ */ ap_mpm_pod_signal(pod); idle_spawn_rate = 1; + maxed_out = 0; } else if (idle_count < ap_daemons_min_free) { /* terminate the free list */ if (free_length == 0) { /* only report this condition once */ static int reported = 0; + static unsigned char sb_digest[APR_MD5_DIGESTSIZE]; + apr_time_t now = apr_time_now(); + apr_md5_ctx_t ctx; + pid_t pid; if (!reported) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, @@ -870,6 +920,83 @@ reported = 1; } idle_spawn_rate = 1; + + /* If after one maintenace interval we still see the same + * situation on the scoreboard, close all client sockets in + * read state and at least 10% of all client sockets. + * Crude, but seems to clear things out. + */ + if (maxed_out) { + apr_time_t diff = now - maxed_out; + + if (diff >= SCOREBOARD_MAINTENANCE_INTERVAL) { + unsigned char cur_digest[APR_MD5_DIGESTSIZE]; + + /* Current digest of the scoreboard. + */ + apr_md5_init(&ctx); + for (i = 0; i < ap_daemons_limit; ++i) { + pid = ap_scoreboard_image->parent[i].pid; + apr_md5_update(&ctx, &pid, sizeof(pid)); + + status = ap_scoreboard_image->servers[i][0].status; + apr_md5_update(&ctx, &status, sizeof(status)); + } + apr_md5_final(cur_digest, &ctx); + + /* If we haven't had a change for one maintenance + * interval, we need to make room. + */ + if (memcmp(sb_digest, cur_digest, APR_MD5_DIGESTSIZE)) { + maxed_out = 0; + } + else { + int rdrs = 0, cull = ap_daemons_limit / 10; + + /* Disconnect all readers (includes keep alive). + */ + for (i = 0; i < ap_daemons_limit; ++i) { + pid = ap_scoreboard_image->parent[i].pid; + status = ap_scoreboard_image->servers[i][0].status; + + if (status == SERVER_BUSY_READ || + status == SERVER_BUSY_KEEPALIVE) { + ap_mpm_safe_kill(pid, SIGINT); + rdrs++; + } + } + + /* Make up to 10% of all sockets, if required. + */ + for (i = 0; i < ap_daemons_limit && cull > rdrs; ++i) { + pid = ap_scoreboard_image->parent[i].pid; + status = ap_scoreboard_image->servers[i][0].status; + + if (status != SERVER_BUSY_READ && + status != SERVER_BUSY_KEEPALIVE) { + ap_mpm_safe_kill(pid, SIGINT); + cull--; + } + } + } + } + } + else { + /* Create digest of the scoreboard, see if things + * change next time around. + */ + apr_md5_init(&ctx); + for (i = 0; i < ap_daemons_limit; ++i) { + pid = ap_scoreboard_image->parent[i].pid; + apr_md5_update(&ctx, &pid, sizeof(pid)); + + status = ap_scoreboard_image->servers[i][0].status; + apr_md5_update(&ctx, &status, sizeof(status)); + } + apr_md5_final(sb_digest, &ctx); + + maxed_out = now; + } } else { if (idle_spawn_rate >= 8) { @@ -902,10 +1029,13 @@ else if (idle_spawn_rate < MAX_SPAWN_RATE) { idle_spawn_rate *= 2; } + + maxed_out = 0; } } else { idle_spawn_rate = 1; + maxed_out = 0; } }