/* *************************************************** * Function: handle_timeout * *************************************************** * This function is called whenever there is a timeout. Unless we * are in the TIME_WAIT state, I retransmit all the packets starting * from the beginning of the window. * The RTO is also doubled when there is a retransmission as described * in K&R. */ static void handle_timeout(mysocket_t sd, context_t *ctx) { if (ctx->connection_state==CSTATE_TIME_WAIT){ ctx->connection_state = CSTATE_CLOSED; ctx->done = true; return; } our_dprintf("TIMEOUT waiting for seq:%d\n", ctx->seq_base); assert(ctx->sbuffer.num_entries>0 || !DEBUG); assert(ctx->sbuffer.start->next!=NULL || !DEBUG); struct p_entry *curr = ctx->sbuffer.start->next; ctx->rto= MIN(ctx->rto*2, MAX_RTO); /* Retransmit all packets */ while (curr){ struct tcphdr *hdr = (struct tcphdr*)curr->packet; if (curr->num_transmits>=MAX_RT_TRIES){ our_dprintf("MAX NUMBER OF TRIES REACHED KILLING CONNECTION !!!!\n\n"); ctx->done = true; errno = ETIMEDOUT; return; } hdr->th_ack = ctx->last_ack_sent; curr->num_transmits+=1; /* increase counter */ gettimeofday(&(curr->tstart),NULL); network_send(sd, hdr, curr->packet_size); curr = curr->next; our_dprintf("Retransmitting Seq:%d with Len:%d New RTO:%f\n", hdr->th_seq, ctx->sbuffer.start->next->packet_size, ctx->rto); } }
/* *************************************************** * Function: passive_init * *************************************************** * Passive node waits for a SYN packet to arrive, it then * sends an SYN_ACK using another helper function (below) */ static bool passive_init(mysocket_t sd, context_t *ctx) { ctx->connection_state = CSTATE_LISTEN; struct tcphdr* syn_packet = (struct tcphdr *)calloc(1, (TH_MAX_OFFSET*sizeof(uint32_t))+STCP_MSS); /* Listen for a connection request */ while (true){ stcp_wait_for_event(sd, NETWORK_DATA, NULL); network_recv(sd, syn_packet); if (syn_packet->th_flags == TH_SYN){ break; } } our_dprintf("PASSIVE - RCVD SYN with seq: %d\n", syn_packet->th_seq); /* Prepare SYN_ACK*/ syn_packet->th_ack = syn_packet->th_seq + 1; syn_packet->th_seq = ctx->initial_sequence_num; syn_packet->th_off = TH_MIN_OFFSET; syn_packet->th_flags = TH_SYN | TH_ACK; syn_packet->th_win = RWIN; /*Update connection state variables */ ctx->last_ack_sent = syn_packet->th_ack; ctx->nxt_seq_num = ctx->initial_sequence_num + 1; ctx->connection_state = CSTATE_SYN_RCVD; ctx->seq_base = ctx->nxt_seq_num; our_dprintf("PASSIVE - SEND SYN_ACK Packet with seq: %d and ack: %d \n", syn_packet->th_seq, syn_packet->th_ack); bool success = send_syn_ack(sd, ctx, syn_packet); free(syn_packet); return success; }
/* *************************************************** * Function: handle_ack * *************************************************** * We received an ACK. Check the special case that we received * a duplicate SYN_ACK and if so, send an ACK back (this is the only * ACK packet that requires an ACK to be sent back). Then check if the * ACK falls within the sender window and if so, estimate the new rto * (using helper function below), remove everything upto that sequence * number from the window. Finally, check to see if this is a FIN ACK * and update state variables. */ static void handle_ack(struct tcphdr* hdr, mysocket_t sd, context_t* ctx) { our_dprintf("Ack:%d received - Prior Window Bottom:%d - Window Top: %d\n", hdr->th_ack, ctx->seq_base, ctx->nxt_seq_num); if (hdr->th_flags & TH_SYN) { /* Received a duplicate SYN_ACK during init - can only happen first time around for client */ assert(((hdr->th_seq == ctx->last_ack_sent-1) && (hdr->th_ack==ctx->initial_sequence_num+1)) || !DEBUG); if((hdr->th_seq == ctx->last_ack_sent-1) && (hdr->th_ack==ctx->initial_sequence_num+1)){ send_ack(sd,ctx); return; } } if (!((hdr->th_ack <= ctx->nxt_seq_num) && (hdr->th_ack > ctx->seq_base))) { our_dprintf("Ack outside window. Throw Away!\n"); return; } /* ACK INSIDE WINDOW - HANDLE IT */ estimate_rto(ctx, hdr->th_ack); /* check if we can get a new RTO*/ ctx->seq_base = hdr->th_ack; /* new window bottom */ remove_until_seq(ctx); /* remove everything upto the new window bottom (cumulative acks)*/ /* Check to see if we got ACK for FIN. If so update state variables*/ if (hdr->th_ack == ctx->fin_ack) { if (ctx->connection_state == CSTATE_FIN_WAIT1) { ctx->connection_state = CSTATE_FIN_WAIT2; } else if (ctx->connection_state == CSTATE_LAST_ACK) { assert((ctx->nxt_seq_num==ctx->seq_base) || !DEBUG); ctx->connection_state = CSTATE_CLOSED; ctx->done = 1; } } }
/* *************************************************** * Function: active_init * *************************************************** * Initiate the active side connection. Send the SYN packet, * wait for the SYN_ACK and then send the final SYN. */ static bool active_init(mysocket_t sd, context_t *ctx) { struct tcphdr* syn_packet = (struct tcphdr *)calloc(1,(TH_MAX_OFFSET*sizeof(uint32_t)) + STCP_MSS); if (!sendrcv_syn(sd, ctx, syn_packet)) { return false; /* Timeout waiting for SYN_ACK*/ } ctx->connection_state = CSTATE_SYN_SENT; our_dprintf("Active - RVCD SYN_ACK Seq: %d, ACK: %d \n", syn_packet->th_seq, syn_packet->th_ack); ctx->recv_win = MIN(syn_packet->th_win, CWIN); /* Send final ACK ---- Prepare Headers */ syn_packet->th_ack = syn_packet->th_seq + 1; syn_packet->th_seq = ctx->nxt_seq_num; syn_packet->th_flags = TH_ACK; syn_packet->th_off = TH_MIN_OFFSET; syn_packet->th_win = RWIN; our_dprintf("Active - Sending Final ACK Seq: %d, ACK: %d \n", syn_packet->th_seq, syn_packet->th_ack); network_send(sd, syn_packet, (TH_MIN_OFFSET*sizeof(uint32_t))); /* Update connection state variables */ ctx->seq_base = ctx->nxt_seq_num; ctx->last_ack_sent = syn_packet->th_ack; free(syn_packet); return true; }
/* *************************************************** * Function: send_syn_ack * *************************************************** * Passive node calls this to send SYN_ACK and wait for * the final ACK packet. It tries a total of 6 times before * giving up. */ static bool send_syn_ack(mysocket_t sd, context_t *ctx, struct tcphdr* syn_packet) { struct tcphdr* ack_packet = (struct tcphdr *)calloc(1,(TH_MAX_OFFSET*sizeof(uint32_t))+STCP_MSS); unsigned int event; bool success = false; int num_tries = 1; struct timeval tstart; gettimeofday(&tstart, NULL); /*Start the timer */ network_send(sd, syn_packet, (TH_MIN_OFFSET*sizeof(uint32_t))); while((num_tries < MAX_RT_TRIES) && !success){ event = wait_for_event(sd, NETWORK_DATA, ctx, tstart); if (event & NETWORK_DATA) { network_recv(sd, ack_packet); our_dprintf("Passive (ACK waiting): Ack: %d, Seq:%d, Next Seq:%d, Last Ack Sent: %d\n", ack_packet->th_ack, ack_packet->th_seq, ctx->nxt_seq_num, ctx->last_ack_sent); success = ((ack_packet->th_flags == TH_ACK) && (ack_packet->th_ack == ctx->nxt_seq_num)); } else if (event==TIMEOUT){ our_dprintf("Passive Final INIT ACK not Received TIMEOUT!\n"); num_tries+=1; gettimeofday(&tstart, NULL); our_dprintf("PASSIVE - SEND SYN_ACK Packet with seq: %d and ack: %d \n", syn_packet->th_seq, syn_packet->th_ack); network_send(sd, syn_packet, (TH_MIN_OFFSET*sizeof(uint32_t))); } } if (success){ our_dprintf("PASSIVE - RCVD Last Ack Packet has seq: %d and ack: %d \n", ack_packet->th_seq, ack_packet->th_ack); ctx->recv_win = MIN(syn_packet->th_win, CWIN); } free(ack_packet); return success; }
/* *************************************************** * Function: handle_app_data * *************************************************** * The application has new data to be sent out. First make sure that * there is some space in the window. Get maximum amount of data from * the application, added it to the sender's buffer, and send it across * the network. */ static void handle_app_data(mysocket_t sd, context_t *ctx) { size_t bytes_in_transit = ctx->nxt_seq_num - ctx->seq_base; size_t win_space = ctx->recv_win - bytes_in_transit; if (win_space>0){ char *pkt = (char *)calloc(1,(TH_MIN_OFFSET*sizeof(uint32_t)) + STCP_MSS); struct tcphdr* hdr = (struct tcphdr*)pkt; hdr->th_off = TH_MIN_OFFSET; hdr->th_seq = ctx->nxt_seq_num; hdr->th_flags = 0; hdr->th_win = RWIN; /* Pick the minimum between space in the window and MSS */ size_t data_max = MIN(STCP_MSS, win_space); size_t len = stcp_app_recv(sd, pkt + TCP_DATA_START(hdr), data_max); ctx->nxt_seq_num += len; /*add len to obtain nxt_seq_num to be used */ add_to_send_buffer(ctx, pkt, (TH_MIN_OFFSET*sizeof(uint32_t)) + len); our_dprintf("Sending packet with seq: %d of size: %d\n", hdr->th_seq,(TH_MIN_OFFSET*sizeof(uint32_t)) + len); network_send(sd, pkt, (TH_MIN_OFFSET*sizeof(uint32_t)) + len); free(pkt); } else { our_dprintf("."); } }
int handle_cstate_fin_wait_2(mysocket_t sd, context_t * ctx){ int ret = 0; unsigned int event; our_dprintf("in CSTATE_FIN_WAIT_2\n"); event = stcp_wait_for_event(sd, NETWORK_DATA | TIMEOUT, NULL); /* check whether it was the network, app, or a close request */ uint8_t buff[MAX_WINDOW_SIZE + sizeof(struct tcphdr)]; if (event & NETWORK_DATA) { /* there was network data received */ size_t recd; recd = stcp_network_recv(sd, buff, MAX_WINDOW_SIZE); if (recd == 0) { our_dprintf("bad network recv\n"); return -1; } struct tcphdr * tcp_packet = (struct tcphdr *) buff; if (tcp_packet->th_flags & TH_FIN){ /* if fin */ ctx->recd_last_byte_recd++; ctx->recd_last_byte_read++; ctx->recd_next_byte_expected++; send_syn_ack_fin(sd, ctx, SEND_ACK, 0, ctx->recd_next_byte_expected); ctx->connection_state = CSTATE_TIME_WAIT; } } return ret; }
/* *************************************************** * Function: control_wait * *************************************************** * This function determines the right event to wait for based on window size * and connection state. * If the window is full, then we don't wait for application data as we know * we can't send any. * If there are bytes outstanding (not-acked), then we need to wait using a timeout * value. * If we are TIME_WAIT state, then we set the RTO to the TIME_WAIT_VALUE and only accept * network data (i.e. FIN that need to be acknowledged). */ unsigned int control_wait(mysocket_t sd, context_t *ctx, bool fin_retry) { unsigned int event = 0; size_t bytes_in_transit = ctx->nxt_seq_num - ctx->seq_base; size_t win_space = ctx->recv_win - bytes_in_transit; unsigned int wait_flags = NETWORK_DATA | APP_CLOSE_REQUESTED; if (win_space >0 && !fin_retry) { wait_flags |= APP_DATA; } /* Is there something in flight? */ if (bytes_in_transit > 0) { assert((ctx->sbuffer.num_entries>0) || !DEBUG); our_dprintf("Waiting with RTO for Seq:%d \n", ctx->seq_base); event = wait_with_rto(sd, wait_flags, ctx); } else if (ctx->connection_state==CSTATE_TIME_WAIT) { ctx->rto = TIME_WAIT_VALUE; struct timeval current_time; gettimeofday(¤t_time, NULL); our_dprintf("IN TIME WAIT!!!\n"); event = wait_for_event(sd, NETWORK_DATA, ctx,current_time); } else { /* No unacked packets... take your sweet time*/ event = stcp_wait_for_event(sd, ANY_EVENT, NULL); } return event; }
int handle_cstate_est_send(mysocket_t sd, context_t * ctx){ //our_dprintf("*IN EST SEND\n"); size_t eff_wind = calc_eff_window(ctx); size_t max_to_send = MIN(eff_wind, STCP_MSS); if (max_to_send == 0) { //our_dprintf("window too small to send min(%u, %u) \n", eff_wind, STCP_MSS); return 0; } //our_dprintf("max to send %u\n", max_to_send); /* receive data from app */ size_t recd_app; uint8_t buff[max_to_send]; recd_app = stcp_app_recv(sd, buff, max_to_send); /* construct header */ uint32_t header_size = sizeof(struct tcphdr); uint8_t header_buf[header_size]; memset(header_buf, 0, header_size); struct tcphdr * tcp_header = ( struct tcphdr *) header_buf; tcp_header->th_seq = htonl(ctx->sent_last_byte_sent + 1); tcp_header->th_flags |= TH_SYN; tcp_header->th_off = 5; /* no options, data begins 20 bytes into packet */ /* set adv window size */ tcp_header->th_win = htons(calc_adv_wind(ctx)); ctx->sent_adv_window = calc_adv_wind(ctx); /* copy the data into the tcp buffer */ uint32_t wind_index = ((ctx->sent_last_byte_written + 1) - ctx->initial_sequence_num) % MAX_WINDOW_SIZE; our_dprintf("window index %u\n", wind_index); /* we're wrapping the buffer */ if (wind_index + recd_app > MAX_WINDOW_SIZE) { size_t delta = MAX_WINDOW_SIZE - wind_index; our_dprintf("wrapping the buffer\n"); /* fill the end of the buffer */ memcpy(ctx->send_wind + wind_index, buff, delta); /* restart at the beginning of the buffer */ memcpy(ctx->send_wind, buff+delta, recd_app - delta); stcp_network_send(sd, header_buf, header_size, ctx->send_wind + wind_index, delta, ctx->send_wind, recd_app - delta, NULL); } else { /* don't need to wrap the buffer */ our_dprintf("not wrapping the buffer\n"); memcpy(ctx->send_wind + wind_index, buff, recd_app); stcp_network_send(sd, header_buf, header_size, (ctx->send_wind + wind_index), recd_app, NULL); } ctx->sent_last_byte_sent += recd_app; ctx->sent_last_byte_written += recd_app; return 0; }
int handle_cstate_fin_wait_1(mysocket_t sd, context_t * ctx){ int ret = 0; unsigned int event; our_dprintf("in CSTATE_FIN_WAIT_1\n"); event = stcp_wait_for_event(sd, NETWORK_DATA | TIMEOUT, NULL); /* check whether it was the network, app, or a close request */ uint8_t buff[MAX_WINDOW_SIZE + sizeof(struct tcphdr)]; if (event & NETWORK_DATA) { /* there was network data received */ size_t recd; recd = stcp_network_recv(sd, buff, MAX_WINDOW_SIZE); if (recd == 0) { our_dprintf("bad network recv\n"); return -1; } struct tcphdr * tcp_packet = (struct tcphdr *) buff; if (tcp_packet->th_flags & (TH_ACK | TH_FIN)) { /* if ack + fin */ if (ntohl(tcp_packet->th_ack) < ctx->sent_last_byte_acked || ntohl(tcp_packet->th_ack) > ctx->sent_last_byte_written+1){ our_dprintf("bad ack, expected %u, received %u. returning \n", ctx->sent_last_byte_sent+1, tcp_packet->th_ack); return -1; } ctx->sent_last_byte_acked = ntohl(tcp_packet->th_ack); ctx->recd_last_byte_recd++; ctx->recd_last_byte_read++; ctx->recd_next_byte_expected++; send_syn_ack_fin(sd, ctx, SEND_ACK, 0, ctx->recd_next_byte_expected); ctx->connection_state = CSTATE_TIME_WAIT; } else if (tcp_packet->th_flags & TH_ACK){ /* ack received */ if (ntohl(tcp_packet->th_ack) < ctx->sent_last_byte_acked || ntohl(tcp_packet->th_ack) > ctx->sent_last_byte_written+1){ our_dprintf("bad ack, expected %u, received %u. returning \n", ctx->sent_last_byte_sent+1, tcp_packet->th_ack); return -1; } ctx->sent_last_byte_acked = ntohl(tcp_packet->th_ack); //our_dprintf("*** got an ack %u\n", ctx->sent_last_byte_acked); ctx->connection_state = CSTATE_FIN_WAIT_2; } else if (tcp_packet->th_flags & TH_FIN){ /* else if fin */ ctx->recd_last_byte_recd++; ctx->recd_last_byte_read++; ctx->recd_next_byte_expected++; send_syn_ack_fin(sd, ctx, SEND_ACK, 0, ctx->recd_next_byte_expected); ctx->connection_state = CSTATE_CLOSING; } } return ret; }
int handle_cstate_close_wait(mysocket_t sd, context_t * ctx){ int ret = 0; unsigned int event; our_dprintf("in CSTATE_CLOSE_WAIT\n"); struct timespec wait_time; time_t curtime; curtime = time(0); //our_dprintf("curtime = %u\n", curtime); wait_time.tv_sec = curtime + 240; /* wait atleast 2 lifetimes */ wait_time.tv_nsec = 0; event = stcp_wait_for_event(sd, APP_CLOSE_REQUESTED | TIMEOUT, &wait_time); /* check whether it was the network, app, or a close request */ if (event & APP_CLOSE_REQUESTED) { /* there was network data received */ send_syn_ack_fin(sd, ctx, SEND_FIN, 0, 0); ctx->connection_state = CSTATE_LAST_ACK; } return ret; }
int open_tcp_conn(mysocket_t sd, context_t * ctx, bool_t is_active) { /* make sure the state is closed before we try to open it */ assert(ctx->connection_state == CSTATE_CLOSED); while (ctx->connection_state != CSTATE_ESTABLISHED && ctx->connection_state != CSTATE_FIN_WAIT_1 ) { switch(ctx->connection_state) { case CSTATE_CLOSED: /* connection is closed */ handle_cstate_closed(sd, ctx, is_active); break; case CSTATE_LISTEN: /* listening */ handle_cstate_listen(sd, ctx); break; case CSTATE_SYN_RCVD: /* syn has been received */ handle_cstate_syn_rcvd(sd, ctx); break; case CSTATE_SYN_SENT: /* syn has been sent */ handle_cstate_syn_sent(sd, ctx); break; default: /* shouldn't happen */ our_dprintf("bad open tcp conn state\n"); assert(0); break; } } return 0; }
/* *************************************************** * Function: transport_init * *************************************************** * initialise the transport layer, and start the main loop, handling * any data from the peer or the application. this function should not * return until the connection is closed. */ void transport_init(mysocket_t sd, bool_t is_active) { context_t *ctx = init_ctx(is_active); bool success = false; if (is_active) { success = active_init(sd, ctx); } else { success = passive_init(sd, ctx); } if (success){ ctx->connection_state = CSTATE_ESTABLISHED; stcp_unblock_application(sd); control_loop(sd, ctx); } else { errno = ETIMEDOUT; stcp_unblock_application(sd); } teardown_resources(ctx); /* Wait for potential pending processes in * other side (this is to fix a bug in the starter code) */ sleep(1); our_dprintf("Exiting Loop!\n"); }
/* *************************************************** * Function: control_loop * *************************************************** * control_loop() is the main STCP loop; it repeatedly waits for one of the * following to happen: * - incoming data from the peer * - new data from the application (via mywrite()) * - the socket to be closed (via myclose()) * - a timeout */ static void control_loop(mysocket_t sd, context_t *ctx) { assert(ctx); unsigned int event = 0; bool fin_retry = false; /* TRUE = APP_CLOSE_REQUESTED but window was full */ while (!ctx->done) { size_t bytes_in_transit = ctx->nxt_seq_num - ctx->seq_base; size_t win_space = ctx->recv_win - bytes_in_transit; event = control_wait(sd, ctx, fin_retry); if (event & NETWORK_DATA) { handle_network_data(sd, ctx); } if ((event & APP_DATA) && !fin_retry) { handle_app_data(sd, ctx); } if (event == TIMEOUT){ handle_timeout(sd, ctx); } if ((event & APP_CLOSE_REQUESTED) || fin_retry) { if (win_space>0){ send_fin(sd, ctx); fin_retry = false; } else { our_dprintf("App requested FIN but window is full\n"); fin_retry = true; } } } }
/* *************************************************** * Function: handle_fin * *************************************************** * This function is called when we receive a FIN (or actually * when we process a FIN in-order from the receive buffer). It * simply updates the states variables and calls stcp_fin_received. * */ static void handle_fin(mysocket_t sd, context_t *ctx) { size_t bytes_in_transit = ctx->nxt_seq_num - ctx->seq_base; our_dprintf("FIN PACKET RECEIVED. ACK SEN. Window Start:%d Window End:%d\n", ctx->seq_base, ctx->nxt_seq_num); /* This is a special case, we sent a FIN first but we received another * FIN before we got an ACK for the first FIN. If this is the case, I just * treat that second FIN as an ACK for the first FIN (though this would not be * necessarily true if both sides were to send FINs simulatenously). We were * not asked to deal with simulatenous shut-down so I just do it this clean * way */ if (ctx->connection_state == CSTATE_FIN_WAIT1) { assert(((ctx->seq_base + 1) == ctx->nxt_seq_num) || !DEBUG); ctx->seq_base = ctx->nxt_seq_num; remove_until_seq(ctx); ctx->connection_state = CSTATE_TIME_WAIT; } else if (ctx->connection_state == CSTATE_FIN_WAIT2) { assert(bytes_in_transit==0 || !DEBUG); ctx->connection_state = CSTATE_TIME_WAIT; } else { assert(ctx->connection_state = CSTATE_ESTABLISHED || !DEBUG); assert(bytes_in_transit==0 || !DEBUG); ctx->connection_state = CSTATE_CLOSE_WAIT; } stcp_fin_received(sd); }
int handle_cstate_listen(mysocket_t sd, context_t * ctx) { int ret = 0; unsigned int event; our_dprintf("in CSTATE_LISTEN\n"); event = stcp_wait_for_event(sd, ANY_EVENT, NULL); /* check whether it was the network, app, or a close request */ if (event & NETWORK_DATA) { /* there was network data received */ size_t recd; recd = stcp_network_recv(sd, ctx->recv_wind, MAX_WINDOW_SIZE); if (recd == 0) { our_dprintf("bad network recv\n"); return -1; } struct tcphdr * tcp_packet = (struct tcphdr *) ctx->recv_wind; //size_t recd_app = recd - 4 * tcp_packet->th_off; if (tcp_packet->th_flags & TH_SYN){ /* syn received */ ctx->initial_recd_seq_num = ntohl(tcp_packet->th_seq); ctx->recd_last_byte_recd = ctx->initial_recd_seq_num; ctx->recd_next_byte_expected = ctx->recd_last_byte_recd + 1; ctx->recd_adv_window = ntohs(tcp_packet->th_win); send_syn_ack_fin(sd, ctx, SEND_SYN | SEND_ACK, ctx->initial_sequence_num, ctx->recd_next_byte_expected); ctx->connection_state = CSTATE_SYN_RCVD; } } if (event & APP_DATA) { /* the application has requested that data be sent */ send_syn_ack_fin(sd, ctx, SEND_SYN, ctx->initial_sequence_num, 0); ctx->connection_state = CSTATE_SYN_SENT; } if (event & APP_CLOSE_REQUESTED) { /* the application has requested that the conn be closed */ ctx->connection_state = CSTATE_CLOSED; } return ret; }
int handle_cstate_syn_rcvd(mysocket_t sd, context_t * ctx) { int ret = 0; unsigned int event; our_dprintf("in CSTATE_SYN_RCVD\n"); event = stcp_wait_for_event(sd, NETWORK_DATA | APP_CLOSE_REQUESTED | TIMEOUT, NULL); /* check whether it was the network, app, or a close request */ if (event & NETWORK_DATA) { /* there was network data received */ size_t recd; recd = stcp_network_recv(sd, ctx->recv_wind, MAX_WINDOW_SIZE); if (recd == 0) { our_dprintf("bad network recv\n"); return -1; } struct tcphdr * tcp_packet = (struct tcphdr *) ctx->recv_wind; if (tcp_packet->th_flags & TH_ACK){ /* ack received */ if (ntohl(tcp_packet->th_ack) < ctx->sent_last_byte_acked || ntohl(tcp_packet->th_ack) > ctx->sent_last_byte_written+1){ our_dprintf("bad ack, expected %u, received %u. returning \n", ctx->sent_last_byte_sent+1, tcp_packet->th_ack); return -1; } ctx->sent_last_byte_acked = ntohl(tcp_packet->th_ack); ctx->recd_adv_window = ntohs(tcp_packet->th_win); //our_dprintf("*** got ack %u, got adv window %u\n", ctx->sent_last_byte_acked, ctx->recd_adv_window); ctx->connection_state = CSTATE_ESTABLISHED; } } if (event & APP_CLOSE_REQUESTED) { /* the application has requested that the conn be closed */ ctx->connection_state = CSTATE_FIN_WAIT_1; close_tcp_conn(sd, ctx); } return ret; }
/* *************************************************** * Function: add_to_recv_buffer * *************************************************** * This function simply adds a packet (if its a valid packet, defined * by the next function) to the receiver buffer. It searches the receiver * buffer for the right place to put the file. The receive buffer is always * kept in anscending order of sequence numbers. */ static void add_to_recv_buffer(context_t *ctx, char* pkt, size_t len) { tcp_seq target_seq = ((struct tcphdr *)pkt)->th_seq; size_t data_len = len - TCP_DATA_START(pkt); if (!is_valid_entry(ctx,target_seq, data_len)){ our_dprintf("Invalid incoming packet - dropping!\n"); return; } struct window* win = &(ctx->rbuffer); assert((win->end->next==NULL) || !DEBUG); /* Create a new link list entry */ struct p_entry* new_entry = (struct p_entry*)calloc(1,sizeof(struct p_entry)); new_entry->packet = (char *)calloc(1, len); memcpy(new_entry->packet, pkt, len); new_entry->packet_size = len; tcp_seq curr_seq; struct p_entry* prev = win->start; struct p_entry* curr = win->start->next; bool inserted = false; /* Search for the right place to add this sequence */ while(curr) { curr_seq = ((struct tcphdr *)curr->packet)->th_seq; if (target_seq < curr_seq) { /* Place found */ inserted = true; new_entry->next = curr; prev->next = new_entry; break; } prev = curr; curr = curr->next; } /* Add to the end of the list if not added before */ if (!inserted){ new_entry->next = NULL; win->end->next = new_entry; win->end = win->end->next; } win->num_entries+=1; our_dprintf("Added Recv: %d to receive buffer\n", ((struct tcphdr*)pkt)->th_seq); }
/* *************************************************** * Function: is_valid_entry * *************************************************** * Determines if an incoming packet "fits" in the receive buffer. * The following conditions must be sastified. * 1.) The incoming sequence number better be greater than or equal to * the last ack we sent out, otherwise we already have the data. * 2.) The incoming sequence number cannot be identical to any sequence * number in the receive buffer. This just means we have a duplicate * packet so no need to store it. * 3.) If the packet fits between sequence X and sequence Y, its length * better not be large enough to overlap with Y, and the length of X * better not be large enough to overlap with the packet's sequence. * This means the packet is trying to overwrite data and I simply drop it. */ static bool is_valid_entry(context_t* ctx, tcp_seq target_seq, size_t data_len) { if (target_seq< ctx->last_ack_sent){ our_dprintf("Received Packet Outside receiver window with seq: %d\n", target_seq); return false; } struct p_entry* prev = ctx->rbuffer.start; struct p_entry* curr = ctx->rbuffer.start->next; while(curr) { tcp_seq curr_seq = ((struct tcphdr *)curr->packet)->th_seq; if (target_seq==curr_seq){ our_dprintf("Received Duplicate Packet with seq: %d\n", target_seq); return false; } else if (target_seq < curr_seq) { if (curr->next){ tcp_seq next_seq = ((struct tcphdr *)curr->next->packet)->th_seq; if ( (target_seq + data_len) > next_seq) { our_dprintf("Packet overwriting 1!!! TARGET SEQ:%d DATA_LEN:%d NEXT_SEQ:%d CURR SEQ:%d\n", target_seq, data_len, curr_seq); assert(!DEBUG); return false; } } if (prev!=ctx->rbuffer.start) { tcp_seq prev_seq = ((struct tcphdr *)prev->packet)->th_seq; size_t prev_len = prev->packet_size - TCP_DATA_START(prev->packet); if ((target_seq>prev_seq) && ((prev_seq + prev_len) > target_seq)){ our_dprintf("Packet overwriting 2!!! PREV SEQ:%d PREV_LEN:%d TARGET_SEQ:%d CURR SEQ:%d\n", prev_seq, prev_len, target_seq, curr_seq); assert(!DEBUG); return false; } } } prev = curr; curr = curr->next; } return true; }
int handle_cstate_closed(mysocket_t sd, context_t * ctx, bool_t is_active) { int ret = 0; our_dprintf("in CSTATE_CLOSED\n"); if (is_active) { send_syn_ack_fin(sd, ctx, SEND_SYN, ctx->initial_sequence_num, 0); ctx->connection_state = CSTATE_SYN_SENT; } else { ctx->connection_state = CSTATE_LISTEN; } return ret; }
/* *************************************************** * Function: send_ack * *************************************************** * Small helper function for preparing an ACK packet and sending * it to the network. */ static void send_ack(mysocket_t sd, context_t* ctx) { struct tcphdr *ack_pkt = (struct tcphdr *)calloc(1, (TH_MIN_OFFSET*sizeof(uint32_t))); ack_pkt->th_flags = TH_ACK; ack_pkt->th_ack = ctx->last_ack_sent; ack_pkt->th_seq = ctx->nxt_seq_num; our_dprintf("Sending ack: %d with seq: %d\n", ack_pkt->th_ack, ack_pkt->th_seq); ack_pkt->th_win = RWIN; ack_pkt->th_off = TH_MIN_OFFSET; network_send(sd, ack_pkt, TH_MIN_OFFSET*sizeof(uint32_t)); free(ack_pkt); }
int close_tcp_conn(mysocket_t sd, context_t * ctx) { while(ctx->connection_state != CSTATE_CLOSED) { switch (ctx->connection_state) { case CSTATE_FIN_WAIT_1: /* first fin wait state */ handle_cstate_fin_wait_1(sd, ctx); break; case CSTATE_FIN_WAIT_2: /* second fin wait state */ handle_cstate_fin_wait_2(sd, ctx); break; case CSTATE_CLOSING: /* currently closing */ handle_cstate_closing(sd, ctx); break; case CSTATE_TIME_WAIT: /* waiting timeout */ handle_cstate_time_wait(sd, ctx); break; case CSTATE_CLOSE_WAIT: /* close wait state */ handle_cstate_close_wait(sd, ctx); break; case CSTATE_LAST_ACK: /* last ack */ handle_cstate_last_ack(sd, ctx); break; default: /* shouldn't happen */ our_dprintf("bad close ctcp state\n"); assert(0); } } ctx->done = TRUE; our_dprintf("ALL DONE\n"); return 0; }
/* *************************************************** * Function: sendrcv_syn * *************************************************** * Helper function for active-side network initiation. * It sends the SYN packet and waits for the right SYN_ACK */ static bool sendrcv_syn(mysocket_t sd, context_t *ctx, struct tcphdr* in_packet) { /* Set SYN Headers */ struct tcphdr* out_packet = (struct tcphdr *)calloc(1,(TH_MIN_OFFSET*sizeof(uint32_t))); out_packet->th_ack = 0; out_packet->th_seq = ctx->initial_sequence_num; out_packet->th_flags = TH_SYN; out_packet->th_off = TH_MIN_OFFSET; out_packet->th_win = RWIN; ctx->nxt_seq_num = ctx->initial_sequence_num + 1; /* SYN Packet = 1 byte */ our_dprintf("Active Sending SYN. Seq:%d\n", out_packet->th_seq); unsigned int event; bool success = false; int num_tries = 1; struct timeval tstart; gettimeofday(&tstart,NULL); /*start the timer */ network_send(sd, out_packet, (TH_MIN_OFFSET*sizeof(uint32_t))); /* Try sending the SYN a total of 6 times before giving up */ while ((num_tries < MAX_RT_TRIES) && !success){ event = wait_for_event(sd, NETWORK_DATA, ctx, tstart); if (event & NETWORK_DATA) { network_recv(sd, in_packet); /* Check to see if we got the right SYN_ACK */ success = ((in_packet->th_flags & (TH_SYN | TH_ACK)) && (in_packet->th_ack == ctx->nxt_seq_num)); } else if (event==TIMEOUT){ our_dprintf("Active - SYN TIMEOUT!, no SYN_ACK!\n"); gettimeofday(&tstart,NULL); num_tries+=1; our_dprintf("Active Resending SYN. Seq:%d\n", out_packet->th_seq); network_send(sd, out_packet, (TH_MIN_OFFSET*sizeof(uint32_t))); } } free(out_packet); return success; }
int handle_cstate_last_ack(mysocket_t sd, context_t * ctx){ int ret = 0; unsigned int event; our_dprintf("in CSTATE_LAST_ACK\n"); event = stcp_wait_for_event(sd, NETWORK_DATA | TIMEOUT, NULL); /* check whether it was the network, app, or a close request */ uint8_t buff[MAX_WINDOW_SIZE + sizeof(struct tcphdr)]; if (event & NETWORK_DATA) { /* there was network data received */ size_t recd; recd = stcp_network_recv(sd, buff, MAX_WINDOW_SIZE); if (recd == 0) { our_dprintf("bad network recv\n"); return -1; } struct tcphdr * tcp_packet = (struct tcphdr *) buff; if (tcp_packet->th_flags & TH_ACK){ /* if ack */ if (ntohl(tcp_packet->th_ack) < ctx->sent_last_byte_acked || ntohl(tcp_packet->th_ack) > ctx->sent_last_byte_written+1){ our_dprintf("bad ack, expected %u, received %u. returning \n", ctx->sent_last_byte_sent+1, tcp_packet->th_ack); return -1; } //our_dprintf("*** got an ack %u\n", ctx->sent_last_byte_acked); ctx->sent_last_byte_acked = ntohl(tcp_packet->th_ack); ctx->connection_state = CSTATE_CLOSED; } } return ret; }
/* *************************************************** * Function: handle_network_data * *************************************************** * A packet has arrived. If its an ACK packet, call a helper * function that deals with acks. If the packet has data or it's * a FIN packet, then add the packet to the receiver buffer only * if it fits in the receiving window. If it doesn't fit, * drop the packet. If it fits, send an acknowledgement. */ static void handle_network_data(mysocket_t sd, context_t *ctx) { char *pkt = (char *)calloc(1, (TH_MAX_OFFSET*sizeof(uint32_t)) + STCP_MSS); size_t total_length = network_recv(sd, pkt); struct tcphdr* hdr = (struct tcphdr *)pkt; ctx->recv_win = MIN(hdr->th_win, CWIN); if (hdr->th_flags & TH_ACK) { handle_ack(hdr, sd, ctx); } size_t data_length = total_length - TCP_DATA_START(hdr); if (data_length>0 || (hdr->th_flags & TH_FIN)) { our_dprintf("Received %d bytes of data with seq: %d, last ack sent: %d\n", data_length, hdr->th_seq, ctx->last_ack_sent); /* Does the packet fit in the receiving window?*/ if ((hdr->th_seq + data_length)<=(ctx->last_ack_sent + RWIN)) { add_to_recv_buffer(ctx, pkt, total_length); if (hdr->th_seq == ctx->last_ack_sent){ /* The packet arrived in order. We can remove all in-order * packets from the receive buffer now */ our_dprintf("Packet In Order. Handling buffer\n"); handle_recv_buffer(sd, ctx); } else { if (hdr->th_seq > ctx->last_ack_sent) { our_dprintf("This packet is out of order. Adding to recv buffer\n"); } } send_ack(sd, ctx); } else { our_dprintf("Packet outside receiver buffer. Dropping!!!\n"); } } free(pkt); }
/* *************************************************** * Function: handle_recv_buffer * *************************************************** * This function is called whenever we get the next packet * we are expecting from the sender in-order. It iterates * through the receive buffer sending packets to the application * for consumption. When a packet is consumed, it is freed and * removed from the buffer. The loop stops as soon as we see * that the next packet is not in order or when we have emptied out * the buffer entirely (whichever happens first). */ static void handle_recv_buffer(mysocket_t sd, context_t *ctx) { struct window* win = &(ctx->rbuffer); struct p_entry* curr = win->start->next; assert(curr || !DEBUG); tcp_seq next_seq, curr_seq; size_t data_len; bool is_fin = false; bool stop_loop = false; /* Iterate through all entries in the receive buffer */ while (win->num_entries>0 && !stop_loop) { data_len = curr->packet_size - TCP_DATA_START(curr->packet); curr_seq = ((struct tcphdr *)curr->packet)->th_seq; /* Special case, FIN packet in receive buffer */ is_fin = (((struct tcphdr *)curr->packet)->th_flags & TH_FIN); if (is_fin){ assert(curr->next==NULL || !DEBUG); ctx->last_ack_sent+=1; if (data_len>0) { /* FIN packet with data. Send data to app */ stcp_app_send(sd, curr->packet + TCP_DATA_START(curr->packet), data_len); } handle_fin(sd, ctx); /* Call helper function */ break; } our_dprintf("Sending packet with Seq: %d to application \n", curr_seq); ctx->last_ack_sent = curr_seq + data_len; stcp_app_send(sd, curr->packet + TCP_DATA_START(curr->packet), data_len); /* Have we reached the end of the buffer?*/ if (curr->next){ /* No, exit only if next sequence is not in order */ next_seq = ((struct tcphdr *)curr->next->packet)->th_seq; if ((data_len + curr_seq)!=next_seq){ stop_loop = true; } } /* Update pointers and free memory */ win->start->next = curr->next; if (win->end == curr){ win->end = win->start; } free(curr->packet); free(curr); curr = win->start->next; win->num_entries-=1; } }
/* *************************************************** * Function: add_to_send_buffer * *************************************************** * A data/fin packet is being sent out. This function adds that packet * to the sender's window (end of link list) in case it timeouts and * needs to be retransmitted. It records the time the packet was sent. */ static void add_to_send_buffer(context_t* ctx, char* pkt, size_t len) { struct window* win = &(ctx->sbuffer); assert((win->end->next==NULL) || !DEBUG); struct p_entry* new_entry = (struct p_entry*)calloc(1,sizeof(struct p_entry)); new_entry->packet = (char *)calloc(1, len); memcpy(new_entry->packet, pkt, len); new_entry->packet_size = len; new_entry->next = NULL; new_entry->num_transmits = 1; gettimeofday(&(new_entry->tstart), NULL); win->end->next = new_entry; /* Attach to the end of the list */ win->end = win->end->next; /* Update the end of the list */ win->num_entries+=1; /* Add 1 to the number of entries */ assert(win->start->next || !DEBUG); our_dprintf("Added Seq: %d to send buffer\n", ((struct tcphdr*)pkt)->th_seq); }
/* initialise the transport layer, and start the main loop, handling * any data from the peer or the application. this function should not * return until the connection is closed. */ void transport_init(mysocket_t sd, bool_t is_active) { context_t *ctx; int res; ctx = (context_t *) calloc(1, sizeof(context_t)); assert(ctx); generate_initial_seq_num(ctx); ctx->connection_state = CSTATE_CLOSED; ctx->send_wind = (uint8_t *) malloc(MAX_WINDOW_SIZE); memset(ctx->send_wind, 0, MAX_WINDOW_SIZE); ctx->recv_wind = (uint8_t *) malloc(MAX_WINDOW_SIZE); memset(ctx->recv_wind, 0, MAX_WINDOW_SIZE); res = open_tcp_conn(sd, ctx, is_active); /* XXX: you should send a SYN packet here if is_active, or wait for one * to arrive if !is_active. after the handshake completes, unblock the * application with stcp_unblock_application(sd). you may also use * this to communicate an error condition back to the application, e.g. * if connection fails; to do so, just set errno appropriately (e.g. to * ECONNREFUSED, etc.) before calling the function. */ stcp_unblock_application(sd); if (ctx->connection_state == CSTATE_ESTABLISHED){ control_loop(sd, ctx); } else if (ctx->connection_state == CSTATE_FIN_WAIT_1){ close_tcp_conn(sd, ctx); } else if (ctx->connection_state == CSTATE_CLOSED) { ; }else { our_dprintf("bad state in transport init"); assert(0); } /* do any cleanup here */ free(ctx->send_wind); free(ctx->recv_wind); free(ctx); }
/* *************************************************** * Function: remove_until_seq * *************************************************** * When we receive an ACK, this is the function we call to * remove all entries up to the new/updated bottom of the * send window. */ static void remove_until_seq(context_t* ctx) { struct window* win = &(ctx->sbuffer); assert((win->start->next!=NULL) || !DEBUG); assert((win->num_entries>0) || !DEBUG); while (win->num_entries>0){ struct p_entry* victim = win->start->next; tcp_seq sequence = ((struct tcphdr*)(victim->packet))->th_seq; if (sequence>=ctx->seq_base) break; our_dprintf("Removing Seq: %d from sender's buffer\n", ((struct tcphdr*)victim->packet)->th_seq); if (victim==win->end) { win->end = win->start; } win->start->next = win->start->next->next; win->num_entries-=1; free(victim->packet); free(victim); } }
/* *************************************************** * Function: send_fin * *************************************************** * The application has requested to be closed and there * is at least 1 byte of room in the window. This function * prepares a fin packet, adds it to the send buffer, and * sends it. It also updates the connection state. */ static void send_fin(mysocket_t sd, context_t *ctx) { our_dprintf("App requested close there is space in window... SENDING FIN\n"); struct tcphdr* fin_pkt = (struct tcphdr *)calloc(1,TH_MIN_OFFSET*sizeof(uint32_t)); fin_pkt->th_seq = ctx->nxt_seq_num; ctx->fin_ack = fin_pkt->th_seq + 1; ctx->nxt_seq_num += 1; /*FIN = 1 byte of data */ fin_pkt->th_off = TH_MIN_OFFSET; fin_pkt->th_flags = TH_FIN; fin_pkt->th_win = RWIN; /* Update State variables depending on previous state */ if (ctx->connection_state == CSTATE_ESTABLISHED ) { ctx->connection_state = CSTATE_FIN_WAIT1; } else { assert(ctx->connection_state == CSTATE_CLOSE_WAIT || !DEBUG); ctx->connection_state = CSTATE_LAST_ACK; } add_to_send_buffer(ctx, (char *)fin_pkt, (TH_MIN_OFFSET*sizeof(uint32_t))); network_send(sd, fin_pkt, TH_MIN_OFFSET*sizeof(uint32_t)); free(fin_pkt); }