SCTP: handling of heartbeating and max-retransmits 25/10425/3
authorMarco Varlese <marco.varlese@suse.com>
Tue, 6 Feb 2018 12:48:30 +0000 (13:48 +0100)
committerFlorin Coras <florin.coras@gmail.com>
Tue, 6 Feb 2018 17:44:23 +0000 (17:44 +0000)
This patch address the need to send/receive heartbeats between peers.
At the same time, the number of unacked heartbeats is tracked and when
the peer requests to send DATA to the remote-peer the value of unacked
heartbeats needs to be checked against the maximum value allowed for
retransmissions. If the unacked heartbeats value is higher then the
remote-peer is considered unreachable and the connetion needs to be
shutdown.

Change-Id: I2b1a21c26775e734dbe82486f40982ed5702dc63
Signed-off-by: Marco Varlese <marco.varlese@suse.com>
src/vnet/sctp/sctp.c
src/vnet/sctp/sctp.h
src/vnet/sctp/sctp_input.c
src/vnet/sctp/sctp_output.c

index f8cd21c..61c0252 100644 (file)
@@ -606,33 +606,43 @@ sctp_expired_timers_cb (u32 conn_index, u32 timer_id)
   /* note: the connection may have already disappeared */
   if (PREDICT_FALSE (sctp_conn == 0))
     return;
-  ASSERT (sctp_conn->state == SCTP_STATE_COOKIE_ECHOED);
+
+  SCTP_DBG ("%s expired", sctp_timer_to_string (timer_id));
 
   switch (timer_id)
     {
+    case SCTP_TIMER_T1_INIT:
+    case SCTP_TIMER_T1_COOKIE:
+    case SCTP_TIMER_T2_SHUTDOWN:
+    case SCTP_TIMER_T3_RXTX:
+      clib_smp_atomic_add (&sctp_conn->sub_conn[conn_index].unacknowledged_hb,
+                          1);
+      sctp_timer_reset (sctp_conn, conn_index, timer_id);
+      break;
     case SCTP_TIMER_T4_HEARTBEAT:
-      {
-       clib_warning ("Heartbeat timeout");
-       break;
-      }
+      sctp_timer_reset (sctp_conn, conn_index, timer_id);
+      goto heartbeat;
     }
-  /* Start cleanup. App wasn't notified yet so use delete notify as
-   * opposed to delete to cleanup session layer state. */
-  stream_session_delete_notify (&sctp_conn->
-                               sub_conn[MAIN_SCTP_SUB_CONN_IDX].connection);
 
-  sctp_connection_timers_reset (sctp_conn);
+  if (sctp_conn->sub_conn[conn_index].unacknowledged_hb >
+      SCTP_ASSOCIATION_MAX_RETRANS)
+    {
+      // The remote-peer is considered to be unreachable hence shutting down
 
-  sctp_connection_cleanup (sctp_conn);
-}
+      /* Start cleanup. App wasn't notified yet so use delete notify as
+       * opposed to delete to cleanup session layer state. */
+      stream_session_delete_notify (&sctp_conn->sub_conn
+                                   [MAIN_SCTP_SUB_CONN_IDX].connection);
 
-/* *INDENT OFF* */
-static sctp_timer_expiration_handler
-  * sctp_timer_expiration_handlers[SCTP_N_TIMERS] = {
-  sctp_expired_timers_cb
-};
+      sctp_connection_timers_reset (sctp_conn);
 
-/* *INDENT ON* */
+      sctp_connection_cleanup (sctp_conn);
+    }
+  return;
+
+heartbeat:
+  sctp_send_heartbeat (sctp_conn);
+}
 
 static void
 sctp_expired_timers_dispatch (u32 * expired_timers)
@@ -649,8 +659,7 @@ sctp_expired_timers_dispatch (u32 * expired_timers)
       SCTP_DBG ("Expired timer ID: %u", timer_id);
 
       /* Handle expiration */
