diff options
-rw-r--r-- | project2/proj2_s4498062/dns/message.py | 40 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/regexes.py | 29 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resolver.py | 94 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resource.py | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | project2/proj2_s4498062/dns_client.py | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | project2/proj2_s4498062/dns_server.py | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | project2/proj2_s4498062/dns_tests.py | 0 |
7 files changed, 142 insertions, 29 deletions
diff --git a/project2/proj2_s4498062/dns/message.py b/project2/proj2_s4498062/dns/message.py index 9f8ead4..a8ad621 100644 --- a/project2/proj2_s4498062/dns/message.py +++ b/project2/proj2_s4498062/dns/message.py @@ -8,14 +8,15 @@ 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=[], answers=[], authorities=[], - additionals=[]): + self, header, questions=None, answers=None, authorities=None, + additionals=None): """ Create a new DNS message Args: @@ -26,10 +27,10 @@ class Message(object): additionals ([ResourceRecord]): the additional section """ self.header = header - self.questions = questions - self.answers = answers - self.authorities = authorities - self.additionals = additionals + 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): @@ -103,6 +104,33 @@ class Message(object): 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 diff --git a/project2/proj2_s4498062/dns/regexes.py b/project2/proj2_s4498062/dns/regexes.py new file mode 100644 index 0000000..48c28c9 --- /dev/null +++ b/project2/proj2_s4498062/dns/regexes.py @@ -0,0 +1,29 @@ +"""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 index 8d12c1d..a046d5d 100644 --- a/project2/proj2_s4498062/dns/resolver.py +++ b/project2/proj2_s4498062/dns/resolver.py @@ -5,15 +5,33 @@ 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.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 @@ -21,11 +39,61 @@ class Resolver(object): caching (bool): caching is enabled if True ttl (int): ttl of cache entries (if > 0) """ - self.nameservers = nameservers + self.nameservers = nameservers + self.ROOT_SERVERS self.timeout = timeout self.caching = caching self.ttl = ttl + 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) == 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) + yield response + except socket.timeout: + pass + + 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 + + domains = re.match(rgx.DOMAIN, domain) + if domains == None: + return None + sub, dom = domains.groups() + if parent != '': + dom += '.' + parent + + 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): + new_hints = [ip for _, [ip] in list(response.get_hints())] + + if new_hints != []: + if sub is '': + return new_hints + using + + result = self.get_hints( + sub, dom, new_hints + using, in_recursion=True) + if result != None: + return result + + return [] + def gethostbyname(self, hostname): """ Translate a host name to IPv4 address. @@ -49,21 +117,9 @@ class Resolver(object): header.opcode = 0 header.rd = 1 query = Message(header, [question]) - #TODO should come from self.nameservers - sock.sendto(query.to_bytes(), ("8.8.8.8", 53)) - - # Receive response - data = sock.recv(512) - response = 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 + + for response in self.do_query(query, self.get_hints(hostname)): + aliases = response.get_aliases() + addresses = response.get_addresses() + + return hostname, aliases, addresses diff --git a/project2/proj2_s4498062/dns/resource.py b/project2/proj2_s4498062/dns/resource.py index 572397f..71f09be 100644 --- a/project2/proj2_s4498062/dns/resource.py +++ b/project2/proj2_s4498062/dns/resource.py @@ -203,7 +203,7 @@ class AAAARecordData(RecordData): return socket.inet_pton(socket.AF_INET6, self.data) @classmethod - def from_bytes(packet, offset, rdlength, parser): + def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: @@ -229,7 +229,7 @@ class GenericRecordData(RecordData): return self.data @classmethod - def from_bytes(packet, offset, rdlength, parser): + def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: diff --git a/project2/proj2_s4498062/dns_client.py b/project2/proj2_s4498062/dns_client.py index d89c1e4..b78c7ee 100644..100755 --- a/project2/proj2_s4498062/dns_client.py +++ b/project2/proj2_s4498062/dns_client.py @@ -26,8 +26,8 @@ def main(): # Print output print hostname - print aliases - print addresses + 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 index 0fcf981..0fcf981 100644..100755 --- a/project2/proj2_s4498062/dns_server.py +++ b/project2/proj2_s4498062/dns_server.py diff --git a/project2/proj2_s4498062/dns_tests.py b/project2/proj2_s4498062/dns_tests.py index b62180b..b62180b 100644..100755 --- a/project2/proj2_s4498062/dns_tests.py +++ b/project2/proj2_s4498062/dns_tests.py |