Files
crossdesk/docs/p2p-punching-implementation-plan.md
T

59 KiB
Raw Blame History

CrossDesk 双端 UDP 打洞增强实现方案

状态:P0 门槛已通过;P1–P4 默认关闭原型、受控运行与跨平台构建已实现;真实异网 C 组已有两次增强直连通过双端日志及抓包核验,正式 P5 对照仍待执行。按用户要求,画面、声音、控制和切换质量不纳入本次验收。日期:2026-09-14。第 15 节保留 P0 历史记录,最新进展与边界见第 16 节。

源码基线:CrossDesk 73e3bf709d7c054df7bd75cfafceda0721c6cda1;MiniRTC 子模块 f169a696cb4e6cb59037ec02e85532e30d3c1262;libnice 0.1.24 加仓库已有补丁。

本文规定第一版的范围、接口职责、协议、资源预算、开发顺序和验收条件。第 1–14 节以设计基线为基础,第 6 节同步当前实验参数;调整理由、实际接口及完成情况以第 15–16 节和所链接的执行报告为准。实现和受控测试不代表真实 NAT 成功率已得到验证。

1. 目标与取证依据

目标是在普通 ICE 检查未建立直连、TURN 已经可以传输的情况下,通过“固定端点一侧扩大目的端口探索+另一侧使用多个本地 UDP 端点”寻找新的可达组合,再接入已有 ICE 检查及中继转直连流程。

2026-09-13 两次连接提供了以下依据:

项目 CrossDesk 16:06 会话 UU 21:30 会话
Mac 的直接探测 同一个本地端口,向 11 个公网端口发出 324 个 Binding Request,含重传 同一个本地端口,向 231 个离散目的端口发送短探测包
Windows 的直接探测 主 ICE 端口向 Mac 公网候选持续检查,双方未捕获到直接入站 两个常规端点检查失败后,49 个不同本地源端口轮流向 Mac 固定公网端点发短包
成功组合 没有观察到 Windows 第 46 个短包端点与 Mac 建立双向通信,随后 STUN 成功并持续传输
成功端点是否继续传输 不适用 Windows :52257,经 NAT 为 :11968,后续沿用该端点

Mac 先前发往 :11968 的短包没有出现在 Windows 捕获中;成功开始于 Windows 自己发出的短包到达 Mac。无需把两个固定载荷短包解释成请求与回显,才能说明双方端口探索的作用。

证据边界:

  • 231、49 是成功附近实际执行的数量,不是已知的 UU 配置上限。
  • 抓包证明多个源端口参与,没有证明所有历史 socket 句柄同时存活,也没有恢复 UU 的端口生成算法。
  • NAT 内部建表和过滤过程仍是推断;本方案不宣称复刻 UU 私有实现。
  • 两次应用运行时间、socket 和服务端不同,不能从这两个样本计算成功率提升幅度。
  • 详细原始取证位于本次任务的本地分析产物;实现与验收必须能够独立于 /tmp 文件运行,不把抓包或会话密钥提交到仓库。

2. 第一版范围和关键决策

2.1 纳入范围

  • 原生 MiniRTC ↔ 原生 MiniRTC;首先验证 Windows x64 与 macOS arm64,Linux 用于编译和可控网络测试。
  • IPv4 UDP,单个 ICE component,选择一个明确的物理接口和原始 host socket。
  • 非强制中继、非 reliable ICE;已有选中路径包含 relay,且 DTLS 握手和远端指纹验证已经成功。
  • 本轮一侧具有同一 socket 对多个 STUN 目标的稳定公网映射观测,另一侧具有目的相关映射观测。
  • 每个 ICE generation 至多执行一轮增强;失败继续使用原连接。

2.2 暂不纳入

  • 浏览器/libdatachannel 对端、iOS 启用、IPv6 探索、两侧都目的相关或两侧信息不足时的通用算法。
  • TURN 尚未建立时的首次连接加速、TurnDisabled 下的增强协商、未启用 SRTP/DTLS 的会话。
  • UPnP/PCP/NAT-PMP 的新实现、网关配置修改、跨多个接口同时建立端口池。
  • 根据 UU 单个样本自动学习端口分布,或把端口枚举成全部普通 ICE 候选。
  • 新增直连失效后无缝回切 TURN 的完整恢复算法。第一版遵循当前恢复行为,验证失败恢复;不得把“仍保留 TURN allocation”描述为已经具备自动回切能力。

2.3 架构决策

  1. 先验证 socket 接入,再实现大范围探测。 新端点在中继 READY 后必须能参与 ICE 并成为媒体端点,这是 P0 的前置验收项。
  2. SDP 只协商能力,运行时协调复用已选中路径。 第一版通过现有 relay 数据路径传递带认证的内部控制包,不新增 WSS 消息类型,也不要求修改 CrossDesk Server。
  3. 新 socket 由 libnice 管理。 不在 GUI、业务数据通道或独立线程中建立脱离 ICE 生命周期的临时 socket 池。
  4. 探索和 ICE 检查分开调度。 数百个目的端口属于有限的探索集合;只有少量命中端点进入普通检查表。
  5. 保留普通 ICE 与已有 30 秒升级窗口。 增强操作不能重置该窗口、重启 DTLS,或延迟正常的中继可用状态。
  6. 默认关闭。 编译扩展存在、运行配置开启、双方声明支持、网络和连接状态满足条件时才启动。

这会使第一版主要改善“已通过中继连接,但希望随后升为 P2P”的场景。将来需要在没有中继时运行同类算法,应另行设计可验证的信令协调通道。

3. 现有代码与拟增加模块

位置 当前职责 / 后续变更
ice_utils.h 已有精确匹配 SDP 能力行与 ufrag 解析;新增独立 v1 能力,不改变旧能力含义
nat_traversal.h 已有映射观测和附近端口预测;提取带 socket/generation 的快照,新增角色选择和有界采样逻辑
ice_agent.h/.cpp 持有 NiceAgent、GMainContext 和 DTLS;接入协调器、验证后的 exporter、内部控制包分流和停止流程
ice_transport.cpp 已有选路日志、trickle 和媒体入口;防止内部端点误发普通候选,新增结果统计,保持媒体状态连续
MiniRTC xmake.lua 注册可选的纯逻辑测试和 ICE 集成测试目标,接入编译开关
libnice 配方 注册新补丁、哈希及包身份版本,增加扩展符号检查,确保双端重新构建依赖
relay upgrade 补丁 已有保留直连检查、定期重试及再次提名;扩展接入点须遵守已有截止时间和优先级条件

拟新增到 MiniRTC 的模块,路径以子模块根目录为基准:

src/ice/punch_config.h                 配置、硬上限、实验模式
src/ice/punch_protocol.h/.cpp          编解码、认证、去重、generation 校验
src/ice/punch_scheduler.h/.cpp         端口采样、预算、时序;可注入时钟和随机源
src/ice/punch_coordinator.h/.cpp       每个 IceAgent 的增强状态机
src/ice/punch_budget.h/.cpp            进程级 socket/发包配额
thirdparty/libnice/patches/punch_pool_0.1.24.patch
tests/ice/punch_*                      纯逻辑和集成测试
tests/ice/lab/                         可控 NAT 场景与抓包核验脚本

MiniRTC 和 CrossDesk 为不同 Git 仓库。后续先在子模块形成可验证的提交,再更新主仓库的子模块指针;不得只修改本机 Xmake 下载缓存中的 libnice 源码。

