ConnProof 如何从网络检测生成证据链
“连不上"是结果,不是原因。ConnProof 的全部设计都围绕一个目标:把"连不上"拆解成一串可验证的事实,让原因从事实中自己显现出来。这篇文章解释工具内部是怎么做到的——不需要读代码,但读完你会明白报告里每一行字的来历,以及为什么你可以信任(或者说,如何去验证)它们。
分层:故障定位的第一性原理
一次成功的 HTTPS 连接是四场接力:DNS 把名字换成地址,TCP 与那个地址的端口握手,TLS 在连接上建立加密信道,应用协议(HTTP、WebSocket)在信道里对话。接力的性质决定了:失败必然发生在某一棒,而那一棒之前的所有棒都是成功的。
这就是 connproof diagnose 按 DNS → TCP → TLS 顺序执行的原因。每一层都是一次真实操作:
- DNS 层并行发出三种查询——A 记录、AAAA 记录、系统级 lookup。三者的一致性本身就是证据:权威解析失败而系统 lookup 成功,说明 hosts 文件或缓存在干预;只有 AAAA 有结果,提示 IPv6-only 的可达性风险;
- TCP 层向目标端口发起真实连接,区分三种失败的语义差异:超时(无回应)、拒绝(明确的 RST 回应 SYN)、不可达(本机路由表就无路可走)。这三种失败在很多应用里被笼统报成"连接失败",但它们指向完全不同的故障位置;
- TLS 层完成完整握手并读取证书。它记录协议版本、加密套件、SNI、证书有效期与主机名匹配——并且从不绕过验证来制造"成功":证书有问题时,问题本身就是最重要的证据。
前一层失败时,后续层标记为 skipped。这不是偷懒,是诚实:DNS 都没解析出来,任何"TLS 检测结果"都只能是编造。
错误码:工具与文档之间的合同
检测发现问题后,ConnProof 不会输出一段随机措辞的描述,而是匹配到规则库中的一条规则,输出它的错误码——比如 TCP 拒绝对应 TCP-002,握手超时对应 TLS-002。规则库是一个单独的数据包(@connproof/diagnostic-rules),每条规则包含:稳定的错误码、URL 友好的 slug、原始报错文本、一句话结论、按概率排序的可能原因、建议操作、适用平台,以及对应文档的路径。
关键的工程约束有三条:
- 错误码一经发布永不更改。
TLS-002十年后仍指向同一类问题;规则内容更新时递增ruleVersion并刷新lastVerified日期,但编号与含义不动。这让错误码可以被写进工单、笔记和搜索引擎而不会失效; - CLI 与文档站读同一份规则数据。网站的错误库侧边栏、首页的高频错误列表都从规则库生成,文档的 frontmatter 与规则逐字段校验一致——两边不可能各说各话;
- 文档 URL 由规范域名加 docPath 推导,不在任何一条规则里写死,保证全库链接的一致性可以被脚本验证。
证据:每条结论的脚注
报告中的每条发现(finding)都携带一个证据数组,每条证据记录五个字段:标签(这是什么观察)、值(观察到了什么,含耗时)、来源(哪个检测器产生的)、时间戳、以及是否经过脱敏处理。举例来说,一次 TLS 检测的证据链长这样:TCP 建连成功耗时 118 毫秒 → SNI 为目标域名 → 握手在 10 秒内未完成。三条证据合在一起,TLS-002(握手超时)的结论就不是断言而是推论——你可以自己核对每一步。
证据设计中有一条隐私红线:证据记录"发生了什么",不记录"内容是什么"。订阅检测会记录响应是 1024 字节的疑似订阅文本,但那 1024 字节本身永远不会进入报告;HTTP 检测判定页面含登录表单,但页面 HTML 不会被保存。
订阅检测的特殊设计
订阅 URL 检测在架构上是个组合体:它串联 DNS、TLS/HTTP 检测,再加一层内容分类——但这层分类被刻意做"钝"了。分类器只回答五个布尔问题:响应是否为空、是否是 HTML、是否具备登录页特征、是否被重定向到登录路径、是否疑似订阅文本。它不解析节点、不识别协议细节、不保留内容——响应体在分类完成的瞬间被丢弃,报告里只留下字节数和布尔结论。这个"钝"是有意的产品边界:诊断需要知道"你拿到的是不是订阅",永远不需要知道"订阅里有什么"。任何把这层分类做"锐"的改动(解析、转换、导出节点)都会被当作违反项目边界拒绝。
状态判定:宁可承认不知道
六种检测状态(pass / fail / warning / skipped / unsupported / inconclusive)中,最能体现设计立场的是最后两种。unsupported 出现在平台能力边界上——例如在无法读取系统代理的环境里,doctor 会明说"无法检测"而不是显示"未启用";inconclusive 出现在证据不足时——浏览器的"在线"信号只能证明网络接口存在,ConnProof 的网页检测就把它标为 inconclusive 并附上原因。
这个立场有实际代价:报告里会出现"不知道",看起来不如全绿的仪表盘漂亮。但诊断工具的价值在于可信,一个会把"没测"说成"正常"的工具,它的"正常"一文不值。
报告:三种形态,一份数据
检测结果先组装成结构化的报告对象(版本化的 JSON 结构,字段集稳定),再按需渲染成三种格式:终端文本(人读)、JSON(机器读,适合客服系统与自动化)、Markdown(贴进工单与 Issue)。三种输出来自同一份数据,不存在"终端显示的和导出的不一样"。
渲染之前,报告整体经过脱敏模块:URL 参数、Token、用户目录、邮箱被替换,IP 部分遮盖。脱敏是默认开启的最后一道工序,关闭它需要交互式确认——这个顺序保证了"忘了脱敏"这种事故在流程上不可能发生。
doctor:面向本机的同一套方法
doctor 命令把同样的证据方法论对准了本机环境:每个检查项(时间、DNS 配置、端口监听、系统代理、路由)都产出带状态的观察记录,跨项交叉推理由固定逻辑完成——例如"系统代理已启用"与"该端口无监听"两条证据的交集自动触发代理残留(PROXY-002)的判定。平台差异被封装在检测器内部:Windows 读注册表、macOS 调用 scutil、Linux 查环境变量,任何平台上无法完成的检查都返回 unsupported 而非编造结果。
你可以验证这一切
以上机制不依赖信任,依赖开源:规则库、检测器、脱敏器、渲染器都是独立的包,各自带着测试。测试全部跑在本地 fixture 服务器上(本地 TLS 证书、本地 WebSocket 服务、模拟 DNS),不依赖任何公网服务——这既让测试稳定,也意味着你可以在断网环境里完整验证工具的行为。想深入的话,从项目架构开始。