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 DHCP server. Instead of processing DHCPDISCOVER requests right away, your main thread (the one calling recvfrom()) will launch a new thread, then go back to waiting on another incoming message. Your main thread will get a total of 8 requests, then join all 8 threads before shutting down.

In the helper threads, you only need to work on constructing a DHCPOFFER message. For simplicity, you can either use the same IP address for all (192.168.1.1) or return them in incrementing order (192.168.1.1, 192.168.1.2, 192.168.1.3, etc.). For additional simplicity, you do not need to implement the full protocol here. Each thread should exit after sending the DHCPOFFER.

However, to show that you are handling these requests in a separate thread, you will not return them in the order received. Instead, in the helper thread, you will call usleep() on the XID value to sleep for a certain number of microseconds before responding. The effect of this is that your responses should be received by the client in a sorted ascending order based on the XID values.

Testing notes

Given the values used for the XID values, the output of this test case (there is only one for this phase) should be deterministic. However, it is possible that a request/respone gets flipped because of fluke timing caused by a lot of processes on stu or other random factors. If this happens, you will need to manually inspect the output to convince yourself that your code is working correctly. 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-2025 Michael S. Kirkpatrick.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.