]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] added the "reqtarpit" and "reqitarpit" features
authorWilly Tarreau <w@1wt.eu>
Sun, 3 Sep 2006 07:56:00 +0000 (09:56 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 3 Sep 2006 07:56:00 +0000 (09:56 +0200)
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.

include/common/regex.h
include/types/session.h
src/cfgparse.c
src/log.c
src/proto_http.c

index c7f1c214a2ab64c4f635ea16fa454d5a3ca1e477..05eae6b095aa877d068bf6577e09bbb78459c656 100644 (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;
index d3dfa8c05d99db9da7631a076433469c43db9ffa..66b1db3cd9a8d6565f13cc5c15e438e64465c3bc 100644 (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() */
index 836dc9aa313a8e03dd961d46d0607559c5986fae..882c0e4671840fe694b3fc271bf0014a13578d12 100644 (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]);
index a9b0eff49fe88c047f7e163b166ce45b4a412d81..237efa2f0ae39c7de49aa5f6e90e621c6af1e5bf 100644 (file)
--- 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,
index aa64e216d803cb4164bb5c493c3bd6d30f8f1254..0b0ce734a86066fa262ac5418196454ec1263085 100644 (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