Skip to content

Commit dcbdc68

Browse files
committed
TS-4882: Implement TCP Fast Open for server sockets.
Add TCP Fast Open support to the proxy.config.net.sock_option_flag_out configuration option. Implement detection of TFO support in SocketManager and use that to guarantee that we don't try to use TFO on hosts where is is not supported or enabled. Add TFO support to both SSLNetVConnection and UnixNetVConnection, enabling it if the corresponding NetVCOption is set. We only enable TFO on server sessions that are going to do TLS or are handling idempotent HTTP methods.
1 parent 346b419 commit dcbdc68

File tree

12 files changed

+334
-16
lines changed

12 files changed

+334
-16
lines changed

doc/admin-guide/files/records.config.en.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,13 +3405,19 @@ Sockets
34053405
TCP_NODELAY (1)
34063406
SO_KEEPALIVE (2)
34073407
SO_LINGER (4) - with a timeout of 0 seconds
3408+
TCP_FASTOPEN (8)
34083409

34093410
.. note::
34103411

34113412
This is a bitmask and you need to decide what bits to set. Therefore,
34123413
you must set the value to ``3`` if you want to enable nodelay and
34133414
keepalive options above.
34143415

3416+
.. note::
3417+
3418+
To allow TCP Fast Open for client sockets on Linux, bit 2 of
3419+
the ``net.ipv4.tcp_fastopen`` sysctl must be set.
3420+
34153421
.. ts:cv:: CONFIG proxy.config.net.sock_send_buffer_size_out INT 0
34163422
:overridable:
34173423

@@ -3431,6 +3437,7 @@ Sockets
34313437
TCP_NODELAY (1)
34323438
SO_KEEPALIVE (2)
34333439
SO_LINGER (4) - with a timeout of 0 seconds
3440+
TCP_FASTOPEN (8)
34343441

34353442
.. note::
34363443

@@ -3443,6 +3450,11 @@ Sockets
34433450
are co-located and large numbers of sockets are retained
34443451
in the TIME_WAIT state.
34453452

3453+
.. note::
3454+
3455+
To allow TCP Fast Open for server sockets on Linux, bit 1 of
3456+
the ``net.ipv4.tcp_fastopen`` sysctl must be set.
3457+
34463458
.. ts:cv:: CONFIG proxy.config.net.sock_mss_in INT 0
34473459
34483460
Same as the command line option ``--accept_mss`` that sets the MSS for all incoming requests.

iocore/eventsystem/I_SocketManager.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@
4545
#define SOCK_CLOEXEC O_CLOEXEC
4646
#endif
4747

48+
#ifndef MSG_FASTOPEN
49+
#if defined(linux)
50+
#define MSG_FASTOPEN 0x20000000
51+
#else
52+
#define MSG_FASTOPEN 0
53+
#endif
54+
#endif
55+
4856
#define DEFAULT_OPEN_MODE 0644
4957

5058
class Thread;
@@ -57,6 +65,9 @@ extern int net_config_poll_timeout;
5765
struct SocketManager {
5866
SocketManager();
5967

68+
// Test whether TCP Fast Open is supported on this host.
69+
static bool fastopen_supported();
70+
6071
// result is the socket or -errno
6172
SOCKET socket(int domain = AF_INET, int type = SOCK_STREAM, int protocol = 0);
6273
SOCKET mc_socket(int domain = AF_INET, int type = SOCK_DGRAM, int protocol = 0, bool bNonBlocking = true);
@@ -89,23 +100,27 @@ struct SocketManager {
89100
int ftruncate(int fildes, off_t length);
90101
int lockf(int fildes, int function, off_t size);
91102
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
103+
92104
#if TS_USE_EPOLL
93105
int epoll_create(int size);
94106
int epoll_close(int eps);
95107
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
96108
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout = net_config_poll_timeout);
97109
#endif
110+
98111
#if TS_USE_KQUEUE
99112
int kqueue();
100113
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents,
101114
const struct timespec *timeout);
102115
#endif
116+
103117
#if TS_USE_PORT
104118
int port_create();
105119
int port_associate(int port, int fd, uintptr_t obj, int events, void *user);
106120
int port_dissociate(int port, int fd, uintptr_t obj);
107121
int port_getn(int port, port_event_t *list, uint_t max, uint_t *nget, timespec_t *timeout);
108122
#endif
123+
109124
int shutdown(int s, int how);
110125
int dup(int s);
111126

iocore/eventsystem/SocketManager.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "ts/ink_platform.h"
2929
#include "P_EventSystem.h"
3030

31+
#include "ts/TextBuffer.h"
32+
#include "ts/TestBox.h"
33+
3134
SocketManager socketManager;
3235