4. 能力、身份和角色协商

4.1 能力开关

拟新增精确 SDP 行:

a=x-minirtc-udp-punch:1

只有本地构建包含对应 libnice 扩展、运行配置开启、原生 UDP 路径允许时才通告。双方同时支持该能力和现有 relay-upgrade 能力才启用。旧的 a=x-minirtc-p2p-enhancement:1 继续表示原有增强能力,不隐式包含端口池协议。

SDP 的能力行不是命令;资源数量由本地配置和认证后的协商共同限制。未知版本或异常重复能力声明只停用此增强,不使普通 ICE 失败。

现有 IceTransport::SetRemoteSdp 会提取媒体段,并显式补回旧增强能力行;新增能力也必须经过该路径保留。需要测试能力位于 session、视频或其他媒体段,以及 fingerprint 提取前后的情况,避免完整 SDP 中支持、传入 IceAgent 后却丢失。

4.2 连接和轮次身份

  • 使用有序的 offer/answer ufrag 对标识 ICE generation;记录本地单调递增 generation 序号,防止异步旧回调误用新连接。
  • 线上 generation_id 为长度前缀编码的 offer/answer ufrag 对的 SHA-256 前 16 字节;实际认证密钥另由本次已验证的 DTLS 会话导出。
  • 原始 offer 发送方担任协调方,分配随机 128-bit round_id。协调角色不等于动态 ICE controlling 角色。
  • 所有控制包、探测包、候选提升请求和回调必须携带 generation/round 范围。重连、ICE restart、接口改变时旧范围作废。

4.3 网络角色

命名:anchor 表示复用固定 socket 的一侧;pool 表示使用额外本地端点的一侧。角色根据观测选择,不与 Windows/macOS、主控/被控绑定。

有效映射快照至少包含:同一原始 host socket 的不透明 ID、本地地址、候选 foundation、component、generation、目标 STUN 地址、公网结果、接收的单调时间、观测序号。

  • stable-observed:当前 generation 的同一 socket 至少对两个不同公网服务器 IP 得到同一公网 IP:port,观测仍在有效期内。
  • dependent-observed:同一 socket 对不同目标出现不同映射;不能把多个本地源端口的结果混在一起分类。
  • unknown:不足、过期、来源不一致,或公网 IP 本身变化。

第一版只在恰好一侧 stable-observed、另一侧 dependent-observed 时开始。前者为 anchor。其余组合记录跳过原因,保留常规 ICE;实验工具可显式固定角色验证接入,但不得在生产中把固定角色伪装成自动分类。

快照默认有效期 15 秒。角色选择只表示实验条件,不把有限的 STUN 观测升级为对 NAT 过滤类型或未来分配行为的保证。anchor 的公网端点必须与被保留的那个真实 socket 对应;不能使用无关联的临时 STUN socket 的结果。

5. 控制通道与报文契约

5.1 通道和密钥

控制包通过 NiceAgent 当前选中的 relay 路径发送,在 IceAgent::OnNiceRecv 中、DTLS/RTP/SRTP 判断之前识别和消费;不送入桌面业务协议,也不占用媒体 SSRC。

直接探测包在被借用的 anchor socket 或 libnice 管理的 pool socket 上发送,使用相同的会话范围,但使用独立的密钥域。原始 socket 收包钩子只接收直接路径的探测种类;TURN 解封装后的逻辑来源不能产生“直连命中”事件。

密钥方案:

  1. 新增 dtls_peer_verified 状态,仅在握手完成且远端 fingerprint 比较成功后设置。当前 dtls_handshake_done_ 在 fingerprint 判断之前赋值,不能单独作为导出增强密钥的条件。
  2. 在 DTLS 所属上下文内调用 SSL_export_keying_material,使用独立标签 EXPORTER-MiniRTC-UDP-Punch-v1,context 使用上节有序 ufrag 编码,use_context=1。
  3. 导出 128 字节,依次拆为四个 32-byte 密钥:offer→answer 控制、answer→offer 控制、offer→answer 探测、answer→offer 探测。不得复用 SRTP 密钥或把密钥写入 SDP/日志。
  4. 报文采用 HMAC-SHA-256 前 16 字节认证完整头和载荷,常量时间比较。控制内容只含连接元数据,不承载媒体或密钥;它是带认证的内部数据报,不宣称因走 relay 就自动被 DTLS 加密。

5.2 固定头

采用显式序列化,整数为网络字节序,不直接发送 C/C++ struct。

字段 长度 约束
magic 4 f3 4d 50 31,避免与常见 STUN、DTLS、RTP 首字节范围混淆
version 1 1
kind 1 见下表,控制与探测种类严格分离
payload_length 2 总数据报必须恰好为 44 + payload_length + 16 字节
generation_id 16 与当前连接一致
round_id 16 HELLO 阶段为全零;其他消息为已接受轮次
sequence 4 按方向和密钥域递增,重传保持原序号与内容
payload 可变 v1 控制采用 UTF-8 JSON;直接探测采用固定二进制载荷
tag 16 HMAC 截断值

控制包总长上限 1,200 字节,直接 PROBE/ACK 总长不超过 128 字节。先校验长度、版本、范围、认证,再解析业务字段;不支持分片重组。未知类型、越界数字、重复 JSON 字段、尾随内容均拒绝。

控制 JSON 使用受限、版本化的字段集;HMAC 对线上原始字节计算,接收端不重新序列化后验签。v1 禁止未知字段,后续增加字段需升级协议契约或显式定义可选字段。

5.3 消息和顺序

kind 作用与必须字段
HELLO (0x01) 本地观测快照、候选锚点、运行预算、剩余升级时间;快照只发送分类所需的少量样本
PREPARE (0x02) 协调方发送 round、角色、双方端点、配置摘要和本轮时长;不能授权任意第三方 IP
READY (0x03) 接收方已保留原始 socket 或分配所需 pool socket,返回实际预算与 PREPARE 摘要
START (0x04) 协调方确认开始;双方按本地 monotonic clock 运行,不传递绝对系统时钟作为启动依据
STARTED (0x05) 确认 START 已接受;重复 START 不重置计时器
RESULT (0x06) 本端已选中直连,包含结果和地址摘要;该消息本身不能改变对端 selected pair
CANCEL (0x07) 超时、状态变化、资源不足或主动停止;对端同样有本地截止时间
PROBE (0x20) 直接包;载荷为网络字节序 uint64 socket_id,随后为 12-byte probe_nonce,共 20 字节
PROBE_ACK (0x21) 直接包;载荷原样返回 PROBE 的 socket_id 和 nonce,由接收它的同一个 socket 发回

协调方通过已有能力与 HELLO 判定条件后发 PREPARE;双方准备完成后执行 START/STARTED。若协商出的实际预算改变,更新 PREPARE 并重新 READY,不在一端静默扩大或缩减配置。

控制状态消息按 200/400/800 毫秒退避,最多重发 3 次;使用本轮总截止时间约束。每种消息响应幂等,重复 READY/START 不重复分配 socket,也不重启轮次。乱序消息只能在对应状态允许的范围内缓存或拒绝,不能越级启动。

每方向、每密钥域保留 256 项序号重放窗口,允许正常乱序;已处理的控制请求可重新发送缓存响应,不能重新执行副作用。探测 ACK 必须匹配本轮尚有效的 nonce 和本地 socket_id。未认证包不回复。

