From efc843e57ea96ea198c63398c454430bc9e6cbcc Mon Sep 17 00:00:00 2001 From: Willy Tarreau 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 d5ecf6c..b4b7701 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -8637,6 +8637,19 @@ strict-sni a certificate. The default certificate is not used. See the "crt" option for more information. +tcp-ut + 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 cfa62f7..e98a9fb 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -838,6 +838,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 */ @@ -1986,8 +1995,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; @@ -2056,6 +2093,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.9.3