智能目标与扫描器分配¶
服务端根据 GeoIP、扫描器能力、忙闲状态和目标执行反馈选择节点。不需要维护扫描器可达网段。目标授权和排除范围继续控制自动目标与 DNS 后续任务的生成边界。
扫描器分配¶
管理员在「运行时配置 → 智能扫描器分配」设置 scan_routing。首次初始化默认启用:
{
"enabled": true,
"preference_seconds": 15,
"candidate_limit": 64,
"feedback_max_age_hours": 168
}
配置通过现有 GET/POST /api/config/runtime/scan_routing 读取和发布,PostgreSQL 中的文档为权威来源。关闭智能分配后,兼容扫描器按原领取路径执行任务。
automatic 任务使用智能选择;selected 和 all 仍为用户选择的每个扫描器创建完整执行实例。每个扫描器仍只有一个活动任务,扫描计划 v1、90 秒租约和现有结果协议保持兼容。
候选节点必须启用、未删除、接受新任务、心跳有效,并支持任务地址族和扫描模式。指定 DNS 解析器时,还检查对应地址族与显式解析器能力。地理信息分别由扫描器上报的 IPv4/IPv6 出口查询;自由文本位置描述不参与计算。
DNS 分配预览、创建和领取统一沿用 IPv4 任务语义;使用 IPv6 解析器时额外要求 IPv6 可用,不能推荐仅支持 IPv6 的节点。
同地区、距离、预留任务数量和执行质量共同影响排序。无 GeoIP 数据、私有地址或混合地域都标记为未知,继续按能力和状态分配。大型 CIDR 不使用单个地址推断地域;小网段仅进行有限采样,地域始终是软偏好。
服务端后台和领取入口会生成短期优先领取记录。领取入口的规划最多等待两秒,后台每轮最多五秒,超时回滚后沿用已有有效偏好或普通领取路径。记录独立于任务执行租约和用户指定扫描器。到期后进入普通领取窗口,不会无限续期;节点掉线、停止接受任务、能力变化或开始其他任务时也可以回退。恢复已经拥有的活动任务沿用其租约;待执行任务的恢复服从相同分配规则。
创建页的「预览扫描器分配」调用只读 POST /api/task/placement-preview:
{"targets":["192.0.2.10"],"scan_profile":"port_only","port":"443","dns_resolvers":[]}
预览返回目标地域、当前推荐节点和候选原因,最多接受 100 个同地址族的明确目标。实际领取会重新验证状态。多地域 IP 目标会原子创建独立 scan_task 并分配同一个 task_group_id;创建接口返回 data.task_ids 和 data.task_group_id。DNS 子任务也按地址族、地域和目标数量分组,保留原有解析来源与父子关系。任务详情展示任务组及每次执行的分配决定。
智能目标刷新¶
「自动扫描策略」增加 strategy=smart_refresh,原 sequential_subnet 继续可用。开关和授权范围沿用现有配置;IPv4/IPv6 分别维护水位和游标。
智能刷新优先考虑尚未成功、已过期或达到失败重试时间的已跟踪目标,再从授权网段补充首次扫描目标。兼容的待执行或运行任务不会重复生成。每轮只检查有限候选窗口,避免一次遍历大型 IPv6 地址空间。
活动任务按规范化端口集合匹配,端口排序、重复项或等价区间表示不会触发重复扫描。查询刷新候选时就排除兼容的活动任务,避免它们占满有限窗口。
刷新状态按规范化扫描模式与端口签名持久化,包含最近成功时间、失败退避、资产身份摘要和下次刷新时间。删除扫描任务不会删除该状态。成功但发现零开放端口仍会刷新成功时间;失败、取消和未完整确认的上传不会刷新成功时间。
新增参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
repeat_after_seconds |
604800 | 稳定目标的有效期,从成功完成计算 |
failure_backoff_seconds |
300 | 失败重试初始退避,指数增长,上限一天 |
change_refresh_seconds |
3600 | 检测到端口、服务或漏洞模板变化后的提前复核间隔,不超过正常有效期 |
资产摘要在每个新确认的上传批次中增量更新,与回执确认同一事务提交,不查询全量历史结果。摘要不包含观测时间、地理信息或易变 banner。重复回执不会重复更新摘要;完整成功扫描后与上次摘要比较,即使端口总数相同也可以发现端口变化。复核结果稳定后恢复正常有效期。
自动执行反馈¶
新扫描端在完成任务时,对该任务已发现开放的 TCP 端点进行最多八个并行连接样本,每个最多等待 800 ms。采样再次检查目标地址与端口范围,不展开目标;无开放端口或纯 DNS 任务不产生 TCP 样本。可选上报最多等待两秒,旧服务端不支持该接口时继续上传扫描结果。
兼容的 dns_ports / dns_full 任务保留实际扫描时的 A/AAAA 解析结果,即使端口结果流不包含 DNS 记录。报告中的可选 resolved_targets 最多包含八条 {domain, ip},只对应已采样端点;服务端校验原任务域名、连接 IP、端口和总量。该对应关系属于扫描器报告的证据,只有任务成功后参与学习,不作为独立可达性证明。OS 连接超时与 800 ms 采样超时均记为 timeout。
POST /api/scanner/task/performance 使用运行令牌,验证执行归属、有效租约、目标范围及样本上限。第一次有效报告固定到当前执行;只有最终结果成功确认后,才更新节点与目标的质量历史。历史按用户、地址族、扫描模式、端口和规范化目标隔离,过期后不参与排序。
连接成功和连接拒绝都证明收到网络响应,提供连接耗时。单次超时、零开放端口和普通扫描引擎失败不形成不可达判定。明确的路由或源地址错误可以通过失败上报中的 failure_class=route_unavailable 或 network_unavailable 产生短期惩罚,并由现有最多三次领取规则限制重试。反馈只影响排序,不生成永久禁止关系。
任务详情中的目标连接样本独立于心跳 RTT。扫描器到服务端的延迟不被用作目标延迟。
升级与验证¶
正常启动会执行幂等 source/database/migrate_smart_scanning.sql,包括使用旧外部初始化 SQL 的部署。先升级服务端,再升级扫描端与前端;旧扫描端可以继续执行任务,只是不提供目标连接指标。建议升级前备份 PostgreSQL,并沿用已有 GeoLite2 数据库目录。
cargo fmt --check
cargo clippy --workspace --all-targets --locked
cargo test --workspace --locked
STORAGE_TEST_DATABASE_URL='host=/path/to/disposable/socket user=test dbname=test' cargo test --locked --lib scan_control::routing -- --ignored --test-threads=1
数据库测试必须使用可丢弃测试库,覆盖跨节点并发领取、待执行恢复、优先到期、节点停止接单、手动选择、失败切换、指标租约与范围校验、回执幂等、零端口成功、任务删除后的有效期及变化后复核。