MEDIUM: tcp: add the "tfo" option to support TCP fastopen on the server
This implements support for the new API which relies on a call to
setsockopt().
On systems that support it (currently, only Linux >= 4.11), this enables
using TCP fast open when connecting to server.
Please note that you should use the retry-on "conn-failure", "empty-response"
and "response-timeout" keywords, or the request won't be able to be retried
on failure.
Co-authored-by: Olivier Houchard <ohouchard@haproxy.com>
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 95f345e..2993e6e 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -12506,6 +12506,14 @@
argument is a delay expressed in milliseconds by default. This only works for
regular TCP connections, and is ignored for other protocols.
+tfo
+ This option enables using TCP fast open when connecting to servers, on
+ systems that support it (currently only the Linux kernel >= 4.11).
+ See the "tfo" bind option for more information about TCP fast open.
+ Please note that when using tfo, you should also use the "conn-failure",
+ "empty-response" and "response-timeout" keywords for "retry-on", or haproxy
+ won't be able to retry the connection on failure.
+
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/common/compat.h b/include/common/compat.h
index 8a7bbd5..1401f91 100644
--- a/include/common/compat.h
+++ b/include/common/compat.h
@@ -128,6 +128,10 @@
#ifndef TCP_FASTOPEN
#define TCP_FASTOPEN 23
#endif
+
+#ifndef TCP_FASTOPEN_CONNECT
+#define TCP_FASTOPEN_CONNECT 30
+#endif
#endif
/* FreeBSD doesn't define SOL_IP and prefers IPPROTO_IP */
diff --git a/include/proto/stream_interface.h b/include/proto/stream_interface.h
index 8a4b77a..2f6a7a5 100644
--- a/include/proto/stream_interface.h
+++ b/include/proto/stream_interface.h
@@ -484,13 +484,17 @@
static inline int si_connect(struct stream_interface *si, struct connection *conn)
{
int ret = SF_ERR_NONE;
+ int conn_flags = 0;
if (unlikely(!conn || !conn->ctrl || !conn->ctrl->connect))
return SF_ERR_INTERNAL;
+ if (!channel_is_empty(si_oc(si)))
+ conn_flags |= CONNECT_HAS_DATA;
+ if (si->conn_retries == si_strm(si)->be->conn_retries)
+ conn_flags |= CONNECT_CAN_USE_TFO;
if (!conn_ctrl_ready(conn) || !conn_xprt_ready(conn)) {
- ret = conn->ctrl->connect(conn, channel_is_empty(si_oc(si)) ?
- CONNECT_HAS_DATA : 0);
+ ret = conn->ctrl->connect(conn, conn_flags);
if (ret != SF_ERR_NONE)
return ret;
diff --git a/include/types/protocol.h b/include/types/protocol.h
index a33d129..1d3404b 100644
--- a/include/types/protocol.h
+++ b/include/types/protocol.h
@@ -88,6 +88,7 @@
#define CONNECT_HAS_DATA 0x00000001 /* There's data available to be sent */
#define CONNECT_DELACK_SMART_CONNECT 0x00000002 /* Use a delayed ACK if the backend has tcp-smart-connect */
#define CONNECT_DELACK_ALWAYS 0x00000004 /* Use a delayed ACK */
+#define CONNECT_CAN_USE_TFO 0x00000008 /* We can use TFO for this connection */
#endif /* _TYPES_PROTOCOL_H */
/*
diff --git a/include/types/server.h b/include/types/server.h
index 7835f11..1a7109a 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -143,6 +143,7 @@
#define SRV_F_CHECKPORT 0x0040 /* this server has a check port configured */
#define SRV_F_AGENTADDR 0x0080 /* this server has a agent addr configured */
#define SRV_F_COOKIESET 0x0100 /* this server has a cookie configured, so don't generate dynamic cookies */
+#define SRV_F_FASTOPEN 0x0100 /* Use TCP Fast Open to connect to server */
/* configured server options for send-proxy (server->pp_opts) */
#define SRV_PP_V1 0x0001 /* proxy protocol version 1 */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index dd99bbb..24b5714 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -3121,6 +3121,14 @@
cfgerr += xprt_get(XPRT_SSL)->prepare_srv(newsrv);
}
+ if ((newsrv->flags & SRV_F_FASTOPEN) &&
+ ((curproxy->retry_type & (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)) !=
+ (PR_RE_DISCONNECTED | PR_RE_TIMEOUT)))
+ ha_warning("parsing [%s:%d] : %s '%s': server '%s' has tfo activated, the backend should be configured with at least 'conn-failure', 'empty-response' and 'response-timeout' or we wouldn't be able to retry the connection on failure.\n",
+ newsrv->conf.file, newsrv->conf.line,
+ proxy_type_str(curproxy), curproxy->id,
+ newsrv->id);
+
/* set the check type on the server */
newsrv->check.type = curproxy->options2 & PR_O2_CHK_ANY;
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index e668a85..95068ee 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -293,6 +293,7 @@
struct server *srv;
struct proxy *be;
struct conn_src *src;
+ int use_fastopen = 0;
conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
@@ -304,6 +305,14 @@
case OBJ_TYPE_SERVER:
srv = objt_server(conn->target);
be = srv->proxy;
+ /* Make sure we check that we have data before activating
+ * TFO, or we could trigger a kernel issue whereby after
+ * a successful connect() == 0, any subsequent connect()
+ * will return EINPROGRESS instead of EISCONN.
+ */
+ use_fastopen = (srv->flags & SRV_F_FASTOPEN) &&
+ ((flags & (CONNECT_CAN_USE_TFO | CONNECT_HAS_DATA)) ==
+ (CONNECT_CAN_USE_TFO | CONNECT_HAS_DATA));
break;
default:
conn->flags |= CO_FL_ERROR;
@@ -493,6 +502,12 @@
if (srv && srv->tcp_ut)
setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &srv->tcp_ut, sizeof(srv->tcp_ut));
#endif
+
+ if (use_fastopen) {
+#if defined(TCP_FASTOPEN_CONNECT)
+ setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one));
+#endif
+ }
if (global.tune.server_sndbuf)
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &global.tune.server_sndbuf, sizeof(global.tune.server_sndbuf));
diff --git a/src/raw_sock.c b/src/raw_sock.c
index 6c5a634..6dde3f8 100644
--- a/src/raw_sock.c
+++ b/src/raw_sock.c
@@ -402,7 +402,7 @@
if (ret < try)
break;
}
- else if (ret == 0 || errno == EAGAIN || errno == ENOTCONN) {
+ else if (ret == 0 || errno == EAGAIN || errno == ENOTCONN || errno == EINPROGRESS) {
/* nothing written, we need to poll for write first */
fd_cant_send(conn->handle.fd);
break;
diff --git a/src/server.c b/src/server.c
index 9916361..a9e7a42 100644
--- a/src/server.c
+++ b/src/server.c
@@ -889,6 +889,13 @@
}
+/* parse the "tfo" server keyword */
+static int srv_parse_tfo(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+ newsrv->flags |= SRV_F_FASTOPEN;
+ return 0;
+}
+
/* Shutdown all connections of a server. The caller must pass a termination
* code in <why>, which must be one of SF_ERR_* indicating the reason for the
* shutdown.
@@ -1277,6 +1284,7 @@
{ "send-proxy-v2", srv_parse_send_proxy_v2, 0, 1 }, /* Enforce use of PROXY V2 protocol */
{ "source", srv_parse_source, -1, 1 }, /* Set the source address to be used to connect to the server */
{ "stick", srv_parse_stick, 0, 1 }, /* Enable stick-table persistence */
+ { "tfo", srv_parse_tfo, 0, 0 }, /* enable TCP Fast Open of server */
{ "track", srv_parse_track, 1, 1 }, /* Set the current state of the server, tracking another one */
{ NULL, NULL, 0 },
}};