Files
ipam/backend/app/services/scan_service.py
T
Your Name 1e1e5957e8 fix(ipam): 修复IP管理页面网段筛选不生效及下拉框过窄
- 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 记录
2026-07-18 09:45:30 +08:00

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