#!/usr/bin/python """This provides the AsyncResolver class, which handles the interface betweeen python data structures and the (C-based) adns library.""" from __future__ import print_function import sys from time import sleep try: import adns except ImportError: #cd /opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/ ; sudo ln -sf /opt/local/include/adns.h adns.h ; cd - print("Missing adns module; perhaps 'sudo rpm -ivh adns-python-1.2.2-1.x86_64.rpm', 'sudo -H pip install https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/adns-python/adns-python-1.2.1.tar.gz' or 'sudo port install adns' ? Exiting.") raise def debug(debug_message): """Print a debugging message.""" sys.stderr.write('___ ' + debug_message + '\n') sys.stderr.flush() def fail(debug_message): """Print a debugging message and quit.""" sys.stderr.write('xxx ' + debug_message + '\n') sys.stderr.flush() sys.exit(1) #Many thanks to # Peteris Krumins (peter@catonmat.net) # http://www.catonmat.net -- good coders code, great reuse # http://www.catonmat.net/blog/asynchronous-dns-resolution #for the initial version of the async dns class below class AsyncResolver(object): """Class for AsyncResolver.""" def __init__(self, hosts_and_types, intensity=100): """ hosts: a list of hosts to resolve intensity: how many hosts to resolve at once """ self.hosts_and_types = hosts_and_types self.intensity = intensity self.adns = adns.init() def resolve(self): """ Resolves hosts and returns two dictionaries: { 'host': 'ip' } and {'host': 'txt_record'}. """ #When updating qt2obj with additional record types, make sure you include the code to handle those types below. #Note: ipv6 support is not included with the 1.2.1 release; a separate patch is needed. qt2obj = {'A': adns.rr.A, 'MX': adns.rr.MX, 'NS': adns.rr.NS, 'SOA': adns.rr.SOAraw, 'TXT': adns.rr.TXT} resolved_objects = {} #Dict(keys=dns_names, values=dict(keys=qtype, values=set(all answers))) active_queries = {} #Dict(keys=query handles, values=tuples(dns_object, query type)) host_queue = self.hosts_and_types[:] def collect_results(): """Pull any successful results out and store in resolved_objects.""" for query in self.adns.completed(): answer = query.check() (host, qtype) = active_queries[query] #debug(str(host) + ' ; ' + str(qtype) + ', status: ' + str(answer[0])) del active_queries[query] #Response codes from adns.h if answer[0] == adns.status.ok: # 0 adns.status.ok (see adns.h for these constants) #debug(str(qtype) + ' answer:: ' + str(answer)) if qtype == "A": resolved_objects[host][qtype].update(answer[3]) #Appends all addresses in the answer tuple to the resolved_objects set for that hostname and query type #elif qtype == "AAAA": # resolved_objects[host][qtype].update(answer[3]) elif qtype == "MX": for one_mx in answer[3]: resolved_objects[host][qtype].add(one_mx[1][0].lower()) #Appends all hostnames in the answer tuple to the resolved_objects set for that hostname and query type if one_mx[1][2]: for one_mx_ip_block in one_mx[1][2]: resolved_objects[host]['MX_IP'].add(one_mx_ip_block[1]) #Appends all mailserver IPs in the answer tuple to the resolved_objects set for that hostname and MX_IP type elif qtype == "NS": for one_ns in answer[3]: resolved_objects[host][qtype].add(one_ns[0].lower()) #Appends all hostnames in the answer tuple to the resolved_objects set for that hostname and query type if one_ns[2]: for one_ns_ip_block in one_ns[2]: resolved_objects[host]['NS_IP'].add(one_ns_ip_block[1]) #Appends all nameserver IPs in the answer tuple to the resolved_objects set for that hostname and NS_IP type elif qtype == "SOA": for an_answer in answer[3]: #an_answer is a tuple containing both strings and integers, so we can't just ''.join(an_answer) . an_answer_strings = [] for atom in an_answer: an_answer_strings.append(str(atom)) resolved_objects[host][qtype].add(' '.join(an_answer_strings)) elif qtype == "TXT": for an_answer in answer[3]: resolved_objects[host][qtype].add(' '.join(an_answer)) else: #debug('ZZZZ answer:: ' + str(answer)) fail('Unknown qtype in status.ok processing: ' + str(qtype) + ':: ' + str(answer)) elif answer[0] == adns.status.prohibitedcname: # 101 adns.status.prohibitedcname #debug(str(qtype) + ' answer:: ' + str(answer)) if host not in resolved_objects: resolved_objects[host] = {} if 'CNAME' not in resolved_objects[host]: resolved_objects[host]['CNAME'] = set() resolved_objects[host]['CNAME'].add(answer[1]) if qtype in qt2obj: query = self.adns.submit(answer[1], qt2obj[qtype]) active_queries[query] = (host, qtype) else: fail('Unknown qtype in status.prohibitedcname processing: ' + str(qtype) + ':: ' + str(answer)) elif answer[0] in (adns.status.rcodeservfail, adns.status.answerdomaininvalid, adns.status.querydomaininvalid, adns.status.nxdomain, adns.status.nodata): #60 rcodeservfail, 102 answerdomaininvalid, 201 querydomaininvalid, 300 nxdomain, 301 nodata if qtype in qt2obj: resolved_objects[host][qtype] = None if qtype == 'NS': resolved_objects[host]['NS_IP'] = None if qtype == 'MX': resolved_objects[host]['MX_IP'] = None else: fail('Unknown qtype in status.nxdomain/querydomaininvalid/nodata processing: ' + str(qtype) + ':: ' + str(answer)) else: if qtype in qt2obj: resolved_objects[host][qtype] = None else: debug('Unknown qtype in status.unhandledstatus processing: ' + str(qtype) + ':: ' + str(answer)) fail("Unhandled response type: " + str(answer[0])) def finished_resolving(): """Have we gotten all the records we asked for?""" #debug(str(len(host_queue)) + '/' + str(len(active_queries))) #return (len(resolved_hosts) + len(resolved_txt_records)) == len(self.hosts_and_types) return (len(host_queue) == 0 and len(active_queries) == 0) while not finished_resolving(): while host_queue and len(active_queries) < self.intensity: host_and_type = host_queue.pop() (host, qtype) = host_and_type if host not in resolved_objects: resolved_objects[host] = {} if qtype not in resolved_objects[host]: resolved_objects[host][qtype] = set() if qtype == 'MX' and 'MX_IP' not in resolved_objects[host]: resolved_objects[host]['MX_IP'] = set() if qtype == 'NS' and 'NS_IP' not in resolved_objects[host]: resolved_objects[host]['NS_IP'] = set() if resolved_objects[host][qtype] is not None: #If it were equal to None, we've already tried to look it up and failed, don't try again if qtype in qt2obj: query = self.adns.submit(host, qt2obj[qtype]) active_queries[query] = (host, qtype) else: fail("Unimplemented record type in adns/resolve: " + str(qtype)) else: debug('Skipping request for ' + str(host) + ';' + str(qtype) + ' because already equals None') sleep(0.01) collect_results() return resolved_objects