3336
#if !HAVE_ACCEPT4
@@ -108,3 +111,36 @@ SocketManager::close(int s)
108111
} while (res == -EINTR);
109112
return res;
110113
}
114+
115+
bool
116+
SocketManager::fastopen_supported()
117+
{
118+
static const unsigned TFO_CLIENT_ENABLE = 1;
119+
120+
ats_scoped_fd fd(::open("/proc/sys/net/ipv4/tcp_fastopen", O_RDONLY));
121+
int value = 0;
122+
123+
if (fd) {
124+
textBuffer buffer(16);
125+
126+
buffer.slurp(fd.get());
127+
value = atoi(buffer.bufPtr());
128+
}
129+
130+
return value & TFO_CLIENT_ENABLE;
131+
}
132+
133+
REGRESSION_TEST(socket_fastopen)(RegressionTest *test, int level, int *pstatus)
134+
{
135+
TestBox box(test, pstatus);
136+
137+
box = REGRESSION_TEST_PASSED;
138+
139+
if (SocketManager::fastopen_supported()) {
140+
box.check(MSG_FASTOPEN != 0, "TCP Fast Open is supported, MSG_FASTOPEN must not be 0");
141+
}
142+
143+
if (::access("/proc/sys/net/ipv4/tcp_fastopen", F_OK) == 0) {
144+
box.check(MSG_FASTOPEN != 0, "TCP Fast Open is available, MSG_FASTOPEN must not be 0");
145+
}
146+
}

iocore/net/BIO_fastopen.cc

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/** @file
2+
*
3+
* OpenSSL socket BIO that does TCP Fast Open.
4+
*
5+
* @section license License
6+
*
7+
* Licensed to the Apache Software Foundation (ASF) under one
8+
* or more contributor license agreements. See the NOTICE file
9+
* distributed with this work for additional information
10+
* regarding copyright ownership. The ASF licenses this file
11+
* to you under the Apache License, Version 2.0 (the
12+
* "License"); you may not use this file except in compliance
13+
* with the License. You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*/
23+
24+
#include "I_Net.h"
25+
#include "I_SocketManager.h"
26+
#include "ts/ink_assert.h"
27+
28+
#include "BIO_fastopen.h"
29+
30+
static int
31+
fastopen_create(BIO *bio)
32+
{
33+
bio->init = 0;
34+
bio->num = NO_FD;
35+
bio->flags = 0;
36+
bio->ptr = NULL;
37+
38+
return 1;
39+
}
40+
41+
static int
42+
fastopen_destroy(BIO *bio)
43+
{
44+
if (bio) {
45+
// We expect this BIO to not own the socket, so we must always
46+
// be in NOCLOSE mode.
47+
ink_assert(bio->shutdown == BIO_NOCLOSE);
48+
fastopen_create(bio);
49+
}
50+
51+
return 1;
52+
}
53+
54+
static int
55+
fastopen_bwrite(BIO *bio, const char *in, int insz)
56+
{
57+
int64_t err;
58+
59+
errno = 0;
60+
BIO_clear_retry_flags(bio);
61+
ink_assert(bio->num != NO_FD);
62+
63+
if (bio->ptr) {
64+
// On the first write only, make a TFO request if TFO is enabled.
65+
// The best documentation on the behavior of the Linux API is in
66+
// RFC 7413. If we get EINPROGRESS it means that the SYN has been
67+
// sent without data dn we sshould retry.
68+
const sockaddr *dst = reinterpret_cast<const sockaddr *>(bio->ptr);
69+
70+
err = socketManager.sendto(bio->num, (void *)in, insz, MSG_FASTOPEN, dst, ats_ip_size(dst));
71+
72+
bio->ptr = NULL;
73+
} else {
74+
err = socketManager.write(bio->num, (void *)in, insz);
75+
}
76+
77+
if (err < 0) {
78+
errno = -err;
79+
if (BIO_sock_non_fatal_error(errno)) {
80+
BIO_set_retry_write(bio);
81+
}
82+
}
83+
84+
return err < 0 ? -1 : err;
85+
}
86+
87+
static int
88+
fastopen_bread(BIO *bio, char *out, int outsz)
89+
{
90+
int64_t err;
91+
92+
errno = 0;
93+
BIO_clear_retry_flags(bio);
94+
ink_assert(bio->num != NO_FD);
95+
96+
// TODO: If we haven't done the fastopen, ink_abort().
97+
98+
err = socketManager.read(bio->num, out, outsz);
99+
if (err < 0) {
100+
errno = -err;
101+
if (BIO_sock_non_fatal_error(errno)) {
102+
BIO_set_retry_write(bio);
103+
}
104+
}
105+
106+
return err < 0 ? -1 : err;
107+
}
108+
109+
static long
110+
fastopen_ctrl(BIO *bio, int cmd, long larg, void *ptr)
111+
{
112+
switch (cmd) {
113+
case BIO_C_SET_FD:
114+
ink_assert(larg == BIO_CLOSE || larg == BIO_NOCLOSE);
115+
ink_assert(bio->num == NO_FD);
116+
117+
bio->init = 1;
118+
bio->shutdown = larg;
119+
bio->num = *reinterpret_cast<int *>(ptr);
120+
return 0;
121+
122+
case BIO_C_SET_CONNECT:
123+
// We only support BIO_set_conn_address(), which sets a sockaddr.
124+
ink_assert(larg == 2);
125+
bio->ptr = ptr;
126+
return 0;
127+
128+
// We are unbuffered so unconditionally succeed on BIO_flush().
129+
case BIO_CTRL_FLUSH:
130+
return 1;
131+
132+
case BIO_CTRL_PUSH:
133+
case BIO_CTRL_POP:
134+
return 0;
135+
136+
default:
137+
#if DEBUG
138+
ink_abort("unsupported BIO control cmd=%d larg=%ld ptr=%p", cmd, larg, ptr);
139+
#endif
140+
141+
return 0;
142+
}
143+
}
144+
145+
static const BIO_METHOD fastopen_methods = {
146+
.type = BIO_TYPE_SOCKET,
147+
.name = "fastopen",
148+
.bwrite = fastopen_bwrite,
149+
.bread = fastopen_bread,
150+
.bputs = NULL,
151+
.bgets = NULL,
152+
.ctrl = fastopen_ctrl,
153+
.create = fastopen_create,
154+
.destroy = fastopen_destroy,
155+
.callback_ctrl = NULL,
156+
};
157+
158+
const BIO_METHOD *
159+
BIO_s_fastopen()
160+
{
161+
return &fastopen_methods;
162+
}

