X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvnet%2Ftcp%2Ftcp_input.c;h=e4bd156391f8a07d1f915d630250917066b630ba;hb=2f04cb9f142abef82cd379432cecdafef9e776db;hp=ea64e4ab754186b9c12a9a6f9980e364313b8875;hpb=c31dc31f84961033ecb6354811e0c360b6cf5f79;p=vpp.git diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index ea64e4ab754..e4bd156391f 100755 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -136,7 +136,7 @@ tcp_options_parse (tcp_header_t * th, tcp_options_t * to, u8 is_syn) /* Zero out all flags but those set in SYN */ to->flags &= (TCP_OPTS_FLAG_SACK_PERMITTED | TCP_OPTS_FLAG_WSCALE - | TCP_OPTS_FLAG_TSTAMP | TCP_OPTION_MSS); + | TCP_OPTS_FLAG_TSTAMP | TCP_OPTS_FLAG_MSS); for (; opts_len > 0; opts_len -= opt_len, data += opt_len) { @@ -503,7 +503,8 @@ tcp_update_rtt (tcp_connection_t * tc, tcp_rate_sample_t * rs, u32 ack) if (tcp_in_cong_recovery (tc)) { /* Accept rtt estimates for samples that have not been retransmitted */ - if ((tc->flags & TCP_CONN_RATE_SAMPLE) && !(rs->flags & TCP_BTS_IS_RXT)) + if ((tc->cfg_flags & TCP_CFG_F_RATE_SAMPLE) + && !(rs->flags & TCP_BTS_IS_RXT)) { mrtt = rs->rtt_time * THZ; goto estimate_rtt; @@ -574,15 +575,6 @@ tcp_estimate_initial_rtt (tcp_connection_t * tc) tcp_update_rto (tc); } -always_inline u8 -tcp_recovery_no_snd_space (tcp_connection_t * tc) -{ - return (tcp_in_fastrecovery (tc) - && tcp_fastrecovery_prr_snd_space (tc) < tc->snd_mss) - || (tcp_in_recovery (tc) - && tcp_available_output_snd_space (tc) < tc->snd_mss); -} - /** * Dequeue bytes for connections that have received acks in last burst */ @@ -603,35 +595,27 @@ tcp_handle_postponed_dequeues (tcp_worker_ctx_t * wrk) tc = tcp_connection_get (pending_deq_acked[i], thread_index); tc->flags &= ~TCP_CONN_DEQ_PENDING; - if (tc->burst_acked) - { - /* 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 (PREDICT_FALSE (!tc->burst_acked)) + continue; - /* If everything has been acked, stop retransmit timer - * otherwise update. */ - tcp_retransmit_timer_update (tc); + /* Dequeue the newly ACKed bytes */ + session_tx_fifo_dequeue_drop (&tc->connection, tc->burst_acked); + tcp_validate_txf_size (tc, tc->snd_una_max - tc->snd_una); - /* Update pacer based on our new cwnd estimate */ - tcp_connection_tx_pacer_update (tc); + if (PREDICT_FALSE (tc->flags & TCP_CONN_PSH_PENDING)) + { + if (seq_leq (tc->psh_seq, tc->snd_una)) + tc->flags &= ~TCP_CONN_PSH_PENDING; } - /* Reset the pacer if we've been idle, i.e., no data sent or if - * we're in recovery and snd space constrained */ - if (tc->data_segs_out == tc->prev_dsegs_out - || tcp_recovery_no_snd_space (tc)) - transport_connection_tx_pacer_reset_bucket (&tc->connection, - wrk->vm->clib_time. - last_cpu_time); - tc->prev_dsegs_out = tc->data_segs_out; + /* If everything has been acked, stop retransmit timer + * otherwise update. */ + tcp_retransmit_timer_update (tc); + + /* Update pacer based on our new cwnd estimate */ + tcp_connection_tx_pacer_update (tc); + + tc->burst_acked = 0; } _vec_len (wrk->pending_deq_acked) = 0; } @@ -889,6 +873,12 @@ scoreboard_next_rxt_hole (sack_scoreboard_t * sb, /* Rule (3): if hole not lost */ else if (seq_lt (hole->start, sb->high_sacked)) { + /* And we didn't already retransmit it */ + if (seq_leq (hole->end, sb->high_rxt)) + { + sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX; + return 0; + } *snd_limited = 0; sb->cur_rxt_hole = scoreboard_hole_index (sb, hole); } @@ -944,8 +934,6 @@ scoreboard_clear (sack_scoreboard_t * sb) sb->sacked_bytes = 0; sb->last_sacked_bytes = 0; sb->last_bytes_delivered = 0; - sb->high_sacked = 0; - sb->high_rxt = 0; sb->lost_bytes = 0; sb->last_lost_bytes = 0; sb->cur_rxt_hole = TCP_INVALID_SACK_HOLE_INDEX; @@ -1345,28 +1333,25 @@ tcp_cc_recover (tcp_connection_t * tc) is_spurious = 1; } - tc->rcv_dupacks = 0; - tc->prr_delivered = 0; - tc->rxt_delivered = 0; - tc->snd_rxt_bytes = 0; - tc->snd_rxt_ts = 0; - tc->rtt_ts = 0; - tc->flags &= ~TCP_CONN_RXT_PENDING; - tcp_connection_tx_pacer_reset (tc, tc->cwnd, 0 /* start bucket */ ); + tc->rcv_dupacks = 0; /* Previous recovery left us congested. Continue sending as part * of the current recovery event with an updated snd_congestion */ if (tc->sack_sb.sacked_bytes) { tc->snd_congestion = tc->snd_nxt; - tc->snd_rxt_ts = tcp_tstamp (tc); - tc->prr_start = tc->snd_una; - scoreboard_init_rxt (&tc->sack_sb, tc->snd_una); tcp_program_retransmit (tc); return is_spurious; } + tc->rxt_delivered = 0; + tc->snd_rxt_bytes = 0; + tc->snd_rxt_ts = 0; + tc->prr_delivered = 0; + tc->rtt_ts = 0; + tc->flags &= ~TCP_CONN_RXT_PENDING; + hole = scoreboard_first_hole (&tc->sack_sb); if (hole && hole->start == tc->snd_una && hole->end == tc->snd_nxt) scoreboard_clear (&tc->sack_sb); @@ -1415,6 +1400,10 @@ tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, { u8 has_sack = tcp_opts_sack_permitted (&tc->rcv_opts); + /* If reneging, wait for timer based retransmits */ + if (PREDICT_FALSE (tcp_is_lost_fin (tc) || tc->sack_sb.is_reneging)) + return; + /* * If not in recovery, figure out if we should enter */ @@ -1441,29 +1430,18 @@ tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, } /* - * Already in recovery. See if we can exit and stop retransmitting + * Already in recovery */ - if (seq_geq (tc->snd_una, tc->snd_congestion)) - { - /* If spurious return, we've already updated everything */ - if (tcp_cc_recover (tc)) - { - tc->tsecr_last_ack = tc->rcv_opts.tsecr; - return; - } - - /* Treat as congestion avoidance ack */ - tcp_cc_rcv_ack (tc, rs); - return; - } - /* * Process (re)transmit feedback. Output path uses this to decide how much * more data to release into the network */ if (has_sack) { + if (!tc->bytes_acked && tc->sack_sb.rxt_sacked) + tcp_fastrecovery_first_on (tc); + tc->rxt_delivered += tc->sack_sb.rxt_sacked; tc->prr_delivered += tc->bytes_acked + tc->sack_sb.last_sacked_bytes - tc->sack_sb.last_bytes_delivered; @@ -1480,9 +1458,12 @@ tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, tc->rxt_delivered = clib_max (tc->rxt_delivered + tc->bytes_acked, tc->snd_rxt_bytes); if (is_dack) - tc->prr_delivered += 1; + tc->prr_delivered += clib_min (tc->snd_mss, + tc->snd_nxt - tc->snd_una); else - tc->prr_delivered += tc->bytes_acked - tc->snd_mss * tc->rcv_dupacks; + tc->prr_delivered += tc->bytes_acked - clib_min (tc->bytes_acked, + tc->snd_mss * + tc->rcv_dupacks); /* If partial ack, assume that the first un-acked segment was lost */ if (tc->bytes_acked || tc->rcv_dupacks == TCP_DUPACK_THRESHOLD) @@ -1491,6 +1472,23 @@ tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, tcp_program_retransmit (tc); } + /* + * See if we can exit and stop retransmitting + */ + if (seq_geq (tc->snd_una, tc->snd_congestion)) + { + /* If spurious return, we've already updated everything */ + if (tcp_cc_recover (tc)) + { + tc->tsecr_last_ack = tc->rcv_opts.tsecr; + return; + } + + /* Treat as congestion avoidance ack */ + tcp_cc_rcv_ack (tc, rs); + return; + } + /* * Notify cc of the event */ @@ -1511,6 +1509,24 @@ tcp_cc_handle_event (tcp_connection_t * tc, tcp_rate_sample_t * rs, tcp_cc_rcv_cong_ack (tc, TCP_CC_PARTIALACK, rs); } +static void +tcp_handle_old_ack (tcp_connection_t * tc, vlib_buffer_t * b, + tcp_rate_sample_t * rs) +{ + if (!tcp_in_cong_recovery (tc)) + return; + + if (tcp_opts_sack_permitted (&tc->rcv_opts)) + tcp_rcv_sacks (tc, vnet_buffer (b)->tcp.ack_number); + + tc->bytes_acked = 0; + + if (tc->cfg_flags & TCP_CFG_F_RATE_SAMPLE) + tcp_bt_sample_delivery_rate (tc, rs); + + tcp_cc_handle_event (tc, rs, 1); +} + /** * Check if duplicate ack as per RFC5681 Sec. 2 */ @@ -1536,10 +1552,6 @@ tcp_ack_is_cc_event (tcp_connection_t * tc, vlib_buffer_t * b, *is_dack = tc->sack_sb.last_sacked_bytes || tcp_ack_is_dupack (tc, b, prev_snd_wnd, prev_snd_una); - /* If reneging, wait for timer based retransmits */ - if (PREDICT_FALSE (tcp_is_lost_fin (tc) || tc->sack_sb.is_reneging)) - return 0; - return (*is_dack || tcp_in_cong_recovery (tc)); } @@ -1580,8 +1592,12 @@ tcp_rcv_ack (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b, tc->errors.below_ack_wnd += 1; *error = TCP_ERROR_ACK_OLD; TCP_EVT (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, 0, 1); + + if (seq_lt (vnet_buffer (b)->tcp.ack_number, tc->snd_una - tc->rcv_wnd)) + return -1; + + tcp_handle_old_ack (tc, b, &rs); + /* Don't drop yet */ return 0; } @@ -1604,13 +1620,14 @@ process_ack: tc->snd_una = vnet_buffer (b)->tcp.ack_number; tcp_validate_txf_size (tc, tc->bytes_acked); - if (tc->flags & TCP_CONN_RATE_SAMPLE) + if (tc->cfg_flags & TCP_CFG_F_RATE_SAMPLE) tcp_bt_sample_delivery_rate (tc, &rs); - tcp_program_dequeue (wrk, tc); - if (tc->bytes_acked) - tcp_update_rtt (tc, &rs, vnet_buffer (b)->tcp.ack_number); + { + tcp_program_dequeue (wrk, tc); + tcp_update_rtt (tc, &rs, vnet_buffer (b)->tcp.ack_number); + } TCP_EVT (TCP_EVT_ACK_RCVD, tc); @@ -1682,6 +1699,7 @@ tcp_rcv_fin (tcp_worker_ctx_t * wrk, tcp_connection_t * tc, vlib_buffer_t * b, /* Account for the FIN and send ack */ tc->rcv_nxt += 1; + tc->flags |= TCP_CONN_FINRCVD; tcp_program_ack (tc); /* Enter CLOSE-WAIT and notify session. To avoid lingering * in CLOSE-WAIT, set timer (reuse WAITCLOSE). */ @@ -2257,7 +2275,8 @@ VLIB_REGISTER_NODE (tcp6_established_node) = static u8 -tcp_lookup_is_valid (tcp_connection_t * tc, tcp_header_t * hdr) +tcp_lookup_is_valid (tcp_connection_t * tc, vlib_buffer_t * b, + tcp_header_t * hdr) { transport_connection_t *tmp = 0; u64 handle; @@ -2269,9 +2288,36 @@ tcp_lookup_is_valid (tcp_connection_t * tc, tcp_header_t * hdr) if (tc->c_lcl_port == 0 && tc->state == TCP_STATE_LISTEN) return 1; + u8 is_ip_valid = 0, val_l, val_r; + + if (tc->connection.is_ip4) + { + ip4_header_t *ip4_hdr = (ip4_header_t *) vlib_buffer_get_current (b); + + val_l = !ip4_address_compare (&ip4_hdr->dst_address, + &tc->connection.lcl_ip.ip4); + val_l = val_l || ip_is_zero (&tc->connection.lcl_ip, 1); + val_r = !ip4_address_compare (&ip4_hdr->src_address, + &tc->connection.rmt_ip.ip4); + val_r = val_r || tc->state == TCP_STATE_LISTEN; + is_ip_valid = val_l && val_r; + } + else + { + ip6_header_t *ip6_hdr = (ip6_header_t *) vlib_buffer_get_current (b); + + val_l = !ip6_address_compare (&ip6_hdr->dst_address, + &tc->connection.lcl_ip.ip6); + val_l = val_l || ip_is_zero (&tc->connection.lcl_ip, 0); + val_r = !ip6_address_compare (&ip6_hdr->src_address, + &tc->connection.rmt_ip.ip6); + val_r = val_r || tc->state == TCP_STATE_LISTEN; + is_ip_valid = val_l && val_r; + } + u8 is_valid = (tc->c_lcl_port == hdr->dst_port && (tc->state == TCP_STATE_LISTEN - || tc->c_rmt_port == hdr->src_port)); + || tc->c_rmt_port == hdr->src_port) && is_ip_valid); if (!is_valid) { @@ -2285,6 +2331,7 @@ tcp_lookup_is_valid (tcp_connection_t * tc, tcp_header_t * hdr) && tmp->rmt_port == hdr->src_port) { TCP_DBG ("half-open is valid!"); + is_valid = 1; } } } @@ -2315,7 +2362,7 @@ tcp_lookup_connection (u32 fib_index, vlib_buffer_t * b, u8 thread_index, TRANSPORT_PROTO_TCP, thread_index, &is_filtered); tc = tcp_get_connection_from_transport (tconn); - ASSERT (tcp_lookup_is_valid (tc, tcp)); + ASSERT (tcp_lookup_is_valid (tc, b, tcp)); } else { @@ -2330,11 +2377,41 @@ tcp_lookup_connection (u32 fib_index, vlib_buffer_t * b, u8 thread_index, TRANSPORT_PROTO_TCP, thread_index, &is_filtered); tc = tcp_get_connection_from_transport (tconn); - ASSERT (tcp_lookup_is_valid (tc, tcp)); + ASSERT (tcp_lookup_is_valid (tc, b, tcp)); } return tc; } +static tcp_connection_t * +tcp_lookup_listener (vlib_buffer_t * b, u32 fib_index, int is_ip4) +{ + session_t *s; + + if (is_ip4) + { + ip4_header_t *ip4 = vlib_buffer_get_current (b); + tcp_header_t *tcp = tcp_buffer_hdr (b); + s = session_lookup_listener4 (fib_index, + &ip4->dst_address, + tcp->dst_port, TRANSPORT_PROTO_TCP, 1); + } + else + { + ip6_header_t *ip6 = vlib_buffer_get_current (b); + tcp_header_t *tcp = tcp_buffer_hdr (b); + s = session_lookup_listener6 (fib_index, + &ip6->dst_address, + tcp->dst_port, TRANSPORT_PROTO_TCP, 1); + + } + if (PREDICT_TRUE (s != 0)) + return tcp_get_connection_from_transport (transport_get_listener + (TRANSPORT_PROTO_TCP, + s->connection_index)); + else + return 0; +} + always_inline void tcp_check_tx_offload (tcp_connection_t * tc, int is_ipv4) { @@ -2361,11 +2438,10 @@ tcp_check_tx_offload (tcp_connection_t * tc, int is_ipv4) sw_if_idx = dpo->dpoi_index; hw_if = vnet_get_sup_hw_interface (vnm, sw_if_idx); - tc->is_tso = - ((hw_if->flags & VNET_HW_INTERFACE_FLAG_SUPPORTS_GSO) == 0) ? 0 : 1; + if (hw_if->flags & VNET_HW_INTERFACE_FLAG_SUPPORTS_GSO) + tc->cfg_flags |= TCP_CFG_F_TSO; } - always_inline uword tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * from_frame, int is_ip4) @@ -2582,7 +2658,8 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, goto drop; } - tcp_check_tx_offload (new_tc0, is_ip4); + if (!(new_tc0->cfg_flags & TCP_CFG_F_NO_TSO)) + tcp_check_tx_offload (new_tc0, is_ip4); /* Read data, if any */ if (PREDICT_FALSE (vnet_buffer (b0)->tcp.data_len)) @@ -2594,7 +2671,9 @@ tcp46_syn_sent_inline (vlib_main_t * vm, vlib_node_runtime_t * node, } else { - tcp_program_ack (new_tc0); + /* Send ack now instead of programming it because connection was + * just established and it's not optional. */ + tcp_send_ack (new_tc0); } drop: @@ -2710,14 +2789,17 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, 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) + if (!(tc0->connection.flags & TRANSPORT_CONNECTION_F_NO_LOOKUP)) { - if (tc0->state != TCP_STATE_CLOSED) - clib_warning ("state changed"); - goto drop; + tcp_connection_t *tmp; + tmp = tcp_lookup_connection (tc0->c_fib_index, b0, thread_index, + is_ip4); + if (tmp->state != tc0->state) + { + if (tc0->state != TCP_STATE_CLOSED) + clib_warning ("state changed"); + goto drop; + } } } @@ -2771,7 +2853,8 @@ tcp46_rcv_process_inline (vlib_main_t * vm, vlib_node_runtime_t * node, tc0->state = TCP_STATE_ESTABLISHED; TCP_EVT (TCP_EVT_STATE_CHANGE, tc0); - tcp_check_tx_offload (tc0, is_ip4); + if (!(tc0->cfg_flags & TCP_CFG_F_NO_TSO)) + tcp_check_tx_offload (tc0, is_ip4); /* Initialize session variables */ tc0->snd_una = vnet_buffer (b0)->tcp.ack_number; @@ -3107,6 +3190,7 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node, { u32 n_left_from, *from, n_syns = 0, *first_buffer; u32 my_thread_index = vm->thread_index; + tcp_connection_t *tc0; from = first_buffer = vlib_frame_vector_args (from_frame); n_left_from = from_frame->n_vectors; @@ -3129,16 +3213,29 @@ tcp46_listen_inline (vlib_main_t * vm, vlib_node_runtime_t * node, b0 = vlib_get_buffer (vm, bi0); lc0 = tcp_listener_get (vnet_buffer (b0)->tcp.connection_index); + if (PREDICT_FALSE (lc0 == 0)) + { + tc0 = tcp_connection_get (vnet_buffer (b0)->tcp.connection_index, + my_thread_index); + if (tc0->state != TCP_STATE_TIME_WAIT) + { + error0 = TCP_ERROR_CREATE_EXISTS; + goto drop; + } + lc0 = tcp_lookup_listener (b0, tc0->c_fib_index, is_ip4); + /* clean up the old session */ + tcp_connection_del (tc0); + } if (is_ip4) { ip40 = vlib_buffer_get_current (b0); - th0 = ip4_next_header (ip40); + th0 = tcp_buffer_hdr (b0); } else { ip60 = vlib_buffer_get_current (b0); - th0 = ip6_next_header (ip60); + th0 = tcp_buffer_hdr (b0); } /* Create child session. For syn-flood protection use filter */ @@ -3537,8 +3634,8 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, if (PREDICT_TRUE (!tc0 + !tc1 == 0)) { - ASSERT (tcp_lookup_is_valid (tc0, tcp_buffer_hdr (b[0]))); - ASSERT (tcp_lookup_is_valid (tc1, tcp_buffer_hdr (b[1]))); + ASSERT (tcp_lookup_is_valid (tc0, b[0], tcp_buffer_hdr (b[0]))); + ASSERT (tcp_lookup_is_valid (tc1, b[1], tcp_buffer_hdr (b[1]))); vnet_buffer (b[0])->tcp.connection_index = tc0->c_c_index; vnet_buffer (b[1])->tcp.connection_index = tc1->c_c_index; @@ -3550,7 +3647,7 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, { if (PREDICT_TRUE (tc0 != 0)) { - ASSERT (tcp_lookup_is_valid (tc0, tcp_buffer_hdr (b[0]))); + ASSERT (tcp_lookup_is_valid (tc0, b[0], tcp_buffer_hdr (b[0]))); vnet_buffer (b[0])->tcp.connection_index = tc0->c_c_index; tcp_input_dispatch_buffer (tm, tc0, b[0], &next[0], &error0); } @@ -3559,7 +3656,7 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, if (PREDICT_TRUE (tc1 != 0)) { - ASSERT (tcp_lookup_is_valid (tc1, tcp_buffer_hdr (b[1]))); + ASSERT (tcp_lookup_is_valid (tc1, b[1], tcp_buffer_hdr (b[1]))); vnet_buffer (b[1])->tcp.connection_index = tc1->c_c_index; tcp_input_dispatch_buffer (tm, tc1, b[1], &next[1], &error1); } @@ -3587,7 +3684,7 @@ tcp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, is_nolookup); if (PREDICT_TRUE (tc0 != 0)) { - ASSERT (tcp_lookup_is_valid (tc0, tcp_buffer_hdr (b[0]))); + ASSERT (tcp_lookup_is_valid (tc0, b[0], tcp_buffer_hdr (b[0]))); vnet_buffer (b[0])->tcp.connection_index = tc0->c_c_index; tcp_input_dispatch_buffer (tm, tc0, b[0], &next[0], &error0); } @@ -3926,7 +4023,7 @@ do { \ 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_SYN, TCP_INPUT_NEXT_LISTEN, 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);