Handy references:
Lab Audio, day 1
Overview:
For this lab, you'll be implementing the DNS protocol to build your very
own iterative name resolver.
Your server program will receive one optional flag (-m) and one argument:
the host name we'd like to resolve. If the flag is absent, you're being asked
to resolve a host name's IP address. If the flag is present, you're being
asked to find the mail exchange for a domain. For example:
$ ./lab3 celery.cs.swarthmore.edu
(...progress output...)
The name celery.cs.swarthmore.edu resolves to: 130.58.68.156
$ ./lab3 -m cs.swarthmore.edu
(...progress output...)
The mail exchange for cs.swarthmore.edu resolves to: 130.58.68.9
Note: you should also be able to look up the mail server that a machine
should use, e.g.,
$ ./lab3 -m sesame.cs.swarthmore.edu
(...progress output...)
The mail exchange for cs.swarthmore.edu resolves to: 130.58.68.9
Here, you'll get an MX answer telling you that cs.swarthmore.edu is the name
of the mail server. You'll then need to do an additional query to resolve its
name to an A record of 130.58.68.9.
You should assume that there will be a file named root-servers.txt in your program's current working
directory and that it contains a list of IP addresses for root DNS servers.
Your program must use this file to find a root server. It should iteratively
work its way down the DNS hierarchy, querying the root, then the TLD, then
authoritative server(s) until resolves the requested host name.
Requirements
- You may use any programming language you want for this lab. I encourage
you to try Python so that you can see the similarities and differences between
high- and low-level languages with respect to network programming. Regardless
of which language you choose, you must NOT use any libraries that simplify
DNS or hide the details of socket programming! Don't make any calls to
gethostbyname()/getaddrinfo() or the equivalent functions in the language you
choose. If you have any doubt about which functions you may use, please
ask!
- If you attempt to query a server and get no response after waiting a short
time (approximately 5 seconds), your program should move on to the next server
and attempt to query that instead.
- Your program should query for host name to IP address mappings (Type A,
decimal value 1) unless given the -m flag, in which case it should query for
mail exchanges (Type MX, decimal value 15).
- Your program should print short status messages regarding its intermediate
steps as it traverses the DNS hierarchy. For each request you make, you should
output the server you're querying and a brief summary of the response you got
back. If you didn't get a response (because you timed out), say so. You
should print:
- When you're about to make a query, tell me who you're querying (IP address
is fine, name + IP is nice if you have that info available.
- Tell me the result of the query (success, failure, timeout, etc.).
- If it was successful and it was the final query, print the final result.
Otherwise, continue querying.
This info will allow me to trace your code's path down the DNS hierarchy.
- If asked to resolve an invalid name, your program should print an error
message.
- You should never ask a DNS server to perform a recursive query for
you.
You may assume that the additional records section will contain the A
records for any server names listed in the NS records of the authority record
section.
High-level checklist
Roughly, your server should follow this sequence:
- Check the arguments to determine if it's being invoked for an A or MX
lookup.
- Populate a collection of root DNS server IP addresses from
root-servers.txt.
- Create a UDP socket and craft a query.
- Send that query to a root server and wait for a response. If you wait too
long, move to the next root.
- Continue this process as you work your way down the hierarchy, only instead
of using the root servers for subsequent queries, use the NS record results
from previous query's response.
- Once you've made it down to the final authoritative server, inform the user
of the result and exit.
You may find that structuring your program with recursion is helpful.
For example, in processing one lookup, you might need to start another. If
your code can call itself again, it'll be easier!
If you have any questions about the lab requirements or specification,
please post on Piazza.
Grading Rubric
This assignment is worth five points.
- 1 point for sending a request to and correctly parsing a response from an
authoritative server (e.g., sending a query directly to our local department's
server for a *.cs.swarthmore.edu host name).
- 1 point for traversing the DNS hierarchy down from the root to an
authoritative server and letting me know which servers you're querying and what
they're telling you along the way.
- 1 point for timing out and moving on to the next server in your list when
you do not receive a response.
- 1 point for correctly detecting invalid host names and printing a
reasonable error message.
- 1 point for resolving MX records.
When submitting, please provide a small executable script named "lab3" along
with your program. This script should take the same arguments as your program
(described above) and it should call your program with those arguments. This
helps me to account for various ways of invoking programs in different
languages when grading your assignments.
Tips
- Try to structure your program in a modular way. You'll have a much better
time if you create one function, that you can call whenever necessary, to
handle a task that comes up repeatedly (e.g., interpreting a DNS response
message). Duplicating code leads to more difficult debugging!
- The DNS protocol uses UDP rather than TCP. This means you only need to
create one socket (make sure to use SOCK_DGRAM rather than SOCK_STREAM!), and
you don't need to connect() it to anything. Instead, you specify the
destination every time you want to send, using a variant of the send() call
named sendto(), which takes additional arguments to specify the destination.
(Python: socket.sendto(),
C: sendto())
- In your queries, you can expect to encounter resource records of type A,
MX, and NS. You're likely to also come across CNAME (in the case of a name
alias), SOA (if you're asked to resolve a name that doesn't exist), and AAAA
(IPv6 answer). You don't need to handle the first two in a special way, just
print what you got and exit. When you get an AAAA response, look to see if you
got other answer records of type A.
- Unlike previous labs, this lab will require you to send binary integer
values, which means you need to worry about byte ordering. In C, the functions
htonl() and ntohl() (32-bit integers) and htons() and ntohs() (16-bit integers)
will help you convert back and forth between host (your local machine's
integer format) and network (the general standard for integers
transmitted over the network) byte orderings. In Python, you'll want to use
the struct
module.
- When waiting for a response (while blocked on recvfrom()), you'll need to
tell the OS that you don't want to block indefinitely, otherwise you might
deadlock. Python makes this easy with the settimeout()
socket method. In C, you can set the SO_RECVTIMEO option with setsockopt().
- Since DNS is not a text-based protocol, Wireshark is a very useful tool for
interpreting the data that you're sending and receiving.
- If you need to check for the presence of a single bit or set a single bit
in a larger integer field, recall the bitwise operations you learned at the
beginning of CS 31. If you bitwise and (&) a variable with a
value that has the bit you want to test, you'll get either 0 (it wasn't set) or
the value (it was set). With bitwise or (|), if you do variable =
variable | value, you will set any of the bits that are 1's in value.
- Test your code in small increments. It's much easier to localize a bug
when you've only changed a few lines.
Submitting
Please remove any debugging output prior to submitting.
To submit your code, simply commit your changes locally using git
add and git commit. Then run git push while in your lab
directory.