SCTP: calculate RTO / RTT and RTTVAR as per RFC

This patch addresses the need to calculate the RTO / RTT and RTTVAR
according to the rules depicted by the RFC4960 at section 6.3.1

Change-Id: I1d346f3c67610070b3f602f32c7738d58b99ffed
Signed-off-by: Marco Varlese <marco.varlese@suse.com>
diff --git a/src/vnet/sctp/sctp.c b/src/vnet/sctp/sctp.c
index d0f37f4..bd62c32 100644
--- a/src/vnet/sctp/sctp.c
+++ b/src/vnet/sctp/sctp.c
@@ -135,6 +135,7 @@
   for (i = 0; i < MAX_SCTP_CONNECTIONS; i++)
     {
       sctp_conn->sub_conn[i].RTO = SCTP_RTO_INIT;
+
       for (j = 0; j < SCTP_N_TIMERS; j++)
 	{
 	  sctp_conn->sub_conn[i].timers[j] = SCTP_TIMER_HANDLE_INVALID;
@@ -601,7 +602,6 @@
 {
   sctp_connection_t *sctp_conn;
 
-  clib_warning ("");
   sctp_conn = sctp_connection_get (conn_index, vlib_get_thread_index ());
   /* note: the connection may have already disappeared */
   if (PREDICT_FALSE (sctp_conn == 0))
@@ -650,8 +650,6 @@
       (*sctp_timer_expiration_handlers[timer_id]) (connection_index,
 						   timer_id);
     }
-
-  clib_warning ("");
 }
 
 void
@@ -783,6 +781,15 @@
   return format (s, "%U", format_sctp_connection_id, sctp_conn);
 }
 
+void
+sctp_update_time (f64 now, u8 thread_index)
+{
+  sctp_set_time_now (thread_index);
+  tw_timer_expire_timers_16t_2w_512sl (&sctp_main.timer_wheels[thread_index],
+				       now);
+  sctp_flush_frames_to_output (thread_index);
+}
+
 /* *INDENT OFF* */
 const static transport_proto_vft_t sctp_proto = {
   .enable = sctp_enable_disable,
@@ -795,6 +802,7 @@
   .send_mss = sctp_session_send_mss,
   .send_space = sctp_session_send_space,
   .tx_fifo_offset = NULL,	//sctp_session_tx_fifo_offset,
+  .update_time = sctp_update_time,
   .get_connection = sctp_session_get_transport,
   .get_listener = sctp_session_get_listener,
   .get_half_open = sctp_half_open_session_get_transport,
diff --git a/src/vnet/sctp/sctp.h b/src/vnet/sctp/sctp.h
index 8f80d84..0634f01 100644
--- a/src/vnet/sctp/sctp.h
+++ b/src/vnet/sctp/sctp.h
@@ -79,9 +79,11 @@
   u32 cwnd; /**< The current congestion window. */
   u32 ssthresh;	/**< The current ssthresh value. */
 
+  u32 rtt_ts;	/**< USED to hold the timestamp of when the packet has been sent */
+
   u32 RTO; /**< The current retransmission timeout value. */
   u32 SRTT; /**< The current smoothed round-trip time. */
-  u32 RTTVAR; /**< The current RTT variation. */
+  f32 RTTVAR; /**< The current RTT variation. */
 
   u32 partially_acked_bytes; /**< The tracking method for increase of cwnd when in
   	  	  	  	  congestion avoidance mode (see Section 7.2.2).*/
@@ -195,9 +197,6 @@
   u32 rcv_a_rwnd;		/**< LOCAL max seg size that includes options. To be updated by congestion algos, etc. */
   u32 snd_a_rwnd;		/**< REMOTE max seg size that includes options. To be updated if peer pushes back on window, etc.*/
 
-  u32 rtt_ts;
-  u32 rtt_seq;
-
   u8 overall_sending_status; /**< 0 indicates first fragment of a user message
   	  	  	  	  	  	  	  	  1 indicates normal stream
   	  	  	  	  	  	  	  	  2 indicates last fragment of a user message */
@@ -523,7 +522,7 @@
 	  SCTP_TIMER_HANDLE_INVALID);
 
   sctp_sub_connection_t *sub = &tc->sub_conn[conn_idx];
-  tc->sub_conn[conn_idx].timers[timer_id] =
+  sub->timers[timer_id] =
     tw_timer_start_16t_2w_512sl (&sctp_main.timer_wheels[sub->c_thread_index],
 				 sub->c_c_index, timer_id, interval);
 }
