X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvnet%2Ftcp%2Ftcp_input.c;h=1b4e8a6ac21776963409db94e7042db0dd826520;hb=1cfcb78940580c8e3645fca0419d32f9286e942d;hp=289a6d66e3e327649e6080875e5b8d493da217b3;hpb=4df3871a98e4f06c23e8466eef303adcd1c83953;p=vpp.git diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index 289a6d66e3e..1b4e8a6ac21 100644 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Cisco and/or its affiliates. + * Copyright (c) 2016-2019 Cisco and/or its affiliates. * 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: @@ -79,9 +79,6 @@ typedef enum _tcp_state_next #define tcp_next_drop(is_ip4) (is_ip4 ? TCP_NEXT_DROP4 \ : TCP_NEXT_DROP6) -vlib_node_registration_t tcp4_established_node; -vlib_node_registration_t tcp6_established_node; - /** * Validate segment sequence number. As per RFC793: * @@ -121,10 +118,11 @@ tcp_segment_in_rcv_wnd (tcp_connection_t * tc, u32 seq, u32 end_seq) * * @param th TCP header * @param to TCP options data structure to be populated + * @param is_syn set if packet is syn * @return -1 if parsing failed */ -int -tcp_options_parse (tcp_header_t * th, tcp_options_t * to) +static inline int +tcp_options_parse (tcp_header_t * th, tcp_options_t * to, u8 is_syn) { const u8 *data; u8 opt_len, opts_len, kind; @@ -135,7 +133,8 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to) data = (const u8 *) (th + 1); /* Zero out all flags but those set in SYN */ - to->flags &= (TCP_OPTS_FLAG_SACK_PERMITTED | TCP_OPTS_FLAG_WSCALE); + to->flags &= (TCP_OPTS_FLAG_SACK_PERMITTED | TCP_OPTS_FLAG_WSCALE + | TCP_OPTS_FLAG_TSTAMP | TCP_OPTION_MSS); for (; opts_len > 0; opts_len -= opt_len, data += opt_len) { @@ -165,6 +164,8 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to) switch (kind) { case TCP_OPTION_MSS: + if (!is_syn) + break; if ((opt_len == TCP_OPTION_LEN_MSS) && tcp_syn (th)) { to->flags |= TCP_OPTS_FLAG_MSS; @@ -172,27 +173,29 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to) } break; case TCP_OPTION_WINDOW_SCALE: + if (!is_syn) + break; if ((opt_len == TCP_OPTION_LEN_WINDOW_SCALE) && tcp_syn (th)) { to->flags |= TCP_OPTS_FLAG_WSCALE; to->wscale = data[2]; if (to->wscale > TCP_MAX_WND_SCALE) - { - clib_warning ("Illegal window scaling value: %d", - to->wscale); - to->wscale = TCP_MAX_WND_SCALE; - } + to->wscale = TCP_MAX_WND_SCALE; } break; case TCP_OPTION_TIMESTAMP: - if (opt_len == TCP_OPTION_LEN_TIMESTAMP) + if (is_syn) + to->flags |= TCP_OPTS_FLAG_TSTAMP; + if ((to->flags & TCP_OPTS_FLAG_TSTAMP) + && opt_len == TCP_OPTION_LEN_TIMESTAMP) { - to->flags |= TCP_OPTS_FLAG_TSTAMP; to->tsval = clib_net_to_host_u32 (*(u32 *) (data + 2)); to->tsecr = clib_net_to_host_u32 (*(u32 *) (data + 6)); } break; case TCP_OPTION_SACK_PERMITTED: + if (!is_syn) + break; if (opt_len == TCP_OPTION_LEN_SACK_PERMITTED && tcp_syn (th)) to->flags |= TCP_OPTS_FLAG_SACK_PERMITTED; break; @@ -235,7 +238,7 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to) always_inline int tcp_segment_check_paws (tcp_connection_t * tc) { - return tcp_opts_tstamp (&tc->rcv_opts) && tc->tsval_recent + return tcp_opts_tstamp (&tc->rcv_opts) && timestamp_lt (tc->rcv_opts.tsval, tc->tsval_recent); } @@ -257,7 +260,7 @@ tcp_update_timestamp (tcp_connection_t * tc, u32 seq, u32 seq_end) { ASSERT (timestamp_leq (tc->tsval_recent, tc->rcv_opts.tsval)); tc->tsval_recent = tc->rcv_opts.tsval; - tc->tsval_recent_age = tcp_time_now (); + tc->tsval_recent_age = tcp_time_now_w_thread (tc->c_thread_index); } } @@ -271,80 +274,107 @@ tcp_update_timestamp (tcp_connection_t * tc, u32 seq, u32 seq_end) * @return 0 if segments passes validation. */ static int -tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0, - vlib_buffer_t * b0, tcp_header_t * th0, - u32 * next0, u32 * error0) +tcp_segment_validate (tcp_worker_ctx_t * wrk, tcp_connection_t * tc0, + vlib_buffer_t * b0, tcp_header_t * th0, u32 * error0) { /* We could get a burst of RSTs interleaved with acks */ if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED)) { tcp_send_reset (tc0); *error0 = TCP_ERROR_CONNECTION_CLOSED; - goto drop; + goto error; } if (PREDICT_FALSE (!tcp_ack (th0) && !tcp_rst (th0) && !tcp_syn (th0))) { *error0 = TCP_ERROR_SEGMENT_INVALID; - goto drop; + goto error; } - if (PREDICT_FALSE (tcp_options_parse (th0, &tc0->rcv_opts))) + if (PREDICT_FALSE (tcp_options_parse (th0, &tc0->rcv_opts, 0))) { - clib_warning ("options parse error"); *error0 = TCP_ERROR_OPTIONS; - goto drop; + goto error; } if (PREDICT_FALSE (tcp_segment_check_paws (tc0))) { *error0 = TCP_ERROR_PAWS; - if (CLIB_DEBUG > 2) - clib_warning ("paws failed\n%U", format_tcp_connection, tc0, 2); TCP_EVT_DBG (TCP_EVT_PAWS_FAIL, tc0, vnet_buffer (b0)->tcp.seq_number, vnet_buffer (b0)->tcp.seq_end); /* If it just so happens that a segment updates tsval_recent for a * segment over 24 days old, invalidate tsval_recent. */ if (timestamp_lt (tc0->tsval_recent_age + TCP_PAWS_IDLE, - tcp_time_now ())) + tcp_time_now_w_thread (tc0->c_thread_index))) { - /* Age isn't reset until we get a valid tsval (bsd inspired) */ - tc0->tsval_recent = 0; - clib_warning ("paws failed - really old segment. REALLY?"); + tc0->tsval_recent = tc0->rcv_opts.tsval; + clib_warning ("paws failed: 24-day old segment"); } - else + /* Drop after ack if not rst. Resets can fail paws check as per + * RFC 7323 sec. 5.2: When an segment is received, it MUST NOT + * be subjected to the PAWS check by verifying an acceptable value in + * SEG.TSval */ + else if (!tcp_rst (th0)) { - /* Drop after ack if not rst */ - if (!tcp_rst (th0)) - { - tcp_make_ack (tc0, b0); - TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp); - goto error; - } + tcp_program_ack (wrk, tc0); + TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp); + goto error; } - goto drop; } /* 1st: check sequence number */ if (!tcp_segment_in_rcv_wnd (tc0, vnet_buffer (b0)->tcp.seq_number, vnet_buffer (b0)->tcp.seq_end)) { - *error0 = TCP_ERROR_RCV_WND; - /* If our window is 0 and the packet is in sequence, let it pass - * through for ack processing. It should be dropped later. */ - if (!(tc0->rcv_wnd == 0 - && tc0->rcv_nxt == vnet_buffer (b0)->tcp.seq_number)) + /* SYN/SYN-ACK retransmit */ + if (tcp_syn (th0) + && vnet_buffer (b0)->tcp.seq_number == tc0->rcv_nxt - 1) { - /* If not RST, send dup ack */ - if (!tcp_rst (th0)) + tcp_options_parse (th0, &tc0->rcv_opts, 1); + if (tc0->state == TCP_STATE_SYN_RCVD) { - tcp_make_ack (tc0, b0); - TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp); - goto error; + tcp_send_synack (tc0); + TCP_EVT_DBG (TCP_EVT_SYN_RCVD, tc0, 0); + *error0 = TCP_ERROR_SYNS_RCVD; } - goto drop; + else + { + tcp_program_ack (wrk, tc0); + TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, tc0); + *error0 = TCP_ERROR_SYN_ACKS_RCVD; + } + goto error; + } + + /* If our window is 0 and the packet is in sequence, let it pass + * through for ack processing. It should be dropped later. */ + if (tc0->rcv_wnd < tc0->snd_mss + && tc0->rcv_nxt == vnet_buffer (b0)->tcp.seq_number) + goto check_reset; + + /* If we entered recovery and peer did so as well, there's a chance that + * dup acks won't be acceptable on either end because seq_end may be less + * than rcv_las. This can happen if acks are lost in both directions. */ + if (tcp_in_recovery (tc0) + && seq_geq (vnet_buffer (b0)->tcp.seq_number, + tc0->rcv_las - tc0->rcv_wnd) + && seq_leq (vnet_buffer (b0)->tcp.seq_end, + tc0->rcv_nxt + tc0->rcv_wnd)) + goto check_reset; + + *error0 = TCP_ERROR_RCV_WND; + + /* If not RST, send dup ack */ + if (!tcp_rst (th0)) + { + tcp_program_dupack (wrk, tc0); + TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc0, vnet_buffer (b0)->tcp); } + goto error; + + check_reset: + ; } /* 2nd: check the RST bit */ @@ -352,25 +382,17 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0, { tcp_connection_reset (tc0); *error0 = TCP_ERROR_RST_RCVD; - goto drop; + goto error; } /* 3rd: check security and precedence (skip) */ - /* 4th: check the SYN bit */ + /* 4th: check the SYN bit (in window) */ if (PREDICT_FALSE (tcp_syn (th0))) { - /* TODO implement RFC 5961 */ - if (tc0->state == TCP_STATE_SYN_RCVD) - { - tcp_make_synack (tc0, b0); - TCP_EVT_DBG (TCP_EVT_SYN_RCVD, tc0, 0); - } - else - { - tcp_make_ack (tc0, b0); - TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, tc0); - } + /* As per RFC5961 send challenge ack instead of reset */ + tcp_program_ack (wrk, tc0); + *error0 = TCP_ERROR_SPURIOUS_SYN; goto error; } @@ -379,20 +401,32 @@ tcp_segment_validate (vlib_main_t * vm, tcp_connection_t * tc0, vnet_buffer (b0)->tcp.seq_end); return 0; -drop: - *next0 = tcp_next_drop (tc0->c_is_ip4); - return -1; error: - *next0 = tcp_next_output (tc0->c_is_ip4); return -1; } always_inline int -tcp_rcv_ack_is_acceptable (tcp_connection_t * tc0, vlib_buffer_t * tb0) +tcp_rcv_ack_no_cc (tcp_connection_t * tc, vlib_buffer_t * b, u32 * error) { /* SND.UNA =< SEG.ACK =< SND.NXT */ - return (seq_leq (tc0->snd_una, vnet_buffer (tb0)->tcp.ack_number) - && seq_leq (vnet_buffer (tb0)->tcp.ack_number, tc0->snd_nxt)); + if (!(seq_leq (tc->snd_una, vnet_buffer (b)->tcp.ack_number) + && seq_leq (vnet_buffer (b)->tcp.ack_number, tc->snd_nxt))) + { + if (seq_leq (vnet_buffer (b)->tcp.ack_number, tc->snd_una_max) + && seq_gt (vnet_buffer (b)->tcp.ack_number, tc->snd_una)) + { + tc->snd_nxt = vnet_buffer (b)->tcp.ack_number; + goto acceptable; + } + *error = TCP_ERROR_ACK_INVALID; + return -1; + } + +acceptable: + tc->bytes_acked = vnet_buffer (b)->tcp.ack_number - tc->snd_una; + tc->snd_una = vnet_buffer (b)->tcp.ack_number; + *error = TCP_ERROR_ACK_OK; + return 0; } /** @@ -427,12 +461,14 @@ tcp_estimate_rtt (tcp_connection_t * tc, u32 mrtt) } } +#ifndef CLIB_MARCH_VARIANT void tcp_update_rto (tcp_connection_t * tc) { tc->rto = clib_min (tc->srtt + (tc->rttvar << 2), TCP_RTO_MAX); tc->rto = clib_max (tc->rto, TCP_RTO_MIN); } +#endif /* CLIB_MARCH_VARIANT */ /** * Update RTT estimate and RTO timer @@ -453,18 +489,27 @@ tcp_update_rtt (tcp_connection_t * tc, u32 ack) /* Karn's rule, part 1. Don't use retransmitted segments to estimate * RTT because they're ambiguous. */ if (tcp_in_cong_recovery (tc) || tc->sack_sb.sacked_bytes) - goto done; + { + if (tcp_in_recovery (tc)) + return 0; + goto done; + } if (tc->rtt_ts && seq_geq (ack, tc->rtt_seq)) { - mrtt = tcp_time_now () - tc->rtt_ts; + f64 sample = tcp_time_now_us (tc->c_thread_index) - tc->rtt_ts; + tc->mrtt_us = tc->mrtt_us + (sample - tc->mrtt_us) * 0.125; + mrtt = clib_max ((u32) (sample * THZ), 1); + /* Allow measuring of a new RTT */ + tc->rtt_ts = 0; } /* As per RFC7323 TSecr can be used for RTTM only if the segment advances * snd_una, i.e., the left side of the send window: * seq_lt (tc->snd_una, ack). This is a condition for calling update_rtt */ else if (tcp_opts_tstamp (&tc->rcv_opts) && tc->rcv_opts.tsecr) { - mrtt = tcp_time_now () - tc->rcv_opts.tsecr; + u32 now = tcp_tstamp (tc); + mrtt = clib_max (now - tc->rcv_opts.tsecr, 1); } /* Ignore dubious measurements */ @@ -475,35 +520,97 @@ tcp_update_rtt (tcp_connection_t * tc, u32 ack) done: - /* Allow measuring of a new RTT */ - tc->rtt_ts = 0; - /* If we got here something must've been ACKed so make sure boff is 0, - * even if mrrt is not valid since we update the rto lower */ + * even if mrtt is not valid since we update the rto lower */ tc->rto_boff = 0; tcp_update_rto (tc); return 0; } +static void +tcp_estimate_initial_rtt (tcp_connection_t * tc) +{ + u8 thread_index = vlib_num_workers ()? 1 : 0; + int mrtt; + + if (tc->rtt_ts) + { + tc->mrtt_us = tcp_time_now_us (thread_index) - tc->rtt_ts; + tc->mrtt_us = clib_max (tc->mrtt_us, 0.0001); + mrtt = clib_max ((u32) (tc->mrtt_us * THZ), 1); + tc->rtt_ts = 0; + } + else + { + mrtt = tcp_time_now_w_thread (thread_index) - tc->rcv_opts.tsecr; + mrtt = clib_max (mrtt, 1); + /* Due to retransmits we don't know the initial mrtt */ + if (tc->rto_boff && mrtt > 1 * THZ) + mrtt = 1 * THZ; + tc->mrtt_us = (f64) mrtt *TCP_TICK; + } + + if (mrtt > 0 && mrtt < TCP_RTT_MAX) + tcp_estimate_rtt (tc, mrtt); + tcp_update_rto (tc); +} + /** - * Dequeue bytes that have been acked and while at it update RTT estimates. + * Dequeue bytes for connections that have received acks in last burst */ static void -tcp_dequeue_acked (tcp_connection_t * tc, u32 ack) +tcp_handle_postponed_dequeues (tcp_worker_ctx_t * wrk) { - /* Dequeue the newly ACKed add SACKed bytes */ - stream_session_dequeue_drop (&tc->connection, - tc->bytes_acked + tc->sack_sb.snd_una_adv); + u32 thread_index = wrk->vm->thread_index; + u32 *pending_deq_acked; + tcp_connection_t *tc; + int i; - tcp_validate_txf_size (tc, tc->snd_una_max - tc->snd_una); + if (!vec_len (wrk->pending_deq_acked)) + return; - /* Update rtt and rto */ - tcp_update_rtt (tc, ack); + pending_deq_acked = wrk->pending_deq_acked; + for (i = 0; i < vec_len (pending_deq_acked); i++) + { + tc = tcp_connection_get (pending_deq_acked[i], thread_index); + tc->flags &= ~TCP_CONN_DEQ_PENDING; - /* If everything has been acked, stop retransmit timer - * otherwise update. */ - tcp_retransmit_timer_update (tc); + if (PREDICT_FALSE (!tc->burst_acked)) + continue; + + /* Dequeue the newly ACKed bytes */ + session_tx_fifo_dequeue_drop (&tc->connection, tc->burst_acked); + tc->burst_acked = 0; + tcp_validate_txf_size (tc, tc->snd_una_max - tc->snd_una); + + if (PREDICT_FALSE (tc->flags & TCP_CONN_PSH_PENDING)) + { + if (seq_leq (tc->psh_seq, tc->snd_una)) + tc->flags &= ~TCP_CONN_PSH_PENDING; + } + + /* If everything has been acked, stop retransmit timer + * otherwise update. */ + tcp_retransmit_timer_update (tc); + + /* If not congested, update pacer based on our new + * cwnd estimate */ + if (!tcp_in_fastrecovery (tc)) + tcp_connection_tx_pacer_update (tc); + } + _vec_len (wrk->pending_deq_acked) = 0; +} + +static void +tcp_program_dequeue (tcp_worker_ctx_t * wrk, tcp_connection_t * tc) +{ + if (!(tc->flags & TCP_CONN_DEQ_PENDING)) + { + vec_add1 (wrk->pending_deq_acked, tc->c_c_index); + tc->flags |= TCP_CONN_DEQ_PENDING; + } + tc->burst_acked += tc->bytes_acked + tc->sack_sb.snd_una_adv; } /** @@ -514,7 +621,7 @@ tcp_ack_is_dupack (tcp_connection_t * tc, vlib_buffer_t * b, u32 prev_snd_wnd, u32 prev_snd_una) { return ((vnet_buffer (b)->tcp.ack_number == prev_snd_una) - && seq_gt (tc->snd_una_max, tc->snd_una) + && seq_gt (tc->snd_nxt, tc->snd_una) && (vnet_buffer (b)->tcp.seq_end == vnet_buffer (b)->tcp.seq_number) && (prev_snd_wnd == tc->snd_wnd)); } @@ -534,7 +641,61 @@ tcp_ack_is_cc_event (tcp_connection_t * tc, vlib_buffer_t * b, return ((*is_dack || tcp_in_cong_recovery (tc)) && !tcp_is_lost_fin (tc)); } -void +#ifndef CLIB_MARCH_VARIANT +static u32 +scoreboard_hole_index (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole) +{ + ASSERT (!pool_is_free_index (sb->holes, hole - sb->holes)); + return hole - sb->holes; +} + +static u32 +scoreboard_hole_bytes (sack_scoreboard_hole_t * hole) +{ + return hole->end - hole->start; +} + +sack_scoreboard_hole_t * +scoreboard_get_hole (sack_scoreboard_t * sb, u32 index) +{ + if (index != TCP_INVALID_SACK_HOLE_INDEX) + return pool_elt_at_index (sb->holes, index); + return 0; +} + +sack_scoreboard_hole_t * +scoreboard_next_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole) +{ + if (hole->next != TCP_INVALID_SACK_HOLE_INDEX) + return pool_elt_at_index (sb->holes, hole->next); + return 0; +} + +sack_scoreboard_hole_t * +scoreboard_prev_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole) +{ + if (hole->prev != TCP_INVALID_SACK_HOLE_INDEX) + return pool_elt_at_index (sb->holes, hole->prev); + return 0; +} + +sack_scoreboard_hole_t * +scoreboard_first_hole (sack_scoreboard_t * sb) +{ + if (sb->head != TCP_INVALID_SACK_HOLE_INDEX) + return pool_elt_at_index (sb->holes, sb->head); + return 0; +} + +sack_scoreboard_hole_t * +scoreboard_last_hole (sack_scoreboard_t * sb) +{ + if (sb->tail != TCP_INVALID_SACK_HOLE_INDEX) + return pool_elt_at_index (sb->holes, sb->tail); + return 0; +} + +static void scoreboard_remove_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole) { sack_scoreboard_hole_t *next, *prev; @@ -564,12 +725,12 @@ scoreboard_remove_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * hole) /* Poison the entry */ if (CLIB_DEBUG > 0) - memset (hole, 0xfe, sizeof (*hole)); + clib_memset (hole, 0xfe, sizeof (*hole)); pool_put (sb->holes, hole); } -sack_scoreboard_hole_t * +static sack_scoreboard_hole_t * scoreboard_insert_hole (sack_scoreboard_t * sb, u32 prev_index, u32 start, u32 end) { @@ -577,7 +738,7 @@ scoreboard_insert_hole (sack_scoreboard_t * sb, u32 prev_index, u32 hole_index; pool_get (sb->holes, hole); - memset (hole, 0, sizeof (*hole)); + clib_memset (hole, 0, sizeof (*hole)); hole->start = start; hole->end = end; @@ -605,43 +766,51 @@ scoreboard_insert_hole (sack_scoreboard_t * sb, u32 prev_index, return hole; } +#endif /* CLIB_MARCH_VARIANT */ -void +#ifndef CLIB_MARCH_VARIANT +static void scoreboard_update_bytes (tcp_connection_t * tc, sack_scoreboard_t * sb) { - sack_scoreboard_hole_t *hole, *prev; + sack_scoreboard_hole_t *left, *right; u32 bytes = 0, blks = 0; sb->lost_bytes = 0; sb->sacked_bytes = 0; - hole = scoreboard_last_hole (sb); - if (!hole) + left = scoreboard_last_hole (sb); + if (!left) return; - if (seq_gt (sb->high_sacked, hole->end)) + if (seq_gt (sb->high_sacked, left->end)) { - bytes = sb->high_sacked - hole->end; + bytes = sb->high_sacked - left->end; blks = 1; } - while ((prev = scoreboard_prev_hole (sb, hole)) - && (bytes < (TCP_DUPACK_THRESHOLD - 1) * tc->snd_mss - && blks < TCP_DUPACK_THRESHOLD)) + while ((right = left) + && bytes < (TCP_DUPACK_THRESHOLD - 1) * tc->snd_mss + && blks < TCP_DUPACK_THRESHOLD + /* left not updated if above conditions fail */ + && (left = scoreboard_prev_hole (sb, right))) { - bytes += hole->start - prev->end; + bytes += right->start - left->end; blks++; - hole = prev; } - while (hole) + /* left is first lost */ + if (left) { - sb->lost_bytes += scoreboard_hole_bytes (hole); - hole->is_lost = 1; - prev = hole; - hole = scoreboard_prev_hole (sb, hole); - if (hole) - bytes += prev->start - hole->end; + do + { + sb->lost_bytes += scoreboard_hole_bytes (right); + left->is_lost = 1; + left = scoreboard_prev_hole (sb, right); + if (left) + bytes += right->start - left->end; + } + while ((right = left)); } + sb->sacked_bytes = bytes; } @@ -653,8 +822,7 @@ scoreboard_update_bytes (tcp_connection_t * tc, sack_scoreboard_t * sb) sack_scoreboard_hole_t * scoreboard_next_rxt_hole (sack_scoreboard_t * sb, sack_scoreboard_hole_t * start, - u8 have_sent_1_smss, - u8 * can_rescue, u8 * snd_limited) + u8 have_unsent, u8 * can_rescue, u8 * snd_limited) { sack_scoreboard_hole_t *hole = 0; @@ -676,16 +844,16 @@ scoreboard_next_rxt_hole (sack_scoreboard_t * sb, } else { - /* Rule (2): output takes care of transmitting new data */ - if (!have_sent_1_smss) + /* Rule (2): available unsent data */ + if (have_unsent) { - hole = 0; sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX; + return 0; } /* Rule (3): if hole not lost */ else if (seq_lt (hole->start, sb->high_sacked)) { - *snd_limited = 1; + *snd_limited = 0; sb->cur_rxt_hole = scoreboard_hole_index (sb, hole); } /* Rule (4): if hole beyond high_sacked */ @@ -704,19 +872,51 @@ scoreboard_next_rxt_hole (sack_scoreboard_t * sb, return hole; } +#endif /* CLIB_MARCH_VARIANT */ -void -scoreboard_init_high_rxt (sack_scoreboard_t * sb, u32 seq) +static void +scoreboard_init_high_rxt (sack_scoreboard_t * sb, u32 snd_una) { sack_scoreboard_hole_t *hole; hole = scoreboard_first_hole (sb); if (hole) { - seq = seq_gt (seq, hole->start) ? seq : hole->start; + snd_una = seq_gt (snd_una, hole->start) ? snd_una : hole->start; sb->cur_rxt_hole = sb->head; } - sb->high_rxt = seq; + sb->high_rxt = snd_una; + sb->rescue_rxt = snd_una - 1; +} + +#ifndef CLIB_MARCH_VARIANT +void +scoreboard_init (sack_scoreboard_t * sb) +{ + sb->head = TCP_INVALID_SACK_HOLE_INDEX; + sb->tail = TCP_INVALID_SACK_HOLE_INDEX; + sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX; +} + +void +scoreboard_clear (sack_scoreboard_t * sb) +{ + sack_scoreboard_hole_t *hole; + while ((hole = scoreboard_first_hole (sb))) + { + scoreboard_remove_hole (sb, hole); + } + ASSERT (sb->head == sb->tail && sb->head == TCP_INVALID_SACK_HOLE_INDEX); + ASSERT (pool_elts (sb->holes) == 0); + sb->sacked_bytes = 0; + sb->last_sacked_bytes = 0; + sb->last_bytes_delivered = 0; + sb->snd_una_adv = 0; + sb->high_sacked = 0; + sb->high_rxt = 0; + sb->lost_bytes = 0; + sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX; } +#endif /* CLIB_MARCH_VARIANT */ /** * Test that scoreboard is sane after recovery @@ -724,14 +924,16 @@ scoreboard_init_high_rxt (sack_scoreboard_t * sb, u32 seq) * Returns 1 if scoreboard is empty or if first hole beyond * snd_una. */ -u8 +static u8 tcp_scoreboard_is_sane_post_recovery (tcp_connection_t * tc) { sack_scoreboard_hole_t *hole; hole = scoreboard_first_hole (&tc->sack_sb); - return (!hole || seq_geq (hole->start, tc->snd_una)); + return (!hole || (seq_geq (hole->start, tc->snd_una) + && seq_lt (hole->end, tc->snd_nxt))); } +#ifndef CLIB_MARCH_VARIANT void tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) { @@ -742,21 +944,24 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) int i, j; sb->last_sacked_bytes = 0; - sb->snd_una_adv = 0; - old_sacked_bytes = sb->sacked_bytes; sb->last_bytes_delivered = 0; + sb->snd_una_adv = 0; if (!tcp_opts_sack (&tc->rcv_opts) && sb->head == TCP_INVALID_SACK_HOLE_INDEX) return; + old_sacked_bytes = sb->sacked_bytes; + /* Remove invalid blocks */ blk = tc->rcv_opts.sacks; while (blk < vec_end (tc->rcv_opts.sacks)) { if (seq_lt (blk->start, blk->end) && seq_gt (blk->start, tc->snd_una) - && seq_gt (blk->start, ack) && seq_leq (blk->end, tc->snd_una_max)) + && seq_gt (blk->start, ack) + && seq_lt (blk->start, tc->snd_nxt) + && seq_leq (blk->end, tc->snd_nxt)) { blk++; continue; @@ -791,7 +996,7 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) { /* If no holes, insert the first that covers all outstanding bytes */ last_hole = scoreboard_insert_hole (sb, TCP_INVALID_SACK_HOLE_INDEX, - tc->snd_una, tc->snd_una_max); + tc->snd_una, tc->snd_nxt); sb->tail = scoreboard_hole_index (sb, last_hole); tmp = tc->rcv_opts.sacks[vec_len (tc->rcv_opts.sacks) - 1]; sb->high_sacked = tmp.end; @@ -802,17 +1007,17 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) * last hole end */ tmp = tc->rcv_opts.sacks[vec_len (tc->rcv_opts.sacks) - 1]; last_hole = scoreboard_last_hole (sb); - if (seq_gt (tc->snd_una_max, last_hole->end)) + if (seq_gt (tc->snd_nxt, last_hole->end)) { if (seq_geq (last_hole->start, sb->high_sacked)) { - last_hole->end = tc->snd_una_max; + last_hole->end = tc->snd_nxt; } /* New hole after high sacked block */ - else if (seq_lt (sb->high_sacked, tc->snd_una_max)) + else if (seq_lt (sb->high_sacked, tc->snd_nxt)) { scoreboard_insert_hole (sb, sb->tail, sb->high_sacked, - tc->snd_una_max); + tc->snd_nxt); } } /* Keep track of max byte sacked for when the last hole @@ -887,18 +1092,26 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) } } + if (pool_elts (sb->holes) == 1) + { + hole = scoreboard_first_hole (sb); + if (hole->start == ack + sb->snd_una_adv && hole->end == tc->snd_nxt) + scoreboard_remove_hole (sb, hole); + } + scoreboard_update_bytes (tc, sb); sb->last_sacked_bytes = sb->sacked_bytes - (old_sacked_bytes - sb->last_bytes_delivered); ASSERT (sb->last_sacked_bytes <= sb->sacked_bytes || tcp_in_recovery (tc)); - ASSERT (sb->sacked_bytes == 0 - || sb->sacked_bytes < tc->snd_una_max - seq_max (tc->snd_una, ack)); - ASSERT (sb->last_sacked_bytes + sb->lost_bytes <= tc->snd_una_max - - seq_max (tc->snd_una, ack)); + ASSERT (sb->sacked_bytes == 0 || tcp_in_recovery (tc) + || sb->sacked_bytes < tc->snd_nxt - seq_max (tc->snd_una, ack)); + ASSERT (sb->last_sacked_bytes + sb->lost_bytes <= tc->snd_nxt + - seq_max (tc->snd_una, ack) || tcp_in_recovery (tc)); ASSERT (sb->head == TCP_INVALID_SACK_HOLE_INDEX || tcp_in_recovery (tc) || sb->holes[sb->head].start == ack + sb->snd_una_adv); TCP_EVT_DBG (TCP_EVT_CC_SCOREBOARD, tc); } +#endif /* CLIB_MARCH_VARIANT */ /** * Try to update snd_wnd based on feedback received from peer. @@ -919,7 +1132,7 @@ tcp_update_snd_wnd (tcp_connection_t * tc, u32 seq, u32 ack, u32 snd_wnd) tc->snd_wl2 = ack; TCP_EVT_DBG (TCP_EVT_SND_WND, tc); - if (tc->snd_wnd < tc->snd_mss) + if (PREDICT_FALSE (tc->snd_wnd < tc->snd_mss)) { /* Set persist timer if not set and we just got 0 wnd */ if (!tcp_timer_is_active (tc, TCP_TIMER_PERSIST) @@ -929,7 +1142,7 @@ tcp_update_snd_wnd (tcp_connection_t * tc, u32 seq, u32 ack, u32 snd_wnd) else { tcp_persist_timer_reset (tc); - if (!tcp_in_recovery (tc) && tc->rto_boff > 0) + if (PREDICT_FALSE (!tcp_in_recovery (tc) && tc->rto_boff > 0)) { tc->rto_boff = 0; tcp_update_rto (tc); @@ -938,6 +1151,7 @@ tcp_update_snd_wnd (tcp_connection_t * tc, u32 seq, u32 ack, u32 snd_wnd) } } +#ifndef CLIB_MARCH_VARIANT /** * Init loss recovery/fast recovery. * @@ -948,11 +1162,15 @@ void tcp_cc_init_congestion (tcp_connection_t * tc) { tcp_fastrecovery_on (tc); - tc->snd_congestion = tc->snd_una_max; + tc->snd_congestion = tc->snd_nxt; tc->cwnd_acc_bytes = 0; + tc->snd_rxt_bytes = 0; + tc->prev_ssthresh = tc->ssthresh; + tc->prev_cwnd = tc->cwnd; tc->cc_algo->congestion (tc); TCP_EVT_DBG (TCP_EVT_CC_EVT, tc, 4); } +#endif /* CLIB_MARCH_VARIANT */ static void tcp_cc_recovery_exit (tcp_connection_t * tc) @@ -960,39 +1178,48 @@ tcp_cc_recovery_exit (tcp_connection_t * tc) tc->rto_boff = 0; tcp_update_rto (tc); tc->snd_rxt_ts = 0; - tc->snd_nxt = tc->snd_una_max; + tc->rtt_ts = 0; tcp_recovery_off (tc); TCP_EVT_DBG (TCP_EVT_CC_EVT, tc, 3); } +#ifndef CLIB_MARCH_VARIANT void -tcp_cc_fastrecovery_exit (tcp_connection_t * tc) +tcp_cc_fastrecovery_clear (tcp_connection_t * tc) { - tc->cc_algo->recovered (tc); tc->snd_rxt_bytes = 0; tc->rcv_dupacks = 0; - tc->snd_nxt = tc->snd_una_max; + tc->rtt_ts = 0; + tcp_fastrecovery_off (tc); - tcp_fastrecovery_1_smss_off (tc); + tcp_fastrecovery_first_off (tc); + TCP_EVT_DBG (TCP_EVT_CC_EVT, tc, 3); } +#endif /* CLIB_MARCH_VARIANT */ static void tcp_cc_congestion_undo (tcp_connection_t * tc) { tc->cwnd = tc->prev_cwnd; tc->ssthresh = tc->prev_ssthresh; - tc->snd_nxt = tc->snd_una_max; tc->rcv_dupacks = 0; if (tcp_in_recovery (tc)) - tcp_cc_recovery_exit (tc); + { + tcp_cc_recovery_exit (tc); + tc->snd_nxt = seq_max (tc->snd_nxt, tc->snd_congestion); + } + else if (tcp_in_fastrecovery (tc)) + { + tcp_cc_fastrecovery_clear (tc); + } + tcp_cc_undo_recovery (tc); ASSERT (tc->rto_boff == 0); TCP_EVT_DBG (TCP_EVT_CC_EVT, tc, 5); - /* TODO extend for fastrecovery */ } -static u8 -tcp_cc_is_spurious_retransmit (tcp_connection_t * tc) +static inline u8 +tcp_cc_is_spurious_timeout_rxt (tcp_connection_t * tc) { return (tcp_in_recovery (tc) && tc->rto_boff == 1 && tc->snd_rxt_ts @@ -1000,7 +1227,21 @@ tcp_cc_is_spurious_retransmit (tcp_connection_t * tc) && timestamp_lt (tc->rcv_opts.tsecr, tc->snd_rxt_ts)); } -int +static inline u8 +tcp_cc_is_spurious_fast_rxt (tcp_connection_t * tc) +{ + return (tcp_in_fastrecovery (tc) + && tc->cwnd > tc->ssthresh + 3 * tc->snd_mss); +} + +static u8 +tcp_cc_is_spurious_retransmit (tcp_connection_t * tc) +{ + return (tcp_cc_is_spurious_timeout_rxt (tc) + || tcp_cc_is_spurious_fast_rxt (tc)); +} + +static int tcp_cc_recover (tcp_connection_t * tc) { ASSERT (tcp_in_cong_recovery (tc)); @@ -1013,7 +1254,10 @@ tcp_cc_recover (tcp_connection_t * tc) if (tcp_in_recovery (tc)) tcp_cc_recovery_exit (tc); else if (tcp_in_fastrecovery (tc)) - tcp_cc_fastrecovery_exit (tc); + { + tcp_cc_recovered (tc); + tcp_cc_fastrecovery_clear (tc); + } ASSERT (tc->rto_boff == 0); ASSERT (!tcp_in_cong_recovery (tc)); @@ -1022,13 +1266,12 @@ tcp_cc_recover (tcp_connection_t * tc) } static void -tcp_cc_update (tcp_connection_t * tc, vlib_buffer_t * b) +tcp_cc_update (tcp_connection_t * tc, tcp_rate_sample_t * rs) { ASSERT (!tcp_in_cong_recovery (tc) || tcp_is_lost_fin (tc)); /* Congestion avoidance */ - tc->cc_algo->rcv_ack (tc); - tc->tsecr_last_ack = tc->rcv_opts.tsecr; + tcp_cc_rcv_ack (tc, rs); /* If a cumulative ack, make sure dupacks is 0 */ tc->rcv_dupacks = 0; @@ -1056,11 +1299,87 @@ tcp_should_fastrecover (tcp_connection_t * tc) || tcp_should_fastrecover_sack (tc)); } +#ifndef CLIB_MARCH_VARIANT +void +tcp_program_fastretransmit (tcp_worker_ctx_t * wrk, tcp_connection_t * tc) +{ + if (!(tc->flags & TCP_CONN_FRXT_PENDING)) + { + vec_add1 (wrk->pending_fast_rxt, tc->c_c_index); + tc->flags |= TCP_CONN_FRXT_PENDING; + } +} + +void +tcp_do_fastretransmits (tcp_worker_ctx_t * wrk) +{ + u32 *ongoing_fast_rxt, burst_bytes, sent_bytes, thread_index; + u32 max_burst_size, burst_size, n_segs = 0, n_segs_now; + tcp_connection_t *tc; + u64 last_cpu_time; + int i; + + if (vec_len (wrk->pending_fast_rxt) == 0 + && vec_len (wrk->postponed_fast_rxt) == 0) + return; + + thread_index = wrk->vm->thread_index; + last_cpu_time = wrk->vm->clib_time.last_cpu_time; + ongoing_fast_rxt = wrk->ongoing_fast_rxt; + vec_append (ongoing_fast_rxt, wrk->postponed_fast_rxt); + vec_append (ongoing_fast_rxt, wrk->pending_fast_rxt); + + _vec_len (wrk->postponed_fast_rxt) = 0; + _vec_len (wrk->pending_fast_rxt) = 0; + + max_burst_size = VLIB_FRAME_SIZE / vec_len (ongoing_fast_rxt); + max_burst_size = clib_max (max_burst_size, 1); + + for (i = 0; i < vec_len (ongoing_fast_rxt); i++) + { + tc = tcp_connection_get (ongoing_fast_rxt[i], thread_index); + if (!tc) + continue; + if (!tcp_in_fastrecovery (tc)) + { + tc->flags &= ~TCP_CONN_FRXT_PENDING; + continue; + } + + if (n_segs >= VLIB_FRAME_SIZE) + { + vec_add1 (wrk->postponed_fast_rxt, ongoing_fast_rxt[i]); + continue; + } + + tc->flags &= ~TCP_CONN_FRXT_PENDING; + burst_size = clib_min (max_burst_size, VLIB_FRAME_SIZE - n_segs); + burst_bytes = transport_connection_tx_pacer_burst (&tc->connection, + last_cpu_time); + burst_size = clib_min (burst_size, burst_bytes / tc->snd_mss); + if (!burst_size) + { + tcp_program_fastretransmit (wrk, tc); + continue; + } + + n_segs_now = tcp_fast_retransmit (wrk, tc, burst_size); + sent_bytes = clib_min (n_segs_now * tc->snd_mss, burst_bytes); + transport_connection_tx_pacer_update_bytes (&tc->connection, + sent_bytes); + n_segs += n_segs_now; + } + _vec_len (ongoing_fast_rxt) = 0; + wrk->ongoing_fast_rxt = ongoing_fast_rxt; +} +#endif /* CLIB_MARCH_VARIANT */ + /** * One function to rule them all ... and in the darkness bind them */ static void -tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) +tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, + u32 is_dack) { u32 rxt_delivered; @@ -1068,7 +1387,7 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) { if (tc->bytes_acked) goto partial_ack; - tcp_fast_retransmit (tc); + tcp_program_fastretransmit (tcp_get_worker (tc->c_thread_index), tc); return; } /* @@ -1078,8 +1397,7 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) else if (is_dack && !tcp_in_recovery (tc)) { TCP_EVT_DBG (TCP_EVT_DUPACK_RCVD, tc, 1); - ASSERT (tc->snd_una != tc->snd_una_max - || tc->sack_sb.last_sacked_bytes); + ASSERT (tc->snd_una != tc->snd_nxt || tc->sack_sb.last_sacked_bytes); tc->rcv_dupacks++; @@ -1087,66 +1405,79 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) if (tc->rcv_dupacks > TCP_DUPACK_THRESHOLD && !tc->bytes_acked) { ASSERT (tcp_in_fastrecovery (tc)); - tc->cc_algo->rcv_cong_ack (tc, TCP_CC_DUPACK); + tcp_cc_rcv_cong_ack (tc, TCP_CC_DUPACK, rs); return; } else if (tcp_should_fastrecover (tc)) { + u32 pacer_wnd; + ASSERT (!tcp_in_fastrecovery (tc)); - /* If of of the two conditions lower hold, reset dupacks because - * we're probably after timeout (RFC6582 heuristics). - * If Cumulative ack does not cover more than congestion threshold, - * and: - * 1) The following doesn't hold: The congestion window is greater - * than SMSS bytes and the difference between highest_ack - * and prev_highest_ack is at most 4*SMSS bytes - * 2) Echoed timestamp in the last non-dup ack does not equal the - * stored timestamp - */ - if (seq_leq (tc->snd_una, tc->snd_congestion) - && ((!(tc->cwnd > tc->snd_mss - && tc->bytes_acked <= 4 * tc->snd_mss)) - || (tc->rcv_opts.tsecr != tc->tsecr_last_ack))) + /* Heuristic to catch potential late dupacks + * after fast retransmit exits */ + if (is_dack && tc->snd_una == tc->snd_congestion + && timestamp_leq (tc->rcv_opts.tsecr, tc->tsecr_last_ack)) { tc->rcv_dupacks = 0; return; } tcp_cc_init_congestion (tc); - tc->cc_algo->rcv_cong_ack (tc, TCP_CC_DUPACK); - - /* The first segment MUST be retransmitted */ - tcp_retransmit_first_unacked (tc); + tcp_cc_rcv_cong_ack (tc, TCP_CC_DUPACK, rs); - /* Post retransmit update cwnd to ssthresh and account for the - * three segments that have left the network and should've been - * buffered at the receiver XXX */ - tc->cwnd = tc->ssthresh + tc->rcv_dupacks * tc->snd_mss; - ASSERT (tc->cwnd >= tc->snd_mss); - - /* If cwnd allows, send more data */ if (tcp_opts_sack_permitted (&tc->rcv_opts)) { - scoreboard_init_high_rxt (&tc->sack_sb, - tc->snd_una + tc->snd_mss); - tcp_fast_retransmit_sack (tc); + tc->cwnd = tc->ssthresh; + scoreboard_init_high_rxt (&tc->sack_sb, tc->snd_una); } else { - tcp_fast_retransmit_no_sack (tc); + /* Post retransmit update cwnd to ssthresh and account for the + * three segments that have left the network and should've been + * buffered at the receiver XXX */ + tc->cwnd = tc->ssthresh + 3 * tc->snd_mss; } + + /* Constrain rate until we get a partial ack */ + pacer_wnd = clib_max (0.1 * tc->cwnd, 2 * tc->snd_mss); + tcp_connection_tx_pacer_reset (tc, pacer_wnd, + 0 /* start bucket */ ); + tcp_program_fastretransmit (tcp_get_worker (tc->c_thread_index), + tc); return; } else if (!tc->bytes_acked || (tc->bytes_acked && !tcp_in_cong_recovery (tc))) { - tc->cc_algo->rcv_cong_ack (tc, TCP_CC_DUPACK); + tcp_cc_rcv_cong_ack (tc, TCP_CC_DUPACK, rs); return; } else goto partial_ack; } + /* Don't allow entry in fast recovery if still in recovery, for now */ + else if (0 && is_dack && tcp_in_recovery (tc)) + { + /* If of of the two conditions lower hold, reset dupacks because + * we're probably after timeout (RFC6582 heuristics). + * If Cumulative ack does not cover more than congestion threshold, + * and: + * 1) The following doesn't hold: The congestion window is greater + * than SMSS bytes and the difference between highest_ack + * and prev_highest_ack is at most 4*SMSS bytes + * 2) Echoed timestamp in the last non-dup ack does not equal the + * stored timestamp + */ + if (seq_leq (tc->snd_una, tc->snd_congestion) + && ((!(tc->cwnd > tc->snd_mss + && tc->bytes_acked <= 4 * tc->snd_mss)) + || (tc->rcv_opts.tsecr != tc->tsecr_last_ack))) + { + tc->rcv_dupacks = 0; + return; + } + } if (!tc->bytes_acked) return; @@ -1157,11 +1488,15 @@ partial_ack: /* * Legitimate ACK. 1) See if we can exit recovery */ - /* XXX limit this only to first partial ack? */ - tcp_retransmit_timer_update (tc); + + /* Update the pacing rate. For the first partial ack we move from + * the artificially constrained rate to the one after congestion */ + tcp_connection_tx_pacer_update (tc); if (seq_geq (tc->snd_una, tc->snd_congestion)) { + tcp_retransmit_timer_update (tc); + /* If spurious return, we've already updated everything */ if (tcp_cc_recover (tc)) { @@ -1169,11 +1504,8 @@ partial_ack: return; } - tc->snd_nxt = tc->snd_una_max; - /* Treat as congestion avoidance ack */ - tc->cc_algo->rcv_ack (tc); - tc->tsecr_last_ack = tc->rcv_opts.tsecr; + tcp_cc_rcv_ack (tc, rs); return; } @@ -1181,6 +1513,9 @@ partial_ack: * Legitimate ACK. 2) If PARTIAL ACK try to retransmit */ + /* XXX limit this only to first partial ack? */ + tcp_retransmit_timer_update (tc); + /* RFC6675: If the incoming ACK is a cumulative acknowledgment, * reset dupacks to 0. Also needed if in congestion recovery */ tc->rcv_dupacks = 0; @@ -1188,55 +1523,60 @@ partial_ack: /* Post RTO timeout don't try anything fancy */ if (tcp_in_recovery (tc)) { - tc->cc_algo->rcv_ack (tc); - tc->tsecr_last_ack = tc->rcv_opts.tsecr; + tcp_cc_rcv_ack (tc, rs); + transport_add_tx_event (&tc->connection); return; } /* Remove retransmitted bytes that have been delivered */ - ASSERT (tc->bytes_acked + tc->sack_sb.snd_una_adv - >= tc->sack_sb.last_bytes_delivered - || (tc->flags & TCP_CONN_FINSNT)); - - if (seq_lt (tc->snd_una, tc->sack_sb.high_rxt)) + if (tcp_opts_sack_permitted (&tc->rcv_opts)) { + ASSERT (tc->bytes_acked + tc->sack_sb.snd_una_adv + >= tc->sack_sb.last_bytes_delivered + || (tc->flags & TCP_CONN_FINSNT)); + /* If we have sacks and we haven't gotten an ack beyond high_rxt, * remove sacked bytes delivered */ - rxt_delivered = tc->bytes_acked + tc->sack_sb.snd_una_adv - - tc->sack_sb.last_bytes_delivered; - ASSERT (tc->snd_rxt_bytes >= rxt_delivered); - tc->snd_rxt_bytes -= rxt_delivered; + if (seq_lt (tc->snd_una, tc->sack_sb.high_rxt)) + { + rxt_delivered = tc->bytes_acked + tc->sack_sb.snd_una_adv + - tc->sack_sb.last_bytes_delivered; + ASSERT (tc->snd_rxt_bytes >= rxt_delivered); + tc->snd_rxt_bytes -= rxt_delivered; + } + else + { + /* Apparently all retransmitted holes have been acked */ + tc->snd_rxt_bytes = 0; + tc->sack_sb.high_rxt = tc->snd_una; + } } else { - /* Either all retransmitted holes have been acked, or we're - * "in the blind" and retransmitting segment by segment */ - tc->snd_rxt_bytes = 0; + tcp_fastrecovery_first_on (tc); + if (tc->snd_rxt_bytes > tc->bytes_acked) + tc->snd_rxt_bytes -= tc->bytes_acked; + else + tc->snd_rxt_bytes = 0; } - tc->cc_algo->rcv_cong_ack (tc, TCP_CC_PARTIALACK); + tcp_cc_rcv_cong_ack (tc, TCP_CC_PARTIALACK, rs); /* * Since this was a partial ack, try to retransmit some more data */ - tcp_fast_retransmit (tc); -} - -void -tcp_cc_init (tcp_connection_t * tc) -{ - tc->cc_algo = tcp_cc_algo_get (TCP_CC_NEWRENO); - tc->cc_algo->init (tc); + tcp_program_fastretransmit (tcp_get_worker (tc->c_thread_index), tc); } /** * Process incoming ACK */ static int -tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b, - tcp_header_t * th, u32 * next, u32 * error) +tcp_rcv_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b, + tcp_header_t * th, u32 * error) { u32 prev_snd_wnd, prev_snd_una; + tcp_rate_sample_t rs = { 0 }; u8 is_dack; TCP_EVT_DBG (TCP_EVT_CC_STAT, tc); @@ -1244,34 +1584,19 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b, /* If the ACK acks something not yet sent (SEG.ACK > SND.NXT) */ if (PREDICT_FALSE (seq_gt (vnet_buffer (b)->tcp.ack_number, tc->snd_nxt))) { - /* When we entered recovery, we reset snd_nxt to snd_una. Seems peer - * still has the data so accept the ack */ - if (tcp_in_recovery (tc) - && seq_leq (vnet_buffer (b)->tcp.ack_number, tc->snd_congestion) - && seq_geq (vnet_buffer (b)->tcp.ack_number, tc->snd_una)) + /* We've probably entered recovery and the peer still has some + * of the data we've sent. Update snd_nxt and accept the ack */ + if (seq_leq (vnet_buffer (b)->tcp.ack_number, tc->snd_una_max) + && seq_gt (vnet_buffer (b)->tcp.ack_number, tc->snd_una)) { - tc->snd_una_max = tc->snd_nxt = vnet_buffer (b)->tcp.ack_number; + tc->snd_nxt = vnet_buffer (b)->tcp.ack_number; goto process_ack; } - /* If we have outstanding data and this is within the window, accept it, - * probably retransmit has timed out. Otherwise ACK segment and then - * drop it */ - if (seq_gt (vnet_buffer (b)->tcp.ack_number, tc->snd_una_max)) - { - tcp_make_ack (tc, b); - *next = tcp_next_output (tc->c_is_ip4); - *error = TCP_ERROR_ACK_INVALID; - TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 0, - vnet_buffer (b)->tcp.ack_number); - return -1; - } - - TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 2, - vnet_buffer (b)->tcp.ack_number); - - tc->snd_nxt = vnet_buffer (b)->tcp.ack_number; *error = TCP_ERROR_ACK_FUTURE; + TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 0, + vnet_buffer (b)->tcp.ack_number); + return -1; } /* If old ACK, probably it's an old dupack */ @@ -1281,15 +1606,16 @@ tcp_rcv_ack (tcp_connection_t * tc, vlib_buffer_t * b, TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 1, vnet_buffer (b)->tcp.ack_number); if (tcp_in_fastrecovery (tc) && tc->rcv_dupacks == TCP_DUPACK_THRESHOLD) - tcp_cc_handle_event (tc, 1); + tcp_cc_handle_event (tc, 0, 1); /* Don't drop yet */ return 0; } +process_ack: + /* * Looks okay, process feedback */ -process_ack: if (tcp_opts_sack_permitted (&tc->rcv_opts)) tcp_rcv_sacks (tc, vnet_buffer (b)->tcp.ack_number); @@ -1303,7 +1629,13 @@ process_ack: tcp_validate_txf_size (tc, tc->bytes_acked); if (tc->bytes_acked) - tcp_dequeue_acked (tc, vnet_buffer (b)->tcp.ack_number); + { + tcp_program_dequeue (wrk, tc); + tcp_update_rtt (tc, vnet_buffer (b)->tcp.ack_number); + } + + if (tc->flags & TCP_CONN_RATE_SAMPLE) + tcp_bt_sample_delivery_rate (tc, &rs); TCP_EVT_DBG (TCP_EVT_ACK_RCVD, tc); @@ -1313,21 +1645,78 @@ process_ack: if (tcp_ack_is_cc_event (tc, b, prev_snd_wnd, prev_snd_una, &is_dack)) { - tcp_cc_handle_event (tc, is_dack); + tcp_cc_handle_event (tc, &rs, is_dack); if (!tcp_in_cong_recovery (tc)) - return 0; - *error = TCP_ERROR_ACK_DUP; - return vnet_buffer (b)->tcp.data_len ? 0 : -1; - } + { + *error = TCP_ERROR_ACK_OK; + return 0; + } + *error = TCP_ERROR_ACK_DUP; + if (vnet_buffer (b)->tcp.data_len || tcp_is_fin (th)) + return 0; + return -1; + } /* * Update congestion control (slow start/congestion avoidance) */ - tcp_cc_update (tc, b); + tcp_cc_update (tc, &rs); *error = TCP_ERROR_ACK_OK; return 0; } +static void +tcp_program_disconnect (tcp_worker_ctx_t * wrk, tcp_connection_t * tc) +{ + if (!tcp_disconnect_pending (tc)) + { + vec_add1 (wrk->pending_disconnects, tc->c_c_index); + tcp_disconnect_pending_on (tc); + } +} + +static void +tcp_handle_disconnects (tcp_worker_ctx_t * wrk) +{ + u32 thread_index, *pending_disconnects; + tcp_connection_t *tc; + int i; + + if (!vec_len (wrk->pending_disconnects)) + return; + + thread_index = wrk->vm->thread_index; + pending_disconnects = wrk->pending_disconnects; + for (i = 0; i < vec_len (pending_disconnects); i++) + { + tc = tcp_connection_get (pending_disconnects[i], thread_index); + tcp_disconnect_pending_off (tc); + session_transport_closing_notify (&tc->connection); + } + _vec_len (wrk->pending_disconnects) = 0; +} + +static void +tcp_rcv_fin (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b, + u32 * error) +{ + /* Reject out-of-order fins */ + if (vnet_buffer (b)->tcp.seq_end != tc->rcv_nxt) + return; + + /* Account for the FIN and send ack */ + tc->rcv_nxt += 1; + tcp_program_ack (wrk, tc); + /* Enter CLOSE-WAIT and notify session. To avoid lingering + * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */ + tcp_connection_set_state (tc, TCP_STATE_CLOSE_WAIT); + tcp_program_disconnect (wrk, tc); + tcp_timer_update (tc, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME); + TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc); + *error = TCP_ERROR_FIN_RCVD; +} + +#ifndef CLIB_MARCH_VARIANT static u8 tcp_sack_vector_is_sane (sack_block_t * sacks) { @@ -1354,7 +1743,7 @@ tcp_sack_vector_is_sane (sack_block_t * sacks) void tcp_update_sack_list (tcp_connection_t * tc, u32 start, u32 end) { - sack_block_t *new_list = 0, *block = 0; + sack_block_t *new_list = tc->snd_sacks_fl, *block = 0; int i; /* If the first segment is ooo add it to the list. Last write might've moved @@ -1386,19 +1775,14 @@ tcp_update_sack_list (tcp_connection_t * tc, u32 start, u32 end) /* Save to new SACK list if we have space. */ if (vec_len (new_list) < TCP_MAX_SACK_BLOCKS) - { - vec_add1 (new_list, tc->snd_sacks[i]); - } - else - { - clib_warning ("sack discarded"); - } + vec_add1 (new_list, tc->snd_sacks[i]); } ASSERT (vec_len (new_list) <= TCP_MAX_SACK_BLOCKS); /* Replace old vector with new one */ - vec_free (tc->snd_sacks); + vec_reset_length (tc->snd_sacks); + tc->snd_sacks_fl = tc->snd_sacks; tc->snd_sacks = new_list; /* Segments should not 'touch' */ @@ -1413,9 +1797,10 @@ tcp_sack_list_bytes (tcp_connection_t * tc) bytes += tc->snd_sacks[i].end - tc->snd_sacks[i].start; return bytes; } +#endif /* CLIB_MARCH_VARIANT */ /** Enqueue data for delivery to application */ -always_inline int +static int tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b, u16 data_len) { @@ -1437,25 +1822,16 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b, else if (written > data_len) { tc->rcv_nxt += written; - - /* Send ACK confirming the update */ - tc->flags |= TCP_CONN_SNDACK; TCP_EVT_DBG (TCP_EVT_CC_INPUT, tc, data_len, written); } else if (written > 0) { /* We've written something but FIFO is probably full now */ tc->rcv_nxt += written; - - /* Depending on how fast the app is, all remaining buffers in burst will - * not be enqueued. Inform peer */ - tc->flags |= TCP_CONN_SNDACK; - error = TCP_ERROR_PARTIALLY_ENQUEUED; } else { - tc->flags |= TCP_CONN_SNDACK; return TCP_ERROR_FIFO_FULL; } @@ -1470,11 +1846,11 @@ tcp_session_enqueue_data (tcp_connection_t * tc, vlib_buffer_t * b, } /** Enqueue out-of-order data */ -always_inline int +static int tcp_session_enqueue_ooo (tcp_connection_t * tc, vlib_buffer_t * b, u16 data_len) { - stream_session_t *s0; + session_t *s0; int rv, offset; ASSERT (seq_gt (vnet_buffer (b)->tcp.seq_number, tc->rcv_nxt)); @@ -1504,15 +1880,15 @@ tcp_session_enqueue_ooo (tcp_connection_t * tc, vlib_buffer_t * b, s0 = session_get (tc->c_s_index, tc->c_thread_index); /* Get the newest segment from the fifo */ - newest = svm_fifo_newest_ooo_segment (s0->server_rx_fifo); + newest = svm_fifo_newest_ooo_segment (s0->rx_fifo); if (newest) { - offset = ooo_segment_offset (s0->server_rx_fifo, newest); + offset = ooo_segment_offset_prod (s0->rx_fifo, newest); ASSERT (offset <= vnet_buffer (b)->tcp.seq_number - tc->rcv_nxt); start = tc->rcv_nxt + offset; - end = start + ooo_segment_length (s0->server_rx_fifo, newest); + end = start + ooo_segment_length (s0->rx_fifo, newest); tcp_update_sack_list (tc, start, end); - svm_fifo_newest_ooo_segment_reset (s0->server_rx_fifo); + svm_fifo_newest_ooo_segment_reset (s0->rx_fifo); TCP_EVT_DBG (TCP_EVT_CC_SACKS, tc); } } @@ -1529,8 +1905,8 @@ tcp_can_delack (tcp_connection_t * tc) { /* Send ack if ... */ if (TCP_ALWAYS_ACK - /* just sent a rcv wnd 0 */ - || (tc->flags & TCP_CONN_SENT_RCV_WND0) != 0 + /* just sent a rcv wnd 0 + || (tc->flags & TCP_CONN_SENT_RCV_WND0) != 0 */ /* constrained to send ack */ || (tc->flags & TCP_CONN_SNDACK) != 0 /* we're almost out of tx wnd */ @@ -1574,7 +1950,8 @@ tcp_buffer_discard_bytes (vlib_buffer_t * b, u32 n_bytes_to_drop) * It handles both in order or out-of-order data. */ static int -tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0) +tcp_segment_rcv (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, + vlib_buffer_t * b) { u32 error, n_bytes_to_drop, n_data_bytes; @@ -1593,9 +1970,8 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0) * retransmissions since we may not have any data to send */ if (seq_leq (vnet_buffer (b)->tcp.seq_end, tc->rcv_nxt)) { - tcp_make_ack (tc, b); + tcp_program_ack (wrk, tc); error = TCP_ERROR_SEGMENT_OLD; - *next0 = tcp_next_output (tc->c_is_ip4); goto done; } @@ -1607,7 +1983,6 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0) if (tcp_buffer_discard_bytes (b, n_bytes_to_drop)) { error = TCP_ERROR_SEGMENT_OLD; - *next0 = tcp_next_drop (tc->c_is_ip4); goto done; } goto in_order; @@ -1615,9 +1990,7 @@ tcp_segment_rcv (tcp_connection_t * tc, vlib_buffer_t * b, u32 * next0) /* RFC2581: Enqueue and send DUPACK for fast retransmit */ error = tcp_session_enqueue_ooo (tc, b, n_data_bytes); - *next0 = tcp_next_output (tc->c_is_ip4); - tcp_make_ack (tc, b); - vnet_buffer (b)->tcp.flags = TCP_BUF_FLAG_DUPACK; + tcp_program_dupack (wrk, tc); TCP_EVT_DBG (TCP_EVT_DUPACK_SENT, tc, vnet_buffer (b)->tcp); goto done; } @@ -1629,14 +2002,12 @@ in_order: error = tcp_session_enqueue_data (tc, b, n_data_bytes); if (tcp_can_delack (tc)) { - *next0 = tcp_next_drop (tc->c_is_ip4); if (!tcp_timer_is_active (tc, TCP_TIMER_DELACK)) tcp_timer_set (tc, TCP_TIMER_DELACK, TCP_DELACK_TIME); goto done; } - *next0 = tcp_next_output (tc->c_is_ip4); - tcp_make_ack (tc, b); + tcp_program_ack (wrk, tc); done: return error; @@ -1648,7 +2019,7 @@ typedef struct tcp_connection_t tcp_connection; } tcp_rx_trace_t; -u8 * +static u8 * format_tcp_rx_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); @@ -1664,7 +2035,7 @@ format_tcp_rx_trace (u8 * s, va_list * args) return s; } -u8 * +static u8 * format_tcp_rx_trace_short (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); @@ -1685,13 +2056,14 @@ tcp_set_rx_trace_data (tcp_rx_trace_t * t0, tcp_connection_t * tc0, { if (tc0) { - clib_memcpy (&t0->tcp_connection, tc0, sizeof (t0->tcp_connection)); + clib_memcpy_fast (&t0->tcp_connection, tc0, + sizeof (t0->tcp_connection)); } else { th0 = tcp_buffer_hdr (b0); } - clib_memcpy (&t0->tcp_header, th0, sizeof (t0->tcp_header)); + clib_memcpy_fast (&t0->tcp_header, th0, sizeof (t0->tcp_header)); } static void @@ -1771,136 +2143,95 @@ tcp46_established_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int is_ip4) { u32 thread_index = vm->thread_index, errors = 0; - u32 n_left_from, next_index, *from, *to_next; + tcp_worker_ctx_t *wrk = tcp_get_worker (thread_index); + u32 n_left_from, *from, *first_buffer; u16 err_counters[TCP_N_ERROR] = { 0 }; - u8 is_fin = 0; if (node->flags & VLIB_NODE_FLAG_TRACE) tcp_established_trace_frame (vm, node, frame, is_ip4); - from = vlib_frame_vector_args (frame); + first_buffer = from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; - next_index = node->cached_next_index; while (n_left_from > 0) { - u32 n_left_to_next; + u32 bi0, error0 = TCP_ERROR_ACK_OK; + vlib_buffer_t *b0; + tcp_header_t *th0; + tcp_connection_t *tc0; - vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); - while (n_left_from > 0 && n_left_to_next > 0) + if (n_left_from > 1) { - u32 bi0; - vlib_buffer_t *b0; - tcp_header_t *th0 = 0; - tcp_connection_t *tc0; - u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_ACK_OK; - - if (n_left_from > 1) - { - vlib_buffer_t *pb; - pb = vlib_get_buffer (vm, from[1]); - vlib_prefetch_buffer_header (pb, LOAD); - CLIB_PREFETCH (pb->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); - } - - 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); - tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index, - thread_index); + vlib_buffer_t *pb; + pb = vlib_get_buffer (vm, from[1]); + vlib_prefetch_buffer_header (pb, LOAD); + CLIB_PREFETCH (pb->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD); + } - if (PREDICT_FALSE (tc0 == 0)) - { - error0 = TCP_ERROR_INVALID_CONNECTION; - goto done; - } + bi0 = from[0]; + from += 1; + n_left_from -= 1; - th0 = tcp_buffer_hdr (b0); - /* N.B. buffer is rewritten if segment is ooo. Thus, th0 becomes a - * dangling reference. */ - is_fin = tcp_is_fin (th0); + b0 = vlib_get_buffer (vm, bi0); + tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index, + thread_index); - /* SYNs, FINs and data consume sequence numbers */ - vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number - + tcp_is_syn (th0) + is_fin + vnet_buffer (b0)->tcp.data_len; + if (PREDICT_FALSE (tc0 == 0)) + { + error0 = TCP_ERROR_INVALID_CONNECTION; + goto done; + } - /* TODO header prediction fast path */ + th0 = tcp_buffer_hdr (b0); - /* 1-4: check SEQ, RST, SYN */ - if (PREDICT_FALSE (tcp_segment_validate (vm, tc0, b0, th0, &next0, - &error0))) - { - tcp_maybe_inc_err_counter (err_counters, error0); - TCP_EVT_DBG (TCP_EVT_SEG_INVALID, tc0, vnet_buffer (b0)->tcp); - goto done; - } + /* TODO header prediction fast path */ - /* 5: check the ACK field */ - if (PREDICT_FALSE (tcp_rcv_ack (tc0, b0, th0, &next0, &error0))) - { - tcp_maybe_inc_err_counter (err_counters, error0); - goto done; - } + /* 1-4: check SEQ, RST, SYN */ + if (PREDICT_FALSE (tcp_segment_validate (wrk, tc0, b0, th0, &error0))) + { + TCP_EVT_DBG (TCP_EVT_SEG_INVALID, tc0, vnet_buffer (b0)->tcp); + goto done; + } - /* 6: check the URG bit TODO */ + /* 5: check the ACK field */ + if (PREDICT_FALSE (tcp_rcv_ack (wrk, tc0, b0, th0, &error0))) + goto done; - /* 7: process the segment text */ - if (vnet_buffer (b0)->tcp.data_len) - { - error0 = tcp_segment_rcv (tc0, b0, &next0); - tcp_maybe_inc_err_counter (err_counters, error0); - } + /* 6: check the URG bit TODO */ - /* 8: check the FIN bit */ - if (PREDICT_FALSE (is_fin)) - { - /* Enter CLOSE-WAIT and notify session. To avoid lingering - * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */ - /* Account for the FIN if nothing else was received */ - if (vnet_buffer (b0)->tcp.data_len == 0) - tc0->rcv_nxt += 1; - tcp_make_ack (tc0, b0); - next0 = tcp_next_output (tc0->c_is_ip4); - tc0->state = TCP_STATE_CLOSE_WAIT; - stream_session_disconnect_notify (&tc0->connection); - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME); - TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0); - tcp_inc_err_counter (err_counters, TCP_ERROR_FIN_RCVD, 1); - } + /* 7: process the segment text */ + if (vnet_buffer (b0)->tcp.data_len) + error0 = tcp_segment_rcv (wrk, tc0, b0); - done: - b0->error = node->errors[error0]; - vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, - n_left_to_next, bi0, next0); - } + /* 8: check the FIN bit */ + if (PREDICT_FALSE (tcp_is_fin (th0))) + tcp_rcv_fin (wrk, tc0, b0, &error0); - vlib_put_next_frame (vm, node, next_index, n_left_to_next); + done: + tcp_inc_err_counter (err_counters, error0, 1); } - errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP, - thread_index); - err_counters[TCP_ERROR_EVENT_FIFO_FULL] = errors; + errors = session_main_flush_enqueue_events (TRANSPORT_PROTO_TCP, + thread_index); + err_counters[TCP_ERROR_MSG_QUEUE_FULL] = errors; tcp_store_err_counters (established, err_counters); - tcp_flush_frame_to_output (vm, thread_index, is_ip4); + tcp_handle_postponed_dequeues (wrk); + tcp_handle_disconnects (wrk); + vlib_buffer_free (vm, first_buffer, frame->n_vectors); return frame->n_vectors; } -static uword -tcp4_established (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp4_established_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_established_inline (vm, node, from_frame, 1 /* is_ip4 */ ); } -static uword -tcp6_established (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp6_established_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_established_inline (vm, node, from_frame, 0 /* is_ip4 */ ); } @@ -1908,7 +2239,6 @@ tcp6_established (vlib_main_t * vm, vlib_node_runtime_t * node, /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp4_established_node) = { - .function = tcp4_established, .name = "tcp4-established", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -1925,12 +2255,9 @@ VLIB_REGISTER_NODE (tcp4_established_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp4_established_node, tcp4_established); - /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp6_established_node) = { - .function = tcp6_established, .name = "tcp6-established", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -1948,11 +2275,6 @@ VLIB_REGISTER_NODE (tcp6_established_node) = /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp6_established_node, tcp6_established); - -vlib_node_registration_t tcp4_syn_sent_node; -vlib_node_registration_t tcp6_syn_sent_node; - static u8 tcp_lookup_is_valid (tcp_connection_t * tc, tcp_header_t * hdr) { @@ -2037,275 +2359,265 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int is_ip4) { tcp_main_t *tm = vnet_get_tcp_main (); - u32 n_left_from, next_index, *from, *to_next; - u32 my_thread_index = vm->thread_index, errors = 0; + u32 n_left_from, *from, *first_buffer, errors = 0; + u32 my_thread_index = vm->thread_index; + tcp_worker_ctx_t *wrk = tcp_get_worker (my_thread_index); - from = vlib_frame_vector_args (from_frame); + from = first_buffer = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; - next_index = node->cached_next_index; - while (n_left_from > 0) { - u32 n_left_to_next; + u32 bi0, ack0, seq0, error0 = TCP_ERROR_NONE; + tcp_connection_t *tc0, *new_tc0; + tcp_header_t *tcp0 = 0; + tcp_rx_trace_t *t0; + vlib_buffer_t *b0; - vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + bi0 = from[0]; + from += 1; + n_left_from -= 1; - while (n_left_from > 0 && n_left_to_next > 0) + b0 = vlib_get_buffer (vm, bi0); + tc0 = + tcp_half_open_connection_get (vnet_buffer (b0)->tcp.connection_index); + if (PREDICT_FALSE (tc0 == 0)) { - u32 bi0, ack0, seq0; - vlib_buffer_t *b0; - tcp_rx_trace_t *t0; - tcp_header_t *tcp0 = 0; - tcp_connection_t *tc0; - tcp_connection_t *new_tc0; - u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_ENQUEUED; - - 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); - tc0 = - tcp_half_open_connection_get (vnet_buffer (b0)-> - tcp.connection_index); - if (PREDICT_FALSE (tc0 == 0)) - { - error0 = TCP_ERROR_INVALID_CONNECTION; - goto drop; - } - - /* Half-open completed recently but the connection was't removed - * yet by the owning thread */ - if (PREDICT_FALSE (tc0->flags & TCP_CONN_HALF_OPEN_DONE)) - { - /* Make sure the connection actually exists */ - ASSERT (tcp_lookup_connection (tc0->c_fib_index, b0, - my_thread_index, is_ip4)); - goto drop; - } - - ack0 = vnet_buffer (b0)->tcp.ack_number; - seq0 = vnet_buffer (b0)->tcp.seq_number; - tcp0 = tcp_buffer_hdr (b0); - - /* Crude check to see if the connection handle does not match - * the packet. Probably connection just switched to established */ - if (PREDICT_FALSE (tcp0->dst_port != tc0->c_lcl_port - || tcp0->src_port != tc0->c_rmt_port)) - goto drop; - - if (PREDICT_FALSE - (!tcp_ack (tcp0) && !tcp_rst (tcp0) && !tcp_syn (tcp0))) - goto drop; - - /* SYNs, FINs and data consume sequence numbers */ - vnet_buffer (b0)->tcp.seq_end = seq0 + tcp_is_syn (tcp0) - + tcp_is_fin (tcp0) + vnet_buffer (b0)->tcp.data_len; - - /* - * 1. check the ACK bit - */ - - /* - * If the ACK bit is set - * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send a reset (unless - * the RST bit is set, if so drop the segment and return) - * - * and discard the segment. Return. - * If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable. - */ - if (tcp_ack (tcp0)) - { - if (seq_leq (ack0, tc0->iss) || seq_gt (ack0, tc0->snd_nxt)) - { - clib_warning ("ack not in rcv wnd"); - if (!tcp_rst (tcp0)) - tcp_send_reset_w_pkt (tc0, b0, is_ip4); - goto drop; - } - - /* Make sure ACK is valid */ - if (seq_gt (tc0->snd_una, ack0)) - { - clib_warning ("ack invalid"); - goto drop; - } - } + error0 = TCP_ERROR_INVALID_CONNECTION; + goto drop; + } - /* - * 2. check the RST bit - */ + /* Half-open completed recently but the connection was't removed + * yet by the owning thread */ + if (PREDICT_FALSE (tc0->flags & TCP_CONN_HALF_OPEN_DONE)) + { + /* Make sure the connection actually exists */ + ASSERT (tcp_lookup_connection (tc0->c_fib_index, b0, + my_thread_index, is_ip4)); + error0 = TCP_ERROR_SPURIOUS_SYN_ACK; + goto drop; + } - if (tcp_rst (tcp0)) - { - /* If ACK is acceptable, signal client that peer is not - * willing to accept connection and drop connection*/ - if (tcp_ack (tcp0)) - tcp_connection_reset (tc0); - goto drop; - } + ack0 = vnet_buffer (b0)->tcp.ack_number; + seq0 = vnet_buffer (b0)->tcp.seq_number; + tcp0 = tcp_buffer_hdr (b0); - /* - * 3. check the security and precedence (skipped) - */ + /* Crude check to see if the connection handle does not match + * the packet. Probably connection just switched to established */ + if (PREDICT_FALSE (tcp0->dst_port != tc0->c_lcl_port + || tcp0->src_port != tc0->c_rmt_port)) + { + error0 = TCP_ERROR_INVALID_CONNECTION; + goto drop; + } - /* - * 4. check the SYN bit - */ + if (PREDICT_FALSE (!tcp_ack (tcp0) && !tcp_rst (tcp0) + && !tcp_syn (tcp0))) + { + error0 = TCP_ERROR_SEGMENT_INVALID; + goto drop; + } - /* No SYN flag. Drop. */ - if (!tcp_syn (tcp0)) + /* SYNs consume sequence numbers */ + vnet_buffer (b0)->tcp.seq_end += tcp_is_syn (tcp0); + + /* + * 1. check the ACK bit + */ + + /* + * If the ACK bit is set + * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send a reset (unless + * the RST bit is set, if so drop the segment and return) + * + * and discard the segment. Return. + * If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable. + */ + if (tcp_ack (tcp0)) + { + if (seq_leq (ack0, tc0->iss) || seq_gt (ack0, tc0->snd_nxt)) { - clib_warning ("not synack"); + if (!tcp_rst (tcp0)) + tcp_send_reset_w_pkt (tc0, b0, my_thread_index, is_ip4); + error0 = TCP_ERROR_RCV_WND; goto drop; } - /* Parse options */ - if (tcp_options_parse (tcp0, &tc0->rcv_opts)) + /* Make sure ACK is valid */ + if (seq_gt (tc0->snd_una, ack0)) { - clib_warning ("options parse fail"); + error0 = TCP_ERROR_ACK_INVALID; goto drop; } + } - /* Valid SYN or SYN-ACK. Move connection from half-open pool to - * current thread pool. */ - pool_get (tm->connections[my_thread_index], new_tc0); - clib_memcpy (new_tc0, tc0, sizeof (*new_tc0)); - new_tc0->c_c_index = new_tc0 - tm->connections[my_thread_index]; - new_tc0->c_thread_index = my_thread_index; - new_tc0->rcv_nxt = vnet_buffer (b0)->tcp.seq_end; - new_tc0->irs = seq0; - new_tc0->timers[TCP_TIMER_ESTABLISH] = TCP_TIMER_HANDLE_INVALID; - new_tc0->timers[TCP_TIMER_RETRANSMIT_SYN] = - TCP_TIMER_HANDLE_INVALID; - new_tc0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; - - /* If this is not the owning thread, wait for syn retransmit to - * expire and cleanup then */ - if (tcp_half_open_connection_cleanup (tc0)) - tc0->flags |= TCP_CONN_HALF_OPEN_DONE; - - if (tcp_opts_tstamp (&new_tc0->rcv_opts)) - { - new_tc0->tsval_recent = new_tc0->rcv_opts.tsval; - new_tc0->tsval_recent_age = tcp_time_now (); - } + /* + * 2. check the RST bit + */ - if (tcp_opts_wscale (&new_tc0->rcv_opts)) - new_tc0->snd_wscale = new_tc0->rcv_opts.wscale; + if (tcp_rst (tcp0)) + { + /* If ACK is acceptable, signal client that peer is not + * willing to accept connection and drop connection*/ + if (tcp_ack (tcp0)) + tcp_connection_reset (tc0); + error0 = TCP_ERROR_RST_RCVD; + goto drop; + } - /* RFC1323: SYN and SYN-ACK wnd not scaled */ - new_tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window); - new_tc0->snd_wl1 = seq0; - new_tc0->snd_wl2 = ack0; + /* + * 3. check the security and precedence (skipped) + */ - tcp_connection_init_vars (new_tc0); + /* + * 4. check the SYN bit + */ - /* SYN-ACK: See if we can switch to ESTABLISHED state */ - if (PREDICT_TRUE (tcp_ack (tcp0))) - { - /* Our SYN is ACKed: we have iss < ack = snd_una */ + /* No SYN flag. Drop. */ + if (!tcp_syn (tcp0)) + { + error0 = TCP_ERROR_SEGMENT_INVALID; + goto drop; + } - /* TODO Dequeue acknowledged segments if we support Fast Open */ - new_tc0->snd_una = ack0; - new_tc0->state = TCP_STATE_ESTABLISHED; + /* Parse options */ + if (tcp_options_parse (tcp0, &tc0->rcv_opts, 1)) + { + error0 = TCP_ERROR_OPTIONS; + goto drop; + } - /* Make sure las is initialized for the wnd computation */ - new_tc0->rcv_las = new_tc0->rcv_nxt; + /* Valid SYN or SYN-ACK. Move connection from half-open pool to + * current thread pool. */ + pool_get (tm->connections[my_thread_index], new_tc0); + clib_memcpy_fast (new_tc0, tc0, sizeof (*new_tc0)); + new_tc0->c_c_index = new_tc0 - tm->connections[my_thread_index]; + new_tc0->c_thread_index = my_thread_index; + new_tc0->rcv_nxt = vnet_buffer (b0)->tcp.seq_end; + new_tc0->irs = seq0; + new_tc0->timers[TCP_TIMER_ESTABLISH_AO] = TCP_TIMER_HANDLE_INVALID; + new_tc0->timers[TCP_TIMER_RETRANSMIT_SYN] = TCP_TIMER_HANDLE_INVALID; + new_tc0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + + /* If this is not the owning thread, wait for syn retransmit to + * expire and cleanup then */ + if (tcp_half_open_connection_cleanup (tc0)) + tc0->flags |= TCP_CONN_HALF_OPEN_DONE; + + if (tcp_opts_tstamp (&new_tc0->rcv_opts)) + { + new_tc0->tsval_recent = new_tc0->rcv_opts.tsval; + new_tc0->tsval_recent_age = tcp_time_now (); + } - /* Notify app that we have connection. If session layer can't - * allocate session send reset */ - if (session_stream_connect_notify (&new_tc0->connection, 0)) - { - clib_warning ("connect notify fail"); - tcp_send_reset_w_pkt (new_tc0, b0, is_ip4); - tcp_connection_cleanup (new_tc0); - goto drop; - } + if (tcp_opts_wscale (&new_tc0->rcv_opts)) + new_tc0->snd_wscale = new_tc0->rcv_opts.wscale; + else + new_tc0->rcv_wscale = 0; - /* Make sure after data segment processing ACK is sent */ - new_tc0->flags |= TCP_CONN_SNDACK; + new_tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window) + << new_tc0->snd_wscale; + new_tc0->snd_wl1 = seq0; + new_tc0->snd_wl2 = ack0; - /* Update rtt with the syn-ack sample */ - tcp_update_rtt (new_tc0, vnet_buffer (b0)->tcp.ack_number); - TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, new_tc0); - } - /* SYN: Simultaneous open. Change state to SYN-RCVD and send SYN-ACK */ - else - { - new_tc0->state = TCP_STATE_SYN_RCVD; + tcp_connection_init_vars (new_tc0); - /* Notify app that we have connection */ - if (session_stream_connect_notify (&new_tc0->connection, 0)) - { - tcp_connection_cleanup (new_tc0); - tcp_send_reset_w_pkt (tc0, b0, is_ip4); - TCP_EVT_DBG (TCP_EVT_RST_SENT, tc0); - goto drop; - } + /* SYN-ACK: See if we can switch to ESTABLISHED state */ + if (PREDICT_TRUE (tcp_ack (tcp0))) + { + /* Our SYN is ACKed: we have iss < ack = snd_una */ - tc0->rtt_ts = 0; - tcp_init_snd_vars (tc0); - tcp_make_synack (new_tc0, b0); - next0 = tcp_next_output (is_ip4); + /* TODO Dequeue acknowledged segments if we support Fast Open */ + new_tc0->snd_una = ack0; + new_tc0->state = TCP_STATE_ESTABLISHED; - goto drop; - } + /* Make sure las is initialized for the wnd computation */ + new_tc0->rcv_las = new_tc0->rcv_nxt; - /* Read data, if any */ - if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len)) - { - clib_warning ("rcvd data in syn-sent"); - error0 = tcp_segment_rcv (new_tc0, b0, &next0); - if (error0 == TCP_ERROR_ACK_OK) - error0 = TCP_ERROR_SYN_ACKS_RCVD; - tcp_maybe_inc_counter (syn_sent, error0, 1); - } - else + /* Notify app that we have connection. If session layer can't + * allocate session send reset */ + if (session_stream_connect_notify (&new_tc0->connection, 0)) { - tcp_make_ack (new_tc0, b0); - next0 = tcp_next_output (new_tc0->c_is_ip4); + tcp_send_reset_w_pkt (new_tc0, b0, my_thread_index, is_ip4); + tcp_connection_cleanup (new_tc0); + error0 = TCP_ERROR_CREATE_SESSION_FAIL; + goto drop; } - drop: + new_tc0->tx_fifo_size = + transport_tx_fifo_size (&new_tc0->connection); + /* Update rtt with the syn-ack sample */ + tcp_estimate_initial_rtt (new_tc0); + TCP_EVT_DBG (TCP_EVT_SYNACK_RCVD, new_tc0); + error0 = TCP_ERROR_SYN_ACKS_RCVD; + } + /* SYN: Simultaneous open. Change state to SYN-RCVD and send SYN-ACK */ + else + { + new_tc0->state = TCP_STATE_SYN_RCVD; - b0->error = error0 ? node->errors[error0] : 0; - if (PREDICT_FALSE - ((b0->flags & VLIB_BUFFER_IS_TRACED) && tcp0 != 0)) + /* Notify app that we have connection */ + if (session_stream_connect_notify (&new_tc0->connection, 0)) { - t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); - clib_memcpy (&t0->tcp_header, tcp0, sizeof (t0->tcp_header)); - clib_memcpy (&t0->tcp_connection, tc0, - sizeof (t0->tcp_connection)); + tcp_connection_cleanup (new_tc0); + tcp_send_reset_w_pkt (tc0, b0, my_thread_index, is_ip4); + TCP_EVT_DBG (TCP_EVT_RST_SENT, tc0); + error0 = TCP_ERROR_CREATE_SESSION_FAIL; + goto drop; } - vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, - n_left_to_next, bi0, next0); + new_tc0->tx_fifo_size = + transport_tx_fifo_size (&new_tc0->connection); + new_tc0->rtt_ts = 0; + tcp_init_snd_vars (new_tc0); + tcp_send_synack (new_tc0); + error0 = TCP_ERROR_SYNS_RCVD; + goto drop; } - vlib_put_next_frame (vm, node, next_index, n_left_to_next); + /* Read data, if any */ + if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len)) + { + clib_warning ("rcvd data in syn-sent"); + error0 = tcp_segment_rcv (wrk, new_tc0, b0); + if (error0 == TCP_ERROR_ACK_OK) + error0 = TCP_ERROR_SYN_ACKS_RCVD; + } + else + { + tcp_program_ack (wrk, new_tc0); + } + + drop: + + tcp_inc_counter (syn_sent, error0, 1); + if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED) && tcp0 != 0)) + { + t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); + clib_memcpy_fast (&t0->tcp_header, tcp0, sizeof (t0->tcp_header)); + clib_memcpy_fast (&t0->tcp_connection, tc0, + sizeof (t0->tcp_connection)); + } } - errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP, - my_thread_index); - tcp_inc_counter (syn_sent, TCP_ERROR_EVENT_FIFO_FULL, errors); + errors = session_main_flush_enqueue_events (TRANSPORT_PROTO_TCP, + my_thread_index); + tcp_inc_counter (syn_sent, TCP_ERROR_MSG_QUEUE_FULL, errors); + vlib_buffer_free (vm, first_buffer, from_frame->n_vectors); + return from_frame->n_vectors; } -static uword -tcp4_syn_sent (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp4_syn_sent_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_syn_sent_inline (vm, node, from_frame, 1 /* is_ip4 */ ); } -static uword -tcp6_syn_sent_rcv (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp6_syn_sent_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_syn_sent_inline (vm, node, from_frame, 0 /* is_ip4 */ ); } @@ -2313,7 +2625,6 @@ tcp6_syn_sent_rcv (vlib_main_t * vm, vlib_node_runtime_t * node, /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp4_syn_sent_node) = { - .function = tcp4_syn_sent, .name = "tcp4-syn-sent", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2330,12 +2641,9 @@ VLIB_REGISTER_NODE (tcp4_syn_sent_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp4_syn_sent_node, tcp4_syn_sent); - /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp6_syn_sent_node) = { - .function = tcp6_syn_sent_rcv, .name = "tcp6-syn-sent", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2352,11 +2660,6 @@ VLIB_REGISTER_NODE (tcp6_syn_sent_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp6_syn_sent_node, tcp6_syn_sent_rcv); - -vlib_node_registration_t tcp4_rcv_process_node; -vlib_node_registration_t tcp6_rcv_process_node; - /** * Handles reception for all states except LISTEN, SYN-SENT and ESTABLISHED * as per RFC793 p. 64 @@ -2365,367 +2668,376 @@ always_inline uword tcp46_rcv_process_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, n_fins = 0; - u32 my_thread_index = vm->thread_index, errors = 0; + u32 thread_index = vm->thread_index, errors = 0, *first_buffer; + tcp_worker_ctx_t *wrk = tcp_get_worker (thread_index); + u32 n_left_from, *from, max_dequeue; - from = vlib_frame_vector_args (from_frame); + from = first_buffer = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; - next_index = node->cached_next_index; while (n_left_from > 0) { - u32 n_left_to_next; + u32 bi0, error0 = TCP_ERROR_NONE; + tcp_header_t *tcp0 = 0; + tcp_connection_t *tc0; + vlib_buffer_t *b0; + u8 is_fin0; - vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + bi0 = from[0]; + from += 1; + n_left_from -= 1; - while (n_left_from > 0 && n_left_to_next > 0) + b0 = vlib_get_buffer (vm, bi0); + tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index, + thread_index); + if (PREDICT_FALSE (tc0 == 0)) { - u32 bi0; - vlib_buffer_t *b0; - tcp_header_t *tcp0 = 0; - tcp_connection_t *tc0; - u32 next0 = tcp_next_drop (is_ip4), error0 = TCP_ERROR_NONE; - u8 is_fin0; + error0 = TCP_ERROR_INVALID_CONNECTION; + goto drop; + } - bi0 = from[0]; - to_next[0] = bi0; - from += 1; - to_next += 1; - n_left_from -= 1; - n_left_to_next -= 1; + tcp0 = tcp_buffer_hdr (b0); + is_fin0 = tcp_is_fin (tcp0); - b0 = vlib_get_buffer (vm, bi0); - tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index, - my_thread_index); - if (PREDICT_FALSE (tc0 == 0)) + if (CLIB_DEBUG) + { + tcp_connection_t *tmp; + tmp = tcp_lookup_connection (tc0->c_fib_index, b0, thread_index, + is_ip4); + if (tmp->state != tc0->state) { - error0 = TCP_ERROR_INVALID_CONNECTION; + if (tc0->state != TCP_STATE_CLOSED) + clib_warning ("state changed"); goto drop; } + } + + /* + * Special treatment for CLOSED + */ + if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED)) + { + error0 = TCP_ERROR_CONNECTION_CLOSED; + goto drop; + } - tcp0 = tcp_buffer_hdr (b0); - is_fin0 = tcp_is_fin (tcp0); + /* + * For all other states (except LISTEN) + */ - /* SYNs, FINs and data consume sequence numbers */ - vnet_buffer (b0)->tcp.seq_end = vnet_buffer (b0)->tcp.seq_number - + tcp_is_syn (tcp0) + is_fin0 + vnet_buffer (b0)->tcp.data_len; + /* 1-4: check SEQ, RST, SYN */ + if (PREDICT_FALSE (tcp_segment_validate (wrk, tc0, b0, tcp0, &error0))) + goto drop; - if (CLIB_DEBUG) + /* 5: check the ACK field */ + switch (tc0->state) + { + case TCP_STATE_SYN_RCVD: + + /* Make sure the segment is exactly right */ + if (tc0->rcv_nxt != vnet_buffer (b0)->tcp.seq_number || is_fin0) { - tcp_connection_t *tmp; - tmp = tcp_lookup_connection (tc0->c_fib_index, b0, - my_thread_index, is_ip4); - if (tmp->state != tc0->state) - { - clib_warning ("state changed"); - goto drop; - } + tcp_connection_reset (tc0); + error0 = TCP_ERROR_SEGMENT_INVALID; + goto drop; } /* - * Special treatment for CLOSED + * If the segment acknowledgment is not acceptable, form a + * reset segment, + * + * and send it. */ - if (PREDICT_FALSE (tc0->state == TCP_STATE_CLOSED)) + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) { - error0 = TCP_ERROR_CONNECTION_CLOSED; + tcp_connection_reset (tc0); goto drop; } - /* - * For all other states (except LISTEN) - */ + /* Update rtt and rto */ + tcp_estimate_initial_rtt (tc0); + + /* Switch state to ESTABLISHED */ + tc0->state = TCP_STATE_ESTABLISHED; + TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); + + /* Initialize session variables */ + tc0->snd_una = vnet_buffer (b0)->tcp.ack_number; + tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window) + << tc0->rcv_opts.wscale; + tc0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number; + tc0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number; - /* 1-4: check SEQ, RST, SYN */ - if (PREDICT_FALSE (tcp_segment_validate (vm, tc0, b0, tcp0, - &next0, &error0))) + /* Reset SYN-ACK retransmit and SYN_RCV establish timers */ + tcp_retransmit_timer_reset (tc0); + tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH); + if (session_stream_accept_notify (&tc0->connection)) { - tcp_maybe_inc_counter (rcv_process, error0, 1); + error0 = TCP_ERROR_MSG_QUEUE_FULL; + tcp_connection_reset (tc0); goto drop; } + error0 = TCP_ERROR_ACK_OK; + break; + case TCP_STATE_ESTABLISHED: + /* We can get packets in established state here because they + * were enqueued before state change */ + if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0)) + goto drop; + + break; + case TCP_STATE_FIN_WAIT_1: + /* In addition to the processing for the ESTABLISHED state, if + * our FIN is now acknowledged then enter FIN-WAIT-2 and + * continue processing in that state. */ + if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0)) + goto drop; - /* 5: check the ACK field */ - switch (tc0->state) + /* Still have to send the FIN */ + if (tc0->flags & TCP_CONN_FINPNDG) { - case TCP_STATE_SYN_RCVD: - /* - * If the segment acknowledgment is not acceptable, form a - * reset segment, - * - * and send it. - */ - if (!tcp_rcv_ack_is_acceptable (tc0, b0)) - { - TCP_DBG ("connection not accepted"); - tcp_send_reset_w_pkt (tc0, b0, is_ip4); - error0 = TCP_ERROR_ACK_INVALID; - goto drop; - } + /* TX fifo finally drained */ + max_dequeue = transport_max_tx_dequeue (&tc0->connection); + if (max_dequeue <= tc0->burst_acked) + tcp_send_fin (tc0); + /* If a fin was received and data was acked extend wait */ + else if ((tc0->flags & TCP_CONN_FINRCVD) && tc0->bytes_acked) + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, + TCP_CLOSEWAIT_TIME); + } + /* If FIN is ACKed */ + else if (tc0->snd_una == tc0->snd_nxt) + { + /* Stop all retransmit timers because we have nothing more + * to send. */ + tcp_connection_timers_reset (tc0); - /* Update rtt and rto */ - tcp_update_rtt (tc0, vnet_buffer (b0)->tcp.ack_number); - - /* Switch state to ESTABLISHED */ - tc0->state = TCP_STATE_ESTABLISHED; - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - - /* Initialize session variables */ - tc0->snd_una = vnet_buffer (b0)->tcp.ack_number; - tc0->snd_wnd = clib_net_to_host_u16 (tcp0->window) - << tc0->rcv_opts.wscale; - tc0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number; - tc0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number; - - /* Reset SYN-ACK retransmit and SYN_RCV establish timers */ - tcp_retransmit_timer_reset (tc0); - tcp_timer_reset (tc0, TCP_TIMER_ESTABLISH); - stream_session_accept_notify (&tc0->connection); - error0 = TCP_ERROR_ACK_OK; - break; - case TCP_STATE_ESTABLISHED: - /* We can get packets in established state here because they - * were enqueued before state change */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) + /* We already have a FIN but didn't transition to CLOSING + * because of outstanding tx data. Close the connection. */ + if (tc0->flags & TCP_CONN_FINRCVD) { - tcp_maybe_inc_counter (rcv_process, error0, 1); + tcp_connection_set_state (tc0, TCP_STATE_CLOSED); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); goto drop; } - break; - case TCP_STATE_FIN_WAIT_1: - /* In addition to the processing for the ESTABLISHED state, if - * our FIN is now acknowledged then enter FIN-WAIT-2 and - * continue processing in that state. */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) - { - tcp_maybe_inc_counter (rcv_process, error0, 1); - goto drop; - } + tcp_connection_set_state (tc0, TCP_STATE_FIN_WAIT_2); + /* Enable waitclose because we're willing to wait for peer's + * FIN but not indefinitely. */ + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); - /* Still have to send the FIN */ - if (tc0->flags & TCP_CONN_FINPNDG) - { - /* TX fifo finally drained */ - if (!session_tx_fifo_max_dequeue (&tc0->connection)) - tcp_send_fin (tc0); - } - /* If FIN is ACKed */ - else if (tc0->snd_una == tc0->snd_una_max) - { - tc0->state = TCP_STATE_FIN_WAIT_2; - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - - /* Stop all retransmit timers because we have nothing more - * to send. Enable waitclose though because we're willing to - * wait for peer's FIN but not indefinitely. */ - tcp_connection_timers_reset (tc0); - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); - } - break; - case TCP_STATE_FIN_WAIT_2: - /* In addition to the processing for the ESTABLISHED state, if - * the retransmission queue is empty, the user's CLOSE can be - * acknowledged ("ok") but do not delete the TCB. */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) - { - tcp_maybe_inc_counter (rcv_process, error0, 1); - goto drop; - } - break; - case TCP_STATE_CLOSE_WAIT: - /* Do the same processing as for the ESTABLISHED state. */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) - { - tcp_maybe_inc_counter (rcv_process, error0, 1); - goto drop; - } - if (tc0->flags & TCP_CONN_FINPNDG) - { - /* TX fifo finally drained */ - if (!session_tx_fifo_max_dequeue (&tc0->connection)) - { - tcp_send_fin (tc0); - tcp_connection_timers_reset (tc0); - tc0->state = TCP_STATE_LAST_ACK; - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, - TCP_2MSL_TIME); - } - } - break; - case TCP_STATE_CLOSING: - /* In addition to the processing for the ESTABLISHED state, if - * the ACK acknowledges our FIN then enter the TIME-WAIT state, - * otherwise ignore the segment. */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) - { - tcp_maybe_inc_counter (rcv_process, error0, 1); - goto drop; - } + /* Don't try to deq the FIN acked */ + if (tc0->burst_acked > 1) + session_tx_fifo_dequeue_drop (&tc0->connection, + tc0->burst_acked - 1); + tc0->burst_acked = 0; + } + break; + case TCP_STATE_FIN_WAIT_2: + /* In addition to the processing for the ESTABLISHED state, if + * the retransmission queue is empty, the user's CLOSE can be + * acknowledged ("ok") but do not delete the TCB. */ + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; + tc0->burst_acked = 0; + break; + case TCP_STATE_CLOSE_WAIT: + /* Do the same processing as for the ESTABLISHED state. */ + if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0)) + goto drop; - tc0->state = TCP_STATE_TIME_WAIT; - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); - goto drop; + if (!(tc0->flags & TCP_CONN_FINPNDG)) + break; - break; - case TCP_STATE_LAST_ACK: - /* The only thing that [should] arrive in this state is an - * acknowledgment of our FIN. If our FIN is now acknowledged, - * delete the TCB, enter the CLOSED state, and return. */ + /* Still have outstanding tx data */ + max_dequeue = transport_max_tx_dequeue (&tc0->connection); + if (max_dequeue > tc0->burst_acked) + break; - if (!tcp_rcv_ack_is_acceptable (tc0, b0)) - { - error0 = TCP_ERROR_ACK_INVALID; - goto drop; - } - error0 = TCP_ERROR_ACK_OK; - tc0->snd_una = vnet_buffer (b0)->tcp.ack_number; - /* Apparently our ACK for the peer's FIN was lost */ - if (is_fin0 && tc0->snd_una != tc0->snd_una_max) - { - tcp_send_fin (tc0); - goto drop; - } + tcp_send_fin (tc0); + tcp_connection_timers_reset (tc0); + tcp_connection_set_state (tc0, TCP_STATE_LAST_ACK); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); + break; + case TCP_STATE_CLOSING: + /* In addition to the processing for the ESTABLISHED state, if + * the ACK acknowledges our FIN then enter the TIME-WAIT state, + * otherwise ignore the segment. */ + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; - tc0->state = TCP_STATE_CLOSED; - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - /* Delete the connection/session since the pipes should be - * clear by now */ - tcp_connection_del (tc0); + if (tc0->snd_una != tc0->snd_nxt) + goto drop; - goto drop; + tcp_connection_timers_reset (tc0); + tcp_connection_set_state (tc0, TCP_STATE_TIME_WAIT); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); + goto drop; - break; - case TCP_STATE_TIME_WAIT: - /* The only thing that can arrive in this state is a - * retransmission of the remote FIN. Acknowledge it, and restart - * the 2 MSL timeout. */ + break; + case TCP_STATE_LAST_ACK: + /* The only thing that [should] arrive in this state is an + * acknowledgment of our FIN. If our FIN is now acknowledged, + * delete the TCB, enter the CLOSED state, and return. */ - if (tcp_rcv_ack (tc0, b0, tcp0, &next0, &error0)) - { - tcp_maybe_inc_counter (rcv_process, error0, 1); - goto drop; - } + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; - tcp_make_ack (tc0, b0); - next0 = tcp_next_output (is_ip4); - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); + /* Apparently our ACK for the peer's FIN was lost */ + if (is_fin0 && tc0->snd_una != tc0->snd_nxt) + { + tcp_send_fin (tc0); goto drop; - - break; - default: - ASSERT (0); } - /* 6: check the URG bit TODO */ + tcp_connection_set_state (tc0, TCP_STATE_CLOSED); - /* 7: process the segment text */ - switch (tc0->state) - { - case TCP_STATE_ESTABLISHED: - case TCP_STATE_FIN_WAIT_1: - case TCP_STATE_FIN_WAIT_2: - if (vnet_buffer (b0)->tcp.data_len) - { - error0 = tcp_segment_rcv (tc0, b0, &next0); - tcp_maybe_inc_counter (rcv_process, error0, 1); - } - else if (is_fin0) - tc0->rcv_nxt += 1; - break; - case TCP_STATE_CLOSE_WAIT: - case TCP_STATE_CLOSING: - case TCP_STATE_LAST_ACK: - case TCP_STATE_TIME_WAIT: - /* This should not occur, since a FIN has been received from the - * remote side. Ignore the segment text. */ - break; - } + /* Don't free the connection from the data path since + * we can't ensure that we have no packets already enqueued + * to output. Rely instead on the waitclose timer */ + tcp_connection_timers_reset (tc0); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); + + goto drop; + + break; + case TCP_STATE_TIME_WAIT: + /* The only thing that can arrive in this state is a + * retransmission of the remote FIN. Acknowledge it, and restart + * the 2 MSL timeout. */ + + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; - /* 8: check the FIN bit */ if (!is_fin0) goto drop; - switch (tc0->state) - { - case TCP_STATE_ESTABLISHED: - case TCP_STATE_SYN_RCVD: - /* Send FIN-ACK notify app and enter CLOSE-WAIT */ - tcp_connection_timers_reset (tc0); - tcp_retransmit_timer_set (tc0); - tcp_make_fin (tc0, b0); - tc0->snd_nxt += 1; - tc0->snd_una_max = tc0->snd_nxt; - next0 = tcp_next_output (tc0->c_is_ip4); - stream_session_disconnect_notify (&tc0->connection); - tc0->state = TCP_STATE_CLOSE_WAIT; - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - break; - case TCP_STATE_CLOSE_WAIT: - case TCP_STATE_CLOSING: - case TCP_STATE_LAST_ACK: - /* move along .. */ - break; - case TCP_STATE_FIN_WAIT_1: - tc0->state = TCP_STATE_CLOSING; - tcp_make_ack (tc0, b0); - next0 = tcp_next_output (is_ip4); - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - /* Wait for ACK but not forever */ - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); - break; - case TCP_STATE_FIN_WAIT_2: - /* Got FIN, send ACK! Be more aggressive with resource cleanup */ - tc0->state = TCP_STATE_TIME_WAIT; - tcp_connection_timers_reset (tc0); - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); - tcp_make_ack (tc0, b0); - next0 = tcp_next_output (is_ip4); - TCP_EVT_DBG (TCP_EVT_STATE_CHANGE, tc0); - break; - case TCP_STATE_TIME_WAIT: - /* Remain in the TIME-WAIT state. Restart the time-wait - * timeout. - */ - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); - break; - } - TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0); - n_fins += 1; + tcp_program_ack (wrk, tc0); + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); + goto drop; + + break; + default: + ASSERT (0); + } + + /* 6: check the URG bit TODO */ - drop: - b0->error = error0 ? node->errors[error0] : 0; + /* 7: process the segment text */ + switch (tc0->state) + { + case TCP_STATE_ESTABLISHED: + case TCP_STATE_FIN_WAIT_1: + case TCP_STATE_FIN_WAIT_2: + if (vnet_buffer (b0)->tcp.data_len) + error0 = tcp_segment_rcv (wrk, tc0, b0); + break; + case TCP_STATE_CLOSE_WAIT: + case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: + case TCP_STATE_TIME_WAIT: + /* This should not occur, since a FIN has been received from the + * remote side. Ignore the segment text. */ + break; + } + + /* 8: check the FIN bit */ + if (!is_fin0) + goto drop; + + TCP_EVT_DBG (TCP_EVT_FIN_RCVD, tc0); - if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + switch (tc0->state) + { + case TCP_STATE_ESTABLISHED: + /* Account for the FIN and send ack */ + tc0->rcv_nxt += 1; + tcp_program_ack (wrk, tc0); + tcp_connection_set_state (tc0, TCP_STATE_CLOSE_WAIT); + tcp_program_disconnect (wrk, tc0); + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME); + break; + case TCP_STATE_SYN_RCVD: + /* Send FIN-ACK, enter LAST-ACK and because the app was not + * notified yet, set a cleanup timer instead of relying on + * disconnect notify and the implicit close call. */ + tcp_connection_timers_reset (tc0); + tc0->rcv_nxt += 1; + tcp_send_fin (tc0); + tcp_connection_set_state (tc0, TCP_STATE_LAST_ACK); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); + break; + case TCP_STATE_CLOSE_WAIT: + case TCP_STATE_CLOSING: + case TCP_STATE_LAST_ACK: + /* move along .. */ + break; + case TCP_STATE_FIN_WAIT_1: + tc0->rcv_nxt += 1; + + if (tc0->flags & TCP_CONN_FINPNDG) { - tcp_rx_trace_t *t0 = - vlib_add_trace (vm, node, b0, sizeof (*t0)); - tcp_set_rx_trace_data (t0, tc0, tcp0, b0, is_ip4); + /* If data is outstanding, stay in FIN_WAIT_1 and try to finish + * sending it. Since we already received a fin, do not wait + * for too long. */ + tc0->flags |= TCP_CONN_FINRCVD; + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_CLOSEWAIT_TIME); } - - vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, - n_left_to_next, bi0, next0); + else + { + tcp_connection_set_state (tc0, TCP_STATE_CLOSING); + tcp_program_ack (wrk, tc0); + /* Wait for ACK for our FIN but not forever */ + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); + } + break; + case TCP_STATE_FIN_WAIT_2: + /* Got FIN, send ACK! Be more aggressive with resource cleanup */ + tc0->rcv_nxt += 1; + tcp_connection_set_state (tc0, TCP_STATE_TIME_WAIT); + tcp_connection_timers_reset (tc0); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); + tcp_program_ack (wrk, tc0); + break; + case TCP_STATE_TIME_WAIT: + /* Remain in the TIME-WAIT state. Restart the time-wait + * timeout. + */ + tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_TIMEWAIT_TIME); + break; } + error0 = TCP_ERROR_FIN_RCVD; + + drop: - vlib_put_next_frame (vm, node, next_index, n_left_to_next); + tcp_inc_counter (rcv_process, error0, 1); + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + tcp_rx_trace_t *t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); + tcp_set_rx_trace_data (t0, tc0, tcp0, b0, is_ip4); + } } - errors = session_manager_flush_enqueue_events (TRANSPORT_PROTO_TCP, - my_thread_index); - tcp_inc_counter (rcv_process, TCP_ERROR_EVENT_FIFO_FULL, errors); - tcp_inc_counter (rcv_process, TCP_ERROR_FIN_RCVD, n_fins); + errors = session_main_flush_enqueue_events (TRANSPORT_PROTO_TCP, + thread_index); + tcp_inc_counter (rcv_process, TCP_ERROR_MSG_QUEUE_FULL, errors); + tcp_handle_postponed_dequeues (wrk); + tcp_handle_disconnects (wrk); + vlib_buffer_free (vm, first_buffer, from_frame->n_vectors); + return from_frame->n_vectors; } -static uword -tcp4_rcv_process (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp4_rcv_process_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_rcv_process_inline (vm, node, from_frame, 1 /* is_ip4 */ ); } -static uword -tcp6_rcv_process (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp6_rcv_process_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_rcv_process_inline (vm, node, from_frame, 0 /* is_ip4 */ ); } @@ -2733,7 +3045,6 @@ tcp6_rcv_process (vlib_main_t * vm, vlib_node_runtime_t * node, /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp4_rcv_process_node) = { - .function = tcp4_rcv_process, .name = "tcp4-rcv-process", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2750,12 +3061,9 @@ VLIB_REGISTER_NODE (tcp4_rcv_process_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp4_rcv_process_node, tcp4_rcv_process); - /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp6_rcv_process_node) = { - .function = tcp6_rcv_process, .name = "tcp6-rcv-process", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2772,11 +3080,6 @@ VLIB_REGISTER_NODE (tcp6_rcv_process_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp6_rcv_process_node, tcp6_rcv_process); - -vlib_node_registration_t tcp4_listen_node; -vlib_node_registration_t tcp6_listen_node; - /** * LISTEN state processing as per RFC 793 p. 65 */ @@ -2784,175 +3087,160 @@ always_inline uword tcp46_listen_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, n_syns = 0; + u32 n_left_from, *from, n_syns = 0, *first_buffer; u32 my_thread_index = vm->thread_index; - from = vlib_frame_vector_args (from_frame); + from = first_buffer = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; - next_index = node->cached_next_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; - tcp_rx_trace_t *t0; - tcp_header_t *th0 = 0; - tcp_connection_t *lc0; - ip4_header_t *ip40; - ip6_header_t *ip60; - tcp_connection_t *child0; - u32 error0 = TCP_ERROR_NONE, next0 = tcp_next_drop (is_ip4); - - bi0 = from[0]; - to_next[0] = bi0; - from += 1; - to_next += 1; - n_left_from -= 1; - n_left_to_next -= 1; + u32 bi0; + vlib_buffer_t *b0; + tcp_rx_trace_t *t0; + tcp_header_t *th0 = 0; + tcp_connection_t *lc0; + ip4_header_t *ip40; + ip6_header_t *ip60; + tcp_connection_t *child0; + u32 error0 = TCP_ERROR_NONE; - b0 = vlib_get_buffer (vm, bi0); - lc0 = tcp_listener_get (vnet_buffer (b0)->tcp.connection_index); + bi0 = from[0]; + from += 1; + n_left_from -= 1; - if (is_ip4) - { - ip40 = vlib_buffer_get_current (b0); - th0 = ip4_next_header (ip40); - } - else - { - ip60 = vlib_buffer_get_current (b0); - th0 = ip6_next_header (ip60); - } + b0 = vlib_get_buffer (vm, bi0); + lc0 = tcp_listener_get (vnet_buffer (b0)->tcp.connection_index); - /* Create child session. For syn-flood protection use filter */ + if (is_ip4) + { + ip40 = vlib_buffer_get_current (b0); + th0 = ip4_next_header (ip40); + } + else + { + ip60 = vlib_buffer_get_current (b0); + th0 = ip6_next_header (ip60); + } - /* 1. first check for an RST: handled in dispatch */ - /* if (tcp_rst (th0)) - goto drop; */ + /* Create child session. For syn-flood protection use filter */ - /* 2. second check for an ACK: handled in dispatch */ - /* if (tcp_ack (th0)) - { - tcp_send_reset (b0, is_ip4); - goto drop; - } */ + /* 1. first check for an RST: handled in dispatch */ + /* if (tcp_rst (th0)) + goto drop; + */ - /* 3. check for a SYN (did that already) */ + /* 2. second check for an ACK: handled in dispatch */ + /* if (tcp_ack (th0)) + { + tcp_send_reset (b0, is_ip4); + goto drop; + } + */ - /* Make sure connection wasn't just created */ - child0 = tcp_lookup_connection (lc0->c_fib_index, b0, - my_thread_index, is_ip4); - if (PREDICT_FALSE (child0->state != TCP_STATE_LISTEN)) - { - error0 = TCP_ERROR_CREATE_EXISTS; - goto drop; - } + /* 3. check for a SYN (did that already) */ - /* Create child session and send SYN-ACK */ - child0 = tcp_connection_new (my_thread_index); - child0->c_lcl_port = th0->dst_port; - child0->c_rmt_port = th0->src_port; - child0->c_is_ip4 = is_ip4; - child0->state = TCP_STATE_SYN_RCVD; - child0->c_fib_index = lc0->c_fib_index; + /* Make sure connection wasn't just created */ + child0 = tcp_lookup_connection (lc0->c_fib_index, b0, my_thread_index, + is_ip4); + if (PREDICT_FALSE (child0->state != TCP_STATE_LISTEN)) + { + error0 = TCP_ERROR_CREATE_EXISTS; + goto drop; + } - if (is_ip4) - { - child0->c_lcl_ip4.as_u32 = ip40->dst_address.as_u32; - child0->c_rmt_ip4.as_u32 = ip40->src_address.as_u32; - } - else - { - clib_memcpy (&child0->c_lcl_ip6, &ip60->dst_address, - sizeof (ip6_address_t)); - clib_memcpy (&child0->c_rmt_ip6, &ip60->src_address, - sizeof (ip6_address_t)); - } + /* Create child session and send SYN-ACK */ + child0 = tcp_connection_alloc (my_thread_index); + child0->c_lcl_port = th0->dst_port; + child0->c_rmt_port = th0->src_port; + child0->c_is_ip4 = is_ip4; + child0->state = TCP_STATE_SYN_RCVD; + child0->c_fib_index = lc0->c_fib_index; - if (tcp_options_parse (th0, &child0->rcv_opts)) - { - clib_warning ("options parse fail"); - goto drop; - } + if (is_ip4) + { + child0->c_lcl_ip4.as_u32 = ip40->dst_address.as_u32; + child0->c_rmt_ip4.as_u32 = ip40->src_address.as_u32; + } + else + { + clib_memcpy_fast (&child0->c_lcl_ip6, &ip60->dst_address, + sizeof (ip6_address_t)); + clib_memcpy_fast (&child0->c_rmt_ip6, &ip60->src_address, + sizeof (ip6_address_t)); + } - child0->irs = vnet_buffer (b0)->tcp.seq_number; - child0->rcv_nxt = vnet_buffer (b0)->tcp.seq_number + 1; - child0->rcv_las = child0->rcv_nxt; - child0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + if (tcp_options_parse (th0, &child0->rcv_opts, 1)) + { + error0 = TCP_ERROR_OPTIONS; + tcp_connection_free (child0); + goto drop; + } - /* RFC1323: TSval timestamps sent on {SYN} and {SYN,ACK} - * segments are used to initialize PAWS. */ - if (tcp_opts_tstamp (&child0->rcv_opts)) - { - child0->tsval_recent = child0->rcv_opts.tsval; - child0->tsval_recent_age = tcp_time_now (); - } + child0->irs = vnet_buffer (b0)->tcp.seq_number; + child0->rcv_nxt = vnet_buffer (b0)->tcp.seq_number + 1; + child0->rcv_las = child0->rcv_nxt; + child0->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; - if (tcp_opts_wscale (&child0->rcv_opts)) - child0->snd_wscale = child0->rcv_opts.wscale; + /* RFC1323: TSval timestamps sent on {SYN} and {SYN,ACK} + * segments are used to initialize PAWS. */ + if (tcp_opts_tstamp (&child0->rcv_opts)) + { + child0->tsval_recent = child0->rcv_opts.tsval; + child0->tsval_recent_age = tcp_time_now (); + } - child0->snd_wnd = clib_net_to_host_u16 (th0->window) - << child0->snd_wscale; - child0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number; - child0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number; + if (tcp_opts_wscale (&child0->rcv_opts)) + child0->snd_wscale = child0->rcv_opts.wscale; - tcp_connection_init_vars (child0); - TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0, 1); + child0->snd_wnd = clib_net_to_host_u16 (th0->window) + << child0->snd_wscale; + child0->snd_wl1 = vnet_buffer (b0)->tcp.seq_number; + child0->snd_wl2 = vnet_buffer (b0)->tcp.ack_number; - if (stream_session_accept (&child0->connection, lc0->c_s_index, - 0 /* notify */ )) - { - clib_warning ("session accept fail"); - tcp_connection_cleanup (child0); - error0 = TCP_ERROR_CREATE_SESSION_FAIL; - goto drop; - } + tcp_connection_init_vars (child0); + child0->rto = TCP_RTO_MIN; - /* Reuse buffer to make syn-ack and send */ - tcp_make_synack (child0, b0); - next0 = tcp_next_output (is_ip4); - tcp_timer_set (child0, TCP_TIMER_ESTABLISH, TCP_SYN_RCVD_TIME); + if (session_stream_accept (&child0->connection, lc0->c_s_index, + lc0->c_thread_index, 0 /* notify */ )) + { + tcp_connection_cleanup (child0); + error0 = TCP_ERROR_CREATE_SESSION_FAIL; + goto drop; + } - drop: - if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) - { - t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); - clib_memcpy (&t0->tcp_header, th0, sizeof (t0->tcp_header)); - clib_memcpy (&t0->tcp_connection, lc0, - sizeof (t0->tcp_connection)); - } + TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0, 1); + child0->tx_fifo_size = transport_tx_fifo_size (&child0->connection); + tcp_send_synack (child0); + tcp_timer_set (child0, TCP_TIMER_ESTABLISH, TCP_SYN_RCVD_TIME); - n_syns += (error0 == TCP_ERROR_NONE); - b0->error = node->errors[error0]; + drop: - vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, - n_left_to_next, bi0, next0); + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + t0 = vlib_add_trace (vm, node, b0, sizeof (*t0)); + clib_memcpy_fast (&t0->tcp_header, th0, sizeof (t0->tcp_header)); + clib_memcpy_fast (&t0->tcp_connection, lc0, + sizeof (t0->tcp_connection)); } - vlib_put_next_frame (vm, node, next_index, n_left_to_next); + n_syns += (error0 == TCP_ERROR_NONE); } tcp_inc_counter (listen, TCP_ERROR_SYNS_RCVD, n_syns); + vlib_buffer_free (vm, first_buffer, from_frame->n_vectors); + return from_frame->n_vectors; } -static uword -tcp4_listen (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp4_listen_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_listen_inline (vm, node, from_frame, 1 /* is_ip4 */ ); } -static uword -tcp6_listen (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp6_listen_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_listen_inline (vm, node, from_frame, 0 /* is_ip4 */ ); } @@ -2960,7 +3248,6 @@ tcp6_listen (vlib_main_t * vm, vlib_node_runtime_t * node, /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp4_listen_node) = { - .function = tcp4_listen, .name = "tcp4-listen", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2977,12 +3264,9 @@ VLIB_REGISTER_NODE (tcp4_listen_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp4_listen_node, tcp4_listen); - /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp6_listen_node) = { - .function = tcp6_listen, .name = "tcp6-listen", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -2999,11 +3283,6 @@ VLIB_REGISTER_NODE (tcp6_listen_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp6_listen_node, tcp6_listen); - -vlib_node_registration_t tcp4_input_node; -vlib_node_registration_t tcp6_input_node; - typedef enum _tcp_input_next { TCP_INPUT_NEXT_DROP, @@ -3061,7 +3340,7 @@ tcp_input_trace_frame (vlib_main_t * vm, vlib_node_runtime_t * node, static void tcp_input_set_error_next (tcp_main_t * tm, u16 * next, u32 * error, u8 is_ip4) { - if (*error == TCP_ERROR_FILTERED) + if (*error == TCP_ERROR_FILTERED || *error == TCP_ERROR_WRONG_THREAD) { *next = TCP_INPUT_NEXT_DROP; } @@ -3085,18 +3364,24 @@ tcp_input_lookup_buffer (vlib_buffer_t * b, u8 thread_index, u32 * error, int n_advance_bytes, n_data_bytes; transport_connection_t *tc; tcp_header_t *tcp; - u8 is_filtered = 0; + u8 result = 0; if (is_ip4) { ip4_header_t *ip4 = vlib_buffer_get_current (b); + int ip_hdr_bytes = ip4_header_bytes (ip4); + if (PREDICT_FALSE (b->current_length < ip_hdr_bytes + sizeof (*tcp))) + { + *error = TCP_ERROR_LENGTH; + return 0; + } tcp = ip4_next_header (ip4); vnet_buffer (b)->tcp.hdr_offset = (u8 *) tcp - (u8 *) ip4; - n_advance_bytes = (ip4_header_bytes (ip4) + tcp_header_bytes (tcp)); + n_advance_bytes = (ip_hdr_bytes + tcp_header_bytes (tcp)); n_data_bytes = clib_net_to_host_u16 (ip4->length) - n_advance_bytes; /* Length check. Checksum computed by ipx_local no need to compute again */ - if (PREDICT_FALSE (n_advance_bytes < 0)) + if (PREDICT_FALSE (n_data_bytes < 0)) { *error = TCP_ERROR_LENGTH; return 0; @@ -3105,11 +3390,16 @@ tcp_input_lookup_buffer (vlib_buffer_t * b, u8 thread_index, u32 * error, tc = session_lookup_connection_wt4 (fib_index, &ip4->dst_address, &ip4->src_address, tcp->dst_port, tcp->src_port, TRANSPORT_PROTO_TCP, - thread_index, &is_filtered); + thread_index, &result); } else { ip6_header_t *ip6 = vlib_buffer_get_current (b); + if (PREDICT_FALSE (b->current_length < sizeof (*ip6) + sizeof (*tcp))) + { + *error = TCP_ERROR_LENGTH; + return 0; + } tcp = ip6_next_header (ip6); vnet_buffer (b)->tcp.hdr_offset = (u8 *) tcp - (u8 *) ip6; n_advance_bytes = tcp_header_bytes (tcp); @@ -3117,25 +3407,34 @@ tcp_input_lookup_buffer (vlib_buffer_t * b, u8 thread_index, u32 * error, - n_advance_bytes; n_advance_bytes += sizeof (ip6[0]); - if (PREDICT_FALSE (n_advance_bytes < 0)) + if (PREDICT_FALSE (n_data_bytes < 0)) { *error = TCP_ERROR_LENGTH; return 0; } + if (PREDICT_FALSE + (ip6_address_is_link_local_unicast (&ip6->dst_address))) + { + ip4_main_t *im = &ip4_main; + fib_index = vec_elt (im->fib_index_by_sw_if_index, + vnet_buffer (b)->sw_if_index[VLIB_RX]); + } tc = session_lookup_connection_wt6 (fib_index, &ip6->dst_address, &ip6->src_address, tcp->dst_port, tcp->src_port, TRANSPORT_PROTO_TCP, - thread_index, &is_filtered); + thread_index, &result); } vnet_buffer (b)->tcp.seq_number = clib_net_to_host_u32 (tcp->seq_number); vnet_buffer (b)->tcp.ack_number = clib_net_to_host_u32 (tcp->ack_number); vnet_buffer (b)->tcp.data_offset = n_advance_bytes; vnet_buffer (b)->tcp.data_len = n_data_bytes; + vnet_buffer (b)->tcp.seq_end = vnet_buffer (b)->tcp.seq_number + + n_data_bytes; vnet_buffer (b)->tcp.flags = 0; - *error = is_filtered ? TCP_ERROR_FILTERED : *error; + *error = result ? TCP_ERROR_NONE + result : *error; return tcp_get_connection_from_transport (tc); } @@ -3160,8 +3459,9 @@ tcp_input_dispatch_buffer (tcp_main_t * tm, tcp_connection_t * tc, vnet_buffer (b)->tcp.flags = tc->state; if (*error == TCP_ERROR_DISPATCH) - clib_warning ("disp error state %U flags %U", format_tcp_state, - state, format_tcp_flags, (int) flags); + clib_warning ("tcp conn %u disp error state %U flags %U", + tc->c_c_index, format_tcp_state, state, + format_tcp_flags, (int) flags); } } @@ -3174,7 +3474,7 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; u16 nexts[VLIB_FRAME_SIZE], *next; - tcp_set_time_now (thread_index); + tcp_set_time_now (tcp_get_worker (thread_index)); from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; @@ -3271,16 +3571,14 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, return frame->n_vectors; } -static uword -tcp4_input (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp4_input_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_input_inline (vm, node, from_frame, 1 /* is_ip4 */ ); } -static uword -tcp6_input (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * from_frame) +VLIB_NODE_FN (tcp6_input_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * from_frame) { return tcp46_input_inline (vm, node, from_frame, 0 /* is_ip4 */ ); } @@ -3288,7 +3586,6 @@ tcp6_input (vlib_main_t * vm, vlib_node_runtime_t * node, /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp4_input_node) = { - .function = tcp4_input, .name = "tcp4-input", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -3306,12 +3603,9 @@ VLIB_REGISTER_NODE (tcp4_input_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp4_input_node, tcp4_input); - /* *INDENT-OFF* */ VLIB_REGISTER_NODE (tcp6_input_node) = { - .function = tcp6_input, .name = "tcp6-input", /* Takes a vector of packets. */ .vector_size = sizeof (u32), @@ -3329,8 +3623,7 @@ VLIB_REGISTER_NODE (tcp6_input_node) = }; /* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (tcp6_input_node, tcp6_input); - +#ifndef CLIB_MARCH_VARIANT static void tcp_dispatch_table_init (tcp_main_t * tm) { @@ -3348,16 +3641,62 @@ do { \ tm->dispatch_table[TCP_STATE_##t][f].error = (e); \ } while (0) - /* SYNs for new connections -> tcp-listen. */ + /* RFC 793: In LISTEN if RST drop and if ACK return RST */ + _(LISTEN, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, TCP_ERROR_ACK_INVALID); + _(LISTEN, TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, TCP_ERROR_INVALID_CONNECTION); _(LISTEN, TCP_FLAG_SYN, TCP_INPUT_NEXT_LISTEN, TCP_ERROR_NONE); - _(LISTEN, TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, TCP_ERROR_NONE); - _(LISTEN, TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, TCP_ERROR_RST_RCVD); + _(LISTEN, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, + TCP_ERROR_ACK_INVALID); + _(LISTEN, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, + TCP_ERROR_INVALID_CONNECTION); + _(LISTEN, TCP_FLAG_FIN, TCP_INPUT_NEXT_RESET, TCP_ERROR_SEGMENT_INVALID); _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, TCP_ERROR_NONE); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, + TCP_ERROR_SEGMENT_INVALID); + _(LISTEN, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); /* ACK for for a SYN-ACK -> tcp-rcv-process. */ _(SYN_RCVD, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(SYN_RCVD, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); _(SYN_RCVD, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(SYN_RCVD, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); /* SYN-ACK for a SYN */ _(SYN_SENT, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_SYN_SENT, TCP_ERROR_NONE); @@ -3365,52 +3704,155 @@ do { \ _(SYN_SENT, TCP_FLAG_RST, TCP_INPUT_NEXT_SYN_SENT, TCP_ERROR_NONE); _(SYN_SENT, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_SYN_SENT, TCP_ERROR_NONE); + _(SYN_SENT, TCP_FLAG_FIN, TCP_INPUT_NEXT_SYN_SENT, TCP_ERROR_NONE); + _(SYN_SENT, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_SYN_SENT, + TCP_ERROR_NONE); /* ACK for for established connection -> tcp-established. */ _(ESTABLISHED, TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); /* FIN for for established connection -> tcp-established. */ _(ESTABLISHED, TCP_FLAG_FIN, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_ESTABLISHED, + TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_ESTABLISHED, + TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, + TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST, + TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); _(ESTABLISHED, TCP_FLAG_RST, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); _(ESTABLISHED, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); _(ESTABLISHED, TCP_FLAG_SYN, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); _(ESTABLISHED, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_ESTABLISHED, + TCP_ERROR_NONE); + _(ESTABLISHED, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_ESTABLISHED, TCP_ERROR_NONE); + _(ESTABLISHED, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); /* ACK or FIN-ACK to our FIN */ _(FIN_WAIT_1, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(FIN_WAIT_1, TCP_FLAG_ACK | TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); /* FIN in reply to our FIN from the other side */ + _(FIN_WAIT_1, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); _(FIN_WAIT_1, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(FIN_WAIT_1, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_1, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); _(CLOSING, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSING, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); /* FIN confirming that the peer (app) has closed */ _(FIN_WAIT_2, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(FIN_WAIT_2, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(FIN_WAIT_2, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_2, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(FIN_WAIT_2, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); _(CLOSE_WAIT, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(CLOSE_WAIT, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSE_WAIT, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(CLOSE_WAIT, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, 0, TCP_INPUT_NEXT_DROP, TCP_ERROR_SEGMENT_INVALID); _(LAST_ACK, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(LAST_ACK, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(LAST_ACK, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_SYN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_SYN | TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); + _(LAST_ACK, TCP_FLAG_SYN | TCP_FLAG_RST | TCP_FLAG_ACK, + TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(TIME_WAIT, TCP_FLAG_SYN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(TIME_WAIT, TCP_FLAG_FIN, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(TIME_WAIT, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); _(TIME_WAIT, TCP_FLAG_RST, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); + _(TIME_WAIT, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, + TCP_ERROR_NONE); _(TIME_WAIT, TCP_FLAG_ACK, TCP_INPUT_NEXT_RCV_PROCESS, TCP_ERROR_NONE); - _(CLOSED, TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, TCP_ERROR_CONNECTION_CLOSED); + /* RFC793 CLOSED: An incoming segment containing a RST is discarded. An + * incoming segment not containing a RST causes a RST to be sent in + * response.*/ _(CLOSED, TCP_FLAG_RST, TCP_INPUT_NEXT_DROP, TCP_ERROR_CONNECTION_CLOSED); - _(CLOSED, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, + _(CLOSED, TCP_FLAG_RST | TCP_FLAG_ACK, TCP_INPUT_NEXT_DROP, TCP_ERROR_CONNECTION_CLOSED); + _(CLOSED, TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, TCP_ERROR_NONE); + _(CLOSED, TCP_FLAG_SYN, TCP_INPUT_NEXT_RESET, TCP_ERROR_NONE); + _(CLOSED, TCP_FLAG_FIN | TCP_FLAG_ACK, TCP_INPUT_NEXT_RESET, + TCP_ERROR_NONE); #undef _ } -clib_error_t * +static clib_error_t * tcp_input_init (vlib_main_t * vm) { clib_error_t *error = 0; @@ -3427,6 +3869,8 @@ tcp_input_init (vlib_main_t * vm) VLIB_INIT_FUNCTION (tcp_input_init); +#endif /* CLIB_MARCH_VARIANT */ + /* * fd.io coding-style-patch-verification: ON *