summaryrefslogtreecommitdiff
path: root/project2/proj2_s4498062
diff options
context:
space:
mode:
Diffstat (limited to 'project2/proj2_s4498062')
-rw-r--r--project2/proj2_s4498062/dns/resolver.py140
-rw-r--r--project2/proj2_s4498062/dns/resource.py10
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 """