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

master
Stephane Bortzmeyer 10 years ago
parent 0b74fa93c7
commit 93d5fffbb9
  1. 16
      DNSLG/Answer.py
  2. 23
      DNSLG/Formatter.py
  3. 85
      DNSLG/Resolver.py
  4. 36
      DNSLG/__init__.py
  5. 16
      test-my-resolver.py

@ -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

@ -52,9 +52,6 @@ class TextFormatter(Formatter):
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 @@ class TextFormatter(Formatter):
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 @@ class ZoneFormatter(Formatter):
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 @@ class JsonFormatter(Formatter):
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 @@ class JsonFormatter(Formatter):
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 @@ class XmlFormatter(Formatter):
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 @@ class XmlFormatter(Formatter):
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 @@ class HtmlFormatter(Formatter):
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()

@ -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

@ -18,6 +18,7 @@ import netaddr
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 @@ class Querier:
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 @@ class Querier:
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 @@ Disallow: /
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 @@ Disallow: /
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:

@ -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 ""
Loading…
Cancel
Save