Blob Blame History Raw
From 6248d4bad6a79ca6a39d5a60ac038bf22eb28dae Mon Sep 17 00:00:00 2001
From: Willy Tarreau <w@1wt.eu>
Date: Wed, 4 Feb 2015 00:45:58 +0100
Subject: [PATCH] MEDIUM: tcp: implement tcp-ut bind option to set
 TCP_USER_TIMEOUT

On Linux since 2.6.37, it's possible to set the socket timeout for
pending outgoing data, with an accuracy of 1 millisecond. This is
pretty handy to deal with dead connections to clients and or servers.

For now we only implement it on the frontend side (bind line) so
that when a client disappears from the net, we're able to quickly
get rid of its connection and possibly release a server connection.
This can be useful with long-lived connections where an application
level timeout is not suited because long pauses are expected (remote
terminals, connection pools, etc).

Thanks to Thijs Houtenbos and John Eckersberg for the suggestion.
---
 doc/configuration.txt    | 13 +++++++++++++
 include/types/listener.h |  1 +
 src/proto_tcp.c          | 42 +++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 19df5ae..12b9236 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -8547,6 +8547,19 @@ strict-sni
   a certificate. The default certificate is not used.
   See the "crt" option for more information.
 
+tcp-ut <delay>
+  Sets the TCP User Timeout for all incoming connections instanciated from this
+  listening socket. This option is available on Linux since version 2.6.37. It
+  allows haproxy to configure a timeout for sockets which contain data not
+  receiving an acknoledgement for the configured delay. This is especially
+  useful on long-lived connections experiencing long idle periods such as
+  remote terminals or database connection pools, where the client and server
+  timeouts must remain high to allow a long period of idle, but where it is
+  important to detect that the client has disappeared in order to release all
+  resources associated with its connection (and the server's session). The
+  argument is a delay expressed in milliseconds by default. This only works
+  for regular TCP connections, and is ignored for other protocols.
+
 tfo
   Is an optional keyword which is supported only on Linux kernels >= 3.7. It
   enables TCP Fast Open on the listening socket, which means that clients which
diff --git a/include/types/listener.h b/include/types/listener.h
index 83b63af..2d71df6 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -175,6 +175,7 @@ struct listener {
 	struct list wait_queue;		/* link element to make the listener wait for something (LI_LIMITED)  */
 	unsigned int analysers;		/* bitmap of required protocol analysers */
 	int maxseg;			/* for TCP, advertised MSS */
+	int tcp_ut;                     /* for TCP, user timeout */
 	char *interface;		/* interface name or NULL */
 
 	struct list by_fe;              /* chaining in frontend's list of listeners */
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index 72dc92b..f833861 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -829,6 +829,15 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 		}
 	}
 #endif
+#if defined(TCP_USER_TIMEOUT)
+	if (listener->tcp_ut) {
+		if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT,
+			       &listener->tcp_ut, sizeof(listener->tcp_ut)) == -1) {
+			msg = "cannot set TCP User Timeout";
+			err |= ERR_WARN;
+		}
+	}
+#endif
 #if defined(TCP_DEFER_ACCEPT)
 	if (listener->options & LI_O_DEF_ACCEPT) {
 		/* defer accept by up to one second */
@@ -1973,8 +1982,36 @@ static int bind_parse_mss(char **args, int cur_arg, struct proxy *px, struct bin
 }
 #endif
 
+#ifdef TCP_USER_TIMEOUT
+/* parse the "tcp-ut" bind keyword */
+static int bind_parse_tcp_ut(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+	const char *ptr = NULL;
+	struct listener *l;
+	unsigned int timeout;
+
+	if (!*args[cur_arg + 1]) {
+		memprintf(err, "'%s' : missing TCP User Timeout value", args[cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	ptr = parse_time_err(args[cur_arg + 1], &timeout, TIME_UNIT_MS);
+	if (ptr) {
+		memprintf(err, "'%s' : expects a positive delay in milliseconds", args[cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	list_for_each_entry(l, &conf->listeners, by_bind) {
+		if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6)
+			l->tcp_ut = timeout;
+	}
+
+	return 0;
+}
+#endif
+
 #ifdef SO_BINDTODEVICE
-/* parse the "mss" bind keyword */
+/* parse the "interface" bind keyword */
 static int bind_parse_interface(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
 {
 	struct listener *l;
@@ -2043,6 +2080,9 @@ static struct bind_kw_list bind_kws = { "TCP", { }, {
 #ifdef TCP_MAXSEG
 	{ "mss",           bind_parse_mss,          1 }, /* set MSS of listening socket */
 #endif
+#ifdef TCP_USER_TIMEOUT
+	{ "tcp-ut",        bind_parse_tcp_ut,       1 }, /* set User Timeout on listening socket */
+#endif
 #ifdef TCP_FASTOPEN
 	{ "tfo",           bind_parse_tfo,          0 }, /* enable TCP_FASTOPEN of listening socket */
 #endif
-- 
1.8.1.4