SHA256
Updated to have better json format and statistics.
This commit is contained in:
+199
-68
@@ -7,39 +7,73 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from dns import message
|
import dns.message as message
|
||||||
from dns import query
|
import dns.query as query
|
||||||
from dns import rdatatype
|
import dns.rdatatype as rdatatype
|
||||||
from dns import rcode
|
import dns.rcode as rcode
|
||||||
from dns import name
|
import dns.name as name
|
||||||
from dns import flags
|
import dns.flags as flags
|
||||||
from dns import exception
|
import dns.exception as exception
|
||||||
|
|
||||||
# Get list of supported record types
|
# Get list of supported record types
|
||||||
supported_types = sorted([rdatatype.to_text(rdtype) for rdtype in rdatatype.RdataType])
|
supported_types = sorted(
|
||||||
|
[rdatatype.to_text(rdtype) for rdtype in rdatatype.RdataType]
|
||||||
|
)
|
||||||
|
|
||||||
# Dictionary of public DNS servers
|
# Dictionary of public DNS servers
|
||||||
builtin_nameservers = {
|
builtin_nameservers = {
|
||||||
'google': ['8.8.8.8', '8.8.4.4'],
|
"google": ["8.8.8.8", "8.8.4.4"],
|
||||||
'cloudflare': ['1.1.1.1', '1.0.0.1', '1.1.1.2', '1.0.0.2'],
|
"cloudflare": ["1.1.1.1", "1.0.0.1", "1.1.1.2", "1.0.0.2"],
|
||||||
'quad9': ['9.9.9.9', '149.112.112.112'],
|
"quad9": ["9.9.9.9", "149.112.112.112"],
|
||||||
'gte': ['4.2.2.1', '4.2.2.2', '4.2.2.3', '4.2.2.4', '4.2.2.5', '4.2.2.6'],
|
"gte": ["4.2.2.1", "4.2.2.2", "4.2.2.3", "4.2.2.4", "4.2.2.5", "4.2.2.6"],
|
||||||
'opendns': ['208.67.222.222', '208.67.220.220', '208.67.222.123', '208.67.220.123'],
|
"opendns": ["208.67.222.222", "208.67.220.220", "208.67.222.123", "208.67.220.123"],
|
||||||
'verisign': ['64.6.64.6', '64.6.65.6'],
|
"verisign": ["64.6.64.6", "64.6.65.6"],
|
||||||
'comodo': ['8.26.56.26', '8.20.247.20'],
|
"comodo": ["8.26.56.26", "8.20.247.20"],
|
||||||
'level3': ['209.244.0.3', '209.244.0.4'],
|
"level3": ["209.244.0.3", "209.244.0.4"],
|
||||||
}
|
}
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--domain', help='Helps if all records to query are on the same domain.')
|
parser.add_argument(
|
||||||
parser.add_argument('--nameservers', nargs='+', help='IPv4 or IPv6 address of nameserver(s) to be queried.')
|
"--domain", help="Helps if all records to query are on the same domain."
|
||||||
parser.add_argument('--ns-builtin', nargs='+', help=f'Built-in public DNS servers. Options: {", ".join(builtin_nameservers.keys())}, all')
|
)
|
||||||
parser.add_argument('--records', nargs='*', help='Hostname(s) to query. If not provided with --domain, queries the domain itself.')
|
parser.add_argument(
|
||||||
parser.add_argument('--record-type', default='A', help='DNS record type to query. Default: A')
|
"--nameservers",
|
||||||
parser.add_argument('--threads', type=int, default=None, help='Number of threads to use for queries. Default: CPU count - 1')
|
nargs="+",
|
||||||
parser.add_argument('--timeout', type=float, default=15.0, help='DNS query timeout in seconds. Default: 5.0')
|
help="IPv4 or IPv6 address of nameserver(s) to be queried.",
|
||||||
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.')
|
parser.add_argument(
|
||||||
|
"--ns-builtin",
|
||||||
|
nargs="+",
|
||||||
|
help=f"Built-in public DNS servers. Options: {', '.join(builtin_nameservers.keys())}, all",
|
||||||
|
)
|
||||||
|
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=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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Build nameserver map: IP -> provider name
|
# Build nameserver map: IP -> provider name
|
||||||
@@ -49,7 +83,7 @@ builtin_providers = [] # list of provider keys used
|
|||||||
# Resolve builtin nameservers to actual IPs
|
# Resolve builtin nameservers to actual IPs
|
||||||
if args.ns_builtin:
|
if args.ns_builtin:
|
||||||
# Handle 'all' special case
|
# Handle 'all' special case
|
||||||
if 'all' in [p.lower() for p in args.ns_builtin]:
|
if "all" in [p.lower() for p in args.ns_builtin]:
|
||||||
builtin_providers = list(builtin_nameservers.keys())
|
builtin_providers = list(builtin_nameservers.keys())
|
||||||
else:
|
else:
|
||||||
builtin_providers = [p.lower() for p in args.ns_builtin]
|
builtin_providers = [p.lower() for p in args.ns_builtin]
|
||||||
@@ -57,7 +91,9 @@ if args.ns_builtin:
|
|||||||
for provider in builtin_providers:
|
for provider in builtin_providers:
|
||||||
if provider not in builtin_nameservers:
|
if provider not in builtin_nameservers:
|
||||||
print(f"Error: Unknown DNS provider '{provider}'")
|
print(f"Error: Unknown DNS provider '{provider}'")
|
||||||
print(f"Available providers: {', '.join(builtin_nameservers.keys())}, all")
|
print(
|
||||||
|
f"Available providers: {', '.join(builtin_nameservers.keys())}, all"
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
for ip in builtin_nameservers[provider]:
|
for ip in builtin_nameservers[provider]:
|
||||||
nameserver_map[ip] = provider
|
nameserver_map[ip] = provider
|
||||||
@@ -80,7 +116,7 @@ if not all_nameservers:
|
|||||||
# Handle listing record types
|
# Handle listing record types
|
||||||
if args.list_record_types:
|
if args.list_record_types:
|
||||||
print("Supported DNS record types:")
|
print("Supported DNS record types:")
|
||||||
print((", ").join(supported_types))
|
print(", ".join(supported_types))
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
# Convert record type string to dns.rdatatype constant
|
# Convert record type string to dns.rdatatype constant
|
||||||
@@ -88,8 +124,8 @@ try:
|
|||||||
record_type = rdatatype.from_text(args.record_type)
|
record_type = rdatatype.from_text(args.record_type)
|
||||||
except rdatatype.UnknownRdatatype:
|
except rdatatype.UnknownRdatatype:
|
||||||
print(f"Error: Unknown record type '{args.record_type}'")
|
print(f"Error: Unknown record type '{args.record_type}'")
|
||||||
print(f"\nSupported record types:")
|
print("\nSupported record types:")
|
||||||
print((', ').join(supported_types))
|
print(", ".join(supported_types))
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Function to perform a single DNS query
|
# Function to perform a single DNS query
|
||||||
@@ -109,102 +145,159 @@ def perform_query(full_domain, ns, record_type, timeout):
|
|||||||
r = query.udp(q, ns, timeout=timeout)
|
r = query.udp(q, ns, timeout=timeout)
|
||||||
protocol_used = "UDP"
|
protocol_used = "UDP"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] UDP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] UDP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# Check if UDP returned ETPA error - if so, try TCP
|
# Check if UDP returned ETPA error - if so, try TCP
|
||||||
udp_rcode = r.rcode()
|
udp_rcode = r.rcode()
|
||||||
udp_rcode_name = rcode.to_text(udp_rcode)
|
udp_rcode_name = rcode.to_text(udp_rcode)
|
||||||
if udp_rcode_name == "ETPA":
|
if udp_rcode_name == "ETPA":
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] UDP returned ETPA, retrying with TCP...", file=sys.stderr)
|
print(
|
||||||
|
"[DEBUG] UDP returned ETPA, retrying with TCP...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
r = query.tcp(q, ns, timeout=timeout)
|
r = query.tcp(q, ns, timeout=timeout)
|
||||||
protocol_used = "TCP"
|
protocol_used = "TCP"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
# If TCP also returned ETPA, try TLS
|
# If TCP also returned ETPA, try TLS
|
||||||
tcp_rcode = r.rcode()
|
tcp_rcode = r.rcode()
|
||||||
tcp_rcode_name = rcode.to_text(tcp_rcode)
|
tcp_rcode_name = rcode.to_text(tcp_rcode)
|
||||||
if tcp_rcode_name == "ETPA":
|
if tcp_rcode_name == "ETPA":
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP also returned ETPA, retrying with TLS...", file=sys.stderr)
|
print(
|
||||||
|
"[DEBUG] TCP also returned ETPA, retrying with TLS...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
r = query.tls(q, ns, timeout=timeout)
|
r = query.tls(q, ns, timeout=timeout)
|
||||||
protocol_used = "TLS"
|
protocol_used = "TLS"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
except Exception as tcp_error:
|
except Exception as tcp_error:
|
||||||
# Fall back to TLS if TCP fails
|
# Fall back to TLS if TCP fails
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP failed ({tcp_error}), trying TLS...", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TCP failed ({tcp_error}), trying TLS...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
r = query.tls(q, ns, timeout=timeout)
|
r = query.tls(q, ns, timeout=timeout)
|
||||||
protocol_used = "TLS"
|
protocol_used = "TLS"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
# If UDP returns truncated, retry with TCP
|
# If UDP returns truncated, retry with TCP
|
||||||
elif r.flags & flags.TC:
|
elif r.flags & flags.TC:
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] UDP Response truncated (TC flag set), retrying with TCP...", file=sys.stderr)
|
print(
|
||||||
|
"[DEBUG] UDP Response truncated (TC flag set), retrying with TCP...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
r = query.tcp(q, ns, timeout=timeout)
|
r = query.tcp(q, ns, timeout=timeout)
|
||||||
protocol_used = "TCP"
|
protocol_used = "TCP"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
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:
|
except Exception as tcp_error:
|
||||||
# Fall back to TLS if TCP fails
|
# Fall back to TLS if TCP fails
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP failed ({tcp_error}), trying TLS...", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TCP failed ({tcp_error}), trying TLS...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
r = query.tls(q, ns, timeout=timeout)
|
r = query.tls(q, ns, timeout=timeout)
|
||||||
protocol_used = "TLS"
|
protocol_used = "TLS"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
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:
|
except Exception as udp_error:
|
||||||
# Fall back to TCP if UDP fails
|
# Fall back to TCP if UDP fails
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] UDP failed ({udp_error}), trying TCP...", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] UDP failed ({udp_error}), trying TCP...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
r = query.tcp(q, ns, timeout=timeout)
|
r = query.tcp(q, ns, timeout=timeout)
|
||||||
protocol_used = "TCP"
|
protocol_used = "TCP"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TCP Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
# If TCP returned ETPA, try TLS
|
# If TCP returned ETPA, try TLS
|
||||||
tcp_rcode = r.rcode()
|
tcp_rcode = r.rcode()
|
||||||
tcp_rcode_name = rcode.to_text(tcp_rcode)
|
tcp_rcode_name = rcode.to_text(tcp_rcode)
|
||||||
if tcp_rcode_name == "ETPA":
|
if tcp_rcode_name == "ETPA":
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP returned ETPA, retrying with TLS...", file=sys.stderr)
|
print(
|
||||||
|
"[DEBUG] TCP returned ETPA, retrying with TLS...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
r = query.tls(q, ns, timeout=timeout)
|
r = query.tls(q, ns, timeout=timeout)
|
||||||
protocol_used = "TLS"
|
protocol_used = "TLS"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
except Exception as tcp_error:
|
except Exception as tcp_error:
|
||||||
# Fall back to TLS if TCP fails
|
# Fall back to TLS if TCP fails
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TCP failed ({tcp_error}), trying TLS...", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TCP failed ({tcp_error}), trying TLS...",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
r = query.tls(q, ns, timeout=timeout)
|
r = query.tls(q, ns, timeout=timeout)
|
||||||
protocol_used = "TLS"
|
protocol_used = "TLS"
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}", file=sys.stderr)
|
print(
|
||||||
|
f"[DEBUG] TLS Response: Answer={len(r.answer)} RRsets, TC flag: {bool(r.flags & flags.TC)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# Verbose debugging
|
# Verbose debugging
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"\n[DEBUG] Query: {full_domain} (@{ns}) Type: {rdatatype.to_text(record_type)}", file=sys.stderr)
|
print(
|
||||||
|
f"\n[DEBUG] Query: {full_domain} (@{ns}) Type: {rdatatype.to_text(record_type)}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
print(f"[DEBUG] Response code: {rcode.to_text(r.rcode())}", file=sys.stderr)
|
print(f"[DEBUG] Response code: {rcode.to_text(r.rcode())}", file=sys.stderr)
|
||||||
print(f"[DEBUG] Answer section: {len(r.answer)} RRsets", file=sys.stderr)
|
print(f"[DEBUG] Answer section: {len(r.answer)} RRsets", file=sys.stderr)
|
||||||
print(f"[DEBUG] Authority section: {len(r.authority)} RRsets", file=sys.stderr)
|
print(
|
||||||
print(f"[DEBUG] Additional section: {len(r.additional)} RRsets", file=sys.stderr)
|
f"[DEBUG] Authority section: {len(r.authority)} RRsets",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Additional section: {len(r.additional)} RRsets",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
print(f"[DEBUG] Full message: {r}", file=sys.stderr)
|
print(f"[DEBUG] Full message: {r}", file=sys.stderr)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
response_rcode = r.rcode()
|
response_rcode = r.rcode()
|
||||||
|
|
||||||
# Check response code
|
# Check response code
|
||||||
response_rcode = r.rcode()
|
|
||||||
if response_rcode != 0: # 0 = NOERROR
|
if response_rcode != 0: # 0 = NOERROR
|
||||||
rcode_name = rcode.to_text(response_rcode)
|
rcode_name = rcode.to_text(response_rcode)
|
||||||
return (full_domain, ns, f"RCODE: {rcode_name}", protocol_used)
|
return (full_domain, ns, f"RCODE: {rcode_name}", protocol_used)
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
# Check answer section first
|
# Check answer section first
|
||||||
answers = r.answer
|
answers = r.answer
|
||||||
if answers:
|
if answers:
|
||||||
@@ -253,7 +346,7 @@ if args.domain:
|
|||||||
if rec == args.domain:
|
if rec == args.domain:
|
||||||
full_domain = args.domain
|
full_domain = args.domain
|
||||||
else:
|
else:
|
||||||
full_domain = f'{rec}.{args.domain}'
|
full_domain = f"{rec}.{args.domain}"
|
||||||
full_domains.append(full_domain)
|
full_domains.append(full_domain)
|
||||||
else:
|
else:
|
||||||
# Query each record as a full domain
|
# Query each record as a full domain
|
||||||
@@ -274,8 +367,10 @@ thread_count = args.threads if args.threads else max(1, os.cpu_count() - 1)
|
|||||||
# Execute queries in parallel
|
# Execute queries in parallel
|
||||||
results_map = {} # (full_domain, ns) -> (resolved_ip, protocol_used)
|
results_map = {} # (full_domain, ns) -> (resolved_ip, protocol_used)
|
||||||
with ThreadPoolExecutor(max_workers=thread_count) as executor:
|
with ThreadPoolExecutor(max_workers=thread_count) as executor:
|
||||||
futures = [executor.submit(perform_query, full_domain, ns, record_type, args.timeout)
|
futures = [
|
||||||
for full_domain, ns, _ in query_tasks]
|
executor.submit(perform_query, full_domain, ns, record_type, args.timeout)
|
||||||
|
for full_domain, ns, _ in query_tasks
|
||||||
|
]
|
||||||
|
|
||||||
for future in futures:
|
for future in futures:
|
||||||
full_domain, ns, resolved_ips, protocol_used = future.result()
|
full_domain, ns, resolved_ips, protocol_used = future.result()
|
||||||
@@ -283,23 +378,21 @@ with ThreadPoolExecutor(max_workers=thread_count) as executor:
|
|||||||
results_map[(full_domain, ns)] = (resolved_ips, protocol_used)
|
results_map[(full_domain, ns)] = (resolved_ips, protocol_used)
|
||||||
|
|
||||||
# Build the output structure
|
# Build the output structure
|
||||||
output = {}
|
output = {"records": {}, "metadata": {"timing": {}, "statistics": {}}}
|
||||||
|
|
||||||
# Initialize output structure based on provider grouping
|
# Initialize records structure based on provider grouping
|
||||||
for full_domain in full_domains:
|
for full_domain in full_domains:
|
||||||
output[full_domain] = {"records": {}}
|
output["records"][full_domain] = {"results": {}}
|
||||||
|
|
||||||
# Group by provider if using builtin
|
# Group by provider if using builtin
|
||||||
if builtin_providers:
|
if builtin_providers:
|
||||||
for provider in builtin_providers:
|
for provider in builtin_providers:
|
||||||
output[full_domain]["records"][provider] = {}
|
output["records"][full_domain]["results"][provider] = {}
|
||||||
if manual_nameservers:
|
if manual_nameservers:
|
||||||
output[full_domain]["records"]["manual_selection"] = {}
|
output["records"][full_domain]["results"]["manual_selection"] = {}
|
||||||
else:
|
|
||||||
# Just use the flat nameserver structure if no builtin used
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Populate results
|
# Populate results and collect statistics
|
||||||
|
provider_stats = {} # provider -> {result: count, protocol: count}
|
||||||
for full_domain in full_domains:
|
for full_domain in full_domains:
|
||||||
if builtin_providers:
|
if builtin_providers:
|
||||||
for ns in all_nameservers:
|
for ns in all_nameservers:
|
||||||
@@ -317,7 +410,27 @@ for full_domain in full_domains:
|
|||||||
entry["result"] = results[0] if len(results) == 1 else results
|
entry["result"] = results[0] if len(results) == 1 else results
|
||||||
if protocol_used:
|
if protocol_used:
|
||||||
entry["protocol"] = protocol_used
|
entry["protocol"] = protocol_used
|
||||||
output[full_domain]["records"][provider][ns] = entry
|
output["records"][full_domain]["results"][provider][ns] = entry
|
||||||
|
|
||||||
|
# Collect statistics
|
||||||
|
if provider not in provider_stats:
|
||||||
|
provider_stats[provider] = {}
|
||||||
|
result_val = entry.get("result")
|
||||||
|
if result_val:
|
||||||
|
# Handle both single results and lists of results
|
||||||
|
if isinstance(result_val, list):
|
||||||
|
for item in result_val:
|
||||||
|
provider_stats[provider][item] = (
|
||||||
|
provider_stats[provider].get(item, 0) + 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
provider_stats[provider][result_val] = (
|
||||||
|
provider_stats[provider].get(result_val, 0) + 1
|
||||||
|
)
|
||||||
|
if protocol_used:
|
||||||
|
provider_stats[provider][protocol_used] = (
|
||||||
|
provider_stats[provider].get(protocol_used, 0) + 1
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Flat structure for manual nameservers only
|
# Flat structure for manual nameservers only
|
||||||
for ns in all_nameservers:
|
for ns in all_nameservers:
|
||||||
@@ -334,17 +447,35 @@ for full_domain in full_domains:
|
|||||||
entry["result"] = results[0] if len(results) == 1 else results
|
entry["result"] = results[0] if len(results) == 1 else results
|
||||||
if protocol_used:
|
if protocol_used:
|
||||||
entry["protocol"] = protocol_used
|
entry["protocol"] = protocol_used
|
||||||
output[full_domain]["records"][ns] = entry
|
output["records"][full_domain]["results"][ns] = entry
|
||||||
|
|
||||||
# Calculate and add timing information
|
# Calculate timing information
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
end_time_iso = datetime.fromtimestamp(end_time).isoformat()
|
end_time_iso = datetime.fromtimestamp(end_time).isoformat()
|
||||||
elapsed_time = end_time - start_time
|
elapsed_time = end_time - start_time
|
||||||
|
elapsed_ms = int(elapsed_time * 1000)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
output["metadata"] = {
|
output["metadata"] = {
|
||||||
"start_time": start_time_iso,
|
"start_time": start_time_iso,
|
||||||
"end_time": end_time_iso,
|
"end_time": end_time_iso,
|
||||||
"runtime": f"{elapsed_time:.2f}s"
|
"runtime": f"{elapsed_time:.2f}s"
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
output["metadata"]["timing"]["start_time"] = start_time_iso
|
||||||
|
output["metadata"]["timing"]["end_time"] = end_time_iso
|
||||||
|
output["metadata"]["timing"]["runtime_ms"] = elapsed_ms
|
||||||
|
|
||||||
|
# Build statistics section
|
||||||
|
for provider, stats in provider_stats.items():
|
||||||
|
output["metadata"]["statistics"][provider] = stats
|
||||||
|
|
||||||
|
# Calculate totals
|
||||||
|
total_stats = {}
|
||||||
|
for provider_stats_dict in provider_stats.values():
|
||||||
|
for key, count in provider_stats_dict.items():
|
||||||
|
total_stats[key] = total_stats.get(key, 0) + count
|
||||||
|
output["metadata"]["statistics"]["total"] = total_stats
|
||||||
|
>>>>>>> b2565dd (Updated to have better json format and statistics.)
|
||||||
|
|
||||||
print(json.dumps(output, indent=4))
|
print(json.dumps(output, indent=4))
|
||||||
|
|||||||
Reference in New Issue
Block a user