上述直接 socket_id 是本地注册表的不透明标识,不是 OS 文件描述符;不得由远端指定本地 FD。

6. 探测算法与预算

6.1 anchor 的行为

  1. 保留与稳定公网观测对应的实际 host socket,沿用当前物理接口绑定。
  2. 在已确认的对端公网 IP 上,从 1024–65535 无放回抽取目的端口,默认 1024 个。先用可注入的 CSPRNG 生成随机集合,再将选中的离散端口按数值递增调度;测试使用固定种子。只对抽样结果排序,避免退化为从最低端口开始的连续扫描。
  3. 现有普通候选和邻近预测继续由原 ICE 调度;新探索集合避免重复消耗这些目标的探测预算。低于 1024 的探测以及非均匀采样留待后续实验,不声称第一版覆盖 UU 的全部端口范围。
  4. 每个新目标默认发一次 PROBE。收到 pool 的认证 PROBE 时,从同一 socket 向该包实际来源端点回 PROBE_ACK;该源 IP 必须仍属于本轮批准的对端公网 IP。
  5. 原始来包地址用于路径发现;不能用包内宣称的地址替代源地址,也不能把 TURN 逻辑地址当成直接源地址。

6.2 pool 的行为

  1. 在同一选定物理接口上分配默认 128 个不同 UDP 本地端口;绑定地址和接口遵循原 ICE socket 的平台规则。本地端口仍由系统分配,不要求其数值递增,也不据此推断公网映射顺序。
  2. 新 socket 不为每个端点申请 TURN allocation、启动独立 DTLS 或开展完整多 STUN 采集,只向 anchor 已协商的公网端点探索。
  3. 以 round-robin 调度 PROBE,最多 3 轮;相邻轮至少间隔 1 秒。全部端点保持可接收至本轮停止或被接入 ICE,不能发完就关闭。
  4. 收到 anchor 的有效 PROBE 或匹配 ACK 后,对该具体 socket 请求提升并启动普通 ICE 检查;收到 PROBE 时先从同一 socket 回复 ACK。
  5. 不根据本地源端口序列推算公网端口。探测过程中没有第三方 STUN 返回也不妨碍命中后的标准 ICE 验证。

两侧时间窗需要重叠,但不要求包在同一毫秒发出。允许丢失第一包、控制消息重传和两端系统时钟有偏差。

6.3 当前实验配置

以下是按用户要求扩大数量并改为离散递增发送后的实验参数,不是从 UU 反推的最佳值。调整理由见第 16.3 节;历史测试仍保留其原始参数。所有值通过经过校验的不可变 PunchConfig 进入每个 IceAgent。

参数 默认实验值 v1 硬上限 / 规则
enabled false 必须双方开启
trigger_delay_ms 1500 从首次选中 relay 计时,并等待 DTLS 指纹已验证
mapping_sample_max_age_ms 15000 不跨 generation 或接口变化复用
pool_size 128 每会话最多 128;禁止远端扩容越过本地上限
target_port_count 1024 最多 1024,唯一端口数
probe_pps 128 本端全部新增 PROBE/ACK 合计最多 200 包/秒
burst_packets 8 token bucket;事件循环滞后时不能瞬间补发整个积压队列
pool_probe_rounds 3 v1 最多 3;重发也计入总预算
round_duration_ms 10000 探索阶段时长,最多 15000,并受原 relay upgrade 截止时间限制
ice_finish_reserve_ms 3000 到原升级截止前停止新探索,留给已命中 ICE 检查
probe_packet_budget 1024 本端 PROBE/ACK 累计上限;发送失败也记录尝试计数
control_packet_budget 48 本端全部控制消息与重传总量
promoted_endpoint_limit 4 最多 8;只有命中端点进入普通 ICE 检查
rounds_per_generation 1 重复能力/控制消息不创建新轮次
process_active_round_limit 2 超额会话跳过增强,正常媒体不排队
process_extra_socket_limit 256 进程内所有 IceAgent 共享配额
process_probe_pps 200 进程级公平调度,优先及时 ACK,仍受总预算约束

定义两个截止时间:T_probe = min(本轮开始 + 探索时长, 原升级截止 - ICE 保留时间);T_finish = min(T_probe + ICE 保留时间, 原升级截止)。到 T_probe 停止新探索、释放未命中端点,已提升且正在检查的端点进入 DRAINING,继续标准 ICE 至 T_finish。到 T_finish 才取消仍未完成的增强检查;已选中的端点继续归正常 ICE 管理。协商、分配、重传均不延长原有 30 秒窗口。双方各自检查本地剩余时间;不足时拒绝开始。

控制包与新增 PROBE/ACK 单独计数;提升后的普通 STUN 检查遵循 libnice 原节拍,但需要记录它们带来的额外事务和重传。媒体包不计入探测预算。

首次实现用内部配置和明确命名的实验环境变量加载配置,仅读取一次,不增加公开 Params 布局或 GUI 开关;例如 MINIRTC_UDP_PUNCH=off|wide-only|pool-wide。无效值禁用增强并打印原因。端口池大小等调参入口统一由同一校验函数处理。

wide-only 保留同样的协议、角色和锚点选择,但 pool 侧只用一个端点;pool-wide 使用配置的端点池。基线 off 仍保留已有附近端口预测。不能让实验分组顺带改变其他 ICE、TURN 或重试配置。

单端点模式也经过新的协调和端点接入流程,因此 A/B 差异不能全部归因于采样范围;B/C 对比用于隔离多个本地端点带来的增量作用。

7. libnice 接入与命中后的完整闭环

7.1 后端职责

新增能力全部在 NiceAgent 的 GMainContext 中执行。C++ 只持有带 generation 的不透明句柄,不直接持有可悬空的 NiceSocket*。

拟提供的内部接口契约,确切 C 签名在 P0 验证后冻结:

拟新增接口 语义
punch_get_capabilities() 扩展版本及可用能力;不能仅因头文件存在就通告支持
punch_acquire_anchor(base_candidate) 借用并保留真实 anchor socket,返回句柄与实际本地地址
punch_create_pool(base_candidate, count) component 拥有新 socket,逐项报告分配成功/失败;只做直接探索
punch_send(handle, remote, bytes) 从指定 socket 发出原始直接 UDP,不走当前 selected relay
punch_packet 事件 句柄、原始外层 src/dst、字节、generation;只在直接 socket 接收路径产生
punch_promote_and_check(handle, anchor_candidate) 原子地把该 pool socket 注册为可检查的本地 host 候选,向已有 anchor 远端候选触发普通 STUN
punch_stop_discovery(round) 到探索截止时停止自定义探测,释放未命中端点;保留已提升并在验证的端点
punch_cancel(round) 撤销探索 source、借用引用和未被 ICE 接管的 socket;幂等
punch_get_upgrade_deadline() 返回已有升级截止的 monotonic 时间,避免 C++ 层另起不一致的 30 秒计时

不要用 nice_agent_set_port_range() 代替端口池;该接口只约束 host 候选的端口分配范围。不要调用强制 selected-candidate API 绕过 ICE 验证。

接入点要覆盖:component 的 socket source 管理、UDP 接收分流、候选与 socket 的关联、候选对入队、READY 后重新唤起检查计时器、候选裁剪和销毁。新增 socket 使用现有 UDP 后端的创建过程,保持 Windows/Apple 物理接口绑定行为。

