/** 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); }
/** RECEIVE user call */ tcp_error_t tcp_uc_receive(tcp_conn_t *conn, void *buf, size_t size, size_t *rcvd, xflags_t *xflags) { size_t xfer_size; log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_uc_receive()", conn->name); fibril_mutex_lock(&conn->lock); if (conn->cstate == st_closed) { fibril_mutex_unlock(&conn->lock); return TCP_ENOTEXIST; } /* Wait for data to become available */ while (conn->rcv_buf_used == 0 && !conn->rcv_buf_fin && !conn->reset) { log_msg(LOG_DEFAULT, LVL_DEBUG, "tcp_uc_receive() - wait for data"); fibril_condvar_wait(&conn->rcv_buf_cv, &conn->lock); } if (conn->rcv_buf_used == 0) { *rcvd = 0; *xflags = 0; if (conn->rcv_buf_fin) { /* End of data, peer closed connection */ fibril_mutex_unlock(&conn->lock); return TCP_ECLOSING; } else { /* Connection was reset */ assert(conn->reset); fibril_mutex_unlock(&conn->lock); return TCP_ERESET; } } /* Copy data from receive buffer to user buffer */ xfer_size = min(size, conn->rcv_buf_used); memcpy(buf, conn->rcv_buf, xfer_size); *rcvd = xfer_size; /* Remove data from receive buffer */ memmove(conn->rcv_buf, conn->rcv_buf + xfer_size, conn->rcv_buf_used - xfer_size); conn->rcv_buf_used -= xfer_size; conn->rcv_wnd += xfer_size; /* TODO */ *xflags = 0; /* Send new size of receive window */ tcp_tqueue_ctrl_seg(conn, CTL_ACK); log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: tcp_uc_receive() - returning %zu bytes", conn->name, xfer_size); fibril_mutex_unlock(&conn->lock); return TCP_EOK; }
/** Synchronize connection. * * This is the first step of an active connection attempt, * sends out SYN and sets up ISS and SND.xxx. */ void tcp_conn_sync(tcp_conn_t *conn) { /* XXX select ISS */ conn->iss = 1; conn->snd_nxt = conn->iss; conn->snd_una = conn->iss; conn->ap = ap_active; tcp_tqueue_ctrl_seg(conn, CTL_SYN); tcp_conn_state_set(conn, st_syn_sent); }
/** 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); }
/** 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 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; }