Files
ipam/backend/app/api/v1/enhanced_scan.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

210 lines
6.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Dict, Any, Optional
from app.core.database import get_db
from app.core.security import get_current_user
from app.schemas.network import ScanTask, IPAddress
from app.models.network import TaskType, TaskStatus
from app.services.network_service import NetworkService
from app.services.enhanced_scan_service import EnhancedScanService
from app.services.scan_service import ScanService
router = APIRouter(
prefix="/scan",
tags=["增强扫描"],
dependencies=[Depends(get_current_user)],
)
@router.post("/comprehensive/{network_id}", response_model=ScanTask, summary="综合扫描网段")
def comprehensive_scan(
network_id: int,
background_tasks: BackgroundTasks,
enable_ping: bool = True,
enable_arp: bool = True,
enable_dns: bool = True,
timeout: int = 2,
db: Session = Depends(get_db)
):
"""
对指定网段执行综合扫描
- Ping 扫描检测在线状态
- ARP 扫描获取MAC地址
- 反向DNS解析获取主机名
- MAC厂商识别
"""
network = NetworkService.get_by_id(db, network_id)
if not network:
raise HTTPException(status_code=404, detail="网段不存在")
# 创建扫描任务
task = ScanService.create_scan_task(db, TaskType.PING, network_id=network_id)
try:
# 立即执行扫描(后续改为Celery异步)
ScanService.update_task_status(db, task.id, TaskStatus.RUNNING, progress=0)
# 执行综合扫描
results = EnhancedScanService.scan_network(
network.cidr,
enable_ping=enable_ping,
enable_arp=enable_arp,
enable_dns=enable_dns,
timeout=timeout
)
# 更新IP状态
stats = EnhancedScanService.bulk_update_ips_from_scan(db, results)
# 刷新网段统计字段(used_ips/reserved_ips),保证网段列表利用率能即时反映
NetworkService.get_stats(db, network_id)
# 更新任务状态
ScanService.update_task_status(
db,
task.id,
TaskStatus.COMPLETED,
progress=100,
total_count=stats['total_scanned'],
success_count=stats['online_count']
)
except Exception as e:
ScanService.update_task_status(
db,
task.id,
TaskStatus.FAILED,
error_message=str(e)
)
raise HTTPException(status_code=500, detail=f"扫描失败: {str(e)}")
# 重新获取任务状态
task = ScanService.get_task_by_id(db, task.id)
return task
@router.post("/comprehensive/ip/{ip_address}", summary="单个IP综合扫描")
def comprehensive_scan_ip(
ip_address: str,
enable_ping: bool = True,
enable_arp: bool = True,
enable_dns: bool = True,
timeout: int = 2,
db: Session = Depends(get_db)
):
"""
对单个IP执行综合扫描
"""
result = EnhancedScanService.comprehensive_scan(
ip_address,
enable_ping=enable_ping,
enable_arp=enable_arp,
enable_dns=enable_dns,
timeout=timeout
)
# 如果IP在数据库中,更新信息
EnhancedScanService.update_ip_from_scan_result(db, result)
# 刷新所属网段统计字段(used_ips/reserved_ips),保证网段列表利用率能即时反映
db_ip = db.query(IPAddress).filter(IPAddress.ip_address == ip_address).first()
if db_ip:
NetworkService.get_stats(db, db_ip.network_id)
db.commit()
return result
@router.get("/mac/vendor/{mac_address}", summary="MAC地址厂商识别")
def get_mac_vendor(mac_address: str):
"""
根据MAC地址识别厂商
"""
vendor = EnhancedScanService.get_mac_vendor(mac_address)
return {
"mac_address": mac_address,
"vendor": vendor or "Unknown"
}
@router.post("/dns/reverse/{ip_address}", summary="反向DNS解析")
def reverse_dns(ip_address: str, timeout: int = 2):
"""
反向DNS解析获取主机名
"""
hostname = EnhancedScanService.reverse_dns_lookup(ip_address, timeout)
return {
"ip_address": ip_address,
"hostname": hostname or "Unknown"
}
@router.get("/online/ips", summary="获取所有在线IP列表")
def get_online_ips(
network_id: Optional[int] = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""
获取所有在线的IP地址列表
"""
from app.models.network import IPAddress as IPAddressModel, IPStatus
query = db.query(IPAddressModel).filter(IPAddressModel.status == IPStatus.ONLINE)
if network_id:
query = query.filter(IPAddressModel.network_id == network_id)
total = query.count()
items = query.order_by(IPAddressModel.ip_address).offset(skip).limit(limit).all()
return {
"total": total,
"online_count": total,
"items": items
}
@router.get("/statistics/summary", summary="扫描统计摘要")
def get_scan_statistics(db: Session = Depends(get_db)):
"""
获取扫描统计摘要信息
"""
from app.models.network import IPAddress as IPAddressModel, IPStatus
from app.models.network import ScanTask, TaskStatus
# IP 统计
total_ips = db.query(IPAddressModel).count()
online_ips = db.query(IPAddressModel).filter(IPAddressModel.status == IPStatus.ONLINE).count()
offline_ips = db.query(IPAddressModel).filter(IPAddressModel.status == IPStatus.OFFLINE).count()
reserved_ips = db.query(IPAddressModel).filter(IPAddressModel.status == IPStatus.RESERVED).count()
with_mac = db.query(IPAddressModel).filter(IPAddressModel.mac_address.isnot(None)).count()
with_hostname = db.query(IPAddressModel).filter(IPAddressModel.hostname.isnot(None)).count()
# 任务统计
total_tasks = db.query(ScanTask).count()
completed_tasks = db.query(ScanTask).filter(ScanTask.status == TaskStatus.COMPLETED).count()
failed_tasks = db.query(ScanTask).filter(ScanTask.status == TaskStatus.FAILED).count()
return {
"ip_statistics": {
"total": total_ips,
"online": online_ips,
"offline": offline_ips,
"reserved": reserved_ips,
"available": total_ips - online_ips - offline_ips - reserved_ips,
"with_mac": with_mac,
"with_hostname": with_hostname,
"online_rate": round(online_ips / total_ips * 100, 2) if total_ips > 0 else 0
},
"task_statistics": {
"total": total_tasks,
"completed": completed_tasks,
"failed": failed_tasks,
"success_rate": round(completed_tasks / total_tasks * 100, 2) if total_tasks > 0 else 0
}
}