Project 3: Multithreaded servers

In this project, you will build on Project 2 by implementing a DHCP server rather than a client.

Set up a repository on stu.cs.jmu.edu based on the instructions in the CS 361 Submission Procedures, using the submit directory p3-dhcp.git and the file name p3-dhcp.tar.gz.

Implementation requirements

This project builds on Project 2 but from the perspective of the server. In this project, you will not need to focus as much on the interpretation and manipulation of the binary data, as we have provided some helper functions that provide that functionality for you. Instead, your code will focus on receiving UDP messages from a client and constructing the appropriate response.

As with the previous project, this project will be built incrementally. Your first task involves responding to a single client message, then shutting down. Next, you will implement the server responses for the full four-message sequence, assigning IP addresses for up to four clients. You'll then add support for releasing an IP address. Finally, you'll add a minimal multithreaded structure to emulate the behavior of a more realistic server.

Phase 1: DHCP echo server (partial credit)

For this phase, you will implement the server partner of the client from Phase 3 of Project 2. In that step, the command-line option -x 0 served as a special case where the client sent a single DHCPDISCOVER message and the server replied with a DHCPOFFER. Your code will need to set up a server socket, receive the message from a client, construct the response, and send it back.

Similarly, your echo server will also respond to DHCPREQUEST messages based on the request's server ID option. Throughout this project, you will assume that your server is operating on 192.168.1.0 (note, though, that your actual socket communication will use 127.0.0.1 for all communication). If the DHCPREQUEST includes a server ID that matches, then you will respond with the corresponding DHCPACK. Otherwise, you will send a DHCPNAK to indicate the request failed. You do not need to do any other error checking.

As in the previous project, you will need to consult RFC 2131 and RFC 2132 to determine the fields that are required for each message type. Note that many of the fields in the BOOTP struct (such as chaddr) are identical between the client and server messages. Consequently, you can simplify your implementation by using the received data as the basis of your response.

Testing your server

Throughout this project, you are building a server. Servers typically execute in the background without a user directly viewing its STDOUT output. To capture this nature, all output from your code will be discarded by make test. To assist with debugging, you can print out anything that you want, but the testing framework will be based solely on output from the provided client program.

To test your code, you should use two windows. In one window, you should execute your server by running ./dhcps -s 10 to keep the server running for 10 seconds before shutting down. In a second window, you will run the provided ./tests/client program with one of the text files in tests/data. You do not need to understand or modify the structure of these files, but an explanation is provided below for those interested.


Phase 2: A DHCP server (C requirements)

In this phase, you'll extend your echo server by implementing the full four-message sequence for DHCP. That is, you will first receive a DHCPDISCOVER and reply with a DHCPOFFER that includes a unique IP address. The client will then send a DHCPREQUEST. If the IP address and server ID match, you will respond with a DHCPACK. However, if there is a mismatch for either, you will send a DHCPNACK.

You'll need to support up to 4 clients, assigning the IP addresses 192.168.1.1, 192.168.1.2, etc. If you receive a 5th DHCPDISCOVER, you should reply with a DHCPNAK.


Phase 3: Tracking and releasing assignments (B requirements)

For this phase, you'll need to build a mechanism for tracking the IP addresses that have been assigned to a given chaddr. You'll need to do this for a couple of reasons. First, messages might be interleaved. For example, assume you originally encounter the following sequence:

Client 1 (xid=15, chaddr=1234) sends DHCPDISCOVER
Server offers 192.168.1.1 to Client 1
Client 1 sends DHCPREQUEST for 192.168.1.1
Server sends ACK to Client 1
Client 2 (xid=99, chaddr=5566) sends DHCPDISCOVER
Server offers 192.168.1.2 to Client 2
...

You now have to support the following sequence:

Client 1 (xid=15, chaddr=1234) sends DHCPDISCOVER
Server offers 192.168.1.1 to Client 1
Client 2 (xid=99, chaddr=5566) sends DHCPDISCOVER
Server offers 192.168.1.2 to Client 2
Client 1 sends DHCPREQUEST for 192.168.1.1
Server sends ACK to Client 1
...

The second reason for tracking offers is to add support for DHCPRELEASE messages. These are sent by clients voluntarily to allow an IP address to be re-assigned. When you receive a DHCPRELEASE, you do not send a reply to the client.

It is important to note one key feature that goes along with DHCPRELEASE actions: servers try to assign the same IP address to subsequent DHCPDISCOVER messages from the same chaddr. That is, if a client previously used 192.168.1.3, sent a DHCPRELEASE to release its DHCP lease, then sends a subsequent DHCPDISCOVER, the server should again offer 192.168.1.3 unless it has been assigned to a different client.

There are many ways you could implement this tracking and removal of records. A simple way would be to create an array of four records, each of which contains the chaddr and a "tombstone" record, which are commonly used in data structures like hash tables. When you use a tombstone, you just mark the record as no longer in use but leave the data otherwise intact.


Phase 4: Once more with threads (A requirements)

In this final phase, you'll emulate part of the behavior of a multithreaded server. Instead of processing the requests right away, your server will collect the requests into a small queue until the queue is full. Once the queue is full, your server will process all the requests.

Given that implementing a multithreaded server can be a HUGE undertaking, we are going to greatly simplify the task so that it is feasible but you still get some insight into how to implement this. The only test case for this feature will start by sending 5 DHCPDISCOVER messages. After receiving the fourth, you should start a separate thread to handle those four messages. Later, you will receive DHCPRELEASE messages. You should then start a separate thread for the fifth original request.

Testing notes

There is a single test case for this phase. Due to the nature of multithreaded programs, it is possible that the output cannot match the expected output as a result of timing issues. When grading this phase, we will also run your code manually and inspect the implementation to determine if it is correct.


Additional notes

The files used by the provided client program contain information about how to generate messages in a predictable manner. These files contain lines such as the following:

1 1 42 010102020303:...
3 1 42 010102020303 192.168.1.0 192.168.1.1:[...]

Each line starts by indicating the message type (1=DHCPDISCOVER on the first line 3=DHCPREQUEST on the second), then the hardware type (1=ETH for both), and the hardware address (010102020303 is chaddr). The DHCPREQUEST message must also include a server ID (192.168.1.0) and requested IP address (192.168.1.1). Note that the information after the ":" is just to provide a human-readable interpretation and is not actually used.



James Madison University logo


© 2011-2024 Michael S. Kirkpatrick.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.