diff options
Diffstat (limited to 'project2')
-rw-r--r-- | project2/proj2_s4498062/.gitignore | 2 | ||||
-rw-r--r-- | project2/proj2_s4498062/README.md | 45 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/__init__.py | 0 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/cache.py | 114 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/classes.py | 43 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/domainname.py | 112 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/message.py | 325 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/rcodes.py | 70 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/regexes.py | 33 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resolver.py | 153 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resource.py | 241 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/server.py | 51 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/types.py | 55 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/zone.py | 54 | ||||
-rwxr-xr-x | project2/proj2_s4498062/dns_client.py | 34 | ||||
-rwxr-xr-x | project2/proj2_s4498062/dns_server.py | 36 | ||||
-rwxr-xr-x | project2/proj2_s4498062/dns_tests.py | 46 |
17 files changed, 0 insertions, 1414 deletions
diff --git a/project2/proj2_s4498062/.gitignore b/project2/proj2_s4498062/.gitignore deleted file mode 100644 index 0191c0c..0000000 --- a/project2/proj2_s4498062/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -.dns.cache diff --git a/project2/proj2_s4498062/README.md b/project2/proj2_s4498062/README.md deleted file mode 100644 index 7aee804..0000000 --- a/project2/proj2_s4498062/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# 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 deleted file mode 100644 index e69de29..0000000 --- a/project2/proj2_s4498062/dns/__init__.py +++ /dev/null diff --git a/project2/proj2_s4498062/dns/cache.py b/project2/proj2_s4498062/dns/cache.py deleted file mode 100644 index a3a9304..0000000 --- a/project2/proj2_s4498062/dns/cache.py +++ /dev/null @@ -1,114 +0,0 @@ -"""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 -import time - -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, - "timestamp": obj.timestamp - } - 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"]) - timestamp = dct["timestamp"] - return ResourceRecord(name, type_, class_, ttl, rdata, timestamp) - - -class RecordCache(object): - """ Cache for ResourceRecords """ - - FILE = '.dns.cache' - - def __init__(self): - """ Initialize the RecordCache - - Args: - ttl (int): TTL of cached entries (if > 0) - """ - self.records = [] - self.read_cache_file() - - def __del__(self): - self.write_cache_file() - - def remove_old(self): - """Remove entries for which the TTL has expired""" - now = int(time.clock()) - for record in reversed(self.records): - if record.ttl + record.timestamp < now: - self.records.remove(record) - - 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 - """ - self.remove_old() - for record in self.records: - if record.name == dname and \ - record.type_ == type_ and \ - record.class_ == class_: - yield record - - def add_record(self, record): - """ Add a new Record to the cache - - Args: - record (ResourceRecord): the record added to the cache - """ - self.records.append(record) - - def read_cache_file(self): - """ Read the cache file from disk """ - try: - with open(self.FILE, 'r') as jsonfile: - self.records = json.load( - jsonfile, object_hook=resource_from_json) - except IOError: - pass - - def write_cache_file(self): - """ Write the cache file to disk """ - self.remove_old() - with open(self.FILE, 'w') as jsonfile: - json.dump(self.records, jsonfile, cls=ResourceEncoder, indent=4) diff --git a/project2/proj2_s4498062/dns/classes.py b/project2/proj2_s4498062/dns/classes.py deleted file mode 100644 index 9446f01..0000000 --- a/project2/proj2_s4498062/dns/classes.py +++ /dev/null @@ -1,43 +0,0 @@ -""" DNS CLASS and QCLASS values - -This module contains an Enum of CLASS and QCLASS values. The Enum also contains -a method for converting values to strings. See sections 3.2.4 and 3.2.5 of RFC -1035 for more information. -""" - - -class Class(object): - """ Enum of CLASS and QCLASS values - - Usage: - >>> Class.IN - 1 - >>> Class.ANY - 255 - """ - - IN = 1 - CS = 2 - CH = 3 - HS = 4 - ANY = 255 - - by_string = { - "IN": IN, - "CS": CS, - "CH": CH, - "HS": HS, - "*": ANY - } - - by_value = dict([(y, x) for x, y in by_string.items()]) - - @staticmethod - def to_string(class_): - """Convert a class to a string""" - return Class.by_value[class_] - - @staticmethod - def from_string(string): - """Convert a string to a class""" - return Class.by_string[string] diff --git a/project2/proj2_s4498062/dns/domainname.py b/project2/proj2_s4498062/dns/domainname.py deleted file mode 100644 index 6dbb039..0000000 --- a/project2/proj2_s4498062/dns/domainname.py +++ /dev/null @@ -1,112 +0,0 @@ -""" 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 _, 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): - dnames = [] - - # Read the domain names - for _ in range(num): - # Read a new domain name - dname = "" - prev_offsets = [] - done = False - while done is False: - # Read length of next label - llength = struct.unpack_from("!B", packet, offset)[0] - - # Done reading domain when length is zero - if llength == 0: - offset += 1 - break - - # Compression label - elif (llength >> 6) == 3: - new_offset = offset + 2 - target = struct.unpack_from("!H", packet, offset)[0] - target -= 3 << 14 - label = self.labels[target] - done = True - - # Normal label - else: - new_offset = offset + llength + 1 - label = struct.unpack_from( - "{}s".format(llength), - packet, offset+1)[0] - - # Add label to dictionary - self.labels[offset] = label - for prev_offset in prev_offsets: - self.labels[prev_offset] += "." + label - prev_offsets.append(offset) - - # Update offset - offset = new_offset - - # Append label to domain name - if len(dname) > 0: - dname += "." - dname += label - - # Append domain name to list - dnames.append(dname) - - return dnames, offset diff --git a/project2/proj2_s4498062/dns/message.py b/project2/proj2_s4498062/dns/message.py deleted file mode 100644 index a8ad621..0000000 --- a/project2/proj2_s4498062/dns/message.py +++ /dev/null @@ -1,325 +0,0 @@ -""" DNS messages - -This module contains classes for DNS messages, their header section and -question fields. See section 4 of RFC 1035 for more info. -""" - -import struct - -from dns.domainname import Parser, Composer -from dns.resource import ResourceRecord -from dns.types import Type - - -class Message(object): - """ DNS message """ - - def __init__( - self, header, questions=None, answers=None, authorities=None, - additionals=None): - """ Create a new DNS message - - Args: - header (Header): the header section - questions ([Question]): the question section - answers ([ResourceRecord]): the answer section - authorities ([ResourceRecord]): the authority section - additionals ([ResourceRecord]): the additional section - """ - self.header = header - self.questions = questions if questions != None else [] - self.answers = answers if answers != None else [] - self.authorities = authorities if authorities != None else [] - self.additionals = additionals if additionals != None else [] - - @property - def resources(self): - """ Getter for all resource records """ - return self.answers + self.authorities + self.additionals - - def to_bytes(self): - """ Convert Message to bytes """ - composer = Composer() - - # Add header - result = self.header.to_bytes() - - # Add questions - for question in self.questions: - offset = len(result) - result += question.to_bytes(offset, composer) - - # Add answers - for answer in self.answers: - offset = len(result) - result += answer.to_bytes(offset, composer) - - # Add authorities - for authority in self.authorities: - offset = len(result) - result += authority.to_bytes(offset, composer) - - # Add additionals - for additional in self.additionals: - offset = len(result) - result += additional.to_bytes(offset, composer) - - return result - - @classmethod - def from_bytes(cls, packet): - """ Create Message from bytes - - Args: - packet (bytes): byte representation of the message - """ - parser = Parser() - - # Parse header - header, offset = Header.from_bytes(packet), 12 - - # Parse questions - questions = [] - for _ in range(header.qd_count): - question, offset = Question.from_bytes(packet, offset, parser) - questions.append(question) - - # Parse answers - answers = [] - for _ in range(header.an_count): - answer, offset = ResourceRecord.from_bytes(packet, offset, parser) - answers.append(answer) - - # Parse authorities - authorities = [] - for _ in range(header.ns_count): - auth, offset = ResourceRecord.from_bytes(packet, offset, parser) - authorities.append(auth) - - # Parse additionals - additionals = [] - for _ in range(header.ar_count): - add, offset = ResourceRecord.from_bytes(packet, offset, parser) - additionals.append(add) - - return cls(header, questions, answers, authorities, additionals) - - def get_addresses(self): - """Get the addresses from a response""" - return self.get_data('answers', Type.A) - - def get_hints(self): - """Get the nameservers from a response""" - hints = self.get_data('authorities', Type.NS) - results = [] - for hint in hints: - addits = [ - a.rdata.data for a in self.additionals if - a.name == hint and a.type_ == Type.A] - if addits == []: - continue - results.append((hint, addits)) - return results - - def get_aliases(self): - """Get the aliases from a response""" - return self.get_data('additionals', Type.CNAME) - - def get_data(self, key, type_=None): - """Yield from a message section, possibly filtering by type""" - for k in getattr(self, key): - if type_ == None or k.type_ == type_: - yield k.rdata.data - - -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): - """The flags of the header""" - return self._flags - - @flags.setter - def flags(self, value): - """Set the flags of the header""" - if value >= (1 << 16): - raise ValueError("value too big for flags") - self._flags = value - - @property - def qr(self): - """The QR flag""" - return self._flags & (1 << 15) - - @qr.setter - def qr(self, value): - """Set the QR flag""" - if value: - self._flags |= (1 << 15) - else: - self._flags &= ~(1 << 15) - - @property - def opcode(self): - """The opcode of the header""" - return (self._flags & (((1 << 4) - 1) << 11)) >> 11 - - @opcode.setter - def opcode(self, value): - """Set the opcode""" - if value > 0b1111: - raise ValueError("invalid opcode") - self._flags &= ~(((1 << 4) - 1) << 11) - self._flags |= value << 11 - - @property - def aa(self): - """The AA flag""" - return self._flags & (1 << 10) - - @aa.setter - def aa(self, value): - """Set the AA flag""" - if value: - self._flags |= (1 << 10) - else: - self._flags &= ~(1 << 10) - - @property - def tc(self): - """The TC flag""" - return self._flags & (1 << 9) - - @tc.setter - def tc(self, value): - """Set the TC flag""" - if value: - self._flags |= (1 << 9) - else: - self._flags &= ~(1 << 9) - - @property - def rd(self): - """The RD flag""" - return self._flags & (1 << 8) - - @rd.setter - def rd(self, value): - """Set the RD flag""" - if value: - self._flags |= (1 << 8) - else: - self._flags &= ~(1 << 8) - - @property - def ra(self): - """The RA flag""" - return self._flags & (1 << 7) - - @ra.setter - def ra(self, value): - """Set the RA flag""" - if value: - self._flags |= (1 << 7) - else: - self._flags &= ~(1 << 7) - - @property - def z(self): - """The Z flag""" - return self._flags & (((1 << 3) - 1) << 4) >> 4 - - @z.setter - def z(self, value): - """Set the Z flag""" - if value: - raise ValueError("non-zero zero flag") - - @property - def rcode(self): - """The return code""" - return self._flags & ((1 << 4) - 1) - - @rcode.setter - def rcode(self, value): - """Set the return code""" - 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 deleted file mode 100644 index 8f63b90..0000000 --- a/project2/proj2_s4498062/dns/rcodes.py +++ /dev/null @@ -1,70 +0,0 @@ -""" DNS RCODE values - -This module contains an Enum of RCODE values. See section 4.1.4 of RFC 1035 for -more info. -""" - - -class RCode(object): - """ Enum of RCODE values - - Usage: - >>> NoError - 0 - >>> NXDomain - 3 - """ - - NoError = 0 - FormErr = 1 - ServFail = 2 - NXDomain = 3 - NotImp = 4 - Refused = 5 - YXDomain = 6 - YXRRSet = 7 - NXRRSet = 8 - NotAuth = 9 - NotZone = 10 - BADVERS = 16 - BADSIG = 16 - BADKEY = 17 - BADTIME = 18 - BADMODE = 19 - BADNAME = 20 - BADALG = 21 - BADTRUNC = 22 - - by_string = { - "NoError": NoError, - "FormErr": FormErr, - "ServFail": ServFail, - "NXDomain": NXDomain, - "NotImp": NotImp, - "Refused": Refused, - "YXDomain": YXDomain, - "YXRRSet": YXRRSet, - "NXRRSet": NXRRSet, - "NotAuth": NotAuth, - "NotZone": NotZone, - "BADVERS": BADVERS, - "BADSIG": BADSIG, - "BADKEY": BADKEY, - "BADTIME": BADTIME, - "BADMODE": BADMODE, - "BADNAME": BADNAME, - "BADALG": BADALG, - "BADTRUNC": BADTRUNC - } - - by_value = dict([(y, x) for x, y in by_string.items()]) - - @staticmethod - def to_string(rcode): - """Convert a return code to a string""" - return RCode.by_value[rcode] - - @staticmethod - def from_string(string): - """Convert a string to a return code""" - return RCode.by_string[string] diff --git a/project2/proj2_s4498062/dns/regexes.py b/project2/proj2_s4498062/dns/regexes.py deleted file mode 100644 index 07c1eed..0000000 --- a/project2/proj2_s4498062/dns/regexes.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Regexes used in the DNS protocol""" - - -def grpm(regex): - """Make a matching group""" - return grp(regex, matching=True) - - -def grp(regex, matching=False): - """Make a group""" - return r'(' + (r'' if matching else r'?:') + regex + r')' - - -def opt(regex): - """Make an optional group""" - return grp(grp(regex) + r'?') - - -def regex_opt_r(*regexes): - """Make a group that matches one of the given regexes""" - return grp(r'|'.join(regexes)) - - -DIGIT = r'\d' -LETTER = r'[a-zA-Z]' -LETDIG = grp(regex_opt_r(DIGIT, LETTER)) -LETDIGHYP = grp(regex_opt_r(LETDIG, r'-')) -LDHSTR = grp(LETDIGHYP + r'+') -LABEL = grp(LETTER + opt(opt(LDHSTR) + LETDIG)) -SUBDOMAIN = grp(grpm(grp(LABEL + r'\.') + r'*') + grpm(LABEL)) -DOMAIN = regex_opt_r(SUBDOMAIN, r' ') - -IP = r'(?:(?:\d{1,3}\.){3}\d{1,3})' diff --git a/project2/proj2_s4498062/dns/resolver.py b/project2/proj2_s4498062/dns/resolver.py deleted file mode 100644 index af60686..0000000 --- a/project2/proj2_s4498062/dns/resolver.py +++ /dev/null @@ -1,153 +0,0 @@ -""" 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 re -import socket - -from dns.cache import RecordCache -from dns.classes import Class -from dns.message import Message, Header, Question -from dns.types import Type -import dns.regexes as rgx - - -class Resolver(object): - """ DNS resolver """ - - ROOT_SERVERS = [ - '198.41.0.4', - '192.228.79.201', - '192.33.4.12', - '199.7.91.13', - '192.203.230.10', - '192.5.5.241', - '192.112.36.4', - '198.97.190.53', - '192.36.148.17', - '192.58.128.30', - '193.0.14.129', - '199.7.83.42', - '202.12.27.33' - ] - - def __init__(self, nameservers, timeout, caching, ttl): - """ Initialize the resolver - - Args: - caching (bool): caching is enabled if True - ttl (int): ttl of cache entries (if > 0) - """ - self.nameservers = nameservers + self.ROOT_SERVERS - self.timeout = timeout - self.caching = caching - self.ttl = ttl - - if self.caching: - self.cache = RecordCache() - - def do_query(self, query, using): - """Send a query to a list of name servers""" - for hint in using: - if re.match(rgx.IP, hint) is None: - continue - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(self.timeout) - try: - sock.sendto(query.to_bytes(), (hint, 53)) - data = sock.recv(512) - response = Message.from_bytes(data) - - if self.caching: - for record in response.answers + \ - response.authorities + \ - response.additionals: - self.cache.add_record(record) - - yield response - except socket.timeout: - pass - - def try_hint(self, sub, dom, hints): - """Helper for get_hints""" - if sub == '': - for hint in hints: - yield hint - else: - for new_hint in self.get_hints( - sub, dom, hints, in_recursion=True): - yield new_hint - - def get_hints(self, domain, parent='', using=None, in_recursion=False): - """Get a list of nameservers for a domain""" - if using is None: - using = [] - - if not in_recursion: - using += self.nameservers - - print 'Trying', domain, parent, 'using', using - - domains = re.match(rgx.DOMAIN, domain) - if domains is None: - return - sub, dom = domains.groups() - if parent != '': - dom += '.' + parent - - if self.caching: - hints = self.cache.lookup(dom, Type.NS, Class.IN) - for hint in self.try_hint( - sub, dom, [r.rdata.data for r in hints]): - yield hint - - header = Header(0, 0, 1, 0, 0, 0) - header.qr = 0 - header.opcode = 0 - header.rd = 0 - query = Message(header, [Question(dom, Type.NS, Class.IN)]) - - for response in self.do_query(query, using): - hints = [ip for _, [ip] in response.get_hints()] - for hint in self.try_hint(sub, dom, hints): - yield hint - - def gethostbyname(self, hostname): - """ Translate a host name to IPv4 address. - - 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(self.timeout) - - if self.caching: - addrs = list(self.cache.lookup(hostname, Type.A, Class.IN)) - cnames = list(self.cache.lookup(hostname, Type.CNAME, Class.IN)) - if addrs != []: - return ( - hostname, - [r.rdata.data for r in cnames], - [r.rdata.data for r in addrs]) - - # Create and send query - question = Question(hostname, Type.A, Class.IN) - header = Header(9001, 0, 1, 0, 0, 0) - header.qr = 0 - header.opcode = 0 - header.rd = 1 - query = Message(header, [question]) - - for response in self.do_query(query, self.get_hints(hostname)): - aliases = response.get_aliases() - addresses = response.get_addresses() - - return hostname, aliases, addresses - - return hostname, [], [] diff --git a/project2/proj2_s4498062/dns/resource.py b/project2/proj2_s4498062/dns/resource.py deleted file mode 100644 index d54cdba..0000000 --- a/project2/proj2_s4498062/dns/resource.py +++ /dev/null @@ -1,241 +0,0 @@ -""" 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 -import time - -from dns.types import Type - - -class ResourceRecord(object): - """ DNS resource record """ - def __init__(self, name, type_, class_, ttl, rdata, timestamp=time.time()): - """ 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 - self.timestamp = timestamp - - def to_bytes(self, offset, composer): - """ Convert ResourceRecord to bytes """ - record = composer.to_bytes(offset, [self.name]) - record += struct.pack("!HHI", self.type_, self.class_, self.ttl) - offset += len(record) + 2 - rdata = self.rdata.to_bytes(offset, composer) - record += struct.pack("!H", len(rdata)) + rdata - return record - - @classmethod - def from_bytes(cls, packet, offset, parser): - """ Convert ResourceRecord from bytes """ - names, offset = parser.from_bytes(packet, offset, 1) - name = names[0] - type_, class_, ttl, rdlen = struct.unpack_from("!HHIH", packet, offset) - offset += 10 - rdata = RecordData.from_bytes(type_, packet, offset, rdlen, parser) - offset += rdlen - 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): - """Data of an A record""" - - 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): - """Data of a CNAME record""" - - 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): - """Data of an NS record""" - - 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): - """Data of an AAAA record""" - - def to_bytes(self, offset, composer): - """ Convert to bytes - - Args: - offset (int): offset in message - composer (Composer): domain name composer - """ - return socket.inet_pton(socket.AF_INET6, self.data) - - @classmethod - def from_bytes(cls, packet, offset, rdlength, parser): - """ Create a RecordData object from bytes - - Args: - packet (bytes): packet - offset (int): offset in message - rdlength (int): length of rdata - parser (int): domain name parser - """ - data = socket.inet_ntop(socket.AF_INET6, packet[offset:offset+16]) - return cls(data) - - -class GenericRecordData(RecordData): - """Data of a generic record""" - - def to_bytes(self, offset, composer): - """ Convert to bytes - - Args: - offset (int): offset in message - composer (Composer): domain name composer - """ - return self.data - - @classmethod - def from_bytes(cls, packet, offset, rdlength, parser): - """ Create a RecordData object from bytes - - Args: - packet (bytes): packet - offset (int): offset in message - rdlength (int): length of rdata - parser (int): domain name parser - """ - data = packet[offset:offset+rdlength] - return cls(data) diff --git a/project2/proj2_s4498062/dns/server.py b/project2/proj2_s4498062/dns/server.py deleted file mode 100644 index f412311..0000000 --- a/project2/proj2_s4498062/dns/server.py +++ /dev/null @@ -1,51 +0,0 @@ -""" A recursive DNS server - -This module provides a recursive DNS server. You will have to implement this -server using the algorithm described in section 4.3.2 of RFC 1034. -""" - -from threading import Thread - - -class RequestHandler(Thread): - """ A handler for requests to the DNS server """ - - def __init__(self): - """ Initialize the handler thread """ - super(RequestHandler).__init__() - self.daemon = True - - def run(self): - """ Run the handler thread """ - # TODO: Handle DNS request - pass - - -class Server(object): - """ A recursive DNS server """ - - def __init__(self, port, caching, ttl): - """ Initialize the server - - Args: - port (int): port that server is listening on - caching (bool): server uses resolver with caching if true - ttl (int): ttl for records (if > 0) of cache - """ - self.caching = caching - self.ttl = ttl - self.port = port - self.done = False - # TODO: create socket - - def serve(self): - """ Start serving request """ - # TODO: start listening - while not self.done: - # TODO: receive request and open handler - pass - - def shutdown(self): - """ Shutdown the server """ - self.done = True - # TODO: shutdown socket diff --git a/project2/proj2_s4498062/dns/types.py b/project2/proj2_s4498062/dns/types.py deleted file mode 100644 index 3c2fb29..0000000 --- a/project2/proj2_s4498062/dns/types.py +++ /dev/null @@ -1,55 +0,0 @@ -""" DNS TYPE and QTYPE values - -This module contains an Enum for TYPE and QTYPE values. This Enum also contains -a method for converting Enum values to strings. See sections 3.2.2 and 3.2.3 of -RFC 1035 for more information. -""" - -class Type(object): - """ DNS TYPE and QTYPE - - Usage: - >>> Type.A - 1 - >>> Type.CNAME - 5 - """ - A = 1 - NS = 2 - CNAME = 5 - SOA = 6 - WKS = 11 - PTR = 12 - HINFO = 13 - MINFO = 14 - MX = 15 - TXT = 16 - AAAA = 28 - ANY = 255 - - by_string = { - "A": A, - "NS": NS, - "CNAME": CNAME, - "SOA": SOA, - "WKS": WKS, - "PTR": PTR, - "HINFO": HINFO, - "MINFO": MINFO, - "MX": MX, - "TXT": TXT, - "AAAA": AAAA, - "*": ANY - } - - by_value = dict([(y, x) for x, y in by_string.items()]) - - @staticmethod - def to_string(type_): - """Convert a type to a string""" - return Type.by_value[type_] - - @staticmethod - def from_string(string): - """Convert a string to a type""" - return Type.by_string[string] diff --git a/project2/proj2_s4498062/dns/zone.py b/project2/proj2_s4498062/dns/zone.py deleted file mode 100644 index accea99..0000000 --- a/project2/proj2_s4498062/dns/zone.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/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 deleted file mode 100755 index ecc3f3d..0000000 --- a/project2/proj2_s4498062/dns_client.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/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 list(aliases) - print list(addresses) - -if __name__ == "__main__": - main() diff --git a/project2/proj2_s4498062/dns_server.py b/project2/proj2_s4498062/dns_server.py deleted file mode 100755 index a518546..0000000 --- a/project2/proj2_s4498062/dns_server.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/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 deleted file mode 100755 index 71e5660..0000000 --- a/project2/proj2_s4498062/dns_tests.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/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() - |