1e1e5957e8
- Ips.vue: el-select 添加 width: 280px,修复网段下拉框过窄 - Ips.vue: query 参数 networkId → network_id(与 FastAPI snake_case 一致) - Scan.vue: el-select 用 network.id 代替整个 network 对象作为 value,修复切换网段不生效 - enhanced_scan_service.py: 替换失效的 scapy ARP 扫描为 arp-scan 命令;扫描结果自动创建缺失 IP 记录
149 lines
4.6 KiB
Python
149 lines
4.6 KiB
Python
from typing import List, Dict, Any, Optional
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime
|
|
import asyncio
|
|
import subprocess
|
|
import ipaddress
|
|
|
|
from app.models.network import ScanTask, Network, IPAddress
|
|
from app.models.network import TaskStatus, TaskType, IPStatus
|
|
from app.core.config import settings
|
|
|
|
|
|
class ScanService:
|
|
"""扫描服务"""
|
|
|
|
@staticmethod
|
|
def ping_single_ip(ip_address: str, timeout: int = 2) -> Dict[str, Any]:
|
|
"""
|
|
对单个IP执行Ping扫描
|
|
"""
|
|
try:
|
|
result = subprocess.run(
|
|
['ping', '-c', '1', '-W', str(timeout), ip_address],
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
is_alive = result.returncode == 0
|
|
|
|
return {
|
|
'ip_address': ip_address,
|
|
'status': 'online' if is_alive else 'offline',
|
|
'response_time': None,
|
|
'success': is_alive
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'ip_address': ip_address,
|
|
'status': 'error',
|
|
'error': str(e),
|
|
'success': False
|
|
}
|
|
|
|
@staticmethod
|
|
def ping_network(cidr: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
对整个网段执行Ping扫描(同步版本)
|
|
"""
|
|
network = ipaddress.ip_network(cidr, strict=False)
|
|
ips = [str(ip) for ip in network.hosts()]
|
|
|
|
results = []
|
|
for ip in ips:
|
|
result = ScanService.ping_single_ip(ip, settings.PING_TIMEOUT)
|
|
results.append(result)
|
|
|
|
return results
|
|
|
|
@staticmethod
|
|
def create_scan_task(db: Session, task_type: TaskType,
|
|
network_id: Optional[int] = None) -> ScanTask:
|
|
"""
|
|
创建扫描任务
|
|
"""
|
|
task = ScanTask(
|
|
task_type=task_type,
|
|
network_id=network_id,
|
|
status=TaskStatus.PENDING,
|
|
progress=0
|
|
)
|
|
db.add(task)
|
|
db.commit()
|
|
db.refresh(task)
|
|
return task
|
|
|
|
@staticmethod
|
|
def update_task_status(db: Session, task_id: int, status: TaskStatus,
|
|
progress: int = None, total_count: int = None,
|
|
success_count: int = None, failed_count: int = None,
|
|
error_message: str = None):
|
|
"""
|
|
更新任务状态
|
|
"""
|
|
task = db.query(ScanTask).filter(ScanTask.id == task_id).first()
|
|
if not task:
|
|
return
|
|
|
|
task.status = status
|
|
if progress is not None:
|
|
task.progress = progress
|
|
if total_count is not None:
|
|
task.total_count = total_count
|
|
if success_count is not None:
|
|
task.success_count = success_count
|
|
if failed_count is not None:
|
|
task.failed_count = failed_count
|
|
if error_message:
|
|
task.error_message = error_message
|
|
|
|
if status == TaskStatus.RUNNING and task.started_at is None:
|
|
task.started_at = datetime.utcnow()
|
|
elif status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
|
|
task.completed_at = datetime.utcnow()
|
|
|
|
db.commit()
|
|
|
|
@staticmethod
|
|
def get_task_by_id(db: Session, task_id: int) -> Optional[ScanTask]:
|
|
"""
|
|
获取任务信息
|
|
"""
|
|
return db.query(ScanTask).filter(ScanTask.id == task_id).first()
|
|
|
|
@staticmethod
|
|
def get_task_list(db: Session, skip: int = 0, limit: int = 50) -> tuple[int, List[ScanTask]]:
|
|
"""
|
|
获取任务列表
|
|
"""
|
|
query = db.query(ScanTask)
|
|
total = query.count()
|
|
items = query.order_by(ScanTask.id.desc()).offset(skip).limit(limit).all()
|
|
return total, items
|
|
|
|
@staticmethod
|
|
def update_ip_status_from_scan_results(db: Session, results: List[Dict[str, Any]]):
|
|
"""
|
|
根据扫描结果更新IP状态
|
|
"""
|
|
online_count = 0
|
|
offline_count = 0
|
|
|
|
for result in results:
|
|
ip_address = result.get('ip_address')
|
|
is_online = result.get('success', False)
|
|
|
|
db_ip = db.query(IPAddress).filter(IPAddress.ip_address == ip_address).first()
|
|
if db_ip:
|
|
if is_online:
|
|
db_ip.status = IPStatus.ONLINE
|
|
db_ip.last_seen = datetime.utcnow()
|
|
online_count += 1
|
|
elif db_ip.status == IPStatus.ONLINE:
|
|
# 曾经在线,现在不在线,标记为离线
|
|
db_ip.status = IPStatus.OFFLINE
|
|
offline_count += 1
|
|
|
|
db.commit()
|
|
return online_count, offline_count
|