#!/usr/bin/env python3 """ AutoNetScan - Quick & Thorough Local Network Device Discovery Designed for Kali Linux Live Boot environments. Requires: arp-scan, nmap, python3 (standard library) Run with: sudo python3 autonet_scan.py """ import subprocess import os import sys import socket import html import json import re import pwd from datetime import datetime def log(msg): print(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}", flush=True) def get_home_dir(): if os.geteuid() == 0 and 'SUDO_USER' in os.environ: try: return pwd.getpwnam(os.environ['SUDO_USER']).pw_dir except KeyError: pass return os.path.expanduser('~') def check_dep(cmd): return subprocess.run(['which', cmd], capture_output=True).returncode == 0 def is_valid_ipv4(ip): """Returns True if the string looks like a valid IPv4 address.""" try: return all(0 <= int(x) <= 255 for x in ip.split('.')) except ValueError: return False def detect_network(): log("🔍 Detecting active network interface and subnet...") res = subprocess.run(['ip', 'route', 'show', 'default'], capture_output=True, text=True) if res.returncode != 0: sys.exit("❌ No default route found. Ensure you are connected to a network.") iface = None for line in res.stdout.splitlines(): parts = line.split() if 'dev' in parts: iface = parts[parts.index('dev') + 1] break if not iface: sys.exit("❌ Could not determine network interface.") addr_res = subprocess.run(['ip', '-4', 'addr', 'show', iface], capture_output=True, text=True) subnet = None local_ips = set() for line in addr_res.stdout.splitlines(): if 'inet' in line and '127.0.0.1' not in line: ip_part = line.strip().split()[1] # e.g., 192.168.15.5/24 subnet = subnet if subnet else ip_part local_ips.add(ip_part.split('/')[0]) if not subnet: sys.exit("❌ Could not determine subnet for the detected interface.") log(f"✅ Detected Interface: {iface} | Subnet: {subnet}") return iface, subnet, local_ips def run_arp_scan(iface): log("📡 Starting ARP scan (Layer 2 device discovery)...") if not check_dep('arp-scan'): log("⚠️ Dependency missing: arp-scan. Install via: sudo apt install arp-scan") return {} env = os.environ.copy() env['LANG'] = 'C' cmd = ['arp-scan', '--localnet', f'--interface={iface}'] try: res = subprocess.run(cmd, capture_output=True, text=True, timeout=120, env=env) if res.returncode != 0: log(f"⚠️ arp-scan exited with code {res.returncode}") except subprocess.TimeoutExpired: log("⏱️ ARP scan timed out.") return {} devices = {} for line in res.stdout.splitlines(): if not line.strip() or 'Interface:' in line or 'MAC address' in line or 'Warning' in line: continue parts = line.split() # Ensure it looks like an IP before adding to dict if len(parts) >= 3 and is_valid_ipv4(parts[0]): ip, mac = parts[0], parts[1] vendor = ' '.join(parts[2:]) devices[ip] = {'ip': ip, 'mac': mac, 'vendor': vendor, 'status': 'up', 'hostname': '', 'method': 'ARP'} log(f"✅ ARP scan completed. Found {len(devices)} device(s).") return devices def run_nmap_sweep(subnet): log("🌐 Starting Nmap ping sweep & DNS lookup...") if not check_dep('nmap'): log("⚠️ Dependency missing: nmap. Install via: sudo apt install nmap") return {} env = os.environ.copy() env['LANG'] = 'C' cmd = ['nmap', '-sn', '-PR', '-T4', '--host-timeout=30s', '--system-dns', subnet] try: res = subprocess.run(cmd, capture_output=True, text=True, timeout=180, env=env) if res.returncode != 0: log(f"⚠️ Nmap exited with code {res.returncode}") except subprocess.TimeoutExpired: log("⏱️ Nmap scan timed out.") return {} devices = {} current_ip = None for line in res.stdout.splitlines(): if 'Nmap scan report for' in line: # Robust IP extraction with validation ip_match = re.search(r'\((\d+\.\d+\.\d+\.\d+)\)', line) if ip_match: current_ip = ip_match.group(1) else: # Fallback: take the last word ONLY if it looks like an IP candidate = line.split()[-1] current_ip = candidate if is_valid_ipv4(candidate) else None if current_ip: devices[current_ip] = {'ip': current_ip, 'mac': '', 'vendor': '', 'status': 'up', 'hostname': '', 'method': 'Nmap'} elif 'Host is up' in line and current_ip: pass # Already marked as up via presence in dict elif 'MAC Address:' in line and current_ip: parts = line.split() if len(parts) > 2: devices[current_ip]['mac'] = parts[2] vendor_parts = parts[3:] devices[current_ip]['vendor'] = ' '.join(vendor_parts).replace('(', '').replace(')', '') log(f"✅ Nmap sweep completed. Found {len(devices)} device(s).") return devices def resolve_hostnames(devices): log("🔎 Resolving hostnames via reverse DNS (1s timeout per host)...") original_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(1) resolved_count = 0 attempted = 0 total = len(devices) for ip in list(devices.keys()): attempted += 1 if devices[ip]['hostname']: pass else: try: hostname, _, _ = socket.gethostbyaddr(ip) devices[ip]['hostname'] = hostname resolved_count += 1 except (socket.herror, socket.gaierror, OSError): devices[ip]['hostname'] = 'N/A' print(f"\r🌐 Resolved {resolved_count}/{attempted} hosts...", end="", flush=True) print("\r✅ DNS resolution complete. ") socket.setdefaulttimeout(original_timeout) def merge_devices(arp_devs, nmap_devs): merged = {} for ip, data in arp_devs.items(): merged[ip] = {**data} for ip, data in nmap_devs.items(): if ip in merged: for key in ['mac', 'vendor', 'hostname']: val = data.get(key) or '' if val and val not in ['', 'N/A'] and 'unknown' not in val.lower(): merged[ip][key] = val if not merged[ip]['method'].startswith('Nmap'): merged[ip]['method'] = 'Nmap' else: merged[ip] = {**data} return merged def print_table(devices): sorted_ips = sorted(devices.keys(), key=lambda x: tuple(map(int, x.split('.')))) header = f"{'IP':<16} {'MAC':<20} {'Vendor':<30} {'Hostname'}" print("\n" + header) print("-" * len(header)) for ip in sorted_ips: d = devices[ip] mac = (d['mac'] or 'N/A')[:19].ljust(20) vendor = (d['vendor'] or 'N/A')[:28].ljust(30) hostname = (d['hostname'] or 'N/A')[:20].ljust(20) print(f"{ip:<16} {mac} {vendor} {hostname}") def generate_html(devices, iface, subnet, start_time): now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S') total = len(devices) sorted_ips = sorted(devices.keys(), key=lambda x: tuple(map(int, x.split('.')))) # ----- HTML rows with copyable IP and MAC ----- rows = [] for idx, ip in enumerate(sorted_ips, 1): d = devices[ip] escaped_ip = html.escape(d['ip']) escaped_mac = html.escape(d['mac'] or 'N/A') escaped_vendor = html.escape(d['vendor'] or 'N/A') escaped_hostname = html.escape(d['hostname'] or 'N/A') escaped_method = html.escape(d['method']) row = f""" {idx} {escaped_ip} {escaped_mac} {escaped_vendor} {escaped_hostname} UP {escaped_method} """ rows.append(row) rows_html = "\n".join(rows) # ----- Text report (for download) ----- sep = "-" * 90 text_lines = [ f"Network Scan Report", f"Scan Date: {now_str}", f"Interface: {iface}", f"Subnet: {subnet}", f"Total Devices: {total}", "", f"{'#':3} {'IP Address':<16} {'MAC Address':<18} {'Vendor':<25} {'Hostname'}{'Discovery':>12}", sep, ] for idx, ip in enumerate(sorted_ips, 1): d = devices[ip] mac = d['mac'] or 'N/A' vendor = d['vendor'] or 'N/A' hostname = d['hostname'] or 'N/A' method = d['method'] text_lines.append( f"{idx:<3} {d['ip']:<16} {mac:<18} {vendor:<25.25} {hostname:<25.25} {method:>12}" ) text_lines.append(sep) text_lines.append("Generated by AutoNetScan • Kali Live Boot Compatible") text_report = "\n".join(text_lines) text_report_js = json.dumps(text_report) return f""" Network Scan Report