@@ -542,15 +541,6 @@
   sub->timers[timer_id] = SCTP_TIMER_HANDLE_INVALID;
 }
 
-always_inline void
-sctp_update_time (f64 now, u32 thread_index)
-{
-  sctp_set_time_now (thread_index);
-  tw_timer_expire_timers_16t_2w_512sl (&sctp_main.timer_wheels[thread_index],
-				       now);
-  sctp_flush_frames_to_output (thread_index);
-}
-
 /**
  * Try to cleanup half-open connection
  *
@@ -599,6 +589,61 @@
   return sctp_main.time_now[vlib_get_thread_index ()];
 }
 
+#define ABS(x) ((x) > 0) ? (x) : -(x);
+
+always_inline void
+sctp_calculate_rto (sctp_connection_t * sctp_conn, u8 conn_idx)
+{
+  /* See RFC4960, 6.3.1.  RTO Calculation */
+  u32 RTO = 0;
+  f32 RTTVAR = 0;
+  u32 now = sctp_time_now ();
+  u32 prev_ts = sctp_conn->sub_conn[conn_idx].rtt_ts;
+  u32 R = prev_ts - now;
+
+  if (sctp_conn->sub_conn[conn_idx].RTO == 0)	// C1: Let's initialize our RTO
+    {
+      sctp_conn->sub_conn[conn_idx].RTO = SCTP_RTO_MIN;
+      return;
+    }
+
+  if (sctp_conn->sub_conn[conn_idx].RTO == SCTP_RTO_MIN && sctp_conn->sub_conn[conn_idx].SRTT == 0)	// C2: First RTT calculation
+    {
+      sctp_conn->sub_conn[conn_idx].SRTT = R;
+      RTTVAR = R / 2;
+
+      if (RTTVAR == 0)
+	RTTVAR = 100e-3;	/* 100 ms */
+
+      sctp_conn->sub_conn[conn_idx].RTTVAR = RTTVAR;
+    }
+  else				// C3: RTT already exists; let's recalculate
+    {
+      RTTVAR = (1 - SCTP_RTO_BETA) * sctp_conn->sub_conn[conn_idx].RTTVAR +
+	SCTP_RTO_BETA * ABS (sctp_conn->sub_conn[conn_idx].SRTT - R);
+
+      if (RTTVAR == 0)
+	RTTVAR = 100e-3;	/* 100 ms */
+
+      sctp_conn->sub_conn[conn_idx].RTTVAR = RTTVAR;
+
+      sctp_conn->sub_conn[conn_idx].SRTT =
+	(1 - SCTP_RTO_ALPHA) * sctp_conn->sub_conn[conn_idx].SRTT +
+	SCTP_RTO_ALPHA * R;
+    }
+
+  RTO =
+    sctp_conn->sub_conn[conn_idx].SRTT +
+    4 * sctp_conn->sub_conn[conn_idx].RTTVAR;
+  if (RTO < SCTP_RTO_MIN)	// C6
+    RTO = SCTP_RTO_MIN;
+
+  if (RTO > SCTP_RTO_MAX)	// C7
+    RTO = SCTP_RTO_MAX;
+
+  sctp_conn->sub_conn[conn_idx].RTO = RTO;
+}
+
 always_inline void
 sctp_timer_update (sctp_connection_t * tc, u8 conn_idx, u8 timer_id,
 		   u32 interval)
diff --git a/src/vnet/sctp/sctp_input.c b/src/vnet/sctp/sctp_input.c
index 4462450..0337e9f 100644
--- a/src/vnet/sctp/sctp_input.c
+++ b/src/vnet/sctp/sctp_input.c
@@ -427,8 +427,8 @@
 always_inline u16
 sctp_handle_init_ack (sctp_header_t * sctp_hdr,
 		      sctp_chunks_common_hdr_t * sctp_chunk_hdr,
-		      sctp_connection_t * sctp_conn, vlib_buffer_t * b0,
-		      u16 sctp_implied_length)
+		      sctp_connection_t * sctp_conn, u8 idx,
+		      vlib_buffer_t * b0, u16 sctp_implied_length)
 {
   sctp_init_ack_chunk_t *init_ack_chunk =
     (sctp_init_ack_chunk_t *) (sctp_hdr);
@@ -450,6 +450,8 @@
   if (sctp_is_bundling (sctp_implied_length, &init_ack_chunk->chunk_hdr))
     return SCTP_ERROR_BUNDLING_VIOLATION;
 
+  sctp_calculate_rto (sctp_conn, idx);
+
   /* remote_tag to be placed in the VERIFICATION_TAG field of the COOKIE_ECHO chunk */
   sctp_conn->remote_tag = init_ack_chunk->initiate_tag;
   sctp_conn->remote_initial_tsn =
@@ -535,7 +537,7 @@
 
   /* Start the T1_COOKIE timer */
   sctp_timer_set (sctp_conn, sctp_pick_conn_idx_on_chunk (COOKIE_ECHO),
-		  SCTP_TIMER_T1_COOKIE, SCTP_RTO_INIT);
+		  SCTP_TIMER_T1_COOKIE, sctp_conn->sub_conn[idx].RTO);
 
   return SCTP_ERROR_NONE;
 }
