summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--project2/proj2_s4498062/.gitignore2
-rw-r--r--project2/proj2_s4498062/README.md90
-rw-r--r--project2/proj2_s4498062/dns/__init__.py2
-rw-r--r--project2/proj2_s4498062/dns/cache.py182
-rw-r--r--project2/proj2_s4498062/dns/classes.py86
-rw-r--r--project2/proj2_s4498062/dns/domainname.py226
-rw-r--r--project2/proj2_s4498062/dns/message.py544
-rw-r--r--project2/proj2_s4498062/dns/rcodes.py138
-rw-r--r--project2/proj2_s4498062/dns/resolver.py140
-rw-r--r--project2/proj2_s4498062/dns/resource.py472
-rw-r--r--project2/proj2_s4498062/dns/server.py106
-rw-r--r--project2/proj2_s4498062/dns/types.py110
-rw-r--r--project2/proj2_s4498062/dns/zone.py108
-rw-r--r--project2/proj2_s4498062/dns_client.py62
-rw-r--r--project2/proj2_s4498062/dns_server.py64
-rw-r--r--project2/proj2_s4498062/dns_tests.py78
16 files changed, 1217 insertions, 1193 deletions
diff --git a/project2/proj2_s4498062/.gitignore b/project2/proj2_s4498062/.gitignore
index 94487b9..0d20b64 100644
--- a/project2/proj2_s4498062/.gitignore
+++ b/project2/proj2_s4498062/.gitignore
@@ -1 +1 @@
-*.pyc
+*.pyc
diff --git a/project2/proj2_s4498062/README.md b/project2/proj2_s4498062/README.md
index ce89073..7aee804 100644
--- a/project2/proj2_s4498062/README.md
+++ b/project2/proj2_s4498062/README.md
@@ -1,45 +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.
-
+# 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
index 144e7c6..18ff536 100644
--- a/project2/proj2_s4498062/dns/__init__.py
+++ b/project2/proj2_s4498062/dns/__init__.py
@@ -1 +1 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python2
diff --git a/project2/proj2_s4498062/dns/cache.py b/project2/proj2_s4498062/dns/cache.py
index 1b64b51..eab51c5 100644
--- a/project2/proj2_s4498062/dns/cache.py
+++ b/project2/proj2_s4498062/dns/cache.py
@@ -1,91 +1,91 @@
-#!/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
-
-
+#!/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
index aeb1da7..767b2f6 100644
--- a/project2/proj2_s4498062/dns/classes.py
+++ b/project2/proj2_s4498062/dns/classes.py
@@ -1,43 +1,43 @@
-#!/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_):
- return Class.by_value[class_]
-
- @staticmethod
- def from_string(string):
- return Class.by_string[string]
+#!/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_):
+ return Class.by_value[class_]
+
+ @staticmethod
+ def from_string(string):
+ return Class.by_string[string]
diff --git a/project2/proj2_s4498062/dns/domainname.py b/project2/proj2_s4498062/dns/domainname.py
index 09f98fa..85e14bf 100644
--- a/project2/proj2_s4498062/dns/domainname.py
+++ b/project2/proj2_s4498062/dns/domainname.py
@@ -1,113 +1,113 @@
-#!/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):
- def __init__(self):
- self.offsets = dict()
-
- def to_bytes(self, offset, dnames):
- # Convert each domain name in to bytes
- result = b""
- for i, dname in enumerate(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):
- def __init__(self):
- self.labels = dict()
-
- def from_bytes(self, packet, offset, num):
- begin_offset = offset
- dnames = []
-
- # Read the domain names
- for i 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
+#!/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):
+ def __init__(self):
+ self.offsets = dict()
+
+ def to_bytes(self, offset, dnames):
+ # Convert each domain name in to bytes
+ result = b""
+ for i, dname in enumerate(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):
+ def __init__(self):
+ self.labels = dict()
+
+ def from_bytes(self, packet, offset, num):
+ begin_offset = offset
+ dnames = []
+
+ # Read the domain names
+ for i 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
index f948f2f..3c86443 100644
--- a/project2/proj2_s4498062/dns/message.py
+++ b/project2/proj2_s4498062/dns/message.py
@@ -1,272 +1,272 @@
-#!/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 socket
-import struct
-
-from dns.classes import Class
-from dns.domainname import Parser, Composer
-from dns.resource import ResourceRecord
-from dns.types import Type
-
-
-class Message(object):
- """ DNS message """
-
- def __init__(self, header, questions=[], answers=[], authorities=[], additionals=[]):
- """ 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
- """
- 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 i in range(header.qd_count):
- question, offset = Question.from_bytes(packet, offset, parser)
- questions.append(question)
-
- # Parse answers
- answers = []
- for i in range(header.an_count):
- answer, offset = ResourceRecord.from_bytes(packet, offset, parser)
- answers.append(answer)
-
- # Parse authorities
- authorities = []
- for i in range(header.ns_count):
- authority, offset = ResourceRecord.from_bytes(packet, offset, parser)
- authorities.append(authority)
-
- # Parse additionals
- additionals = []
- for i 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 ShortHeader
- 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
+#!/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 socket
+import struct
+
+from dns.classes import Class
+from dns.domainname import Parser, Composer
+from dns.resource import ResourceRecord
+from dns.types import Type
+
+
+class Message(object):
+ """ DNS message """
+
+ def __init__(self, header, questions=[], answers=[], authorities=[], additionals=[]):
+ """ 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
+ """
+ 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 i in range(header.qd_count):
+ question, offset = Question.from_bytes(packet, offset, parser)
+ questions.append(question)
+
+ # Parse answers
+ answers = []
+ for i in range(header.an_count):
+ answer, offset = ResourceRecord.from_bytes(packet, offset, parser)
+ answers.append(answer)
+
+ # Parse authorities
+ authorities = []
+ for i in range(header.ns_count):
+ authority, offset = ResourceRecord.from_bytes(packet, offset, parser)
+ authorities.append(authority)
+
+ # Parse additionals
+ additionals = []
+ for i 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 ShortHeader
+ 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
index d1b8c15..a35482c 100644
--- a/project2/proj2_s4498062/dns/rcodes.py
+++ b/project2/proj2_s4498062/dns/rcodes.py
@@ -1,69 +1,69 @@
-#!/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):
- return RCode.by_value[rcode]
-
- @staticmethod
- def from_string(string):
- return RCode.by_string[string]
+#!/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):
+ return RCode.by_value[rcode]
+
+ @staticmethod
+ def from_string(string):
+ return RCode.by_string[string]
diff --git a/project2/proj2_s4498062/dns/resolver.py b/project2/proj2_s4498062/dns/resolver.py
index c9f6632..ca4be95 100644
--- a/project2/proj2_s4498062/dns/resolver.py
+++ b/project2/proj2_s4498062/dns/resolver.py
@@ -1,70 +1,70 @@
-#!/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.cache import RecordCache
-from dns.classes import Class
-from dns.message import Message, Header, Question
-from dns.rcodes import RCode
-from dns.types import Type
-
-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)
- """
- 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
+#!/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.cache import RecordCache
+from dns.classes import Class
+from dns.message import Message, Header, Question
+from dns.rcodes import RCode
+from dns.types import Type
+
+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)
+ """
+ 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
index 57fc53e..350c87d 100644
--- a/project2/proj2_s4498062/dns/resource.py
+++ b/project2/proj2_s4498062/dns/resource.py
@@ -1,236 +1,236 @@
-#!/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.classes import Class
-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 """
- name = composer.to_bytes(offset, [self.name])
- offset += len(name)
- rdata = self.rdata.to_bytes(offset, composer)
- return (name +
- struct.pack("!HHIH",
- self.type_,
- self.class_,
- self.ttl,
- len(rdata)) +
- rdata)
-
- @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):
- 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):
- 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):
- 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):
- 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(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):
- 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(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)
+#!/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.classes import Class
+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 """
+ name = composer.to_bytes(offset, [self.name])
+ offset += len(name)
+ rdata = self.rdata.to_bytes(offset, composer)
+ return (name +
+ struct.pack("!HHIH",
+ self.type_,
+ self.class_,
+ self.ttl,
+ len(rdata)) +
+ rdata)
+
+ @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):
+ 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):
+ 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):
+ 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):
+ 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(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):
+ 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(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
index 7a8fc6d..7234e36 100644
--- a/project2/proj2_s4498062/dns/server.py
+++ b/project2/proj2_s4498062/dns/server.py
@@ -1,53 +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.
-"""
-
-import socket
-from threading import Thread
-
-
-class RequestHandler(Thread):
- """ A handler for requests to the DNS server """
-
- def __init__(self):
- """ Initialize the handler thread """
- super().__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
- # 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
+#!/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.
+"""
+
+import socket
+from threading import Thread
+
+
+class RequestHandler(Thread):
+ """ A handler for requests to the DNS server """
+
+ def __init__(self):
+ """ Initialize the handler thread """
+ super().__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
+ # 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
index 27f2652..f0d9ef3 100644
--- a/project2/proj2_s4498062/dns/types.py
+++ b/project2/proj2_s4498062/dns/types.py
@@ -1,55 +1,55 @@
-#!/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_):
- return Type.by_value[type_]
-
- @staticmethod
- def from_string(string):
- return Type.by_string[string]
+#!/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_):
+ return Type.by_value[type_]
+
+ @staticmethod
+ def from_string(string):
+ return Type.by_string[string]
diff --git a/project2/proj2_s4498062/dns/zone.py b/project2/proj2_s4498062/dns/zone.py
index e4d3f27..accea99 100644
--- a/project2/proj2_s4498062/dns/zone.py
+++ b/project2/proj2_s4498062/dns/zone.py
@@ -1,54 +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
+#!/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
index ab92f31..d89c1e4 100644
--- a/project2/proj2_s4498062/dns_client.py
+++ b/project2/proj2_s4498062/dns_client.py
@@ -1,28 +1,34 @@
-#!/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(["localhost"], 15, args.caching, args.ttl)
- hostname, aliases, addresses = resolver.gethostbyname(args.hostname)
-
- # Print output
- print(hostname)
- print(aliases)
- print(addresses)
+#!/usr/bin/env python2
+""" Simple DNS client
+
+A simple example of a client using the DNS resolver.
+"""
+
+import dns.resolver
+
+def main():
+ """Run the client"""
+ # 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(["localhost"], 15, args.caching, args.ttl)
+ hostname, aliases, addresses = resolver.gethostbyname(args.hostname)
+
+ # Print output
+ print hostname
+ print aliases
+ print addresses
+
+if __name__ == "__main__":
+ main()
+
diff --git a/project2/proj2_s4498062/dns_server.py b/project2/proj2_s4498062/dns_server.py
index f1d4b09..0fcf981 100644
--- a/project2/proj2_s4498062/dns_server.py
+++ b/project2/proj2_s4498062/dns_server.py
@@ -1,28 +1,36 @@
-#!/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()
+#!/usr/bin/env python2
+
+""" DNS server
+
+This script contains the code for starting a DNS server.
+"""
+
+import dns.server
+
+def main():
+ """Run the server"""
+ # 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()
+
+if __name__ == "__main__":
+ main()
+
diff --git a/project2/proj2_s4498062/dns_tests.py b/project2/proj2_s4498062/dns_tests.py
index 66c04ed..be7da9b 100644
--- a/project2/proj2_s4498062/dns_tests.py
+++ b/project2/proj2_s4498062/dns_tests.py
@@ -1,34 +1,44 @@
-#!/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()
+#!/usr/bin/env python2
+""" Tests for your DNS resolver and server """
+
+import sys
+import unittest
+
+PORT = 5353
+SERVER = "localhost"
+
+class TestResolver(unittest.TestCase):
+ """Unit tests for the resolver"""
+ pass
+
+
+class TestResolverCache(unittest.TestCase):
+ """Unit tests for the resolver cache"""
+ pass
+
+
+class TestServer(unittest.TestCase):
+ """Unit tests for the server"""
+ pass
+
+
+def main():
+ """Run the tests"""
+ # 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()
+ PORT = args.port
+ SERVER = args.server
+
+ # Pass the extra arguments to unittest
+ sys.argv[1:] = extra
+
+ # Start test suite
+ unittest.main()
+
+if __name__ == "__main__":
+ main()
+