Skip to content

Commit 0480b45

Browse files
author
Nick Kew
committed
mod_noloris just moved from discussion to attracting its first patch
on dev@. That means it wants to be in svn. Adding to modules/experimental pending anything more definite. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@790205 13f79535-47bb-0310-9956-ffa450edef68
1 parent 87568eb commit 0480b45

1 file changed

Lines changed: 245 additions & 0 deletions

File tree

modules/experimental/mod_noloris.c

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/* Licensed to the Apache Software Foundation (ASF) under one or more
2+
* contributor license agreements. See the NOTICE file distributed with
3+
* this work for additional information regarding copyright ownership.
4+
* The ASF licenses this file to You under the Apache License, Version 2.0
5+
* (the "License"); you may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
/* The use of the scoreboard in this module is based on a similar
19+
* but simpler module, mod_antiloris by Kees Monshouwer, from
20+
* ftp://ftp.monshouwer.eu/pub/linux/mod_antiloris/
21+
* Note the FIXME that affects both modules.
22+
*
23+
* The major difference is that mod_antiloris checks the scoreboard
24+
* on every request. This implies a per-request overhead that grows
25+
* with the scoreboard, and gets very expensive on a big server.
26+
* On the other hand, this module (mod_noloris) may be slower to
27+
* react to a DoS attack, and in the case of a very small server
28+
* it might be too late.
29+
*
30+
* Author's untested instinct: mod_antiloris will suit servers with
31+
* Prefork MPM and low traffic. A server with a threaded MPM
32+
* (or possibly a big prefork server with lots of memory) should
33+
* raise MaxClients and use mod_noloris.
34+
*/
35+
36+
#include "httpd.h"
37+
#include "http_config.h"
38+
#include "http_connection.h"
39+
#include "http_log.h"
40+
#include "mpm_common.h"
41+
#include "ap_mpm.h"
42+
#include "apr_hash.h"
43+
44+
module AP_MODULE_DECLARE_DATA noloris_module;
45+
module AP_MODULE_DECLARE_DATA core_module;
46+
47+
static unsigned int default_max_connections;
48+
static apr_hash_t *trusted;
49+
static apr_interval_time_t recheck_time;
50+
static apr_shm_t *shm;
51+
static apr_size_t shm_size;
52+
static int server_limit;
53+
static int thread_limit;
54+
55+
static int noloris_conn(conn_rec *conn)
56+
{
57+
/*** FIXME
58+
* This is evil: we're assuming info that's private to the scoreboard
59+
* We need to do that because there's no API to update the scoreboard
60+
* on a connection, only with a request (or NULL to say not processing
61+
* any request). We need a version of ap_update_child_status that
62+
* accepts a conn_rec.
63+
*/
64+
struct { int child_num; int thread_num; } *sbh = conn->sbh;
65+
66+
char *shm_rec;
67+
worker_score *ws;
68+
if (shm == NULL) {
69+
return DECLINED; /* we're disabled */
70+
}
71+
72+
/* check the IP is not banned */
73+
shm_rec = apr_shm_baseaddr_get(shm);
74+
if (strstr(shm_rec, conn->remote_ip)) {
75+
apr_socket_t *csd = ap_get_module_config(conn->conn_config, &core_module);
76+
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn,
77+
"Dropping connection from banned IP %s", conn->remote_ip);
78+
//ap_flush_conn(conn); /* just close it */
79+
apr_socket_close(csd);
80+
81+
return DONE;
82+
}
83+
84+
/* store this client IP for the monitor to pick up */
85+
/* under traditional scoreboard, none of this happens until
86+
* there's a request_rec. This is where we use the illegally-
87+
* obtained private info from the scoreboard.
88+
*/
89+
90+
ws = &ap_scoreboard_image->servers[sbh->child_num][sbh->thread_num];
91+
strcpy(ws->client, conn->remote_ip);
92+
93+
return DECLINED;
94+
}
95+
static int noloris_monitor(apr_pool_t *pool)
96+
{
97+
static apr_hash_t *connections = NULL;
98+
static apr_time_t last_check = 0;
99+
static int *totals;
100+
101+
int i, j;
102+
int *n;
103+
int index = 0;
104+
apr_hash_index_t *hi;
105+
char *ip;
106+
apr_time_t time_now;
107+
char *shm_rec;
108+
worker_score *ws;
109+
110+
/* do nothing if disabled */
111+
if (shm == NULL) {
112+
return 0;
113+
}
114+
115+
/* skip check if it's not due yet */
116+
time_now = apr_time_now();
117+
if (time_now - last_check < recheck_time) {
118+
return 0;
119+
}
120+
last_check = time_now;
121+
122+
/* alloc lots of stuff at start, so we don't leak memory per-call */
123+
if (connections == NULL) {
124+
connections = apr_hash_make(pool);
125+
totals = apr_palloc(pool, server_limit*thread_limit);
126+
ip = apr_palloc(pool, 18);
127+
}
128+
129+
/* Get a per-client count of connections in READ state */
130+
for (i = 0; i < server_limit; ++i) {
131+
for (j = 0; j < thread_limit; ++j) {
132+
ws = ap_get_scoreboard_worker(i, j);
133+
if (ws->status == SERVER_BUSY_READ) {
134+
n = apr_hash_get(connections, ws->client, APR_HASH_KEY_STRING);
135+
if (n == NULL) {
136+
n = totals + index++ ;
137+
*n = 0;
138+
}
139+
++*n;
140+
apr_hash_set(connections, ws->client, APR_HASH_KEY_STRING, n);
141+
}
142+
}
143+
}
144+
145+
/* reset shm before writing to it.
146+
* We're only dealing with approx. counts, so we ignore the race condition
147+
* with our prospective readers
148+
*/
149+
shm_rec = apr_shm_baseaddr_get(shm);
150+
memset(shm_rec, NULL, shm_size);
151+
152+
/* Now check the hash for clients with too many connections in READ state */
153+
for (hi = apr_hash_first(NULL, connections); hi; hi = apr_hash_next(hi)) {
154+
apr_hash_this(hi, (const void**) &ip, NULL, (void**)&n);
155+
if (*n >= default_max_connections) {
156+
/* if this isn't a trusted proxy, we mark it as bad */
157+
if (!apr_hash_get(trusted, ip, APR_HASH_KEY_STRING)) {
158+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, 0,
159+
"noloris: banning %s with %d connections in READ state",
160+
ip, *n);
161+
strcpy(shm_rec++, " "); /* space == separator */
162+
strcpy(shm_rec, ip);
163+
shm_rec += strlen(ip);
164+
}
165+
}
166+
}
167+
apr_hash_clear(connections);
168+
return 0;
169+
}
170+
static int noloris_post(apr_pool_t *pconf, apr_pool_t *ptmp, apr_pool_t *plog,
171+
server_rec *s)
172+
{
173+
apr_status_t rv;
174+
int max_bans = thread_limit * server_limit / default_max_connections;
175+
shm_size = 18 * max_bans;
176+
177+
rv = apr_shm_create(&shm, shm_size, NULL, pconf);
178+
if (rv != APR_SUCCESS) {
179+
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
180+
"Failed to create shm segment; mod_noloris disabled");
181+
apr_hash_clear(trusted);
182+
shm = NULL;
183+
}
184+
return 0;
185+
}
186+
static int noloris_pre(apr_pool_t *pconf, apr_pool_t *ptmp, apr_pool_t *plog)
187+
{
188+
ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
189+
ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);
190+
191+
/* set up default config stuff here */
192+
trusted = apr_hash_make(pconf);
193+
default_max_connections = 50;
194+
recheck_time = apr_time_from_sec(10);
195+
return 0;
196+
}
197+
static void noloris_hooks(apr_pool_t *p)
198+
{
199+
ap_hook_process_connection(noloris_conn, NULL, NULL, APR_HOOK_FIRST);
200+
ap_hook_pre_config(noloris_pre, NULL, NULL, APR_HOOK_MIDDLE);
201+
ap_hook_post_config(noloris_post, NULL, NULL, APR_HOOK_MIDDLE);
202+
ap_hook_monitor(noloris_monitor, NULL, NULL, APR_HOOK_MIDDLE);
203+
}
204+
static const char *noloris_trusted(cmd_parms *cmd, void *cfg, const char *val)
205+
{
206+
const char* err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
207+
if (!err) {
208+
apr_hash_set(trusted, val, APR_HASH_KEY_STRING, &noloris_module);
209+
}
210+
return err;
211+
}
212+
static const char *noloris_recheck(cmd_parms *cmd, void *cfg, const char *val)
213+
{
214+
const char* err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
215+
if (!err) {
216+
recheck_time = apr_time_from_sec(atoi(val));
217+
}
218+
return err;
219+
}
220+
static const char *noloris_max_conn(cmd_parms *cmd, void *cfg, const char *val)
221+
{
222+
const char* err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
223+
if (!err) {
224+
default_max_connections = atoi(val);
225+
}
226+
return err;
227+
}
228+
static const command_rec noloris_cmds[] = {
229+
AP_INIT_ITERATE("TrustedProxy", noloris_trusted, NULL, RSRC_CONF,
230+
"IP addresses from which to allow unlimited connections"),
231+
AP_INIT_TAKE1("ClientRecheckTime", noloris_recheck, NULL, RSRC_CONF,
232+
"Time interval for rechecking client connection tables"),
233+
AP_INIT_TAKE1("MaxClientConnections", noloris_max_conn, NULL, RSRC_CONF,
234+
"Max connections in READ state to permit from an untrusted client"),
235+
{NULL}
236+
};
237+
module AP_MODULE_DECLARE_DATA noloris_module = {
238+
STANDARD20_MODULE_STUFF,
239+
NULL,
240+
NULL,
241+
NULL,
242+
NULL,
243+
noloris_cmds,
244+
noloris_hooks
245+
};

0 commit comments

Comments
 (0)