Hi Willy,
On Fri, Feb 10, Willy Tarreau wrote:
> > How should I send the patches ? One commit for
> > http_server_error/http_get_status_idx changes and tarpit deny_status
> > parser / doc in another commit ?
>
> Yes that's the prefered way to do it, one commit per architecture or
> functional change to ease review and bug tracking later.
I'm including two commits for this feature.
- 0001-MEDIUM-http_error_message-txn-status-http_get_status.patch
Removes second argument from http_error_message and uses
txn->status / http_get_status_idx to map the 200..504 status code
to HTTP_ERR_200..504 enum.
(http_get_status_idx has default return value of HTTP_ERR_500, is
this ok ?)
- 0002-MINOR-http-request-tarpit-deny_status.patch
Adds http-request tarpit deny_status functionality.
(depends on the 0001-... commit).
Alternative implementation (0001-....) for http_return_srv_error
could be something like this:
void http_return_srv_error(struct stream *s, struct stream_interface *si)
{
int err_type = si->err_type;
int send_msg = 1;
if (err_type & SI_ET_QUEUE_ABRT)
http_server_error(s, si, SF_ERR_CLICL, SF_FINST_Q, 503, NULL);
else if (err_type & SI_ET_CONN_ABRT) {
if (s->txn->flags & TX_NOT_FIRST)
send_msg = 0;
http_server_error(s, si, SF_ERR_CLICL, SF_FINST_C, 503, NULL);
}
...
...
else /* SI_ET_CONN_OTHER and others */
http_server_error(s, si, SF_ERR_INTERNAL, SF_FINST_C, 500, NULL);
if (send_msg)
bo_putchk(s->res.buf, http_error_message(s));
}
-Jarno
--
Jarno Huuskonen
>From 1f8f67d28d44e1fc43a255f61b70437dc5fdacbb Mon Sep 17 00:00:00 2001
From: Jarno Huuskonen <[email protected]>
Date: Mon, 6 Mar 2017 14:21:49 +0200
Subject: [PATCH 1/2] MEDIUM: http_error_message: txn->status /
http_get_status_idx.
X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4
This commit removes second argument(msgnum) from http_error_message and
changes http_error_message to use s->txn->status/http_get_status_idx for
mapping status code from 200..504 to HTTP_ERR_200..HTTP_ERR_504(enum).
This is needed for http-request tarpit deny_status commit.
---
include/proto/proto_http.h | 2 +-
src/filters.c | 2 +-
src/proto_http.c | 93 ++++++++++++++++++++++++++++++----------------
3 files changed, 62 insertions(+), 35 deletions(-)
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 6c81766..9409df3 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -136,7 +136,7 @@ struct act_rule *parse_http_res_cond(const char **args,
const char *file, int li
void free_http_req_rules(struct list *r);
void free_http_res_rules(struct list *r);
void http_reply_and_close(struct stream *s, short status, struct chunk *msg);
-struct chunk *http_error_message(struct stream *s, int msgnum);
+struct chunk *http_error_message(struct stream *s);
struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum,
struct proxy *curproxy,
const char **args, char
**errmsg, int use_fmt, int dir);
int smp_fetch_cookie(const struct arg *args, struct sample *smp, const char
*kw, void *private);
diff --git a/src/filters.c b/src/filters.c
index 9ec794a..cafc449 100644
--- a/src/filters.c
+++ b/src/filters.c
@@ -1069,7 +1069,7 @@ handle_analyzer_result(struct stream *s, struct channel
*chn,
http_reply_and_close(s, s->txn->status, NULL);
else {
s->txn->status = 400;
- http_reply_and_close(s, 400, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, 400, http_error_message(s));
}
}
diff --git a/src/proto_http.c b/src/proto_http.c
index 2d567c1..9239823 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -366,6 +366,26 @@ const char *get_reason(unsigned int status)
}
}
+/* This function returns HTTP_ERR_<num> (enum) matching http status code.
+ * Returned value should match codes from http_err_codes.
+ */
+static const int http_get_status_idx(unsigned int status)
+{
+ switch (status) {
+ case 200: return HTTP_ERR_200;
+ case 400: return HTTP_ERR_400;
+ case 403: return HTTP_ERR_403;
+ case 405: return HTTP_ERR_405;
+ case 408: return HTTP_ERR_408;
+ case 429: return HTTP_ERR_429;
+ case 500: return HTTP_ERR_500;
+ case 502: return HTTP_ERR_502;
+ case 503: return HTTP_ERR_503;
+ case 504: return HTTP_ERR_504;
+ default: return HTTP_ERR_500;
+ }
+}
+
void init_proto_http()
{
int i;
@@ -1031,10 +1051,10 @@ static void http_server_error(struct stream *s, struct
stream_interface *si,
channel_erase(si_oc(si));
channel_auto_close(si_ic(si));
channel_auto_read(si_ic(si));
- if (status > 0 && msg) {
+ if (status > 0)
s->txn->status = status;
+ if (msg)
bo_inject(si_ic(si), msg->str, msg->len);
- }
if (!(s->flags & SF_ERR_MASK))
s->flags |= err;
if (!(s->flags & SF_FINST_MASK))
@@ -1045,8 +1065,10 @@ static void http_server_error(struct stream *s, struct
stream_interface *si,
* and message.
*/
-struct chunk *http_error_message(struct stream *s, int msgnum)
+struct chunk *http_error_message(struct stream *s)
{
+ const int msgnum = http_get_status_idx(s->txn->status);
+
if (s->be->errmsg[msgnum].str)
return &s->be->errmsg[msgnum];
else if (strm_fe(s)->errmsg[msgnum].str)
@@ -1258,34 +1280,39 @@ void http_return_srv_error(struct stream *s, struct
stream_interface *si)
{
int err_type = si->err_type;
+ /* set s->txn->status for http_error_message(s) */
+ s->txn->status = 503;
+
if (err_type & SI_ET_QUEUE_ABRT)
http_server_error(s, si, SF_ERR_CLICL, SF_FINST_Q,
- 503, http_error_message(s, HTTP_ERR_503));
+ 503, http_error_message(s));
else if (err_type & SI_ET_CONN_ABRT)
http_server_error(s, si, SF_ERR_CLICL, SF_FINST_C,
503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
- http_error_message(s, HTTP_ERR_503));
+ http_error_message(s));
else if (err_type & SI_ET_QUEUE_TO)
http_server_error(s, si, SF_ERR_SRVTO, SF_FINST_Q,
- 503, http_error_message(s, HTTP_ERR_503));
+ 503, http_error_message(s));
else if (err_type & SI_ET_QUEUE_ERR)
http_server_error(s, si, SF_ERR_SRVCL, SF_FINST_Q,
- 503, http_error_message(s, HTTP_ERR_503));
+ 503, http_error_message(s));
else if (err_type & SI_ET_CONN_TO)
http_server_error(s, si, SF_ERR_SRVTO, SF_FINST_C,
503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
- http_error_message(s, HTTP_ERR_503));
+ http_error_message(s));
else if (err_type & SI_ET_CONN_ERR)
http_server_error(s, si, SF_ERR_SRVCL, SF_FINST_C,
503, (s->flags & SF_SRV_REUSED) ? NULL :
- http_error_message(s, HTTP_ERR_503));
+ http_error_message(s));
else if (err_type & SI_ET_CONN_RES)
http_server_error(s, si, SF_ERR_RESOURCE, SF_FINST_C,
503, (s->txn->flags & TX_NOT_FIRST) ? NULL :
- http_error_message(s, HTTP_ERR_503));
- else /* SI_ET_CONN_OTHER and others */
+ http_error_message(s));
+ else { /* SI_ET_CONN_OTHER and others */
+ s->txn->status = 500;
http_server_error(s, si, SF_ERR_INTERNAL, SF_FINST_C,
- 500, http_error_message(s, HTTP_ERR_500));
+ 500, http_error_message(s));
+ }
}
extern const char sess_term_cond[8];
@@ -2772,7 +2799,7 @@ int http_wait_for_request(struct stream *s, struct
channel *req, int an_bit)
txn->status = 408;
msg->err_state = msg->msg_state;
msg->msg_state = HTTP_MSG_ERROR;
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_408));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
req->analysers &= AN_REQ_FLT_END;
stream_inc_http_req_ctr(s);
@@ -2802,7 +2829,7 @@ int http_wait_for_request(struct stream *s, struct
channel *req, int an_bit)
txn->status = 400;
msg->err_state = msg->msg_state;
msg->msg_state = HTTP_MSG_ERROR;
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_400));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
req->analysers &= AN_REQ_FLT_END;
stream_inc_http_err_ctr(s);
stream_inc_http_req_ctr(s);
@@ -2931,7 +2958,7 @@ int http_wait_for_request(struct stream *s, struct
channel *req, int an_bit)
if (ret) {
/* we fail this request, let's return 503
service unavail */
txn->status = 503;
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_503));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL; /* we don't
want a real error here */
goto return_prx_cond;
@@ -2940,7 +2967,7 @@ int http_wait_for_request(struct stream *s, struct
channel *req, int an_bit)
/* nothing to fail, let's reply normaly */
txn->status = 200;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_200));
+ http_reply_and_close(s, txn->status, http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL; /* we don't want a real error
here */
goto return_prx_cond;
@@ -3175,7 +3202,7 @@ int http_wait_for_request(struct stream *s, struct
channel *req, int an_bit)
txn->req.err_state = txn->req.msg_state;
txn->req.msg_state = HTTP_MSG_ERROR;
txn->status = 400;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, txn->status, http_error_message(s));
sess->fe->fe_counters.failed_req++;
if (sess->listener->counters)
@@ -4356,7 +4383,7 @@ int http_process_req_common(struct stream *s, struct
channel *req, int an_bit, s
if (unlikely(!stream_int_register_handler(&s->si[1],
objt_applet(s->target)))) {
txn->status = 500;
s->logs.tv_request = now;
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_500));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_RESOURCE;
@@ -4496,7 +4523,7 @@ int http_process_req_common(struct stream *s, struct
channel *req, int an_bit, s
txn->flags |= TX_CLDENY;
txn->status = http_err_codes[deny_status];
s->logs.tv_request = now;
- http_reply_and_close(s, txn->status, http_error_message(s,
deny_status));
+ http_reply_and_close(s, txn->status, http_error_message(s));
stream_inc_http_err_ctr(s);
sess->fe->fe_counters.denied_req++;
if (sess->fe != s->be)
@@ -4517,7 +4544,7 @@ int http_process_req_common(struct stream *s, struct
channel *req, int an_bit, s
txn->req.err_state = txn->req.msg_state;
txn->req.msg_state = HTTP_MSG_ERROR;
txn->status = 400;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, txn->status, http_error_message(s));
sess->fe->fe_counters.failed_req++;
if (sess->listener->counters)
@@ -4587,7 +4614,7 @@ int http_process_request(struct stream *s, struct channel
*req, int an_bit)
txn->req.msg_state = HTTP_MSG_ERROR;
txn->status = 500;
req->analysers &= AN_REQ_FLT_END;
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_500));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_RESOURCE;
@@ -4863,7 +4890,7 @@ int http_process_request(struct stream *s, struct channel
*req, int an_bit)
txn->req.msg_state = HTTP_MSG_ERROR;
txn->status = 400;
req->analysers &= AN_REQ_FLT_END;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, txn->status, http_error_message(s));
sess->fe->fe_counters.failed_req++;
if (sess->listener->counters)
@@ -4904,7 +4931,7 @@ int http_process_tarpit(struct stream *s, struct channel
*req, int an_bit)
txn->status = 500;
if (!(req->flags & CF_READ_ERROR))
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_500));
+ http_reply_and_close(s, txn->status, http_error_message(s));
req->analysers &= AN_REQ_FLT_END;
req->analyse_exp = TICK_ETERNITY;
@@ -5020,7 +5047,7 @@ int http_wait_for_request_body(struct stream *s, struct
channel *req, int an_bit
if ((req->flags & CF_READ_TIMEOUT) || tick_is_expired(req->analyse_exp,
now_ms)) {
txn->status = 408;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_408));
+ http_reply_and_close(s, txn->status, http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_CLITO;
@@ -5054,7 +5081,7 @@ int http_wait_for_request_body(struct stream *s, struct
channel *req, int an_bit
txn->req.err_state = txn->req.msg_state;
txn->req.msg_state = HTTP_MSG_ERROR;
txn->status = 400;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, txn->status, http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
@@ -5859,7 +5886,7 @@ int http_request_forward_body(struct stream *s, struct
channel *req, int an_bit)
http_reply_and_close(s, txn->status, NULL);
} else {
txn->status = 400;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_400));
+ http_reply_and_close(s, txn->status, http_error_message(s));
}
req->analysers &= AN_REQ_FLT_END;
s->res.analysers &= AN_RES_FLT_END; /* we're in data phase, we want to
abort both directions */
@@ -5882,7 +5909,7 @@ int http_request_forward_body(struct stream *s, struct
channel *req, int an_bit)
http_reply_and_close(s, txn->status, NULL);
} else {
txn->status = 502;
- http_reply_and_close(s, txn->status, http_error_message(s,
HTTP_ERR_502));
+ http_reply_and_close(s, txn->status, http_error_message(s));
}
req->analysers &= AN_REQ_FLT_END;
s->res.analysers &= AN_RES_FLT_END; /* we're in data phase, we want to
abort both directions */
@@ -6025,7 +6052,7 @@ int http_wait_for_response(struct stream *s, struct
channel *rep, int an_bit)
txn->status = 502;
s->si[1].flags |= SI_FL_NOLINGER;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_502));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
@@ -6060,7 +6087,7 @@ int http_wait_for_response(struct stream *s, struct
channel *rep, int an_bit)
txn->status = 502;
s->si[1].flags |= SI_FL_NOLINGER;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_502));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_SRVCL;
@@ -6085,7 +6112,7 @@ int http_wait_for_response(struct stream *s, struct
channel *rep, int an_bit)
txn->status = 504;
s->si[1].flags |= SI_FL_NOLINGER;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_504));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_SRVTO;
@@ -6106,7 +6133,7 @@ int http_wait_for_response(struct stream *s, struct
channel *rep, int an_bit)
txn->status = 400;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_400));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_CLICL;
@@ -6135,7 +6162,7 @@ int http_wait_for_response(struct stream *s, struct
channel *rep, int an_bit)
txn->status = 502;
s->si[1].flags |= SI_FL_NOLINGER;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_502));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_SRVCL;
@@ -6617,7 +6644,7 @@ int http_process_res_common(struct stream *s, struct
channel *rep, int an_bit, s
s->logs.t_data = -1; /* was not a valid
response */
s->si[1].flags |= SI_FL_NOLINGER;
channel_truncate(rep);
- http_reply_and_close(s, txn->status,
http_error_message(s, HTTP_ERR_502));
+ http_reply_and_close(s, txn->status,
http_error_message(s));
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
if (!(s->flags & SF_FINST_MASK))
--
1.8.3.1
>From c7a1b63bb3c748929db38c8ff23f145be687b018 Mon Sep 17 00:00:00 2001
From: Jarno Huuskonen <[email protected]>
Date: Mon, 6 Mar 2017 14:56:36 +0200
Subject: [PATCH 2/2] MINOR: http-request tarpit deny_status.
X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4
Implements deny_status for http-request tarpit rule (allows setting
custom http status code). This commit depends on:
MEDIUM: http_error_message: txn->status / http_get_status_idx.
---
doc/configuration.txt | 7 ++++---
src/proto_http.c | 26 ++++++++++++++++----------
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 25167cc..18a30f7 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3646,8 +3646,8 @@ http-check send-state
See also : "option httpchk", "http-check disable-on-404"
-http-request { allow | tarpit | auth [realm <realm>] | redirect <rule> |
- deny [deny_status <status>] |
+http-request { allow | auth [realm <realm>] | redirect <rule> |
+ tarpit [deny_status <status>] | deny [deny_status <status>] |
add-header <name> <fmt> | set-header <name> <fmt> |
capture <sample> [ len <length> | id <id> ] |
del-header <name> | set-nice <nice> | set-log-level <level> |
@@ -3691,7 +3691,8 @@ http-request { allow | tarpit | auth [realm <realm>] |
redirect <rule> |
- "tarpit" : this stops the evaluation of the rules and immediately blocks
the request without responding for a delay specified by "timeout tarpit"
or "timeout connect" if the former is not set. After that delay, if the
- client is still connected, an HTTP error 500 is returned so that the
+ client is still connected, an HTTP error 500 (or optionally the status
+ code specified as an argument to "deny_status") is returned so that the
client does not suspect it has been tarpitted. Logs will report the flags
"PT". The goal of the tarpit rule is to slow down robots during an attack
when they're limited on the number of concurrent requests. It can be very
diff --git a/src/proto_http.c b/src/proto_http.c
index 9239823..7fed50e 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -4410,8 +4410,10 @@ int http_process_req_common(struct stream *s, struct
channel *req, int an_bit, s
if (txn->flags & TX_CLDENY)
goto deny;
- if (txn->flags & TX_CLTARPIT)
+ if (txn->flags & TX_CLTARPIT) {
+ deny_status = HTTP_ERR_500;
goto tarpit;
+ }
}
/* add request headers from the rule sets in the same order */
@@ -4500,6 +4502,8 @@ int http_process_req_common(struct stream *s, struct
channel *req, int an_bit, s
if (s->be->cookie_name || sess->fe->capture_name)
manage_client_side_cookies(s, req);
+ txn->status = http_err_codes[deny_status];
+
req->analysers &= AN_REQ_FLT_END; /* remove switching rules etc... */
req->analysers |= AN_REQ_HTTP_TARPIT;
req->analyse_exp = tick_add_ifset(now_ms, s->be->timeout.tarpit);
@@ -4929,7 +4933,6 @@ int http_process_tarpit(struct stream *s, struct channel
*req, int an_bit)
*/
s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now);
- txn->status = 500;
if (!(req->flags & CF_READ_ERROR))
http_reply_and_close(s, txn->status, http_error_message(s));
@@ -9075,15 +9078,21 @@ struct act_rule *parse_http_req_cond(const char **args,
const char *file, int li
goto out_err;
}
- rule->deny_status = HTTP_ERR_403;
if (!strcmp(args[0], "allow")) {
rule->action = ACT_ACTION_ALLOW;
cur_arg = 1;
- } else if (!strcmp(args[0], "deny") || !strcmp(args[0], "block")) {
+ } else if (!strcmp(args[0], "deny") || !strcmp(args[0], "block") ||
!strcmp(args[0], "tarpit")) {
int code;
int hc;
- rule->action = ACT_ACTION_DENY;
+ if (!strcmp(args[0], "tarpit")) {
+ rule->action = ACT_HTTP_REQ_TARPIT;
+ rule->deny_status = HTTP_ERR_500;
+ }
+ else {
+ rule->action = ACT_ACTION_DENY;
+ rule->deny_status = HTTP_ERR_403;
+ }
cur_arg = 1;
if (strcmp(args[cur_arg], "deny_status") == 0) {
cur_arg++;
@@ -9103,13 +9112,10 @@ struct act_rule *parse_http_req_cond(const char **args,
const char *file, int li
}
if (hc >= HTTP_ERR_SIZE) {
- Warning("parsing [%s:%d] : status code %d not
handled, using default code 403.\n",
- file, linenum, code);
+ Warning("parsing [%s:%d] : status code %d not
handled, using default code %d.\n",
+ file, linenum, code,
http_err_codes[rule->deny_status]);
}
}
- } else if (!strcmp(args[0], "tarpit")) {
- rule->action = ACT_HTTP_REQ_TARPIT;
- cur_arg = 1;
} else if (!strcmp(args[0], "auth")) {
rule->action = ACT_HTTP_REQ_AUTH;
cur_arg = 1;
--
1.8.3.1