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