[MEDIUM] added the "reqtarpit" and "reqitarpit" features

It is now possible to tarpit connections based on regex matches.
The tarpit timeout is equal to the contimeout. A 500 server error
response is faked, and the logs show the status flags as "PT" which
indicate the connection has been tarpitted.
This commit is contained in:
Willy Tarreau 2006-09-03 09:56:00 +02:00
parent f8306d5391
commit b8750a82a2
5 changed files with 86 additions and 7 deletions

View File

@ -37,6 +37,7 @@
#define ACT_REMOVE 2 /* remove the matching header */
#define ACT_DENY 3 /* deny the request */
#define ACT_PASS 4 /* pass this header without allowing or denying the request */
#define ACT_TARPIT 5 /* tarpit the connection matching this request */
struct hdr_exp {
struct hdr_exp *next;

View File

@ -73,6 +73,7 @@
#define SN_FINST_D 0x00004000 /* session ended during data phase */
#define SN_FINST_L 0x00005000 /* session ended while pushing last data to client */
#define SN_FINST_Q 0x00006000 /* session ended while waiting in queue for a server slot */
#define SN_FINST_T 0x00007000 /* session ended tarpitted */
#define SN_FINST_MASK 0x00007000 /* mask to get only final session state flags */
#define SN_FINST_SHIFT 12 /* bit shift */
@ -95,6 +96,7 @@
#define SN_ASSIGNED 0x00800000 /* no need to assign a server to this session */
#define SN_ADDR_SET 0x01000000 /* this session's server address has been set */
#define SN_SELF_GEN 0x02000000 /* the proxy generates data for the client (eg: stats) */
#define SN_CLTARPIT 0x04000000 /* the session is tarpitted (anti-dos) */
/* WARNING: if new fields are added, they must be initialized in event_accept() */

View File

@ -1348,6 +1348,26 @@ int cfg_parse_listen(char *file, int linenum, char **args)
chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL);
}
else if (!strcmp(args[0], "reqtarpit")) { /* tarpit a request if a header matches this regex */
regex_t *preg;
if (curproxy == &defproxy) {
Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
return -1;
}
if (*(args[1]) == 0) {
Alert("parsing [%s:%d] : '%s' expects <regex> as an argument.\n", file, linenum, args[0]);
return -1;
}
preg = calloc(1, sizeof(regex_t));
if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
return -1;
}
chain_regex(&curproxy->req_exp, preg, ACT_TARPIT, NULL);
}
else if (!strcmp(args[0], "reqirep")) { /* replace request header from a regex, ignoring case */
regex_t *preg;
if (curproxy == &defproxy) {
@ -1454,6 +1474,26 @@ int cfg_parse_listen(char *file, int linenum, char **args)
chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL);
}
else if (!strcmp(args[0], "reqitarpit")) { /* tarpit a request if a header matches this regex ignoring case */
regex_t *preg;
if (curproxy == &defproxy) {
Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
return -1;
}
if (*(args[1]) == 0) {
Alert("parsing [%s:%d] : '%s' expects <regex> as an argument.\n", file, linenum, args[0]);
return -1;
}
preg = calloc(1, sizeof(regex_t));
if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) {
Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
return -1;
}
chain_regex(&curproxy->req_exp, preg, ACT_TARPIT, NULL);
}
else if (!strcmp(args[0], "reqadd")) { /* add request header */
if (curproxy == &defproxy) {
Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);

View File

@ -52,7 +52,7 @@ const char *monthname[12] = {
};
const char sess_term_cond[8] = "-cCsSPRI"; /* normal, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal */
const char sess_fin_state[8] = "-RCHDLQ7"; /* cliRequest, srvConnect, srvHeader, Data, Last, Queue, unknown */
const char sess_fin_state[8] = "-RCHDLQT"; /* cliRequest, srvConnect, srvHeader, Data, Last, Queue, Tarpit */
const char sess_cookie[4] = "NIDV"; /* No cookie, Invalid cookie, cookie for a Down server, Valid cookie */
const char sess_set_cookie[8] = "N1I3PD5R"; /* No set-cookie, unknown, Set-Cookie Inserted, unknown,
Set-cookie seen and left unchanged (passive), Set-cookie Deleted,

View File

@ -451,6 +451,17 @@ int process_cli(struct session *t)
*/
tv_eternity(&req->rex);
/* When a connection is tarpitted, we use the queue timeout for the
* tarpit delay, which currently happens to be the server's connect
* timeout. If unset, then set it to zero because we really want it
* to expire at one moment.
*/
if (t->flags & SN_CLTARPIT) {
tv_delayfrom(&req->cex, &now,
t->proxy->contimeout ? t->proxy->contimeout : 0);
}
goto process_data;
}
@ -637,23 +648,27 @@ int process_cli(struct session *t)
if (regexec(exp->preg, req->h, MAX_MATCH, pmatch, 0) == 0) {
switch (exp->action) {
case ACT_ALLOW:
if (!(t->flags & SN_CLDENY))
if (!(t->flags & (SN_CLDENY | SN_CLTARPIT)))
t->flags |= SN_CLALLOW;
break;
case ACT_REPLACE:
if (!(t->flags & SN_CLDENY)) {
if (!(t->flags & (SN_CLDENY | SN_CLTARPIT))) {
int len = exp_replace(trash, req->h, exp->replace, pmatch);
ptr += buffer_replace2(req, req->h, ptr, trash, len);
}
break;
case ACT_REMOVE:
if (!(t->flags & SN_CLDENY))
if (!(t->flags & (SN_CLDENY | SN_CLTARPIT)))
delete_header = 1;
break;
case ACT_DENY:
if (!(t->flags & SN_CLALLOW))
if (!(t->flags & (SN_CLALLOW | SN_CLTARPIT)))
t->flags |= SN_CLDENY;
break;
case ACT_TARPIT:
if (!(t->flags & (SN_CLALLOW | SN_CLDENY)))
t->flags |= SN_CLTARPIT;
break;
case ACT_PASS: /* we simply don't deny this one */
break;
}
@ -678,7 +693,7 @@ int process_cli(struct session *t)
*/
if (!delete_header &&
(t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL || t->proxy->appsession_name !=NULL)
&& !(t->flags & SN_CLDENY) && (ptr >= req->h + 8)
&& !(t->flags & (SN_CLDENY|SN_CLTARPIT)) && (ptr >= req->h + 8)
&& (strncasecmp(req->h, "Cookie: ", 8) == 0)) {
char *p1, *p2, *p3, *p4;
char *del_colon, *del_cookie, *colon;
@ -938,7 +953,7 @@ int process_cli(struct session *t)
} /* end of cookie processing on this header */
/* let's look if we have to delete this header */
if (delete_header && !(t->flags & SN_CLDENY)) {
if (delete_header && !(t->flags & (SN_CLDENY|SN_CLTARPIT))) {
buffer_replace2(req, req->h, req->lr, NULL, 0);
/* WARNING: ptr is not valid anymore, since the header may have
* been deleted or truncated ! */
@ -1338,6 +1353,27 @@ int process_srv(struct session *t)
return 1;
}
else {
if (t->flags & SN_CLTARPIT) {
/* This connection is being tarpitted. The CLIENT side has
* already set the connect expiration date to the right
* timeout. We just have to check that it has not expired.
*/
if (tv_cmp2_ms(&req->cex, &now) > 0)
return 0;
/* We will set the queue timer to the time spent, just for
* logging purposes. We fake a 500 server error, so that the
* attacker will not suspect his connection has been tarpitted.
* It will not cause trouble to the logs because we can exclude
* the tarpitted connections by filtering on the 'PT' status flags.
*/
tv_eternity(&req->cex);
t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now);
srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T,
500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500);
return 1;
}
/* Right now, we will need to create a connection to the server.
* We might already have tried, and got a connection pending, in
* which case we will not do anything till it's pending. It's up