From: Willy Tarreau Date: Sun, 3 Sep 2006 07:56:00 +0000 (+0200) Subject: [MEDIUM] added the "reqtarpit" and "reqitarpit" features X-Git-Tag: v1.3.2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b8750a82a27296e9a86586d4e44b07c67e845391;p=thirdparty%2Fhaproxy.git [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. --- diff --git a/include/common/regex.h b/include/common/regex.h index c7f1c214a2..05eae6b095 100644 --- a/include/common/regex.h +++ b/include/common/regex.h @@ -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; diff --git a/include/types/session.h b/include/types/session.h index d3dfa8c05d..66b1db3cd9 100644 --- a/include/types/session.h +++ b/include/types/session.h @@ -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() */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 836dc9aa31..882c0e4671 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 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 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]); diff --git a/src/log.c b/src/log.c index a9b0eff49f..237efa2f0a 100644 --- a/src/log.c +++ b/src/log.c @@ -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, diff --git a/src/proto_http.c b/src/proto_http.c index aa64e216d8..0b0ce734a8 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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