From 294e4676a3b775a7accb50eb8428f293c218b5e2 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <w@1wt.eu>
Date: Mon, 11 May 2015 18:30:33 +0200
Subject: [PATCH 2/2] BUG/MEDIUM: http: don't forward client shutdown without
 NOLINGER except for tunnels

There's an issue related with shutting down POST transfers or closing the
connection after the end of the upload : the shutdown is forwarded to the
server regardless of the abortonclose option. The problem it causes is that
during a scan, brute force or whatever, it becomes possible that all source
ports are exhausted with all sockets in TIME_WAIT state.

There are multiple issues at once in fact :
  - no action is done for the close, it automatically happens at the lower
    layers thanks for channel_auto_close(), so we cannot act on NOLINGER ;

  - we *do* want to continue to send a clean shutdown in tunnel mode because
    some protocols transported over HTTP may need this, regardless of option
    abortonclose, thus we can't set the option inconditionally

  - for all other modes, we do want to close the dirty way because we're
    certain whether we've sent everything or not, and we don't want to eat
    all source ports.

The solution is a bit complex and applies to DONE/TUNNEL states :

  1) disable automatic close for everything not a tunnel and not just
     keep-alive / server-close. Force-close is now covered, as is HTTP/1.0
     which implicitly works in force-close mode ;

  2) when processing option abortonclose, we know we can disable lingering
     if the client has closed and the connection is not in tunnel mode.

Since the last case above leads to a situation where the client side reports
an error, we know the connection will not be reused, so leaving the flag on
the stream-interface is safe. A client closing in the middle of the data
transmission already aborts the transaction so this case is not a problem.

This fix must be backported to 1.5 where the problem was detected.
(cherry picked from commit bbfb6c40854925367ae5f9e8b22c5c9a18dc69d5)
---
 src/proto_http.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/proto_http.c b/src/proto_http.c
index 0ac3a47..5db64b5 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -5452,9 +5452,10 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
 				msg->sov -= msg->next;
 			msg->next = 0;
 
-			/* for keep-alive we don't want to forward closes on DONE */
-			if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL ||
-			    (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL)
+			/* we don't want to forward closes on DONE except in
+			 * tunnel mode.
+			 */
+			if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN)
 				channel_dont_close(req);
 			if (http_resync_states(s)) {
 				/* some state changes occurred, maybe the analyser
@@ -5478,10 +5479,15 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
 			 * want to monitor the client's connection and forward
 			 * any shutdown notification to the server, which will
 			 * decide whether to close or to go on processing the
-			 * request.
+			 * request. We only do that in tunnel mode, and not in
+			 * other modes since it can be abused to exhaust source
+			 * ports.
 			 */
 			if (s->be->options & PR_O_ABRT_CLOSE) {
 				channel_auto_read(req);
+				if ((req->flags & (CF_SHUTR|CF_READ_NULL)) &&
+				    ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN))
+					s->si[1].flags |= SI_FL_NOLINGER;
 				channel_auto_close(req);
 			}
 			else if (s->txn.meth == HTTP_METH_POST) {
-- 
2.0.5

