tcp: bug fix Fast Open client retransmission
authorYuchung Cheng <ycheng@google.com>
Thu, 6 Dec 2012 08:45:32 +0000 (08:45 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 7 Dec 2012 19:39:28 +0000 (14:39 -0500)
If SYN-ACK partially acks SYN-data, the client retransmits the
remaining data by tcp_retransmit_skb(). This increments lost recovery
state variables like tp->retrans_out in Open state. If loss recovery
happens before the retransmission is acked, it triggers the WARN_ON
check in tcp_fastretrans_alert(). For example: the client sends
SYN-data, gets SYN-ACK acking only ISN, retransmits data, sends
another 4 data packets and get 3 dupacks.

Since the retransmission is not caused by network drop it should not
update the recovery state variables. Further the server may return a
smaller MSS than the cached MSS used for SYN-data, so the retranmission
needs a loop. Otherwise some data will not be retransmitted until timeout
or other loss recovery events.

Signed-off-by: Yuchung Cheng <ycheng@google.com>
Acked-by: Neal Cardwell <ncardwell@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/tcp.h
net/ipv4/tcp_input.c
net/ipv4/tcp_output.c

index 6feeccd83dd7557abd30e32c0547f483b79bd238..4af45e33105db2ee37b8ca66d6ec4122aa487456 100644 (file)
@@ -525,6 +525,7 @@ static inline __u32 cookie_v6_init_sequence(struct sock *sk,
 extern void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
                                      int nonagle);
 extern bool tcp_may_send_now(struct sock *sk);
+extern int __tcp_retransmit_skb(struct sock *, struct sk_buff *);
 extern int tcp_retransmit_skb(struct sock *, struct sk_buff *);
 extern void tcp_retransmit_timer(struct sock *sk);
 extern void tcp_xmit_retransmit_queue(struct sock *);
index 609ff98aeb47ce99500614ff4a8a9cfe3a290d28..181fc8234a529d5b6663f683e0da19e5ab17c1e8 100644 (file)
@@ -5645,7 +5645,11 @@ static bool tcp_rcv_fastopen_synack(struct sock *sk, struct sk_buff *synack,
        tcp_fastopen_cache_set(sk, mss, cookie, syn_drop);
 
        if (data) { /* Retransmit unacked data in SYN */
-               tcp_retransmit_skb(sk, data);
+               tcp_for_write_queue_from(data, sk) {
+                       if (data == tcp_send_head(sk) ||
+                           __tcp_retransmit_skb(sk, data))
+                               break;
+               }
                tcp_rearm_rto(sk);
                return true;
        }
index 2798706cb06385aa87fdb05ba1ed2c6a0a14e6e5..948ac275b9b52ed10c1adaa9f931760a0903c788 100644 (file)
@@ -2309,12 +2309,11 @@ static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to,
  * state updates are done by the caller.  Returns non-zero if an
  * error occurred which prevented the send.
  */
-int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
+int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
 {
        struct tcp_sock *tp = tcp_sk(sk);
        struct inet_connection_sock *icsk = inet_csk(sk);
        unsigned int cur_mss;
-       int err;
 
        /* Inconslusive MTU probe */
        if (icsk->icsk_mtup.probe_size) {
@@ -2387,11 +2386,17 @@ int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
        if (unlikely(NET_IP_ALIGN && ((unsigned long)skb->data & 3))) {
                struct sk_buff *nskb = __pskb_copy(skb, MAX_TCP_HEADER,
                                                   GFP_ATOMIC);
-               err = nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) :
-                            -ENOBUFS;
+               return nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) :
+                             -ENOBUFS;
        } else {
-               err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
+               return tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
        }
+}
+
+int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
+{
+       struct tcp_sock *tp = tcp_sk(sk);
+       int err = __tcp_retransmit_skb(sk, skb);
 
        if (err == 0) {
                /* Update global TCP statistics. */