From 771af73845124f1622da3e52b588ba6f683d26b585eaf583a6280cced44cc451 Mon Sep 17 00:00:00 2001 From: Discsearcher Date: Wed, 12 Aug 2026 10:32:52 -0400 Subject: [PATCH] Added more output to track query method used and to support TLS queries. --- dns_compare.py | 53 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/dns_compare.py b/dns_compare.py index 6db9f72..6fe9b1c 100755 --- a/dns_compare.py +++ b/dns_compare.py @@ -37,7 +37,7 @@ parser.add_argument('--ns-builtin', nargs='+', help=f'Built-in public DNS server parser.add_argument('--records', nargs='*', help='Hostname(s) to query. If not provided with --domain, queries the domain itself.') parser.add_argument('--record-type', default='A', help='DNS record type to query. Default: A') parser.add_argument('--threads', type=int, default=None, help='Number of threads to use for queries. Default: CPU count - 1') -parser.add_argument('--timeout', type=float, default=5.0, help='DNS query timeout in seconds. Default: 5.0') +parser.add_argument('--timeout', type=float, default=15.0, help='DNS query timeout in seconds. Default: 5.0') parser.add_argument('--verbose', action='store_true', help='Enable verbose debugging output.') parser.add_argument('--list-record-types', action='store_true', help='Print list of supported record types and exit.') args = parser.parse_args() @@ -94,7 +94,7 @@ except rdatatype.UnknownRdatatype: # Function to perform a single DNS query def perform_query(full_domain, ns, record_type, timeout): - """Perform a DNS query and return results""" + """Perform a DNS query and return results with protocol used""" try: qname = name.from_text(full_domain) q = message.make_query(qname, record_type) @@ -102,9 +102,12 @@ def perform_query(full_domain, ns, record_type, timeout): # Add EDNS0 extension for better compatibility (like dig does) q.use_edns(edns=0, ednsflags=0, payload=4096) + protocol_used = None + # Try UDP first try: r = query.udp(q, ns, timeout=timeout) + protocol_used = "UDP" if args.verbose: print(f"[DEBUG] UDP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr) @@ -114,6 +117,7 @@ def perform_query(full_domain, ns, record_type, timeout): print(f"[DEBUG] UDP Response truncated (TC flag set), retrying with TCP...", file=sys.stderr) try: r = query.tcp(q, ns, timeout=timeout) + protocol_used = "TCP" if args.verbose: print(f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr) except Exception as tcp_error: @@ -121,6 +125,7 @@ def perform_query(full_domain, ns, record_type, timeout): if args.verbose: print(f"[DEBUG] TCP failed ({tcp_error}), trying TLS...", file=sys.stderr) r = query.tls(q, ns, timeout=timeout) + protocol_used = "TLS" if args.verbose: print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr) except Exception as udp_error: @@ -129,6 +134,7 @@ def perform_query(full_domain, ns, record_type, timeout): print(f"[DEBUG] UDP failed ({udp_error}), trying TCP...", file=sys.stderr) try: r = query.tcp(q, ns, timeout=timeout) + protocol_used = "TCP" if args.verbose: print(f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr) except Exception as tcp_error: @@ -136,6 +142,7 @@ def perform_query(full_domain, ns, record_type, timeout): if args.verbose: print(f"[DEBUG] TCP failed ({tcp_error}), trying TLS...", file=sys.stderr) r = query.tls(q, ns, timeout=timeout) + protocol_used = "TLS" if args.verbose: print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr) @@ -155,7 +162,7 @@ def perform_query(full_domain, ns, record_type, timeout): response_rcode = r.rcode() if response_rcode != 0: # 0 = NOERROR rcode_name = rcode.to_text(response_rcode) - return (full_domain, ns, f"RCODE: {rcode_name}") + return (full_domain, ns, f"RCODE: {rcode_name}", protocol_used) results = [] @@ -165,7 +172,7 @@ def perform_query(full_domain, ns, record_type, timeout): for rrset in answers: for item in rrset: results.append(str(item)) - return (full_domain, ns, results) + return (full_domain, ns, results, protocol_used) # If no answer, check additional section (some servers put records there) additional = r.additional @@ -174,7 +181,7 @@ def perform_query(full_domain, ns, record_type, timeout): for item in rrset: results.append(str(item)) if results: - return (full_domain, ns, results) + return (full_domain, ns, results, protocol_used) # If still no results, check authority section authority = r.authority @@ -183,15 +190,15 @@ def perform_query(full_domain, ns, record_type, timeout): for item in rrset: results.append(str(item)) if results: - return (full_domain, ns, results) + return (full_domain, ns, results, protocol_used) # Truly no records found - return (full_domain, ns, "NO RECORDS") + return (full_domain, ns, "NO RECORDS", protocol_used) except (exception.Timeout, TimeoutError): - return (full_domain, ns, "TIMED OUT") + return (full_domain, ns, "TIMED OUT", None) except Exception as e: error_msg = str(e) if str(e) else type(e).__name__ - return (full_domain, ns, f"ERROR: {error_msg}") + return (full_domain, ns, f"ERROR: {error_msg}", None) # Start timer start_time = time.time() @@ -226,15 +233,15 @@ for full_domain in full_domains: thread_count = args.threads if args.threads else max(1, os.cpu_count() - 1) # Execute queries in parallel -results_map = {} # (full_domain, ns) -> resolved_ip +results_map = {} # (full_domain, ns) -> (resolved_ip, protocol_used) with ThreadPoolExecutor(max_workers=thread_count) as executor: futures = [executor.submit(perform_query, full_domain, ns, record_type, args.timeout) for full_domain, ns, _ in query_tasks] for future in futures: - full_domain, ns, resolved_ips = future.result() + full_domain, ns, resolved_ips, protocol_used = future.result() if resolved_ips is not None: - results_map[(full_domain, ns)] = resolved_ips + results_map[(full_domain, ns)] = (resolved_ips, protocol_used) # Build the output structure output = {} @@ -259,26 +266,36 @@ for full_domain in full_domains: for ns in all_nameservers: provider = nameserver_map.get(ns) if (full_domain, ns) in results_map: - results = results_map[(full_domain, ns)] + results, protocol_used = results_map[(full_domain, ns)] + # Create entry with result and protocol + entry = {} # Handle error/timeout/rcode cases (strings) if isinstance(results, str): - output[full_domain]["records"][provider][ns] = results + entry["result"] = results else: results = sorted(results) # Store as single value if only one result, otherwise as array - output[full_domain]["records"][provider][ns] = results[0] if len(results) == 1 else results + entry["result"] = results[0] if len(results) == 1 else results + if protocol_used: + entry["protocol"] = protocol_used + output[full_domain]["records"][provider][ns] = entry else: # Flat structure for manual nameservers only for ns in all_nameservers: if (full_domain, ns) in results_map: - results = results_map[(full_domain, ns)] + results, protocol_used = results_map[(full_domain, ns)] + # Create entry with result and protocol + entry = {} # Handle error/timeout/rcode cases (strings) if isinstance(results, str): - output[full_domain]["records"][ns] = results + entry["result"] = results else: results = sorted(results) # Store as single value if only one result, otherwise as array - output[full_domain]["records"][ns] = results[0] if len(results) == 1 else results + entry["result"] = results[0] if len(results) == 1 else results + if protocol_used: + entry["protocol"] = protocol_used + output[full_domain]["records"][ns] = entry # Calculate and add timing information end_time = time.time()