7.2 直接接收分流

  • anchor 原始 socket:在 libnice 将未知源的非 STUN 数据丢弃之前,识别仅在已协商轮次生效的 PROBE/ACK,传给协调器验证。
  • pool 探索 socket:只处理本轮的直接 PROBE/ACK;普通业务数据不向上交付。提升后允许该 socket 进入正常 libnice STUN/媒体处理。
  • 接收钩子必须区分原始 UDP、TURN 服务器来源及 TURN 解封装后来源。嵌套的同 magic 载荷不得走直接命中分支。
  • 在原来的 NiceSocket 事件循环中读取,禁止另一个 recvfrom 线程竞争同一 socket 的 STUN 或媒体包。
  • 回调不能在 libnice 内部持有不可重入锁时同步反向调用后端;必要时保留有界字节副本和句柄,投递到同一 context 的后续任务。

7.3 标准 ICE 验证路径

第一版固定由 pool 端首先对新 socket 启动普通检查,避免将每个猜测端口伪装成已发现的远端候选:

  1. pool 从某个 socket 收到有效 PROBE 或匹配 ACK,确认该端点有直接入站证据。
  2. 后端保持该实际 socket 不关闭,将其提升为正常本地 host 候选,并与已有 anchor 公网候选建立检查对。
  3. pool 从该 socket 发出正常 Binding Request,使用当前 generation 的 ICE 凭据、真实 controlling/controlled 角色和既有 priority 规则。
  4. anchor 在原始 socket 上收到请求,经 libnice 认证后按标准逻辑学习新的 peer-reflexive 远端地址、响应并触发反向检查。
  5. 双向检查形成 valid pair,controlling 一侧通过现有 relay-upgrade 路径执行 nomination/renomination。
  6. 收到真实 selected-pair 变化,双方才各自将状态记为 DIRECT_SELECTED。RESULT、短包 ACK 或发现 prflx 均不能代替这个条件。

直接 probe 认证和 ICE 认证是两个不同步骤。成功 probe 不标记候选 valid,不伪造 STUN 成功事务,也不提前对外报告“学到 prflx”。

必须覆盖 pool 为 controlling 和 pool 为 controlled 两种情况。新增 pair 的优先级由现有候选计算规则产生,并验证满足已有“优先级高于所选 relay 才升级”的条件。

7.4 候选数量与 end-of-candidates

探索 socket 在命中之前不参加全量候选配对,不为端点与目标端口的全部组合建立检查对象;控制提升数量,逐个接入正常检查。

内部增强端点不通过普通 new_candidate 信令发送。为这些端点明确添加内部来源标记,并在现有 new-candidate-full 回调处处理,防止在已发送 end-of-candidates 后又误发一批普通候选。既有 host/srflx/relay 的正常收集和结束标记不受影响。

这属于双方协商的 MiniRTC 扩展;不能假定标准浏览器或未打补丁的 libnice 在 READY 后接受相同的动态行为。

7.5 媒体连续性和所有权

  • selected relay 在新的 pair 真正选中前持续传输;不调用 ICE restart,不创建第二套 DTLS/SRTP 会话,不重置 SSRC、编码器和传输控制器。
  • 探索中:component 持有 pool socket;协调器持有不透明句柄。
  • 提升后:所有权转入正常候选/检查生命周期,协调器只保留观察标记。
  • 选中后:候选及 component 保持 socket 存活直到连接关闭;取消探测不能关闭它。
  • 未选中的已提升 socket 若仍被进行中的 STUN/checklist 引用,先在 context 中安全移除相应工作,再释放。不能按一个“关闭整个池”的循环直接 close。
  • 借用的 anchor socket 和原 relay socket 始终由原 ICE 生命周期管理,探测清理只释放额外引用。

进程级额外 socket 配额随实际 socket 生命周期持有。提升或选中只转移配额所有者,不能提前返还配额后让进程实际句柄数越过上限。

8. 状态机、取消和故障行为

DISABLED / INELIGIBLE
          |
          v
WAIT_RELAY_AND_VERIFIED_DTLS
          |
          v
EXCHANGE_HELLO -> PREPARING -> READY -> PROBING
                                      |    |
                                      |    v
                                      |  ICE_VALIDATING -> DIRECT_SELECTED
                                      |    |
                                      v    v
                                      DRAINING -> DIRECT_SELECTED
                                          |
                                          v
                                       STOPPED

ICE_VALIDATING 表示至少一个端点已提升;在数量与时间预算内可继续其余探索。单个命中检查失败不能立刻报告整个实验成功或关闭仍在工作的 relay。

到 T_probe 或探索预算耗尽时停止新探索;存在待验证端点则进入 DRAINING,否则结束。DRAINING 只继续已经登记的 ICE 工作,不再创建新端点或扩大端口集合;最多持续到 T_finish。

任何活动状态均可因下列事件进入 STOPPED:

  • 对端不支持、模式不符、观测不足、预算不足、控制协商超时。
  • 普通 ICE 提前选中了直连;立即停止本增强,区分成功来源为 ordinary-ice。
  • 原升级窗口到期、会话断开、ICE generation 改变、被借用 socket/接口失效。
  • 本轮 socket 分配不足、队列过载、认证上下文消失、协议冲突。

无效外部包只丢弃和限频计数,不能让任意错误报文取消有效会话。已认证对端主动 CANCEL 可以停止本轮,但不关闭媒体连接。

关闭顺序必须是:禁止新调度并标记取消 → 作废 generation/round 任务 → 移除 GSource/回调 → 释放探索资源 → 安全处理未选中提升端点 → 执行现有 NiceAgent 异步关闭 → 清理 exporter 密钥和 DTLS。工作不能阻塞或反向等待自己的 nice_thread。

构建默认开关关闭时应没有额外 socket、计时器、控制包或能力通告。运行关闭后不能靠收到对端消息隐式开启。

9. 日志与测量口径

INFO 记录阶段和结论;逐包记录放入可选调试日志。所有记录包含 connection/generation/round 标识,不打印 ICE 密码、exporter 或完整认证 tag。

事件 必须字段
eligibility 模式、双方能力、角色依据、样本 socket/年龄、跳过原因
prepare/start 协商实际预算、本地截止时间、anchor 本地/公网端点、批准的对端 IP
probe-tx/rx 本地 socket_id、实际地址、原始外层源/目的、nonce、direct/control/relay 标记
promote 命中的具体 socket、触发原因、候选 foundation、候选对标识
ICE check TID、pair、角色、状态、外层地址、TURN peer(若有)
selected 完整本地/远端候选类型和地址、成功来源、是否沿用命中 socket
stop 原因、持续时间、唯一目标数、socket 峰值、发送尝试/成功/失败/重传、清理结果

成功率至少分别报告:

  • 全部连接尝试中的直连建立比例。
  • 满足增强条件并实际启动的连接中的增强成功比例。
  • 普通 ICE 自行成功、增强成功、继续 relay、连接失败的数量。

分母包含失败/超时,不把“跳过增强”混入增强失败,也不能只汇总成功样本。记录 P50/P95 建立耗时、额外包数、峰值句柄和 CPU。切换期间的媒体间隙为可选观测,缺测记 null,不作为本次验收门槛。每条结论都能追到 session ID、二进制版本和抓包。

10. 分阶段实施与验收

