Index: /trunk/misc/dns-o-matic.py
===================================================================
--- /trunk/misc/dns-o-matic.py (revision 157)
+++ /trunk/misc/dns-o-matic.py (revision 158)
@@ -17,4 +17,5 @@
 import DNS
 from netaddr import IPNetwork, IPAddress
+from pprint import PrettyPrinter
 
 DNS.DiscoverNameServers()
@@ -34,51 +35,70 @@
 
 
-def RecursiveLookup(data,record,type,nameserver,astart=None,ptrstart=None):
-	r = None
-	new_data = {'record':record, 'results':[], 'status':None}
-
+def RecursiveLookup(data,rec_lookup,type,nameserver,astart=None,ptrstart=None):
 	# exit when a request loop is detected
 	if type == 'PTR':
 		if ptrstart == None:
-			ptrstart = record
-		elif ptrstart == record:
-			data['results'].append(new_data)
-			return data
-		else:
-			ptrstart = record
+			ptrstart = rec_lookup
+		elif ptrstart == rec_lookup:
+			return
+		else:
+			ptrstart = rec_lookup
 	else:
 		if astart == None:
-			astart = record
-		elif astart == record:
-			data['results'].append(new_data)
-			return data
-		else:
-			astart = record
-
-	# reverse lookup request gives you all the hostnames that the given ip address resolves to
+			astart = rec_lookup
+		elif astart == rec_lookup:
+			return
+		else:
+			astart = rec_lookup
+
+	r = None
 	if type == 'PTR':
-		rec = IPAddress(record)
+		# reverse lookup request gives you all the hostnames that the given ip address resolves to
+		rec = IPAddress(rec_lookup)
 		r = Request(rec.reverse_dns,type,nameserver)
-
-	# forward lookup request gives you all the ip addresses that the given hostname resolves to
-	else:
-		r = Request(record,type,nameserver)
-
-	# make note of status message and recurse if there were answers
+	else:
+		# forward lookup request gives you all the ip addresses that the given hostname resolves to
+		r = Request(rec_lookup,type,nameserver)
+
+	# we shouldn't ever time out
 	if r == None:
-		new_data['status'] = 'TIMEOUT'
-		data['results'].append(new_data)
-	else:
-		new_data['status'] = r.header['status']
-		for rec,rec_type in [(x['data'],x['type']) for x in r.answers]:
+		sys.stderr.write("ERROR: Timed out querying for %s records from %s\n" % (type, nameserver))
+		sys.exit(1)
+
+	# loop through all the answers
+	if r.header['status'] == 'NOERROR':
+		for rec_result,rec_type in [(x['data'],x['type']) for x in r.answers]:
 
 			# despite only ever asking for PTR or A records, we sometimes get CNAMEs, so discard them
 			if rec_type not in set([DNS.Type.A, DNS.Type.PTR]):
 				continue
+
+			# add to the list of answers
+			answer = {
+					  'ns'          : nameserver,
+					  'lookup_of'   : rec_lookup,
+					  'lookup_type' : type,
+					  'resolves_to' : rec_result,
+					  'status'      : r.header['status'],
+					  'subrecords'  : [],
+					  }
+			data.append(answer)
+
+			# rinse and repeat
 			if type == 'PTR':
-				RecursiveLookup(new_data,rec,'A',nameserver,astart,ptrstart)
+				RecursiveLookup(answer['subrecords'],rec_result,'A',  nameserver,astart,ptrstart)
 			else:
-				RecursiveLookup(new_data,rec,'PTR',nameserver,astart,ptrstart)
-		data['results'].append(new_data)
+				RecursiveLookup(answer['subrecords'],rec_result,'PTR',nameserver,astart,ptrstart)
+	else:
+		# add the error to the list of answers
+		answer = {
+				  'ns'          : nameserver,
+				  'lookup_of'   : rec_lookup,
+				  'lookup_type' : type,
+				  'resolves_to' : '',
+				  'status'      : r.header['status'],
+				  'subrecords'  : [],
+				  }
+		data.append(answer)
 
 
@@ -130,29 +150,34 @@
 
 def GetSubnetAnalysis(nameservers,subnet):
