diff options
-rw-r--r-- | project2/proj2_s4498062/dns/resolver.py | 140 | ||||
-rw-r--r-- | project2/proj2_s4498062/dns/resource.py | 10 |
2 files changed, 123 insertions, 27 deletions
diff --git a/project2/proj2_s4498062/dns/resolver.py b/project2/proj2_s4498062/dns/resolver.py index 29451bf..ffb7c16 100644 --- a/project2/proj2_s4498062/dns/resolver.py +++ b/project2/proj2_s4498062/dns/resolver.py @@ -7,20 +7,37 @@ 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. """ +from random import randint import socket from dns.classes import Class from dns.types import Type import dns.cache -import dns.message +from dns.message import Message, Question, Header import dns.rcodes class Resolver(object): """ DNS resolver """ - def __init__(self, caching, ttl): + 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, caching, ttl, timeout=3): """ Initialize the resolver Args: @@ -29,6 +46,38 @@ class Resolver(object): """ self.caching = caching self.ttl = ttl + self.timeout = timeout + + def do_query(self, hint, hostname, type_, class_=Class.IN): + """Do a query to a hint""" + ident = randint(0, 65535) + header = Header(ident, 0, 1, 0, 0, 0) + header.qr = 0 + header.opcode = 0 + header.rd = 1 + req = Message(header, [Question(hostname, type_, class_)]) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(self.timeout) + sock.sendto(req.to_bytes(), (hint, 53)) + + try: + data = sock.recv(512) + resp = Message.from_bytes(data) + if resp.header.ident == ident: + return resp + except socket.timeout: + pass + return None + + def do_query_to_multiple(self, hints, hostname, type_, class_=Class.IN): + """Do a query to multiple hints, return the remaining hints""" + while hints != []: + hint = hints.pop() + response = self.do_query(hint, hostname, type_, class_) + if response is not None: + return hints, response + return [], None def gethostbyname(self, hostname): """ Translate a host name to IPv4 address. @@ -43,31 +92,68 @@ class Resolver(object): Returns: (str, [str], [str]): (hostname, aliaslist, ipaddrlist) """ - timeout = 2 - 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)) + domains = hostname.split('.') + hints = self.ROOT_SERVERS - # Receive response - data = sock.recv(512) - response = dns.message.Message.from_bytes(data) + if domains == []: + return hostname, [], [] - # Get data + domain = domains.pop(-1) 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 + while hints != []: + hints, resp = self.do_query_to_multiple(hints, domain, Type.A) + if resp == None: + continue + + info = resp.answers + resp.authorities + resp.additionals + + aliases += [ + r.rdata.data for r in info + if r.match(type_=Type.CNAME, class_=Class.IN, name=domain)] + + # Case 1: answer + ips = [ + r.rdata.data for r in info + if r.match(type_=Type.A, class_=Class.IN, name=domain)] + if ips != []: + return hostname, aliases, ips + # Case 2: name servers + auths = [ + r.rdata.data for r in info + if r.match(type_=Type.NS, class_=Class.IN, name=domain)] + ips = [ + add.rdata.data for ns in auths for add in info + if add.match(name=ns, type_=Type.A)] + if ips != []: + hints += ips + if domain != hostname: + domain = domains.pop(-1) + '.' + domain + continue + if auths != []: + auths = [h for a in auths for h in self.gethostbyname(a)[2]] + hints += auths + if domain != hostname: + domain = domains.pop(-1) + '.' + domain + continue + # Case 3: delegation to other name servers + parent = '.'.join(domain.split('.')[1:]) + refs = [ + r.rdata.data for r in info + if r.match(type_=Type.NS, class_=Class.IN, name=parent)] + ips = [ + add.rdata.data for ns in refs for add in info + if add.match(name=ns, type_=Type.A)] + if ips != []: + hints += ips + continue + if refs != []: + refs = [h for r in refs for h in self.gethostbyname(r)[2]] + hints += refs + continue + # Case 4: aliases + for alias in aliases: + _, extra_aliases, alias_addresses = self.gethostbyname(alias) + if alias_addresses != []: + return hostname, aliases + extra_aliases, alias_addresses + + return hostname, aliases, [] diff --git a/project2/proj2_s4498062/dns/resource.py b/project2/proj2_s4498062/dns/resource.py index 19a09bb..89201ec 100644 --- a/project2/proj2_s4498062/dns/resource.py +++ b/project2/proj2_s4498062/dns/resource.py @@ -30,6 +30,13 @@ class ResourceRecord(object): self.ttl = ttl self.rdata = rdata + def match(self, name=None, type_=None, class_=None, ttl=None): + """Check if the record matches properties""" + return (name is None or self.name == name) and \ + (type_ is None or self.type_ == type_) and \ + (class_ is None or self.class_ == class_) and \ + (ttl is None or self.ttl == ttl) + def to_bytes(self, offset, composer): """ Convert ResourceRecord to bytes """ record = composer.to_bytes(offset, [self.name]) @@ -51,6 +58,9 @@ class ResourceRecord(object): offset += rdlength return cls(name, type_, class_, ttl, rdata), offset + def __repr__(self): + return ' '.join(map(str, [self.name, self.type_, self.rdata.data])) + class RecordData(object): """ Record Data """ |