MEDIUM: server: implement TCP_USER_TIMEOUT on the server

This is equivalent to commit 2af207a ("MEDIUM: tcp: implement tcp-ut
bind option to set TCP_USER_TIMEOUT") except that this time it works
on the server side. The purpose is to detect dead server connections
even when checks are rare, disabled, or after a soft reload (since
checks are disabled there as well), and to ensure client connections
will get killed faster.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index b34a80c..c6219d2 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10598,6 +10598,21 @@
 
   Supported in default-server: No
 
+tcp-ut <delay>
+  Sets the TCP User Timeout for all outgoing connections to this server. 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 server has disappeared in order to release all resources
+  associated with its connection (and the client's session). One typical use
+  case is also to force dead server connections to die when health checks are
+  too slow or during a soft reload since health checks are then disabled. The
+  argument is a delay expressed in milliseconds by default. This only works for
+  regular TCP connections, and is ignored for other protocols.
+
 track [<proxy>/]<server>
   This option enables ability to set the current state of the server by tracking
   another one. It is possible to track a server which itself tracks another
diff --git a/include/types/server.h b/include/types/server.h
index abd1130..3e25c34 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -216,6 +216,7 @@
 	time_t last_change;			/* last time, when the state was changed */
 
 	int puid;				/* proxy-unique server ID, used for SNMP, and "first" LB algo */
+	int tcp_ut;                             /* for TCP, user timeout */
 
 	struct check check;                     /* health-check specific configuration */
 	struct check agent;                     /* agent specific configuration */
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index df10ccb..cce0acb 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -39,7 +39,6 @@
 
 #include <types/global.h>
 #include <types/capture.h>
-#include <types/server.h>
 #include <types/connection.h>
 
 #include <proto/acl.h>
@@ -56,6 +55,7 @@
 #include <proto/proto_tcp.h>
 #include <proto/proxy.h>
 #include <proto/sample.h>
+#include <proto/server.h>
 #include <proto/stream.h>
 #include <proto/stick_table.h>
 #include <proto/stream_interface.h>
@@ -500,6 +500,11 @@
                 setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
 #endif
 
+#ifdef TCP_USER_TIMEOUT
+	/* there is not much more we can do here when it fails, it's still minor */
+	if (srv && srv->tcp_ut)
+		setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &srv->tcp_ut, sizeof(srv->tcp_ut));
+#endif
 	if (global.tune.server_sndbuf)
                 setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &global.tune.server_sndbuf, sizeof(global.tune.server_sndbuf));
 
@@ -2310,6 +2315,31 @@
 }
 #endif
 
+#ifdef TCP_USER_TIMEOUT
+/* parse the "tcp-ut" server keyword */
+static int srv_parse_tcp_ut(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+	const char *ptr = NULL;
+	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;
+	}
+
+	if (newsrv->addr.ss_family == AF_INET || newsrv->addr.ss_family == AF_INET6)
+		newsrv->tcp_ut = timeout;
+
+	return 0;
+}
+#endif
+
 static struct cfg_kw_list cfg_kws = {ILH, {
 	{ CFG_LISTEN, "tcp-request",  tcp_parse_tcp_req },
 	{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
@@ -2385,6 +2415,12 @@
 	{ NULL, NULL, 0 },
 }};
 
+static struct srv_kw_list srv_kws = { "TCP", { }, {
+#ifdef TCP_USER_TIMEOUT
+	{ "tcp-ut",        srv_parse_tcp_ut,        1,  0 }, /* set TCP user timeout on server */
+#endif
+	{ NULL, NULL, 0 },
+}};
 
 static struct action_kw_list tcp_req_conn_actions = {ILH, {
 	{ "silent-drop", tcp_parse_silent_drop },
@@ -2421,6 +2457,7 @@
 	cfg_register_keywords(&cfg_kws);
 	acl_register_keywords(&acl_kws);
 	bind_register_keywords(&bind_kws);
+	srv_register_keywords(&srv_kws);
 	tcp_req_conn_keywords_register(&tcp_req_conn_actions);
 	tcp_req_cont_keywords_register(&tcp_req_cont_actions);
 	tcp_res_cont_keywords_register(&tcp_res_cont_actions);