6.824 - Fall 2004

6.824 Lab 3: A TCP proxy

Due: Thursday, February 26th, 1:00pm.


Introduction

In this lab, you will write a TCP proxy server using libasync, a library that supports event-based programming. The point of this lab is to familiarize yourself with libasync, which you'll need to use in subsequent labs. Please consult the Libasync tutorial to learn how to use libasync. If that is not enough, please look at Using libasync.

A TCP proxy is a server which acts as an intermediary between a client and another server, called the destination server. Clients establish connections to the TCP proxy server, which then establishes a connection to the destination server. These paired connections are connected by the dotted lines in the figure below. The proxy server sends data received from the client to the destination server and forwards data received from the destination server to the client. The TCP proxy server acts as both a server and a client.

A TCP proxy server can be useful to get around services which restrict connections based on the network addresses or to forward services through firewalls.

The assignment

The proxy server you will build for this lab will be invoked at the command line as follows:

% ./tcpproxy destination-host destination-port listen-port

For example, to redirect all connections to port 3000 on your local machine to yahoo's web server, run:

% ./tcpproxy www.yahoo.com 80 3000 &

The proxy server will accept connections from multiple clients and forward them using multiple connections to the server. The proxy should forward data in both directions: from client to server and from server to client. No client or server should be able to hang the proxy server by refusing to read or write data on its connection. For instance, if one client suddenly stops reading from the socket to the proxy, other clients should not notice interruptions of service through the proxy.

The proxy must also handle hung clients and servers. In particular, if one end keeps transmitting data but the the other stops reading, the proxy must not buffer an unlimited amount of data. Be aware of the fact that suio's input() method allocates memory. If the proxy has buffered data in one direction and is unable to write any of it for 10 seconds, it should abort both connection pairs.

Your proxy must be written using libasync; again, consult the Libasync tutorial.

Connection termination

The proxy must handle end-of-file (EOF) conditions as transparently as possible. If it reads end-of-file from one socket, it should pass the condition along to the other socket after writing any remaining buffered data. (You can do this using the shutdown() system call; if invoked as shutdown(fd, SHUT_WR) parameter it writes EOF to fd, but can still read from fd. For clarity's sake: close() is equivalent to 2 shutdown calls---one with SHUT_WR and one with SHUT_RD.)

The proxy should continue to forward data in the other direction. The proxy should terminate a connection pair and close the file descriptors under the following two circumstances:

  1. The proxy has read an end-of-file (or experienced a read error) in both directions and has written all remaining buffered data.
  2. The proxy experiences a write error in either direction.
The reason for giving up more easily on write errors is that they signify some failure of the higher-level protocol. A read end-of-file can be a legitimate part of a protocol, whereas when a program writes data to the network, it indicates a serious problem if no one is there to read it.

Setting up your project directory

Download the lab starter files from http://pdos.lcs.mit.edu/6.824/labs/tcpproxy.tar.gz to get going.
% wget http://pdos.lcs.mit.edu/6.824/labs/tcpproxy.tar.gz
% tar xvzf tcpproxy.tar.gz
% cd tcpproxy
% gmake
...

You should use the tcpproxy.C file we supply you with as a starting point.

Testing

You should test your proxy to make sure that it continues to forward data even when some connections aren't responding. Here's one test you should be able to pass.

First, run the proxy and point it at pdos.lcs.mit.edu's HTTP port.

% ./tcpproxy pdos.lcs.mit.edu 80 1234
Note that you may have to pick a local port other than 1234 if someone is already using 1234. Now, in another window, use telnet to fetch /cgi-bin/big through the proxy:
% telnet 127.0.0.1 1234
Trying 127.0.0.1...
Connected to localhost (127.0.0.1).
Escape character is '^]'.
GET /cgi-bin/big
Watch the data go by for a while, then interrupt the output by typing control-], after which telnet should stop and print telnet>. Now check that the proxy hasn't been hung because telnet isn't reading data; open another window and fetch something else:
% telnet 127.1 1234
Trying 127.0.0.1...
Connected to localhost (127.0.0.1).
Escape character is '^]'.
GET /ok
You were able to fetch the data.
Connection closed by foreign host.
%
If you see "You were able to fetch the data," your program passes the test.

Once your proxy passes some basic tests, you can test it with the tester, ./tcpproxy-test. Assuming your proxy is in ./tcpproxy, you can test it as follows:

% ./tcpproxy-test ./tcpproxy
Single echo connection: passed
Two echo connections: passed
20 echo connections: passed
Bulk data, 20 connections: passed
Mix of blocked and normal: passed
One-way shutdown: passed
Early close: passed
Non-timeout of active client: passed
Timeout of lazy client: passed
% 
Your program should pass all phases of the tests. If all goes well, tcpproxy-test should completely finish in a minute or two. If it spends more than about 30 seconds on any one test, there is probably something wrong with your proxy.

Tests

tcpproxy-test creates a TCP socket on a randomly chosen port, and accepts connections on that port. By default, it just echos back data it reads from those connections, but it can be told to do other things instead.

tcpproxy-test then starts your proxy program, telling it to forward connections to the above-mentioned port. This means that tcpproxy-test is connecting through your proxy to itself.

tcpproxy-test performs the following tests:

Single echo connection

tcpproxy-test creates one connection through your proxy, and sends 10 4-byte messages on the connection. tcpproxy-test waits for the echoed reply before sending each new message. tcpproxy-test verifies that the correct data was echoed.

Two echo connections and 20 echo connections

tcpproxy-test creates N connections through your proxy. At the server end, tcpproxy-test just writes data it reads back to the connection. At the client end, tcpproxy-test copies data read from connection i to connection i+1. tcpproxy-test sends some 4-byte messages through this chain and verifies that they arrive at the other end.

Bulk data, 20 connections

As above, tcpproxy-test sets up a chain of 20 connections. It then sends 2 megabytes of data through the chain, and verifies that the same data arrives at the other end of the chain.

Mix of blocked and normal

tcpproxy-test runs the above bulk test. In addition, it sets up a connection through the proxy but doesn't read data from the server end of the connection. tcpproxy-test writes as much data as it can to that connection. tcpproxy-test expects that your proxy will forward all of the bulk data; it doesn't explicitly check how you handle the blocked connection.

One-way shutdown

tcpproxy-test sets up a connection, and then calls shutdown(s, SHUT_WR). On the server side, tcpproxy-test waits for an end-of-file, and then writes one byte to the connection. tcpproxy-test verifies that your proxy forwards that byte.

Early close

In this test, the server side closes the connection immediately after accepting it. Then the client write a few bytes to the connection, separated by one-second pauses. tcpproxy-test expects that your proxy will eventually close the connection.

Non-timeout of active client

tcpproxy-test sets up a connection and writes to it periodically, with multi-second pauses between writes. tcpproxy-test expects that your proxy will leave the connection open.

Timeout of lazy forward client

The server end of the connection does not read any data, but the client sends data as fast as it can. tcpproxy-test expects that your proxy will close the connection after 10 seconds.

Timeout of lazy reverse client

As above, but in the reverse direction (the server generates data, but the client does not read it).

Handin procedure

You should hand in a gzipped tarball tcpproxy-handin.tgz produced by gmake dist. Copy this file to ~/handin/tcpproxy-handin.tgz. Set permissions for this file to 600 (chmod 600 ~/handin/tcpproxy-handin.tgz). We will use the first copy of the file that we can find after the deadline---we try every few minutes.


For questions or comments, email 6.824-staff@pdos.lcs.mit.edu.
Back to 6.824 home.