-	data = {'subnet':subnet, 'nameservers':[], 'addresses':[]}
-	primary = -1
-	for i,(ns,pri) in enumerate(nameservers):
+	data = {'subnet':str(subnet), 'nameservers':[], 'addresses':[]}
+
+	# make a note of which is the primary nameserver
+	primary = ''
+	for ns,pri in nameservers:
 		data['nameservers'].append({'name':ns, 'primary':pri})
 		if pri:
-			primary = i
+			primary = ns
 
 	# iterate through all valid addresses in the subnet
-	sys.stderr.write("Subnet %s...\n" % str(subnet));
+	sys.stderr.write("Processing Subnet: %s\n" % str(subnet))
 	for ip in [str(x) for x in list(subnet) if x != subnet.broadcast and x != subnet.ip]:
-		sys.stderr.write("Processing %s\n" % ip);
+		sys.stderr.write(".")
+
+		# request the crap out of the DNS servers
 		address = {'ip':ip, 'results':[]}
 		for ns,pri in nameservers:
-			RecursiveLookup(address,ip,'PTR',ns)
+			RecursiveLookup(address['results'],ip,'PTR',ns)
 
 		# forget about addresses that don't have records in *any* nameservers
 		for result in address['results']:
-			if result['status'] == 'NOERROR':
+			if result['status'] != 'NXDOMAIN':
 				data['addresses'].append(address)
 				break
+	sys.stderr.write("\n")
 
 	# iterate through all addresses for which we have results
-	for address in data['addresses']:
-		for ns_result in address['results']:
-			FindValidLoops(ns_result)
+#	for address in data['addresses']:
+#		for ns_result in address['results']:
+#			FindValidLoops(ns_result)
 	return data
 
@@ -160,25 +185,21 @@
 def RecursivePrintResults(data,level=0):
 	# display the record we're looking up
-	if data['valid']:
-		style = "class='good'"
-	else:
-		style = ""
-	if not data['status'] and len(data['results']) == 0:
-		print "    <p %s style='padding-left:%spx'><span class='record'>%s</span></p>" % (style, `level * 20`, data['record'])
-	else:
-		print "    <p %s style='padding-left:%spx'><span class='record'>%s</span> resolves to:</p>" % (style, `level * 20`, data['record'])
-
-	# recursively display lookup results or show error
-	next_level = level + 1
-	if len(data['results']) > 0:
-		for result in data['results']:
-			RecursivePrintResults(result,next_level)
-	elif data['status']:
-		print "    <p style='padding-left:%spx'><span class='record'>%s</span></p>" % (`next_level * 20`, data['status'])
+	if data['resolves_to'] != '':
+		print "    <p style='padding-left:%spx'><span class='record'>%s</span> resolves to: <span class='record'>%s</span></p>" % (`level * 20`, data['lookup_of'], data['resolves_to'])
+	else:
+		print "    <p style='padding-left:%spx'><span class='record'>%s</span> does not resolve to anything!</p>" % (`level * 20`, data['lookup_of'])
+
+	# recursively display subrecords
+	if len(data['subrecords']) > 0:
+		for rec in data['subrecords']:
+			RecursivePrintResults(rec,level + 1)
 
 
 def PrintSubnetAnalysis(data):
-	print "<h2>Analysis of the %s subnet</h2>" % data['subnet']
-	print "<table border='1'>"
+	subnet_id = "subnet-" + data['subnet'].replace('/','-').replace('.','-')
+	print "<h2 id='%s_heading'>Analysis of the %s subnet</h2>" % (subnet_id, data['subnet'])
+	print "<p><a class='debugs_on'  id='%s_on'  href='#%s_heading'>Show Debugs</a>" % (subnet_id, subnet_id) + \
+	         "<a class='debugs_off' id='%s_off' href='#%s_heading'>Hide Debugs</a></p>" % (subnet_id, subnet_id)
+	print "<table id='%s_table' border='1'>" % subnet_id
 
 	heading = "<tr><th>address</th>"
