From: Wei Wang <redacted>
The patch series is to add support for new userspace API for TCP fastopen
sockets.
In the current code, user has to call sendto()/sendmsg() with special flag
MSG_FASTOPEN for TCP fastopen sockets. This API is quite different from the
normal TCP socket API and can be cumbersome for applications to make use
fastopen sockets.
So this new patch introduces a new way of using TCP fastopen sockets which
is similar to normal TCP sockets with a new sockopt TCP_FASTOPEN_CONNECT.
More details about it is described in the third patch.
(First 2 patches are preparations for the third patch.)
Wei Wang (3):
net/tcp-fastopen: refactor cookie check logic
net: Remove __sk_dst_reset() in tcp_v6_connect()
net/tcp-fastopen: Add new API support
include/linux/tcp.h | 3 ++-
include/net/inet_sock.h | 6 +++++-
include/net/tcp.h | 3 +++
include/uapi/linux/tcp.h | 1 +
net/ipv4/af_inet.c | 31 ++++++++++++++++++++-------
net/ipv4/tcp.c | 35 ++++++++++++++++++++++++++++++-
net/ipv4/tcp_fastopen.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++
net/ipv4/tcp_ipv4.c | 7 ++++++-
net/ipv4/tcp_output.c | 16 ++------------
net/ipv6/tcp_ipv6.c | 6 +++++-
10 files changed, 136 insertions(+), 26 deletions(-)
--
2.11.0.483.g087da7b7c-goog
From: Wei Wang <redacted>
Refactor the cookie check logic in tcp_send_syn_data() into a function.
This function will be called else where in later changes.
Signed-off-by: Wei Wang <redacted>
---
include/net/tcp.h | 2 ++
net/ipv4/tcp_fastopen.c | 21 +++++++++++++++++++++
net/ipv4/tcp_output.c | 16 ++--------------
3 files changed, 25 insertions(+), 14 deletions(-)
@@ -325,3 +325,24 @@ struct sock *tcp_try_fastopen(struct sock *sk, struct sk_buff *skb,*foc=valid_foc;returnNULL;}++booltcp_fastopen_cookie_check(structsock*sk,u16*mss,+structtcp_fastopen_cookie*cookie)+{+unsignedlonglast_syn_loss=0;+intsyn_loss=0;++tcp_fastopen_cache_get(sk,mss,cookie,&syn_loss,&last_syn_loss);++/* Recurring FO SYN losses: no cookie or data in SYN */+if(syn_loss>1&&+time_before(jiffies,last_syn_loss+(60*HZ<<syn_loss))){+cookie->len=-1;+returnfalse;+}+if(sysctl_tcp_fastopen&TFO_CLIENT_NO_COOKIE){+cookie->len=-1;+returntrue;+}+returncookie->len>0;+}
@@ -3267,23 +3267,11 @@ static int tcp_send_syn_data(struct sock *sk, struct sk_buff *syn){structtcp_sock*tp=tcp_sk(sk);structtcp_fastopen_request*fo=tp->fastopen_req;-intsyn_loss=0,space,err=0;-unsignedlonglast_syn_loss=0;+intspace,err=0;structsk_buff*syn_data;tp->rx_opt.mss_clamp=tp->advmss;/* If MSS is not cached */-tcp_fastopen_cache_get(sk,&tp->rx_opt.mss_clamp,&fo->cookie,-&syn_loss,&last_syn_loss);-/* Recurring FO SYN losses: revert to regular handshake temporarily */-if(syn_loss>1&&-time_before(jiffies,last_syn_loss+(60*HZ<<syn_loss))){-fo->cookie.len=-1;-gotofallback;-}--if(sysctl_tcp_fastopen&TFO_CLIENT_NO_COOKIE)-fo->cookie.len=-1;-elseif(fo->cookie.len<=0)+if(!tcp_fastopen_cookie_check(sk,&tp->rx_opt.mss_clamp,&fo->cookie))gotofallback;/* MSS for SYN-data is based on cached MSS and bounded by PMTU and
From: Wei Wang <redacted>
Remove __sk_dst_reset() in the failure handling because __sk_dst_reset()
will eventually get called when sk is released. No need to handle it in
the protocol specific connect call.
This is also to make the code path consistent with ipv4.
Signed-off-by: Wei Wang <redacted>
---
net/ipv6/tcp_ipv6.c | 1 -
1 file changed, 1 deletion(-)
From: Wei Wang <redacted>
This patch adds a new socket option, TCP_FASTOPEN_CONNECT, as an
alternative way to perform Fast Open on the active side (client). Prior
to this patch, a client needs to replace the connect() call with
sendto(MSG_FASTOPEN). This can be cumbersome for applications who want
to use Fast Open: these socket operations are often done in lower layer
libraries used by many other applications. Changing these libraries
and/or the socket call sequences are not trivial. A more convenient
approach is to perform Fast Open by simply enabling a socket option when
the socket is created w/o changing other socket calls sequence:
s = socket()
create a new socket
setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN_CONNECT …);
newly introduced sockopt
If set, new functionality described below will be used.
Return ENOTSUPP if TFO is not supported or not enabled in the
kernel.
connect()
With cookie present, return 0 immediately.
With no cookie, initiate 3WHS with TFO cookie-request option and
return -1 with errno = EINPROGRESS.
write()/sendmsg()
With cookie present, send out SYN with data and return the number of
bytes buffered.
With no cookie, and 3WHS not yet completed, return -1 with errno =
EINPROGRESS.
No MSG_FASTOPEN flag is needed.
read()
Return -1 with errno = EWOULDBLOCK/EAGAIN if connect() is called but
write() is not called yet.
Return -1 with errno = EWOULDBLOCK/EAGAIN if connection is
established but no msg is received yet.
Return number of bytes read if socket is established and there is
msg received.
The new API simplifies life for applications that always perform a write()
immediately after a successful connect(). Such applications can now take
advantage of Fast Open by merely making one new setsockopt() call at the time
of creating the socket. Nothing else about the application's socket call
sequence needs to change.
Signed-off-by: Wei Wang <redacted>
---
include/linux/tcp.h | 3 ++-
include/net/inet_sock.h | 6 +++++-
include/net/tcp.h | 1 +
include/uapi/linux/tcp.h | 1 +
net/ipv4/af_inet.c | 31 ++++++++++++++++++++++++-------
net/ipv4/tcp.c | 35 ++++++++++++++++++++++++++++++++++-
net/ipv4/tcp_fastopen.c | 33 +++++++++++++++++++++++++++++++++
net/ipv4/tcp_ipv4.c | 7 ++++++-
net/ipv6/tcp_ipv6.c | 5 +++++
9 files changed, 111 insertions(+), 11 deletions(-)
@@ -222,7 +222,8 @@ struct tcp_sock {u32chrono_stat[3];/* Time in jiffies for chrono_stat stats */u8chrono_type:2,/* current chronograph type */rate_app_limited:1,/* rate_{delivered,interval_us} limited? */-unused:5;+fastopen_connect:1,/* FASTOPEN_CONNECT sockopt */+unused:4;u8nonagle:4,/* Disable Nagle algorithm? */thin_lto:1,/* Use linear timeouts for thin streams */unused1:1,
@@ -116,6 +116,7 @@ enum {#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */#define TCP_REPAIR_WINDOW 29 /* Get/set window parameters */+#define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect */structtcp_repair_opt{__u32opt_code;
@@ -593,7 +604,10 @@ int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,err=-EISCONN;gotoout;caseSS_CONNECTING:-err=-EALREADY;+if(inet_sk(sk)->defer_connect)+err=-EINPROGRESS;+else+err=-EALREADY;/* Fall out of switch with err, set for this state */break;caseSS_UNCONNECTED:
@@ -607,6 +621,9 @@ int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,sock->state=SS_CONNECTING;+if(!err&&inet_sk(sk)->defer_connect)+gotoout;+/* Just entered SS_CONNECTING state; the only*differenceisthatreturnvalueinnon-blocking*caseisEINPROGRESS,ratherthanEALREADY.
@@ -533,6 +533,12 @@ unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)if(tp->urg_data&TCP_URG_VALID)mask|=POLLPRI;+}elseif(sk->sk_state==TCP_SYN_SENT&&inet_sk(sk)->defer_connect){+/* Active TCP fastopen socket with defer_connect+*ReturnPOLLOUTsoapplicationcancallwrite()+*inorderforkerneltogenerateSYN+data+*/+mask|=POLLOUT|POLLWRNORM;}/* This barrier is coupled with smp_wmb() in tcp_reset() */smp_rmb();
@@ -1085,9 +1092,19 @@ static int tcp_sendmsg_fastopen(struct sock *sk, struct msghdr *msg,tp->fastopen_req->data=msg;tp->fastopen_req->size=size;+if(inet->defer_connect){+err=tcp_connect(sk);+/* Same failure procedure as in tcp_v4/6_connect */+if(err){+tcp_set_state(sk,TCP_CLOSE);+inet->inet_dport=0;+sk->sk_route_caps=0;+}+}flags=(msg->msg_flags&MSG_DONTWAIT)?O_NONBLOCK:0;err=__inet_stream_connect(sk->sk_socket,msg->msg_name,msg->msg_namelen,flags);+inet->defer_connect=0;*copied=tp->fastopen_req->copied;tcp_free_fastopen_req(tp);returnerr;
@@ -346,3 +346,36 @@ bool tcp_fastopen_cookie_check(struct sock *sk, u16 *mss,}returncookie->len>0;}++/* This function checks if we want to defer sending SYN until the first+*write().Wedeferunderthefollowingconditions:+*1.fastopen_connectsockoptisset+*2.wehaveavalidcookie+*Returnvalue:returntrueifwewanttodeferuntilapplicationwritesdata+*returnfalseifwewanttosendoutSYNimmediately+*/+booltcp_fastopen_defer_connect(structsock*sk,int*err)+{+structtcp_fastopen_cookiecookie={.len=0};+structtcp_sock*tp=tcp_sk(sk);+u16mss;++if(tp->fastopen_connect&&!tp->fastopen_req){+if(tcp_fastopen_cookie_check(sk,&mss,&cookie)){+inet_sk(sk)->defer_connect=1;+returntrue;+}++/* Alloc fastopen_req in order for FO option to be included+*inSYN+*/+tp->fastopen_req=kzalloc(sizeof(*tp->fastopen_req),+sk->sk_allocation);+if(tp->fastopen_req)+tp->fastopen_req->cookie=cookie;+else+*err=-ENOBUFS;+}+returnfalse;+}+EXPORT_SYMBOL(tcp_fastopen_defer_connect);
@@ -232,6 +232,7 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)/* OK, now commit destination to socket. */sk->sk_gso_type=SKB_GSO_TCPV4;sk_setup_caps(sk,&rt->dst);+rt=NULL;if(!tp->write_seq&&likely(!tp->repair))tp->write_seq=secure_tcp_sequence_number(inet->inet_saddr,
@@ -242,9 +243,13 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)inet->inet_id=tp->write_seq^jiffies;+if(tcp_fastopen_defer_connect(sk,&err))+returnerr;+if(err)+gotofailure;+err=tcp_connect(sk);-rt=NULL;if(err)gotofailure;
From: Eric Dumazet <hidden> Date: 2017-01-23 19:13:38
On Mon, 2017-01-23 at 10:59 -0800, Wei Wang wrote:
From: Wei Wang <redacted>
Refactor the cookie check logic in tcp_send_syn_data() into a function.
This function will be called else where in later changes.
Signed-off-by: Wei Wang <redacted>
---
From: Eric Dumazet <hidden> Date: 2017-01-23 19:14:14
On Mon, 2017-01-23 at 10:59 -0800, Wei Wang wrote:
From: Wei Wang <redacted>
Remove __sk_dst_reset() in the failure handling because __sk_dst_reset()
will eventually get called when sk is released. No need to handle it in
the protocol specific connect call.
This is also to make the code path consistent with ipv4.
Signed-off-by: Wei Wang <redacted>
---
Suggested-by: Eric Dumazet <edumazet@google.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Thanks.
From: Eric Dumazet <hidden> Date: 2017-01-23 19:15:56
On Mon, 2017-01-23 at 10:59 -0800, Wei Wang wrote:
From: Wei Wang <redacted>
This patch adds a new socket option, TCP_FASTOPEN_CONNECT, as an
alternative way to perform Fast Open on the active side (client). Prior
to this patch, a client needs to replace the connect() call with
sendto(MSG_FASTOPEN). This can be cumbersome for applications who want
to use Fast Open: these socket operations are often done in lower layer
libraries used by many other applications. Changing these libraries
and/or the socket call sequences are not trivial. A more convenient
approach is to perform Fast Open by simply enabling a socket option when
the socket is created w/o changing other socket calls sequence:
Signed-off-by: Wei Wang <redacted>
---
Thanks for this hard work Wei.
Acked-by: Eric Dumazet <edumazet@google.com>
On Mon, Jan 23, 2017 at 10:59 AM, Wei Wang [off-list ref] wrote:
From: Wei Wang <redacted>
Refactor the cookie check logic in tcp_send_syn_data() into a function.
This function will be called else where in later changes.
Signed-off-by: Wei Wang <redacted>
---
@@ -325,3 +325,24 @@ struct sock *tcp_try_fastopen(struct sock *sk, struct sk_buff *skb,*foc=valid_foc;returnNULL;}++booltcp_fastopen_cookie_check(structsock*sk,u16*mss,+structtcp_fastopen_cookie*cookie)+{+unsignedlonglast_syn_loss=0;+intsyn_loss=0;++tcp_fastopen_cache_get(sk,mss,cookie,&syn_loss,&last_syn_loss);++/* Recurring FO SYN losses: no cookie or data in SYN */+if(syn_loss>1&&+time_before(jiffies,last_syn_loss+(60*HZ<<syn_loss))){+cookie->len=-1;+returnfalse;+}+if(sysctl_tcp_fastopen&TFO_CLIENT_NO_COOKIE){+cookie->len=-1;+returntrue;+}+returncookie->len>0;+}
@@ -3267,23 +3267,11 @@ static int tcp_send_syn_data(struct sock *sk, struct sk_buff *syn){structtcp_sock*tp=tcp_sk(sk);structtcp_fastopen_request*fo=tp->fastopen_req;-intsyn_loss=0,space,err=0;-unsignedlonglast_syn_loss=0;+intspace,err=0;structsk_buff*syn_data;tp->rx_opt.mss_clamp=tp->advmss;/* If MSS is not cached */-tcp_fastopen_cache_get(sk,&tp->rx_opt.mss_clamp,&fo->cookie,-&syn_loss,&last_syn_loss);-/* Recurring FO SYN losses: revert to regular handshake temporarily */-if(syn_loss>1&&-time_before(jiffies,last_syn_loss+(60*HZ<<syn_loss))){-fo->cookie.len=-1;-gotofallback;-}--if(sysctl_tcp_fastopen&TFO_CLIENT_NO_COOKIE)-fo->cookie.len=-1;-elseif(fo->cookie.len<=0)+if(!tcp_fastopen_cookie_check(sk,&tp->rx_opt.mss_clamp,&fo->cookie))gotofallback;/* MSS for SYN-data is based on cached MSS and bounded by PMTU and--
On Mon, Jan 23, 2017 at 10:59 AM, Wei Wang [off-list ref] wrote:
From: Wei Wang <redacted>
This patch adds a new socket option, TCP_FASTOPEN_CONNECT, as an
alternative way to perform Fast Open on the active side (client). Prior
to this patch, a client needs to replace the connect() call with
sendto(MSG_FASTOPEN). This can be cumbersome for applications who want
to use Fast Open: these socket operations are often done in lower layer
libraries used by many other applications. Changing these libraries
and/or the socket call sequences are not trivial. A more convenient
approach is to perform Fast Open by simply enabling a socket option when
the socket is created w/o changing other socket calls sequence:
s = socket()
create a new socket
setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN_CONNECT …);
newly introduced sockopt
If set, new functionality described below will be used.
Return ENOTSUPP if TFO is not supported or not enabled in the
kernel.
connect()
With cookie present, return 0 immediately.
With no cookie, initiate 3WHS with TFO cookie-request option and
return -1 with errno = EINPROGRESS.
write()/sendmsg()
With cookie present, send out SYN with data and return the number of
bytes buffered.
With no cookie, and 3WHS not yet completed, return -1 with errno =
EINPROGRESS.
No MSG_FASTOPEN flag is needed.
read()
Return -1 with errno = EWOULDBLOCK/EAGAIN if connect() is called but
write() is not called yet.
Return -1 with errno = EWOULDBLOCK/EAGAIN if connection is
established but no msg is received yet.
Return number of bytes read if socket is established and there is
msg received.
The new API simplifies life for applications that always perform a write()
immediately after a successful connect(). Such applications can now take
advantage of Fast Open by merely making one new setsockopt() call at the time
of creating the socket. Nothing else about the application's socket call
sequence needs to change.
Signed-off-by: Wei Wang <redacted>
---
Acked-by: Yuchung Cheng <redacted>
Thanks for making this happen.
@@ -222,7 +222,8 @@ struct tcp_sock {u32chrono_stat[3];/* Time in jiffies for chrono_stat stats */u8chrono_type:2,/* current chronograph type */rate_app_limited:1,/* rate_{delivered,interval_us} limited? */-unused:5;+fastopen_connect:1,/* FASTOPEN_CONNECT sockopt */+unused:4;u8nonagle:4,/* Disable Nagle algorithm? */thin_lto:1,/* Use linear timeouts for thin streams */unused1:1,
@@ -116,6 +116,7 @@ enum {#define TCP_SAVE_SYN 27 /* Record SYN headers for new connections */#define TCP_SAVED_SYN 28 /* Get SYN headers recorded for connection */#define TCP_REPAIR_WINDOW 29 /* Get/set window parameters */+#define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect */structtcp_repair_opt{__u32opt_code;
@@ -593,7 +604,10 @@ int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,err=-EISCONN;gotoout;caseSS_CONNECTING:-err=-EALREADY;+if(inet_sk(sk)->defer_connect)+err=-EINPROGRESS;+else+err=-EALREADY;/* Fall out of switch with err, set for this state */break;caseSS_UNCONNECTED:
@@ -607,6 +621,9 @@ int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,sock->state=SS_CONNECTING;+if(!err&&inet_sk(sk)->defer_connect)+gotoout;+/* Just entered SS_CONNECTING state; the only*differenceisthatreturnvalueinnon-blocking*caseisEINPROGRESS,ratherthanEALREADY.
@@ -533,6 +533,12 @@ unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)if(tp->urg_data&TCP_URG_VALID)mask|=POLLPRI;+}elseif(sk->sk_state==TCP_SYN_SENT&&inet_sk(sk)->defer_connect){+/* Active TCP fastopen socket with defer_connect+*ReturnPOLLOUTsoapplicationcancallwrite()+*inorderforkerneltogenerateSYN+data+*/+mask|=POLLOUT|POLLWRNORM;}/* This barrier is coupled with smp_wmb() in tcp_reset() */smp_rmb();
@@ -1085,9 +1092,19 @@ static int tcp_sendmsg_fastopen(struct sock *sk, struct msghdr *msg,tp->fastopen_req->data=msg;tp->fastopen_req->size=size;+if(inet->defer_connect){+err=tcp_connect(sk);+/* Same failure procedure as in tcp_v4/6_connect */+if(err){+tcp_set_state(sk,TCP_CLOSE);+inet->inet_dport=0;+sk->sk_route_caps=0;+}+}flags=(msg->msg_flags&MSG_DONTWAIT)?O_NONBLOCK:0;err=__inet_stream_connect(sk->sk_socket,msg->msg_name,msg->msg_namelen,flags);+inet->defer_connect=0;*copied=tp->fastopen_req->copied;tcp_free_fastopen_req(tp);returnerr;
@@ -346,3 +346,36 @@ bool tcp_fastopen_cookie_check(struct sock *sk, u16 *mss,}returncookie->len>0;}++/* This function checks if we want to defer sending SYN until the first+*write().Wedeferunderthefollowingconditions:+*1.fastopen_connectsockoptisset+*2.wehaveavalidcookie+*Returnvalue:returntrueifwewanttodeferuntilapplicationwritesdata+*returnfalseifwewanttosendoutSYNimmediately+*/+booltcp_fastopen_defer_connect(structsock*sk,int*err)+{+structtcp_fastopen_cookiecookie={.len=0};+structtcp_sock*tp=tcp_sk(sk);+u16mss;++if(tp->fastopen_connect&&!tp->fastopen_req){+if(tcp_fastopen_cookie_check(sk,&mss,&cookie)){+inet_sk(sk)->defer_connect=1;+returntrue;+}++/* Alloc fastopen_req in order for FO option to be included+*inSYN+*/+tp->fastopen_req=kzalloc(sizeof(*tp->fastopen_req),+sk->sk_allocation);+if(tp->fastopen_req)+tp->fastopen_req->cookie=cookie;+else+*err=-ENOBUFS;+}+returnfalse;+}+EXPORT_SYMBOL(tcp_fastopen_defer_connect);
@@ -232,6 +232,7 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)/* OK, now commit destination to socket. */sk->sk_gso_type=SKB_GSO_TCPV4;sk_setup_caps(sk,&rt->dst);+rt=NULL;if(!tp->write_seq&&likely(!tp->repair))tp->write_seq=secure_tcp_sequence_number(inet->inet_saddr,
@@ -242,9 +243,13 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)inet->inet_id=tp->write_seq^jiffies;+if(tcp_fastopen_defer_connect(sk,&err))+returnerr;+if(err)+gotofailure;+err=tcp_connect(sk);-rt=NULL;if(err)gotofailure;
Hi Wei,
first, thanks a lot for doing this, it's really awesome!
I'm testing it on 4.9 on haproxy and I met a corner case : when I
perform a connect() to a server and I have nothing to send, upon
POLLOUT notification since I have nothing to send I simply probe the
connection using connect() again to see if it returns EISCONN or
anything else. But here now I'm seeing EINPROGRESS loops.
To illustrate this, here's what I'm doing :
:8000 :8001
[ client ] ---> [ proxy ] ---> [ server ]
The proxy is configured to enable TFO to the server and the server
supports TFO as well. The proxy and the server are in fact two proxy
instances in haproxy running in the same process for convenience.
When I already have data to send here's what I'm seeing (so it works fine) :
06:29:16.861190 accept4(7, {sa_family=AF_INET, sin_port=htons(33986), sin_addr=inet_addr("192.168.0.176")}, [128->16], SOCK
_NONBLOCK) = 9
06:29:16.861277 setsockopt(9, SOL_TCP, TCP_NODELAY, [1], 4) = 0
06:29:16.861342 accept4(7, 0x7ffd0d794430, [128], SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
06:29:16.861417 recvfrom(9, "BLAH\n", 7006, 0, NULL, NULL) = 5
06:29:16.861509 recvfrom(9, 0x2619329, 7001, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
06:29:16.861657 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 10
06:29:16.861730 fcntl(10, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
06:29:16.861779 setsockopt(10, SOL_TCP, TCP_NODELAY, [1], 4) = 0
06:29:16.861882 setsockopt(10, SOL_TCP, 0x1e /* TCP_??? */, [1], 4) = 0
06:29:16.861942 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
06:29:16.862015 epoll_ctl(3, EPOLL_CTL_ADD, 9, {EPOLLIN|EPOLLRDHUP, {u32=9, u64=9}}) = 0
06:29:16.862072 epoll_wait(3, [], 200, 0) = 0
06:29:16.862126 sendto(10, "BLAH\n", 5, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = 5
06:29:16.862281 epoll_wait(3, [{EPOLLIN, {u32=8, u64=8}}], 200, 0) = 1
06:29:16.862334 recvfrom(10, 0x26173a4, 8030, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
06:29:16.862385 accept4(8, {sa_family=AF_INET, sin_port=htons(46760), sin_addr=inet_addr("127.0.0.1")}, [128->16], SOCK_NON
BLOCK) = 11
06:29:16.862450 setsockopt(11, SOL_TCP, TCP_NODELAY, [1], 4) = 0
06:29:16.862504 accept4(8, 0x7ffd0d794430, [128], SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
06:29:16.862564 recvfrom(11, "BLAH\n", 7006, 0, NULL, NULL) = 5
When I don't have data, here's what I'm seeing :
06:29:24.047801 accept4(7, {sa_family=AF_INET, sin_port=htons(33988), sin_addr=inet_addr("192.168.0.176")}, [128->16], SOCK
_NONBLOCK) = 9
06:29:24.047899 setsockopt(9, SOL_TCP, TCP_NODELAY, [1], 4) = 0
06:29:24.047966 accept4(7, 0x7ffdedb2c7f0, [128], SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable)
06:29:24.048043 recvfrom(9, 0xd31324, 7006, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
06:29:24.048281 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 10
06:29:24.048342 fcntl(10, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
06:29:24.048392 setsockopt(10, SOL_TCP, TCP_NODELAY, [1], 4) = 0
06:29:24.048447 setsockopt(10, SOL_TCP, 0x1e /* TCP_??? */, [1], 4) = 0
06:29:24.048508 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
06:29:24.048593 epoll_ctl(3, EPOLL_CTL_ADD, 9, {EPOLLIN|EPOLLRDHUP, {u32=9, u64=9}}) = 0
06:29:24.048651 epoll_wait(3, [], 200, 0) = 0
06:29:24.048699 getsockopt(10, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
06:29:24.048751 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
06:29:24.048808 epoll_ctl(3, EPOLL_CTL_ADD, 10, {EPOLLOUT, {u32=10, u64=10}}) = 0
06:29:24.048860 epoll_wait(3, [{EPOLLOUT, {u32=10, u64=10}}], 200, 1000) = 1
06:29:24.048912 getsockopt(10, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
06:29:24.048963 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
06:29:24.049018 epoll_wait(3, [{EPOLLOUT, {u32=10, u64=10}}], 200, 1000) = 1
06:29:24.049072 getsockopt(10, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
06:29:24.049122 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
I theorically understand why but I think we have something wrong here
and instead we should have -1 EISCONN (to pretend the connection is
established) or return EALREADY (to mention that a previous request was
already made and that we're waiting for the next step).
While I can instrument my connect() *not* to use TFO when connecting
without any pending data, I don't always know this (eg when I use
openssl and cross fingers so that it decides to quickly send something
on the next round).
I think it's easy to fall into this tricky corner case and am wondering
what can be done about it. Does the EINPROGRESS happen only because there
is no cookie yet ? If so, shouldn't the connect's status change in this
case ?
Thanks,
Willy
On Mon, Jan 23, 2017 at 10:59:22AM -0800, Wei Wang wrote:
This patch adds a new socket option, TCP_FASTOPEN_CONNECT, as an
alternative way to perform Fast Open on the active side (client).
Wei, I think that nothing prevents from reusin the original TCP_FASTOPEN
sockopt instead of adding a new one. The original one does this :
case TCP_FASTOPEN:
if (val >= 0 && ((1 << sk->sk_state) & (TCPF_CLOSE |
TCPF_LISTEN))) {
tcp_fastopen_init_key_once(true);
fastopen_queue_tune(sk, val);
} else {
err = -EINVAL;
}
break;
and your new option does this :
case TCP_FASTOPEN_CONNECT:
if (val > 1 || val < 0) {
err = -EINVAL;
} else if (sysctl_tcp_fastopen & TFO_CLIENT_ENABLE) {
if (sk->sk_state == TCP_CLOSE)
tp->fastopen_connect = val;
else
err = -EINVAL;
} else {
err = -EOPNOTSUPP;
}
break;
Now if we compare :
- the value ranges are the same (0,1)
- tcp_fastopen_init_key_once() only performs an initialization once
- fastopen_queue_tune() only sets sk->max_qlen based on the backlog,
this has no effect on an outgoing connection ;
- tp->fastopen_connect can be applied to a listening socket without
side effect.
Thus I think we can merge them this way :
case TCP_FASTOPEN:
if (val >= 0) {
if ((sysctl_tcp_fastopen & TFO_CLIENT_ENABLE) &&
(sk->sk_state == TCP_CLOSE)
tp->fastopen_connect = val;
if ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))) {
tcp_fastopen_init_key_once(true);
fastopen_queue_tune(sk, val);
}
} else {
err = -EINVAL;
}
break;
And for the userland, the API is even simpler because we can use the
same TCP_FASTOPEN sockopt regardless of the socket direction. Also,
I don't know if TCP_FASTOPEN is supported on simultaneous connect,
but at least if it works it would be easier to understand this way.
Do you think there's a compelling reason for adding a new option or
are you interested in a small patch to perform the change above ?
Regards,
Willy
On Mon, Jan 23, 2017 at 11:30 PM, Willy Tarreau [off-list ref] wrote:
On Mon, Jan 23, 2017 at 10:59:22AM -0800, Wei Wang wrote:
quoted
This patch adds a new socket option, TCP_FASTOPEN_CONNECT, as an
alternative way to perform Fast Open on the active side (client).
Wei, I think that nothing prevents from reusin the original TCP_FASTOPEN
sockopt instead of adding a new one. The original one does this :
case TCP_FASTOPEN:
if (val >= 0 && ((1 << sk->sk_state) & (TCPF_CLOSE |
TCPF_LISTEN))) {
tcp_fastopen_init_key_once(true);
fastopen_queue_tune(sk, val);
} else {
err = -EINVAL;
}
break;
and your new option does this :
case TCP_FASTOPEN_CONNECT:
if (val > 1 || val < 0) {
err = -EINVAL;
} else if (sysctl_tcp_fastopen & TFO_CLIENT_ENABLE) {
if (sk->sk_state == TCP_CLOSE)
tp->fastopen_connect = val;
else
err = -EINVAL;
} else {
err = -EOPNOTSUPP;
}
break;
Now if we compare :
- the value ranges are the same (0,1)
- tcp_fastopen_init_key_once() only performs an initialization once
- fastopen_queue_tune() only sets sk->max_qlen based on the backlog,
this has no effect on an outgoing connection ;
- tp->fastopen_connect can be applied to a listening socket without
side effect.
Thus I think we can merge them this way :
case TCP_FASTOPEN:
if (val >= 0) {
if ((sysctl_tcp_fastopen & TFO_CLIENT_ENABLE) &&
(sk->sk_state == TCP_CLOSE)
tp->fastopen_connect = val;
if ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN))) {
tcp_fastopen_init_key_once(true);
fastopen_queue_tune(sk, val);
}
} else {
err = -EINVAL;
}
break;
And for the userland, the API is even simpler because we can use the
same TCP_FASTOPEN sockopt regardless of the socket direction. Also,
I don't know if TCP_FASTOPEN is supported on simultaneous connect,
but at least if it works it would be easier to understand this way.
It supports partially (i.e. send SYN data but not accept simul.
SYN-data crossing).
Here is the snippet of packetdrill test we use internally:
`sysctl net.ipv4.tcp_timestamps=0`
// Cache warmup: send a Fast Open cookie request
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
0.000 sendto(3, ..., 0, MSG_FASTOPEN, ..., ...) = -1 EINPROGRESS
(Operation is now in progress)
0.000 > S 0:0(0) <mss 1460,nop,nop,sackOK,nop,wscale 8,FO,nop,nop>
0.010 < S. 123:123(0) ack 1 win 14600 <mss
1460,nop,nop,sackOK,nop,wscale 6,FO abcd1234,nop,nop>
0.010 > . 1:1(0) ack 1
0.020 close(3) = 0
0.020 > F. 1:1(0) ack 1
0.030 < F. 1:1(0) ack 2 win 92
0.030 > . 2:2(0) ack 2
//
// Test: simulatenous fast open
//
+.010 socket(..., SOCK_STREAM, IPPROTO_TCP) = 4
+.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0
+.000 sendto(4, ..., 1000, MSG_FASTOPEN, ..., ...) = 1000
+.000 > S 0:1000(1000) <mss 1460,nop,nop,sackOK,nop,wscale 8,FO
abcd1234,nop,nop>
// Simul. SYN-data crossing: we don't support that yet so ack only remote ISN
+.005 < S 1234:1734(500) win 14600 <mss 1040,nop,nop,sackOK,nop,wscale
6,FO 87654321,nop,nop>
+.000 > S. 0:0(0) ack 1235 <mss 1460,nop,nop,sackOK,nop,wscale 8>
// SYN data is never retried.
+.045 < S. 1234:1234(0) ack 1001 win 14600 <mss
940,nop,nop,sackOK,nop,wscale 6,FO 12345678,nop,nop>
+.000 > . 1001:1001(0) ack 1
// The other end retries
+.100 < P. 1:501(500) ack 1000 win 257
+.000 > . 1001:1001(0) ack 501
+.000 read(4, ..., 4096) = 500
+.000 close(4) = 0
+.000 > F. 1001:1001(0) ack 501
+.050 < F. 501:501(0) ack 1002 win 257
+.000 > . 1002:1002(0) ack 502
Do you think there's a compelling reason for adding a new option or
are you interested in a small patch to perform the change above ?
Problem is that might break existing applications that were using
TCP_FASTOPEN before a connect() (it was a NOP until now)
I prefer we use a separate new option to be 100% safe, not adding
regressions.
Only new applications, tested, will use this new feature at their risk.
06:29:24.048751 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
man connect
<quote>
Generally, connection-based protocol sockets may successfully connect()
only once;
</quote>
I would prefer we do not add yet another bit in tcp kernel sockets, to
work around some oddity in your program Willy.
06:29:24.048808 epoll_ctl(3, EPOLL_CTL_ADD, 10, {EPOLLOUT, {u32=10, u64=10}}) = 0
06:29:24.048860 epoll_wait(3, [{EPOLLOUT, {u32=10, u64=10}}], 200, 1000) = 1
06:29:24.048912 getsockopt(10, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
06:29:24.048963 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
06:29:24.049018 epoll_wait(3, [{EPOLLOUT, {u32=10, u64=10}}], 200, 1000) = 1
06:29:24.049072 getsockopt(10, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
06:29:24.049122 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
I theorically understand why but I think we have something wrong here
and instead we should have -1 EISCONN (to pretend the connection is
established) or return EALREADY (to mention that a previous request was
already made and that we're waiting for the next step).
While I can instrument my connect() *not* to use TFO when connecting
without any pending data, I don't always know this (eg when I use
openssl and cross fingers so that it decides to quickly send something
on the next round).
I think it's easy to fall into this tricky corner case and am wondering
what can be done about it. Does the EINPROGRESS happen only because there
is no cookie yet ? If so, shouldn't the connect's status change in this
case ?
Thanks,
Willy
06:29:24.048751 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
I totally agree.
man connect
<quote>
Generally, connection-based protocol sockets may successfully connect()
only once;
</quote>
I would prefer we do not add yet another bit in tcp kernel sockets, to
work around some oddity in your program Willy.
I'm fine with chasing the bug on my side and fixing it, but there's a
semantic trouble anyway with returning -EINPROGRESS :
- connect() = 0 indicates that the connection is established
- then a further connect() should return -EISCONN, and does so when
not using TFO
man connect says this regarding EINPROGRESS :
The socket is nonblocking and the connection cannot be completed immediately.
It is possible to select(2) or poll(2) for completion by selecting the
socket for writing. After select(2) indicates writability, use getsockopt(2)
to read the SO_ERROR option at level SOL_SOCKET to determine whether connect()
completed successfully (SO_ERROR is zero) or unsuccess-fully (SO_ERROR is
one of the usual error codes listed here, explaining the reason for the failure).
Here we clearly have an incompatibility between this EINPROGRESS saying
that we must poll, and poll returning POLLOUT suggesting that it's now
OK.
I'm totally fine with not using an extra bit in a scarce area, but then
we can either add an extra argument to __inet_stream_connect() to say
"this is sendmsg" or just add an extra flag in the last argument.
But in general I don't feel comfortable with a semantics that doesn't
completely match the current and documented one :-/
Thanks,
Willy
Problem is that might break existing applications that were using
TCP_FASTOPEN before a connect() (it was a NOP until now)
I prefer we use a separate new option to be 100% safe, not adding
regressions.
Only new applications, tested, will use this new feature at their risk.
That's indeed a good point. I Yuchung's comment above made me wonder
about application's portability but very few OSes will use this and in
the end it might be that portable applications will just add :
#define TCP_FASTOPEN_CONNECT TCP_FASTOPEN
For other OSes and use TCP_FASTOPEN_CONNECT only for the connect() case.
Willy
06:29:24.048751 connect(10, {sa_family=AF_INET, sin_port=htons(8001), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRES
S (Operation now in progress)
I totally agree.
quoted
man connect
<quote>
Generally, connection-based protocol sockets may successfully connect()
only once;
</quote>
I would prefer we do not add yet another bit in tcp kernel sockets, to
work around some oddity in your program Willy.
I'm fine with chasing the bug on my side and fixing it, but there's a
semantic trouble anyway with returning -EINPROGRESS :
- connect() = 0 indicates that the connection is established
- then a further connect() should return -EISCONN, and does so when
not using TFO
man connect says this regarding EINPROGRESS :
The socket is nonblocking and the connection cannot be completed immediately.
It is possible to select(2) or poll(2) for completion by selecting the
socket for writing. After select(2) indicates writability, use getsockopt(2)
to read the SO_ERROR option at level SOL_SOCKET to determine whether connect()
completed successfully (SO_ERROR is zero) or unsuccess-fully (SO_ERROR is
one of the usual error codes listed here, explaining the reason for the failure).
Here we clearly have an incompatibility between this EINPROGRESS saying
that we must poll, and poll returning POLLOUT suggesting that it's now
OK.
I'm totally fine with not using an extra bit in a scarce area, but then
we can either add an extra argument to __inet_stream_connect() to say
"this is sendmsg" or just add an extra flag in the last argument.
But in general I don't feel comfortable with a semantics that doesn't
completely match the current and documented one :-/
Thanks,
Willy
We do not return -1 / EINPROGRESS but 0
Do not call connect() twice, it is clearly not supposed to work.
Fact that it happened to work is still kept for applications not using
new features (like TCP_FASTOPEN_CONNECT), we wont break this.
I would prefer you submit _if_ needed a patch on top of Wei patch, which
was carefully tested with our ~500 packetdrill tests.
TCP_FASTOPEN_CONNECT + connect() returning 0 is already a violation of
past behavior, in the sense that no connection really happened yet.
An application exploiting this return value and consider the server is
reachable would be mistaken.
We do not support connect() + TCP_FASTOPEN_CONNECT + read(), because we
do not want to add yet another conditional test in recvmsg() fast path
for such feature, while existing sendmsg() can already be used to send a
SYN with FastOpen option.
So people are not expected to blindly add TCP_FASTOPEN_CONNECT to every
TCP socket they allocate/use. This would add too much bloat to the
kernel.
On Tue, Jan 24, 2017 at 10:51:25AM -0800, Eric Dumazet wrote:
We do not return -1 / EINPROGRESS but 0
Do not call connect() twice, it is clearly not supposed to work.
Yes it is, it normally returns -1 / EISCONN on a regular socket :
EISCONN
The socket is already connected.
Fact that it happened to work is still kept for applications not using
new features (like TCP_FASTOPEN_CONNECT), we wont break this.
Sure but as we saw, deeply burried silent bugs having no effect in
existing applications can suddenly become problematic once TFO is
enabled, and the semantics difference between the two are minimal
enough to warrant being closed.
I would prefer you submit _if_ needed a patch on top of Wei patch, which
was carefully tested with our ~500 packetdrill tests.
I totally understand and rest assured that I have a great respect for
this amount of test, which is also why I find the feature really exciting.
I'll probably propose something involving an extra argument then, this
will be much easier to review in the perspective of the existing tests.
TCP_FASTOPEN_CONNECT + connect() returning 0 is already a violation of
past behavior, in the sense that no connection really happened yet.
I agree but semantically it could be considered that it means "connect()
already called successfully, feel free to proceed with send() whenever
you want" and that's why it's appealing ;-)
An application exploiting this return value and consider the server is
reachable would be mistaken.
100% agree, I even had a private discussion regarding this, mentionning
that I already added a test in haproxy to only enable it if there are
data scheduled for leaving. In my case it's easy because I already have
the same test to decide whether or not to disable TCP_QUICKACK to save
one packet by sending the payload with the first ACK. So in short it will
be :
if (data) {
if (disable_quick_ack)
setsockopt(fd, SOL_TCP, TCP_QUICKACK, &zero, sizeof(&zero));
if (enable_fastopen)
setsockopt(fd, SOL_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(&one));
}
connect(fd, ...);
But I certainly understand that in some implementations it's could be
trickier. That just reminds me that I haven't tested it combined with
splicing. I'll have to try this.
We do not support connect() + TCP_FASTOPEN_CONNECT + read(), because we
do not want to add yet another conditional test in recvmsg() fast path
for such feature, while existing sendmsg() can already be used to send a
SYN with FastOpen option.
Yes, I think the mechanism is complex enough internally not to try to
make it even more complex :-)
So people are not expected to blindly add TCP_FASTOPEN_CONNECT to every
TCP socket they allocate/use. This would add too much bloat to the
kernel.
I really think that the true benefit of TFO is for HTTP and SSL where
the client speaks first and already has something to say when the decision
to connect is made. It should be clear in implementors' minds that it
cannot be a default setting and that it doesn't make sense.
Thanks,
Willy
From: David Miller <davem@davemloft.net> Date: 2017-01-25 19:09:51
From: Wei Wang <redacted>
Date: Mon, 23 Jan 2017 10:59:19 -0800
The patch series is to add support for new userspace API for TCP fastopen
sockets.
In the current code, user has to call sendto()/sendmsg() with special flag
MSG_FASTOPEN for TCP fastopen sockets. This API is quite different from the
normal TCP socket API and can be cumbersome for applications to make use
fastopen sockets.
So this new patch introduces a new way of using TCP fastopen sockets which
is similar to normal TCP sockets with a new sockopt TCP_FASTOPEN_CONNECT.
More details about it is described in the third patch.
(First 2 patches are preparations for the third patch.)