-      (*sctp_timer_expiration_handlers[timer_id]) (connection_index,
-                                                  timer_id);
+      sctp_expired_timers_cb (connection_index, timer_id);
     }
 }
 
index 0634f01..9129aa8 100644 (file)
@@ -41,6 +41,27 @@ typedef enum _sctp_timers
 
 #define SCTP_TIMER_HANDLE_INVALID ((u32) ~0)
 
+always_inline char *
+sctp_timer_to_string (u8 timer_id)
+{
+  switch (timer_id)
+    {
+    case SCTP_TIMER_T1_INIT:
+      return "SCTP_TIMER_T1_INIT";
+    case SCTP_TIMER_T1_COOKIE:
+      return "SCTP_TIMER_T1_COOKIE";
+    case SCTP_TIMER_T2_SHUTDOWN:
+      return "SCTP_TIMER_T2_SHUTDOWN";
+    case SCTP_TIMER_T3_RXTX:
+      return "SCTP_TIMER_T3_RXTX";
+    case SCTP_TIMER_T4_HEARTBEAT:
+      return "SCTP_TIMER_T4_HEARTBEAT";
+    case SCTP_TIMER_T5_SHUTDOWN_GUARD:
+      return "SCTP_TIMER_T5_SHUTDOWN_GUARD";
+    }
+  return NULL;
+}
+
 typedef enum _sctp_error
 {
 #define sctp_error(n,s) SCTP_ERROR_##n,
@@ -104,6 +125,9 @@ typedef struct _sctp_sub_connection
   u32 last_time; /**< The time to which this destination was last sent a packet to.
                                  This can be used to determine if a HEARTBEAT is needed. */
 
+  u8 unacknowledged_hb;        /**< Used to track how many unacknowledged heartbeats we had;
+                                 If more than Max.Retransmit then connetion is considered unreachable. */
+
 } sctp_sub_connection_t;
 
 typedef struct
@@ -216,15 +240,16 @@ void sctp_sub_connection_add_ip4 (u8 thread_index,
                                  sctp_ipv4_addr_param_t * ipv4_addr);
 void sctp_sub_connection_add_ip6 (u8 thread_index,
                                  sctp_ipv6_addr_param_t * ipv6_addr);
-void sctp_connection_close (sctp_connection_t * tc);
-void sctp_connection_cleanup (sctp_connection_t * tc);
-void sctp_connection_del (sctp_connection_t * tc);
+void sctp_connection_close (sctp_connection_t * sctp_conn);
+void sctp_connection_cleanup (sctp_connection_t * sctp_conn);
+void sctp_connection_del (sctp_connection_t * sctp_conn);
 
 u32 sctp_push_header (transport_connection_t * tconn, vlib_buffer_t * b);
-void sctp_send_init (sctp_connection_t * tc);
-void sctp_send_shutdown (sctp_connection_t * tc);
-void sctp_send_shutdown_ack (sctp_connection_t * tc);
-void sctp_send_shutdown_complete (sctp_connection_t * tc);
+void sctp_send_init (sctp_connection_t * sctp_conn);
+void sctp_send_shutdown (sctp_connection_t * sctp_conn);
+void sctp_send_shutdown_ack (sctp_connection_t * sctp_conn);
+void sctp_send_shutdown_complete (sctp_connection_t * sctp_conn);
+void sctp_send_heartbeat (sctp_connection_t * sctp_conn);
 void sctp_flush_frame_to_output (vlib_main_t * vm, u8 thread_index,
                                 u8 is_ip4);
 void sctp_flush_frames_to_output (u8 thread_index);
