Computer Networks Project 2


The goals of this project are:

  1. to learn about TCP connection establishment
  2. to practice error detection
  3. to learn about retransmission
  4. to learn about TCP windows
  5. to become familiar with TCP header formats, including the TCP pseudo-header

This is an individual project. You may discuss ideas with your colleagues, both on and off the mailing list, but you must be the sole author of all your code.

The project is due Tuesday, October 29th, at any time. Submission is electronic, following the same rules as project 1, i.e. send me all your source code, including your makefile and your status file, as attachments (preferably text/plain only), without binaries. You are welcome to tar, zip, or otherwise gather and compress your files before sending them.

No credit will be given for late submissions.

The assignment is to implement:

This project will run this TCP over UDP on any host that you have available.

The only IP processing needed is including the IP pseudo-header in the TCP checksum.

Project Specification

Your job is to implement a sockets library, tcp.c. I have provided a skeleton for it, tcp.c, which you should modify to implement the desired functionality. You should make it at least functional enough to allow running of the files server.c and client.c, which simply send a file to each other using specified buffers and shared code in common.c and common.h. You are also welcome to use the makefile.

In running server and client, you must start the server before the client (unless you are trying to test client retransmission), and must provide the correct local (and for the client, remote) IP addresses. You may pick any port numbers you like, though the server and client must match (i.e. the client's remote port must be the same as the server's local port).

Run the server or the client without arguments to see what arguments they take. For both, the last argument is a file to send to the peer.

If you are uncertain about what the sockets API functions do (that you are implementing), I encourage you to consult the man pages, the Internet, and the instructor and your colleagues.

While you are of course free to modify server.c and client.c, I suggest you use an unmodified copy for your final testing, since that is what the instructor plans to use.

For your TCP server to communicate to its peer, it must begin by calling read_config_file(), which reads a simconfig file with the same format as one in project 1 to initialize the global variable udp_sim. Only 1 line is needed in the simconfig file, since a TCP connection is only with one peer.

Once udp_sim has been initialized, udp_sim.remote contains the address that can be used in calls to sendto to send packets to the peer (you should use a different sockaddr in calls to recvfrom).

The maximum size sent in any single send operation is 536 bytes.

Because of the need for retransmission and to handle incoming packets (e.g. acks, new data) at any time, your tcp.c should be multithreaded. The clients and servers are also multithreaded, with one thread calling tcp_read and the other thread calling tcp_write, so that is all the user-level synchronization you need to be able to handle. Of course, your tcp_read has to block and wait for data if no data is available.

