You are here: Home / Past Courses / Fall 2016 - ECPE 177 / Projects / Project 3 - Network Tester (Part 1)

Project 3 - Network Tester (Part 1)

Project Objectives

In this project, you will build the first part of a network testing tool, specifically the communication framework that allows the tool to function.  In doing so, you will gain:

  • Hands-on experience with thread-based parallel programming
  • Hands-on experience implementing a fixed length, binary (not ASCII) protocol

 

Requirements

The high-level goal of Project 3 is as follows:

Implement a client/server control protocol that will allow (in the subsequent project) for testing of network bandwidth and latency using both TCP and UDP.

You must use the Python 3 programming language, specifically version 3.4.x or newer, which should be widely available.
(Note: On most systems, the binary will be called python3.  To be safe, run python --version and python3 --version at the command line to see what you have.) 

You must develop your test program on a Linux operating system.  Either a virtual machine or dual-boot arrangement is acceptable. (Your assignments will be graded on a Ubuntu 16.04 LTS machine, if you are interested in developing in exactly the same environment.)

Your test program should be runnable from a file called tester.py.  You can import additional Python files (so that your entire project is not in a single file), but the user should not invoke these helper files directly.

The following command-line arguments should be supported: 

  • --help   :  This argument will print out a helpful message describing what arguments the program takes
  • --version   :  This argument will print out the version number of the program (e.g. tester.py 1.0)
  • --test  :  The argument can select the various test modes: TCP_STREAM ,  TCP_RR , UDP_STREAM , and UDP_RR .  If not set, the default should be TCP_STREAM.  Note that these specific tests are not implemented until the next project. This argument is only used by the client.
  • --target  :  This argument specifies the IP or hostname the client contacts to run the test.  If not set, the default should be localhost.  This argument is only used by the client.
  • --time  :   This argument specifies the duration of the test in seconds.  If not set, the default should be 30 seconds. This argument is only used by the client. 
  • --client  : This argument configures the tester to act as a client  (this option is mutually exclusive with --server)
  • --server  : This argument configures the tester to act as a server  (this option is mutually exclusive with --client)

You should use the argparse Python library instead of parsing the arguments yourself. Argparse will provide the --help and --version arguments for "free".

Some arguments (like --test and --time) are only relevant if client mode is activated, and should have no effect on the server.

Example invocations:

$ ./tester.py --help
usage: tester.py [-h] [--version] [--test TEST] [--target TARGET]
                 [--client | --server]

Network tester for COMP/ECPE 177

optional arguments:
  -h, --help       show this help message and exit
  --version        show program's version number and exit
  --test TEST      Set mode: TCP_STREAM, TCP_RR, UDP_STREAM, UDP_RR
  --target TARGET  Target IP of client
  --time TIME      Test time in seconds
  --client         Client mode
  --server         Server mode
$ ./tester.py --server
(The program runs in *server* mode, quietly listening for clients to connect...)
(The server does *not* report any test results - Only the client reports the results) 
$ ./tester.py --client --target=10.15.20.30 --test=TCP_STREAM
(The program runs in *client* mode, connects to 10.15.20.30, and begins a TCP_STREAM bandwidth test) 
(The client reports test results when finished) 

 

Note that your single program (tester.py) can operate in either a client mode or server mode, depending on how it is invoked.  Thus, you are submitting only one program for this project, not two. 

 

Threads

In its final form (in the next project), your client and server will use two sockets at the same time for communication:  a data socket, used for the test data, and a control socket, used to negotiate the parameters of the test with the server and return test results when finished.  Both the data socket and control socket must be active at the same time. To support this, your project must implement thread-based parallelism by providing two threads:

  1. Control (Main) thread - This thread will manage the control socket for the client or server.  This thread should always be active as long as your program is running.
  2. Data thread - This thread will manage the data socket for the client or server. The data thread should be created with the user starts a test, and destroyed when the test is finished.  For the purposes of this project, no test is implemented.  The data thread simply runs, prints a "Data thread opening" message, waits for n seconds (as specified by the user), prints a "Data thread closing" message, and exits.

 

Control Socket

Before exchanging test data, the client and server should communicate over a separate "control" TCP socket to negotiate the parameters of the test.  This control socket will also be used to return experiment data (such as packet loss) from the server to the client.  The communication over the control socket is done in the form of four messages:  Request, Reply, Done, and Results.  Note that these messages are composed of bytes packed into a fixed-sized structure, not the variable length ASCII strings seen in the earlier HTTP project.