@@ -189,20 +210,20 @@
 			heading += "<th>%s</th>" % ns['name']
 	heading += "</tr>"
-			
+
 	for i,address in enumerate(data['addresses']):
 		if i % 25 == 0:
 			print heading
 		print "<tr>\n  <td><p>%s</p></td>" % address['ip']
-		for ns_result in address['results']:
+		for ns in data['nameservers']:
 			print "  <td>"
-			RecursivePrintResults(ns_result)
+			for result in [x for x in address['results'] if x['ns'] == ns['name']]:
+				RecursivePrintResults(result)
 			print "  </td>"
 		print "</tr>"
 	print "</table>"
 
-	# for debugging
-	import pprint
-	print "<pre style='display:none;'>"
-	pp = pprint.PrettyPrinter(indent=2)
+	# print the raw data structure for debugging
+	print "<pre id='%s_debugs' style='display:none;'>" % subnet_id
+	pp = PrettyPrinter(indent=2)
 	pp.pprint(data)
 	print "</pre>"
@@ -216,16 +237,23 @@
   <script type="text/javascript">
     jQuery(document).ready(function($) {
-      $("a#debugs_on").css('display', 'inline');
-      $("a#debugs_on").click(function() {
-        $("pre").css('display', 'block');
-        $("table").css('display', 'none');
-        $("a#debugs_on").css('display', 'none');
-        $("a#debugs_off").css('display', 'inline');
+      /* display the show debugs buttons by default */
+      $("a.debugs_on").css('display', 'inline');
+
+      /* show debugs button click event callback */
+      $("a.debugs_on").click(function() {
+        var subnet = "#" + $(this).attr('id').split('_')[0];
+        $(subnet+"_debugs").css('display', 'block');
+        $(subnet+"_table").css('display', 'none');
+        $(subnet+"_on").css('display', 'none');
+        $(subnet+"_off").css('display', 'inline');
       });
-      $("a#debugs_off").click(function() {
-        $("pre").css('display', 'none');
-        $("table").css('display', 'block');
-        $("a#debugs_on").css('display', 'inline');
-        $("a#debugs_off").css('display', 'none');
+
+      /* hide debugs button click event callback */
+      $("a.debugs_off").click(function() {
+        var subnet = "#" + $(this).attr('id').split('_')[0];
+        $(subnet+"_debugs").css('display', 'none');
+        $(subnet+"_table").css('display', 'table');
+        $(subnet+"_on").css('display', 'inline');
+        $(subnet+"_off").css('display', 'none');
       });
     });
@@ -234,4 +262,5 @@
     th {
       font-family:sans-serif;
+      padding:5px 0px;
     }
     td {
@@ -240,4 +269,5 @@
       font-size:0.75em;
       color:#666;
+      padding:5px;
     }
     td p {
@@ -249,12 +279,6 @@
       color:black;
     }
-    .good {
-      background-color:#cec;
-    }
-    .good .record {
-      color:#393;
-    }
-    .bad {
-      background-color:#d99;
+    .debugs_on, .debugs_off {
+      display:none;
     }
   </style>
@@ -262,10 +286,9 @@
 <body>
 <h1>DNS-o-Matic Report</h1>
-  <a id='debugs_on' href='#' style='display:none;'>Show Debugs</a>
-  <a id='debugs_off' href='#' style='display:none;'>Hide Debugs</a>
 """
 
 domain = 'cse-servelec.com'
-subnets = [IPNetwork('194.62.153.0/24'),
+subnets = [
+		   IPNetwork('194.62.153.0/24'),
 		   IPNetwork('194.62.154.0/24'),
 		   ]
