diff --git a/doc/configuration.txt b/doc/configuration.txt index 7c87dc82f..a591a21f9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -788,6 +788,8 @@ option forwardfor X X X X option httpchk X - X X [no] option http-server- close X X X X +[no] option http-use-proxy- + header X X X - [no] option httpclose X X X X option httplog X X X X [no] option http_proxy X X X X @@ -2623,6 +2625,35 @@ no option http-server-close See also : "option forceclose" and "option httpclose" +option http-use-proxy-header +[no] option http-use-proxy-header + Make use of non-standard Proxy-Connection header instead of Connection + May be used in sections : defaults | frontend | listen | backend + yes | yes | yes | no + Arguments : none + + While RFC2616 explicitly states that HTTP/1.1 agents must use the + Connection header to indicate their wish of persistent or non-persistent + connections, both browsers and proxies ignore this header for proxied + connections and make use of the undocumented, non-standard Proxy-Connection + header instead. The issue begins when trying to put a load balancer between + browsers and such proxies, because there will be a difference between what + haproxy understands and what the client and the proxy agree on. + + By setting this option in a frontend, haproxy can automatically switch to use + that non-standard header if it sees proxied requests. A proxied request is + defined here as one where the URI begins with neither a '/' nor a '*'. The + choice of header only affects requests passing through proxies making use of + one of the "httpclose", "forceclose" and "http-server-close" options. Note + that this option can only be specified in a frontend and will affect the + request along its whole life. + + This option should normally never be used, except in front of a proxy. + + See also : "option httpclose", "option forceclose" and "option + http-server-close". + + option httpclose no option httpclose Enable or disable passive HTTP connection closing diff --git a/include/types/proto_http.h b/include/types/proto_http.h index ae4e1eb7d..3d38d57b2 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -98,6 +98,7 @@ #define TX_HDR_CONN_PRS 0x08000000 /* "connection" header already parsed (req or res), results below */ #define TX_HDR_CONN_CLO 0x10000000 /* "Connection: close" was present at least once */ #define TX_HDR_CONN_KAL 0x20000000 /* "Connection: keep-alive" was present at least once */ +#define TX_USE_PX_CONN 0x40000000 /* Use "Proxy-Connection" instead of "Connection" */ /* The HTTP parser is more complex than it looks like, because we have to diff --git a/include/types/proxy.h b/include/types/proxy.h index c4fb50532..0571f1747 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -135,6 +135,7 @@ #define PR_O2_AS_M_ANY 0x00010000 /* mask covering all PR_O2_AS_M_* values */ #define PR_O2_MYSQL_CHK 0x00020000 /* use MYSQL check for server health */ +#define PR_O2_USE_PXHDR 0x00040000 /* use Proxy-Connection for proxy requests */ /* end of proxy->options2 */ /* bits for sticking rules */ diff --git a/src/cfgparse.c b/src/cfgparse.c index eea9d3e7d..109c0583d 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -147,6 +147,7 @@ static const struct cfg_opt cfg_opts2[] = { "tcp-smart-accept", PR_O2_SMARTACC, PR_CAP_FE, 0 }, { "tcp-smart-connect", PR_O2_SMARTCON, PR_CAP_BE, 0 }, { "independant-streams", PR_O2_INDEPSTR, PR_CAP_FE|PR_CAP_BE, 0 }, + { "http-use-proxy-header", PR_O2_USE_PXHDR, PR_CAP_FE, 0 }, { NULL, 0, 0, 0 } }; diff --git a/src/proto_http.c b/src/proto_http.c index 7cb4faa49..b3608d4e4 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -761,8 +761,14 @@ void perform_http_redirect(struct session *s, struct stream_interface *si) memcpy(rdr.str + rdr.len, path, len); rdr.len += len; - memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23); - rdr.len += 23; + + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: close\r\n\r\n", 29); + rdr.len += 29; + } else { + memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23); + rdr.len += 23; + } /* prepare to return without error. */ si->shutr(si); @@ -1849,13 +1855,20 @@ static int http_upgrade_v09_to_v10(struct buffer *req, struct http_msg *msg, str void http_parse_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int to_del) { struct hdr_ctx ctx; + const char *hdr_val = "Connection"; + int hdr_len = 10; if (txn->flags & TX_HDR_CONN_PRS) return; + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + hdr_val = "Proxy-Connection"; + hdr_len = 16; + } + ctx.idx = 0; txn->flags &= ~(TX_CON_KAL_SET|TX_CON_CLO_SET); - while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) { + while (http_find_header2(hdr_val, hdr_len, msg->sol, &txn->hdr_idx, &ctx)) { if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) { txn->flags |= TX_HDR_CONN_KAL; if ((to_del & 2) && buf) @@ -1884,11 +1897,19 @@ void http_parse_connection_header(struct http_txn *txn, struct http_msg *msg, st void http_change_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int wanted) { struct hdr_ctx ctx; + const char *hdr_val = "Connection"; + int hdr_len = 10; ctx.idx = 0; + + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + hdr_val = "Proxy-Connection"; + hdr_len = 16; + } + txn->flags &= ~(TX_CON_CLO_SET | TX_CON_KAL_SET); - while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) { + while (http_find_header2(hdr_val, hdr_len, msg->sol, &txn->hdr_idx, &ctx)) { if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) { if (wanted & TX_CON_KAL_SET) txn->flags |= TX_CON_KAL_SET; @@ -1908,12 +1929,24 @@ void http_change_connection_header(struct http_txn *txn, struct http_msg *msg, s if ((wanted & TX_CON_CLO_SET) && !(txn->flags & TX_CON_CLO_SET)) { txn->flags |= TX_CON_CLO_SET; - http_header_add_tail2(buf, msg, &txn->hdr_idx, "Connection: close", 17); + hdr_val = "Connection: close"; + hdr_len = 17; + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + hdr_val = "Proxy-Connection: close"; + hdr_len = 23; + } + http_header_add_tail2(buf, msg, &txn->hdr_idx, hdr_val, hdr_len); } if ((wanted & TX_CON_KAL_SET) && !(txn->flags & TX_CON_KAL_SET)) { txn->flags |= TX_CON_KAL_SET; - http_header_add_tail2(buf, msg, &txn->hdr_idx, "Connection: keep-alive", 22); + hdr_val = "Connection: keep-alive"; + hdr_len = 22; + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + hdr_val = "Proxy-Connection: keep-alive"; + hdr_len = 28; + } + http_header_add_tail2(buf, msg, &txn->hdr_idx, hdr_val, hdr_len); } return; } @@ -2541,6 +2574,19 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit) /* "connection" has not been parsed yet */ txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL); + /* if the frontend has "option http-use-proxy-header", we'll check if + * we have what looks like a proxied connection instead of a connection, + * and in this case set the TX_USE_PX_CONN flag to use Proxy-connection. + * Note that this is *not* RFC-compliant, however browsers and proxies + * happen to do that despite being non-standard :-( + * We consider that a request not beginning with either '/' or '*' is + * a proxied connection, which covers both "scheme://location" and + * CONNECT ip:port. + */ + if ((s->fe->options2 & PR_O2_USE_PXHDR) && + msg->sol[msg->sl.rq.u] != '/' && msg->sol[msg->sl.rq.u] != '*') + txn->flags |= TX_USE_PX_CONN; + /* transfer length unknown*/ txn->flags &= ~TX_REQ_XFER_LEN; @@ -2940,8 +2986,13 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL)) { /* keep-alive possible */ if (!(txn->flags & TX_REQ_VER_11)) { - memcpy(rdr.str + rdr.len, "\r\nConnection: keep-alive", 24); - rdr.len += 24; + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: keep-alive", 30); + rdr.len += 30; + } else { + memcpy(rdr.str + rdr.len, "\r\nConnection: keep-alive", 24); + rdr.len += 24; + } } memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); rdr.len += 4; @@ -2956,8 +3007,13 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s break; } else { /* keep-alive not possible */ - memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23); - rdr.len += 23; + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: close\r\n\r\n", 29); + rdr.len += 29; + } else { + memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23); + rdr.len += 23; + } stream_int_retnclose(req->prod, &rdr); goto return_prx_cond; }