@@ -764,6 +766,7 @@
 			 sctp_chunks_common_hdr_t * sctp_chunk_hdr,
 			 sctp_connection_t * sctp_conn, vlib_buffer_t * b0)
 {
+  u32 now = sctp_time_now ();
 
   /* Build TCB */
   u8 idx = sctp_pick_conn_idx_on_chunk (COOKIE_ECHO);
@@ -777,7 +780,8 @@
       return SCTP_ERROR_INVALID_TAG;
     }
 
-  u32 now = sctp_time_now ();
+  sctp_calculate_rto (sctp_conn, idx);
+
   u32 creation_time =
     clib_net_to_host_u32 (cookie_echo->cookie.creation_time);
   u32 cookie_lifespan =
@@ -815,14 +819,14 @@
       return SCTP_ERROR_INVALID_TAG;
     }
 
+  sctp_calculate_rto (sctp_conn, idx);
+
   sctp_timer_reset (sctp_conn, idx, SCTP_TIMER_T1_COOKIE);
   /* Change state */
   sctp_conn->state = SCTP_STATE_ESTABLISHED;
 
   stream_session_accept_notify (&sctp_conn->sub_conn[idx].connection);
 
-  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T3_RXTX, SCTP_RTO_INIT);
-
   return SCTP_ERROR_NONE;
 
 }
@@ -959,11 +963,10 @@
 
 		  error0 =
 		    sctp_handle_init_ack (sctp_hdr, sctp_chunk_hdr,
-					  new_sctp_conn, b0,
+					  new_sctp_conn, idx, b0,
 					  sctp_implied_length);
 
 		  sctp_init_mss (new_sctp_conn);
-		  //sctp_init_snd_vars (new_sctp_conn);
 
 		  if (session_stream_connect_notify
 		      (&new_sctp_conn->sub_conn[idx].connection, 0))
@@ -1416,7 +1419,12 @@
       return SCTP_ERROR_INVALID_TAG;
     }
 
-  sctp_timer_update (sctp_conn, idx, SCTP_TIMER_T3_RXTX, SCTP_RTO_INIT);
+  sctp_calculate_rto (sctp_conn, idx);
+
+  sctp_timer_update (sctp_conn, idx, SCTP_TIMER_T3_RXTX,
+		     sctp_conn->sub_conn[idx].RTO);
+
+  sctp_conn->sub_conn[idx].RTO_pending = 0;
 
   *next0 = sctp_next_output (sctp_conn->sub_conn[idx].connection.is_ip4);
 
diff --git a/src/vnet/sctp/sctp_output.c b/src/vnet/sctp/sctp_output.c
index 3d870ff..ed9ffd3 100644
--- a/src/vnet/sctp/sctp_output.c
+++ b/src/vnet/sctp/sctp_output.c
@@ -556,6 +556,9 @@
   vnet_sctp_set_chunk_type (&cookie_ack_chunk->chunk_hdr, COOKIE_ACK);
   vnet_sctp_set_chunk_length (&cookie_ack_chunk->chunk_hdr, chunk_len);
 
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
+
   vnet_buffer (b)->sctp.connection_index =
     sctp_conn->sub_conn[idx].connection.c_index;
 }
@@ -589,6 +592,10 @@
   vnet_sctp_set_chunk_length (&cookie_echo_chunk->chunk_hdr, chunk_len);
   clib_memcpy (&(cookie_echo_chunk->cookie), sc,
 	       sizeof (sctp_state_cookie_param_t));
+
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
+
   vnet_buffer (b)->sctp.connection_index =
     sctp_conn->sub_conn[idx].connection.c_index;
 }
