Programming Project 1

Due Wednesday 11/9 @ 11:59pm

Overview

In this programming assignment, you will implement a simple, reliable, sequenced message transport protocol (RMP) on top of UDP. Recall that UDP provides point-to-point, unreliable datagram service between a pair of hosts. In this programming assignment, we will develop a more structured protocol which ensures reliable, end-to-end delivery of messages in the face of packet loss, preserves ordering of transmitted messages, and preserves message boundaries.

Unlike TCP, RMP preserves message boundaries, delivering entire messages to the application, and thus is not a logical bytestream. Also, RMP will not provide congestion control mechanisms, although it will support window-based flow control using a receiver-advertised window. Finally, whereas TCP allows fully bidirectional communication, our implementation of RMP will be asymmetric. The two RMP endpoints will be designated the "sender" and the "receiver" respectively. Data packets will only flow in the "forward" direction from the sender to the receiver, while acknowledgments will only flow in the "reverse" direction from the receiver back to the sender.

To support reliability in a protocol like RMP, state must be maintained at both endpoints. Thus, as in TCP, connection set-up and connection teardown phases will be an integral part of the protocol.

As we mentioned earlier, congestion control will not be a part of RMP. However, this does not imply that a sender can transmit as fast as it pleases. RMP uses window-based flow control whereby the receiver regulates the sender by transmitting an advertised window (as in TCP) which bounds the amount of data RMP can inject into the network at any point in time.

Binaries and Source Code

The basic receiever (gzipped ReceiveApp binary)

The augmented receiever (5% packet loss, ack loss, out of order arrival rate).

Advanced (and not required): If you have the basic receiver working, and are debugging your code against the augmented receiver, you may want to modify the source code to test various configurations. Use the makefile to compile. NOW POSTED

RMP

Packet Headers

RMP packets have 3 *two*-byte fields, "type", "window", and "seqno". Each of these store unsigned integer values. The "type" field takes on 5 possible values. DATA = 0, ACK = 1, SYN = 2, FIN = 3, RESET = 4. Unlike TCP, in which multiple types can be set simultaneously, RMP packets must be of exactly one of the types specified above.

The "window" field specifies the receiver's advertised window in bytes and is used only in packets of type 1 (ACK). The window field must be set to zero for all packets of other types. The maximum window size is 2^16 - 1.

The "seqno" field indicates the sequence number of the packet. This field is used in all packets except RESET packets, when it is set to zero. For DATA packets, the sequence number increases by the size (in bytes) of each packet. For ACK packets, the sequence number acts as a cumulative acknowledgment, and indicates the number of the next byte expected by the receiver. For SYN packets, the sequence number is one smaller than the sequence number of the first data packet of the connection. For FIN packets, the sequence number is one larger than the sequence number of the last byte of the last data packet of the connection. The maximum sequence number is 2^16 - 1 and the maximum message size in RMP is 1000 bytes.

State Diagram

The asymmetry between sender and receiver in this protocol leads to somewhat different state diagrams for the two endpoints. The state diagram for RMP is as follows:

The receiver can be in four possible states: CLOSED, LISTEN, ESTABLISHED and TIME_WAIT. Initially, it is in the CLOSED state. Upon issuing a passive open, it enters the LISTEN state. (Note that the receiver is the passive host in our protocol, while the sender actively opens the connection). While in the LISTEN state, the receiver waits for a SYN packet to arrive on the correct port number. When it does, it responds with an ACK, and moves to the ESTABLISHED state. After the sender has transmitted all data (and received acknowledgments), it will send a FIN packet to the receiver. Upon receipt of the FIN, the receiver moves to the TIME_WAIT state. As in TCP, it remains in TIME_WAIT for two maximum segment lifetimes (MSLs) before re-entering the CLOSED state. Assume for this assignment that the MSL is 2 seconds.

