summaryrefslogtreecommitdiff
path: root/project2
diff options
context:
space:
mode:
authorCamil Staps2016-05-23 22:01:31 +0200
committerCamil Staps2016-05-23 22:01:31 +0200
commit7500f59ce0dbd8d376c008001dd638ad2844ab2b (patch)
tree7c7ce48b737ed7a34534375ee2891e026b26455a /project2
parentAssignment 7, deel 2 (diff)
Framework 2
Diffstat (limited to 'project2')
-rw-r--r--project2/proj2_s4498062/.gitignore1
-rw-r--r--project2/proj2_s4498062/README.md45
-rw-r--r--project2/proj2_s4498062/dns/__init__.py1
-rw-r--r--project2/proj2_s4498062/dns/cache.py89
-rw-r--r--project2/proj2_s4498062/dns/classes.py55
-rw-r--r--project2/proj2_s4498062/dns/domainname.py127
-rw-r--r--project2/proj2_s4498062/dns/message.py278
-rw-r--r--project2/proj2_s4498062/dns/rcodes.py81
-rw-r--r--project2/proj2_s4498062/dns/resolver.py72
-rw-r--r--project2/proj2_s4498062/dns/resource.py241
-rw-r--r--project2/proj2_s4498062/dns/server.py53
-rw-r--r--project2/proj2_s4498062/dns/types.py67
-rw-r--r--project2/proj2_s4498062/dns/zone.py54
-rw-r--r--project2/proj2_s4498062/dns_client.py28
-rw-r--r--project2/proj2_s4498062/dns_server.py28
-rw-r--r--project2/proj2_s4498062/dns_tests.py34
16 files changed, 1254 insertions, 0 deletions
diff --git a/project2/proj2_s4498062/.gitignore b/project2/proj2_s4498062/.gitignore
new file mode 100644
index 0000000..94487b9
--- /dev/null
+++ b/project2/proj2_s4498062/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/project2/proj2_s4498062/README.md b/project2/proj2_s4498062/README.md
new file mode 100644
index 0000000..ce89073
--- /dev/null
+++ b/project2/proj2_s4498062/README.md
@@ -0,0 +1,45 @@
+# Project 2 Framework
+
+## Description
+
+This directory contains a framework for a DNS resolver and a recursive DNS server.
+The framework provides classes for manipulating DNS messages (and converting them to bytes).
+The framework also contains a few stubs which you need to implement.
+Most files contain pointers to the relevant sections of RFC 1034 and RFC 1035.
+These are not the only relevant sections though, and you might need to read more of the RFCs.
+
+It is probably a good idea to read RFC 1034 before proceeding.
+This RFC explains an overview of DNS and introduces some of the naming which is also used in the framework.
+
+## File structure
+
+* proj1_sn1_sn2
+ * dns
+ * cache.py: Contains a cache for the resolver. You have to implement this.
+ * classes.py: Enum of CLASSes and QCLASSes.
+ * domainname.py: Classes for reading and writing domain names as bytes.
+ * message.py: Classes for DNS messages.
+ * rcodes.py: Enum of RCODEs.
+ * resolver.py: Class for a DNS resolver. You have to implement this.
+ * resource.py: Classes for DNS resource records.
+ * server.py: Contains a DNS server. You have to implement this.
+ * types.py: Enum of TYPEs and QTYPEs.
+ * zone.py: name space zones. You have to implement this.
+ * dns_client.py: A simple DNS client, which serves as an example user of the resolver.
+ * dns_server.py: Code for starting the DNS server and parsing args.
+ * dns_tests.py: Tests for your resolver, cache and server. You have to implement this.
+
+## Implementation Hints and Tips
+
+You should start with implementing the resolver, which you need for the server.
+You will need message.py, resource.py, types.py, classes.py and rcodes.py.
+You can ignore the code for converting from and to bytes from these files if
+you want, but it might be useful (especially for debugging).
+
+After finishing the resolver you need to implement caching and the DNS server.
+You can implement these in any order that you like.
+I suggest implementing the recursive part (the resolving) of your DNS server, before implementing the management of the servers zone.
+
+Wireshark and dns_client.py are useful tools for debugging your resolver.
+Wireshark and nslookup are useful tools for debugging your server.
+
diff --git a/project2/proj2_s4498062/dns/__init__.py b/project2/proj2_s4498062/dns/__init__.py
new file mode 100644
index 0000000..18ff536
--- /dev/null
+++ b/project2/proj2_s4498062/dns/__init__.py
@@ -0,0 +1 @@
+#!/usr/bin/env python2
diff --git a/project2/proj2_s4498062/dns/cache.py b/project2/proj2_s4498062/dns/cache.py
new file mode 100644
index 0000000..e148d3a
--- /dev/null
+++ b/project2/proj2_s4498062/dns/cache.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python2
+
+"""A cache for resource records
+
+This module contains a class which implements a cache for DNS resource records,
+you still have to do most of the implementation. The module also provides a
+class and a function for converting ResourceRecords from and to JSON strings.
+It is highly recommended to use these.
+"""
+
+import json
+
+from dns.resource import ResourceRecord, RecordData
+from dns.types import Type
+from dns.classes import Class
+
+
+class ResourceEncoder(json.JSONEncoder):
+ """ Conver ResourceRecord to JSON
+
+ Usage:
+ string = json.dumps(records, cls=ResourceEncoder, indent=4)
+ """
+ def default(self, obj):
+ if isinstance(obj, ResourceRecord):
+ return {
+ "name": obj.name,
+ "type": Type.to_string(obj.type_),
+ "class": Class.to_string(obj.class_),
+ "ttl": obj.ttl,
+ "rdata": obj.rdata.data
+ }
+ return json.JSONEncoder.default(self, obj)
+
+
+def resource_from_json(dct):
+ """ Convert JSON object to ResourceRecord
+
+ Usage:
+ records = json.loads(string, object_hook=resource_from_json)
+ """
+ name = dct["name"]
+ type_ = Type.from_string(dct["type"])
+ class_ = Class.from_string(dct["class"])
+ ttl = dct["ttl"]
+ rdata = RecordData.create(type_, dct["rdata"])
+ return ResourceRecord(name, type_, class_, ttl, rdata)
+
+
+class RecordCache(object):
+ """ Cache for ResourceRecords """
+
+ def __init__(self, ttl):
+ """ Initialize the RecordCache
+
+ Args:
+ ttl (int): TTL of cached entries (if > 0)
+ """
+ self.records = []
+ self.ttl = ttl
+
+ def lookup(self, dname, type_, class_):
+ """ Lookup resource records in cache
+
+ Lookup for the resource records for a domain name with a specific type
+ and class.
+
+ Args:
+ dname (str): domain name
+ type_ (Type): type
+ class_ (Class): class
+ """
+ pass
+
+ def add_record(self, record):
+ """ Add a new Record to the cache
+
+ Args:
+ record (ResourceRecord): the record added to the cache
+ """
+ pass
+
+ def read_cache_file(self):
+ """ Read the cache file from disk """
+ pass
+
+ def write_cache_file(self):
+ """ Write the cache file to disk """
+ pass
diff --git a/project2/proj2_s4498062/dns/classes.py b/project2/proj2_s4498062/dns/classes.py
new file mode 100644
index 0000000..2a6a67d
--- /dev/null
+++ b/project2/proj2_s4498062/dns/classes.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python2
+
+""" DNS CLASS and QCLASS values
+
+This module contains an Enum of CLASS and QCLASS values. The Enum also contains
+a method for converting values to strings. See sections 3.2.4 and 3.2.5 of RFC
+1035 for more information.
+"""
+
+
+class Class(object):
+ """ Enum of CLASS and QCLASS values
+
+ Usage:
+ >>> Class.IN
+ 1
+ >>> Class.ANY
+ 255
+ """
+
+ IN = 1
+ CS = 2
+ CH = 3
+ HS = 4
+ ANY = 255
+
+ by_string = {
+ "IN": IN,
+ "CS": CS,
+ "CH": CH,
+ "HS": HS,
+ "*": ANY
+ }
+
+ by_value = dict([(y, x) for x, y in by_string.items()])
+
+ @staticmethod
+ def to_string(class_):
+ """ Convert a Class to a string
+
+ Usage:
+ >>> Class.to_string(Class.IN)
+ 'IN'
+ """
+ return Class.by_value[class_]
+
+ @staticmethod
+ def from_string(string):
+ """ Convert a string to a Class
+
+ Usage:
+ >>> Class.from_string('IN')
+ 1
+ """
+ return Class.by_string[string]
diff --git a/project2/proj2_s4498062/dns/domainname.py b/project2/proj2_s4498062/dns/domainname.py
new file mode 100644
index 0000000..8876b48
--- /dev/null
+++ b/project2/proj2_s4498062/dns/domainname.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python2
+
+""" Parsing and composing domain names
+
+This module contains two classes for converting domain names to and from bytes.
+You won't have to use these classes. They're used internally in Message,
+Question, ResourceRecord and RecordData. You can read section 4.1.4 of RFC 1035
+if you want more info.
+"""
+
+import struct
+
+
+class Composer(object):
+ """ Converts a string representation of a domain name to bytes """
+
+ def __init__(self):
+ self.offsets = dict()
+
+ def to_bytes(self, offset, dnames):
+ # Convert each domain name in to bytes
+ result = b""
+ for dname in dnames:
+ # Split domain name into labels
+ labels = dname.split(".")
+
+ # Determine keys of subdomains in offset dict
+ keys = []
+ for label in reversed(labels):
+ name = label
+ if keys:
+ name += "." + keys[-1]
+ keys.append(name)
+ keys.reverse()
+
+ # Convert label to bytes
+ add_null = True
+ for j, label in enumerate(labels):
+ if keys[j] in self.offsets:
+ offset = self.offsets[keys[j]]
+ pointer = (3 << 14) + offset
+ result += struct.pack("!H", pointer)
+ add_null = False
+ offset += 2
+ break
+ else:
+ self.offsets[keys[j]] = offset
+ result += struct.pack("!B{}s".format(len(label)),
+ len(label),
+ label)
+ offset += 1 + len(label)
+
+ # Add null character at end
+ if add_null:
+ result += b"\x00"
+ offset += 1
+
+ return result
+
+
+class Parser(object):
+ """ Convert byte representations of domain names to strings """
+
+ def __init__(self):
+ self.labels = dict()
+
+ def from_bytes(self, packet, offset, num):
+ """ Convert domain name from bytes to string
+
+ Args:
+ packet (bytes): packet containing the domain name
+ offset (int): offset of domain name in packet
+ num (int): number of domain names to decode
+
+ Returns:
+ str, int
+ """
+
+ dnames = []
+
+ # Read the domain names
+ for _ in range(num):
+ # Read a new domain name
+ dname = ""
+ prev_offsets = []
+ done = False
+ while done is False:
+ # Read length of next label
+ llength = struct.unpack_from("!B", packet, offset)[0]
+
+ # Done reading domain when length is zero
+ if llength == 0:
+ offset += 1
+ break
+
+ # Compression label
+ elif (llength >> 6) == 3:
+ new_offset = offset + 2
+ target = struct.unpack_from("!H", packet, offset)[0]
+ target -= 3 << 14
+ label = self.labels[target]
+ done = True
+
+ # Normal label
+ else:
+ new_offset = offset + llength + 1
+ label = struct.unpack_from("{}s".format(llength),
+ packet, offset+1)[0]
+
+ # Add label to dictionary
+ self.labels[offset] = label
+ for prev_offset in prev_offsets:
+ self.labels[prev_offset] += "." + label
+ prev_offsets.append(offset)
+
+ # Update offset
+ offset = new_offset
+
+ # Append label to domain name
+ if len(dname) > 0:
+ dname += "."
+ dname += label
+
+ # Append domain name to list
+ dnames.append(dname)
+
+ return dnames, offset
diff --git a/project2/proj2_s4498062/dns/message.py b/project2/proj2_s4498062/dns/message.py
new file mode 100644
index 0000000..54933b7
--- /dev/null
+++ b/project2/proj2_s4498062/dns/message.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python2
+
+""" DNS messages
+
+This module contains classes for DNS messages, their header section and
+question fields. See section 4 of RFC 1035 for more info.
+"""
+
+import struct
+
+from dns.domainname import Parser, Composer
+from dns.resource import ResourceRecord
+
+
+class Message(object):
+ """ DNS message """
+
+ def __init__(self, header, questions=None, answers=None, authorities=None, additionals=None):
+ """ Create a new DNS message
+
+ Args:
+ header (Header): the header section
+ questions ([Question]): the question section
+ answers ([ResourceRecord]): the answer section
+ authorities ([ResourceRecord]): the authority section
+ additionals ([ResourceRecord]): the additional section
+ """
+ if questions is None:
+ questions = []
+ if answers is None:
+ answers = []
+ if authorities is None:
+ authorities = []
+ if additionals is None:
+ additionals = []
+
+ self.header = header
+ self.questions = questions
+ self.answers = answers
+ self.authorities = authorities
+ self.additionals = additionals
+
+ @property
+ def resources(self):
+ """ Getter for all resource records """
+ return self.answers + self.authorities + self.additionals
+
+ def to_bytes(self):
+ """ Convert Message to bytes """
+ composer = Composer()
+
+ # Add header
+ result = self.header.to_bytes()
+
+ # Add questions
+ for question in self.questions:
+ offset = len(result)
+ result += question.to_bytes(offset, composer)
+
+ # Add answers
+ for answer in self.answers:
+ offset = len(result)
+ result += answer.to_bytes(offset, composer)
+
+ # Add authorities
+ for authority in self.authorities:
+ offset = len(result)
+ result += authority.to_bytes(offset, composer)
+
+ # Add additionals
+ for additional in self.additionals:
+ offset = len(result)
+ result += additional.to_bytes(offset, composer)
+
+ return result
+
+ @classmethod
+ def from_bytes(cls, packet):
+ """ Create Message from bytes
+
+ Args:
+ packet (bytes): byte representation of the message
+ """
+ parser = Parser()
+
+ # Parse header
+ header, offset = Header.from_bytes(packet), 12
+
+ # Parse questions
+ questions = []
+ for _ in range(header.qd_count):
+ question, offset = Question.from_bytes(packet, offset, parser)
+ questions.append(question)
+
+ # Parse answers
+ answers = []
+ for _ in range(header.an_count):
+ answer, offset = ResourceRecord.from_bytes(packet, offset, parser)
+ answers.append(answer)
+
+ # Parse authorities
+ authorities = []
+ for _ in range(header.ns_count):
+ authority, offset = ResourceRecord.from_bytes(packet, offset, parser)
+ authorities.append(authority)
+
+ # Parse additionals
+ additionals = []
+ for _ in range(header.ar_count):
+ additional, offset = ResourceRecord.from_bytes(packet, offset, parser)
+ additionals.append(additional)
+
+ return cls(header, questions, answers, authorities, additionals)
+
+
+class Header(object):
+ """ The header section of a DNS message
+
+ Contains a number of properties which are accessible as normal member
+ variables.
+
+ See section 4.1.1 of RFC 1035 for their meaning.
+ """
+
+ def __init__(self, ident, flags, qd_count, an_count, ns_count, ar_count):
+ """ Create a new Header object
+
+ Args:
+ ident (int): identifier
+ qd_count (int): number of entries in question section
+ an_count (int): number of entries in answer section
+ ns_count (int): number of entries in authority section
+ ar_count (int): number of entries in additional section
+ """
+ self.ident = ident
+ self._flags = flags
+ self.qd_count = qd_count
+ self.an_count = an_count
+ self.ns_count = ns_count
+ self.ar_count = ar_count
+
+ def to_bytes(self):
+ """ Convert header to bytes """
+ return struct.pack("!6H",
+ self.ident,
+ self._flags,
+ self.qd_count,
+ self.an_count,
+ self.ns_count,
+ self.ar_count)
+
+ @classmethod
+ def from_bytes(cls, packet):
+ """ Convert Header from bytes """
+ if len(packet) < 12:
+ raise ValueError("header is too short")
+ return cls(*struct.unpack_from("!6H", packet))
+
+ @property
+ def flags(self):
+ return self._flags
+ @flags.setter
+ def flags(self, value):
+ if value >= (1 << 16):
+ raise ValueError("value too big for flags")
+ self._flags = value
+
+ @property
+ def qr(self):
+ return self._flags & (1 << 15)
+ @qr.setter
+ def qr(self, value):
+ if value:
+ self._flags |= (1 << 15)
+ else:
+ self._flags &= ~(1 << 15)
+
+ @property
+ def opcode(self):
+ return (self._flags & (((1 << 4) - 1) << 11)) >> 11
+ @opcode.setter
+ def opcode(self, value):
+ if value > 0b1111:
+ raise ValueError("invalid opcode")
+ self._flags &= ~(((1 << 4) - 1) << 11)
+ self._flags |= value << 11
+
+ @property
+ def aa(self):
+ return self._flags & (1 << 10)
+ @aa.setter
+ def aa(self, value):
+ if value:
+ self._flags |= (1 << 10)
+ else:
+ self._flags &= ~(1 << 10)
+
+ @property
+ def tc(self):
+ return self._flags & (1 << 9)
+ @tc.setter
+ def tc(self, value):
+ if value:
+ self._flags |= (1 << 9)
+ else:
+ self._flags &= ~(1 << 9)
+
+ @property
+ def rd(self):
+ return self._flags & (1 << 8)
+ @rd.setter
+ def rd(self, value):
+ if value:
+ self._flags |= (1 << 8)
+ else:
+ self._flags &= ~(1 << 8)
+
+ @property
+ def ra(self):
+ return self._flags & (1 << 7)
+ @ra.setter
+ def ra(self, value):
+ if value:
+ self._flags |= (1 << 7)
+ else:
+ self._flags &= ~(1 << 7)
+
+ @property
+ def z(self):
+ return self._flags & (((1 << 3) - 1) << 4) >> 4
+ @z.setter
+ def z(self, value):
+ if value:
+ raise ValueError("non-zero zero flag")
+
+ @property
+ def rcode(self):
+ return self._flags & ((1 << 4) - 1)
+ @rcode.setter
+ def rcode(self, value):
+ if value > 0b1111:
+ raise ValueError("invalid return code")
+ self._flags &= ~((1 << 4) - 1)
+ self._flags |= value
+
+
+class Question(object):
+ """ An entry in the question section.
+
+ See section 4.1.2 of RFC 1035 for more info.
+ """
+
+ def __init__(self, qname, qtype, qclass):
+ """ Create a new entry in the question section
+
+ Args:
+ qname (str): QNAME
+ qtype (Type): QTYPE
+ qclass (Class): QCLASS
+ """
+ self.qname = qname
+ self.qtype = qtype
+ self.qclass = qclass
+
+ def to_bytes(self, offset, composer):
+ """ Convert Question to bytes """
+ bqname = composer.to_bytes(offset, [self.qname])
+ bqtype = struct.pack("!H", self.qtype)
+ bqclass = struct.pack("!H", self.qclass)
+ return bqname + bqtype + bqclass
+
+ @classmethod
+ def from_bytes(cls, packet, offset, parser):
+ """ Convert Question from bytes """
+ qnames, offset = parser.from_bytes(packet, offset, 1)
+ qname = qnames[0]
+ qtype, qclass = struct.unpack_from("!2H", packet, offset)
+ return cls(qname, qtype, qclass), offset + 4
diff --git a/project2/proj2_s4498062/dns/rcodes.py b/project2/proj2_s4498062/dns/rcodes.py
new file mode 100644
index 0000000..4b1d33d
--- /dev/null
+++ b/project2/proj2_s4498062/dns/rcodes.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python2
+
+""" DNS RCODE values
+
+This module contains an Enum of RCODE values. See section 4.1.4 of RFC 1035 for
+more info.
+"""
+
+class RCode(object):
+ """ Enum of RCODE values
+
+ Usage:
+ >>> NoError
+ 0
+ >>> NXDomain
+ 3
+ """
+
+ NoError = 0
+ FormErr = 1
+ ServFail = 2
+ NXDomain = 3
+ NotImp = 4
+ Refused = 5
+ YXDomain = 6
+ YXRRSet = 7
+ NXRRSet = 8
+ NotAuth = 9
+ NotZone = 10
+ BADVERS = 16
+ BADSIG = 16
+ BADKEY = 17
+ BADTIME = 18
+ BADMODE = 19
+ BADNAME = 20
+ BADALG = 21
+ BADTRUNC = 22
+
+ by_string = {
+ "NoError": NoError,
+ "FormErr": FormErr,
+ "ServFail": ServFail,
+ "NXDomain": NXDomain,
+ "NotImp": NotImp,
+ "Refused": Refused,
+ "YXDomain": YXDomain,
+ "YXRRSet": YXRRSet,
+ "NXRRSet": NXRRSet,
+ "NotAuth": NotAuth,
+ "NotZone": NotZone,
+ "BADVERS": BADVERS,
+ "BADSIG": BADSIG,
+ "BADKEY": BADKEY,
+ "BADTIME": BADTIME,
+ "BADMODE": BADMODE,
+ "BADNAME": BADNAME,
+ "BADALG": BADALG,
+ "BADTRUNC": BADTRUNC
+ }
+
+ by_value = dict([(y, x) for x, y in by_string.items()])
+
+ @staticmethod
+ def to_string(rcode):
+ """ Convert an RCode to a string
+
+ Usage:
+ >>> RCode.to_string(RCode.NoError)
+ 'NoError'
+ """
+ return RCode.by_value[rcode]
+
+ @staticmethod
+ def from_string(string):
+ """ Convert a string to an RCode
+
+ Usage:
+ >>> RCode.from_string('NoError')
+ 0
+ """
+ return RCode.by_string[string]
diff --git a/project2/proj2_s4498062/dns/resolver.py b/project2/proj2_s4498062/dns/resolver.py
new file mode 100644
index 0000000..ba27f74
--- /dev/null
+++ b/project2/proj2_s4498062/dns/resolver.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python2
+
+""" DNS Resolver
+
+This module contains a class for resolving hostnames. You will have to implement
+things in this module. This resolver will be both used by the DNS client and the
+DNS server, but with a different list of servers.
+"""
+
+import socket
+
+from dns.classes import Class
+from dns.types import Type
+
+import dns.cache
+import dns.message
+import dns.rcodes
+
+class Resolver(object):
+ """ DNS resolver """
+
+ def __init__(self, caching, ttl):
+ """ Initialize the resolver
+
+ Args:
+ caching (bool): caching is enabled if True
+ ttl (int): ttl of cache entries (if > 0)
+ """
+ self.caching = caching
+ self.ttl = ttl
+
+ def gethostbyname(self, hostname):
+ """ Translate a host name to IPv4 address.
+
+ Currently this method contains an example. You will have to replace
+ this example with example with the algorithm described in section
+ 5.3.3 in RFC 1034.
+
+ Args:
+ hostname (str): the hostname to resolve
+
+ Returns:
+ (str, [str], [str]): (hostname, aliaslist, ipaddrlist)
+ """
+ timeout = 2
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.settimeout(timeout)
+
+ # Create and send query
+ question = dns.message.Question(hostname, Type.A, Class.IN)
+ header = dns.message.Header(9001, 0, 1, 0, 0, 0)
+ header.qr = 0
+ header.opcode = 0
+ header.rd = 1
+ query = dns.message.Message(header, [question])
+ sock.sendto(query.to_bytes(), ("8.8.8.8", 53))
+
+ # Receive response
+ data = sock.recv(512)
+ response = dns.message.Message.from_bytes(data)
+
+ # Get data
+ aliases = []
+ for additional in response.additionals:
+ if additional.type_ == Type.CNAME:
+ aliases.append(additional.rdata.data)
+ addresses = []
+ for answer in response.answers:
+ if answer.type_ == Type.A:
+ addresses.append(answer.rdata.data)
+
+ return hostname, aliases, addresses
diff --git a/project2/proj2_s4498062/dns/resource.py b/project2/proj2_s4498062/dns/resource.py
new file mode 100644
index 0000000..fdf51de
--- /dev/null
+++ b/project2/proj2_s4498062/dns/resource.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python2
+
+""" A DNS resource record
+
+This class contains classes for DNS resource records and record data. This
+module is fully implemented. You will have this module in the implementation
+of your resolver and server.
+"""
+
+import socket
+import struct
+
+from dns.types import Type
+
+
+class ResourceRecord(object):
+ """ DNS resource record """
+ def __init__(self, name, type_, class_, ttl, rdata):
+ """ Create a new resource record
+
+ Args:
+ name (str): domain name
+ type_ (Type): the type
+ class_ (Class): the class
+ rdata (RecordData): the record data
+ """
+ self.name = name
+ self.type_ = type_
+ self.class_ = class_
+ self.ttl = ttl
+ self.rdata = rdata
+
+ def to_bytes(self, offset, composer):
+ """ Convert ResourceRecord to bytes """
+ record = composer.to_bytes(offset, [self.name])
+ record += struct.pack("!HHI", self.type_, self.class_, self.ttl)
+ offset += len(record) + 2
+ rdata = self.rdata.to_bytes(offset, composer)
+ record += struct.pack("!H", len(rdata)) + rdata
+ return record
+
+ @classmethod
+ def from_bytes(cls, packet, offset, parser):
+ """ Convert ResourceRecord from bytes """
+ names, offset = parser.from_bytes(packet, offset, 1)
+ name = names[0]
+ type_, class_, ttl, rdlength = struct.unpack_from("!HHIH", packet, offset)
+ offset += 10
+ rdata = RecordData.from_bytes(type_, packet, offset, rdlength, parser)
+ offset += rdlength
+ return cls(name, type_, class_, ttl, rdata), offset
+
+
+class RecordData(object):
+ """ Record Data """
+
+ def __init__(self, data):
+ """ Initialize the record data
+
+ Args:
+ data (str): data
+ """
+ self.data = data
+
+ @staticmethod
+ def create(type_, data):
+ """ Create a RecordData object from bytes
+
+ Args:
+ type_ (Type): type
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ classdict = {
+ Type.A: ARecordData,
+ Type.CNAME: CNAMERecordData,
+ Type.NS: NSRecordData,
+ Type.AAAA: AAAARecordData
+ }
+ if type_ in classdict:
+ return classdict[type_](data)
+ else:
+ return GenericRecordData(data)
+
+ @staticmethod
+ def from_bytes(type_, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ type_ (Type): type
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ classdict = {
+ Type.A: ARecordData,
+ Type.CNAME: CNAMERecordData,
+ Type.NS: NSRecordData,
+ Type.AAAA: AAAARecordData
+ }
+ if type_ in classdict:
+ return classdict[type_].from_bytes(
+ packet, offset, rdlength, parser)
+ else:
+ return GenericRecordData.from_bytes(
+ packet, offset, rdlength, parser)
+
+
+class ARecordData(RecordData):
+ """ Record data for A type """
+
+ def to_bytes(self, offset, composer):
+ """ Convert to bytes
+
+ Args:
+ offset (int): offset in message
+ composer (Composer): domain name composer
+ """
+ return socket.inet_aton(self.data)
+
+ @classmethod
+ def from_bytes(cls, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ data = socket.inet_ntoa(packet[offset:offset+4])
+ return cls(data)
+
+
+class CNAMERecordData(RecordData):
+ """ Record data for CNAME type """
+
+ def to_bytes(self, offset, composer):
+ """ Convert to bytes
+
+ Args:
+ offset (int): offset in message
+ composer (Composer): domain name composer
+ """
+ return composer.to_bytes(offset, [self.data])
+
+ @classmethod
+ def from_bytes(cls, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ names, offset = parser.from_bytes(packet, offset, 1)
+ data = names[0]
+ return cls(data)
+
+
+class NSRecordData(RecordData):
+ """ Record data for NS type """
+
+ def to_bytes(self, offset, composer):
+ """ Convert to bytes
+
+ Args:
+ offset (int): offset in message
+ composer (Composer): domain name composer
+ """
+ return composer.to_bytes(offset, [self.data])
+
+ @classmethod
+ def from_bytes(cls, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ names, offset = parser.from_bytes(packet, offset, 1)
+ data = names[0]
+ return cls(data)
+
+
+class AAAARecordData(RecordData):
+ """ Record data for AAAA type """
+
+ def to_bytes(self, offset, composer):
+ """ Convert to bytes
+
+ Args:
+ offset (int): offset in message
+ composer (Composer): domain name composer
+ """
+ return socket.inet_pton(socket.AF_INET6, self.data)
+
+ @classmethod
+ def from_bytes(cls, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ data = socket.inet_ntop(socket.AF_INET6, packet[offset:offset+16])
+ return cls(data)
+
+
+class GenericRecordData(RecordData):
+ """ Generic Record Data (for other types) """
+
+ def to_bytes(self, offset, composer):
+ """ Convert to bytes
+
+ Args:
+ offset (int): offset in message
+ composer (Composer): domain name composer
+ """
+ return self.data
+
+ @classmethod
+ def from_bytes(cls, packet, offset, rdlength, parser):
+ """ Create a RecordData object from bytes
+
+ Args:
+ packet (bytes): packet
+ offset (int): offset in message
+ rdlength (int): length of rdata
+ parser (int): domain name parser
+ """
+ data = packet[offset:offset+rdlength]
+ return cls(data)
diff --git a/project2/proj2_s4498062/dns/server.py b/project2/proj2_s4498062/dns/server.py
new file mode 100644
index 0000000..d4e3109
--- /dev/null
+++ b/project2/proj2_s4498062/dns/server.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python2
+
+""" A recursive DNS server
+
+This module provides a recursive DNS server. You will have to implement this
+server using the algorithm described in section 4.3.2 of RFC 1034.
+"""
+
+from threading import Thread
+
+
+class RequestHandler(Thread):
+ """ A handler for requests to the DNS server """
+
+ def __init__(self):
+ """ Initialize the handler thread """
+ super(RequestHandler, self).__init__()
+ self.daemon = True
+
+ def run(self):
+ """ Run the handler thread """
+ # TODO: Handle DNS request
+ pass
+
+
+class Server(object):
+ """ A recursive DNS server """
+
+ def __init__(self, port, caching, ttl):
+ """ Initialize the server
+
+ Args:
+ port (int): port that server is listening on
+ caching (bool): server uses resolver with caching if true
+ ttl (int): ttl for records (if > 0) of cache
+ """
+ self.caching = caching
+ self.ttl = ttl
+ self.port = port
+ self.done = False
+ # TODO: create socket
+
+ def serve(self):
+ """ Start serving request """
+ # TODO: start listening
+ while not self.done:
+ # TODO: receive request and open handler
+ pass
+
+ def shutdown(self):
+ """ Shutdown the server """
+ self.done = True
+ # TODO: shutdown socket
diff --git a/project2/proj2_s4498062/dns/types.py b/project2/proj2_s4498062/dns/types.py
new file mode 100644
index 0000000..f918050
--- /dev/null
+++ b/project2/proj2_s4498062/dns/types.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python2
+
+""" DNS TYPE and QTYPE values
+
+This module contains an Enum for TYPE and QTYPE values. This Enum also contains
+a method for converting Enum values to strings. See sections 3.2.2 and 3.2.3 of
+RFC 1035 for more information.
+"""
+
+class Type(object):
+ """ DNS TYPE and QTYPE
+
+ Usage:
+ >>> Type.A
+ 1
+ >>> Type.CNAME
+ 5
+ """
+ A = 1
+ NS = 2
+ CNAME = 5
+ SOA = 6
+ WKS = 11
+ PTR = 12
+ HINFO = 13
+ MINFO = 14
+ MX = 15
+ TXT = 16
+ AAAA = 28
+ ANY = 255
+
+ by_string = {
+ "A": A,
+ "NS": NS,
+ "CNAME": CNAME,
+ "SOA": SOA,
+ "WKS": WKS,
+ "PTR": PTR,
+ "HINFO": HINFO,
+ "MINFO": MINFO,
+ "MX": MX,
+ "TXT": TXT,
+ "AAAA": AAAA,
+ "*": ANY
+ }
+
+ by_value = dict([(y, x) for x, y in by_string.items()])
+
+ @staticmethod
+ def to_string(type_):
+ """ Convert a Type to a string
+
+ Usage:
+ >>> Type.to_string(Type.A)
+ 'A'
+ """
+ return Type.by_value[type_]
+
+ @staticmethod
+ def from_string(string):
+ """ Convert a string to a Type
+
+ Usage:
+ >>> Type.from_string('CNAME')
+ 5
+ """
+ return Type.by_string[string]
diff --git a/project2/proj2_s4498062/dns/zone.py b/project2/proj2_s4498062/dns/zone.py
new file mode 100644
index 0000000..8ada3db
--- /dev/null
+++ b/project2/proj2_s4498062/dns/zone.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python2
+
+""" Zones of domain name space
+
+See section 6.1.2 of RFC 1035 and section 4.2 of RFC 1034.
+Instead of tree structures we simply use dictionaries from domain names to
+zones or record sets.
+
+These classes are merely a suggestion, feel free to use something else.
+"""
+
+
+class Catalog(object):
+ """ A catalog of zones """
+
+ def __init__(self):
+ """ Initialize the catalog """
+ self.zones = {}
+
+ def add_zone(self, name, zone):
+ """ Add a new zone to the catalog
+
+ Args:
+ name (str): root domain name
+ zone (Zone): zone
+ """
+ self.zones[name] = zone
+
+
+class Zone(object):
+ """ A zone in the domain name space """
+
+ def __init__(self):
+ """ Initialize the Zone """
+ self.records = {}
+
+ def add_node(self, name, record_set):
+ """ Add a record set to the zone
+
+ Args:
+ name (str): domain name
+ record_set ([ResourceRecord]): resource records
+ """
+ self.records[name] = record_set
+
+ def read_master_file(self, filename):
+ """ Read the zone from a master file
+
+ See section 5 of RFC 1035.
+
+ Args:
+ filename (str): the filename of the master file
+ """
+ pass
diff --git a/project2/proj2_s4498062/dns_client.py b/project2/proj2_s4498062/dns_client.py
new file mode 100644
index 0000000..b15d566
--- /dev/null
+++ b/project2/proj2_s4498062/dns_client.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python2
+
+""" Simple DNS client
+
+A simple example of a client using the DNS resolver.
+"""
+
+import dns.resolver
+
+if __name__ == "__main__":
+ # Parse arguments
+ import argparse
+ parser = argparse.ArgumentParser(description="DNS Client")
+ parser.add_argument("hostname", help="hostname to resolve")
+ parser.add_argument("-c", "--caching", action="store_true",
+ help="Enable caching")
+ parser.add_argument("-t", "--ttl", metavar="time", type=int, default=0,
+ help="TTL value of cached entries")
+ args = parser.parse_args()
+
+ # Resolve hostname
+ resolver = dns.resolver.Resolver(args.caching, args.ttl)
+ hostname, aliases, addresses = resolver.gethostbyname(args.hostname)
+
+ # Print output
+ print(hostname)
+ print(aliases)
+ print(addresses)
diff --git a/project2/proj2_s4498062/dns_server.py b/project2/proj2_s4498062/dns_server.py
new file mode 100644
index 0000000..07e0d9a
--- /dev/null
+++ b/project2/proj2_s4498062/dns_server.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python2
+
+""" DNS server
+
+This script contains the code for starting a DNS server.
+"""
+
+import dns.server
+
+if __name__ == "__main__":
+ # Parse arguments
+ import argparse
+ parser = argparse.ArgumentParser(description="DNS Server")
+ parser.add_argument("-c", "--caching", action="store_true",
+ help="Enable caching")
+ parser.add_argument("-t", "--ttl", metavar="time", type=int, default=0,
+ help="TTL value of cached entries (if > 0)")
+ parser.add_argument("-p", "--port", type=int, default=5353,
+ help="Port which server listens on")
+ args = parser.parse_args()
+
+ # Start server
+ server = dns.server.Server(args.port, args.caching, args.ttl)
+ try:
+ server.serve()
+ except KeyboardInterrupt:
+ server.shutdown()
+ print()
diff --git a/project2/proj2_s4498062/dns_tests.py b/project2/proj2_s4498062/dns_tests.py
new file mode 100644
index 0000000..26bc00b
--- /dev/null
+++ b/project2/proj2_s4498062/dns_tests.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python2
+
+""" Tests for your DNS resolver and server """
+
+portnr = 5353
+server = "localhost"
+
+class TestResolver(unittest.TestCase):
+ pass
+
+
+class TestResolverCache(unittest.TestCase):
+ pass
+
+
+class TestServer(unittest.TestCase):
+ pass
+
+
+if __name__ == "__main__":
+ # Parse command line arguments
+ import argparse
+ parser = argparse.ArgumentParser(description="HTTP Tests")
+ parser.add_argument("-s", "--server", type=str, default="localhost")
+ parser.add_argument("-p", "--port", type=int, default=5001)
+ args, extra = parser.parse_known_args()
+ portnr = args.port
+ server = args.server
+
+ # Pass the extra arguments to unittest
+ sys.argv[1:] = extra
+
+ # Start test suite
+ unittest.main()