🌐 Network Device Discovery Report

Scan Date: {now_str} | Interface: {html.escape(iface)} | Subnet: {html.escape(subnet)} | Total Devices: {total}
Generated by AutoNetScan • Kali Live Boot Compatible
{rows_html}
#IP AddressMAC AddressVendor / ManufacturerHostname / DNSStatusDiscovery Method
""" def main(): if os.geteuid() != 0: sys.exit("❌ This script must be run as root. Use: sudo python3 autonet_scan.py") log("🚀 AutoNetScan - Quick Network Device Discovery") start_time = datetime.now() for dep in ['arp-scan', 'nmap']: if not check_dep(dep): sys.exit(f"❌ Critical dependency missing: {dep}. Install via: sudo apt install {dep}") home_dir = get_home_dir() log(f"📁 Target output directory: {home_dir}") iface, subnet, local_ips = detect_network() arp_devs = run_arp_scan(iface) nmap_devs = run_nmap_sweep(subnet) if not arp_devs and not nmap_devs: log("⚠️ No devices found. Check interface status or local firewall rules.") sys.exit(0) devices = merge_devices(arp_devs, nmap_devs) # Exclude local scanning host IPs excluded = [ip for ip in local_ips if ip in devices] for ip in excluded: del devices[ip] log(f"ℹ️ Excluded local host IP ({ip}) from results.") resolve_hostnames(devices) # Final safety filter before printing/generating report valid_devices = {k: v for k, v in devices.items() if is_valid_ipv4(k)} if len(valid_devices) != len(devices): log("⚠️ Removed invalid device entries from report.") print_table(valid_devices) output_file = os.path.join(home_dir, 'network_scan_report.html') html_content = generate_html(valid_devices, iface, subnet, start_time) with open(output_file, 'w', encoding='utf-8') as f: f.write(html_content) log(f"✅ Report saved to: {output_file}") log("🌐 Open it in any web browser. Scanning complete.") if __name__ == '__main__': main()