以下顺序是依赖关系。P0 未通过时先解决后端接入问题,不能通过增加扫描范围掩盖“已命中却无法接入 ICE”。

P0:验证 READY 后新增 socket 与媒体切换

交付:最小 libnice 扩展及双端集成测试,在受控网络中创建一个额外 pool socket,获得直接入站后提升、普通 ICE 验证、选中并传输。

验收:

  • 从真实 selected relay、READY 和已验证 DTLS 状态开始,端点在此后创建。
  • Windows/macOS 均验证创建、物理接口绑定、接收、提升及结束清理;两个 ICE 角色都覆盖。
  • 捕获证明命中的本地端口和后续 STUN/媒体端口一致。
  • 验证候选不会因 READY、检查上限、同 foundation/base 或 end-of-candidates 流程被错误抑制。
  • 选路变化不重启 DTLS;未命中和主动取消保留原 relay。
  • 冻结后端接口、所有权表和必要补丁范围,记录实际代码接入点。

P0 工具只能使用实验提供的明确对端地址,不含广范围探测。

P1:协议、密钥与配置

交付:能力行、验证后 exporter、报文编解码、重放窗口、HELLO/PREPARE/READY/START 协商和默认关闭配置。

验收:新旧版本互连正常;关闭时无新增行为;无效 tag、旧 generation、重复 START、丢失 READY、时钟偏差不导致越级启动或重复分配。控制包不会进入 DTLS/SRTP/业务包解析。

P2:角色、端口采样和预算

交付:映射快照、角色判断、无放回端口采样、token bucket、pool 轮发策略、进程级配额。

验收:假时钟可以复现相同发送计划;延迟唤醒不超突发上限;观测过期/多 socket 混淆被拒绝;同进程多连接不突破共享限制;短 ACK 不绕过总预算。

P3:组合探测与提升

交付:anchor、pool、直接报文接收钩子、命中后普通 ICE 检查、selected-pair 归因。

验收:完整实现第 7 节闭环;只提升命中端点;来自 TURN 内层的相同短包不产生直接命中;成功、超时和普通 ICE 抢先成功均正确清理。

P4:生命周期、兼容与构建

交付:断开/重连/接口变化处理,libnice 配方和包身份更新,新符号检测,Windows/macOS/Linux 编译验证。

验收:连续创建/停止至少 100 轮无残留端口池或 source,关闭竞态检查通过;旧依赖缓存不能错误通告新能力;iOS/WebRTC 路径未启用此模式且正常构建。

P5:真实网络对照与启用决定

交付:A/B/C 配置、自动汇总脚本、双端抓包复核和实验结论。

  • A:现有策略,增强关闭。
  • B:相同新协议与相同的自动角色判定规则,一侧单端点,另一侧扩大目的端口;角色必须由有效观测支持。
  • C:与 B 相同,但使用端口池。

按交错顺序重复 A/B/C,第一轮每组至少 30 次;保持网络、版本、STUN/TURN 和其余配置相同,记录每次启动间隔和真实端点。观察到一次成功不等于方案验收完成。

先验收机制和无回归,再评价收益:每个增强成功样本必须能证明直接命中 → 标准 STUN → nomination → 直接媒体;默认关闭路径无行为变化;资源全部在上限内。报告成功比例的不确定性,不从小样本承诺固定提升幅度。

验收范围调整(2026-09-14,用户明确要求):不验收画面、声音、控制和切换质量, 不再将这些项目列为阻塞项,也不将免验记为测试通过。仍需证明同一命中 socket 完成标准 ICE 提名并持续承载应用数据,保留 DTLS、兼容性及资源生命周期检查。

在真实网络证明净收益且兼容/退出检查通过之前,保持默认关闭;通过后再单独形成默认启用及参数调整变更。

11. 测试设计

11.1 纯逻辑与协议测试

新增拟定目标 minirtc_punch_unit_test,覆盖:

  • 跨 generation、跨 socket 的样本隔离和角色对称性。
  • 端口采样范围、唯一性、可复现性、耗尽与数量边界。
  • 速率/包数/时长/进程配额,事件循环暂停后的行为。
  • 报文截断、长度溢出、错误域密钥、错 tag、旧轮次、nonce/socket 不匹配、乱序与重放。
  • READY/START/CANCEL 重复、断开在任意状态发生、部分分配失败。
  • 默认关闭、新旧能力组合、强制 relay、reliable、无 DTLS 验证的跳过条件。

11.2 libnice 集成测试

新增拟定目标 minirtc_punch_integration_test。当前 MiniRTC 没有已注册的可直接运行的 ICE 测试目标,旧 tests/peerconnection 示例不是可用基线。实现时必须注册目标并写出准确的构建/执行命令。

集成测试验证真实 NiceAgent、真实 socket、STUN 认证、triggered check、选路、媒体收发及销毁。模拟器内的单元测试不能替代 P0。

11.3 可控网络场景

搭建受控 UDP/NAT 测试拓扑,或具有真实 socket 和明确翻译规则的测试代理。不要默认 Linux 普通 MASQUERADE 就等于本次目的相关映射;必须先通过多目标 STUN 验证场景行为。

必测:

  1. LAN/普通可直连网络:普通 ICE 成功,不启动或及时停止增强。
  2. 一侧稳定映射且有端口相关过滤,另一侧目的相关映射:未知新映射参与匹配。
  3. 丢弃 anchor 最早的探测,让 pool 后来的包首先到达:复现 UU 的关键可达顺序。
  4. 没有目标端口交集:到期后保持 relay,释放探索资源。
  5. 模拟 UDP 丢失、重复、乱序,控制包延迟,以及两端墙上时钟相差至少 500 ms。
  6. 只有 TURN 内层收到同内容数据:不得记录为直接命中。
  7. 两侧都目的相关、观测不足、IPv6-only:v1 跳过,原功能继续。
  8. pool 任一端点命中后断开、提名中取消、接口变化、旧任务到达新 generation。
  9. 进程中超过两个连接请求增强:资源限制与普通媒体均正常。

11.4 真实会话验收

双方记录二进制/提交版本、实际配置、网卡、抓包起止与内核丢包计数;按 nonce、STUN TID、外层地址和成功后的 socket 对齐。NAT 可能重写端口,不能仅按发端源端口搜索对端抓包。

“直连成功”的严格条件:候选对两端均不含 relay,标准检查和提名完成,双端网卡观察到相匹配的外层 UDP,并持续承载应用数据。日志中的 prflx 或一个短包 ACK 不算成功。

12. 已识别的实现风险与解决位置

风险 解决阶段
READY 后新增 socket 无法进入检查或被裁剪 P0 验证并冻结必要 libnice 改动
为全部端口组合创建 ICE 对象导致检查表膨胀 P2/P3 将探索与少量提升分离
临时 socket 成功后被清理,媒体仍发旧端口 P0/P3 明确所有权转移并核对原始端口
把 TURN 解封装逻辑来源当成直接来包 P3 在原始 socket 层分类,P5 双端抓包验收
现有 DTLS done 标志先于指纹验证 P1 增加明确的 peer-verified 状态与导出门槛
自定义控制包落入 SRTP/业务解析 P1/P3 按版本和种类分流,协议测试与媒体回归
libnice/GLib 回调锁内重入、退出时悬空引用 P0/P4 同 context 调度、句柄注册表与幂等停止
两端升级窗口不同或旧消息延长新轮次 P1/P2 本地 monotonic 截止、generation/round 隔离
头文件与旧 libnice 静态库不匹配 P4 包身份、补丁哈希、编译/链接及运行能力检测
把一次成功当作普遍成功率提升 P5 分组重复实验,保留全部失败和跳过样本

