#!/usr/bin/env python # -*- coding: utf-8 -*- import subprocess import platform import ipaddress from concurrent.futures import ThreadPoolExecutor from tabulate import tabulate import colorama from colorama import Fore, Style # 初始化颜色设置 colorama.init(autoreset=True) def ping_ip(ip): """Ping单个IP地址,返回是否可达""" param = '-n' if platform.system().lower() == 'windows' else '-c' timeout = '-w' if platform.system().lower() == 'windows' else '-W' command = ['ping', param, '1', timeout, '500', str(ip)] try: response = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=1) return ip, response.returncode == 0 except subprocess.TimeoutExpired: return ip, False def parse_ip_range(ip_range): """解析IP范围字符串(如10.10.0.1 或 10.10.0.1-10.10.0.255)""" # 如果只输入了一个IP if '-' not in ip_range: # 提取网络前缀 ip_parts = ip_range.split('.') if len(ip_parts) != 4: raise ValueError("请输入有效的IPv4地址") # 构建C类网段范围 start_ip = ip_range end_ip = f"{ip_parts[0]}.{ip_parts[1]}.{ip_parts[2]}.255" print(f"自动扩展扫描范围: {start_ip} 到 {end_ip}") else: start_ip, end_ip = ip_range.split('-') start = ipaddress.ip_address(start_ip) end = ipaddress.ip_address(end_ip) # 提取网络前缀 prefix = '.'.join(str(start).split('.')[:-1]) + '.' ip_list = [] current = start while current <= end: ip_list.append(current) current += 1 return ip_list, prefix def ip_to_int(ip): """将IP地址转换为整数用于排序""" return int(ipaddress.ip_address(ip)) def scan_network(ip_range): """扫描IP范围并返回结果""" try: ip_list, prefix = parse_ip_range(ip_range) except Exception as e: print(f"{Fore.RED}错误: {e}{Style.RESET_ALL}") return None, None results = [] print(f"扫描中: {len(ip_list)} 个IP地址...") with ThreadPoolExecutor(max_workers=100) as executor: futures = [executor.submit(ping_ip, ip) for ip in ip_list] for i, future in enumerate(futures): ip, status = future.result() results.append((str(ip), status)) # 显示进度 if (i + 1) % 10 == 0 or (i + 1) == len(ip_list): online_count = sum(1 for _, status in results if status) offline_count = sum(1 for _, status in results if not status) # 使用多行字符串避免语法错误 progress_msg = ( f"\r进度: {i + 1}/{len(ip_list)} | " f"{Fore.GREEN}在线: {online_count}{Style.RESET_ALL} | " f"{Fore.RED}离线: {offline_count}{Style.RESET_ALL}" ) print(progress_msg, end='') print("\n扫描完成!") return results, prefix def display_results(results, prefix): """以紧凑格式显示结果,每行20个IP""" # 按IP地址排序 sorted_results = sorted(results, key=lambda x: ip_to_int(x[0])) # 提取IP最后一段和状态 compact_results = [] for ip, status in sorted_results: host_part = ip.split('.')[-1] compact_results.append((host_part, status)) # 计算统计信息 online_count = sum(1 for _, status in results if status) offline_count = sum(1 for _, status in results if not status) total_count = len(results) # 显示结果 print("\n" + "=" * 80) summary = ( f"扫描结果摘要: {prefix}0/24 | 总计 {total_count} 个IP | " f"{Fore.GREEN}在线: {online_count}{Style.RESET_ALL} | " f"{Fore.RED}离线: {offline_count}{Style.RESET_ALL}" ) print(summary) print("=" * 80 + "\n") # 准备表格数据 - 每行20个IP table_data = [] # 将数据分组,每组20个IP for i in range(0, len(compact_results), 20): row = compact_results[i:i + 20] table_row = [] for host, status in row: # 使用ASCII字符替代Unicode图标 if status: display_text = f"{Fore.GREEN}{host}*{Style.RESET_ALL}" # 使用星号*表示在线 else: display_text = f"{Fore.RED}{host}x{Style.RESET_ALL}" # 使用x表示离线 table_row.append(display_text) # 填充不足20个的行 while len(table_row) < 20: table_row.append("") table_data.append(table_row) # 输出表格(不显示列号标题) print(tabulate(table_data, tablefmt="simple_grid", stralign="center")) # 添加图例说明 print(f"\n图例: {Fore.GREEN}* 在线{Style.RESET_ALL} | {Fore.RED}x 离线{Style.RESET_ALL}") print(f"注: 数字表示IP地址最后一段,例如 '1*' 表示 {prefix}1 在线") def show_menu(): """显示菜单选项""" print("\n" + "=" * 60) print(f"{Fore.CYAN}IP网段扫描工具{Style.RESET_ALL}") print("=" * 60) print("功能说明:") print(f" - 输入单个IP (如 {Fore.YELLOW}10.10.0.1{Style.RESET_ALL}) 将自动扫描整个网段") print(f" - 输入IP范围 (如 {Fore.YELLOW}10.10.0.1-10.10.0.100{Style.RESET_ALL}) 扫描指定范围") print(f" - 输入 {Fore.RED}0{Style.RESET_ALL} 退出程序") print("=" * 60) def main(): while True: show_menu() ip_range = input("\n请输入要扫描的IP地址或范围: ").strip() # 退出条件 if ip_range == '0': print(f"{Fore.YELLOW}感谢使用,程序已退出!{Style.RESET_ALL}") break # 验证输入是否为空 if not ip_range: print(f"{Fore.RED}错误: 输入不能为空{Style.RESET_ALL}") continue # 执行扫描 results, prefix = scan_network(ip_range) # 显示结果(如果扫描成功) if results and prefix: display_results(results, prefix) if __name__ == "__main__": main()