2727 - ipaddress
2828"""
2929
30+ import sys
31+ import os
3032import argparse
31- import concurrent .futures
32- import dns .name
33- import dns .query
34- import dns .resolver
35- import dns .reversename
36- import dns .zone
37- import ipaddress
3833import json
3934import socket
40- import sys
35+ import concurrent . futures
4136import time
4237from collections import defaultdict
4338from datetime import datetime
39+ import ipaddress
4440
45- # Configure DNS resolver
46- dns_resolver = dns .resolver .Resolver ()
41+ # Check dependencies
42+ def check_dependencies ():
43+ """Check if required dependencies are installed."""
44+ try :
45+ import dns .name
46+ import dns .query
47+ import dns .resolver
48+ import dns .reversename
49+ import dns .zone
50+ except ImportError :
51+ print ("Error: Required package 'dnspython' is not installed." )
52+ print ("\n To install the required package, run:" )
53+ print (" pip install dnspython" )
54+ print ("\n On Windows:" )
55+ print (" python -m pip install dnspython" )
56+ print ("\n On Linux/MacOS:" )
57+ print (" pip3 install dnspython" )
58+ sys .exit (1 )
4759
48- # Default DNS servers (can be overridden via command line)
60+ try :
61+ import ipaddress
62+ except ImportError :
63+ print ("Error: Required package 'ipaddress' is not installed." )
64+ print ("\n To install the required package, run:" )
65+ print (" pip install ipaddress" )
66+ print ("\n On Windows:" )
67+ print (" python -m pip install ipaddress" )
68+ print ("\n On Linux/MacOS:" )
69+ print (" pip3 install ipaddress" )
70+ sys .exit (1 )
71+
72+ # Constants
4973DEFAULT_DNS_SERVERS = [
5074 '8.8.8.8' , # Google
5175 '1.1.1.1' , # Cloudflare
6589 'portal' , 'ssh' , 'git' , 'cdn' , 'cloud' , 'support' , 'web'
6690]
6791
92+ # Utility functions
93+ def save_results (results , output , domain = None , ip = None , ip_range = None , dns_servers = None ):
94+ """Save results to a file."""
95+ try :
96+ with open (output , 'w' ) as f :
97+ # Add metadata
98+ result_data = {
99+ 'metadata' : {
100+ 'timestamp' : datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' ),
101+ 'target_domain' : domain ,
102+ 'target_ip' : ip ,
103+ 'target_range' : ip_range ,
104+ 'dns_servers' : dns_servers
105+ },
106+ 'results' : results
107+ }
108+
109+ json .dump (result_data , f , indent = 2 )
110+
111+ print (f"Results saved to { output } " )
112+ except Exception as e :
113+ print (f"Error saving results: { e } " )
114+
115+ # CLI arguments parsing
116+ def parse_arguments ():
117+ """Parse command line arguments."""
118+ parser = argparse .ArgumentParser (description = "DNS Reconnaissance Tool" )
119+ parser .add_argument ("--domain" , "-d" , help = "Target domain" )
120+ parser .add_argument ("--ip" , "-i" , help = "Target IP address for reverse lookup" )
121+ parser .add_argument ("--range" , "-r" , help = "IP range in CIDR notation (e.g., 192.168.1.0/24)" )
122+ parser .add_argument ("--server" , "-s" , action = "append" , help = "DNS server to use (can be specified multiple times)" )
123+ parser .add_argument ("--timeout" , "-t" , type = int , default = 5 , help = "Timeout for DNS queries in seconds" )
124+ parser .add_argument ("--threads" , "-n" , type = int , default = 10 , help = "Number of threads for concurrent queries" )
125+ parser .add_argument ("--verbose" , "-v" , action = "store_true" , help = "Enable verbose output" )
126+ parser .add_argument ("--output" , "-o" , help = "Output file path (JSON format)" )
127+ parser .add_argument ("--subdomains" , "-w" , help = "Path to subdomain wordlist file" )
128+ parser .add_argument ("--history" , action = "store_true" , help = "Attempt to retrieve historical DNS records" )
129+
130+ args = parser .parse_args ()
131+
132+ # Validate that at least one target is specified
133+ if not (args .domain or args .ip or args .range ):
134+ parser .error ("At least one target (--domain, --ip, or --range) must be specified" )
135+
136+ return args
137+
138+ # Main DNS class
68139class DNSRecon :
69140 def __init__ (self , domain = None , ip = None , ip_range = None , dns_servers = None ,
70141 timeout = 5 , threads = 10 , verbose = False , output = None ,
@@ -84,6 +155,13 @@ def __init__(self, domain=None, ip=None, ip_range=None, dns_servers=None,
84155 subdomains (str): Path to subdomain wordlist file
85156 history (bool): Retrieve historical DNS records if available
86157 """
158+ # Import DNS modules here to ensure dependencies are checked first
159+ import dns .name
160+ import dns .query
161+ import dns .resolver
162+ import dns .reversename
163+ import dns .zone
164+
87165 self .domain = domain
88166 self .ip = ip
89167 self .ip_range = ip_range
@@ -104,9 +182,10 @@ def __init__(self, domain=None, ip=None, ip_range=None, dns_servers=None,
104182 }
105183
106184 # Configure resolver
107- dns_resolver .timeout = self .timeout
108- dns_resolver .lifetime = self .timeout
109- dns_resolver .nameservers = [self .dns_servers [0 ]] # Primary DNS server
185+ self .resolver = dns .resolver .Resolver ()
186+ self .resolver .timeout = self .timeout
187+ self .resolver .lifetime = self .timeout
188+ self .resolver .nameservers = [self .dns_servers [0 ]] # Primary DNS server
110189
111190 def run (self ):
112191 """
@@ -159,7 +238,14 @@ def run(self):
159238 print (f"\n Reconnaissance completed in { duration :.2f} seconds" )
160239
161240 if self .output :
162- self ._save_results ()
241+ save_results (
242+ self .results ,
243+ self .output ,
244+ domain = self .domain ,
245+ ip = self .ip ,
246+ ip_range = self .ip_range ,
247+ dns_servers = self .dns_servers
248+ )
163249
164250 return self .results
165251
@@ -177,14 +263,14 @@ def _query_record(self, domain, record_type):
177263 try :
178264 if record_type == 'DMARC' :
179265 # DMARC records are stored as TXT records at _dmarc.domain
180- answers = dns_resolver .resolve (f'_dmarc.{ domain } ' , 'TXT' )
266+ answers = self . resolver .resolve (f'_dmarc.{ domain } ' , 'TXT' )
181267 elif record_type == 'SPF' :
182268 # SPF records are stored as TXT records
183- answers = dns_resolver .resolve (domain , 'TXT' )
269+ answers = self . resolver .resolve (domain , 'TXT' )
184270 # Filter for SPF records
185271 return [str (rdata ).strip ('"' ) for rdata in answers if 'spf' in str (rdata ).lower ()]
186272 else :
187- answers = dns_resolver .resolve (domain , record_type )
273+ answers = self . resolver .resolve (domain , record_type )
188274
189275 if record_type == 'A' or record_type == 'AAAA' :
190276 return [rdata .address for rdata in answers ]
@@ -397,7 +483,7 @@ def _resolve_subdomain(self, subdomain):
397483 str: IP address(es) if resolved, None otherwise
398484 """
399485 try :
400- answers = dns_resolver .resolve (subdomain , 'A' )
486+ answers = self . resolver .resolve (subdomain , 'A' )
401487 return ', ' .join (rdata .address for rdata in answers )
402488 except Exception :
403489 return None
@@ -414,7 +500,7 @@ def _perform_reverse_lookup(self, ip):
414500 """
415501 try :
416502 reverse_name = dns .reversename .from_address (ip )
417- answers = dns_resolver .resolve (reverse_name , 'PTR' )
503+ answers = self . resolver .resolve (reverse_name , 'PTR' )
418504 hostnames = [str (rdata ).rstrip ('.' ) for rdata in answers ]
419505
420506 if hostnames :
@@ -487,7 +573,7 @@ def _perform_reverse_lookup_quiet(self, ip):
487573 """
488574 try :
489575 reverse_name = dns .reversename .from_address (ip )
490- answers = dns_resolver .resolve (reverse_name , 'PTR' )
576+ answers = self . resolver .resolve (reverse_name , 'PTR' )
491577 hostnames = [str (rdata ).rstrip ('.' ) for rdata in answers ]
492578
493579 if hostnames :
@@ -498,47 +584,13 @@ def _perform_reverse_lookup_quiet(self, ip):
498584 pass
499585
500586 return []
501-
502- def _save_results (self ):
503- """Save results to a file."""
504- try :
505- with open (self .output , 'w' ) as f :
506- # Add metadata
507- result_data = {
508- 'metadata' : {
509- 'timestamp' : datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' ),
510- 'target_domain' : self .domain ,
511- 'target_ip' : self .ip ,
512- 'target_range' : self .ip_range ,
513- 'dns_servers' : self .dns_servers
514- },
515- 'results' : self .results
516- }
517-
518- json .dump (result_data , f , indent = 2 )
519-
520- print (f"Results saved to { self .output } " )
521- except Exception as e :
522- print (f"Error saving results: { e } " )
523587
524588def main ():
525- parser = argparse .ArgumentParser (description = "DNS Reconnaissance Tool" )
526- parser .add_argument ("--domain" , "-d" , help = "Target domain" )
527- parser .add_argument ("--ip" , "-i" , help = "Target IP address for reverse lookup" )
528- parser .add_argument ("--range" , "-r" , help = "IP range in CIDR notation (e.g., 192.168.1.0/24)" )
529- parser .add_argument ("--server" , "-s" , action = "append" , help = "DNS server to use (can be specified multiple times)" )
530- parser .add_argument ("--timeout" , "-t" , type = int , default = 5 , help = "Timeout for DNS queries in seconds" )
531- parser .add_argument ("--threads" , "-n" , type = int , default = 10 , help = "Number of threads for concurrent queries" )
532- parser .add_argument ("--verbose" , "-v" , action = "store_true" , help = "Enable verbose output" )
533- parser .add_argument ("--output" , "-o" , help = "Output file path (JSON format)" )
534- parser .add_argument ("--subdomains" , "-w" , help = "Path to subdomain wordlist file" )
535- parser .add_argument ("--history" , action = "store_true" , help = "Attempt to retrieve historical DNS records" )
536-
537- args = parser .parse_args ()
589+ # Check dependencies first
590+ check_dependencies ()
538591
539- # Validate that at least one target is specified
540- if not (args .domain or args .ip or args .range ):
541- parser .error ("At least one target (--domain, --ip, or --range) must be specified" )
592+ # Parse command line arguments
593+ args = parse_arguments ()
542594
543595 try :
544596 recon = DNSRecon (
0 commit comments