13. 后续执行入口

后续实现从 P0 开始,先读取本方案和当时仓库状态,确认源码基线是否变化。每完成一个阶段,在对应实现提交说明中记录:改动范围、验收证据、未解决问题和本方案是否需要修订。

如果 P0 证明某个后端接口或 selected-pair 路径不可按本文实现,应先根据实际 libnice 代码修订这一处设计并给出替代路径,再继续后续阶段;不要把未完成的直接验证改成强制选路或延长超时。

本方案当前的执行目标是一个默认关闭、可复现实验、完整管理 socket 生命周期的原型。通过 P5 后,才讨论默认启用、扩大网络适用范围,以及首次连接阶段的增强。

14. 技术依据

15. 本轮 P0 实施记录(2026-09-13)

MiniRTC 实施提交:7f0e666216891c9b79bd4a8f75b63741fe1fabf2。 本机最终检查为 22/22 通过,包括 en1 实际 IPv4 与内核接口绑定读回;这不是跨 NAT 成功率。详情见 P0 测试记录 与 构建、双机执行和抓包步骤。

本节是执行记录,不改变 P0→P5 的依赖门槛。本轮没有完成可启用的“双端 UDP 打洞增强”产品功能。 P0 的本机机制已可运行;Windows、双端物理网卡抓包 和实际 CrossDesk 媒体尚待验收,因此不启动 P1–P5,不通告生产 v1 能力,也不进行 512 目的端口/64 本地端点的广范围探索。

交付位于 MiniRTC 子模块,不能仅依赖开发机缓存:

  • thirdparty/libnice/patches/punch_pool_0.1.24.patch:在前三个现有补丁后应用, 新增 nice/punch.h 与单端点实验后端,补丁 SHA、包身份版本与符号链接检查已进配方。
  • tests/ice/punch_integration_test.cpp:真实 NiceAgent、TURN、DTLS、SRTP 与 UDP 转发门控,pool 的两个 ICE 角色均可执行;在代码中检查内核实际 selected socket。
  • tests/ice/punch_peer_test.cpp:两个独立进程/两台 LAN 主机的 P0 实验入口, 私有共享目录提供测试信令,便于 Windows/macOS 后续配合;不是生产控制协议。
  • tests/ice/lab/run_p0.py:临时 coturn、STUN 就绪检查、场景矩阵、二进制 SHA、 结构化结果与清理;tests/ice/lab/p0-experiments.md 给出准确构建、双机操作和抓包清单。
  • src/ice/ice_agent.*:明确 dtls_peer_verified_,统一两个握手完成入口的指纹 验证;导出 SRTP 前要求验证成功;有界保留早于本端 READY 的 DTLS flight。

15.1 经源码和实验确认的调整

  1. priv_conn_check_add_for_candidate_pair_matched() 原来会将 READY 降为 CONNECTED。 对已打开 P0 端点且处于原升级窗口内的连接保留 READY,正常 STUN 排队、优先级、 valid pair 和 nomination 不变。取消增强检查时,尚存的 selected relay 不因 checklist 中提名对象已裁剪而被错误宣告 FAILED。
  2. 直接接收分流必须排除两类中继:本端 TURN 解封装,以及对端 allocation 向 本端 host 发出的普通外层 UDP。后者在 pool 为 controlling 的负向测试中被发现, 因而在原始接收钩子中额外排除已知远端 relay candidate 地址。
  3. 初始实验复现了既有 DTLS 次序问题:ClientHello 到达时本端仍为 CONNECTED, 原代码丢包且对端没有后续包推动握手。现在最多暂存 4 个、每个不超过 2048 字节的 早到 DTLS 数据报,仍由原 READY 回调启动同一个 DTLS 会话;没有重新握手、修改 ICE/DTLS 超时或延长升级窗口。BIO 方法初始化也改为线程安全的一次性初始化。
  4. libnice 的 SDP 解析器按 LF 切分,直接复制 ufrag/password;实验初版擅自将 libnice 输出改成 CRLF,造成密码尾部多出 CR 与真实 STUN integrity 失败。 夹具已保留原 LF 格式。此修正仅在测试信令中,不改写生产信令行为。

15.2 当前后端契约及边界

公开名称刻意标为 NICE_PUNCH_P0_ABI,不是 udp-punch:1 的运行能力证明。

接口 P0 实际语义
nice_agent_punch_get_abi/live_sockets 实验 ABI、进程额外 socket 存量
nice_agent_punch_get_deadline 读取现有有效 relay upgrade 截止,无新窗口
nice_agent_punch_open 指定真实 IPv4 host base,在 READY relay 上借用或创建一个端点
nice_agent_punch_send 指定 generation/不透明 handle,向明确地址发送 80 字节实验探测
punch-packet 锁外信号,带范围、实际外层地址与有界字节副本;不表示 ICE valid
nice_agent_punch_promote 保持 socket,加入一个普通 host/已知 anchor remote 检查对
nice_agent_punch_stop(..., FALSE) 停止探索,关闭未提升端点,继续已提升检查
nice_agent_punch_stop(..., TRUE) 先剪除未选中检查再关 socket;已选中端点保持传输

原始 anchor、relay 的所有权始终属于原 ICE;新 socket 属于 component 的 SocketSource;提升不关闭或重建 socket。额外 socket 配额在实际 SocketSource 释放之后返还,选中后仍占配额直到关闭。句柄不暴露 FD,不在 libnice 锁内反向调用。

P0 仅允许每 component 一个实验端点,进程硬限制 256 个额外 socket。它没有实现 P1 的生产认证上下文、P2 速率公平预算、P3 广范围组合探测或 P4 完整重连协调器。 实验 HMAC 密钥仅来自可信夹具/私有测试信令,不声称已经完成 DTLS exporter。 MINIRTC_UDP_PUNCH=wide-only|pool-wide 目前没有被实现或读取;产品仍关闭。

15.3 验收和下一步

当前可运行的构建与实验步骤见子模块 tests/ice/lab/p0-experiments.md;可公开的测试摘要保存在同目录的结果记录中。 成功意味着本机真实中继 → 验证过的 DTLS → 新 socket 的认证入站 → 正常 STUN/ nomination → 同一 socket 的双向 SRTP,不能将编译、短包 ACK 或一次局域网成功 当作 P0 总验收或 P5 的真实网络收益。

P0 总门槛仍需 Windows x64 编译/执行、Windows/macOS 双端抓包、两个 ICE 角色、 实际桌面媒体连续性及退出检查。完成这些后再冻结生产后端接口并进入 P1,沿用本文 其余协议、预算和 P1–P5 验收要求。此处没有通过降低验收要求提前启动广范围探索。

2026-09-13 后续进展:用户报告同步后的 Windows 构建成功,尚未取得 Windows 网络测试日志或二进制哈希;不能将构建成功记为 P0 运行验收。两端实际不在同一 局域网,当前双机夹具要求明确且互相可达的 UDP 地址,因此不能直接使用两端 私网 IP 运行该夹具。run_p0.py 新增外部实验 TURN 模式,使 Windows 可以先 独立完成本机双 ICE 角色、SRTP 和资源清理矩阵,无需本机安装 coturn。外部 TURN 不等于双机直连拓扑,此调整不改变 P0 门槛、原升级窗口或后续阶段依赖。

