The objective of the lab is to implement a tunneling service that employs both a TCP based control plane to manage sessions and a UDP based data plane to transport app traffic.
Read chapters 4 and 5 from Peterson & Davie (textbook).
Note: The problem description includes the Bonus Problem which pertains to code that supports tunneling session termination. Deposit the part without termination support in subdirectory v1/. If you choose to do the Bonus Problem, please copy the code of v1/ into subdirectory v2/. Then perform modifications to add session termination support. The components pertaining to session termination can be implemented in a modular fashion.
Tunneling is a packet forwarding technique that allows packets sent to a final destination to make a detour to one or more intermediate forwarding nodes (e.g., routers and servers). This can help obfuscate the true identity of the sender from the receiver (i.e., final destination) as well as the true identity of the final destination from third-party observers that monitor network traffic. For example, some servers in a country may only respond the clients whose source IP address belongs to devices located in the country. The same goes for organizations including Purdue, where some services are available only from devices on campus (i.e., source IP addresses assigned to devices at Purdue). Virtual private networks (VPNs) provide a means for by-passing such restrictions by deploying a packet forwarding server with a source IP address in a given country or organization through which packets from a client are forwarded. To the final destination device a client request will appear to originate from a machine with IP address in the same country or organization. To an observer who captures packets upstream (i.e., close to the client) the destination will appear to be the forwarding node, hence hiding the true identity of the final destination.
When employing multiple forwarding nodes to which packets are bounced around like in a pinball machine before being forwarded to the final destination, identity of the source can be anonymized so that even when some forwarding nodes are compromised the sender's true identity requires effort to uncover. As with many technologies, tunneling may be used for innocuous as well as nefarious purposes.
We will build a single-hop tunneling server where a host sends traffic to the tunneling server which forwards the packets to the final destination, making it appear to the destination that the packets originate from the tunneling server. If the final destination responds, the tunneling server forwards the packets to the host. Our tunneling server is targeted at UDP-based applications such as the ping client/server of lab2, Problem 2. The tunneling server will utilize a control plane where requests to set up and tear down tunnels are mediated using TCP. The tunneling server and UDP app will use a separate data plane to transmit app traffic that helps hide the identities of the true source and destination.
The tunneling server, tunneld, is executed at a lab machine, say, amber05,
% tunneld 10.168.53.14 62222 xyzBC
where the first argument is the server's IPv4 address,
62222 the port number on which to accept TCP client
requests, and the third argument xyzBC is
a secret key (a string of 5 ASCII upper and lower case
characters). If binding fails (e.g., the specified port number is already in use)
then tunneld outputs a suitable message to stdout and terminates. The user
reruns tunneld with a different port number (assuming the IPv4 address is
correct) until bind() succeeds. tunneld follows a concurrent client/server
design where the parent process uses a STREAM socket to accept new tunnel set-up
and existing tunnel tear-down requests from the client, tunnelcli.
Upon receiving a new session request from client tunnel manager tunnelcli
(see Section 1.4) and checking its validity,
tunneld updates a data structure, struct nexthop nexthoptab[5],
struct nexthop {
unsigned long dstaddr;
unsigned short destpt;
unsigned long srcaddr;
unsigned short srcpt;
unsigned short egresspt;
int rcvudpsocket;
int sndudpsocket;
}
by filling in the fields dstaddr, dstpt, and srcaddr based on
values communicated by tunnelcli. Then it forks a child which populates
the remaining fields which are used to forward application UDP packets.
tunneld limits active forwarding sessions to 5.
Initialize all the fields of nexthoptab[] to 0. nexthoptab[i].srcaddr
(i = 0, 1, ..., 4) equaling 0 will indicate that the i'th table entry is
free.
Before forking a child process, the parent stores the index of
nexthoptab[] for the new tunneling session in a local variable,
unsigned short sessionid, so that the child knows which entry (i.e.,
forwarding task) it is responsible for.
Child code. Whereas the parent process of the concurrent server is responsible for setting up new tunneling session requests, a child process is responsible for carrying out the actual forwarding and handling termination of a tunneling session. Thus a child is involved in both the control plane and data plane, with the latter being its primary responsibility. When a child process is forked it creates a UDP socket and binds it to a port starting at 56600. If bind() fails because a port number is already in use, the child increments the port number and calls bind() until it succeeds. This is the port number on which the child expects app UDP packets to arrive. The socket ID is stored in the rcvudpsocket field of the nexthoptab[] entry that the child is responsible for. The child uses the TCP socket set up by the parent to communicate the 2-byte port number to the client by calling write(). The client, tunnelcli, outputs the received port number to stdout so that it can be used by the legacy UDP ping app, upingc, when transmitting ping packets to the tunneling server.
The child process creates a second UDP socket that will be used to forward the payload received from the client. The socket ID is stored in the field sndudpsocket. It is bound to an unused port number starting at 57500 (incrementing the port number of bind() fails) which is stored in the field egresspt. Responses from the final destination will arrive at port egresspt whose payload the child forwards to the client using the UDP socket stored in rcvudpsocket by calling sendto(). When a UDP packet arrives on port srcpt, its source address is compared with srcaddr to verify that it is from a valid client. If the IPv4 addresses do not match, an error message is output to stdout and the UDP packet is discarded.
To monitor packets arriving on the two UDP sockets, the child may register a signal handler for SIGPOLL/SIGIO which calls recvfrom() to receive a payload to forward on the other UDP socket by calling sendto(). For lab4 we will use a different method. For the purpose of packet forwarding in lab4, a more suitable method is to block on the select() system call to monitor activity on multiple file descriptors. Use select() to monitor activity on the three socket descriptors: two UDP, one TCP. Monitoring of the TCP socket is needed only if session termination is supported as part of the Bonus Problem. In the latter case, if the child receives on the TCP socket a 2-byte message containing an unsigned short value of 209, it closes the TCP socket and communicates the index of nexthoptab[] that it was responsible for through a FIFO, parentchildfifo, created by the parent. When the parent receives a byte on the FIFO, it clears the corresponding entry in nexthoptab[] to free it up. Server design that uses blocking select() complements the asynchronous software design implemented in lab2 and lab3. select() (or epoll()) is suited when multiple descriptors need to be monitored and the software architecture is amenable to assigning a dedicated thread to do so.
The client side consists of two apps, tunnelcli that sets up a tunneling session
by contacting the tunneld, and the legacy UDP app that sends its data to the tunneling
server which forwards the payload to the final destination.
The tunneling client, tunnelcli, is executed at a lab machine, say, amber02 by
% tunnelcli 10.168.53.14 62222 xyzBC 10.168.53.13 10.168.53.18 56001
where the first two arguments specify the coordinates of the tunneling server
tunneld, the third argument is a secret key (same as tunneld), the
fourth argument is the IPv4 address of the machine where a UDP client app
will run (e.g., 10.168.53.14 for amber05), and the last two arguments
specify the coordinates of the final destination
(i.e., UDP server app); in the above example, 10.168.53.18 for amber09.
If tunneling session set-up is successful, tunnelcli will receive the UDP
port number of the UDP socket of the child process forked by tunneld over its
TCP control plane. tunnelcli prints the UDP port number to stdout which will
be used by the legacy UDP ping app. tunnelcli waits indefinitely
until SIGINT is generated by entering CNTL-C on the keyboard. Code
a signal handler for SIGINT that transmits a 2-byte unsigned short of
value 209 using the TCP socket to tunneld (which will be processed by
its child). Code relevant to terminating a session (in tunnelcli and tunneld)
is optional and constitutes the Bonus Problem (see below).
With the UDP port number in
hand, we can execute the UDP client app, upingc, on amber04
% upingc <secret> 10.168.53.14 60007 <micsec>
where the second argument, in the above example, 10.168.53.14,
specifies the IPv4 address of the tunneling server (i.e., amber machine
where tunneld runs) and the third argument (60007 in the above example)
is the port number output to stdout
by tunnelcli. Thus instead of the IPv4 address and port number of the UDP
ping server, upingc sends its packets to the tunneling server.
In production software where we may invoke the help of the underlying operating system (in our case Linux but similar techniques apply to Windows and MacOS), the legacy UDP ping client, upingc, specifies the IP address and port number of the UDP ping server, not the tunneling server. That is, the legacy ping app is oblivious to the fact that its packets are first sent to the tunneling server which forwards the packets to the UDP ping server. This is achieved by installing kernel modules that intercept UDP datagrams generated by sendto() systems calls that are redirected to the tunneling server unbeknownst to upingc. Since the scope of CS 422 lab assignments does not include kernel mods, we accept that the arguments of upingc reveal that its packets are first transmitted to the tunneling server. Responses sent by upings to the child process of tunneld (i.e., payload carried by UDP) are returned to upingc.
Other than on-going tunneling being revealed to the user running upingc through the command-line arguments, the workings of the legacy UDP app remain unchanged. Performance as captured by RTT estimated by the UDP ping app will be influenced by the app packets making a detour to the tunneling server. This will, in general, inflate the RTT values returned by upingc.
Place your code along with Makefile and README in subdirectory v1/. Test and verify that your single-hop tunneling server implementation works correctly. By default, run the four apps --- tunneld, tunnelcli, upings, upingc --- on four different amber machines in our lab. Other combinations such as running tunnelcli and upingc on the same host is meaningful but not necessary. Your tests also need not consider the scenario where two instances (i.e., processes) of upingc are executed concurrently on the same host. Testing should include scenarios where two or more instances of upingc are executed on different hosts. With respect to gauging performance, run the UDP ping app with/without tunneling back to back, and compare the resultant RTT values. Discuss your finding in lab4ans.pdf.
Note: Starting with lab4, adding adequate annotation/comments to your code is a requirement. Start by putting your full name and username at the top of each source file (i.e., .c, .h, Makefile, README). Annotation of code need not be lengthy but should serve as a guide to help a competent software engineer understand your software.
Termination of a tunneling session initiated by tunnelcli by transmitting a 2-byte unsigned short 209 on its TCP socket is optional in Problem 1. Implementing it in tunnelcli and tunneld is the bonus problem. Deposit the modified code in v2/.
The Bonus Problems are completely optional. They serve to provide additional exercises to understand material. Bonus problems help more readily reach the 40% contributed by lab component to the course grade.
Electronic turn-in instructions:
i) For problems that require answering/explaining questions, submit a write-up as a pdf file called lab4ans.pdf. Place lab4ans.pdf in your directory lab4/. You can use your favorite editor subject to that it is able to export pdf files which several freeware editors do. Files submitted in any other format will not be graded.
ii) We will use turnin to manage lab assignment submissions. Please check that relevant source code including Makefile are included in the relevant subdirectories of lab4. In the parent directory of lab4, run the command
turnin -c cs422 -p lab4 lab4
You can check/list the submitted files using
turnin -c cs422 -p lab4 -v