The sender can be in five possible states: CLOSED, SYN_SENT, ESTABLISHED, CLOSING and FIN_WAIT. Like the receiver, the sender starts in the CLOSED state. It then issues an active open by sending a SYN packet (to the receiver's port), thus entering the SYN_SENT state. This SYN transmission also includes the inital sequence number (ISN) of the conversation. The ISN should be chosen at random from the valid range of possible sequence numbers. Any packet (including the SYN packet) which is not acknowledged in 2 seconds must be retransmitted. If the SYN packet is not acknowledged after three attempts, a RESET packet must be sent to the destination port and the sender moves to the CLOSED state. In the common case in which the SYN is acknowledged correctly (the ACK must have the correct sequence number = ISN + 1), the sender enters the ESTABLISHED state and starts transmitting packets. When the sending application (sitting above RMP) is finished generating data, it issues a "close" operation to RMP. This causes the sender to enter the CLOSING state. At this point, the sender must still ensure that all buffered data arrives at the receiver reliably. Upon verification of successful transmission, the sender sends a FIN packet with the appropriate sequence number and enters the FIN_WAIT state. Once the FIN packet is acknowledged, the sender re-enters the CLOSED state. If no ACK comes after three timeouts (which are also 2 seconds each), the sender should send a RESET packet and return to the CLOSED state.

In the event that one party detects that the other is misbehaving, it should reset the connection by sending a RESET packet. For example, one easy way in which a receiver can misbehave is to acknowledge a packet that has not yet been transmitted.

Implementation

We have implemented the the RMP receiver process; you must write the sender functionality. Your code will consist of a very simple application-level component, and an implementation of the transport-layer functionality to support the RMP send API. You may write the sender in any language you choose including Java, Python, C, or C++. The application-level component is to read in a file specified on the UNIX command line, open up an RMP connection with parameters also specified on the command line, then send the contents of the file (reliably) in 1000 byte datagrams using the RMP API. The RMP send API consists of the following three methods:

The first method is called once by the application at the sending side to open and establish an RMP connection to a remote receiver. The second function is called repeatedly by the application to transmit a message across an established connection, and the final function is used by the application when it has no more messages to transmit, and wishes to teardown the connection. (Our life is simplified by virtue of having only one RMP connection open at a time). To manage your protocol, you will need a collection of RMP state variables to help you keep track of things like state transitions, sliding windows and buffers. In TCP, this data structure is referred to as a control block: it's probably a good idea to create a control block class of your own, and have a member variable of this type in your primary class (such as rmp_send_socket).

While a typical protocol implementation would consist of many threads of control, we will simplify things by using a single-threaded approach to implementing the sender's functionality. One possible decomposition of rmp_send() is as follows:

By continuously calling rmp_send(), the data transfer will proceed smoothly. Eventually, when the application has no more data to transmit, it calls rmp_close(). At this point, your RMP connection must make sure all buffered data is transmitted and acknowledged before tearing down the connection.

Testing

To test your code, receiver executables for you to test your sender against are available. Start with the basic receiver which has a small, fixed advertised window and does not lose packets. Correct behavior against this receiver will be worth approximately 60% of the credit. More complex receivers that vary their window size and simulate a network which reorders and drops packets, and misbehave, are also available for you to test against. You should do all of your testing in a local environment without going over the network. By routing packets to "localhost", those packets are sent a special software interface known as the loopback interface, which redirects the packets immediately back to the localhost. You can run our receiver executable in one window on csgateway:

 % ./ReceiveApp localhost 4562 4182 RecvFileName
then run your sender in another window:
 % ./SendApp localhost 4182 4562 SendFileName
ReceiveApp and SendApp are both executables that take four command line arguments: a destination hostname (dh), a destination port (dp), a source port (sp), and an output file name (or input file name for SendApp). Note that source and destination port numbers are reversed in the two calls so that the programs plug together naturally. Our ReceiveApp executable is an application which expects to be sent a file via RMP, and uses the receive-side RMP API (that we've implemented) to accept your sender's connection, reconstruct the file from the data sent over the socket, then writes it to a file specified on the command line (here: RecvFileName).

Simplifications

You need not worry about the following aspects of the implementation.

On the other hand, don't oversimplify. You will need to deal with: This programming assignment will be time-consuming, but will make important concepts we covered in class much more familiar to you. Please make sure to allocate enough time to be able to submit a nicely debugged assignment.

How to Proceed

Large assignments such as this can be daunting and it can be hard to figure out where to start. I strongly recommend implementing the solution piece by piece, testing thoroughly as you go. Here are some suggestions:

Submission and Grading Guidelines

To submit your assignments, please zip up all source files that you used and submit them on Moodle. Please include a README file which briefly describes which portions of the assignment you were able to complete, and tells us how to unzip, compile and run your code on csgateway.clarku.edu. (We cannot test your code on an environment of your choosing, so please test this procedure yourself on csgateway before submitting!). To reiterate: we will be running and testing your code on the csgateway machine -- make absolutely sure that your program compiles and runs correctly on csgateway well before the deadline -- there will be portability issues if you develop your code on another platform, and these may take considerable time to resolve! Programs which do not compile will be worth nothing; programs which run, but are unable to reliably transfer a small file against the basic receiver will get at most 30 percent of the credit.

Academic Honesty and Collaboration

Cooperation is recommended in understanding programming concepts and system features. But the actual solution of the assignments including all programming must be your individual work. For example, copying without attribution any part of someone else's program is plagiarism, even if you have modified the code. The University takes acts of cheating and plagiarism very seriously.