/** Segment arrived in state where segments are processed in sequence order. * * Queue segment in incoming segments queue for processing. * * @param conn Connection * @param seg Segment */ static void tcp_conn_sa_queue(tcp_conn_t *conn, tcp_segment_t *seg) { tcp_segment_t *pseg; log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_seq(%p, %p)", conn, seg); /* Discard unacceptable segments ("old duplicates") */ if (!seq_no_segment_acceptable(conn, seg)) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Replying ACK to unacceptable segment."); tcp_tqueue_ctrl_seg(conn, CTL_ACK); tcp_segment_delete(seg); return; } /* Queue for processing */ tcp_iqueue_insert_seg(&conn->incoming, seg); /* * Process all segments from incoming queue that are ready. * Unacceptable segments are discarded by tcp_iqueue_get_ready_seg(). * * XXX Need to return ACK for unacceptable segments */ while (tcp_iqueue_get_ready_seg(&conn->incoming, &pseg) == EOK) tcp_conn_seg_process(conn, pseg); }
/** Bounce segment directy into receive queue without constructing the PDU. * * This is for testing purposes only. * * @param sp Socket pair, oriented for transmission * @param seg Segment */ void tcp_rqueue_bounce_seg(tcp_sockpair_t *sp, tcp_segment_t *seg) { tcp_sockpair_t rident; log_msg(LVL_DEBUG, "tcp_rqueue_bounce_seg()"); #ifdef BOUNCE_TRANSCODE tcp_pdu_t *pdu; tcp_segment_t *dseg; if (tcp_pdu_encode(sp, seg, &pdu) != EOK) { log_msg(LVL_WARN, "Not enough memory. Segment dropped."); return; } if (tcp_pdu_decode(pdu, &rident, &dseg) != EOK) { log_msg(LVL_WARN, "Not enough memory. Segment dropped."); return; } tcp_pdu_delete(pdu); /** Insert decoded segment into rqueue */ tcp_rqueue_insert_seg(&rident, dseg); tcp_segment_delete(seg); #else /* Reverse the identification */ tcp_sockpair_flipped(sp, &rident); /* Insert segment back into rqueue */ tcp_rqueue_insert_seg(&rident, seg); #endif }
/** Segment arrived in Listen state. * * @param conn Connection * @param seg Segment */ static void tcp_conn_sa_listen(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_listen(%p, %p)", conn, seg); if ((seg->ctrl & CTL_RST) != 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Ignoring incoming RST."); return; } if ((seg->ctrl & CTL_ACK) != 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Incoming ACK, send acceptable RST."); tcp_reply_rst(&conn->ident, seg); return; } if ((seg->ctrl & CTL_SYN) == 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "SYN not present. Ignoring segment."); return; } log_msg(LOG_DEFAULT, LVL_DEBUG, "Got SYN, sending SYN, ACK."); conn->rcv_nxt = seg->seq + 1; conn->irs = seg->seq; log_msg(LOG_DEFAULT, LVL_DEBUG, "rcv_nxt=%u", conn->rcv_nxt); if (seg->len > 1) log_msg(LOG_DEFAULT, LVL_WARN, "SYN combined with data, ignoring data."); /* XXX select ISS */ conn->iss = 1; conn->snd_nxt = conn->iss; conn->snd_una = conn->iss; /* * Surprisingly the spec does not deal with initial window setting. * Set SND.WND = SEG.WND and set SND.WL1 so that next segment * will always be accepted as new window setting. */ conn->snd_wnd = seg->wnd; conn->snd_wl1 = seg->seq; conn->snd_wl2 = seg->seq; tcp_conn_state_set(conn, st_syn_received); tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */); tcp_segment_delete(seg); }
/** Get next ready segment from incoming queue. * * Return the segment with the earliest sequence number if it is ready. * A segment is ready if its SEG.SEQ is earlier or equal to RCV.NXT. * * @param iqueue Incoming queue * @param seg Place to store pointer to segment * @return EOK on success, ENOENT if no segment is ready */ int tcp_iqueue_get_ready_seg(tcp_iqueue_t *iqueue, tcp_segment_t **seg) { tcp_iqueue_entry_t *iqe; link_t *link; log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_get_ready_seg()"); link = list_first(&iqueue->list); if (link == NULL) { log_msg(LOG_DEFAULT, LVL_DEBUG, "iqueue is empty"); return ENOENT; } iqe = list_get_instance(link, tcp_iqueue_entry_t, link); while (!seq_no_segment_acceptable(iqueue->conn, iqe->seg)) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Skipping unacceptable segment (RCV.NXT=%" PRIu32 ", RCV.NXT+RCV.WND=%" PRIu32 ", SEG.SEQ=%" PRIu32 ", SEG.LEN=%" PRIu32 ")", iqueue->conn->rcv_nxt, iqueue->conn->rcv_nxt + iqueue->conn->rcv_wnd, iqe->seg->seq, iqe->seg->len); list_remove(&iqe->link); tcp_segment_delete(iqe->seg); link = list_first(&iqueue->list); if (link == NULL) { log_msg(LOG_DEFAULT, LVL_DEBUG, "iqueue is empty"); return ENOENT; } iqe = list_get_instance(link, tcp_iqueue_entry_t, link); } /* Do not return segments that are not ready for processing */ if (!seq_no_segment_ready(iqueue->conn, iqe->seg)) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Next segment not ready: SEG.SEQ=%u, " "RCV.NXT=%u, SEG.LEN=%u", iqe->seg->seq, iqueue->conn->rcv_nxt, iqe->seg->len); return ENOENT; } log_msg(LOG_DEFAULT, LVL_DEBUG, "Returning ready segment %p", iqe->seg); list_remove(&iqe->link); *seg = iqe->seg; free(iqe); return EOK; }
/** Process incoming segment. * * We are in connection state where segments are processed in order * of sequence number. This processes one segment taken from the * connection incoming segments queue. * * @param conn Connection * @param seg Segment */ static void tcp_conn_seg_process(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_seg_process(%p, %p)", conn, seg); tcp_segment_dump(seg); /* Check whether segment is acceptable */ /* XXX Permit valid ACKs, URGs and RSTs */ /* if (!seq_no_segment_acceptable(conn, seg)) { log_msg(LOG_DEFAULT, LVL_WARN, "Segment not acceptable, dropping."); if ((seg->ctrl & CTL_RST) == 0) { tcp_tqueue_ctrl_seg(conn, CTL_ACK); } return; } */ if (tcp_conn_seg_proc_rst(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_sp(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_syn(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_ack(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_urg(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_text(conn, seg) == cp_done) return; if (tcp_conn_seg_proc_fin(conn, seg) == cp_done) return; /* * If anything is left from the segment, insert it back into the * incoming segments queue. */ if (seg->len > 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "Re-insert segment %p. seg->len=%zu", seg, (size_t) seg->len); tcp_iqueue_insert_seg(&conn->incoming, seg); } else { tcp_segment_delete(seg); } }
/** Process segment ACK field in Established state. * * @param conn Connection * @param seg Segment * @return cp_done if we are done with this segment, cp_continue * if not */ static cproc_t tcp_conn_seg_proc_ack_est(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_seg_proc_ack_est(%p, %p)", conn, seg); log_msg(LOG_DEFAULT, LVL_DEBUG, "SEG.ACK=%u, SND.UNA=%u, SND.NXT=%u", (unsigned)seg->ack, (unsigned)conn->snd_una, (unsigned)conn->snd_nxt); if (!seq_no_ack_acceptable(conn, seg->ack)) { log_msg(LOG_DEFAULT, LVL_DEBUG, "ACK not acceptable."); if (!seq_no_ack_duplicate(conn, seg->ack)) { log_msg(LOG_DEFAULT, LVL_WARN, "Not acceptable, not duplicate. " "Send ACK and drop."); /* Not acceptable, not duplicate. Send ACK and drop. */ tcp_tqueue_ctrl_seg(conn, CTL_ACK); tcp_segment_delete(seg); return cp_done; } else { log_msg(LOG_DEFAULT, LVL_DEBUG, "Ignoring duplicate ACK."); } } else { /* Update SND.UNA */ conn->snd_una = seg->ack; } if (seq_no_new_wnd_update(conn, seg)) { conn->snd_wnd = seg->wnd; conn->snd_wl1 = seg->seq; conn->snd_wl2 = seg->ack; log_msg(LOG_DEFAULT, LVL_DEBUG, "Updating send window, SND.WND=%" PRIu32 ", SND.WL1=%" PRIu32 ", SND.WL2=%" PRIu32, conn->snd_wnd, conn->snd_wl1, conn->snd_wl2); } /* * Prune acked segments from retransmission queue and * possibly transmit more data. */ tcp_tqueue_ack_received(conn); return cp_continue; }
/** Process segment ACK field in Syn-Received state. * * @param conn Connection * @param seg Segment * @return cp_done if we are done with this segment, cp_continue * if not */ static cproc_t tcp_conn_seg_proc_ack_sr(tcp_conn_t *conn, tcp_segment_t *seg) { if (!seq_no_ack_acceptable(conn, seg->ack)) { /* ACK is not acceptable, send RST. */ log_msg(LOG_DEFAULT, LVL_WARN, "Segment ACK not acceptable, sending RST."); tcp_reply_rst(&conn->ident, seg); tcp_segment_delete(seg); return cp_done; } log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: SYN ACKed -> Established", conn->name); tcp_conn_state_set(conn, st_established); /* XXX Not mentioned in spec?! */ conn->snd_una = seg->ack; return cp_continue; }
/** Process segment ACK field. * * @param conn Connection * @param seg Segment * @return cp_done if we are done with this segment, cp_continue * if not */ static cproc_t tcp_conn_seg_proc_ack(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_ack(%p, %p)", conn->name, conn, seg); if ((seg->ctrl & CTL_ACK) == 0) { log_msg(LOG_DEFAULT, LVL_WARN, "Segment has no ACK. Dropping."); tcp_segment_delete(seg); return cp_done; } switch (conn->cstate) { case st_syn_received: return tcp_conn_seg_proc_ack_sr(conn, seg); case st_established: return tcp_conn_seg_proc_ack_est(conn, seg); case st_fin_wait_1: return tcp_conn_seg_proc_ack_fw1(conn, seg); case st_fin_wait_2: return tcp_conn_seg_proc_ack_fw2(conn, seg); case st_close_wait: return tcp_conn_seg_proc_ack_cw(conn, seg); case st_closing: return tcp_conn_seg_proc_ack_cls(conn, seg); case st_last_ack: return tcp_conn_seg_proc_ack_la(conn, seg); case st_time_wait: return tcp_conn_seg_proc_ack_tw(conn, seg); case st_listen: case st_syn_sent: case st_closed: assert(false); } assert(false); }
/** Process segment text. * * @param conn Connection * @param seg Segment * @return cp_done if we are done with this segment, cp_continue * if not */ static cproc_t tcp_conn_seg_proc_text(tcp_conn_t *conn, tcp_segment_t *seg) { size_t text_size; size_t xfer_size; log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_text(%p, %p)", conn->name, conn, seg); switch (conn->cstate) { case st_established: case st_fin_wait_1: case st_fin_wait_2: /* OK */ break; case st_close_wait: case st_closing: case st_last_ack: case st_time_wait: /* Invalid since FIN has been received. Ignore text. */ return cp_continue; case st_listen: case st_syn_sent: case st_syn_received: case st_closed: assert(false); } /* * Process segment text */ assert(seq_no_segment_ready(conn, seg)); /* Trim anything outside our receive window */ tcp_conn_trim_seg_to_wnd(conn, seg); /* Determine how many bytes to copy */ text_size = tcp_segment_text_size(seg); xfer_size = min(text_size, conn->rcv_buf_size - conn->rcv_buf_used); /* Copy data to receive buffer */ tcp_segment_text_copy(seg, conn->rcv_buf + conn->rcv_buf_used, xfer_size); conn->rcv_buf_used += xfer_size; /* Signal to the receive function that new data has arrived */ if (xfer_size > 0) { fibril_condvar_broadcast(&conn->rcv_buf_cv); if (conn->cb != NULL && conn->cb->recv_data != NULL) conn->cb->recv_data(conn, conn->cb_arg); } log_msg(LOG_DEFAULT, LVL_DEBUG, "Received %zu bytes of data.", xfer_size); /* Advance RCV.NXT */ conn->rcv_nxt += xfer_size; /* Update receive window. XXX Not an efficient strategy. */ conn->rcv_wnd -= xfer_size; /* Send ACK */ if (xfer_size > 0) tcp_tqueue_ctrl_seg(conn, CTL_ACK); if (xfer_size < seg->len) { /* Trim part of segment which we just received */ tcp_conn_trim_seg_to_wnd(conn, seg); } else { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: Nothing left in segment, dropping " "(xfer_size=%zu, SEG.LEN=%" PRIu32 ", seg->ctrl=%u)", conn->name, xfer_size, seg->len, (unsigned int) seg->ctrl); /* Nothing left in segment */ tcp_segment_delete(seg); return cp_done; } return cp_continue; }
/** Segment arrived in Syn-Sent state. * * @param conn Connection * @param seg Segment */ static void tcp_conn_sa_syn_sent(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_conn_sa_syn_sent(%p, %p)", conn, seg); if ((seg->ctrl & CTL_ACK) != 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "snd_una=%u, seg.ack=%u, snd_nxt=%u", conn->snd_una, seg->ack, conn->snd_nxt); if (!seq_no_ack_acceptable(conn, seg->ack)) { if ((seg->ctrl & CTL_RST) == 0) { log_msg(LOG_DEFAULT, LVL_WARN, "ACK not acceptable, send RST"); tcp_reply_rst(&conn->ident, seg); } else { log_msg(LOG_DEFAULT, LVL_WARN, "RST,ACK not acceptable, drop"); } return; } } if ((seg->ctrl & CTL_RST) != 0) { /* If we get here, we have either an acceptable ACK or no ACK */ if ((seg->ctrl & CTL_ACK) != 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: Connection reset. -> Closed", conn->name); /* Reset connection */ tcp_conn_reset(conn); return; } else { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: RST without ACK, drop", conn->name); return; } } /* XXX precedence */ if ((seg->ctrl & CTL_SYN) == 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, "No SYN bit, ignoring segment."); return; } conn->rcv_nxt = seg->seq + 1; conn->irs = seg->seq; if ((seg->ctrl & CTL_ACK) != 0) { conn->snd_una = seg->ack; /* * Prune acked segments from retransmission queue and * possibly transmit more data. */ tcp_tqueue_ack_received(conn); } log_msg(LOG_DEFAULT, LVL_DEBUG, "Sent SYN, got SYN."); /* * Surprisingly the spec does not deal with initial window setting. * Set SND.WND = SEG.WND and set SND.WL1 so that next segment * will always be accepted as new window setting. */ log_msg(LOG_DEFAULT, LVL_DEBUG, "SND.WND := %" PRIu32 ", SND.WL1 := %" PRIu32 ", " "SND.WL2 = %" PRIu32, seg->wnd, seg->seq, seg->seq); conn->snd_wnd = seg->wnd; conn->snd_wl1 = seg->seq; conn->snd_wl2 = seg->seq; if (seq_no_syn_acked(conn)) { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: syn acked -> Established", conn->name); tcp_conn_state_set(conn, st_established); tcp_tqueue_ctrl_seg(conn, CTL_ACK /* XXX */); } else { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: syn not acked -> Syn-Received", conn->name); tcp_conn_state_set(conn, st_syn_received); tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */); } tcp_segment_delete(seg); }
/** Process segment FIN field. * * @param conn Connection * @param seg Segment * @return cp_done if we are done with this segment, cp_continue * if not */ static cproc_t tcp_conn_seg_proc_fin(tcp_conn_t *conn, tcp_segment_t *seg) { log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_conn_seg_proc_fin(%p, %p)", conn->name, conn, seg); log_msg(LOG_DEFAULT, LVL_DEBUG, " seg->len=%zu, seg->ctl=%u", (size_t) seg->len, (unsigned) seg->ctrl); /* Only process FIN if no text is left in segment. */ if (tcp_segment_text_size(seg) == 0 && (seg->ctrl & CTL_FIN) != 0) { log_msg(LOG_DEFAULT, LVL_DEBUG, " - FIN found in segment."); /* Send ACK */ tcp_tqueue_ctrl_seg(conn, CTL_ACK); conn->rcv_nxt++; conn->rcv_wnd--; /* Change connection state */ switch (conn->cstate) { case st_listen: case st_syn_sent: case st_closed: /* Connection not synchronized */ assert(false); case st_syn_received: case st_established: log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Close-Wait", conn->name); tcp_conn_state_set(conn, st_close_wait); break; case st_fin_wait_1: log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Closing", conn->name); tcp_conn_state_set(conn, st_closing); break; case st_fin_wait_2: log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: FIN received -> Time-Wait", conn->name); tcp_conn_state_set(conn, st_time_wait); /* Start the Time-Wait timer */ tcp_conn_tw_timer_set(conn); break; case st_close_wait: case st_closing: case st_last_ack: /* Do nothing */ break; case st_time_wait: /* Restart the Time-Wait timer */ tcp_conn_tw_timer_set(conn); break; } /* Add FIN to the receive buffer */ conn->rcv_buf_fin = true; fibril_condvar_broadcast(&conn->rcv_buf_cv); if (conn->cb != NULL && conn->cb->recv_data != NULL) conn->cb->recv_data(conn, conn->cb_arg); tcp_segment_delete(seg); return cp_done; } return cp_continue; }