summaryrefslogtreecommitdiff
path: root/project2/proj2_s4498062/dns
diff options
context:
space:
mode:
Diffstat (limited to 'project2/proj2_s4498062/dns')
-rw-r--r--project2/proj2_s4498062/dns/message.py40
-rw-r--r--project2/proj2_s4498062/dns/regexes.py29
-rw-r--r--project2/proj2_s4498062/dns/resolver.py94
-rw-r--r--project2/proj2_s4498062/dns/resource.py4
4 files changed, 140 insertions, 27 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: