The goals of this project are:
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:
#ifndef TCP_RCV_BUFFER_SIZE #define TCP_RCV_BUFFER_SIZE 4096 #endif /* TCP_RCV_BUFFER_SIZE */
The amount of unused space in this buffer is the window size you should send to your peer. I will never specify a buffer size greater than 65,535 bytes.
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.
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:
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.
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.
$ 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