This is my first time submitting a modification to haproxy, so I would
appreciate feedback.

We've been experimenting with using the stick tables feature in Haproxy to
do rate limiting by IP at the edge. We know from past experience that we
will need to maintain a whitelist because schools and small ISPs (in
particular) have a habit of proxying a significant number of requests
through a handful of addresses without providing x-forwarded-for to
differentiate between actual origins. My employer has a strict "we talk to
our customers" policy (what a unique concept!) so when we do rate limit
someone we want to return a custom error page which explains in a positive
way why we are not serving he requested page and how our support group will
be happy to add them to the white list if they contact us.

This patch adds support for error codes 429 and 405 to Haproxy and a
"deny_status XXX" option to "http-request deny" where you can specify which
code is returned with 403 being the default. We really want to do this the
"haproxy way" and hope to have this patch included in the mainline. We'll
be happy address any feedback on how this is implemented.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9a04200..daba1b9 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2612,7 +2612,8 @@ errorfile <code> <file>
                                  yes   |    yes   |   yes  |   yes
   Arguments :
     <code>    is the HTTP status code. Currently, HAProxy is capable of
-              generating codes 200, 400, 403, 408, 500, 502, 503, and 504.
+              generating codes 200, 400, 403, 405, 408, 429, 500, 502, 503, and
+              504.
 
     <file>    designates a file containing the full HTTP response. It is
               recommended to follow the common practice of appending ".http" to
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 5a4489d..d649fdd 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -309,7 +309,9 @@ enum {
        HTTP_ERR_200 = 0,
        HTTP_ERR_400,
        HTTP_ERR_403,
+       HTTP_ERR_405,
        HTTP_ERR_408,
+       HTTP_ERR_429,
        HTTP_ERR_500,
        HTTP_ERR_502,
        HTTP_ERR_503,
@@ -417,6 +419,7 @@ struct http_req_rule {
        struct list list;
        struct acl_cond *cond;                 /* acl condition to meet */
        unsigned int action;                   /* HTTP_REQ_* */
+       short deny_status;                     /* HTTP status to return to user 
when denying */
        int (*action_ptr)(struct http_req_rule *rule, struct proxy *px, struct 
session *s, struct http_txn *http_txn);  /* ptr to custom action */
        union {
                struct {
@@ -484,6 +487,7 @@ struct http_txn {
        unsigned int flags;             /* transaction flags */
        enum http_meth_t meth;          /* HTTP method */
        /* 1 unused byte here */
+       short rule_deny_status;         /* HTTP status from rule when denying */
        short status;                   /* HTTP status from the server, 
negative if from proxy */
 
        char *uri;                      /* first line if log needed, NULL 
otherwise */
diff --git a/src/proto_http.c b/src/proto_http.c
index 611a8c1..989d399 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -131,7 +131,9 @@ const int http_err_codes[HTTP_ERR_SIZE] = {
        [HTTP_ERR_200] = 200,  /* used by "monitor-uri" */
        [HTTP_ERR_400] = 400,
        [HTTP_ERR_403] = 403,
+       [HTTP_ERR_405] = 405,
        [HTTP_ERR_408] = 408,
+       [HTTP_ERR_429] = 429,
        [HTTP_ERR_500] = 500,
        [HTTP_ERR_502] = 502,
        [HTTP_ERR_503] = 503,
@@ -163,6 +165,14 @@ static const char *http_err_msgs[HTTP_ERR_SIZE] = {
        "\r\n"
        "<html><body><h1>403 Forbidden</h1>\nRequest forbidden by 
administrative rules.\n</body></html>\n",
 
+       [HTTP_ERR_405] =
+       "HTTP/1.0 405 Method Not Allowed\r\n"
+       "Cache-Control: no-cache\r\n"
+       "Connection: close\r\n"
+       "Content-Type: text/html\r\n"
+       "\r\n"
+       "<html><body><h1>405 Method Not Allowed</h1>\nA request was made of a 
resource using a request method not supported by that 
resource\n</body></html>\n",
+
        [HTTP_ERR_408] =
        "HTTP/1.0 408 Request Time-out\r\n"
        "Cache-Control: no-cache\r\n"
@@ -171,6 +181,14 @@ static const char *http_err_msgs[HTTP_ERR_SIZE] = {
        "\r\n"
        "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a 
complete request in time.\n</body></html>\n",
 
+       [HTTP_ERR_429] =
+       "HTTP/1.0 429 Too Many Requests\r\n"
+       "Cache-Control: no-cache\r\n"
+       "Connection: close\r\n"
+       "Content-Type: text/html\r\n"
+       "\r\n"
+       "<html><body><h1>429 Too Many Requests</h1>\nYou have sent too many 
requests in a given amount of time.\n</body></html>\n",
+
        [HTTP_ERR_500] =
        "HTTP/1.0 500 Server Error\r\n"
        "Cache-Control: no-cache\r\n"
@@ -3408,10 +3426,12 @@ resume_execution:
                        return HTTP_RULE_RES_STOP;
 
                case HTTP_REQ_ACT_DENY:
+                       txn->rule_deny_status = rule->deny_status;
                        return HTTP_RULE_RES_DENY;
 
                case HTTP_REQ_ACT_TARPIT:
                        txn->flags |= TX_CLTARPIT;
+                       txn->rule_deny_status = rule->deny_status;
                        return HTTP_RULE_RES_DENY;
 
                case HTTP_REQ_ACT_AUTH:
@@ -4298,9 +4318,9 @@ int http_process_req_common(struct session *s, struct 
channel *req, int an_bit,
 
  deny: /* this request was blocked (denied) */
        txn->flags |= TX_CLDENY;
-       txn->status = 403;
+       txn->status = http_err_codes[txn->rule_deny_status];
        s->logs.tv_request = now;
-       stream_int_retnclose(&s->si[0], http_error_message(s, HTTP_ERR_403));
+       stream_int_retnclose(&s->si[0], http_error_message(s, 
txn->rule_deny_status));
        session_inc_http_err_ctr(s);
        s->fe->fe_counters.denied_req++;
        if (s->fe != s->be)
@@ -8987,12 +9007,38 @@ struct http_req_rule *parse_http_req_cond(const char 
**args, const char *file, i
                goto out_err;
        }
 
+       rule->deny_status = HTTP_ERR_403;
        if (!strcmp(args[0], "allow")) {
                rule->action = HTTP_REQ_ACT_ALLOW;
                cur_arg = 1;
        } else if (!strcmp(args[0], "deny") || !strcmp(args[0], "block")) {
+               int code;
+               int hc;
+
                rule->action = HTTP_REQ_ACT_DENY;
                cur_arg = 1;
+                if (strcmp(args[cur_arg], "deny_status") == 0) {
+                        cur_arg++;
+                        if (!args[cur_arg]) {
+                                Alert("parsing [%s:%d] : error detected in %s 
'%s' while parsing 'http-request %s' rule : missing status code.\n",
+                                      file, linenum, proxy_type_str(proxy), 
proxy->id, args[0]);
+                                goto out_err;
+                        }
+
+                        code = atol(args[cur_arg]);
+                        cur_arg++;
+                        for (hc = 0; hc < HTTP_ERR_SIZE; hc++) {
+                                if (http_err_codes[hc] == code) {
+                                        rule->deny_status = hc;
+                                        break;
+                                }
+                        }
+
+                        if (hc >= HTTP_ERR_SIZE) {
+                                Warning("parsing [%s:%d] : status code %d not 
handled, using default code 403.\n",
+                                        file, linenum, code);
+                        }
+                }
        } else if (!strcmp(args[0], "tarpit")) {
                rule->action = HTTP_REQ_ACT_TARPIT;
                cur_arg = 1;

Reply via email to