From df5a99cef13ff6a22c195091be45152dc65f5d71 Mon Sep 17 00:00:00 2001 From: Marco Varlese Date: Tue, 6 Feb 2018 13:48:30 +0100 Subject: [PATCH] SCTP: handling of heartbeating and max-retransmits 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 --- src/vnet/sctp/sctp.c | 49 +++++++++++++---------- src/vnet/sctp/sctp.h | 41 +++++++++++++++---- src/vnet/sctp/sctp_input.c | 35 ++++++++++++++--- src/vnet/sctp/sctp_output.c | 96 +++++++++++++++++++++++++++++++++------------ 4 files changed, 164 insertions(+), 57 deletions(-) diff --git a/src/vnet/sctp/sctp.c b/src/vnet/sctp/sctp.c index f8cd21c63ec..61c0252b1ba 100644 --- a/src/vnet/sctp/sctp.c +++ b/src/vnet/sctp/sctp.c @@ -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); } } diff --git a/src/vnet/sctp/sctp.h b/src/vnet/sctp/sctp.h index 0634f019af7..9129aa80682 100644 --- a/src/vnet/sctp/sctp.h +++ b/src/vnet/sctp/sctp.h @@ -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); diff --git a/src/vnet/sctp/sctp_input.c b/src/vnet/sctp/sctp_input.c index 0337e9f8aa0..66536884482 100644 --- a/src/vnet/sctp/sctp_input.c +++ b/src/vnet/sctp/sctp_input.c @@ -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: diff --git a/src/vnet/sctp/sctp_output.c b/src/vnet/sctp/sctp_output.c index ed9ffd36262..3ec54dc6c1a 100644 --- a/src/vnet/sctp/sctp_output.c +++ b/src/vnet/sctp/sctp_output.c @@ -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) -- 2.16.6