Project 4: Reliable Transport Protocol

Build something like TCP on top of UDP

Description

You will design a simple transport protocol that provides reliable datagram service. Your protocol will be responsible for ensuring data is delivered in order, without duplicates, missing data, or errors. Since the local area networks at Northeastern are far too reliable to be interesting, we will provide you with access to a virtual machine that will emulate an unreliable network.

For the assignment, you will write code that will transfer a file reliably between two nodes (a sender and a receiver). You may assume that the receiver is run first and will wait indefinitely, and the sender can just send the data to the receiver.

Your Programs

For this project, you will submit two programs: a sending program 3700send that accepts data and sends it across the network, and a receiving program 3700recv that receives data and prints it out in-order. You may not use any transport protocol libraries in your project (such as TCP); you must use UDP. You must construct the packets and acknowledgements yourself, and interpret the incoming packets yourself.

Requirements

You have to design your own packet format and use UDP as the transport layer protocol. Your packet might include fields for packet type, acknowledgement number, advertised window, data, etc. This part of the assignment is entirely up to you. There are several things your code must do:

  • The sender must accept data from STDIN, sending data until EOF is reached
  • The sender and receiver must work together to transmit the data reliably
  • The receiver must print out the received data to STDOUT in order and without errors
  • The sender and receiver must print out specified debugging messages to STDERR
  • Your sender and receiver must gracefully exit
  • Your code must be able to transfer a file with any number of packets dropped, duplicated, and delayed, and under a variety of different available bandwidths and link latencies
  • Your sending program must be named 3700send and your receiving program must be named 3700recv
  • Datagrams generated by your programs must each contain less than or equal to 1472 bytes of data per datagram (i.e., the 1500 byte Ethernet MTU - the 20 byte IP header - the 8 byte UDP header)

You may implement any reliability algorithm(s) you choose. However, more sophisticated algorithms (i.e., those that perform better) will be given higher credit. For example, some desirable properties include (but are not limited to):

  • Fast: Require little time to transfer a file.
  • Low overhead: Require low data volume to be exchanged over the network, including data bytes, headers, retransmissions, acknowledgements, etc.

Regardless, correctness matters most; performance is a secondary concern. We will test your code and measure these two performance metrics; better performance will result in higher credit. Remember that network-facing code should be written defensively. Your code should check the integrity of every packet received. We will test your code by reordering packets, delaying packets, and dropping packets; you should handle these errors gracefully, recover, and not crash.

Language

You can write your code in whatever language you choose, as long as your code compiles and runs on an unmodified Vagrant virtual machine on the command line. Do not use libraries that are not installed by default in the Vagrant VM. You may use IDEs (e.g., Eclipse) during development, but do not turn in your IDE project without a Makefile. Make sure you code has no dependencies on your IDE.

Testing Environment

The goal of this project is to get you to develop a TCP-like, reliable transport protocol. As such, we need a way for you to test your code on an unreliable network, i.e., one that is slow, drops packets, reorders and duplicates packets, etc. Unfortunately, real networks are far too reliable for this purpose. As such, you will develop and test your programs inside a virtual machine (VM) that is pre-configured with tools for creating lossy, slow, unreliable network links.

You will need to download, install, and setup two tools to gain access to the VM for this project: VirtualBox and Vagrant. The former is a tool that can run VMs; the latter is a tool for automating the deployment and configuration of VMs. Both tools are available for Windows, OSX (on Intel chips; if you have an M1 Mac we will help you find a workaround), and major Linux distributions. Note: Vagrant is compatible with other VM tools besides VirtualBox, but we will only be providing support for VirtualBox in this class. You may use other VM tools at your own peril.

Installation

Download and install Oracle VirtualBox v6.1.26 from here. Download and install Vagrant v2.2.18 from here.

After you have installed VirtualBox and Vagrant, you are ready to setup the VM that you will use for this project. Open a terminal and create a folder that will house (1) the VM, (2) the Vagrant configuration files, and (3) synchronized files from within the Vagrant VM (more on this in a little bit). Create this folder in a place you will remember, like your home directory or desktop. Once this directory is created, cd into it and run the following command:

$ vagrant init cs3700/transport-environment --box-version 0.0.1

This will create a Vagrant configuration file in the directory that describes the VM. You do not need to understand or modify this configuration file (unless you have problems with your Vagrant VM, see the notes on issues at the end of the webpage).

Managing and Accessing the VM

From within the directory where the Vagrant configuration is stored, run the following command to boot up the Vagrant VM:

$ vagrant up

This command can take a while, as it downloads the image for the VM, unpacks it, and executes it. Once the command is done you won’t see any changes to your computer, but the Vagrant VM is running in the background. To access the Vagrant VM, use the following command to ssh into it:

$ vagrant ssh

If you are asked for password, use “vagrant”. At this point, you should have sshed into the Vagrant VM, and you are ready to begin working on the project.

To temporarily pause the Vagrant VM, open a terminal, cd into the directory where the Vagrant configuration is stored, and use the following command:

$ vagrant halt