iocore/net/BIO_fastopen.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/** @file
2+
*
3+
* OpenSSL socket BIO that does TCP Fast Open.
4+
*
5+
* @section license License
6+
*
7+
* Licensed to the Apache Software Foundation (ASF) under one
8+
* or more contributor license agreements. See the NOTICE file
9+
* distributed with this work for additional information
10+
* regarding copyright ownership. The ASF licenses this file
11+
* to you under the Apache License, Version 2.0 (the
12+
* "License"); you may not use this file except in compliance
13+
* with the License. You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*/
23+
24+
#ifndef BIO_FASTOPEN_H_DA87E080_DB75_4C9C_959A_40243592D454
25+
#define BIO_FASTOPEN_H_DA87E080_DB75_4C9C_959A_40243592D454
26+
27+
#include <openssl/bio.h>
28+
29+
// Return a BIO_METHOD for a socket BIO that implements TCP Fast Open.
30+
const BIO_METHOD *BIO_s_fastopen();
31+
32+
// OpenSSL 1.0.2h has BIO_set_conn_ip(), but master has BIO_set_conn_address(). Use
33+
// the API from master since it makes more sense.
34+
#if !defined(BIO_set_conn_address)
35+
#define BIO_set_conn_address(b, addr) BIO_ctrl(b, BIO_C_SET_CONNECT, 2, (char *)addr)
36+
#endif
37+
38+
#endif /* BIO_FASTOPEN_H_DA87E080_DB75_4C9C_959A_40243592D454 */

iocore/net/I_NetVConnection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ struct NetVCOptions {
142142
/// Make socket block on connect (default: @c false)
143143
bool f_blocking_connect;
144144

145+
// Use TCP Fast Open on this socket. The connect(2) call will be omitted.
146+
bool f_tcp_fastopen = false;
147+
145148
/// Control use of SOCKS.
146149
/// Set to @c NO_SOCKS to disable use of SOCKS. Otherwise SOCKS is
147150
/// used if available.

iocore/net/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ test_certlookup_LDADD = \
5050
$(top_builddir)/iocore/eventsystem/libinkevent.a
5151

5252
libinknet_a_SOURCES = \
53+
BIO_fastopen.cc \
54+
BIO_fastopen.h \
5355
Connection.cc \
5456
I_Net.h \
5557
I_NetProcessor.h \

iocore/net/SSLNetVConnection.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "P_SSLUtils.h"
2929
#include "InkAPIInternal.h" // Added to include the ssl_hook definitions
3030
#include "P_SSLConfig.h"
31+
#include "BIO_fastopen.h"
3132
#include "Log.h"
3233

3334
#include <climits>
@@ -143,7 +144,14 @@ make_ssl_connection(SSL_CTX *ctx, SSLNetVConnection *netvc)
143144

144145
// Only set up the bio stuff for the server side
145146
if (netvc->get_context() == NET_VCONNECTION_OUT) {
146-
SSL_set_fd(ssl, netvc->get_socket());
147+
BIO *bio = BIO_new(const_cast<BIO_METHOD *>(BIO_s_fastopen()));
148+
BIO_set_fd(bio, netvc->get_socket(), BIO_NOCLOSE);
149+
150+
if (netvc->options.f_tcp_fastopen) {
151+
BIO_set_conn_address(bio, netvc->get_remote_addr());
152+
}
153+
154+
SSL_set_bio(ssl, bio, bio);
147155
} else {
148156
netvc->initialize_handshake_buffers();
149157
BIO *rbio = BIO_new(BIO_s_mem());

0 commit comments

Comments
 (0)