diff options
-rw-r--r-- | project2/proj2_s4498062/.gitignore | 2 | ||||
-rw-r--r-- | project2/proj2_s4498062/README.md | 90 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/__init__.py | 2 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/cache.py | 182 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/classes.py | 86 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/domainname.py | 226 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/message.py | 544 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/rcodes.py | 138 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resolver.py | 140 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resource.py | 472 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/server.py | 106 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/types.py | 110 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/zone.py | 108 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns_client.py | 62 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns_server.py | 64 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns_tests.py | 78 |
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() + |