#!/usr/bin/env python
# -*- coding: utf-8 -*-"""
IP端口掃描程序
輸入IP地址,掃描該IP哪些端口對外是開放的,輸出端口列表
"""import socket
import sys
import concurrent.futures
import ipaddress
from tabulate import tabulate
import timedef is_valid_ip(ip):"""驗證IP地址是否有效"""try:ipaddress.ip_address(ip)return Trueexcept ValueError:return Falsedef scan_port(ip, port, timeout=1):"""掃描單個端口是否開放參數:ip (str): 目標IP地址port (int): 要掃描的端口timeout (float): 連接超時時間(秒)返回:bool: 如果端口開放返回True,否則返回False"""try:# 創建TCP套接字sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.settimeout(timeout)# 嘗試連接result = sock.connect_ex((ip, port))# 關閉套接字sock.close()# 如果連接成功,端口是開放的return result == 0except (socket.error, socket.timeout, OSError):return Falsedef scan_ports(ip, port_range=None, max_workers=100):"""掃描IP地址的多個端口參數:ip (str): 目標IP地址port_range (tuple): 端口范圍,格式為(起始端口, 結束端口)max_workers (int): 最大并發線程數返回:list: 開放端口列表"""if port_range is None:# 默認掃描常見端口port_range = (1, 1024)start_port, end_port = port_rangeopen_ports = []total_ports = end_port - start_port + 1print(f"開始掃描 {ip} 的端口 {start_port}-{end_port}...")start_time = time.time()# 使用線程池進行并發掃描with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:# 創建掃描任務future_to_port = {executor.submit(scan_port, ip, port): port for port in range(start_port, end_port + 1)}# 處理完成的任務completed = 0for future in concurrent.futures.as_completed(future_to_port):port = future_to_port[future]completed += 1# 顯示進度if completed % 100 == 0 or completed == total_ports:progress = (completed / total_ports) * 100elapsed = time.time() - start_timeprint(f"進度: {completed}/{total_ports} ({progress:.1f}%) - 已用時間: {elapsed:.1f}秒", end="\r")try:if future.result():open_ports.append(port)print(f"\n發現開放端口: {port}")except Exception as e:print(f"\n掃描端口 {port} 時出錯: {e}")print(f"\n掃描完成! 總用時: {time.time() - start_time:.1f}秒")return open_portsdef display_open_ports(ip, open_ports):"""顯示開放端口列表"""if not open_ports:print(f"\n{ip} 沒有發現開放的端口")return# 嘗試獲取常見端口的服務名稱port_info = []for port in sorted(open_ports):try:service = socket.getservbyport(port)except (socket.error, OSError):service = "未知"port_info.append([port, service])# 顯示表格print(f"\n{ip} 的開放端口:")headers = ["端口", "可能的服務"]print(tabulate(port_info, headers=headers, tablefmt="grid"))def main():"""主函數"""# 獲取用戶輸入if len(sys.argv) > 1:target_ip = sys.argv[1]else:target_ip = input("請輸入要掃描的IP地址: ")# 驗證IP地址if not is_valid_ip(target_ip):print(f"錯誤: '{target_ip}' 不是有效的IP地址")sys.exit(1)# 獲取端口范圍try:custom_range = input("請輸入要掃描的端口范圍 (格式: 起始端口-結束端口) [默認: 1-1024]: ")if custom_range:start, end = map(int, custom_range.split('-'))if start < 1 or end > 65535 or start > end:raise ValueErrorport_range = (start, end)else:port_range = (1, 1024)except ValueError:print("錯誤: 無效的端口范圍,使用默認范圍 1-1024")port_range = (1, 1024)# 掃描端口open_ports = scan_ports(target_ip, port_range)# 顯示結果display_open_ports(target_ip, open_ports)if __name__ == "__main__":try:main()except KeyboardInterrupt:print("\n\n掃描被用戶中斷")sys.exit(0)except Exception as e:print(f"\n程序執行出錯: {e}")sys.exit(1)