Changelog:
- 19 Oct 2024: don’t require generating request with multiple questions, since that’s apparently prohibited by RFC 9619
- 21 Oct 2024: specify that you may assume –process-response’s answer only includes responsive records
- 23 Oct 2024: have create-request responses prefixed with size for consistency with response handling; change text2pcap advice accordingly
- 23 Oct 2024: add link to dns samples; –send-request testing instructions
- 25 Oct 2024: add nxdomain to dns samples + seperate link
- 26 Oct 2024: allow trailing . or not in json domain name output since the dns-samples are not consistent about this
- 26 Oct 2024: update dns-samples to fix cname-to-aaaa and cname-to-aaaa2 being duplicates
- 26 Oct 2024: mention argparse in hints
- 28 Oct 2024: provide examples of using sys.stdout.buffer and sys.stdin.buffer + correct after first version had typo
- 29 Oct 2024: always show –ipv4 or –ipv6 being passed when –create-request is used
- 30 Oct 2024: make it extra clear that you cannot call tshark in your dns.py, it must do its own parsing
- 30 Oct 2024; explicit mention where to submit code
- 1 Nov 2024: note potential issues using powershell
1 Your Task
In
dns.py
, write a program where:python3 dns.py --create-request HOSTNAME --ipv4
creates a DNS query message that requests the IPv4 addresses of HOSTNAME with recursion disabled, and writes it to stdout (as in usingsys.stdout.buffer
in Python3) prefixed by the request size as a 2-byte big endian number (like would be used when sending a DNS request over TCP).python3 dns,py --create-request HOSTNAME --ipv6
should do the same, but request the IPv6 address.python3 dns.py --process-response
reads a DNS query response (for a query asking for an IPv4 and/or IPv6 address for a hostname) from stdin (as in usingsys.stdin.buffer
in Python3) and parses it. (You may not use a DNS library or call out to another program to do the parsing or request generation.) The input DNS query response will be prefixed by its size in bytes, as happens when it is sent by a DNS server.Then, it outputs JSON in the format specified in the
output format
section below identifying:- if it’s an error;
- the IPv4 and IPv6 addresses identified in the answer part of the response, if any;
- if no IPv4 and IPv6 address is identified in the answer part of the response, then:
- if a CNAME needs to be followed to find the IPv4 or IPv6 address, the name to lookup in the recursive query
- the names and IPv4 and/or IPv6 addresses of potential DNS servers to contact to resolve that name as part of a recursive query
To keep the assignment simpler, you do not have to implement DNS name compression in your responses.
python3 dns.py --send-request HOSTNAME --server SERVER --port PORT --ipv4
orpython3 dns.py --send-request HOSTNAME --server SERVER --port PORT --ipv6
makes a DNS request using TCP to the DNS server with name SERVER on port number PORT. The request should be the same request that would be generated by the--create-request
option. After making the request, the program should retrieve the response and process the result as in--process-response
, outputting the corresponding JSON to stdout.
Submit your code by uploading dns.py to the submission site.
2 output format
When processing a response, output a JSON dictionary with the entries with the following keys and corresponding values:
kind
— one of:error
(if a non-zero response status is returned)address
(if an A or AAAA record existed in the answer section)next-name
(if a CNAME was found in the answer section and no addresses were found in the answer section)next-server
(if no addresses or CNAMEs were found in the answer section and NS records were found in the authority section)malformed
(if the response was missing or malformed in some way)
addresses
(only required ifkind
isaddress
): a list of the IPv4 addresses and IPv6 addressesnext-name
(only required ifkind
isnext-name
): the domain name from CNAME record; if there are a chain of CNAME records, from the last CNAME record in the chain. (You may assume the CNAME records are present in the answer in order.)next-server-names
(only required ifkind
isnext-server
): a list of the domain names of the next servers from the NS recordsnext-server-addresses
(only required ifkind
isnext-server
): a list of DNS server IPv4 and IPv6 addresses
In addition, you may include extra entries in the dictionary at your discretion. You may assume that any returned records in the answer or authority are responsive to the query. (For example, we won’t include an A record or CNAME record for an unrelated domain in the answer section.)
In this JSON output:
- all lists must be represented as JSON arrays
- all IPv4 addresses must be represented as strings in dotted-quad format (example:
128.143.67.11
), - all IPv6 addresses must be represented as strings in one of the formats specified in RFC 4291, section 2.21 (examples:
2607:f8b0:4004:c21::63
,2607:F8B0:4004:0C21:0000:0000:0000:0063
), and - all domain names with labels seperated by
.
s and optionally a trailing.
(examples:www.cs.virginia.edu.
,www.cs.virginia.edu
)
3 some DNS references
Official specifications:
TCP/IP Illustrated, Volume 2, Chapter 11 (available via UVa library through O’Reilly; login/create an account with your UVa email)
4 sample DNS responses
Here is an archive of DNS responses suitable for passing to
python3 --process-response
. The.dat
files are the DNS responses, which you can test with something likepython3 --process-response <file.dat
(Note that there are reports that these command do not work without modificaiton in Powershell (probably because it treats
file.dat
as a text instead of binary file). I know they will work on portal.cs.virginia.edu.)the
.txt
files show a human-readable version which is created by running a command likeod -Ax -tx1 -v file.dat | text2pcap -T 53,53 - - | tshark -T text -r - -V >file.txt
to have
tshark
(command-line version of wireshark) process the DNS response (fromfile.dat
into `file.txt), then extracting the relevant part of the output.The
.reference.json
files are the responses our reference implementation generated.The original version of the DNS samples archive did not contain a sample for
name not found
; it now does, or you can download the extra dat file here.It also contained duplicate records for cname-to-aaaa and cname-to-aaaa2; this is now corrected; you can download an actually different cname-to-aaaa2 dat and txt file.
5 testing –send-request
You can test send-request using the
nc
utility, which can act as a simple TCP server.For example, you can run
nc -l -p 0 -s 127.0.0.1 -v <input.dat > output.dat
in one terminal. (The
-l
option says to listen for (act as a server) instead of making connections (act as a client). The-p
option selects the input port number to bind to; binding to port 0 has the OS select a port number. The-s 127.0.0.1
selects an IP address of 127.0.0.1, which is an IP address forlocalhost
— it only contacts the current machine.)This commad will output a message like
Listening on 127.0.0.1 46245
Pay special attention to the port number listed after the IP address. Then in another terminal on the same machine you can run a command like
python3 dns.py --send-request www.cs.virginia.edu --server 127.0.0.1 --port 46245
(If you are connecting to portal.cs.virginia.edu, see note below regarding getting two terminals on the same machine.)
When your
dns.py
connects tonc
, it will receive the contents ofinput.dat
and whatever it sends will be written tooutput.dat
Regarding portal.cs.virginia.edu:
ssh user@portal.cs.virginia.edu
connects you to one of several machines, namesportal01
throughportal11
. You can tell which of those machines you are logged in using your command prompt and/or by running thehostname
command. Once logged into one portal machine or sufficiently on the UVa network, you can log into a particular portal machine usingssh portalXX.cs.virginia.edu
whereportallXX
is the name of the machine with the number. With some SSH clients (probably including Linux/OS X’s), you can have the SSH client login to portal.cs.virginia.edu and then SSH from that machine to portalXX.cs.virginia.edu with a command likessh -J user@portal.cs.virginia.edu user@portalXX.cs.virginia.edu
. (See also OpenSSH’s manapage’s section on the -J option.)
6 Hints
6.1 Python argument parsing
- If found Python’s standard argparse library helpful.
6.2 Checking requests in wireshark
For debugging, you can convert the output of
python3 dns.py --create-request HOSTNAME ...
to a PCAP file you can open with wireshark using the
text2pcap
utility.One way to do this for a request for www.cs.virginia.edu:
python3 dns.py --create-request www.cs.virginia.edu --ipv4 > request.dat od -Ax -tx1 -v < request.dat > request.od text2pcap -T 53,53 request.od request.pcapng
(Note that there are reports that these command do not work without modificaiton in Powershell (probably because it treats
request.dat
as a text instead of binary file). I know they will work on portal.cs.virginia.edu.)The first command runs
python3 dns.py --create-request www.cs.virginia.edu
, but sends its output to the filerequest.dat
. The second command convertsrequest.dat
into a text-format (with hexadecimal) file that is saved inrequest.od
. The third takesrequest.od
and converts it to a file that wireshark can open by adding an ethernet and TCP header using source and destination port 53. (If you used -u 53,53, it would wrap it in a UDP header instead.)You can open up that file in a graphical version of wireshark. Also, on the command line on portal, you can get a dump of the contents with all information with a command like
tshark -T text -V -r request.pcapng > request-as-text.txt
which will output a meachine-reaable text representation as
request-as-text.txt
You can combine all of these into one command with something like:
python3 dns.py --create-request www.cs.virginia.edu | od -Ax -tx1 -v - | text2pcap -T 53,53 - - | tshark -T text -V -r - > request-as-text.txt
6.3 Encoding/decoding big-endian values
- The standard Python struct library is convenient for converting two and from big-endian integers.
6.4 Using sys.stdout.buffer and sys.stdin.buffer
sys.stdout.buffer
andsys.stdin.buffer
provide support for input and output for bytes (instead ofstr
s) in Python.You can use them write or read bytes from stdin and stdout with something like
sys.stdout.buffer.write(bytes([1,2,3,4]))
or
the_4_bytes = sys.stdin.buffer.read(4)
.
6.5 Opening/using TCP connections
You can use the socket library to create TCP connections. Most simple is using
create_connection
followed bysendall
andrecv
:import socket ... sock = socket.create_connection(('host', port)) sock.sendall(b'The bytes to send') data = sock.recv(size)
(
sendall
is like send, but in case not all bytes can be sent at once, it calls send repeatedly until they all are or there is an error.)Note that you may need to call
recv
multiple times in case only some of the bytes you request are available initially.
RFC 4291 only gives examples with upper-case A-F, but we will allow both upper and lower case.↩︎