Description
Introduction
In project 2, you will design and implement RU-DNS, a simple protocol mimicking the Domain
Name System (DNS) we discussed in class. Rather than relying on the Internet’s DNS, you will
build a set of DNS resolvers with private databases and a client program that resolves queries
against this (distributed and hierarchical) database. Overall, there will be 6 programs in this project:
a client, a root DNS server (RS), a local server (LS), an authoritative server (AS), and two top-level
domain servers (TS1 and TS2). Based on this simple DNS system, you will implement iterative
query resolution with local server caching. We do not consider query failures in this project. Please
use stream sockets (TCP transport) for your project (even though the real DNS protocol uses a
different transport, UDP).
Instructions
Please read these instructions carefully before you begin.
• This is an individual project (no partners or groups).
• You must use Python 3 (not Python 2!).
• You are free to discuss the project on Piazza or through other means with your peers and the
instructors. You may refer to the course materials, textbook, and resources on the Internet for
a deeper understanding of the topics. However, you cannot lift solutions from other students
or from the web including GitHub, Stack Overflow, generative AI (e.g. ChatGPT), or other
resources. Do not post this project or parts of it to question-answering services like Chegg,
academic hosting sites such as CourseHero, or generative AI. All written and programmed
solutions must be your original work. We run sophisticated software to detect plagiarism and
carefully monitor student answers. If you are in doubt, please ask us.
• For each question in the project report, please be clear and concise. Vague and rambling
answers will receive zero credit.
• We encourage you to start early and get the bulk of the work for this project done the week(s)
before it is due, rather than holding back your most significant efforts until the submission
date is too close.
RU-DNS message format
Rather than using the Internet’s standardized DNS protocol message format, we will use a simplified
protocol message format for this project. RU-DNS protocol request messages have the following
message format (there are four space-separated items):
0 DomainName identification
where 0 represents that this is a protocol request (not response) message, DomainName is a domain
name that we want to resolve (e.g. www.google.com), and identification is an integer incremented
by the requesting client for each fresh request (starting from 1). Here are examples of request
messages in RU-DNS:
0 www.google.com 5
0 princeton.edu 10
0 njit.edu 16
0 x.ai 23
RU-DNS protocol response messages have the format (five space-separated values):
1 DomainName IPAddress identification flags
where 1 indicates that this is a protocol response (not request) message, DomainName is the domain
whose IP address was requested in a prior RU-DNS request, identification is the identification on
that request, and flags indicate conditions related to the DNS response:
• The flag aa (for “authoritative”) indicates that the response arrived directly from the authoritative name server. However, when the root server RS merely asks the client to contact a
different DNS server, those responses are not considered authoritative, and do not include the
aa flag.
• The flag ns (for “name server”) indicates that the DNS response did not fully resolve the
domain requested, but instead is directing the client to the TLD server for iterative resolution.
For such a response, the returned IPAddress isn’t an IP address, but the hostname of the TLD
server to whom the client is being redirected to.
• The flag nx (for “non-existent”) indicates that the IP address for the requested domain name
did not exist in the local database of the final DNS server which ultimately completed the
resolution process (in the appropriate manner specified below). The IPAddress returned for a
non-existent domain should be set to 0.0.0.0
Exactly one flag is provided in each DNS response. Queries which are successfully resolved
should result in the aa flag set in the response arriving at the client. Queries that failed resolution
(no entry for the requested domain name in the appropriate database) should result in the nx flag set
in the response, with the IPAddress 0.0.0.0. A response that redirects the client to a TLD server
must use the ns flag with the corresponding TS’s hostname filled in the place of the IPAddress. The
identification in the response must match the identification of the query.
Here are examples of DNS response messages in RU-DNS:
1 www.google.com 9.7.5.6 5 aa
1 princeton.edu 0.0.0.0 10 nx
1 njit.edu cheese.cs.rutgers.edu 16 ns
1 njit.edu 10.5.6.7 17 aa
1 x.ai 45.67.89.103 23 aa
Designing the DNS servers
Each DNS server is a separate program named either rs.py, ls.py, as.py, ts1.py, or ts2.py,
corresponding to RS, LS, AS, TS1, and TS2 respectively. There is a client program (client.py).
Each DNS server has a local database of mappings from domain names to IP addresses. Each
server must first read this database (presented as an input file with a fixed name, one per server)
and store the mappings in an easily-searchable data structure in memory. You must write code to
look up this in-memory database upon each RU-DNS query.
Client (client.py): The client.py simulates your client in this DNS System. Its job is to:
• Read in the hostname requests from hostnames.txt
• Generate the Query Commands for the LS
• Send the commands
• Listen on the same configured rudns_port for incoming RU-DNS responses from LS.
• Await the response
• Record responses from your DNS System into resolved.txt
• The client never contacts the root server or TLD servers directly. Instead, all client queries
are sent to the local server (ls.py), which first checks its cache.
Local server (ls.py): The local server (ls.py) simulates a client’s local DNS resolver that typically
runs on the client’s host or within the client’s administrative domain. Its responsibilities are:
• Listen on the same configured rudns_port for incoming RU-DNS queries from the client.
• Load and maintain its database from the lsdatabase.txt file. This database contains the TLDs
for both TS1 and TS2, as well as the domain names of the four other servers (detailed at the
end of this section).
• Maintain its own local cache/database of domain-to-IP mappings (loaded from the provided
file). The cache is consulted first when the client asks the local server to resolve a name.
• If the name is present in the local cache (case-insensitive match), ls.py should return an
authoritative (aa) response to the requester with the domain exactly as stored in the local
database.
• If the name is not present in the local cache, ls.py acts as a forwarder: it will contact the root
server (RS) on behalf of the client to continue iterative resolution. The local server must open
a new TCP connection to the configured root hostname/port and follow the same RU-DNS
message protocol (including the identification field). When a response is received from RS
(which may include an ns redirection to a TLD), ls.py must forward that response back to the
original client and also log the response if required by the assignment’s logging conventions.
• ls.py should preserve the identification field of the original client request when forwarding
responses back to the client, so that the client can match replies to queries.
• In all cases, ls.py relays the final response back to the client, preserving the client’s original
identification number. Additionally, ls.py caches any aa responses it receives more than three
times, so subsequent client queries can be answered locally.
Root server (rs.py): The root server (rs.py) acts as the main switchboard for the entire RU-DNS
system. It doesn’t know every answer, but it knows where to send queries to get them resolved. Its
responsibilities are:
• Listen on the same configured rudns_port for incoming RU-DNS queries from the local server.
• Load and maintain its database from the rsdatabase.txt file.
• Listen and receive queries from ls.py and manage them as follows:
– If the queried domain falls under a top-level domain managed by TS1 or TS2, RS responds
with an ns reply containing the hostname of the corresponding TLD server. ls.py then
issues a new query (with incremented identification) to the specified TLD server.
– If the queried domain is not under one of the TLDs managed by TS1 or TS2, RS attempts
to resolve it directly from its own database. If found, RS returns an aa reply. If not found,
it returns an nx reply.
– When ls.py queries a TLD server, that TLD server attempts to resolve the domain from
its database. If successful, it returns an aa response. If not, it returns an nx response.
– If a query involves a domain managed by the separate authoritative server (AS), then
either RS or ls.py may direct the query to AS based on the root database entries. AS
behaves like a TLD server for its own configured set of domains: it replies aa if the
domain exists in its database or nx otherwise.
• Log all outgoing responses in the order they are sent into a file named rsresponses.txt
Authoritative server (as.py): The authoritative server (as.py) represents an authoritative DNS
server for a set of domain names that are not necessarily under the two TLDs handled by TS1 and
TS2. Its responsibilities are simple and similar to the TLD servers:
• Listen on the configured rudns_port for incoming queries.
• Load an authoritative database of domain-to-IP mappings from its input file and perform
case-insensitive lookups. If a mapping is found, return an authoritative (aa) response with the
domain exactly as present in the authoritative database and the matched IP address.
• If the domain is not present in the authoritative database, return a non-existent (nx) response
with IP address 0.0.0.0.
• as.py does not perform iterative forwarding to TLDs or the root; it is expected to be a final
authority for the names in its own database. In short, its behavior mirrors a TS server but is
used for resolving names that are configured to be answered outside the two TLDs listed in
the root database.
• Log all outgoing responses in the order they are sent into its corresponding output file
Top-Level Domain server (ts1.py and ts2.py): The Top-Level Domain server (ts1.py and ts2.py)
will be handling queries sent by your root server and responding with the IP address.
• Listen on the configured rudns_port for incoming queries from (ls.py).
• Load and maintain its own local database of domain-to-IP mappings from its input file
• Perform a final lookup for any queries it receives. It does not forward queries to any other
server.
– It performs a case-insensitive search for the exact domain name in its database .
– If a mapping is found, it returns an authoritative (aa) response containing the IP address
and the version of the domain name stored in its database.
– If the domain name is not found in its database, it returns a non-existent (nx) response.
• Log all outgoing responses in the order they are sent into its corresponding output file
: Here is an example of the contents of a TS2 database file when TS2 is in charge of the .edu
top-level domain (one mapping per line):
rutgers.edu 128.1.1.4
njit.edu 10.5.6.7
Here is an example of the contents of the TS1 database file when TS1 is in charge of the .com
top-level domain:
princeton.com 192.1.1.7
www.google.com 9.7.5.6
DNS lookups are case-insensitive. If there is a hit in the local DNS database, the server must
respond with the version of the domain name that is in the local DNS database. The lookup
algorithm must find a mapping for the exact (case-insensitive) domain name that is requested. For
example, google.com and www.google.com are considered two different domain names.
The database of the local DNS server will specify the top-level domain that is managed by each
TLD server in the first two lines. The next four lines will contain the domain names of RS, TS1,
TS2, and AS, in that order. Here is an example of the contents of an LS database file:
com
edu
cp.cs.rutgers.edu
cheese.cs.rutgers.edu
ilab2.cs.rutgers.edu
localhost
We have provided minimal starter code. It contains a template for reading command-line args,
and client.py demonstrates how to read from a file.
How we will run and test your code
We will run the programs with command line parameters that provide information on the relevant
listening ports to each program, as follows (your program must parse these command line arguments
correctly):
python3 ts1.py rudns_port
python3 ts2.py rudns_port
python3 rs.py rudns_port
python3 ls.py rudns_port
python3 as.py rudns_port
python3 client.py ls_hostname rudns_port
The argument rudns_port denotes the port on which all the server programs listen to connections
carrying RU-DNS queries. We will not reuse the same listening port on the same machine across
different programs, so each server program must be running on a different machine. The client must
know the root DNS server’s hostname to contact it (this is similar to how a local DNS resolver
must be configured with the list of root DNS servers). This information is available through the
ls_hostname command line argument. The root DNS server will get to know the TS hostnames
through the entries in the root database.
An example. Suppose:
• client.py runs on ilab1.cs.rutgers.edu;
• ls.py runs on ilab2.cs.rutgers.edu;
• as.py runs on cp.cs.rutgers.edu;
• rs.py runs on cheese.cs.rutgers.edu;
• ts1.py runs on java.cs.rutgers.edu;
• ts2.py runs on ilab4.cs.rutgers.edu; and
• the RU-DNS port is set to 45000.
We will execute your programs as follows:
(on java.cs.rutgers.edu)
python3 ts1.py 45000
(on ilab4.cs.rutgers.edu)
python3 ts2.py 45000
(on cheese.cs.rutgers.edu)
python3 rs.py 45000
(on ilab2.cs.rutgers.edu)
python3 ls.py 45000
(on cp.cs.rutgers.edu)
python3 as.py 45000
(on ilab1.cs.rutgers.edu)
python3 client.py ilab2.cs.rutgers.edu 45000
The databases for the five DNS servers will be provided in the files named lsdatabase.txt,
asdatabase.txt, ts1database.txt, ts2database.txt, and rsdatabase.txt. Their formats were described earlier in this document. The client will read a list of domain names to resolve from a file
named hostnames.txt and issue corresponding RU-DNS queries in order. Examples of these files
are provided in the project archive.
Your DNS server programs must log each RU-DNS response sent out over any connection,
in the same order in which they were sent out, in files named rsresponses.txt, lsresponses.txt,
asresponses.txt ts1responses.txt, and ts2responses.txt (respectively at RS, TS1, and TS2).
Your client program must log each RU-DNS response it receives in order in a file named resolved.txt.
Example outputs are provided in the project archive. Your ls.py program should also create a file
named cache.txt which will print the contents of its cache at the end of running all hostnames
within hostnames.txt.
The order of the outputs must be consistent with the order in which the domain names are
queried and that in which RU-DNS messages are sent between the client/servers.
What you must submit
Please turn in six programs rs.py, ts1.py, ts2.py, ls.py, as.py, client.py and a project report
entitled report.pdf. The questions for the report are listed below. We will be running the six
programs on the ilab machines with the Python 3 version on ilab. Please compress the files into a
single Zip archive before uploading to Canvas.
Please do not assume that programs will run on the same machine or that connections are made
to the local machine. In this project, we will test all your programs with remote socket connections,
with client.py, ts1.py, ts2.py, ls.py, as.py and rs.py each running on a different machine. We
will adapt the input files (.txt) and command line arguments accordingly when we do this.
You may simplify the initial design of your programs by testing all programs on one machine first.
However, the design of the project asks all servers to listen on the same port, which cannot happen on
one machine. Hence, we suggest that you start your development by running the servers on different
ports on the same machine first (change the first two lines of rsdatabase to use localhost as the host
names of the TSes), with the client assuming a fixed mapping from a top-level domain to the specific
TS and corresponding port (e.g. say, assuming that edu queries go to TS1 on port p1 and com queries go
to TS2 on port p2 ≠ p1). Once this functionality is tested to work correctly, you can move the servers
to different machines and run the servers on the same fixed port provided through the command
line. In your final submission, the client cannot assume a fixed mapping between a top-level domain and a specific TS. Your final submission must work when we run the servers on distinct machines.
There are example input files (hostnames.txt, rsdatabase.txt, ts1database.txt, ts2database.txt)
available for testing. We will use test cases not provided in these examples when grading your programs. You must populate the output files (rsresponses.txt, ts1responses.txt, ts2responses.txt,
and resolved.txt). You will be graded based on the correctness of the outputs in these files.
Project report
Please answer the following questions for the project report.
• Briefly discuss how you implemented iterative query functionality. Please be clear and specific.
• Is there any portion of your code that does not work as required above? Please explain.
• Did you encounter any difficulties? If so, explain.
• What did you learn from working on this project? Add any interesting observations not
otherwise covered in the questions above. Please be specific and technical in your response.
Notes
• Start your programs by first running the two TS programs, then the RS program, the LS
program, the AS program, and finally the client program, so that all listening sockets are in
place whenever a connect() call is made.
• RU-DNS lookups are case-insensitive. The response must contain the domain name that is in
the local database of the server that generated the response.
• You may assume that each domain name and each line containing a mapping (domain name
to IP address) is smaller than 300 characters.
• You may either use long-lived (persistent) or individual (non-persistent) connections to send
RU-DNS request messages between the different programs.
• Your program should not crash or hang on wellformed inputs.
• Please start this project early to allow plenty of time for questions on Piazza should you run
into difficulties.



