Created
March 3, 2014 13:58
-
-
Save sclaxton/9325435 to your computer and use it in GitHub Desktop.
Implementation of TCP state handling for an undergraduate networks class at the University of Chicago.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * chiTCP - A simple, testable TCP stack | |
| * | |
| * Implementation of the TCP protocol. | |
| * | |
| * chiTCP follows a state machine approach to implementing TCP. | |
| * This means that there is a handler function for each of | |
| * the TCP states (CLOSED, LISTEN, SYN_RCVD, etc.). If an | |
| * event (e.g., a packet arrives) while the connection is | |
| * in a specific state (e.g., ESTABLISHED), then the handler | |
| * function for that state is called, along with information | |
| * about the event that just happened. | |
| * | |
| * Each handler function has the following prototype: | |
| * | |
| * int f(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event); | |
| * | |
| * si is a pointer to the chiTCP server info. The functions in | |
| * this file will not have to access the data in the server info, | |
| * but this pointer is needed to call other functions. | |
| * | |
| * entry is a pointer to the socket entry for the connection that | |
| * is being handled. The socket entry contains the actual TCP | |
| * data (variables, buffers, etc.), which can be extracted | |
| * like this: | |
| * | |
| * tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| * | |
| * other than that, no other fields in "entry" should be read | |
| * or modified. | |
| * | |
| * event is the event that has caused the TCP thread to wake up. The | |
| * list of possible events corresponds roughly to the ones | |
| * specified in http://tools.ietf.org/html/rfc793#section-3.9. | |
| * They are: | |
| * | |
| * APPLICATION_CONNECT: Application has called socket_connect() | |
| * and a three-way handshake must be initiated. | |
| * | |
| * APPLICATION_SEND: Application has called socket_send() and | |
| * there is unsent data in the send buffer. | |
| * | |
| * APPLICATION_RECEIVE: Application has called socket_recv() and | |
| * any received-and-acked data in the recv buffer will be | |
| * collected by the application (up to the maximum specified | |
| * when calling socket_recv). | |
| * | |
| * APPLICATION_CLOSE: Application has called socket_close() and | |
| * a connection tear-down should be initiated. | |
| * | |
| * PACKET_ARRIVAL: A packet has arrived through the network and | |
| * needs to be processed (RFC 793 calls this "SEGMENT ARRIVES") | |
| * | |
| * TIMEOUT: A timeout (e.g., a retransmission timeout) has | |
| * happened. | |
| * | |
| */ | |
| /* | |
| * Copyright (c) 2013-2014, The University of Chicago | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or withsend | |
| * modification, are permitted provided that the following conditions are met: | |
| * | |
| * - Redistributions of source code must retain the above copyright notice, | |
| * this list of conditions and the following disclaimer. | |
| * | |
| * - Redistributions in binary form must reproduce the above copyright notice, | |
| * this list of conditions and the following disclaimer in the documentation | |
| * and/or other materials provided with the distribution. | |
| * | |
| * - Neither the name of The University of Chicago nor the names of its | |
| * contributors may be used to endorse or promote products derived from this | |
| * software withsend specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY send OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| * POSSIBILITY OF SUCH DAMAGE. | |
| * | |
| */ | |
| #include "chitcp/log.h" | |
| #include "chitcp/buffer.h" | |
| #include "serverinfo.h" | |
| #include "connection.h" | |
| #include "tcp.h" | |
| #include "src/simclist/simclist.h" | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #include <assert.h> | |
| #include <netinet/in.h> | |
| int chitcpd_tcp_state_handle_CLOSED(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == APPLICATION_CONNECT) | |
| { | |
| /* We assume it is an active open, so we need to send SYN | |
| * and enter the SYN_SENT state. | |
| */ | |
| chilog(INFO,"handling APPLICATION_CONNECT from state CLOSED"); | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| /* Randomly choose initial ISN */ | |
| srand(time(NULL)); | |
| tcp_seq ISS = (tcp_seq) rand() % 10000; | |
| /*set tcp vars*/ | |
| tcp_data->ISS = ISS; | |
| chilog(INFO,"ISS initialized to %d", ISS); | |
| circular_buffer_init(&tcp_data->recv, TCP_BUFFER_SIZE); | |
| circular_buffer_init(&tcp_data->send, TCP_BUFFER_SIZE); | |
| tcp_data->RCV_WND = circular_buffer_available(&tcp_data->recv); | |
| chilog(INFO,"RCV.WND set to %d", tcp_data->RCV_WND); | |
| tcp_data->SND_WND = 0; | |
| chilog(INFO,"SND.WND initialized to %d", tcp_data->SND_WND); | |
| /* Set SND.UNA and SND.NXT. */ | |
| tcp_data->SND_UNA = ISS; | |
| chilog(INFO,"SND.UNA set to %d", tcp_data->SND_UNA); | |
| tcp_data->SND_NXT = ISS+1; | |
| chilog(INFO,"SND.NXT set to %d", tcp_data->SND_NXT); | |
| /*prepare packet to be sent to server*/ | |
| tcp_packet_t *packet = malloc(sizeof(tcp_packet_t)); | |
| /* Create packet with no payload. */ | |
| chitcpd_tcp_packet_create(entry, packet, NULL, 0); | |
| tcphdr_t *header = TCP_PACKET_HEADER(packet); | |
| header->seq = htonl(ISS); | |
| header->win = htons(get_advertised_window(tcp_data)); | |
| header->syn = 1; | |
| /* Send packet and return. */ | |
| chitcpd_send_tcp_packet(si, entry, packet); | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,packet,LOG_OUTBOUND); | |
| /* Change state to SYN_SENT. */ | |
| chitcpd_update_tcp_state(si, entry, SYN_SENT); | |
| chilog(INFO,"set state to SYN_SENT"); | |
| return CHITCP_OK; | |
| } | |
| else if (event == CLEANUP) | |
| { | |
| /* Any additional cleanup goes here */ | |
| } | |
| else | |
| chilog(WARNING, "In CLOSED state, received unexpected event."); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_LISTEN(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| /* Get packet off queue. */ | |
| tcp_packet_t *in_packet; | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| pthread_mutex_lock(&tcp_data->lock_pending_packets); | |
| in_packet = list_fetch(&tcp_data->pending_packets); | |
| pthread_mutex_unlock(&tcp_data->lock_pending_packets); | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(in_packet); | |
| /*declare reply packet*/ | |
| tcp_packet_t *out_packet; | |
| tcphdr_t *out_header; | |
| if (in_header->rst) { | |
| /* Ignore resets. */ | |
| } else if (in_header->ack) { | |
| /*ignore resets*/ | |
| } else if (in_header->syn) { | |
| /* We need to send syn/ack. */ | |
| int SEG_SEQ = SEG_SEQ(in_packet); | |
| /* Set RCV_NXT and IRS. */ | |
| SET_AND_LOG(tcp_data->RCV_NXT, SEG_SEQ+1); | |
| SET_AND_LOG(tcp_data->IRS, SEG_SEQ); | |
| /* Generate our ISS, sent SND.UNA and SND.NXT */ | |
| srand(time(NULL)); | |
| tcp_seq ISS = (tcp_seq) rand() % 10000; | |
| /*set tcp vars*/ | |
| SET_AND_LOG(tcp_data->ISS, ISS); | |
| circular_buffer_init(&tcp_data->recv, TCP_BUFFER_SIZE); | |
| circular_buffer_init(&tcp_data->send, TCP_BUFFER_SIZE); | |
| SET_AND_LOG(tcp_data->RCV_WND, circular_buffer_available(&tcp_data->recv)); | |
| SET_AND_LOG(tcp_data->SND_WND, 0); | |
| /* Set SND.UNA and SND.NXT. */ | |
| SET_AND_LOG(tcp_data->SND_UNA, ISS); | |
| SET_AND_LOG(tcp_data->SND_NXT, ISS+1); | |
| /*prepare packet for reply*/ | |
| out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->seq = htonl(ISS); | |
| out_header->win = htons(get_advertised_window(tcp_data)); | |
| out_header->ack_seq = htonl(tcp_data->RCV_NXT); | |
| out_header->syn = 1; | |
| out_header->ack = 1; | |
| chitcpd_update_tcp_state(si, entry, SYN_RCVD); | |
| chilog(INFO,"set state to SYN_RCVD"); | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,out_packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| return CHITCP_OK; | |
| } else { | |
| chilog(WARNING, "Something is seriously borked up in state LISTEN."); | |
| } | |
| } | |
| else | |
| chilog(WARNING, "In LISTEN state, received unexpected event."); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_SYN_RCVD(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| /* Get packet off queue. */ | |
| tcp_packet_t *in_packet; | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| pthread_mutex_lock(&tcp_data->lock_pending_packets); | |
| in_packet = list_fetch(&tcp_data->pending_packets); | |
| pthread_mutex_unlock(&tcp_data->lock_pending_packets); | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(in_packet); | |
| /*if segment is acceptable*/ | |
| if ( seg_is_acceptable(in_packet, tcp_data) ) | |
| { | |
| if (in_header->syn) | |
| { | |
| /*rfc793 says in this case to:*/ | |
| /*If the SYN is in the window it is an error, send a reset, any*/ | |
| /*outstanding RECEIVEs and SEND should receive "reset" responses,*/ | |
| /*all segment queues should be flushed, the user should also*/ | |
| /*receive an unsolicited general "connection reset" signal, enter*/ | |
| /*the CLOSED state, delete the TCB, and return.*/ | |
| /*ignore reset*/ | |
| /*ERROR: SYN recvd out of window. Enter state CLOSED*/ | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(ERROR,"ERROR: SYN recvd out of window. Set state to CLOSED."); | |
| return CHITCP_OK; | |
| } | |
| if (in_header->ack) | |
| { | |
| tcp_data->SND_UNA = SEG_ACK(in_packet); | |
| /*enter state ESTABLISHED*/ | |
| SET_AND_LOG(tcp_data->SND_WND, SEG_WND(in_packet)); | |
| chilog(INFO,"Setting SND_WND to %d",tcp_data->SND_WND); | |
| chitcpd_update_tcp_state(si, entry, ESTABLISHED); | |
| chilog(INFO,"Set state to ESTABLISHED"); | |
| } | |
| } else { | |
| handle_seg_not_acceptable(si, entry, in_packet, tcp_data); | |
| } | |
| } | |
| else | |
| chilog(WARNING, "In SYN_RCVD state, received unexpected event."); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_SYN_SENT(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| /* Get packet off queue. */ | |
| tcp_packet_t *in_packet; | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| pthread_mutex_lock(&tcp_data->lock_pending_packets); | |
| in_packet = list_fetch(&tcp_data->pending_packets); | |
| pthread_mutex_unlock(&tcp_data->lock_pending_packets); | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(in_packet); | |
| if (in_header->ack) | |
| { | |
| if ( ack_is_acceptable(in_packet, tcp_data) ) | |
| { | |
| /*ACK is acceptable...move along*/ | |
| } else { | |
| /*ignore unacceptable acks, drop packet and return*/ | |
| chilog(WARNING, "In SYN_SENT state, received unacceptable ACK."); | |
| return CHITCP_OK; | |
| } | |
| } | |
| /*rfc793 specifies handling of the rst bit here which we are skipping*/ | |
| /*rfc793 specifies handling of security/compartment and precedence here | |
| which we assuming to be correct and valid*/ | |
| if (in_header->syn) | |
| { | |
| tcp_packet_t *out_packet; | |
| tcphdr_t *out_header; | |
| uint32_t SEG_SEQ = SEG_SEQ(in_packet); | |
| chilog(INFO, "Setting RCV.NXT to %d.", SEG_SEQ+1); | |
| tcp_data->RCV_NXT = SEG_SEQ + 1; | |
| chilog(INFO, "Setting IRS to %d.", SEG_SEQ); | |
| tcp_data->IRS = SEG_SEQ; | |
| /*note: ack is acceptable if it exists*/ | |
| if (in_header->ack) | |
| { | |
| /*if retransmitting vacate retransmission queue from start index */ | |
| /*to SEG_ACK - SND_UNA - 1*/ | |
| uint32_t SEG_ACK = SEG_ACK(in_packet); | |
| uint32_t SND_NXT = tcp_data->SND_NXT; | |
| tcp_data->SND_UNA = SEG_ACK; | |
| chilog(INFO, "Set SND.UNA to %d.", SEG_ACK); | |
| uint32_t ISS = tcp_data->ISS; | |
| uint32_t RCV_NXT = tcp_data->RCV_NXT; | |
| /*if our SYN has been ACKed*/ | |
| if (SEG_ACK > ISS) | |
| { | |
| /*enter state ESTABLISHED*/ | |
| tcp_data->SND_WND = SEG_WND(in_packet); | |
| chitcpd_update_tcp_state(si, entry, ESTABLISHED); | |
| chilog(INFO,"set state to ESTABLISHED"); | |
| /* Get ready to send the ACK. */ | |
| out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->win = htons(get_advertised_window(tcp_data)); | |
| out_header->seq = htonl(SND_NXT); | |
| out_header->ack_seq = htonl(RCV_NXT); | |
| out_header->ack = 1; | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,out_packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| return CHITCP_OK; | |
| } else { | |
| /*enter state SYN-RCVD*/ | |
| chitcpd_update_tcp_state(si, entry, SYN_RCVD); | |
| chilog(INFO,"set state to SYN_RCVD"); | |
| /* Get ready to send the SYN,ACK. */ | |
| out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->win = htons(get_advertised_window(tcp_data)); | |
| out_header->seq = htonl(ISS); | |
| out_header->ack_seq = htonl(RCV_NXT); | |
| out_header->ack = 1; | |
| out_header->syn = 1; | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,out_packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| return CHITCP_OK; | |
| } | |
| } | |
| } else { | |
| /*no syn or acceptable ack do nothing*/ | |
| chilog(WARNING, "In SYN_SENT state, received no acceptable ACK or SYN."); | |
| } | |
| } | |
| else { | |
| chilog(WARNING, "In SYN_SENT state, received unexpected event."); | |
| } | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_ESTABLISHED(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| if (event == APPLICATION_SEND) | |
| { | |
| process_send_buffer(si, entry, event); | |
| return CHITCP_OK; | |
| } | |
| else if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, ESTABLISHED); | |
| if (!packet) return CHITCP_OK; | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| /* HANDLE FIN */ | |
| if (in_header->fin) { | |
| chitcpd_update_tcp_state(si, entry, CLOSE_WAIT); | |
| chilog(INFO,"Set state to CLOSE_WAIT"); | |
| // close the recv buffer | |
| circular_buffer_close(&tcp_data->recv); | |
| //send_ack(si, entry, tcp_data); | |
| } | |
| chitcp_tcp_packet_free(packet); | |
| } | |
| else if (event == APPLICATION_RECEIVE) | |
| { | |
| chilog(INFO, "Handling event APPLICATION_RECEIVE"); | |
| /* get tcp data */ | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| /* get the amount of space in the rcv buffer */ | |
| uint32_t NEW_RCV_WND = circular_buffer_available(&tcp_data->recv); | |
| uint32_t OLD_RCV_WND = tcp_data->RCV_WND; | |
| chilog(INFO, "updating receive window: RCV_WND [%d] --> [%d]", OLD_RCV_WND, NEW_RCV_WND); | |
| /* set recv window to amount of buffer space */ | |
| SET_AND_LOG(tcp_data->RCV_WND, NEW_RCV_WND); | |
| /*send an ACK of segment put into buffer, piggybacked or not*/ | |
| if (circular_buffer_count(&tcp_data->send)) { | |
| process_send_buffer(si, entry, ESTABLISHED); | |
| } else { | |
| send_ack(si, entry, tcp_data); | |
| } | |
| } | |
| else if (event == APPLICATION_CLOSE) | |
| { | |
| /* Set the closing flag so we send the FIN packet when we next SEND. */ | |
| tcp_data->closing = 1; | |
| chitcpd_update_tcp_state(si, entry, FIN_WAIT_1); | |
| if (!circular_buffer_count(&tcp_data->send)){ | |
| send_fin(si, entry, tcp_data); | |
| circular_buffer_close(&tcp_data->send); | |
| } | |
| return CHITCP_OK; | |
| } | |
| else | |
| chilog(WARNING, "In ESTABLISHED state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_FIN_WAIT_1(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, FIN_WAIT_1); | |
| if (!packet) return CHITCP_OK; | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| /* Handle ACK to our FIN */ | |
| if (in_header->ack && tcp_data->SND_UNA == tcp_data->SND_NXT){ | |
| chitcpd_update_tcp_state(si, entry, FIN_WAIT_2); | |
| chilog(INFO,"Set state to FIN_WAIT_2"); | |
| } | |
| /* Simultaneous close */ | |
| if (in_header->ack && in_header->fin){ | |
| chitcpd_update_tcp_state(si, entry, TIME_WAIT); | |
| chilog(INFO,"Simultaneous close, entering TIME_WAIT "); | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(INFO,"Simultaneous close, entering CLOSING "); | |
| send_ack(si, entry, tcp_data); | |
| } else if (in_header->fin) { | |
| send_ack(si, entry, tcp_data); | |
| chitcpd_update_tcp_state(si, entry, CLOSING); | |
| } | |
| chitcp_tcp_packet_free(packet); | |
| return CHITCP_OK; | |
| } | |
| else | |
| chilog(WARNING, "In FIN_WAIT_1 state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_FIN_WAIT_2(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, FIN_WAIT_2); | |
| if (!packet) return CHITCP_OK; | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| /* HANDLE FIN */ | |
| if (in_header->fin) { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| send_ack(si, entry, tcp_data); | |
| chitcpd_update_tcp_state(si, entry, TIME_WAIT); | |
| chilog(INFO,"Set state to TIME_WAIT"); | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(INFO,"Simultaneous close, entering CLOSED "); | |
| } | |
| chitcp_tcp_packet_free(packet); | |
| } | |
| else | |
| chilog(WARNING, "In FIN_WAIT_2 state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_CLOSE_WAIT(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == APPLICATION_CLOSE) | |
| { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| send_fin(si, entry, tcp_data); | |
| chitcpd_update_tcp_state(si, entry, LAST_ACK); | |
| chilog(INFO,"Set state to LAST_ACK"); | |
| } | |
| else if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, CLOSE_WAIT); | |
| if (packet) chitcp_tcp_packet_free(packet); | |
| } | |
| else | |
| chilog(WARNING, "In CLOSE_WAIT state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_CLOSING(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, CLOSING); | |
| if (!packet) return CHITCP_OK; | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| /* Handle ACK to our FIN */ | |
| if (in_header->ack){ | |
| chitcpd_update_tcp_state(si, entry, TIME_WAIT); | |
| chilog(INFO,"Set state to TIME_WAIT"); | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(INFO,"Simultaneous close, entering CLOSED "); | |
| } | |
| /* HANDLE FIN */ | |
| if (in_header->fin) { | |
| chilog(WARNING,"Got FIN in closing, set state to CLOSED anyway"); | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(INFO,"Set state to CLOSED"); | |
| } | |
| chitcp_tcp_packet_free(packet); | |
| } | |
| else | |
| chilog(WARNING, "In CLOSING state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_TIME_WAIT(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| chilog(WARNING, "Running handler for TIME_WAIT. This should not happen."); | |
| return CHITCP_OK; | |
| } | |
| int chitcpd_tcp_state_handle_LAST_ACK(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| if (event == PACKET_ARRIVAL) | |
| { | |
| tcp_packet_t * packet = post_handshake_packet_accept(si, entry, event, FIN_WAIT_2); | |
| if (!packet) return CHITCP_OK; | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| /* Handle ACK to our FIN */ | |
| if (in_header->ack){ | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| chilog(INFO,"Set state to CLOSED"); | |
| } | |
| chitcp_tcp_packet_free(packet); | |
| } | |
| else | |
| chilog(WARNING, "In LAST_ACK state, received unexpected event (%i).", event); | |
| return CHITCP_OK; | |
| } | |
| /* */ | |
| /* Any additional functions you need should go here */ | |
| /* */ | |
| int ack_is_acceptable(tcp_packet_t *packet, tcp_data_t *data) | |
| { | |
| /*check to see if ack is acceptable*/ | |
| uint32_t SND_UNA = data->SND_UNA; | |
| uint32_t SND_NXT = data->SND_NXT; | |
| uint32_t SEG_ACK = SEG_ACK(packet); | |
| return SND_UNA < SEG_ACK && SEG_ACK <= SND_NXT; | |
| } | |
| int seg_is_acceptable(tcp_packet_t *packet, tcp_data_t *data) | |
| { | |
| uint32_t RCV_NXT = data->RCV_NXT; | |
| uint32_t RCV_WND = data->RCV_WND; | |
| uint32_t SEG_SEQ = SEG_SEQ(packet); | |
| uint32_t SEG_LEN = SEG_LEN(packet); | |
| return ((RCV_NXT <= SEG_SEQ) && (SEG_SEQ < RCV_NXT + RCV_WND)) || | |
| ((RCV_NXT <= SEG_SEQ+SEG_LEN-1) && (SEG_SEQ+SEG_LEN-1 < RCV_NXT+RCV_WND)); | |
| } | |
| void handle_seg_not_acceptable(serverinfo_t *si, chisocketentry_t *entry, tcp_packet_t *packet, tcp_data_t *data){ | |
| chilog(INFO,"Unacceptable segment received."); | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(packet); | |
| if (in_header->ack) | |
| { | |
| /* rfc793 says to allow for acceptable ack that is in an unacceptable segment??? */ | |
| if ( ack_is_acceptable(packet, data) ) | |
| { | |
| chilog(INFO,"Unacceptable segment received containing an acceptable ACK."); | |
| data->SND_UNA = SEG_ACK(packet); | |
| /*enter state ESTABLISHED*/ | |
| chitcpd_update_tcp_state(si, entry, ESTABLISHED); | |
| chilog(INFO,"Set state to ESTABLISHED"); | |
| } | |
| /*ignore unacceptable ACK*/ | |
| } else { | |
| /* Get ready to send ACK in reply to unacceptable segment */ | |
| tcp_packet_t *out_packet; | |
| tcphdr_t *out_header; | |
| out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->seq = htonl(data->SND_NXT); | |
| out_header->win = htons(get_advertised_window(data)); | |
| out_header->ack_seq = htonl(data->RCV_NXT); | |
| out_header->ack = 1; | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,out_packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| /*return and drop the segment*/ | |
| } | |
| } | |
| /* Process the send buffer, sending all the data we can. */ | |
| int process_send_buffer(serverinfo_t *si, chisocketentry_t *entry, tcp_event_type_t event) | |
| { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| int max = (tcp_data->SND_UNA + tcp_data->SND_WND) - tcp_data->SND_NXT; | |
| /* Now we need to segmentize the data in the buffer into some packets | |
| * and send them. | |
| */ | |
| int read; | |
| chilog(INFO, "Fetching packet of max size %d from buffer", max); | |
| uint8_t tmp[TCP_MSS]; | |
| int to_read = TCP_MSS < max ? TCP_MSS : max; | |
| chilog(DEBUG, "to_read value is %d", to_read); | |
| while((max > 0) && ((read = circular_buffer_read(&tcp_data->send, tmp, to_read, 0)) != CHITCP_EWOULDBLOCK)) { | |
| chilog(INFO, "Read %d bytes from buffer while building packet.", read); | |
| send_packet(si, entry, tmp, read); | |
| max -= read; | |
| } | |
| if (tcp_data->closing) { | |
| /* Send FIN segment. */ | |
| tcp_packet_t *packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, packet, NULL, 0); | |
| tcphdr_t *header = TCP_PACKET_HEADER(packet); | |
| header->seq = htonl(tcp_data->SND_NXT); | |
| header->ack_seq = htonl(tcp_data->RCV_NXT); | |
| header->ack = 1; | |
| header->win = htons(get_advertised_window(tcp_data)); | |
| chilog(INFO,"sending fin segment while procesing send buffer:"); | |
| chilog_tcp(INFO,packet,LOG_OUTBOUND); | |
| chilog(INFO,"we should definitely not be sending any more packets after this"); | |
| chitcpd_send_tcp_packet(si, entry, packet); | |
| // close the send buffer | |
| circular_buffer_close(&tcp_data->send); | |
| } | |
| return max; // the amount of data we sent | |
| // TODO: handle errors better? | |
| } | |
| /* Allocate the a packet we from the send buffer and send it. | |
| * max is the maximum size of packet to send | |
| Return the number of bytes we sent. */ | |
| int send_packet(serverinfo_t *si, chisocketentry_t *entry, uint8_t *data, int len) | |
| { | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| tcp_packet_t *packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, packet, data, len); | |
| /* Set all the header fields correctly. */ | |
| tcphdr_t *header = TCP_PACKET_HEADER(packet); | |
| header->seq = htonl(tcp_data->SND_NXT); | |
| header->ack_seq = htonl(tcp_data->RCV_NXT); | |
| header->ack = 1; | |
| header->win = htons(get_advertised_window(tcp_data)); | |
| chilog(INFO,"sending packet while procesing send buffer:"); | |
| chilog_tcp(INFO,packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, packet); | |
| chitcp_tcp_packet_free(packet); | |
| /* Update SND.NXT. */ | |
| tcp_data->SND_NXT += len; | |
| chilog(INFO,"set SND_NXT to %d", tcp_data->SND_NXT); | |
| return 0; | |
| } | |
| uint16_t get_advertised_window(tcp_data_t *data) | |
| { | |
| circular_buffer_t *recv_buff = &data->recv; | |
| if (data->RCV_WND < (circular_buffer_capacity(recv_buff) / 4)) | |
| { | |
| return 0; | |
| } else { | |
| return data->RCV_WND; | |
| } | |
| } | |
| tcp_packet_t *post_handshake_packet_accept(serverinfo_t *si, | |
| chisocketentry_t *entry, | |
| tcp_event_type_t event, | |
| int tcp_state) | |
| { | |
| /* Get packet off queue. */ | |
| tcp_packet_t *in_packet; | |
| tcp_data_t *tcp_data = &entry->socket_state.active.tcp_data; | |
| /* | |
| * General header handling | |
| */ | |
| /* grab the packet from the pending packets queue */ | |
| pthread_mutex_lock(&tcp_data->lock_pending_packets); | |
| in_packet = list_fetch(&tcp_data->pending_packets); | |
| pthread_mutex_unlock(&tcp_data->lock_pending_packets); | |
| /* LOG THAT THANG. */ | |
| chilog(DEBUG, "THIS IS WHAT THE PACKET WE ARE HANDLING LOOKS LIKE:"); | |
| chilog_tcp(DEBUG, in_packet, LOG_INBOUND); | |
| tcphdr_t *in_header = TCP_PACKET_HEADER(in_packet); | |
| /* CHECK IF ACCEPTABLE */ | |
| if (!seg_is_acceptable(in_packet, tcp_data)){ | |
| handle_seg_not_acceptable(si, entry, in_packet, tcp_data); | |
| return NULL; | |
| } | |
| /* ERROR ON SYN */ | |
| if (in_header->syn) { | |
| /* We should not receive a SYN after the handshake, this is an error */ | |
| chilog(ERROR,"ERROR: SYN recvd out of window. Setting state to CLOSED."); | |
| chitcpd_update_tcp_state(si, entry, CLOSED); | |
| return CHITCP_OK; | |
| } | |
| /* HANDLE ACK */ | |
| if (in_header->ack) { | |
| if (ack_is_acceptable(in_packet, tcp_data)) { | |
| SET_AND_LOG(tcp_data->SND_UNA, SEG_ACK(in_packet)); | |
| SET_AND_LOG(tcp_data->SND_WND, SEG_WND(in_packet)); | |
| } else if ( SEG_ACK(in_packet) > tcp_data->SND_NXT) { | |
| send_ack(si, entry, tcp_data); | |
| } | |
| } | |
| /* | |
| * WRITE PAYLOAD TO RECV BUFFER | |
| */ | |
| size_t payload_len = TCP_PAYLOAD_LEN(in_packet); | |
| SET_AND_LOG(tcp_data->RCV_NXT, SEG_SEQ(in_packet) + SEG_LEN(in_packet)); | |
| SET_AND_LOG(tcp_data->RCV_WND, circular_buffer_available(&tcp_data->recv)); | |
| if (payload_len > tcp_data->RCV_WND){ | |
| chilog(ERROR, "Packet received that we cannot handle. Buffer overflow: %d", payload_len); | |
| return in_packet; | |
| } | |
| if (payload_len <= 0){ | |
| return in_packet; | |
| } | |
| /*write payload to recv buffer*/ | |
| uint8_t *payload = TCP_PAYLOAD_START(in_packet); | |
| circular_buffer_write(&tcp_data->recv, payload, payload_len, BUFFER_BLOCKING); | |
| /*send an ACK of segment put into buffer, piggybacked or not*/ | |
| if (circular_buffer_count(&tcp_data->send)) { | |
| process_send_buffer(si, entry, ESTABLISHED); | |
| } else { | |
| send_ack(si, entry, tcp_data); | |
| } | |
| return in_packet; | |
| } | |
| int send_ack(serverinfo_t *si, chisocketentry_t *entry, tcp_data_t *tcp_data) | |
| { | |
| /* send ACK in response to ACK that acks something not yet sent */ | |
| tcp_packet_t *out_packet; | |
| tcphdr_t *out_header; | |
| chilog(INFO, "Sending generic ACK"); | |
| out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->seq = htonl(tcp_data->SND_NXT); | |
| out_header->win = htons(get_advertised_window(tcp_data)); | |
| out_header->ack_seq = htonl(tcp_data->RCV_NXT); | |
| out_header->ack = 1; | |
| out_header->fin = 0; | |
| chilog(INFO,"sending packet:"); | |
| chilog_tcp(INFO,out_packet,LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| /*return and drop the segment*/ | |
| chitcp_tcp_packet_free(out_packet); | |
| return CHITCP_OK; | |
| } | |
| int send_fin(serverinfo_t *si, chisocketentry_t *entry, tcp_data_t *tcp_data) | |
| { | |
| tcp_packet_t *out_packet = malloc(sizeof(tcp_packet_t)); | |
| chitcpd_tcp_packet_create(entry, out_packet, NULL, 0); | |
| tcphdr_t *out_header = TCP_PACKET_HEADER(out_packet); | |
| out_header->seq = htonl(tcp_data->SND_NXT); | |
| out_header->win = htons(get_advertised_window(tcp_data)); | |
| out_header->ack_seq = htonl(tcp_data->RCV_NXT); | |
| out_header->ack = 0; | |
| out_header->fin = 1; | |
| /* Bump SND.NXT when we send a fin. */ | |
| tcp_data->SND_NXT++; | |
| chilog(INFO, "sending FIN packet:"); | |
| chilog_tcp(INFO, out_packet, LOG_OUTBOUND); | |
| chitcpd_send_tcp_packet(si, entry, out_packet); | |
| chitcp_tcp_packet_free(out_packet); | |
| return CHITCP_OK; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment