Assignment 5: Flow Control and DNS
CS640 Fall 2019
For this assignment, you will first implement a Python-based data sender and receiver using the sliding window algorithm. Then you will write your own simple Java-based DNS server that performs recursive DNS resolutions, and appends a special annotation if an IP address belongs to an Amazon EC2 region.
After completing this assignment, you should be able to:
All the skeleton code needed in this assignment can be downloaded to your Mininet VM by
cd ~
wget http://pages.cs.wisc.edu/~akella/CS640/F19/assignment5/assign5.tgz
tar xzf assign5.tgz
cd assign5
In the assign5 folder, you will find two folders: fc and src, containing the code for Part 1 and 2 respectively. There is another file for Part 2 called ec2.csv in assign5, which is the list of public IP address ranges for each EC2 region.
As you work on this assignment, you may want to consult the “Sliding Window” portion of Section 2.5 of Computer Networks: A Systems Approach.
You will implement a simple sliding window protocol (SWP) in Python that transmits data in only one direction (and acknowledgements in the reverse direction). The sender side of the SWP is responsible for: (1) transmitting data packets, (2) guaranteeing the number of “in-flight packets” remains within a fixed bound, and (3) retransmitting data packets if an acknowledgment (ACK) is not received within a pre-determined timeout. The receiver side of the SWP is responsible for sending cumulative ACKs.
Every SWP packet exchanged between the sender receiver contains:
The SWPPacket class defined in swp.py is used to represent an SWP packet. This object-based representation can be converted to a sequence of bytes to be sent across the network using the to_bytes method. Conversely, a sequence of bytes received from the network can be converted to an object-based representation using the SWPPacket.from_bytes method.
The SWP sender and receiver both interact with a simple lower layer protocol (LLP) that sends and receives SWP packets across the network on behalf of the SWP. The LLPEndpoint class in llp.py exposes a basic API for sending and receiving a packet—really just a sequence of bytes—to/from a “remote” endpoint.
NOTE
Please use the constants like _SEND_WINDOW_SIZE, _TIMEOUT & _RECV_WINDOW_SIZE given in the respective classes.
You are responsible for implementing the sender side of SWP by completing the SWPSender class in swp.py.
The sender must do three things:
These tasks should be handled by the _send, _retransmit, and _recv functions, respectively, within the SWPSender class. As you write the code, we recommend you add some debugging statements—use the function logging.debug instead of print—to make it easier to trace your code’s execution.
The _send function is invoked by the send function which is invoked by an “application” (e.g., client.py). The _send function needs to:
The _retransmit function is invoked whenever a retransmission timer—started in _send or a previous invocation of _retransmit—expires. The _retransmit function needs to complete steps 4 and 5 performed by the _send function.
The _recv function runs as a separate thread—started when an SWPSender is created—and receives packets from the lower layer protocol (LLP) until the SWP sender is shutdown. The SWP sender should only receive SWP ACK packets—you should ignore any packets that aren’t SWP ACKs. For every chunk of data that is ACK’d, the _recv function needs to:
Note that SWP ACKs are cumulative, so even though an SWP ACK packet only contains one sequence number, the ACK effectively acknowledges all chunks of data up to and including the chunk of data associated with the sequence number in the SWP ACK.
To test your code:
where PORT >= 1024.
Now that you have finished part 1, you should work on implementing the receiver side of SWP by completing the SWPReceiver class in swp.py.
All functionality for the receiver side of SWP is implemented in the _recv function, which runs as a separate thread—started when an SWPReceiver is created—and receives packets from the lower layer protocol (LLP).
For every SWP data packet that is received, the _recv function needs to:
To test your code:
where PORT >= 1024.
To test retransmission, include the command line argument -l PROBABILITY (that is a lowercase L) when you start the client and/or the server. Replace PROBABILITY with a decimal number between 0.0 to 1.0 (inclusive), indicting the probability that a packet is dropped. If you pass this option to the client, then ACK packets may be dropped. If you pass this option to the server, then data packets may be dropped.
For this part of the assignment you will implement your own simple DNS server in Java. Your server will accept queries from clients, and issue queries to other DNS servers in order to respond to client queries. Your server will also appends a special TXT record if an IP address belongs to an Amazon EC2 region. For simplicity, your server will not cache any DNS records, nor will it be responsible for storing the records for any DNS zones.
You can play around with DNS queries using the dig program (available in most *nix OS). One of the formats to use dig command is
dig +norecurse @name.of.dns.server record-type domain-name
name.of.dns.server is the domain name of the DNS server you wish to query
record-type is the type of DNS record you wish to retrieve, e.g., A
domain-name is the domain name you seek information on
E.g.
dig +norecurse @a.root-servers.net A www.google.com
Before you write any code, you should familiarize yourself with the format of DNS messages. You should read the Network Sorcery RFC Sourcebook page on DNS. You should also issue some DNS queries using dig, and look at the DNS packets using Wireshark.
You will need to have root (or administrator) access to capture packets, so you should issue your queries either from your own machine, or from your Mininet VM. You can use tcpdump in your Mininet VM to capture DNS packets:
sudo tcpdump -n -i eth0 udp port 53 -w dnstrace.pcap
You should then scp the file to a machine with Wireshark. Wireshark is installed on all CS machines by default.
scp dnstrace.pcap USERNAME@MACHINE.cs.wisc.edu:~
If you use your own machine, you can use Wireshark to both capture and view the packets.
In Wireshark, you should select the DNS packet you want to view, then look at its details in the pane in the bottom half of the Wireshark window. You should pay particular attention to the Flags, Questions, and Answers parts of the DNS packet.
Your DNS server should be invoked as follows:
java edu.wisc.cs.sdn.simpledns.SimpleDNS -r <root server ip> -e <ec2 csv>
You should start by writing code that receives and parses DNS queries. Your server should listen for UDP packets on port 8053. You should call the deserialize method in the DNS class in the edu.wisc.cs.sdn.simpledns.packet package to parse the payload of a UDP packet that contains a DNS query. (Hint: Look at DatagramSocket and DatagramPacket in Java)
Your server only needs to handle opcode 0 (standard query), and query types A, AAAA, CNAME, and NS. You can silently drop all other client queries. Also, your server only needs to handle one client query at a time (i.e., it does not need to be multi-threaded).
When your server receives a query of type A, AAAA, CNAME, or NS, with the recursion desired bit set to 1, it should recursively resolve the query, starting from the root name server. If the recursion desired bit is set to 0, it should only query the root name server.
If a client issues a query of type A or AAAA for a domain name, and the domain name resolves to a CNAME, then you should recursively resolve the CNAME to obtain an A or AAAA record for the CNAME. Your reply to the client should include both the CNAME record for the original domain and the A or AAAA record for the CNAME.
If a query is of type A, and your DNS server successfully resolves the query, then you should check if the address(es) are associated with an EC2 region. For each address associated with an EC2 region, you should add a TXT record to the answers you provide to the client. The TXT record should contain the name of the EC2 region (from the CSV file), followed by a hyphen (-), followed by the address in dotted decimal form. For example:
www.code.org TXT Virginia-50.17.209.250
Don’t forget to include the A record(s) as well! Also you can use the DNSRdataString class when creating a DNSResourceRecord of type TXT.
You can test your code using dig. To force queries to use your DNS server, include the arguments “-p 8053 @localhost”. The question, answer, authority, and additional sections output by dig when using your DNS server should match what is output produced by dig when you query your machine’s default DNS server (although the addresses and name servers may be slightly different if an upstream DNS server is using round robin to select which records are returned). You can also use tcpdump to help you debug.
You should create a Makefile under the assign5 folder that enables us to compile and run your Part 2 Java code by executing the following commands:
cd assign5
make
make run
Assuming you are in the assign5 folder, you should pack the submission by:
tar czvf username1_username2.tgz fc/ src/ Makefile
You should submit username1_username2.tgz to Canvas. Please make only one submission per team.