| /* |
| * Copyright (c) 2018 SUSE LLC. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <vnet/sctp/sctp.h> |
| #include <vnet/sctp/sctp_debug.h> |
| #include <vppinfra/random.h> |
| #include <openssl/hmac.h> |
| |
| u32 |
| ip6_sctp_compute_checksum (vlib_main_t * vm, vlib_buffer_t * p0, |
| ip6_header_t * ip0, int *bogus_lengthp); |
| |
| u32 |
| ip4_sctp_compute_checksum (vlib_main_t * vm, vlib_buffer_t * p0, |
| ip4_header_t * ip0); |
| |
| #define foreach_sctp4_output_next \ |
| _ (DROP, "error-drop") \ |
| _ (IP_LOOKUP, "ip4-lookup") |
| |
| #define foreach_sctp6_output_next \ |
| _ (DROP, "error-drop") \ |
| _ (IP_LOOKUP, "ip6-lookup") |
| |
| static char *sctp_error_strings[] = { |
| #define sctp_error(n,s) s, |
| #include <vnet/sctp/sctp_error.def> |
| #undef sctp_error |
| }; |
| |
| typedef enum _sctp_output_next |
| { |
| SCTP_OUTPUT_NEXT_DROP, |
| SCTP_OUTPUT_NEXT_IP_LOOKUP, |
| SCTP_OUTPUT_N_NEXT |
| } sctp_output_next_t; |
| |
| typedef struct |
| { |
| sctp_header_t sctp_header; |
| sctp_connection_t sctp_connection; |
| } sctp_tx_trace_t; |
| |
| always_inline u8 |
| sctp_is_retransmitting (sctp_connection_t * sctp_conn, u8 idx) |
| { |
| return sctp_conn->sub_conn[idx].is_retransmitting; |
| } |
| |
| always_inline uword |
| sctp46_output_inline (vlib_main_t * vm, |
| vlib_node_runtime_t * node, |
| vlib_frame_t * from_frame, int is_ip4) |
| { |
| u32 n_left_from, next_index, *from, *to_next; |
| u32 my_thread_index = vm->thread_index; |
| |
| from = vlib_frame_vector_args (from_frame); |
| n_left_from = from_frame->n_vectors; |
| next_index = node->cached_next_index; |
| sctp_set_time_now (my_thread_index); |
| |
| while (n_left_from > 0) |
| { |
| u32 n_left_to_next; |
| |
| vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); |
| |
| while (n_left_from > 0 && n_left_to_next > 0) |
| { |
| u32 bi0; |
| vlib_buffer_t *b0; |
| sctp_header_t *sctp_hdr = 0; |
| sctp_connection_t *sctp_conn; |
| sctp_tx_trace_t *t0; |
| sctp_header_t *th0 = 0; |
| u32 error0 = SCTP_ERROR_PKTS_SENT, next0 = |
| SCTP_OUTPUT_NEXT_IP_LOOKUP; |
| |
| #if SCTP_DEBUG_STATE_MACHINE |
| u16 packet_length = 0; |
| #endif |
| |
| bi0 = from[0]; |
| to_next[0] = bi0; |
| from += 1; |
| to_next += 1; |
| n_left_from -= 1; |
| n_left_to_next -= 1; |
| |
| b0 = vlib_get_buffer (vm, bi0); |
| |
| sctp_conn = |
| sctp_connection_get (vnet_buffer (b0)->sctp.connection_index, |
| my_thread_index); |
| |
| if (PREDICT_FALSE (sctp_conn == 0)) |
| { |
| error0 = SCTP_ERROR_INVALID_CONNECTION; |
| next0 = SCTP_OUTPUT_NEXT_DROP; |
| goto done; |
| } |
| |
| u8 idx = vnet_buffer (b0)->sctp.subconn_idx; |
| |
| th0 = vlib_buffer_get_current (b0); |
| |
| if (is_ip4) |
| { |
| ip4_header_t *iph4 = vlib_buffer_push_ip4 (vm, |
| b0, |
| &sctp_conn->sub_conn |
| [idx].connection. |
| lcl_ip.ip4, |
| &sctp_conn-> |
| sub_conn |
| [idx].connection. |
| rmt_ip.ip4, |
| IP_PROTOCOL_SCTP, 1); |
| |
| u32 checksum = ip4_sctp_compute_checksum (vm, b0, iph4); |
| |
| sctp_hdr = ip4_next_header (iph4); |
| sctp_hdr->checksum = checksum; |
| |
| vnet_buffer (b0)->l4_hdr_offset = (u8 *) th0 - b0->data; |
| |
| #if SCTP_DEBUG_STATE_MACHINE |
| packet_length = clib_net_to_host_u16 (iph4->length); |
| #endif |
| } |
| else |
| { |
| ip6_header_t *iph6 = vlib_buffer_push_ip6 (vm, |
| b0, |
| &sctp_conn->sub_conn |
| [idx]. |
| connection.lcl_ip. |
| ip6, |
| &sctp_conn->sub_conn |
| [idx]. |
| connection.rmt_ip. |
| ip6, |
| IP_PROTOCOL_SCTP); |
| |
| int bogus = ~0; |
| u32 checksum = ip6_sctp_compute_checksum (vm, b0, iph6, &bogus); |
| ASSERT (!bogus); |
| |
| sctp_hdr = ip6_next_header (iph6); |
| sctp_hdr->checksum = checksum; |
| |
| vnet_buffer (b0)->l3_hdr_offset = (u8 *) iph6 - b0->data; |
| vnet_buffer (b0)->l4_hdr_offset = (u8 *) th0 - b0->data; |
| |
| #if SCTP_DEBUG_STATE_MACHINE |
| packet_length = clib_net_to_host_u16 (iph6->payload_length); |
| #endif |
| } |
| |
| sctp_full_hdr_t *full_hdr = (sctp_full_hdr_t *) sctp_hdr; |
| u8 chunk_type = vnet_sctp_get_chunk_type (&full_hdr->common_hdr); |
| if (chunk_type >= UNKNOWN) |
| { |
| clib_warning |
| ("Trying to send an unrecognized chunk... something is really bad."); |
| error0 = SCTP_ERROR_UNKNOWN_CHUNK; |
| next0 = SCTP_OUTPUT_NEXT_DROP; |
| goto done; |
| } |
| |
| #if SCTP_DEBUG_STATE_MACHINE |
| u8 is_valid = |
| (sctp_conn->sub_conn[idx].connection.lcl_port == |
| sctp_hdr->src_port |
| || sctp_conn->sub_conn[idx].connection.lcl_port == |
| sctp_hdr->dst_port) |
| && (sctp_conn->sub_conn[idx].connection.rmt_port == |
| sctp_hdr->dst_port |
| || sctp_conn->sub_conn[idx].connection.rmt_port == |
| sctp_hdr->src_port); |
| |
| if (!is_valid) |
| { |
| SCTP_DBG_STATE_MACHINE ("BUFFER IS INCORRECT: conn_index = %u, " |
| "packet_length = %u, " |
| "chunk_type = %u [%s], " |
| "connection.lcl_port = %u, sctp_hdr->src_port = %u, " |
| "connection.rmt_port = %u, sctp_hdr->dst_port = %u", |
| sctp_conn->sub_conn[idx]. |
| connection.c_index, packet_length, |
| chunk_type, |
| sctp_chunk_to_string (chunk_type), |
| sctp_conn->sub_conn[idx]. |
| connection.lcl_port, sctp_hdr->src_port, |
| sctp_conn->sub_conn[idx]. |
| connection.rmt_port, |
| sctp_hdr->dst_port); |
| |
| error0 = SCTP_ERROR_UNKNOWN_CHUNK; |
| next0 = SCTP_OUTPUT_NEXT_DROP; |
| goto done; |
| } |
| #endif |
| SCTP_DBG_STATE_MACHINE |
| ("SESSION_INDEX = %u, CONN_INDEX = %u, CURR_CONN_STATE = %u (%s), " |
| "CHUNK_TYPE = %s, " "SRC_PORT = %u, DST_PORT = %u", |
| sctp_conn->sub_conn[idx].connection.s_index, |
| sctp_conn->sub_conn[idx].connection.c_index, |
| sctp_conn->state, sctp_state_to_string (sctp_conn->state), |
| sctp_chunk_to_string (chunk_type), full_hdr->hdr.src_port, |
| full_hdr->hdr.dst_port); |
| |
| /* Let's make sure the state-machine does not send anything crazy */ |
| #if SCTP_DEBUG_STATE_MACHINE |
| if (sctp_validate_output_state_machine (sctp_conn, chunk_type) != 0) |
| { |
| SCTP_DBG_STATE_MACHINE |
| ("Sending the wrong chunk (%s) based on state-machine status (%s)", |
| sctp_chunk_to_string (chunk_type), |
| sctp_state_to_string (sctp_conn->state)); |
| |
| error0 = SCTP_ERROR_UNKNOWN_CHUNK; |
| next0 = SCTP_OUTPUT_NEXT_DROP; |
| goto done; |
| |
| } |
| #endif |
| |
| /* Karn's algorithm: RTT measurements MUST NOT be made using |
| * packets that were retransmitted |
| */ |
| if (!sctp_is_retransmitting (sctp_conn, idx)) |
| { |
| /* Measure RTT with this */ |
| if (chunk_type == DATA |
| && sctp_conn->sub_conn[idx].RTO_pending == 0) |
| { |
| sctp_conn->sub_conn[idx].RTO_pending = 1; |
| sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now (); |
| } |
| else |
| sctp_conn->sub_conn[idx].rtt_ts = sctp_time_now (); |
| } |
| |
| /* Let's take care of TIMERS */ |
| switch (chunk_type) |
| { |
| case COOKIE_ECHO: |
| { |
| sctp_conn->state = SCTP_STATE_COOKIE_ECHOED; |
| break; |
| } |
| case DATA: |
| { |
| SCTP_ADV_DBG_OUTPUT ("PACKET_LENGTH = %u", packet_length); |
| |
| sctp_timer_update (sctp_conn, idx, SCTP_TIMER_T3_RXTX, |
| sctp_conn->sub_conn[idx].RTO); |
| break; |
| } |
| case SHUTDOWN: |
| { |
| /* Start the SCTP_TIMER_T2_SHUTDOWN timer */ |
| sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T2_SHUTDOWN, |
| sctp_conn->sub_conn[idx].RTO); |
| sctp_conn->state = SCTP_STATE_SHUTDOWN_SENT; |
| break; |
| } |
| case SHUTDOWN_ACK: |
| { |
| /* Start the SCTP_TIMER_T2_SHUTDOWN timer */ |
| sctp_timer_set (sctp_conn, idx, SCTP_TIMER_T2_SHUTDOWN, |
| sctp_conn->sub_conn[idx].RTO); |
| sctp_conn->state = SCTP_STATE_SHUTDOWN_ACK_SENT; |
| break; |
| } |
| case SHUTDOWN_COMPLETE: |
| { |
| sctp_conn->state = SCTP_STATE_CLOSED; |
| break; |
| } |
| } |
| |
| vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0; |
| vnet_buffer (b0)->sw_if_index[VLIB_TX] = |
| sctp_conn->sub_conn[idx].c_fib_index; |
| |
| b0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED; |
| |
| SCTP_DBG_STATE_MACHINE |
| ("SESSION_INDEX = %u, CONNECTION_INDEX = %u, " "NEW_STATE = %s, " |
| "CHUNK_SENT = %s", sctp_conn->sub_conn[idx].connection.s_index, |
| sctp_conn->sub_conn[idx].connection.c_index, |
| sctp_state_to_string (sctp_conn->state), |
| sctp_chunk_to_string (chunk_type)); |
| |
| vnet_sctp_common_hdr_params_host_to_net (&full_hdr->common_hdr); |
| |
| done: |
| b0->error = node->errors[error0]; |
| if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) |
| { |
| t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); |
| if (th0) |
| { |
| clib_memcpy_fast (&t0->sctp_header, th0, |
| sizeof (t0->sctp_header)); |
| } |
| else |
| { |
| clib_memset (&t0->sctp_header, 0, sizeof (t0->sctp_header)); |
| } |
| clib_memcpy_fast (&t0->sctp_connection, sctp_conn, |
| sizeof (t0->sctp_connection)); |
| } |
| |
| vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, |
| n_left_to_next, bi0, next0); |
| } |
| |
| vlib_put_next_frame (vm, node, next_index, n_left_to_next); |
| } |
| |
| return from_frame->n_vectors; |
| } |
| |
| VLIB_NODE_FN (sctp4_output_node) (vlib_main_t * vm, |
| vlib_node_runtime_t * node, |
| vlib_frame_t * from_frame) |
| { |
| return sctp46_output_inline (vm, node, from_frame, 1 /* is_ip4 */ ); |
| } |
| |
| VLIB_NODE_FN (sctp6_output_node) (vlib_main_t * vm, |
| vlib_node_runtime_t * node, |
| vlib_frame_t * from_frame) |
| { |
| return sctp46_output_inline (vm, node, from_frame, 0 /* is_ip4 */ ); |
| } |
| |
| /* *INDENT-OFF* */ |
| VLIB_REGISTER_NODE (sctp4_output_node) = |
| { |
| .name = "sctp4-output", |
| /* Takes a vector of packets. */ |
| .vector_size = sizeof (u32), |
| .n_errors = SCTP_N_ERROR, |
| .error_strings = sctp_error_strings, |
| .n_next_nodes = SCTP_OUTPUT_N_NEXT, |
| .next_nodes = { |
| #define _(s,n) [SCTP_OUTPUT_NEXT_##s] = n, |
| foreach_sctp4_output_next |
| #undef _ |
| }, |
| .format_buffer = format_sctp_header, |
| .format_trace = format_sctp_tx_trace, |
| }; |
| /* *INDENT-ON* */ |
| |
| /* *INDENT-OFF* */ |
| VLIB_REGISTER_NODE (sctp6_output_node) = |
| { |
| .name = "sctp6-output", |
| /* Takes a vector of packets. */ |
| .vector_size = sizeof (u32), |
| .n_errors = SCTP_N_ERROR, |
| .error_strings = sctp_error_strings, |
| .n_next_nodes = SCTP_OUTPUT_N_NEXT, |
| .next_nodes = { |
| #define _(s,n) [SCTP_OUTPUT_NEXT_##s] = n, |
| foreach_sctp6_output_next |
| #undef _ |
| }, |
| .format_buffer = format_sctp_header, |
| .format_trace = format_sctp_tx_trace, |
| }; |
| /* *INDENT-ON* */ |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |