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.
66
7- Params
8- --hostname
7+ Params:
8+ --hostname Perform reverse DNS lookup
9+ --mac Include MAC address in output
910
1011v1.1 2/2024 silversword411
1112v1.4 added open port checker
12- v1.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
13+ v1.5 5/2/2024 silversword411 integrated reverse DNS lookup into the ping function with 1-second timeout
14+ v1.6 5/31/2024 silversword411 align output to columns and ports low to high
15+ v1.7 2/18/2025 silversword411 fix columns with long host names and added response time
16+ v1.8 5/21/2025 silversword411 added MAC address lookup with --mac option
1517
1618TODO: Make subnet get automatically detected
1719TODO: run on linux as well
2628import argparse
2729
2830
29- # Function to get the IP address of the primary network interface
3031def get_host_ip ():
3132 s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
3233 try :
@@ -39,7 +40,6 @@ def get_host_ip():
3940 return IP
4041
4142
42- # Function to ping an IP address, check if it is alive, measure response time, and optionally perform a reverse DNS lookup
4343def ping_ip (ip , alive_hosts , do_reverse_dns ):
4444 try :
4545 output = subprocess .check_output (
@@ -50,9 +50,7 @@ def ping_ip(ip, alive_hosts, do_reverse_dns):
5050 if "Reply from" in output :
5151 alive_ip = ipaddress .ip_address (ip )
5252 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
53+ response_time = int (response_time .group (1 )) if response_time else - 1
5654
5755 hostname = "NA"
5856 if do_reverse_dns :
@@ -65,12 +63,22 @@ def ping_ip(ip, alive_hosts, do_reverse_dns):
6563 finally :
6664 s .close ()
6765
68- alive_hosts .append ((alive_ip , hostname , response_time ))
66+ alive_hosts .append (
67+ (alive_ip , hostname , response_time , "" )
68+ ) # Placeholder for MAC
6969 except Exception :
7070 pass
7171
7272
73- # Function to check for open ports
73+ def get_mac_address (ip ):
74+ try :
75+ output = subprocess .check_output (["arp" , "-a" , ip ], universal_newlines = True )
76+ match = re .search (r"(\w{2}-\w{2}-\w{2}-\w{2}-\w{2}-\w{2})" , output )
77+ return match .group (1 ) if match else "N/A"
78+ except Exception :
79+ return "N/A"
80+
81+
7482def check_ports (ip , port , open_ports ):
7583 try :
7684 with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
@@ -81,18 +89,19 @@ def check_ports(ip, port, open_ports):
8189 pass
8290
8391
84- # Parse command-line arguments
8592def parse_arguments ():
8693 parser = argparse .ArgumentParser (
87- description = "Scan network subnet for alive hosts, open ports, and optionally perform reverse DNS lookup ."
94+ description = "Scan network subnet for alive hosts, open ports, and optionally perform reverse DNS or get MAC address ."
8895 )
8996 parser .add_argument (
9097 "--hostname" , help = "Perform reverse DNS lookup" , action = "store_true"
9198 )
99+ parser .add_argument (
100+ "--mac" , help = "Include MAC address in output" , action = "store_true"
101+ )
92102 return parser .parse_args ()
93103
94104
95- # Main function to detect the subnet and scan it
96105def main ():
97106 args = parse_arguments ()
98107 host_ip = get_host_ip ()
@@ -111,12 +120,15 @@ def main():
111120 for t in threads :
112121 t .join ()
113122
114- # Sort the alive hosts numerically
123+ if args .mac :
124+ for i , (ip , hostname , response_time , _ ) in enumerate (alive_hosts ):
125+ mac = get_mac_address (str (ip ))
126+ alive_hosts [i ] = (ip , hostname , response_time , mac )
127+
115128 alive_hosts .sort (key = lambda x : x [0 ])
116129
117- # Launch port checks
118130 port_check_threads = []
119- for host , _ , _ in alive_hosts :
131+ for host , _ , _ , _ in alive_hosts :
120132 for port in [22 , 23 , 25 , 80 , 443 , 2525 , 8443 , 10443 , 10000 , 20000 ]:
121133 t = threading .Thread (target = check_ports , args = (str (host ), port , open_ports ))
122134 t .start ()
@@ -125,28 +137,31 @@ def main():
125137 for t in port_check_threads :
126138 t .join ()
127139
128- # Determine column widths dynamically
129140 max_hostname_length = max (
130- (len (hostname ) for _ , hostname , _ in alive_hosts ), default = 8
141+ (len (hostname ) for _ , hostname , _ , _ in alive_hosts ), default = 8
131142 )
132143 ip_column_width = 16
133- hostname_column_width = max (max_hostname_length , 12 ) + 2 # Minimum width of 12
144+ hostname_column_width = max (max_hostname_length , 12 ) + 2
134145 response_time_column_width = 8
135- ports_column_width = 50 # Static width for ports
146+ mac_column_width = 20 if args .mac else 0
147+ ports_column_width = 50
136148
137- # Print header
138- header = f"{ 'IP' :<{ip_column_width }} { '(ms)' :<{response_time_column_width }} { 'Hostname' :<{hostname_column_width }} { 'Open Ports' :<{ports_column_width }} "
149+ header = f"{ 'IP' :<{ip_column_width }} { '(ms)' :<{response_time_column_width }} { 'Hostname' :<{hostname_column_width }} "
150+ if args .mac :
151+ header += f"{ 'MAC Address' :<{mac_column_width }} "
152+ header += f"{ 'Open Ports' :<{ports_column_width }} "
139153 print (header )
140154 print ("-" * len (header ))
141155
142- # Print results
143- for host , hostname , response_time in alive_hosts :
156+ for host , hostname , response_time , mac in alive_hosts :
144157 ports = sorted (open_ports [str (host )])
145158 ports_str = ", " .join (map (str , ports ))
146159 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- )
160+ line = f"{ str (host ):<{ip_column_width }} { response_time_str :<{response_time_column_width }} { hostname :<{hostname_column_width }} "
161+ if args .mac :
162+ line += f"{ mac :<{mac_column_width }} "
163+ line += f"{ ports_str :<{ports_column_width }} "
164+ print (line )
150165
151166 print (f"\n Total count of alive hosts: { len (alive_hosts )} " )
152167
0 commit comments