X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvnet%2Ftcp%2Ftcp_input.c;h=1b4e8a6ac21776963409db94e7042db0dd826520;hb=1cfcb78940580c8e3645fca0419d32f9286e942d;hp=b7838e2e1a0bd0d3d8baf899d6d8a055ba192638;hpb=e275bed64d12009726fcf43943f1684195cd1e5d;p=vpp.git diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index b7838e2e1a0..1b4e8a6ac21 100644 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -349,7 +349,7 @@ tcp_segment_validate (tcp_worker_ctx_t * wrk, tcp_connection_t * tc0, /* 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 + if (tc0->rcv_wnd < tc0->snd_mss && tc0->rcv_nxt == vnet_buffer (b0)->tcp.seq_number) goto check_reset; @@ -390,8 +390,9 @@ tcp_segment_validate (tcp_worker_ctx_t * wrk, tcp_connection_t * tc0, /* 4th: check the SYN bit (in window) */ if (PREDICT_FALSE (tcp_syn (th0))) { + /* As per RFC5961 send challenge ack instead of reset */ + tcp_program_ack (wrk, tc0); *error0 = TCP_ERROR_SPURIOUS_SYN; - tcp_send_reset (tc0); goto error; } @@ -405,11 +406,27 @@ error: } 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; } /** @@ -491,7 +508,7 @@ tcp_update_rtt (tcp_connection_t * tc, u32 ack) * 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) { - u32 now = tcp_time_now_w_thread (tc->c_thread_index); + u32 now = tcp_tstamp (tc); mrtt = clib_max (now - tc->rcv_opts.tsecr, 1); } @@ -604,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)); } @@ -913,7 +930,7 @@ 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) - && seq_lt (hole->end, tc->snd_una_max))); + && seq_lt (hole->end, tc->snd_nxt))); } #ifndef CLIB_MARCH_VARIANT @@ -943,8 +960,8 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) if (seq_lt (blk->start, blk->end) && seq_gt (blk->start, tc->snd_una) && seq_gt (blk->start, ack) - && seq_lt (blk->start, tc->snd_una_max) - && seq_leq (blk->end, tc->snd_una_max)) + && seq_lt (blk->start, tc->snd_nxt) + && seq_leq (blk->end, tc->snd_nxt)) { blk++; continue; @@ -979,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; @@ -990,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 @@ -1078,8 +1095,7 @@ 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_una_max) + if (hole->start == ack + sb->snd_una_adv && hole->end == tc->snd_nxt) scoreboard_remove_hole (sb, hole); } @@ -1088,8 +1104,8 @@ tcp_rcv_sacks (tcp_connection_t * tc, u32 ack) - (old_sacked_bytes - sb->last_bytes_delivered); ASSERT (sb->last_sacked_bytes <= sb->sacked_bytes || tcp_in_recovery (tc)); ASSERT (sb->sacked_bytes == 0 || tcp_in_recovery (tc) - || 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 + || 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); @@ -1146,7 +1162,7 @@ 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; @@ -1162,7 +1178,6 @@ 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); @@ -1170,13 +1185,10 @@ tcp_cc_recovery_exit (tcp_connection_t * tc) #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->snd_rxt_bytes = 0; tc->rtt_ts = 0; tcp_fastrecovery_off (tc); @@ -1191,12 +1203,17 @@ 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_exit (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); } @@ -1237,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)); @@ -1246,12 +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 */ - tcp_cc_rcv_ack (tc); + tcp_cc_rcv_ack (tc, rs); /* If a cumulative ack, make sure dupacks is 0 */ tc->rcv_dupacks = 0; @@ -1317,18 +1337,22 @@ tcp_do_fastretransmits (tcp_worker_ctx_t * wrk) 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 = tcp_connection_get (ongoing_fast_rxt[i], thread_index); tc->flags &= ~TCP_CONN_FRXT_PENDING; - - if (!tcp_in_fastrecovery (tc)) - continue; - burst_size = clib_min (max_burst_size, VLIB_FRAME_SIZE - n_segs); burst_bytes = transport_connection_tx_pacer_burst (&tc->connection, last_cpu_time); @@ -1354,7 +1378,8 @@ tcp_do_fastretransmits (tcp_worker_ctx_t * wrk) * 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; @@ -1372,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++; @@ -1381,7 +1405,7 @@ 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)) @@ -1400,7 +1424,7 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) } tcp_cc_init_congestion (tc); - tc->cc_algo->rcv_cong_ack (tc, TCP_CC_DUPACK); + tcp_cc_rcv_cong_ack (tc, TCP_CC_DUPACK, rs); if (tcp_opts_sack_permitted (&tc->rcv_opts)) { @@ -1426,7 +1450,7 @@ tcp_cc_handle_event (tcp_connection_t * tc, u32 is_dack) 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 @@ -1480,10 +1504,8 @@ partial_ack: return; } - tc->snd_nxt = tc->snd_una_max; - /* Treat as congestion avoidance ack */ - tcp_cc_rcv_ack (tc); + tcp_cc_rcv_ack (tc, rs); return; } @@ -1501,7 +1523,7 @@ partial_ack: /* Post RTO timeout don't try anything fancy */ if (tcp_in_recovery (tc)) { - tcp_cc_rcv_ack (tc); + tcp_cc_rcv_ack (tc, rs); transport_add_tx_event (&tc->connection); return; } @@ -1532,15 +1554,13 @@ partial_ack: else { tcp_fastrecovery_first_on (tc); - /* Reuse last bytes delivered to track total bytes acked */ - tc->sack_sb.last_bytes_delivered += tc->bytes_acked; 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 @@ -1556,6 +1576,7 @@ 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); @@ -1563,38 +1584,19 @@ tcp_rcv_ack (tcp_worker_ctx_t * wrk, 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 cong recovery, we reset snd_nxt to snd_una. Seems - * peer still has the data so accept the ack */ - if (tcp_in_cong_recovery (tc) - && seq_leq (vnet_buffer (b)->tcp.ack_number, - tc->snd_una + tc->snd_wnd)) + /* 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_nxt = vnet_buffer (b)->tcp.ack_number; - if (seq_gt (tc->snd_nxt, tc->snd_una_max)) - tc->snd_una_max = tc->snd_nxt; 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_program_ack (wrk, tc); - *error = TCP_ERROR_ACK_FUTURE; - 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, + *error = TCP_ERROR_ACK_FUTURE; + TCP_EVT_DBG (TCP_EVT_ACK_RCV_ERR, tc, 0, vnet_buffer (b)->tcp.ack_number); - - tc->snd_nxt = vnet_buffer (b)->tcp.ack_number; - if (seq_gt (tc->snd_nxt, tc->snd_una_max)) - tc->snd_una_max = tc->snd_nxt; - - goto process_ack; + return -1; } /* If old ACK, probably it's an old dupack */ @@ -1604,15 +1606,16 @@ tcp_rcv_ack (tcp_worker_ctx_t * wrk, 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); @@ -1631,6 +1634,9 @@ process_ack: 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); /* @@ -1639,7 +1645,7 @@ 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)) { *error = TCP_ERROR_ACK_OK; @@ -1654,7 +1660,7 @@ process_ack: /* * Update congestion control (slow start/congestion avoidance) */ - tcp_cc_update (tc, b); + tcp_cc_update (tc, &rs); *error = TCP_ERROR_ACK_OK; return 0; } @@ -1694,6 +1700,10 @@ 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); @@ -1765,13 +1775,7 @@ 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); @@ -1879,7 +1883,7 @@ tcp_session_enqueue_ooo (tcp_connection_t * tc, vlib_buffer_t * b, newest = svm_fifo_newest_ooo_segment (s0->rx_fifo); if (newest) { - offset = ooo_segment_offset (s0->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->rx_fifo, newest); @@ -1901,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 */ @@ -2534,9 +2538,9 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, * 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, my_thread_index, is_ip4); tcp_connection_cleanup (new_tc0); + error0 = TCP_ERROR_CREATE_SESSION_FAIL; goto drop; } @@ -2558,6 +2562,7 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, 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; } @@ -2728,24 +2733,24 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, 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_reset (tc0); + error0 = TCP_ERROR_SEGMENT_INVALID; + goto drop; + } + /* * If the segment acknowledgment is not acceptable, form a * reset segment, * * and send it. */ - if (!tcp_rcv_ack_is_acceptable (tc0, b0)) - { - tcp_connection_reset (tc0); - error0 = TCP_ERROR_ACK_INVALID; - goto drop; - } - - /* Make sure the ack is exactly right */ - if (tc0->rcv_nxt != vnet_buffer (b0)->tcp.seq_number || is_fin0) + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) { tcp_connection_reset (tc0); - error0 = TCP_ERROR_SEGMENT_INVALID; goto drop; } @@ -2795,16 +2800,30 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, 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_una_max) + else if (tc0->snd_una == tc0->snd_nxt) { - tcp_connection_set_state (tc0, TCP_STATE_FIN_WAIT_2); - /* 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. */ + * to send. */ tcp_connection_timers_reset (tc0); + + /* 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_connection_set_state (tc0, TCP_STATE_CLOSED); + tcp_timer_set (tc0, TCP_TIMER_WAITCLOSE, TCP_CLEANUP_TIME); + 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); /* Don't try to deq the FIN acked */ @@ -2818,7 +2837,7 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, /* 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 (wrk, tc0, b0, tcp0, &error0)) + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) goto drop; tc0->burst_acked = 0; break; @@ -2827,23 +2846,27 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0)) goto drop; - if (tc0->flags & TCP_CONN_FINPNDG) - { - /* TX fifo finally drained */ - if (!transport_max_tx_dequeue (&tc0->connection)) - { - 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); - } - } + if (!(tc0->flags & TCP_CONN_FINPNDG)) + break; + + /* Still have outstanding tx data */ + max_dequeue = transport_max_tx_dequeue (&tc0->connection); + if (max_dequeue > tc0->burst_acked) + break; + + 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 (wrk, tc0, b0, tcp0, &error0)) + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; + + if (tc0->snd_una != tc0->snd_nxt) goto drop; tcp_connection_timers_reset (tc0); @@ -2857,15 +2880,11 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, * acknowledgment of our FIN. If our FIN is now acknowledged, * delete the TCB, enter the CLOSED state, and return. */ - 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; + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; + /* Apparently our ACK for the peer's FIN was lost */ - if (is_fin0 && tc0->snd_una != tc0->snd_una_max) + if (is_fin0 && tc0->snd_una != tc0->snd_nxt) { tcp_send_fin (tc0); goto drop; @@ -2887,7 +2906,10 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, * retransmission of the remote FIN. Acknowledge it, and restart * the 2 MSL timeout. */ - if (tcp_rcv_ack (wrk, tc0, b0, tcp0, &error0)) + if (tcp_rcv_ack_no_cc (tc0, b0, &error0)) + goto drop; + + if (!is_fin0) goto drop; tcp_program_ack (wrk, tc0); @@ -2952,19 +2974,22 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, break; case TCP_STATE_FIN_WAIT_1: tc0->rcv_nxt += 1; - tcp_connection_set_state (tc0, TCP_STATE_CLOSING); + if (tc0->flags & TCP_CONN_FINPNDG) { - /* Drop all outstanding tx data. */ - session_tx_fifo_dequeue_drop (&tc0->connection, - transport_max_tx_dequeue - (&tc0->connection)); - tcp_send_fin (tc0); + /* 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); } else - tcp_program_ack (wrk, tc0); - /* Wait for ACK for our FIN but not forever */ - tcp_timer_update (tc0, TCP_TIMER_WAITCLOSE, TCP_2MSL_TIME); + { + 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 */ @@ -2997,6 +3022,7 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, 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; @@ -3174,16 +3200,16 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node, tcp_connection_init_vars (child0); child0->rto = TCP_RTO_MIN; - TCP_EVT_DBG (TCP_EVT_SYN_RCVD, child0, 1); if (session_stream_accept (&child0->connection, lc0->c_s_index, - 0 /* notify */ )) + lc0->c_thread_index, 0 /* notify */ )) { tcp_connection_cleanup (child0); error0 = TCP_ERROR_CREATE_SESSION_FAIL; goto drop; } + 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);