|
From f82344c1cf20afcf77e8c3df8f9d341d659da93b Mon Sep 17 00:00:00 2001
|
|
From: Christopher Faulet <cfaulet@haproxy.com>
|
|
Date: Tue, 18 Jul 2017 11:42:08 +0200
|
|
Subject: [PATCH 14/18] BUG/MEDIUM: http: Switch HTTP responses in TUNNEL mode
|
|
when body length is undefined
|
|
|
|
When the body length of a HTTP response is undefined, the HTTP parser is blocked
|
|
in the body parsing. Before HAProxy 1.7, in this case, because
|
|
AN_RES_HTTP_XFER_BODY is never set, there is no visible effect. When the server
|
|
closes its connection to terminate the response, HAProxy catches it as a normal
|
|
closure. Since 1.7, we always set this analyzer to enter at least once in
|
|
http_response_forward_body. But, in the present case, when the server connection
|
|
is closed, http_response_forward_body is called one time too many. The response
|
|
is correctly sent to the client, but an error is catched and logged with "SD--"
|
|
flags.
|
|
|
|
To reproduce the bug, you can use the configuration "tests/test-fsm.cfg". The
|
|
tests 3 and 21 hit the bug.
|
|
|
|
Idea to fix the bug is to switch the response in TUNNEL mode without switching
|
|
the request. This is possible because of previous patches.
|
|
|
|
First, we need to detect responses with undefined body length during states
|
|
synchronization. Excluding tunnelled transactions, when the response length is
|
|
undefined, TX_CON_WANT_CLO is always set on the transaction. So, when states are
|
|
synchronized, if TX_CON_WANT_CLO is set, the response is switched in TUNNEL mode
|
|
and the request remains unchanged.
|
|
|
|
Then, in http_msg_forward_body, we add a specific check to switch the response
|
|
in DONE mode if the body length is undefined and if there is no data filter.
|
|
|
|
This patch depends on following previous commits:
|
|
|
|
* MINOR: http: Switch requests/responses in TUNNEL mode only by checking txn flags
|
|
* MINOR: http: Reorder/rewrite checks in http_resync_states
|
|
|
|
This patch must be backported in 1.7 with 2 previous ones.
|
|
|
|
(cherry picked from commit 1486b0ab6de744e14ae684af105951345534f9ec)
|
|
Signed-off-by: William Lallemand <wlallemand@haproxy.org>
|
|
---
|
|
src/proto_http.c | 37 +++++++++++++++++++++++++------------
|
|
1 file changed, 25 insertions(+), 12 deletions(-)
|
|
|
|
diff --git a/src/proto_http.c b/src/proto_http.c
|
|
index 00a92cdb..e776e4d5 100644
|
|
--- a/src/proto_http.c
|
|
+++ b/src/proto_http.c
|
|
@@ -5354,7 +5354,16 @@ int http_sync_req_state(struct stream *s)
|
|
* let's enforce it now that we're not expecting any new
|
|
* data to come. The caller knows the stream is complete
|
|
* once both states are CLOSED.
|
|
+ *
|
|
+ * However, there is an exception if the response
|
|
+ * length is undefined. In this case, we need to wait
|
|
+ * the close from the server. The response will be
|
|
+ * switched in TUNNEL mode until the end.
|
|
*/
|
|
+ if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN) &&
|
|
+ txn->rsp.msg_state != HTTP_MSG_CLOSED)
|
|
+ goto check_channel_flags;
|
|
+
|
|
if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
|
|
channel_shutr_now(chn);
|
|
channel_shutw_now(chn);
|
|
@@ -5471,8 +5480,16 @@ int http_sync_res_state(struct stream *s)
|
|
* let's enforce it now that we're not expecting any new
|
|
* data to come. The caller knows the stream is complete
|
|
* once both states are CLOSED.
|
|
+ *
|
|
+ * However, there is an exception if the response length
|
|
+ * is undefined. In this case, we switch in TUNNEL mode.
|
|
*/
|
|
- if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
|
|
+ if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN)) {
|
|
+ channel_auto_read(chn);
|
|
+ txn->rsp.msg_state = HTTP_MSG_TUNNEL;
|
|
+ chn->flags |= CF_NEVER_WAIT;
|
|
+ }
|
|
+ else if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) {
|
|
channel_shutr_now(chn);
|
|
channel_shutw_now(chn);
|
|
}
|
|
@@ -6952,14 +6969,6 @@ int http_response_forward_body(struct stream *s, struct channel *res, int an_bit
|
|
if ((msg->flags & HTTP_MSGF_TE_CHNK) || (msg->flags & HTTP_MSGF_COMPRESSING))
|
|
res->flags |= CF_EXPECT_MORE;
|
|
|
|
- /* If there is neither content-length, nor transfer-encoding header
|
|
- * _AND_ there is no data filtering, we can safely forward all data
|
|
- * indefinitely. */
|
|
- if (!(msg->flags & HTTP_MSGF_XFER_LEN) && !HAS_DATA_FILTERS(s, res)) {
|
|
- buffer_flush(res->buf);
|
|
- channel_forward_forever(res);
|
|
- }
|
|
-
|
|
/* the stream handler will take care of timeouts and errors */
|
|
return 0;
|
|
|
|
@@ -7036,9 +7045,13 @@ http_msg_forward_body(struct stream *s, struct http_msg *msg)
|
|
goto missing_data_or_waiting;
|
|
}
|
|
|
|
- /* The server still sending data that should be filtered */
|
|
- if (!(msg->flags & HTTP_MSGF_XFER_LEN) && !(chn->flags & CF_SHUTR))
|
|
- goto missing_data_or_waiting;
|
|
+ /* This check can only be true for a response. HTTP_MSGF_XFER_LEN is
|
|
+ * always set for a request. */
|
|
+ if (!(msg->flags & HTTP_MSGF_XFER_LEN)) {
|
|
+ /* The server still sending data that should be filtered */
|
|
+ if (!(chn->flags & CF_SHUTR) && HAS_DATA_FILTERS(s, chn))
|
|
+ goto missing_data_or_waiting;
|
|
+ }
|
|
|
|
msg->msg_state = HTTP_MSG_ENDING;
|
|
|
|
--
|
|
2.13.0
|
|
|