@@ -253,6 +278,8 @@ void sctp_prepare_cookie_echo_chunk (sctp_connection_t * tc,
 void sctp_prepare_cookie_ack_chunk (sctp_connection_t * tc,
                                    vlib_buffer_t * b);
 void sctp_prepare_sack_chunk (sctp_connection_t * tc, vlib_buffer_t * b);
+void sctp_prepare_heartbeat_ack_chunk (sctp_connection_t * sctp_conn,
+                                      vlib_buffer_t * b);
 
 u16 sctp_check_outstanding_data_chunks (sctp_connection_t * tc);
 
index 0337e9f..6653688 100644 (file)
@@ -798,6 +798,9 @@ sctp_handle_cookie_echo (sctp_header_t * sctp_hdr,
   /* Change state */
   sctp_conn->state = SCTP_STATE_ESTABLISHED;
 
+  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T4_HEARTBEAT,
+                 sctp_conn->sub_conn[idx].RTO);
+
   stream_session_accept_notify (&sctp_conn->sub_conn[idx].connection);
 
   return SCTP_ERROR_NONE;
@@ -825,6 +828,9 @@ sctp_handle_cookie_ack (sctp_header_t * sctp_hdr,
   /* Change state */
   sctp_conn->state = SCTP_STATE_ESTABLISHED;
 
+  sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T4_HEARTBEAT,
+                 sctp_conn->sub_conn[idx].RTO);
+
   stream_session_accept_notify (&sctp_conn->sub_conn[idx].connection);
 
   return SCTP_ERROR_NONE;
@@ -1433,17 +1439,34 @@ sctp_handle_sack (sctp_selective_ack_chunk_t * sack_chunk,
 
 always_inline u16
 sctp_handle_heartbeat (sctp_hb_req_chunk_t * sctp_hb_chunk,
-                      sctp_connection_t * sctp_conn, vlib_buffer_t * b0,
-                      u16 * next0)
+                      sctp_connection_t * sctp_conn, u8 idx,
+                      vlib_buffer_t * b0, u16 * next0)
 {
+  /* Check that the LOCALLY generated tag is being used by the REMOTE peer as the verification tag */
+  if (sctp_conn->local_tag != sctp_hb_chunk->sctp_hdr.verification_tag)
+    {
+      return SCTP_ERROR_INVALID_TAG;
+    }
+
+  sctp_prepare_heartbeat_ack_chunk (sctp_conn, b0);
+
+  *next0 = sctp_next_output (sctp_conn->sub_conn[idx].connection.is_ip4);
+
   return SCTP_ERROR_NONE;
 }
 
 always_inline u16
 sctp_handle_heartbeat_ack (sctp_hb_ack_chunk_t * sctp_hb_ack_chunk,
-                          sctp_connection_t * sctp_conn, vlib_buffer_t * b0,
-                          u16 * next0)
+                          sctp_connection_t * sctp_conn, u8 idx,
+                          vlib_buffer_t * b0, u16 * next0)
 {
+  sctp_conn->sub_conn[idx].unacknowledged_hb -= 1;
+
+  sctp_timer_update (sctp_conn, idx, SCTP_TIMER_T4_HEARTBEAT,
+                    sctp_conn->sub_conn[idx].RTO);
+
+  *next0 = sctp_next_output (sctp_conn->sub_conn[idx].connection.is_ip4);
+
   return SCTP_ERROR_NONE;
 }
 
@@ -1766,13 +1789,13 @@ sctp46_established_phase_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
            case HEARTBEAT:
              error0 =
                sctp_handle_heartbeat ((sctp_hb_req_chunk_t *) sctp_hdr,
-                                      sctp_conn, b0, &next0);
+                                      sctp_conn, idx, b0, &next0);
              break;
 
            case HEARTBEAT_ACK:
              error0 =
                sctp_handle_heartbeat_ack ((sctp_hb_ack_chunk_t *) sctp_hdr,
-                                          sctp_conn, b0, &next0);
+                                          sctp_conn, idx, b0, &next0);
              break;
 
            case DATA:
index ed9ffd3..3ec54dc 100644 (file)
@@ -351,6 +351,13 @@ sctp_enqueue_to_output_i (vlib_main_t * vm, vlib_buffer_t * b, u32 bi,
     }
 }
 