Tip: Although you could build such a structure manually, using the built-in Python struct module is much easier!  You will want to use the "!" symbol when defining your structure in order to specify that the structure is to be built without padding ("standard" data sizes), and that the data format is big endian. 

 

Communication Protocol

Below is an example conversation between client and server, including message format:

< Client opens connection to server on TCP port 5678 >

Test Request (3-byte message sent from client to server to initiate test)

  • Protocol Version (1 byte) - This assignment implements version 1 of the protocol. The field value should be 0x01
  • Message Type (1 byte) - TEST_REQUEST (0x01)
  • Test Type (1 byte) - The following values are permitted
    • TCP_STREAM (0x01)
    • TCP_RR (0x02)
    • UDP_STREAM (0x03)
    • UDP_RR (0x04)
< Server launches data thread >

Test Reply (4-byte message sent from server to client to approve test)

  • Protocol Version (1 byte) - This assignment implements version 1 of the protocol. The field value should be 0x01
  • Message Type (1 byte) - TEST_REPLY (0x02)
  • Test Port (2 bytes) - The TCP port the client should use to exchange performance data with
    • Note: This multi-byte integer must be in ***BIG ENDIAN*** format!

 

< Client launches data thread, prints "Data thread opening" message, waits for the specified time period, prints "Data thread closing" message, and closes data thread. >
(The test measurements will be done here in the next project)

 

Test Done (2-byte message sent from client to server to denote test has concluded)

  • Protocol Version (1 byte) - This assignment implements version 1 of the protocol. The field value should be 0x01
  • Message Type (1 byte) - TEST_DONE (0x03)

 

Test Results (10-byte message sent from server to client)

  • Protocol Version (1 byte) - This assignment implements version 1 of the protocol. The field value should be 0x01
  • Message Type (1 byte) - TEST_RESULTS (0x04)
  • UDP Packets Received (8 bytes) - Set to 0 in this project

< After a Test Results message is sent from the server to the client, the server will close the control socket. >

< The server should stay running after a test completes, and wait for a new test > 

< The client will report all test results.  The server is normally silent > 

 

Robust Design

For Project 3 only:  Your server should set a timeout on the control socket.  If no communication is received from the client in 45 seconds, consider the test failed. Terminate the data thread, and reset the control thread back to its initial condition: waiting for a new client to initiate a test.   You do not need any timeouts on the client control socket.   (This will change in the next project...)

Your client and server should both capture attempts to exit the program via CTRL-C, and exit gracefully.  The specific way you exit is up to you, but your program should not print out any ugly Python exception stack traces and error messages.  Instead, you should print out a message saying "Exiting Client" or "Exiting Server". 

As a general rule, a buggy client should not be able to crash the server! If something goes wrong, your server should abort the test, close the socket(s) with the client, and resume waiting for a new client.

 

Resources

See the main resource page for links that helped me when developing my solution.


Checkpoints

This project has one weekly checkpoint due, in addition to the final project deadline. Checkpoints give the instructor an opportunity to review your in-progress work, and, if problems are found, provide helpful feedback in advance of the project deadline. Checkpoints are graded as full credit, half credit, or no credit.

  • Checkpoint 1: 
    • All arguments are implemented and verified.
      • --test only supports the 4 valid options of TCP_STREAM, TCP_RR, UDP_STREAM, and UDP_RR
      • --time only supports an integer number of seconds
      • --target is only allowed if --client mode is set
      • --client and --server are mutually exclusive
    • Control thread and data thread are launched in both client and server modes, and print appropriate "started"/"stopping" messages for debugging purposes
    • Client control thread can send a Test Request message to the server control thread, which responds with a Test Reply message

 

Submission

There are slight differences between Python 3.x versions (3.3, 3.4, and 3.5). To ensure I use the same version of Python while grading that you did during development, include the following version-checking code during your program's initialization.
Note: Replace "3,4" with the version number of Python that you used.

import sys
if not sys.version_info[:2] == (3,4):
print("Error: need Python 3.4 to run program")
sys.exit(1)
else:
print("Using Python 3.4 to run program")

If your Python program is just a single .py file, simply upload it to Canvas directly.

Otherwise, if your program contains multiple source code files, create a .tar.gz compressed archive and upload that.  To create the archive, assuming your files are in the folder "project3", run:

$ tar -cvzf project3.tar.gz project3

Once created, upload this archive file to the corresponding Canvas assignment and submit.  To extract your archive, I will run:

$ tar -xvf project3.tar.gz