33"""
44This script performs a network scan on a given target or subnet.
55It checks if the target hosts are alive, and if ports 80 (HTTP) and 443 (HTTPS) are open, and optionally performs reverse DNS lookups if specified.
6+
7+ Params
8+ --hostname
9+
610v1.1 2/2024 silversword411
711v1.4 added open port checker
812v1.5 5/2/2024 integrated reverse DNS lookup into the ping function with 1-second timeout
13+ v1.6 5/31/2024 align output to columns and ports low to high
14+ v1.7 2/18/2025 fix columns with long host names and added response time
915
10- TODO: Make subnet get automatically detected instead of assuming /24
11- TODO: Compatible with Linux as well
16+ TODO: Make subnet get automatically detected
17+ TODO: run on linux as well
1218"""
1319
1420import socket
1521import threading
1622import subprocess
1723import ipaddress
24+ import re
1825from collections import defaultdict
1926import argparse
2027
28+
2129# Function to get the IP address of the primary network interface
2230def get_host_ip ():
2331 s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
2432 try :
25- s .connect ((' 10.255.255.255' , 1 ))
33+ s .connect ((" 10.255.255.255" , 1 ))
2634 IP = s .getsockname ()[0 ]
2735 except Exception :
28- IP = ' 127.0.0.1'
36+ IP = " 127.0.0.1"
2937 finally :
3038 s .close ()
3139 return IP
3240
33- # Function to ping an IP address, check if it is alive, and optionally perform a reverse DNS lookup
41+
42+ # Function to ping an IP address, check if it is alive, measure response time, and optionally perform a reverse DNS lookup
3443def ping_ip (ip , alive_hosts , do_reverse_dns ):
3544 try :
36- output = subprocess .check_output (["ping" , "-n" , "1" , "-w" , "1000" , ip ], stderr = subprocess .STDOUT , universal_newlines = True )
45+ output = subprocess .check_output (
46+ ["ping" , "-n" , "1" , "-w" , "1000" , ip ],
47+ stderr = subprocess .STDOUT ,
48+ universal_newlines = True ,
49+ )
3750 if "Reply from" in output :
3851 alive_ip = ipaddress .ip_address (ip )
52+ response_time = re .search (r"time[=<]\s*(\d+)ms" , output )
53+ response_time = (
54+ int (response_time .group (1 )) if response_time else - 1
55+ ) # If no time found, use -1
56+
3957 hostname = "NA"
4058 if do_reverse_dns :
4159 try :
@@ -46,10 +64,12 @@ def ping_ip(ip, alive_hosts, do_reverse_dns):
4664 hostname = "unknown"
4765 finally :
4866 s .close ()
49- alive_hosts .append ((alive_ip , hostname ))
67+
68+ alive_hosts .append ((alive_ip , hostname , response_time ))
5069 except Exception :
5170 pass
5271
72+
5373# Function to check for open ports
5474def check_ports (ip , port , open_ports ):
5575 try :
@@ -60,19 +80,25 @@ def check_ports(ip, port, open_ports):
6080 except Exception :
6181 pass
6282
83+
6384# Parse command-line arguments
6485def parse_arguments ():
65- parser = argparse .ArgumentParser (description = "Scan network subnet for alive hosts, open ports, and optionally perform reverse DNS lookup." )
66- parser .add_argument ("--hostname" , help = "Perform reverse DNS lookup" , action = "store_true" )
86+ parser = argparse .ArgumentParser (
87+ description = "Scan network subnet for alive hosts, open ports, and optionally perform reverse DNS lookup."
88+ )
89+ parser .add_argument (
90+ "--hostname" , help = "Perform reverse DNS lookup" , action = "store_true"
91+ )
6792 return parser .parse_args ()
6893
94+
6995# Main function to detect the subnet and scan it
7096def main ():
7197 args = parse_arguments ()
7298 host_ip = get_host_ip ()
7399 print (f"Detected Host IP: { host_ip } " )
74100
75- subnet = ipaddress .ip_network (f' { host_ip } /24' , strict = False )
101+ subnet = ipaddress .ip_network (f" { host_ip } /24" , strict = False )
76102 alive_hosts = []
77103 open_ports = defaultdict (list )
78104
@@ -90,21 +116,40 @@ def main():
90116
91117 # Launch port checks
92118 port_check_threads = []
93- for host , _ in alive_hosts :
119+ for host , _ , _ in alive_hosts :
94120 for port in [22 , 23 , 25 , 80 , 443 , 2525 , 8443 , 10443 , 10000 , 20000 ]:
95121 t = threading .Thread (target = check_ports , args = (str (host ), port , open_ports ))
96122 t .start ()
97123 port_check_threads .append (t )
98-
124+
99125 for t in port_check_threads :
100126 t .join ()
101127
102- print (f"Alive hosts in the subnet { subnet } :" )
103- for host , hostname in alive_hosts :
104- ports = ', ' .join (str (port ) for port in open_ports [str (host )])
105- print (f"IP: { host } , { hostname } , Open Ports: { ports } " )
128+ # Determine column widths dynamically
129+ max_hostname_length = max (
130+ (len (hostname ) for _ , hostname , _ in alive_hosts ), default = 8
131+ )
132+ ip_column_width = 16
133+ hostname_column_width = max (max_hostname_length , 12 ) + 2 # Minimum width of 12
134+ response_time_column_width = 8
135+ ports_column_width = 50 # Static width for ports
136+
137+ # Print header
138+ header = f"{ 'IP' :<{ip_column_width }} { '(ms)' :<{response_time_column_width }} { 'Hostname' :<{hostname_column_width }} { 'Open Ports' :<{ports_column_width }} "
139+ print (header )
140+ print ("-" * len (header ))
141+
142+ # Print results
143+ for host , hostname , response_time in alive_hosts :
144+ ports = sorted (open_ports [str (host )])
145+ ports_str = ", " .join (map (str , ports ))
146+ response_time_str = f"{ response_time } ms" if response_time >= 0 else "N/A"
147+ print (
148+ f"{ str (host ):<{ip_column_width }} { response_time_str :<{response_time_column_width }} { hostname :<{hostname_column_width }} { ports_str :<{ports_column_width }} "
149+ )
106150
107151 print (f"\n Total count of alive hosts: { len (alive_hosts )} " )
108152
153+
109154if __name__ == "__main__" :
110- main ()
155+ main ()
0 commit comments