+always_inline void
+sctp_enqueue_to_output (vlib_main_t * vm, vlib_buffer_t * b, u32 bi,
+                       u8 is_ip4)
+{
+  sctp_enqueue_to_output_i (vm, b, bi, is_ip4, 0);
+}
+
 always_inline void
 sctp_enqueue_to_output_now (vlib_main_t * vm, vlib_buffer_t * b, u32 bi,
                            u8 is_ip4)
@@ -406,13 +413,6 @@ sctp_enqueue_to_ip_lookup (vlib_main_t * vm, vlib_buffer_t * b, u32 bi,
   sctp_enqueue_to_ip_lookup_i (vm, b, bi, is_ip4, 0);
 }
 
-always_inline void
-sctp_enqueue_to_ip_lookup_now (vlib_main_t * vm, vlib_buffer_t * b, u32 bi,
-                              u8 is_ip4)
-{
-  sctp_enqueue_to_ip_lookup_i (vm, b, bi, is_ip4, 1);
-}
-
 /**
  * Convert buffer to INIT
  */
@@ -806,9 +806,8 @@ sctp_send_shutdown (sctp_connection_t * sctp_conn)
   sctp_prepare_shutdown_chunk (sctp_conn, b);
 
   u8 idx = sctp_pick_conn_idx_on_chunk (SHUTDOWN);
