Permalink
Browse files

First port of DNS-LG to the Low-Level Interface. Very limited at this…

… time
  • Loading branch information...
1 parent 0b74fa9 commit 93d5fffbb986fdee1f1599fd299384f0e300d614 Stephane Bortzmeyer committed Sep 16, 2012
Showing with 128 additions and 48 deletions.
  1. +10 −6 DNSLG/Answer.py
  2. +8 −15 DNSLG/Formatter.py
  3. +85 −0 DNSLG/Resolver.py
  4. +9 −27 DNSLG/__init__.py
  5. +16 −0 test-my-resolver.py
View
@@ -1,8 +1,12 @@
import dns.resolver
+import dns.message
-class ExtendedAnswer(dns.resolver.Answer):
- def __init__(self, initial_answer):
- self.qname = initial_answer.qname
- self.rrsets = [initial_answer.rrset,]
- self.owner_name = initial_answer.rrset.name
-
+class ExtendedAnswer(dns.message.Message):
+
+ def __init__(self, initial_msg):
+ #for field in initial_msg.__attr__:
+ # self.field = initial_msg.field
+ super(ExtendedAnswer, self).__init__()
+ self.nameserver = None
+ self.qname = None
+
View
@@ -52,9 +52,6 @@ def format(self, answer, qtype, flags, querier):
self.output = ""
self.output += "Query for: %s, type %s\n" % (self.domain.encode(querier.encoding),
qtype)
- if answer is not None and (str(answer.owner_name) != self.domain):
- self.output += "Result name: %s\n" % \
- str(answer.owner_name).encode(querier.encoding)
str_flags = ""
if flags & dns.flags.AD:
str_flags += "/ Authentic Data "
@@ -66,7 +63,7 @@ def format(self, answer, qtype, flags, querier):
if answer is None:
self.output += "[No data for this query type]\n"
else:
- for rrset in answer.rrsets:
+ for rrset in answer.answer:
for rdata in rrset:
if rdata.rdtype == dns.rdatatype.A or rdata.rdtype == dns.rdatatype.AAAA:
self.output += "IP address: %s\n" % rdata.address
@@ -158,10 +155,10 @@ def format(self, answer, qtype, flags, querier):
if answer is None:
self.output += "; No data for this type\n"
else:
- for rrset in answer.rrsets:
+ for rrset in answer.answer:
for rdata in rrset:
# TODO: do not hardwire the class
- self.output += "%s\tIN\t" % answer.owner_name # TODO: do not repeat the name if there is a RRset
+ self.output += "%s\tIN\t" % answer.name # TODO: do not repeat the name if there is a RRset
# TODO: it could use some refactoring: most (but _not all_) of types
# use the same code.
if rdata.rdtype == dns.rdatatype.A:
@@ -238,8 +235,8 @@ def format(self, answer, qtype, flags, querier):
if flags & dns.flags.TC:
self.object['TC'] = True
self.object['AnswerSection'] = []
- if answer is not None:
- for rrset in answer.rrsets:
+ if answer.answer is not None:
+ for rrset in answer.answer:
for rdata in rrset: # TODO: sort them? For instance by preference for MX?
if rdata.rdtype == dns.rdatatype.A:
self.object['AnswerSection'].append({'Type': 'A', 'Address': rdata.address})
@@ -313,7 +310,7 @@ def format(self, answer, qtype, flags, querier):
else:
self.object['AnswerSection'].append({'Type': "unknown"}) # TODO: the type number
self.object['AnswerSection'][-1]['TTL'] = rrset.ttl
- self.object['AnswerSection'][-1]['Name'] = str(answer.owner_name)
+ self.object['AnswerSection'][-1]['Name'] = str(rrset.name)
try:
duration = querier.delay.total_seconds()
except AttributeError: # total_seconds appeared only with Python 2.7
@@ -451,7 +448,6 @@ def format(self, answer, qtype, flags, querier):
addresses = []
if answer is not None:
self.rrsets = []
- self.acontext.addGlobal ("ownername", answer.owner_name)
if flags & dns.flags.AD:
ad = 1
else:
@@ -468,7 +464,7 @@ def format(self, answer, qtype, flags, querier):
aa = 0
self.context.addGlobal ("aa", aa)
# TODO: class
- for rrset in answer.rrsets:
+ for rrset in answer.answer:
records = []
self.acontext.addGlobal ("ttl", rrset.ttl)
self.acontext.addGlobal ("type", dns.rdatatype.to_text(rrset.rdtype))
@@ -828,11 +824,8 @@ def format(self, answer, qtype, flags, querier):
self.context.addGlobal ("flags", str_flags)
if answer is not None:
self.rrsets = []
- if str(answer.owner_name).lower() != self.domain.lower():
- self.context.addGlobal ("distinctowner", True)
- self.context.addGlobal ("ownername", answer.owner_name)
icontext = simpleTALES.Context(allowPythonPath=False)
- for rrset in answer.rrsets:
+ for rrset in answer.answer:
records = []
for rdata in rrset:
iresult = simpleTALUtils.FastStringOutput()
View
@@ -0,0 +1,85 @@
+import copy
+
+import dns.message
+import dns.resolver
+import Answer
+
+class Timeout(Exception):
+ pass
+
+class NoSuchDomainName(Exception):
+ pass
+
+class UnknownError(Exception):
+ pass
+
+class Resolver():
+
+ def __init__(self, nameservers=None, maximum=3, timeout=0.5,
+ edns_version=0, edns_payload=4096):
+ # TODO CRIT: DNSSEC
+ # TODO: ednsflags such as NSID
+ """ A "None" value for the parameter nameservers means to use
+ the system's default resolver(s). Otherwise, this parameter
+ is an *array* of the IP addresses of the resolvers.
+ edns_version=0 means EDNS0, the original one. Use -1 for no EDNS """
+ self.maximum = maximum
+ self.timeout = timeout
+ self.original_edns = edns_version
+ self.original_payload = edns_payload
+ if nameservers is None:
+ self.original_nameservers = dns.resolver.get_default_resolver().nameservers
+ else:
+ # TODO: test it is an iterable? And of length > 0?
+ self.original_nameservers = nameservers
+ self.edns = self.original_edns
+ self.payload = self.original_payload
+ self.nameservers = self.original_nameservers
+
+ def query(self, name, type, tcp=False):
+ # TODO CRIT : TCP
+ """ The returned value is a DNSLG.Answer """
+ for ns in self.nameservers:
+ try:
+ message = dns.message.make_query(name, type,
+ use_edns=self.edns, payload=self.payload,
+ want_dnssec=True)
+ except TypeError: # Old DNS Python... Code here just as long as it lingers in some places
+ message = dns.message.make_query(name, type, use_edns=0,
+ want_dnssec=True)
+ message.payload = 4096
+ done = False
+ tests = 0
+ while not done and tests < self.maximum:
+ try:
+ msg = dns.query.udp(message, ns, timeout=self.timeout)
+ if msg.rcode() == dns.rcode.NOERROR:
+ done = True
+ elif msg.rcode() == dns.rcode.NXDOMAIN:
+ raise NoSuchDomainName()
+ # TODO CRIT: if REFUSED or SERVFAIL, tries the next resolver?
+ else:
+ raise UnknownError(msg.rcode)
+ except dns.exception.Timeout:
+ tests += 1
+ if done:
+ response = copy.copy(msg)
+ response.__class__ = Answer.ExtendedAnswer
+ response.nameserver = ns
+ response.qname = name
+ return response
+ # If we are still here, it means no name server answers
+ raise Timeout()
+
+ def set_edns(self, version=0, payload=4096, dnssec=False):
+ """ version=0 means EDNS0, the original one. Use -1 for no EDNS """
+ self.edns = version
+ self.payload = None
+
+ def set_nameservers(self, nameservers):
+ self.nameservers = nameservers
+
+ def reset(self):
+ self.edns = self.original_edns
+ self.payload = self.original_payload
+ self.nameservers = self.original_nameservers
View
@@ -18,6 +18,7 @@
import Formatter
from LeakyBucket import LeakyBucket
import Answer
+import Resolver
# If you need to change thse values, it is better to do it when
# calling the Querier() constructor.
@@ -53,8 +54,7 @@ def __init__(self, email_admin=None, url_doc=None, url_css=None, url_opensearch=
whitelist=default_whitelist, edns_size=default_edns_size,
handle_wk_files=default_handle_wk_files,
google_code=None, description=None, description_html=None):
- self.resolver = dns.resolver.Resolver()
- self.default_nameservers = self.resolver.nameservers
+ self.resolver = Resolver.Resolver(edns_payload=edns_size)
self.buckets = {}
self.base_url = base_url
self.whitelist = whitelist
@@ -68,27 +68,12 @@ def __init__(self, email_admin=None, url_doc=None, url_css=None, url_opensearch=
else:
self.favicon = None
self.encoding = encoding
- self.edns_size = edns_size
self.bucket_size = default_bucket_size
self.google_code = google_code
self.description = description
self.description_html = description_html
- self.reset_resolver()
+ self.resolver.reset()
- def reset_resolver(self):
- self.resolver.nameservers = self.default_nameservers[0:1] # Yes, it
- # decreases resilience but it seems it is the only way to be
- # sure of *which* name server actually replied (TODO: question
- # sent on the dnspython mailing lst on 2012-05-20). POssible
- # improvment: use the low-level interface of DNS Python and
- # handles this ourselves. See issue #3.
- # Default is to use EDNS without the DO bit
- if self.edns_size is not None:
- self.resolver.use_edns(0, 0, self.edns_size)
- else:
- self.resolver.use_edns(-1, 0, 0)
- self.resolver.search = []
-
def default(self, start_response, path):
output = """
I'm the default handler, \"%s\" was called.
@@ -223,27 +208,24 @@ def query(self, start_response, path, client, format="HTML", alt_resolver=None,
formatter = Formatter.ZoneFormatter(domain)
elif format == "XML":
formatter = Formatter.XmlFormatter(domain)
- self.reset_resolver()
+ self.resolver.reset()
if do_dnssec:
- self.resolver.use_edns(0, dns.flags.DO, edns_size)
+ self.resolver.use_edns(0, edns_size)
if alt_resolver:
- self.resolver.nameservers = [alt_resolver,]
+ self.resolver.set_nameservers([alt_resolver,])
query_start = datetime.now()
if qtype != "ADDR":
- answers = self.resolver.query(qdomain, qtype, tcp=tcp)
- answer = Answer.ExtendedAnswer(answers)
+ answer = self.resolver.query(qdomain, qtype, tcp=tcp)
else:
+ # TODO CRIT refaire completement
try:
answers = self.resolver.query(qdomain, "A", tcp=tcp)
- answer = Answer.ExtendedAnswer(answers)
except dns.resolver.NoAnswer:
answer = None
try:
answers = self.resolver.query(qdomain, "AAAA", tcp=tcp)
if answer is not None:
answer.rrsets.append(answers.rrset)
- else:
- answer = Answer.ExtendedAnswer(answers)
except dns.resolver.NoAnswer:
pass
# TODO: what if flags are different with A and AAAA? (Should not happen)
@@ -256,7 +238,7 @@ def query(self, start_response, path, client, format="HTML", alt_resolver=None,
return [output]
query_end = datetime.now()
self.delay = query_end - query_start
- formatter.format(answer, qtype, answers.response.flags, self)
+ formatter.format(answer, qtype, answer.flags, self)
output = formatter.result(self)
send_response(start_response, '200 OK', output, mtype)
except dns.resolver.NXDOMAIN:
View
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+
+import DNSLG
+import sys
+
+resolver = DNSLG.Resolver.Resolver(["127.0.0.1", "8.8.8.8"], maximum=2)
+for name in sys.argv[1:]:
+ result = resolver.query(name, "ANY")
+ print result.answer
+ print "From %s: " % result.nameserver
+ rrsets = result.answer # There is also additional, authority, etc
+ for rrset in rrsets:
+ print "%s/%s ->" % (rrset.name, rrset.rdtype)
+ for rr in rrset:
+ print "\t%s" % rr
+ print ""

0 comments on commit 93d5fff

Please sign in to comment.