MEDIUM: session: add support for tunnel timeouts

Tunnel timeouts are used when TCP connections are forwarded, or
when forwarding upgraded HTTP connections (WebSocket) as well as
CONNECT requests to proxies.

This timeout allows long-lived sessions to be supported without
having to set large timeouts to normal requests.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9aa7e1c..99e4f85 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1117,6 +1117,7 @@
 timeout server                            X          -         X         X
 timeout srvtimeout          (deprecated)  X          -         X         X
 timeout tarpit                            X          X         X         X
+timeout tunnel                            X          -         X         X
 transparent                 (deprecated)  X          -         X         X
 unique-id-format                          X          X         X         -
 unique-id-header                          X          X         X         -
@@ -3551,7 +3552,7 @@
   data sent to the server. Doing so will typically break large HTTP posts from
   slow lines, so use it with caution.
 
-  See also : "timeout client" and "timeout server"
+  See also : "timeout client", "timeout server" and "timeout tunnel"
 
 
 option ldap-check
@@ -5014,7 +5015,8 @@
   This parameter is provided for compatibility but is currently deprecated.
   Please use "timeout server" instead.
 
-  See also : "timeout server", "timeout client" and "clitimeout".
+  See also : "timeout server", "timeout tunnel", "timeout client" and
+             "clitimeout".
 
 
 stats admin { if | unless } <cond>
@@ -6334,7 +6336,9 @@
   client timeout remains equal to the server timeout in order to avoid complex
   situations to debug. It is a good practice to cover one or several TCP packet
   losses by specifying timeouts that are slightly above multiples of 3 seconds
-  (eg: 4 or 5 seconds).
+  (eg: 4 or 5 seconds). If some long-lived sessions are mixed with short-lived
+  sessions (eg: WebSocket and HTTP), it's worth considering "timeout tunnel",
+  which overrides "timeout client" and "timeout server" for tunnels.
 
   This parameter is specific to frontends, but can be specified once for all in
   "defaults" sections. This is in fact one of the easiest solutions not to
@@ -6347,7 +6351,7 @@
   to use it to write new configurations. The form "timeout clitimeout" is
   provided only by backwards compatibility but its use is strongly discouraged.
 
-  See also : "clitimeout", "timeout server".
+  See also : "clitimeout", "timeout server", "timeout tunnel".
 
 
 timeout connect <timeout>
@@ -6508,7 +6512,10 @@
   order to avoid complex situations to debug. Whatever the expected server
   response times, it is a good practice to cover at least one or several TCP
   packet losses by specifying timeouts that are slightly above multiples of 3
-  seconds (eg: 4 or 5 seconds minimum).
+  seconds (eg: 4 or 5 seconds minimum). If some long-lived sessions are mixed
+  with short-lived sessions (eg: WebSocket and HTTP), it's worth considering
+  "timeout tunnel", which overrides "timeout client" and "timeout server" for
+  tunnels.
 
   This parameter is specific to backends, but can be specified once for all in
   "defaults" sections. This is in fact one of the easiest solutions not to
@@ -6521,7 +6528,7 @@
   to use it to write new configurations. The form "timeout srvtimeout" is
   provided only by backwards compatibility but its use is strongly discouraged.
 
-  See also : "srvtimeout", "timeout client".
+  See also : "srvtimeout", "timeout client" and "timeout tunnel".
 
 
 timeout tarpit <timeout>
@@ -6546,6 +6553,47 @@
   See also : "timeout connect", "contimeout".
 
 
+timeout tunnel <timeout>
+  Set the maximum inactivity time on the client and server side for tunnels.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+  Arguments :
+    <timeout> is the timeout value specified in milliseconds by default, but
+              can be in any other unit if the number is suffixed by the unit,
+              as explained at the top of this document.
+
+  The tunnel timeout applies when a bidirectionnal connection is established
+  between a client and a server, and the connection remains inactive in both
+  directions. This timeout supersedes both the client and server timeouts once
+  the connection becomes a tunnel. In TCP, this timeout is used as soon as no
+  analyser remains attached to either connection (eg: tcp content rules are
+  accepted). In HTTP, this timeout is used when a connection is upgraded (eg:
+  when switching to the WebSocket protocol, or forwarding a CONNECT request
+  to a proxy), or after the first response when no keepalive/close option is
+  specified.
+
+  The value is specified in milliseconds by default, but can be in any other
+  unit if the number is suffixed by the unit, as specified at the top of this
+  document. Whatever the expected normal idle time, it is a good practice to
+  cover at least one or several TCP packet losses by specifying timeouts that
+  are slightly above multiples of 3 seconds (eg: 4 or 5 seconds minimum).
+
+  This parameter is specific to backends, but can be specified once for all in
+  "defaults" sections. This is in fact one of the easiest solutions not to
+  forget about it.
+
+  Example :
+        defaults http
+            option http-server-close
+            timeout connect 5s
+            timeout client 30s
+            timeout client 30s
+            timeout server 30s
+            timeout tunnel  1h    # timeout to use with WebSocket and CONNECT
+
+  See also : "timeout client", "timeout server".
+
+
 transparent (deprecated)
   Enable client-side transparent proxying
   May be used in sections :   defaults | frontend | listen | backend
diff --git a/include/proto/proxy.h b/include/proto/proxy.h
index b2e97fe..d5fd3e0 100644
--- a/include/proto/proxy.h
+++ b/include/proto/proxy.h
@@ -69,6 +69,7 @@
 	proxy->timeout.appsession = TICK_ETERNITY;
 	proxy->timeout.httpreq = TICK_ETERNITY;
 	proxy->timeout.check = TICK_ETERNITY;
+	proxy->timeout.tunnel = TICK_ETERNITY;
 }
 
 /* increase the number of cumulated connections received on the designated frontend */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 26af501..a94eee7 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -263,6 +263,7 @@
 		int httpreq;                    /* maximum time for complete HTTP request */
 		int httpka;                     /* maximum time for a new HTTP request when using keep-alive */
 		int check;                      /* maximum time for complete check */
+		int tunnel;                     /* I/O timeout to use in tunnel mode (in ticks) */
 	} timeout;
 	char *id, *desc;			/* proxy id (name) and description */
 	struct list pendconns;			/* pending connections with no server assigned yet */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 5d12056..49d1dbe 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1547,6 +1547,7 @@
 			curproxy->timeout.tarpit = defproxy.timeout.tarpit;
 			curproxy->timeout.httpreq = defproxy.timeout.httpreq;
 			curproxy->timeout.httpka = defproxy.timeout.httpka;
+			curproxy->timeout.tunnel = defproxy.timeout.tunnel;
 			curproxy->source_addr = defproxy.source_addr;
 		}
 
@@ -6127,7 +6128,8 @@
 		if ((curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) &&
 		    (((curproxy->cap & PR_CAP_FE) && !curproxy->timeout.client) ||
 		     ((curproxy->cap & PR_CAP_BE) && (curproxy->srv) &&
-		      (!curproxy->timeout.connect || !curproxy->timeout.server)))) {
+		      (!curproxy->timeout.connect ||
+		       (!curproxy->timeout.server && (curproxy->mode == PR_MODE_HTTP || !curproxy->timeout.tunnel)))))) {
 			Warning("config : missing timeouts for %s '%s'.\n"
 				"   | While not properly invalid, you will certainly encounter various problems\n"
 				"   | with such a configuration. To fix this, please ensure that all following\n"
diff --git a/src/proxy.c b/src/proxy.c
index 9ae9889..bc5779f 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -183,10 +183,14 @@
 		tv = &proxy->timeout.queue;
 		td = &defpx->timeout.queue;
 		cap = PR_CAP_BE;
+	} else if (!strcmp(args[0], "tunnel")) {
+		tv = &proxy->timeout.tunnel;
+		td = &defpx->timeout.tunnel;
+		cap = PR_CAP_BE;
 	} else {
 		memprintf(err,
 		          "'timeout' supports 'client', 'server', 'connect', 'check', "
-		          "'queue', 'http-keep-alive', 'http-request' or 'tarpit', (got '%s')",
+		          "'queue', 'http-keep-alive', 'http-request', 'tunnel' or 'tarpit', (got '%s')",
 		          args[0]);
 		return -1;
 	}
diff --git a/src/session.c b/src/session.c
index 088b436..ae20e5b 100644
--- a/src/session.c
+++ b/src/session.c
@@ -2001,6 +2001,16 @@
 		 */
 		if (!(s->rep->flags & (BF_SHUTR|BF_SHUTW_NOW)))
 			buffer_forward(s->rep, BUF_INFINITE_FORWARD);
+
+		/* if we have no analyser anymore in any direction and have a
+		 * tunnel timeout set, use it now.
+		 */
+		if (!s->req->analysers && s->be->timeout.tunnel) {
+			s->req->rto = s->req->wto = s->rep->rto = s->rep->wto =
+				s->be->timeout.tunnel;
+			s->req->rex = s->req->wex = s->rep->rex = s->rep->wex =
+				tick_add(now_ms, s->be->timeout.tunnel);
+		}
 	}
 
 	/* check if it is wise to enable kernel splicing to forward response data */