/* * Somewhat Deficient TCP Server Connection Emulator (SDTSCE) * * This code emulates the server side of a TCP connection, in a * somewhat deficient manner. It waits for a SYN on a port, * responds with a SYN+ACK, and then sends packets representing a * fixed payload response (drawn from a real Apache web server) * back to the connecting client. It neither stores nor ACKs any * client data. * * This version handles retransmissions: for every packet sent, a * timer is started. If the packet is not ACK-ed within 1 second, * it is retransmitted and the retransmission timer is reset. * Retransmissions will continue until the payload of the packet * is ACK-ed (including cummulatively). * * This code statically pre-allocates the memory for the packets * in flight and their timers. This is wasteful, and would break * for larger files or smaller MSS of TCP packets. * * Find bugs, get extra points. */ /* * Most code cut, only the time-out logic left. */ #include #include #include #include #include // open #include #include #include #include #include // poll #include // timers #include // inet_addr #include #include #include "tcb.h" #include "pkt.h" // Keep the initial Seq number the same for easier debugging #define INIT_SEQ 0xAAAABBBB // Preallocate space for this many packets in flight and their respective timers #define MAX_POLLED 200 void set_rto_timer(pkt_t*, int i); void reset_rto_timer(pkt_t*, int i); void clear_rto_timer(pkt_t *pkt, int i); int tcp_raw_socket(); void recv_packet(pkt_t *pkt); // global timers struct pollfd polled[MAX_POLLED]; int num_polled = 1; // active polling is from num_acked to num_polled-1 int num_acked = 0; pkt_t pkt_in_flight[MAX_POLLED]; // 0 is not used int main(){ int sockfd; int i; sockfd = tcp_raw_socket(); // Polled will always start with the raw socket, then use 1..MAX_POLLED-1 for timers polled[0].fd = sockfd; polled[0].events = POLLIN; for( i=1; i < MAX_POLLED; i++ ){ polled[i].fd = -1; // poll ignores negative fds polled[i].events = POLLIN; } // State machine implementation. Nothing is allowed to fall through without // at least a message while(1){ recv_packet(&pkt); // blocking; gets a packet, handles a timer, or exits. if( pkt.invalid ) // timeout in poll continue; if( ! matches_tcb( &pkt, &tcb) ){ // a real TCP stack would send a RST here fprintf(stderr, "Packet doesn't match the TCB, ignoring packet:\n"); print_packet_summary(stderr, &pkt); continue; } if( IS_SYN(&pkt) && tcb.state == TCP_LISTEN ){ // send SYN+ACK make_and_send_synack( &pkt, &tcb); tcb.state = TCP_SYN_RECV; } // } else { // There must a fprintf(stderr, "Wrong state %d (%s) for packet, ignoring:\n", tcb.state, tcp_states[tcb.state] ); print_packet_info(stderr, &pkt); // A real stack would send a RST here; instead, we just go to LISTEN tcb.state = TCP_LISTEN; // clean-up skipped } } } // When sending a packet, start a timer on it! E.g.: // make_and_send_payload_with_ack( pkt, &pkt_in_flight[i], payload, payload_sz, tcb ); set_rto_timer(&pkt_in_flight[i], i); // // When you receive an ack, find and clear the timer for that packet clear_rto_timer(pkt, i); // Create a raw socket int tcp_raw_socket() { const int on = 1; int sockfd; // create a raw socket for getting and sending packets sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (sockfd < 0){ perror("sock:"); exit(1); } // Set flag so socket expects us to provide IPv4 header. if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof (on)) < 0) { perror ("setsockopt() failed to set IP_HDRINCL "); exit (1); } return sockfd; } // Listens on global polled[], takes action on expired timers void recv_packet(pkt_t *pkt) { int n, rv, i; socklen_t clilen = sizeof(struct sockaddr_in); int sockfd = polled[0].fd; char *buf = pkt->buf; pkt->invalid = 0; printf(" at poll\n"); rv = poll( polled, num_polled, 10000 ); if (rv == -1) { perror("poll:"); // error occurred in poll() } else if (rv == 0) { fprintf(stderr, "Timeout occurred! No data after 10 seconds.\n"); pkt->invalid = 1; return; } else { // We got a packet if (polled[0].revents & POLLIN) { n = recvfrom(sockfd, buf, IP_MAXPACKET, 0, (struct sockaddr *)&(pkt->sin), &clilen); if( n < 0 ){ perror("recv: "); exit(1); } else if( n == 0 ){ fprintf( stderr, "Raw socket recv returned 0; this should not happen\n"); exit(1); } else printf(" rec'd %d bytes\n", n); // Build packet pkt // // print whatever info helps to debug print_packet_info(stdout, pkt); } // done with a packet, now handle timers for( i=1; i < num_polled; i++ ){ // we can actually start with num_acked if(polled[i].revents & POLLIN) { uint64_t exp; // must read the expired timer int s = read(polled[i].fd, &exp, sizeof(uint64_t)); if (s != sizeof(uint64_t)){ perror("timer read: "); exit(1); } fprintf(stderr, "Timeout of TRO %d: %lu\n", i, exp); // restransmit timed-out packet retransmit_on_timeout(&pkt_in_flight[i], i); } } // done with timers } // end poll } // retransmit packet and reset its timer void retransmit_on_timeout( pkt_t *pkt, int i ) { // resend packet with sendto() int sockfd = pkt->sockfd; struct sockaddr *sin = (struct sockaddr*)&(pkt->sin); fprintf(stderr, "Retransmitting pkt %d ack %X (sock %d)\n", i, pkt->ack, sockfd ); if (sendto (sockfd, pkt->buf, pkt->len, 0, sin, sizeof (struct sockaddr_in)) < 0) { perror ("sendto() failed on retransmission"); exit (1); } reset_rto_timer( pkt, i ); } // set as timer i in polled void set_rto_timer(pkt_t *pkt, int i) { struct itimerspec itspec; int tfd = timerfd_create(CLOCK_MONOTONIC, 0); if (tfd == -1){ perror("timerfd_create: "); exit(1); } itspec.it_value.tv_sec = 1; // expire in 1 sec itspec.it_value.tv_nsec = 0; itspec.it_interval.tv_sec = 0; // ...just once itspec.it_interval.tv_nsec = 0; if (timerfd_settime(tfd, 0, &itspec, NULL) == -1){ perror("timerfd_settime: "); exit(1); } // now activate in polled polled[i].fd = tfd; polled[i].events = POLLIN; num_polled = i+1; // now i is polled pkt->rto_fd = tfd; } // reset as timer i in polled void reset_rto_timer(pkt_t *pkt, int i) { struct itimerspec itspec; int tfd = pkt->rto_fd; itspec.it_value.tv_sec = 1; // expire in 1 sec itspec.it_value.tv_nsec = 0; itspec.it_interval.tv_sec = 0; // ...just once itspec.it_interval.tv_nsec = 0; if (timerfd_settime(tfd, 0, &itspec, NULL) == -1){ perror("reset RTO timerfd_settime: "); exit(1); } } // clear as timer i in polled void clear_rto_timer(pkt_t *pkt, int i) { int tfd = pkt->rto_fd; // de-activate in polled[]. poll() ignores negative fds polled[i].fd = -tfd; // or -1. I left -tfd for debugging. close(tfd); // this will dellocated the timer, otherwise you'll run out of FDs }