This command is data preserving: the next time you run vagrant up the VM will resume in the same state it was previously in. If you created files in the VM, they will still be present.

To permanently destroy the Vagrant VM and all data within it, use the following command:

$ vagrant destroy

Getting Files Into and Out of the Vagrant VM

The Vagrant VM is a copy of CentOS Linux, and it has its own file system and virtual hard drive that is totally distinct from the file system in your host OS. However, there is a convenient way to get files into and out of the Vagrant VM: by default, any files placed in the /vagrant directory inside the VM will automatically be synchronized to the directory where the Vagrant configuration is stored in the host OS.

PUT ALL YOUR SOURCE CODE IN THE /vagrant FOLDER

We highly recommend that students place all of their source code for this project in the the /vagrant directory so that it will be synchronized and backed-up to their host OS. If the Vagrant VM crashes, is destroyed, or the host OS is rebooted without halting the VM first, all data inside the VM will be lost. We cannot help you recover source code in these cases.

Starter Code

Very basic starter code in C and Python 2 for the project is available in the /transport-starter-code4 directory in the Vagrant VM. You may use this code as a basis for your project, or you may work from scratch. Provided is a simple implementation that sends one packet at a time; it does not handle any packet retransmissions, delayed packets, or duplicated packets. So, it will work if the network is perfectly reliable. Moreover, if the latency is significant, the implementation will use very little of the available bandwidth.

To get started, you should copy down this directory into the /vagrant directory (i.e., cp -r /transport-starter-code/ /vagrant) in your Vagrant VM. This will make the starter code available in your host OS, since the vagrant folder syncs to the host OS. This will ensure that your modifications to these files persist, i.e., are not deleted, if the Vagrant VM shuts down. Further, you will be able to use a desktop IDE to edit these files if you so choose, while retaining the ability to compile and run them within the Vagrant VM.

You can compile the C code by running make. You can also delete any compiled code and object files by running make clean.

Program Specification

The command line syntax for your sending program is given below. The sender program takes command line arguments of the remote IP address and UDP port number. The syntax for launching your sending program is therefore:

$ ./3700send <recv_host>:<recv_port>
  • recv_host (Required) The IP address of the remote host in a.b.c.d format.
  • recv_port (Required) The UDP port of the remote host.

The sender must open a UDP socket to the given IP address on the given port.

The data that the sender must transmit to the receiver must be supplied in STDIN. The sender must read in the data from STDIN and transmit it to the receiver via the UDP socket.

Your sender may print out any debug information that you wish, but it must do so to STDERR.

The command line syntax for your receiving program is given below. The receiving program takes a single command line argument of the UDP port number to bind to. The syntax for launching your receiving program is therefore:

$ ./3700recv <recv_port>

On startup, the receiver must bind to the specified UDP port and wait for datagrams from the sender. Similar to 3700send, you may add your own output messages to your receiver but they must be printed to STDERR.

The receiver program must print out the data that it receives from the sender to STDOUT. The data that it prints must be identical to the data that was supplied to the sender via STDIN. In other words, data cannot be missing, reordered, or contain any bit-level errors.

Testing Your Code

In order for you to test your code over an unreliable network, you will use tools within the Vagrant VM to create a local network that will drop, reorder, duplicate, and delay your packets. You will use the loopback interface in order to access the local network. In other words, you might run something like ./3700recv 3992 in one terminal and then run ./3700send 127.0.0.1:3992 in another terminal. Note the use of the loopback IP address 127.0.0.1.

You may configure the emulated network conditions by calling the following program that is pre-installed within the Vagrant VM:

$ netsim [--bandwidth <bw-in-mbps>] [--latency <latency-in-ms>] [--delay <percent>] [--drop <percent>] [--reorder <percent>] [--duplicate <percent>]
  • bandwidth: This sets the bandwidth of the link in Mbit per second. If not specified, this is 1 Mb/s.
  • latency: This sets the latency of the link in ms. If not specified, this value is 10 ms.
  • delay: This sets the percent of packets the emulator should delay. If not specified, this is 0.
  • drop: This sets the percent of packets the emulator should drop. If not specified, this is 0.
  • reorder: This sets the percent of packets the emulator should reorder. If not specified, this is 0.
  • duplicate: This sets the percent of packets the emulator should duplicate. If not specified, this is 0.

Once you call this program, it will configure the emulator to delay/drop/reorder/duplicate all UDP and ICMP packets sent by or to you at the specified rate. For example, if you called

$ netsim --bandwidth 0.5 --latency 100 --delay 20 --drop 40

the simulator will configure a network with 500 Kb/s bandwidth and a latency of 100 ms, and will randomly delay 20% of your packets and drop 40%. In order to reset it so that none of your packets are disturbed, you can simply call

$ netsim

with no arguments. Note that the simulator is stateful, meaning your settings will persist until you run netsim again or reboot the Vagrant VM.

Helper Script

In order to make testing your code easier, we have included a script in the Vagrant VM that will launch your receiver, launch your sender, feed the sender input, read the output from the receiver, compare the two, and print out statistics about the transfer. This script is included in the Vagrant VM, and you can run it by executing

