Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net-tcp: Fast Open client - detecting SYN-data drops

On paths with firewalls dropping SYN with data or experimental TCP options,
Fast Open connections will have experience SYN timeout and bad performance.
The solution is to track such incidents in the cookie cache and disables
Fast Open temporarily.

Since only the original SYN includes data and/or Fast Open option, the
SYN-ACK has some tell-tale sign (tcp_rcv_fastopen_synack()) to detect
such drops. If a path has recurring Fast Open SYN drops, Fast Open is
disabled for 2^(recurring_losses) minutes starting from four minutes up to
roughly one and half day. sendmsg with MSG_FASTOPEN flag will succeed but
it behaves as connect() then write().

Signed-off-by: Yuchung Cheng <ycheng@google.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Yuchung Cheng and committed by
David S. Miller
aab48743 cf60af03

+37 -8
+4 -2
include/net/tcp.h
··· 409 409 extern bool tcp_remember_stamp(struct sock *sk); 410 410 extern bool tcp_tw_remember_stamp(struct inet_timewait_sock *tw); 411 411 extern void tcp_fastopen_cache_get(struct sock *sk, u16 *mss, 412 - struct tcp_fastopen_cookie *cookie); 412 + struct tcp_fastopen_cookie *cookie, 413 + int *syn_loss, unsigned long *last_syn_loss); 413 414 extern void tcp_fastopen_cache_set(struct sock *sk, u16 mss, 414 - struct tcp_fastopen_cookie *cookie); 415 + struct tcp_fastopen_cookie *cookie, 416 + bool syn_lost); 415 417 extern void tcp_fetch_timewait_stamp(struct sock *sk, struct dst_entry *dst); 416 418 extern void tcp_disable_fack(struct tcp_sock *tp); 417 419 extern void tcp_close(struct sock *sk, long timeout);
+9 -1
net/ipv4/tcp_input.c
··· 5652 5652 struct tcp_sock *tp = tcp_sk(sk); 5653 5653 struct sk_buff *data = tcp_write_queue_head(sk); 5654 5654 u16 mss = tp->rx_opt.mss_clamp; 5655 + bool syn_drop; 5655 5656 5656 5657 if (mss == tp->rx_opt.user_mss) { 5657 5658 struct tcp_options_received opt; ··· 5665 5664 mss = opt.mss_clamp; 5666 5665 } 5667 5666 5668 - tcp_fastopen_cache_set(sk, mss, cookie); 5667 + /* The SYN-ACK neither has cookie nor acknowledges the data. Presumably 5668 + * the remote receives only the retransmitted (regular) SYNs: either 5669 + * the original SYN-data or the corresponding SYN-ACK is lost. 5670 + */ 5671 + syn_drop = (cookie->len <= 0 && data && 5672 + inet_csk(sk)->icsk_retransmits); 5673 + 5674 + tcp_fastopen_cache_set(sk, mss, cookie, syn_drop); 5669 5675 5670 5676 if (data) { /* Retransmit unacked data in SYN */ 5671 5677 tcp_retransmit_skb(sk, data);
+13 -3
net/ipv4/tcp_metrics.c
··· 32 32 33 33 struct tcp_fastopen_metrics { 34 34 u16 mss; 35 + u16 syn_loss:10; /* Recurring Fast Open SYN losses */ 36 + unsigned long last_syn_loss; /* Last Fast Open SYN loss */ 35 37 struct tcp_fastopen_cookie cookie; 36 38 }; 37 39 ··· 127 125 tm->tcpm_ts = 0; 128 126 tm->tcpm_ts_stamp = 0; 129 127 tm->tcpm_fastopen.mss = 0; 128 + tm->tcpm_fastopen.syn_loss = 0; 130 129 tm->tcpm_fastopen.cookie.len = 0; 131 130 } 132 131 ··· 647 644 static DEFINE_SEQLOCK(fastopen_seqlock); 648 645 649 646 void tcp_fastopen_cache_get(struct sock *sk, u16 *mss, 650 - struct tcp_fastopen_cookie *cookie) 647 + struct tcp_fastopen_cookie *cookie, 648 + int *syn_loss, unsigned long *last_syn_loss) 651 649 { 652 650 struct tcp_metrics_block *tm; 653 651 ··· 663 659 if (tfom->mss) 664 660 *mss = tfom->mss; 665 661 *cookie = tfom->cookie; 662 + *syn_loss = tfom->syn_loss; 663 + *last_syn_loss = *syn_loss ? tfom->last_syn_loss : 0; 666 664 } while (read_seqretry(&fastopen_seqlock, seq)); 667 665 } 668 666 rcu_read_unlock(); 669 667 } 670 668 671 - 672 669 void tcp_fastopen_cache_set(struct sock *sk, u16 mss, 673 - struct tcp_fastopen_cookie *cookie) 670 + struct tcp_fastopen_cookie *cookie, bool syn_lost) 674 671 { 675 672 struct tcp_metrics_block *tm; 676 673 ··· 684 679 tfom->mss = mss; 685 680 if (cookie->len > 0) 686 681 tfom->cookie = *cookie; 682 + if (syn_lost) { 683 + ++tfom->syn_loss; 684 + tfom->last_syn_loss = jiffies; 685 + } else 686 + tfom->syn_loss = 0; 687 687 write_sequnlock_bh(&fastopen_seqlock); 688 688 } 689 689 rcu_read_unlock();
+11 -2
net/ipv4/tcp_output.c
··· 2860 2860 { 2861 2861 struct tcp_sock *tp = tcp_sk(sk); 2862 2862 struct tcp_fastopen_request *fo = tp->fastopen_req; 2863 - int space, i, err = 0, iovlen = fo->data->msg_iovlen; 2863 + int syn_loss = 0, space, i, err = 0, iovlen = fo->data->msg_iovlen; 2864 2864 struct sk_buff *syn_data = NULL, *data; 2865 + unsigned long last_syn_loss = 0; 2865 2866 2866 - tcp_fastopen_cache_get(sk, &tp->rx_opt.mss_clamp, &fo->cookie); 2867 + tcp_fastopen_cache_get(sk, &tp->rx_opt.mss_clamp, &fo->cookie, 2868 + &syn_loss, &last_syn_loss); 2869 + /* Recurring FO SYN losses: revert to regular handshake temporarily */ 2870 + if (syn_loss > 1 && 2871 + time_before(jiffies, last_syn_loss + (60*HZ << syn_loss))) { 2872 + fo->cookie.len = -1; 2873 + goto fallback; 2874 + } 2875 + 2867 2876 if (fo->cookie.len <= 0) 2868 2877 goto fallback; 2869 2878