[TCP]: SACK enhanced FRTO
[firefly-linux-kernel-4.4.55.git] / net / ipv4 / tcp_input.c
index b21e232d5d3337c744ed30643669bf04ca6b63f7..bb3f234668b3f9cce17c8e00ed5a45269e73b07c 100644 (file)
@@ -100,6 +100,7 @@ int sysctl_tcp_abc __read_mostly;
 #define FLAG_ECE               0x40 /* ECE in this ACK                         */
 #define FLAG_DATA_LOST         0x80 /* SACK detected data lossage.             */
 #define FLAG_SLOWPATH          0x100 /* Do not skip RFC checks for window update.*/
+#define FLAG_ONLY_ORIG_SACKED  0x200 /* SACKs only non-rexmit sent before RTO */
 
 #define FLAG_ACKED             (FLAG_DATA_ACKED|FLAG_SYN_ACKED)
 #define FLAG_NOT_DUP           (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
@@ -110,6 +111,8 @@ int sysctl_tcp_abc __read_mostly;
 #define IsFack(tp) ((tp)->rx_opt.sack_ok & 2)
 #define IsDSack(tp) ((tp)->rx_opt.sack_ok & 4)
 
+#define IsSackFrto() (sysctl_tcp_frto == 0x2)
+
 #define TCP_REMNANT (TCP_FLAG_FIN|TCP_FLAG_URG|TCP_FLAG_SYN|TCP_FLAG_PSH)
 
 /* Adapt the MSS value used to make delayed ack decision to the
@@ -1159,6 +1162,18 @@ tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb, u32 prior_snd_
                                                /* clear lost hint */
                                                tp->retransmit_skb_hint = NULL;
                                        }
+                                       /* SACK enhanced F-RTO detection.
+                                        * Set flag if and only if non-rexmitted
+                                        * segments below frto_highmark are
+                                        * SACKed (RFC4138; Appendix B).
+                                        * Clearing correct due to in-order walk
+                                        */
+                                       if (after(end_seq, tp->frto_highmark)) {
+                                               flag &= ~FLAG_ONLY_ORIG_SACKED;
+                                       } else {
+                                               if (!(sacked & TCPCB_RETRANS))
+                                                       flag |= FLAG_ONLY_ORIG_SACKED;
+                                       }
                                }
 
                                TCP_SKB_CB(skb)->sacked |= TCPCB_SACKED_ACKED;
@@ -1224,7 +1239,8 @@ tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb, u32 prior_snd_
 
        tp->left_out = tp->sacked_out + tp->lost_out;
 
-       if ((reord < tp->fackets_out) && icsk->icsk_ca_state != TCP_CA_Loss)
+       if ((reord < tp->fackets_out) && icsk->icsk_ca_state != TCP_CA_Loss &&
+           (tp->frto_highmark && after(tp->snd_una, tp->frto_highmark)))
                tcp_update_reordering(sk, ((tp->fackets_out + 1) - reord), 0);
 
 #if FASTRETRANS_DEBUG > 0
@@ -1236,9 +1252,51 @@ tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb, u32 prior_snd_
        return flag;
 }
 