@@ -732,6 +739,9 @@
 
   sctp_conn->local_tag = init_ack_chunk->initiate_tag;
 
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
+
   vnet_buffer (b)->sctp.connection_index =
     sctp_conn->sub_conn[idx].connection.c_index;
 }
@@ -799,6 +809,9 @@
   sctp_push_ip_hdr (tm, &sctp_conn->sub_conn[idx], b);
   sctp_enqueue_to_output_now (vm, b, bi,
 			      sctp_conn->sub_conn[idx].connection.is_ip4);
+
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
 }
 
 /**
@@ -858,8 +871,12 @@
   sctp_enqueue_to_ip_lookup (vm, b, bi,
 			     sctp_conn->sub_conn[idx].connection.is_ip4);
 
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
+
   /* Start the SCTP_TIMER_T2_SHUTDOWN timer */
-  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T2_SHUTDOWN, SCTP_RTO_INIT);
+  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T2_SHUTDOWN,
+		  sctp_conn->sub_conn[idx].RTO);
   sctp_conn->state = SCTP_STATE_SHUTDOWN_ACK_SENT;
 }
 
@@ -1034,35 +1051,28 @@
   sctp_init_buffer (vm, b);
   sctp_prepare_init_chunk (sctp_conn, b);
 
-  /* Measure RTT with this */
-  sctp_conn->rtt_ts = sctp_time_now ();
-  sctp_conn->rtt_seq = sctp_conn->next_tsn;
-
   sctp_push_ip_hdr (tm, &sctp_conn->sub_conn[idx], b);
   sctp_enqueue_to_ip_lookup_now (vm, b, bi,
 				 sctp_conn->sub_conn[idx].c_is_ip4);
 
+  /* Measure RTT with this */
+  sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
+
   /* Start the T1_INIT timer */
-  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T1_INIT, SCTP_RTO_INIT);
+  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T1_INIT,
+		  sctp_conn->sub_conn[idx].RTO);
+
   /* Change state to COOKIE_WAIT */
   sctp_conn->state = SCTP_STATE_COOKIE_WAIT;
 }
 
-always_inline u8
-sctp_in_cong_recovery (sctp_connection_t * sctp_conn)
-{
-  return 0;
-}
-
 /**
  * Push SCTP header and update connection variables
  */
 static void
-sctp_push_hdr_i (sctp_connection_t * sctp_conn, vlib_buffer_t * b,
+sctp_push_hdr_i (sctp_connection_t * sctp_conn, u8 idx, vlib_buffer_t * b,
 		 sctp_state_t next_state)
 {
-  u8 idx = sctp_pick_conn_idx_on_chunk (DATA);
-
   u16 data_len =
     b->current_length + b->total_length_not_including_first_buffer;
   ASSERT (!b->total_length_not_including_first_buffer
@@ -1112,13 +1122,17 @@
 {
   sctp_connection_t *sctp_conn =
     sctp_get_connection_from_transport (trans_conn);
-  sctp_push_hdr_i (sctp_conn, b, SCTP_STATE_ESTABLISHED);
 
-  if (sctp_conn->rtt_ts == 0 && !sctp_in_cong_recovery (sctp_conn))
+  u8 idx = sctp_pick_conn_idx_on_chunk (DATA);
+
+  sctp_push_hdr_i (sctp_conn, idx, b, SCTP_STATE_ESTABLISHED);
+
+  if (sctp_conn->sub_conn[idx].RTO_pending == 0)
     {
-      sctp_conn->rtt_ts = sctp_time_now ();
-      sctp_conn->rtt_seq = sctp_conn->next_tsn;
+      sctp_conn->sub_conn[idx].RTO_pending = 1;
+      sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now ();
     }
+
   sctp_trajectory_add_start (b0, 3);
 
   return 0;
@@ -1341,14 +1355,14 @@
 	    {
 	      /* Start the SCTP_TIMER_T2_SHUTDOWN timer */
 	      sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T2_SHUTDOWN,
-			      SCTP_RTO_INIT);
+			      sctp_conn->sub_conn[idx].RTO);
 	      sctp_conn->state = SCTP_STATE_SHUTDOWN_SENT;
 	    }
 
 	  if (chunk_type == DATA)
 	    {
 	      sctp_timer_update (sctp_conn, idx, SCTP_TIMER_T3_RXTX,
-				 SCTP_RTO_INIT);
+				 sctp_conn->sub_conn[idx].RTO);
 	    }
 
 	  vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;