$ nettest

This script also takes a couple of arguments to determine what it should do:

$ nettest [--live] [--size {small,medium,large,huge}] [--timeout TIMEOUT]
  • size: The size of the data to send, including 1 KB (small), 10 KB (medium), 100 KB (large), MB (huge). Default is small.
  • live: Instructs the script to echo the STDERR output of 3700send and 3700recv. This may add significant processing time, depending on the amount of output.
  • timeout: The maximum number of seconds to run the sender and receiver before killing them. Defaults to 30 seconds.

Note that you must run the nettest command from within the directory that contains your 3700send and 3700recv programs.

The output of the testing helper script include some statistics about the transfer:

$ nettest --size medium
Data match: Yes
Msg Size: 10000 B
Time elapsed: 174.982 ms
Packets sent: 23
Data on Wire: 13761 B

where Data match is whether the data was transferred correctly.

Testing Script

Additionally, we have included a basic test script that runs your code under a variety of network conditions and also checks your code’s compatibility with the grading script. If your code fails in the test script we provide, you can be assured that it will fare poorly when run under the grading script. To run the test script, simply type

$ testall

This test your programs on a number of inputs. If any errors are detected, the test will print out the expected and actual output. Note that you must run the testall command from within the directory that contains your 3700send and 3700recv programs.

Performance Testing

10% of your grade on this project will come from performance. This includes the time it takes for sender to fully transmit files to the receiver, and the number of bytes sent during the transmission. Your project will be graded against a series of benchmarks that we have established.

To help you know how you’re doing, the testing script will run a series of performance tests at the end. For example, you might see something like the following:

Performance tests
huge 5 Mb/s, 10 ms, 0% drop, 0% duplicate 0% delay        [DATAOK]
0.401 sec elapsed, 976KB sent
Rate: 19Mb/s                                              [ OKAY ]

The first line presents the parameters of the current test. [DATAOK] indicates that the file was delivered successfully, without any errors. [DATAERR] would indicate that the receiver printed out a file containing errors. The following lines present the performance characteristics of the file transfer, including how long it took, how much data was sent, and the overall transmission rate. [OKAY] indicates that performance in this case was acceptable, and would receive full credit. Alternatively, you might see output like this:

Rate: 1Mb/s                                               [PERF50]

[PERFXX] indicates that the performance was at XX% of our target benchmark. In this example, performance hit 50% of the desired benchmark. In these cases, credit would be awarded proportionately to the achieved performance level. [FAIL] indicates that performance was insufficient, and would not receive any credit for performance in this test.

Submitting Your Project

To turn-in your project, you must submit the following three things:

  1. The thoroughly documented source code for your sender and receiver.
  2. A Makefile that compiles your code. Your Makefile may be blank, but it must exist.
  3. A plain-text (no Word or PDF) README.md file. In this file, you should briefly describe your high-level approach, any challenges you faced, and an overview of how you tested your code.

Your README.md, Makefile, source code, etc. should all be placed in the root of a compressed archive (e.g., a .zip or .tar.gz) and then uploaded to Gradescope. Alternatively, you can check these items in to Github and then instruct Gradescope to clone your Github repository.

Double Checking Your Submission

To try and make sure that your submission is (1) complete and (2) will work with our grading scripts, we provide a simple script that checks the formatting of your submission. You can download the script here and invoke it using the following command:

$ ./transport_fmt_chk.py [path to your project directory]

Note that you may need to chmod +x transport_fmt_chk.py to make the script executable.

This script will attempt to make sure that the correct files (e.g., README.md and Makefile) are available in the given directory, that your Makefile will run without errors (or is empty), and that after running the Makefile two programs named 3700send and 3700recv exist in the directory. The script will also try to determine if your files use Windows-style line endings (\r\n) as opposed to Unix-style line endings (\n). If your files are Windows-encoded, you should convert them to Unix-encoding using the dos2unix utility before turning in.

Grading

This project is worth 15% of your final grade. The grading in this project will consist of

  • 90% Program correctness
  • 10% Performance

By definition, you are going to be graded on how gracefully you handle errors; your code should never print out incorrect data. Your code will definitely delays, duplicated packets, and so forth. You should always assume that everyone is trying to break your program. To paraphrase John F. Woods, “Always code as if the [the remote machine you’re communicating with] will be a violent psychopath who knows where you live.”

Additionally, points will be taken off for submissions that send datagrams with greater than 1472 bytes of data.

Common Vagrant Issues and Solutions

  • Note that RTNETLINK answers: No such file or directory in the Vagrant VM is a harmless message and can be ignored.
  • If you are using VirtualBox on Windows, and you receive the error message VBoxManage.exe: error: Appliance import failed, follow the answer here to solve the issue.
  • If the /vagrant directory in the guest machine is not syncing to the host machine, add the line: config.vm.synced_folder “<path_to_local_folder>”, “/cs3700project4” to the Vagrantfile on the host and then look for syncing at within the /cs3700project4 directory on the guest.
  • There are known problems with Vagrant on Macs with M1 chips. Contact the staff or post on Piazza and we will help you find a workaround.