-/* RTO occurred, but do not yet enter loss state. Instead, transmit two new
- * segments to see from the next ACKs whether any data was really missing.
- * If the RTO was spurious, new ACKs should arrive.
+/* F-RTO can only be used if these conditions are satisfied:
+ *  - there must be some unsent new data
+ *  - the advertised window should allow sending it
+ *  - TCP has never retransmitted anything other than head (SACK enhanced
+ *    variant from Appendix B of RFC4138 is more robust here)
+ */
+int tcp_use_frto(struct sock *sk)
+{
+       const struct tcp_sock *tp = tcp_sk(sk);
+       struct sk_buff *skb;
+
+       if (!sysctl_tcp_frto || !sk->sk_send_head ||
+               after(TCP_SKB_CB(sk->sk_send_head)->end_seq,
+                     tp->snd_una + tp->snd_wnd))
+               return 0;
+
+       if (IsSackFrto())
+               return 1;
+
+       /* Avoid expensive walking of rexmit queue if possible */
+       if (tp->retrans_out > 1)
+               return 0;
+
+       skb = skb_peek(&sk->sk_write_queue)->next;      /* Skips head */
+       sk_stream_for_retrans_queue_from(skb, sk) {
+               if (TCP_SKB_CB(skb)->sacked&TCPCB_RETRANS)
+                       return 0;
+               /* Short-circuit when first non-SACKed skb has been checked */
+               if (!(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED))
+                       break;
+       }
+       return 1;
+}
+
+/* RTO occurred, but do not yet enter Loss state. Instead, defer RTO
+ * recovery a bit and use heuristics in tcp_process_frto() to detect if
+ * the RTO was spurious. Only clear SACKED_RETRANS of the head here to
+ * keep retrans_out counting accurate (with SACK F-RTO, other than head
+ * may still have that bit set); TCPCB_LOST and remaining SACKED_RETRANS
+ * bits are handled if the Loss state is really to be entered (in
+ * tcp_enter_frto_loss).
+ *
+ * Do like tcp_enter_loss() would; when RTO expires the second time it
+ * does:
+ *  "Reduce ssthresh if it has not yet been made inside this window."
  */
 void tcp_enter_frto(struct sock *sk)
 {
@@ -1246,39 +1304,69 @@ void tcp_enter_frto(struct sock *sk)
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *skb;
 
-       tp->frto_counter = 1;
-
-       if (icsk->icsk_ca_state <= TCP_CA_Disorder ||
+       if ((!tp->frto_counter && icsk->icsk_ca_state <= TCP_CA_Disorder) ||
            tp->snd_una == tp->high_seq ||
-           (icsk->icsk_ca_state == TCP_CA_Loss && !icsk->icsk_retransmits)) {
+           ((icsk->icsk_ca_state == TCP_CA_Loss || tp->frto_counter) &&
+            !icsk->icsk_retransmits)) {
                tp->prior_ssthresh = tcp_current_ssthresh(sk);
-               tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
+               /* Our state is too optimistic in ssthresh() call because cwnd
+                * is not reduced until tcp_enter_frto_loss() when previous FRTO
+                * recovery has not yet completed. Pattern would be this: RTO,
+                * Cumulative ACK, RTO (2xRTO for the same segment does not end
+                * up here twice).
+                * RFC4138 should be more specific on what to do, even though
+                * RTO is quite unlikely to occur after the first Cumulative ACK
+                * due to back-off and complexity of triggering events ...
+                */
+               if (tp->frto_counter) {
+                       u32 stored_cwnd;
+                       stored_cwnd = tp->snd_cwnd;
+                       tp->snd_cwnd = 2;
+                       tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
+                       tp->snd_cwnd = stored_cwnd;
+               } else {
+                       tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
+               }
+               /* ... in theory, cong.control module could do "any tricks" in
+                * ssthresh(), which means that ca_state, lost bits and lost_out
+                * counter would have to be faked before the call occurs. We
+                * consider that too expensive, unlikely and hacky, so modules
+                * using these in ssthresh() must deal these incompatibility
+                * issues if they receives CA_EVENT_FRTO and frto_counter != 0
+                */
                tcp_ca_event(sk, CA_EVENT_FRTO);
        }
 
-       /* Have to clear retransmission markers here to keep the bookkeeping
-        * in shape, even though we are not yet in Loss state.
-        * If something was really lost, it is eventually caught up
-        * in tcp_enter_frto_loss.
-        */
-       tp->retrans_out = 0;
        tp->undo_marker = tp->snd_una;
        tp->undo_retrans = 0;
 
-       sk_stream_for_retrans_queue(skb, sk) {
+       skb = skb_peek(&sk->sk_write_queue);
+       if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
                TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
+               tp->retrans_out -= tcp_skb_pcount(skb);
        }
        tcp_sync_left_out(tp);
 
-       tcp_set_ca_state(sk, TCP_CA_Open);
-       tp->frto_highmark = tp->snd_nxt;
+       /* Earlier loss recovery underway (see RFC4138; Appendix B).
+        * The last condition is necessary at least in tp->frto_counter case.
+        */
+       if (IsSackFrto() && (tp->frto_counter ||
+           ((1 << icsk->icsk_ca_state) & (TCPF_CA_Recovery|TCPF_CA_Loss))) &&
+           after(tp->high_seq, tp->snd_una)) {
+               tp->frto_highmark = tp->high_seq;
+       } else {
+               tp->frto_highmark = tp->snd_nxt;
+       }
+       tcp_set_ca_state(sk, TCP_CA_Disorder);
+       tp->high_seq = tp->snd_nxt;
+       tp->frto_counter = 1;
 }
 
 /* Enter Loss state after F-RTO was applied. Dupack arrived after RTO,
  * which indicates that we should follow the traditional RTO recovery,
  * i.e. mark everything lost and do go-back-N retransmission.
  */
-static void tcp_enter_frto_loss(struct sock *sk)
+static void tcp_enter_frto_loss(struct sock *sk, int allowed_segments, int flag)
 {
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *skb;
@@ -1287,10 +1375,21 @@ static void tcp_enter_frto_loss(struct sock *sk)
        tp->sacked_out = 0;
        tp->lost_out = 0;
        tp->fackets_out = 0;
+       tp->retrans_out = 0;
 
        sk_stream_for_retrans_queue(skb, sk) {
                cnt += tcp_skb_pcount(skb);
-               TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
+               /*
+                * Count the retransmission made on RTO correctly (only when
+                * waiting for the first ACK and did not get it)...
+                */
+               if ((tp->frto_counter == 1) && !(flag&FLAG_DATA_ACKED)) {
+                       tp->retrans_out += tcp_skb_pcount(skb);
+                       /* ...enter this if branch just for the first segment */
+                       flag |= FLAG_DATA_ACKED;
+               } else {
+                       TCP_SKB_CB(skb)->sacked &= ~(TCPCB_LOST|TCPCB_SACKED_RETRANS);
+               }
                if (!(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED)) {
 
                        /* Do not mark those segments lost that were
@@ -1308,7 +1407,7 @@ static void tcp_enter_frto_loss(struct sock *sk)
        }
        tcp_sync_left_out(tp);
 
-       tp->snd_cwnd = tp->frto_counter + tcp_packets_in_flight(tp)+1;
+       tp->snd_cwnd = tcp_packets_in_flight(tp) + allowed_segments;
        tp->snd_cwnd_cnt = 0;
        tp->snd_cwnd_stamp = tcp_time_stamp;
        tp->undo_marker = 0;
@@ -1529,6 +1628,10 @@ static int tcp_time_to_recover(struct sock *sk, struct tcp_sock *tp)
 {
        __u32 packets_out;
 
+       /* Do not perform any recovery during FRTO algorithm */
+       if (tp->frto_counter)
+               return 0;
+
        /* Trick#1: The loss is proven. */
        if (tp->lost_out)
                return 1;
@@ -1997,8 +2100,7 @@ tcp_fastretrans_alert(struct sock *sk, u32 prior_snd_una,
        /* E. Check state exit conditions. State can be terminated
         *    when high_seq is ACKed. */
        if (icsk->icsk_ca_state == TCP_CA_Open) {
-               if (!sysctl_tcp_frto)
-                       BUG_TRAP(tp->retrans_out == 0);
+               BUG_TRAP(tp->retrans_out == 0);
                tp->retrans_stamp = 0;
        } else if (!before(tp->snd_una, tp->high_seq)) {
                switch (icsk->icsk_ca_state) {
@@ -2467,39 +2569,102 @@ static int tcp_ack_update_window(struct sock *sk, struct tcp_sock *tp,
        return flag;
 }
 
-static void tcp_process_frto(struct sock *sk, u32 prior_snd_una)
+/* A very conservative spurious RTO response algorithm: reduce cwnd and
+ * continue in congestion avoidance.
+ */
+static void tcp_conservative_spur_to_response(struct tcp_sock *tp)
+{
+       tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
+       tp->snd_cwnd_cnt = 0;
+       tcp_moderate_cwnd(tp);
+}
+
+/* F-RTO spurious RTO detection algorithm (RFC4138)
+ *
+ * F-RTO affects during two new ACKs following RTO (well, almost, see inline
+ * comments). State (ACK number) is kept in frto_counter. When ACK advances
+ * window (but not to or beyond highest sequence sent before RTO):
+ *   On First ACK,  send two new segments out.
+ *   On Second ACK, RTO was likely spurious. Do spurious response (response
+ *                  algorithm is not part of the F-RTO detection algorithm
+ *                  given in RFC4138 but can be selected separately).
+ * Otherwise (basically on duplicate ACK), RTO was (likely) caused by a loss
+ * and TCP falls back to conventional RTO recovery.
+ *
+ * Rationale: if the RTO was spurious, new ACKs should arrive from the
+ * original window even after we transmit two new data segments.
+ *
+ * SACK version:
+ *   on first step, wait until first cumulative ACK arrives, then move to
+ *   the second step. In second step, the next ACK decides.
+ *
+ * F-RTO is implemented (mainly) in four functions:
+ *   - tcp_use_frto() is used to determine if TCP is can use F-RTO
+ *   - tcp_enter_frto() prepares TCP state on RTO if F-RTO is used, it is
+ *     called when tcp_use_frto() showed green light
+ *   - tcp_process_frto() handles incoming ACKs during F-RTO algorithm
+ *   - tcp_enter_frto_loss() is called if there is not enough evidence
+ *     to prove that the RTO is indeed spurious. It transfers the control
+ *     from F-RTO to the conventional RTO recovery
+ */
+static int tcp_process_frto(struct sock *sk, u32 prior_snd_una, int flag)
 {
        struct tcp_sock *tp = tcp_sk(sk);
 
        tcp_sync_left_out(tp);
 
-       if (tp->snd_una == prior_snd_una ||
-           !before(tp->snd_una, tp->frto_highmark)) {
-               /* RTO was caused by loss, start retransmitting in
-                * go-back-N slow start
-                */
-               tcp_enter_frto_loss(sk);
-               return;
+       /* Duplicate the behavior from Loss state (fastretrans_alert) */
+       if (flag&FLAG_DATA_ACKED)
+               inet_csk(sk)->icsk_retransmits = 0;
+
+       if (!before(tp->snd_una, tp->frto_highmark)) {
+               tcp_enter_frto_loss(sk, tp->frto_counter + 1, flag);
+               return 1;
        }
 
-       if (tp->frto_counter == 1) {
-               /* First ACK after RTO advances the window: allow two new
-                * segments out.
+       if (!IsSackFrto() || IsReno(tp)) {
+               /* RFC4138 shortcoming in step 2; should also have case c):
+                * ACK isn't duplicate nor advances window, e.g., opposite dir
+                * data, winupdate
                 */
-               tp->snd_cwnd = tcp_packets_in_flight(tp) + 2;
+               if ((tp->snd_una == prior_snd_una) && (flag&FLAG_NOT_DUP) &&
+                   !(flag&FLAG_FORWARD_PROGRESS))
+                       return 1;
+
+               if (!(flag&FLAG_DATA_ACKED)) {
+                       tcp_enter_frto_loss(sk, (tp->frto_counter == 1 ? 0 : 3),
+                                           flag);
+                       return 1;
+               }
        } else {
-               /* Also the second ACK after RTO advances the window.
-                * The RTO was likely spurious. Reduce cwnd and continue
-                * in congestion avoidance
-                */
-               tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
-               tcp_moderate_cwnd(tp);
+               if (!(flag&FLAG_DATA_ACKED) && (tp->frto_counter == 1)) {
+                       /* Prevent sending of new data. */
+                       tp->snd_cwnd = min(tp->snd_cwnd,
+                                          tcp_packets_in_flight(tp));
+                       return 1;
+               }
+
+               if ((tp->frto_counter == 2) &&
+                   (!(flag&FLAG_FORWARD_PROGRESS) ||
+                    ((flag&FLAG_DATA_SACKED) && !(flag&FLAG_ONLY_ORIG_SACKED)))) {
+                       /* RFC4138 shortcoming (see comment above) */
+                       if (!(flag&FLAG_FORWARD_PROGRESS) && (flag&FLAG_NOT_DUP))
+                               return 1;
+
+                       tcp_enter_frto_loss(sk, 3, flag);
+                       return 1;
+               }
        }
 
-       /* F-RTO affects on two new ACKs following RTO.
-        * At latest on third ACK the TCP behavior is back to normal.
-        */
-       tp->frto_counter = (tp->frto_counter + 1) % 3;
+       if (tp->frto_counter == 1) {
+               tp->snd_cwnd = tcp_packets_in_flight(tp) + 2;
+               tp->frto_counter = 2;
+               return 1;
+       } else /* frto_counter == 2 */ {
+               tcp_conservative_spur_to_response(tp);
+               tp->frto_counter = 0;
+       }
+       return 0;
 }
 
 /* This routine deals with incoming acks, but not outgoing ones. */
@@ -2513,6 +2678,7 @@ static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
        u32 prior_in_flight;
        s32 seq_rtt;
        int prior_packets;
+       int frto_cwnd = 0;
 
        /* If the ack is newer than sent or older than previous acks
         * then we can probably ignore it.
@@ -2575,15 +2741,16 @@ static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
        flag |= tcp_clean_rtx_queue(sk, &seq_rtt);
 
        if (tp->frto_counter)
-               tcp_process_frto(sk, prior_snd_una);
+               frto_cwnd = tcp_process_frto(sk, prior_snd_una, flag);
 
        if (tcp_ack_is_dubious(sk, flag)) {
                /* Advance CWND, if state allows this. */
-               if ((flag & FLAG_DATA_ACKED) && tcp_may_raise_cwnd(sk, flag))
+               if ((flag & FLAG_DATA_ACKED) && !frto_cwnd &&
+                   tcp_may_raise_cwnd(sk, flag))
                        tcp_cong_avoid(sk, ack,  seq_rtt, prior_in_flight, 0);
                tcp_fastretrans_alert(sk, prior_snd_una, prior_packets, flag);
        } else {
-               if ((flag & FLAG_DATA_ACKED))
+               if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
                        tcp_cong_avoid(sk, ack, seq_rtt, prior_in_flight, 1);
        }