#!/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"""
{escaped_ip}{escaped_mac}| # | IP Address | MAC Address | Vendor / Manufacturer | Hostname / DNS | Status | Discovery Method |
|---|