From 7120add95d0c5b8a97af861785ec9fe1cfc7eaee Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Mon, 2 May 2016 15:36:05 +0200 Subject: dos2unix --- project2/proj2_s4498062/.gitignore | 2 +- project2/proj2_s4498062/README.md | 90 ++--- project2/proj2_s4498062/dns/__init__.py | 2 +- project2/proj2_s4498062/dns/cache.py | 182 +++++----- project2/proj2_s4498062/dns/classes.py | 86 ++--- project2/proj2_s4498062/dns/domainname.py | 226 ++++++------- project2/proj2_s4498062/dns/message.py | 544 +++++++++++++++--------------- project2/proj2_s4498062/dns/rcodes.py | 138 ++++---- project2/proj2_s4498062/dns/resolver.py | 140 ++++---- project2/proj2_s4498062/dns/resource.py | 472 +++++++++++++------------- project2/proj2_s4498062/dns/server.py | 106 +++--- project2/proj2_s4498062/dns/types.py | 110 +++--- project2/proj2_s4498062/dns/zone.py | 108 +++--- project2/proj2_s4498062/dns_client.py | 62 ++-- project2/proj2_s4498062/dns_server.py | 64 ++-- project2/proj2_s4498062/dns_tests.py | 78 +++-- 16 files changed, 1217 insertions(+), 1193 deletions(-) (limited to 'project2/proj2_s4498062') 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() + -- cgit v1.2.3