-  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);
+  sctp_enqueue_to_output (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 ();
@@ -867,9 +866,8 @@ sctp_send_shutdown_ack (sctp_connection_t * sctp_conn)
   sctp_prepare_shutdown_ack_chunk (sctp_conn, b);
 
   u8 idx = sctp_pick_conn_idx_on_chunk (SHUTDOWN_ACK);
-  sctp_push_ip_hdr (tm, &sctp_conn->sub_conn[idx], b);
-  sctp_enqueue_to_ip_lookup (vm, b, bi,
-                            sctp_conn->sub_conn[idx].connection.is_ip4);
+  sctp_enqueue_to_output (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 ();
@@ -917,6 +915,45 @@ sctp_prepare_sack_chunk (sctp_connection_t * sctp_conn, vlib_buffer_t * b)
     sctp_conn->sub_conn[idx].connection.c_index;
 }
 
+/**
+ * Convert buffer to HEARTBEAT_ACK
+ */
+void
+sctp_prepare_heartbeat_ack_chunk (sctp_connection_t * sctp_conn,
+                                 vlib_buffer_t * b)
+{
+  vlib_main_t *vm = vlib_get_main ();
+
+  u8 idx = sctp_pick_conn_idx_on_chunk (HEARTBEAT_ACK);
+  u16 alloc_bytes = sizeof (sctp_hb_ack_chunk_t);
+
+  sctp_reuse_buffer (vm, b);
+
+  /* As per RFC 4960 the chunk_length value does NOT contemplate
+   * the size of the first header (see sctp_header_t) and any padding
+   */
+  u16 chunk_len = alloc_bytes - sizeof (sctp_header_t);
+
+  alloc_bytes += vnet_sctp_calculate_padding (alloc_bytes);
+
+  sctp_hb_ack_chunk_t *hb_ack = vlib_buffer_push_uninit (b, alloc_bytes);
+
+  hb_ack->sctp_hdr.checksum = 0;
+  /* No need of host_to_net conversion, already in net-byte order */
+  hb_ack->sctp_hdr.src_port = sctp_conn->sub_conn[idx].connection.lcl_port;
+  hb_ack->sctp_hdr.dst_port = sctp_conn->sub_conn[idx].connection.rmt_port;
+  hb_ack->sctp_hdr.verification_tag = sctp_conn->remote_tag;
+  hb_ack->hb_info.param_hdr.type = clib_host_to_net_u16 (1);
+  hb_ack->hb_info.param_hdr.length =
+    clib_host_to_net_u16 (sizeof (hb_ack->hb_info.hb_info));
+
+  vnet_sctp_set_chunk_type (&hb_ack->chunk_hdr, HEARTBEAT_ACK);
+  vnet_sctp_set_chunk_length (&hb_ack->chunk_hdr, chunk_len);
+
+  vnet_buffer (b)->sctp.connection_index =
+    sctp_conn->sub_conn[idx].connection.c_index;
+}
+
 /**
  * Convert buffer to HEARTBEAT
  */
@@ -924,12 +961,9 @@ void
 sctp_prepare_heartbeat_chunk (sctp_connection_t * sctp_conn,
                              vlib_buffer_t * b)
 {
-  vlib_main_t *vm = vlib_get_main ();
   u8 idx = sctp_pick_conn_idx_on_chunk (HEARTBEAT);
   u16 alloc_bytes = sizeof (sctp_hb_req_chunk_t);
 
-  b = sctp_reuse_buffer (vm, b);
-
   /* As per RFC 4960 the chunk_length value does NOT contemplate
    * the size of the first header (see sctp_header_t) and any padding
    */
@@ -971,9 +1005,10 @@ sctp_send_heartbeat (sctp_connection_t * sctp_conn)
   sctp_prepare_heartbeat_chunk (sctp_conn, b);
 
   u8 idx = sctp_pick_conn_idx_on_state (SCTP_STATE_ESTABLISHED);
-  sctp_push_ip_hdr (tm, &sctp_conn->sub_conn[idx], b);
-  sctp_enqueue_to_ip_lookup (vm, b, bi,
-                            sctp_conn->sub_conn[idx].connection.is_ip4);
+  sctp_enqueue_to_output_now (vm, b, bi,
+                             sctp_conn->sub_conn[idx].connection.is_ip4);
+
+  sctp_conn->sub_conn[idx].unacknowledged_hb += 1;
 }
 
 /**
@@ -1023,9 +1058,8 @@ sctp_send_shutdown_complete (sctp_connection_t * sctp_conn)
   sctp_prepare_shutdown_complete_chunk (sctp_conn, b);
 
   u8 idx = sctp_pick_conn_idx_on_chunk (SHUTDOWN_COMPLETE);
-  sctp_push_ip_hdr (tm, &sctp_conn->sub_conn[idx], b);
-  sctp_enqueue_to_ip_lookup (vm, b, bi,
-                            sctp_conn->sub_conn[idx].connection.is_ip4);
+  sctp_enqueue_to_output (vm, b, bi,
+                         sctp_conn->sub_conn[idx].connection.is_ip4);
 
   sctp_conn->state = SCTP_STATE_CLOSED;
 }
@@ -1052,8 +1086,7 @@ sctp_send_init (sctp_connection_t * sctp_conn)
   sctp_prepare_init_chunk (sctp_conn, b);
 
   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);
+  sctp_enqueue_to_ip_lookup (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 ();
@@ -1125,6 +1158,21 @@ sctp_push_header (transport_connection_t * trans_conn, vlib_buffer_t * b)
 
   u8 idx = sctp_pick_conn_idx_on_chunk (DATA);
 
+  if (sctp_conn->sub_conn[idx].unacknowledged_hb >
+      SCTP_ASSOCIATION_MAX_RETRANS)
+    {
+      // The remote-peer is considered to be unreachable hence shutting down
+
+      /* Start cleanup. App wasn't notified yet so use delete notify as
+       * opposed to delete to cleanup session layer state. */
+      stream_session_delete_notify (&sctp_conn->sub_conn
+                                   [MAIN_SCTP_SUB_CONN_IDX].connection);
+
+      sctp_connection_timers_reset (sctp_conn);
+
+      sctp_connection_cleanup (sctp_conn);
+    }
+
   sctp_push_hdr_i (sctp_conn, idx, b, SCTP_STATE_ESTABLISHED);
 
   if (sctp_conn->sub_conn[idx].RTO_pending == 0)