A few other constraints:

  • The maximum segment size (MSS) for your TCP, that is, the maximum amount of data you send in any one segment, is 536 bytes. This does not include the TCP header. You should be reasonably generous in what you accept, and in particular, be sure that you can receive a 1500-byte Ethernet packet.
  • Data should be sent as fast as possible, that is, without unnecessary pauses, but no faster than allowed by the send window. Once the send window is zero, sending should stop until the peer allows the window to grow again.
  • Any packet received with a TCP checksum error should be discarded without further action except printing a message to the screen. Likewise for any packet received with an incorrect source or destination port number.
  • Any packet that is not acknowledged within 5 seconds (except for packets that TCP doesn't ack) should be retransmitted.

    Some Details

    To allow me to test different buffer sizes, your code must use the symbolic constant TCP_RCV_BUFFER_SIZE rather than a numeric value. The declaration given above allows me to test your code with different buffer sizes by compiling with, e.g. -DTCP_RCV_BUFFER_SIZE=1234.

    You are welcome to use the code in checksum.c. It has been tested and should work correctly for any size array. The function returns the inverted checksum in network byte order, so that an array with a correct checksum in it will checksum to x0000 or xffff.

    More in general, you may re-use any code I have provided as part of this project description. As always, if you find suitable code elsewhere on the web or from any other source, you must (a) cite it (i.e. tell me about it), and (b) only take inspiration from it, not simply copy it.

    Your code has to create file descriptors and use them to distinguish different sockets. The client only creates one socket. The server creates a passive socket, and then repeatedly creates an active socket for communication, and closes it before again calling tcp_accept. Use any integers you wish, as long as they help you keep track of the different sockets. FYI, A Unix file descriptor is an index into an array of information about files.

    You MUST include the TCP pseudo-header in your checksum computation. This is a very simple concept -- some fields are included in the checksum computation, but not transmitted as part of the TCP packet (they are normally transmitted as part of the IP header, except in this project we don't send the IP header). The receiver takes these values from the IP header, and uses them in checking the TCP checksum. Since values in the IP header can change without affecting the IP checksum (how is this possible? Good question for debate on the mailing list, though I also explained it in class), this ensures that the packet has been delivered to the correct destination and has the correct number of bytes.

    Since in this project no IP header is actually sent, the IP addresses for the pseudo-header are taken from global variables called local_IP and remote_IP, of type in_addr_t (equivalent to a uint32_t). local_IP and remote_IP are initialized by server.c and client.c using values from the command line.

    The protocol for the pseudo-header is always 6.

    The details of the TCP checksum computation are on p. 17 of RFC 793. One tricky part is the TCP length, which is not the same as the total length sent in the IP header -- it is less by the size of the IP header. Since this project doesn't use IP headers anyway, this part may not be relevant to you.

    The other tricky part is storing the checksum into the buffer: a statement such as

      tcp_header->checksum = inverted_checksum (buffer, buffersize);
    
    should do so. The checksum computation can be done in either endian-ness, and can be stored directly without converting to network byte order. This is because if the addition is done with the bytes "reversed", the same result is computed but with the bytes "reversed" -- this can then be stored directly into the buffer, an operation which will "reverse" the bytes and store the checksum correctly. See RFC 1071, section 2B, for details.

    Although TCP allows data transmission with the very first SYN packet, for this project you should wait until you receive an ack to your SYN before sending any data.

    If the sequence number of your SYN packet is x, the ack for this SYN is number x+1, and the sequence number of the first data packet after the SYN should be x+1. If the first data packet contains n data bytes, the ack for it should be x+1+n, and the sequence number of the second data packet should also be x+1+n. If the last packet before the FIN has sequence number y and m bytes of data, the sequence number of the FIN packet is y+m, and the corresponding acknowledgement number is y+m+1.

    The TCP three-way handshake is designed to get both parties SYNchronized, i.e. to exchange initial sequence numbers and initial window sizes. My own and my peer's values for these important numbers must be saved in a data structure called a Transmission Control Block, or TCB. Real systems have a collection of TCBs, one for each open socket, but for this project a single TCB is sufficient, so if you wish, your TCB may simply be a collection of global variables.

    TCP actually has an adaptive timer, but there is no need to implement it in this project -- the 5-second timer should let you watch TCP at work.

    Your code in tcp.c may set tcp_error to indicate the cause of any error. This value will be printed by the client or the server when a call returns an error, so may help you in debugging.

    I do make mistakes, and this project has been reworked for this year's class. If you think you have found mistake either in the description of this protocol or in any of the software, please send mail to me or to the class mailing list.

    Extra fun

    If you have extra time after finishing this project, you may modify it to create and send the IP as well as the TCP header -- at your choice, the IPv4 header, IPv6, or both. In case of both, you can use the AF_FAMILY field to determine whether an address is IPv4 or IPv6. To use IPv6, you will have to modify server.c and client.c. When this project was originally designed, sending and receiving IPv4 headers was a requirement!

    Such packets could optionally be sent using AF_PACKET sockets instead of the UDP sockets described in this project. You could then interoperate with regular TCP and IP stacks instead of just the ones in this project.

    Needless to say, you should only attempt projects in this section if you have completely finished the regular project at least a week before the deadline.

    Software List

    $ md5sum *.[ch] makefile license
    86ef91134e32eead026f197b13ac70e9  checksum.c
    6cecbadc691a0319a8cd789ea0901d3c  client.c
    7a258ac72b6885ca3fa0b07cbd612c77  common.c
    53b55647998d36ae23d6e9e994dc6f80  common.h
    48f4167148de6d114724d28dcd4998ef  server.c
    089095cb934724609bab6ac7cec05486  tcp.c
    10e81d3f34947e29352ded19a8df9e92  makefile
    7eca32d6c83aee4663b0ba2d08bf4642  license