15.4 两台 Mac 局域网实测

用户提供同局域网的第二台 arm64 Mac 后,已在 192.168.31.125/en1 与 192.168.31.102/en0 执行真实双机 P0;双方系统均为 macOS 26.6.2。 run_p0_ssh.py 仅用 SSH 传递实验 JSON 信令,探测、STUN 和 SRTP 仍通过真实 LAN UDP socket 与原受控门控转发。没有改用 SSH 隧道承载被测传输。

四种 ICE/端口池角色组合的媒体断言均通过。首轮严格双端逐包核对为 3/4 通过, 另一组有一个 SRTP 包只出现在发送端抓包中,原始失败记录已保留,未改判为成功。 该组以新端口、新 run ID 复测后通过,两端均取得零内核丢包统计。四种组合现均有 探测 HMAC → 认证 STUN/提名 → 命中 socket 上双向 SRTP 的双机报文证据。

双机夹具增加开池前至少 10 个中继 SRTP、停止探测后至少 10 个直连 SRTP、 selected socket 的物理接口绑定读回;全部仍在原升级窗口内。仓库新增 verify_p0_capture.py,核对实际 pcapng、ICE MESSAGE-INTEGRITY、提名 TID 和 门控两侧的相同报文,私有密钥和原始抓包不入库。

完整次数、哈希、例外、复现和清理记录见 双 Mac P0 实测记录。 本轮完成 Mac 双机机制与抓包验证,没有替代 Windows 运行、跨平台及实际应用 媒体验收。P0 总验收仍未完成,P1–P5 继续保持未启动,生产功能默认关闭。

15.5 Windows 局域网验收准备(2026-09-14)

用户另提供同局域网 Windows:WLAN 192.168.31.220/24,仓库 D:\SourceCode\crossdesk,账户 desktop-ri1qfbe\kunkundi。用户输出确认 Python 3.13.5 可用,MiniRTC 为 7f0e666,两个 P0 测试 EXE 文件存在。 这些信息不代表测试已运行;升级后的双机媒体断言需要同步并重新构建 peer 目标。

实验驱动增加 --remote-shell powershell,通过编码命令传入 Windows 的字面路径, 仍仅使用 SSH 管道传递私有信令。抓包核验器允许显式指定两端物理和本地回环接口名, 以适配 Npcap 的命名;未将虚拟网卡或回环流量当作物理网卡交付证据。 README 补充 Windows SSH 驱动、两接口抓包和精确接口名核验步骤。

本机已检查 Python 语法、带空格/单引号/中文路径的命令编码、POSIX 参数兼容, 并用原 Mac 抓包回归默认和显式接口选择;改写接口名后的同一份 Mac 报文也能核验, 错误物理接口或 anchor 回环接口会被拒绝。这只是工具回归,未执行 Windows PowerShell 启动、Windows 网络矩阵或 Npcap 验证。当前等待 Windows SSH 接入, 后续使用本机临时 TURN 开展单机矩阵和四种跨平台角色组合,保持原 P0 门槛。

15.6 Windows/macOS P0 验收完成(2026-09-14)

Windows 真实单机矩阵 20/20 通过,包含两个 ICE 角色、100 次生命周期、取消、 未命中、错误指纹和原截止时间。跨平台四角色均已取得命中端口上的认证 STUN、 提名及双向 SRTP 的严格双端抓包证据。完整结果、二进制和抓包 SHA、失败样本及 边界见 Windows/macOS P0 记录。

本轮没有把全部尝试改判为通过:初次双机抓包为 3/4,修正夹具收尾后为 4/5。 最终普通 Mac pool/controlling 一组有中段一个 SRTP 包只出现在 Mac 抓包;同角色 的单向通知延迟 1 秒组严格通过,因此四种角色均已有最终版本证据。另一次 Windows 开池前 TURN 媒体基线失败,针对两种角色复测均通过,原失败仍保留且原因未确立。 Windows 抓包统计包含 16 个 flushed,pcap/dumpcap/接口丢包为 0;未声称总丢包为 0。

经源码和最小实验确认两处夹具修正:Windows IP_UNICAST_IF GET 已为主机序, 去掉重复转换;旧夹具等待对端完成消息时仍发媒体,另一端可先销毁 gate,单向 延迟实验复现最后 13 包未转发。新增停止发送、交换最终序号、双端认证接收确认后 再销毁的屏障;没有修改标准 ICE、原窗口、DTLS 或降低媒体门槛。

完整 CrossDesk 两端编译成功,Mac 使用 xmake r crossdesk 提供 Slint 运行环境。 用户对双向画面、声音、鼠标键盘和三次重连请求回复已完成,未报告异常;此项作为 用户报告的普通应用回归,不能声称独立测量了 pool 切换时的实际编解码连续性。 命中 socket 的切换与认证媒体由 P0 真实 SRTP 夹具验证。

P0 后端接口、所有权与取消语义按 15.2 冻结,允许进入 P1。P0 仍是每 component 一个实验端点;P3 扩展数量时必须继续遵守同一 socket、标准检查和选中资源所有权。 生产能力、广范围探索和默认启用尚未交付,P5 真实未知 NAT 的收益仍需后续对照。

16. P1–P4 实现与 P5 实验入口(2026-09-14)

本节更新当前状态;第 15 节“未开始 P1–P5”等文字是对应时间点的历史记录。 在 P0 跨平台门槛通过后依次完成 P1/P2 基础、P3 多端点接入和 P4 生命周期/构建, 没有在 P0 前放大探索范围。源码及实验工具由主仓库当前 gitlink 固定。完整证据见 P1–P4 验证记录, 准确命令见 v1 实验步骤。

阶段 当前交付及边界
P0 已通过原门槛,保持单端点 ABI 1 与原始失败记录
P1 配置/能力、指纹验证后 exporter、认证协议与协商完成;默认关闭,新旧/未知能力纯逻辑及实际生成能力检查通过
P2 同源观测、角色、CSPRNG 采样、本地与进程预算完成;首次实现时 Mac/Windows/Linux 各 2,952 个单元断言通过,后续清理回归见验证记录
P3 独立 ABI 2、64/128 端点、同 socket 提升/标准检查/提名/媒体闭环实现;三个平台受控 runtime 各 12/12
P4 owner context 清理、DRAINING、取消、重连身份和接口/base 检查实现;百次 source/agent 生命周期、Mac/Windows 完整应用、Linux ICE smoke、iOS 静态库已验证。另有新版 Mac→旧 Windows 的非 SRTP 与 DTLS/SRTP 普通中继互通及清理日志;后者本地增强资格满足、旧端不支持,正确不启动增强。物理网卡真实切换和其余旧版连接组合仍待验证
P5 90 次交错清单与核验工具已准备;完整窗口 A/off 3 次均中继,READY 重查修复后 B/wide-only 3 次均启动但未命中,C/pool-wide 3 次为 2 次增强直连、1 次中继。两个成功会话已补齐 Windows 日志并由仓库 wire 脚本核验 2/2 通过。正式随机 A/B/C、运行版本和捕获统计仍需补齐,不作收益或默认开启决定;画面/声音/控制/切换质量按用户要求免验

