192 lines
6.0 KiB
Python
192 lines
6.0 KiB
Python
#!/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() |