16.1 对实际源码的必要调整

  • 保留已验收的 punch_pool_0.1.24.patch,追加 punch_v1_0.1.24.patch。 nice_agent_punch_get_v1_abi/open_v1 与 P0 API 区分;v1 每池最多 128、进程 256, 提升最多 8。补丁 SHA 与包身份版本进入 MiniRTC 配方,不依赖本机缓存改动。
  • PunchRuntime 由 IceAgent 持有,在同一 GMainContext 使用真实句柄,直接收包 与 TURN 控制分流,认证命中后由 pool 提升。实际 selected pair 才报告结果, 各自的原始截止不会因 HELLO/重传/START 被延长。
  • SDP 指纹最小实验发现原解析吞并 fingerprint 之后所有行并截断 SDP,导致新增 候选/能力位于其后时 DTLS 校验失败。改为逐行解析、保留其余 SDP、拒绝冲突指纹, 并加入真实握手与位置测试;没有通过禁用指纹校验绕过失败。
  • 通用 DEBUG 日志在现有项目中编译关闭,因此添加独立、默认关闭的 MINIRTC_UDP_PUNCH_TRACE=1,只记录有界探测元数据。应用原有 SDP 日志包含 ICE 密码,原始实验文件必须保持私有;新代码不记录 exporter 或完整 tag。
  • 首次启用后的真实应用试跑发现 eligibility 日志读取未赋值的旧 ufrag 指针, 在接收 SDP 时崩溃;最小实验复现后,eligibility/skip 与 exporter 统一从当前 libnice stream 读取 generation 凭据。补充双角色真实 SDP 接收/重建及自动 skip 的 TURN/SRTP 回归,详情见验证记录。此修复不改变协议或绕过探测前置条件。
  • Mac off 试跑发现传输层复制指纹行后缺少 LF,追加原能力时产生 CR CR LF, 无 v1 能力的 SDP 被严格校验拒绝。完整 IceTransport 实验复现后在拼接处补齐 LF;三种运行模式各 36 个组合及错误指纹拒绝通过,证书校验和默认关闭不变。
  • B 组一次启动遇到 READY → CONNECTED → READY 的正常候选重查。后端截止时间 查询只在 READY 有效,旧入口把暂时返回 0 当作最终跳过。保存首次 READY 时 后端提供的真实截止时间;在该时间内等待 CONNECTED 恢复,随后重新让后端验证 是否可探测。不在 CONNECTED 开池,不从恢复时间重新计算窗口;新增真实 TURN 的 auto-recheck 夹具,覆盖两个角色、原截止不变和单次 DTLS/认证中继媒体。
  • 第 10 节 B 组原“固定角色”明确为固定自动判定规则,与第 4 节生产禁用强制 角色保持一致。受控夹具仍可固定角色;真实实验记录观测/角色变化及跳过,不把 它们隐藏为成功或擅自在生产中绕过适用条件。

16.2 验收边界与下一步

已验证 DRAINING 后真实 STUN 重传仍能提名并传输、认证 CANCEL 保留 relay、 TURN 内层同形载荷不会产生直接命中,以及选中 socket 的实际存活和配额保持。 Windows 64 端点百次压力测试在单一窗口到期后拒绝分配;工具通过多个新连接分批, 最终两角色合计 20/20,各累计 100 次分配/停止/端口重绑定,没有扩大 ICE 窗口。 分批首轮 15/20 暴露开池前基线只按发送次数判断的问题;改为原截止前等待双方 各实际收到至少 10 个认证 SRTP 包后才开池。所有初次失败和修正理由保存在报告中, 不追溯认定所有历史媒体缺包原因相同。

真实异网 C 组已有两个成功会话完成双端日志、命中 handle、标准提名和物理抓包 关联;Windows 的 handle 38/44 分别对应命中本地端口 54058/51630,随后持续直连。 成功停止探测后额外 socket 计数各为 1,第三次未命中超时后归零;两端每次均只有 一次 DTLS 握手。完整技术证据及限制见所链接的验证记录。

14:18–14:22 的后续故障实验另记录五个 generation:一轮增强直连、四轮中继。 增强直连在约 10.3 秒入站 UDP 中断后沿用命中 socket 恢复,两轮中继在约 29.9/ 17.2 秒中断后恢复;每次均单次 DTLS,并完成连接清理及后续新 generation 建连。 包含 DRAINING 未命中时取消、探测超时及 HELLO 超时;该轮增强成功的仓库 wire 核验 1/1 通过。用户补充确认 clumsy 丢包率为 100%;过滤范围、方向、启停时间及 真实网卡切换尚未确认,不将观察到的间隙当作精确配置时长或物理接口变化通过, 也不并入正式 A/B/C 分母,详见验证记录。

UU 与 CrossDesk 的端口发现及通道复用差异,按 双端对照测试 先做 8 次诊断连接。进程重启与保留 进程重连分开记录,不把短暂静默当作 NAT 映射清空,也不混入正式 P5 成功率。

用户在 2026-09-14 明确取消画面、声音、控制和切换质量验收;这些项目免验, 不再要求人工回归或质量指标,不追溯标记为已测试。后续用同一版本、同一信令与 STUN/TURN 配置交错执行 A/B/C,核对有效观测、双端物理报文、标准提名、同一命中 socket、同一 DTLS/SRTP 会话和持续应用数据。至少每组 30 次,保留全部失败/缺测, 并完成真实接口变化、重连及连接兼容性检查。在真实收益结论完成前保持默认关闭。

16.3 离散递增与数量调整(2026-09-14,U01/X01 之后)

按用户要求,将 anchor 的随机无重复目标集合改为按端口数值递增发送,默认目标 512 → 1024、pool 64 → 128、每端 PROBE/ACK 速率 100 → 128 PPS。先完成随机 抽样再排序,保留原来的全范围抽样分布及候选排除;pool 本地端口继续由系统分配。 源码确认 runtime 每 10 ms 最多处理 8 个包,因此 128 PPS 不受“一次回调一个包” 限制。单轮无 ACK、无争用的理想调度约 8 秒可完成 1024 个目标;100 PPS 在原 10 秒窗口内不能保证覆盖这些目标,因而配套调整速率,没有延长探索或 ICE 截止。

保留 1024 次 PROBE/ACK 合计预算、8 包突发、200 PPS 进程限速和最多 256 个额外 socket。命中、ACK、发送失败、同进程会话争用、原窗口剩余时间不足都可能减少 实际新目标数量,1024 是计划上限,不承诺每次发完。双方使用 HELLO 逐项取较小 预算,旧参数对端仍收敛到 64/512/100;没有增强能力的旧端保持普通 ICE/中继。 本次不改变后端 ABI、仓库 libnice 补丁、选路、命中 socket 接入和 DTLS 生命周期。

这是一组新的实验配置,历史 U01/X01 结论及参数不改写。需要双方重新构建并重启 才能使用完整新参数;旧 P5 manifest 保留显式参数,新批次应重新生成 manifest。 数量增加及发送顺序本身不构成真实 NAT 成功率提高的证明。

本机 Mac 完整构建、5,430 个单元断言、Python 工具 10/10、三种模式兼容性及 各 36 个传输层 SDP 组合通过;128 端点 runtime 8/8、DRAINING 2/2 通过。 这些是本机回环受控网络结果,Windows 新配置及真实异网尚未运行;详情见 本次回归记录。