你的数据包长什么样?代理协议的报文结构及流量特征分析(一)

Views —

在理解网络协议/数据包之前,你需要有一定的基础知识。

必要的基础知识(一)

IP、端口、ASN

IP(网络层):数据包发送和接收的地址,用于唯一定位一台主机或路由器节点,包含 32 位的 IPv4(如 192.0.2.1)与 128 位的 IPv6(如 2001:db8::1)。内网地址和公网地址拥有不同的 IP 段。

端口(传输层): TCP/UDP 的最终目的地,范围是 0-65535,端口用于区分同一主机上的不同应用进程 (常见端口有 TCP 22: SSH、UDP 53: DNS 、 TCP 80: HTTP、TCP 443: HTTPS、UDP 443: QUIC 等)。

ASN(自治系统编号,Autonomous System Number):全球互联网由成千上万个拥有统一路由管理策略的大型网络(如运营商中国电信/AT&T、大型云厂商 AWS/Cloudflare)拼接而成,每一个独立自治网络称为一个 AS(自治系统)。ASN 是分给它们的唯一编号,用路由协议 (BGP) 计算数据包如何跨网传输。

就像送快递,快递是数据包,IP 就是收发货的具体楼栋地址,端口是房间门牌号,而 ASN 则代表负责派送的快递公司(顺丰之类的)。

IP 有一个非常重要的区别:内网地址和公网地址。 你在命令行输入 ipconfig 或 ip addr 时,电脑上显示的地址可能是 192.168.1.10 ( 192.168.0.0/16 ) ,常见内网 IPv4 地址段还有 172.16.0.0/12、10.0.0.0/8。

家里不同的设备都使用同一网段的内网地址,无法直接在公网路由。家用 IPv4 网络通常依赖路由器的 NAT(网络地址转换) 技术,路由器将内网多台设备发出的连接统一转换为路由器的公网 IP 发出,并在本地维护一张端口映射表来记录对应关系。

所以,网站看到的源 IP,通常是你家路由器或上游 NAT 设备使用的公网地址,而不是电脑上的 192.168.1.10。 image.png

DNS:域名如何变成连接地址

我们习惯记忆域名(如 www.example.com),但网络层建连必须依赖 IP 地址。

DNS(Domain Name System,域名系统) 是一套分布式查询系统,负责把域名解析成连接所需的信息,但它本身不负责建立网站连接。

最常见的地址记录是:

记录类型作用
A返回 IPv4 地址
AAAA返回 IPv6 地址
CNAME表示这个名字是另一个名字的别名
HTTPS / SVCB现代扩展记录,除了地址外,还能直接提供端口、协议提示(如直接走 h3)和 ECH配置等参数

例如:访问 www.example.com,浏览器给 DNS 服务器发查询请求,DNS 返回一个 IP 地址 203.0.113.80,浏览器拿到地址后,结合协议默认端口(如 HTTPS 默认 443),才会连接这个地址上的服务。

加密 DNS

传统 DNS 通常使用 UDP 53。查询内容一般没有加密,路径上能够观察这些数据包的设备(运营商、审查设备),可能看到你查询的域名。

常见的加密 DNS 方式包括:

  • DoT (DNS over TLS):通过 TLS 隧道传输 DNS(通常为 TCP 853)。
  • DoH (DNS over HTTPS):将 DNS 查询伪装在标准的 HTTPS 请求中传输(TCP/UDP 443)。
  • DoQ (DNS over QUIC):通过 QUIC 协议传输 DNS(通常为 UDP 853)。

加密以后,你与解析器之间的网络观察者通常不能直接读取查询内容,但递归解析器仍然知道你查询了什么。

此外,目标连接的 IP、连接时间和流量大小也不会因为 DNS 加密而自动隐藏。

DNS 污染

所谓 DNS 污染,是指当你查询某个特定域名时,收到了被恶意伪造的错误 IP,导致无法与真实网站建立连接。

一种是在无 TLS 下 (UDP 53) 路径上的审查设备直接抢答伪造 IP 直接返回,一种是 DNS 服务器缓存被污染了,给你返回错误的 IP。

比如访问 github.com,DNS 服务器 223.5.5.5:

image.png

本应该解析到 ASN 是 microsoft.com ASN 的 IP,图中新疆地区明显拿到了错误的 IP ,这就是典型的对Github 进行了DNS 污染。

代理场景的 DNS 解析

有两种常见方式:

  • 本地解析:你的设备查域名 → 得到 IP→ 把 IP 交给代理服务器建立连接
  • 远端解析:你的设备把网站域名交给代理服务器 → 代理服务器查询 DNS → 代理服务器连接目标。

这会影响:

  1. 哪一侧的 DNS 解析器会记录你的访问意图
  2. 本地接入网(运营商/防火墙)能否看到针对该域名的 DNS 查询或触发本地 DNS 污染
  3. 最终分配到哪里的 CDN 节点(本地解析可能拿到针对国内优化的 CDN,也可能是被污染的假 IP)

很多所谓的“DNS 泄漏”,就是用户希望目标域名通过代理查 DNS ,实际却在本地发出了 DNS 查询记录,可以在 browserleaks 查看你的 DNS 服务器以及有没有 DNS 泄露。

代理服务器自身的域名解析与目标网站的域名解析是两种情况。如果你的代理节点填的是一个域名,你的设备在连上代理之前,依然得先通过某种本地方式把这个节点域名解析成 IP。

TCP、UDP 与 QUIC

  • TCP:面向连接,提供可靠、有序的字节流传输。
  • UDP:无连接,提供简单、无状态的独立数据报传输。
  • QUIC:构建在 UDP 之上,在用户态自己实现了连接管理、多路复用、可靠重传与 TLS 1.3 加密。

QUIC 很适合与 TCP 比较,但它在传输层是 QUIC 放在 UDP 里面。

TCP:一条有编号、会重传的连续传送带

假设应用要发送 ABCDEFGHIJ ,网络中可能出现丢失、重复和乱序。TCP 使用序号、确认(ACK)、重传等机制,在正常工作时把它整理成可靠、有序的字节流交给接收方。

例如:

1
2
3
先收到:ABC
又收到:GHIJ
中间缺:DEF

在 DEF 重传补齐之前,操作系统内核的 TCP 协议栈会暂存 GHIJ,不会把它们提前交给应用,这就是后面提到的队头阻塞。此外,TCP 还具备流量控制(防止接收方处理不过来)和拥塞控制(防止把网络管道塞爆)。

有握手和重传就意味着 TCP 适合 Web 浏览、文件传输、API 等对可靠性要求极高的场景。

TCP 有三次握手建立连接,加 TLS 需累加握手耗时:

1
2
3
客户端 → SYN
服务器 → SYN + ACK
客户端 → ACK

可以粗略理解为双方确认通信状态、交换初始序号并建立连接状态。这个握手没有完成网站身份验证,也没有自动给数据加密。

另一个必须理解的特点是:TCP 没有规定应用消息怎么分割。 发送方分两次发送:

1
2
第一次:HELLO
第二次:WORLD

接收方可能一次读到并后的数据(粘包):

1
HELLOWORLD

也可能因为网络分片或缓冲区原因,分三次才读完(拆包):

1
2
3
HE
LLOWO
RLD

所以,使用 TCP 的应用协议需要自己设计分帧结构,例如使用长度字段、分隔符或者固定格式。这也是理解代理协议数据包格式的基础:协议中为什么经常会出现 长度、类型 这些字段。

UDP:一次次独立投递的数据包

UDP 无需像 TCP 一样握手,优势就是极低开销与延迟,但是丢包不重传、无原生拥塞控制,需应用层自理。

UDP 本身不负责保证:

  • 数据一定到达;
  • 数据按顺序到达;
  • 丢失的数据自动重传;
  • 对方已经建立了类似 TCP 的连接状态。

例如:

1
2
发送:数据包 1、数据包 2、数据包 3
收到:数据包 3、数据包 1

UDP 不会自动替应用补上数据包 2,也不会替应用排序。

如果应用需要可靠性、拥塞控制或会话管理,就要在 UDP 之上设计相应机制 (例如 QUIC、KCP)。

UDP 非常适合实时音视频、在线游戏等场景,因为应用可以自己决定如何处理丢包和乱序。例如语音中,一个很晚才补回来的片段,可能已经没有播放价值。

但 UDP 在实际网络中不一定比 TCP 快。中国国内运营商就经常对跨网或跨境的 UDP 包进行严格的 QoS 限速甚至随机丢包。实际表现取决于应用设计、网络条件、丢包情况及上层机制。

QUIC 在 UDP 之上更完整的传输机制

QUIC 不只是把 TCP 数据简单装进 UDP。它自己实现了连接管理、确认、丢失恢复、拥塞控制和多流复用,并结合 TLS 1.3 建立安全连接。

QUIC 的握手耗时缩短至 1-RTT 或 0-RTT(快速重连)。

QUIC 的优点是应用层单连接多流解耦 (比如同一条 QUIC 连接,有三个流,一个请求网页,一个请求图片,一个请求 JS),彻底消除了传输层队头阻塞;支持基于 Connection ID 的无缝连接迁移(如 Wi-Fi 切蜂窝网络不中断)。

QUIC 一个流内的数据通常仍然需要按顺序交付。但流 A 的某段数据丢失时,流 B 已完整收到的数据,可以继续交付,这缓解了 TCP 单一有序字节流造成的跨流等待。

不过,多个 QUIC 流底层仍然共享同一条物理网络路径和同一个拥塞控制窗口,一个 UDP 包也可能包含多个流的数据。因此,丢包依然可能影响多个请求,只是阻塞机制有所不同,不会像 TCP 一样丢一个包整条连接等待。

HTTP/1.1、HTTP/2 与 HTTP/3

HTTP 定义了浏览器与网站之间如何表达请求和响应。现在访问一个普通网页,大部分请求都是通过 HTTP 协议传输的。

版本常见传输方式主要组织方式
HTTP/1.1TCP;HTTPS 时再使用 TLS纯文本格式的请求行、头部与正文
HTTP/2通常为 TLS + TCP二进制分帧、单连接多流复用、HPACK 头部压缩
HTTP/3QUIC,底层是 UDP映射到 QUIC 流、头部压缩

注:HTTP/2 规范虽然允许明文运行),但主流浏览器仅支持基于 TLS 的 HTTP/2;而 HTTP/3 底层的 QUIC 则强制要求 TLS 1.3。

HTTP/1.1 纯文本协议

它长这样:

1
2
3
4
GET /article HTTP/1.1
Host: www.example.com
User-Agent: ExampleBrowser
Authorization: Bearer token

GET 是 HTTP 规定的请求方法,RESTful API 还规定了 POST、PUT、DELETE等。 /article 是 HTTP 规定的请求路径,后面 HTTP/1.1 是协议版本。 下面的Host、User-Agent 是请求头,比如网站认证用的 Authorization: Bearer token 就是请求头里的一行。

它表示客户端希望获取 /article。

它的瓶颈应用层队头阻塞。在同一个 TCP 连接上,请求只能串行响应(必须等上一个请求完整返回,才能发送下一个响应),为了缓解这个问题,浏览器通常会对同一个域名并发建立最多 6 条独立的 TCP 连接。

HTTP/2 把一条连接分成多个逻辑流

HTTP/2 基于 TCP,引入二进制分帧层,将数据切分成帧,实现单连接多路复用,多个请求和响应在同一条 TCP 连接中交错并发传输,配合 HPACK 算法对重复的 HTTP 头部进行压缩。

也就是不同请求的数据被组织成帧,帧中带有 Stream ID。接收方只需根据帧头里的 Stream ID 把交错到达的数据分配回各个请求,这样就实现了单连接多路复用。

它的瓶颈转到了 TCP 层队头阻塞。底层仍是单一 TCP 连接,网络一旦丢包,TCP 的滑动窗口会停滞等待重传,导致所有并发 Stream 全部受阻。

HTTP/3 抛弃 TCP,拥抱 QUIC (UDP)

在 HTTP/3 中,不同的 HTTP 请求直接映射到底层不同的 QUIC 流上,并将头部压缩算法升级为适配乱序传输的 QPACK。由于 QUIC 在传输层就已经实现了各流独立排序与重传,单一请求丢包只会导致该请求对应的流等待,其他请求完全不受干扰。

这里的改进可以概括为:

  • HTTP/1:一个 TCP 一个 HTTP 流,浏览器建立多条 TCP 连接
  • HTTP/2:多个 HTTP 流,共用一条 TCP 有序字节流
  • HTTP/3:多个 HTTP 流直接跑在独立的 QUIC 流上

HTTP/3 仍然可能因为共享拥塞控制、应用依赖或头部压缩状态等原因发生等待,并未彻底消除了所有阻塞。

现代代理协议为了对抗审查,极少再裸奔自定义二进制格式,而是大量伪装成 HTTP 连接(例如借用 HTTP/1.1 升级机制的 WebSocket、基于 HTTP/2 的 gRPC/H2 传输,以及伪装成 HTTP/3 网站的 Hysteria2 / TUIC)。此外,代理中的多路复用(Mux)与连接并发特征,也与 HTTP/2、HTTP/3 的机制相关。

协议封装、外层流量与内层流量

协议封装指的是应用层协议,比如 HTTP/1.1 就是把内容按照 HTTP 规定的格式塞进 TCP 除了头之类的正文里传输。

例如一个 HTTPS 包的结构: image.png

代理加入以后,会出现外层通信和内层通信。 假设这条 HTTPS 连接经过了基于 TLS 协议的代理:

image.png

外层 TLS 你的设备与代理服务器之间建立,在代理服务器结束。代理解开这一层后,可以读取代理协议中的IP、SNI、认证信息等内容。如果里面承载的是浏览器与网站之间的 HTTPS,代理继续看到的仍然是内层 TLS 密文。

也就是说,在同一次网页访问中,逻辑上同时存在两层加密会话:

  • 外层加密:你的设备 ↔ 代理服务器
  • 内层加密:你的浏览器 ↔ 目标网站

而在传输层,TCP 连接通常被代理切成了独立的两段:

  • 第一段 TCP:你的设备 ↔ 代理服务器
  • 第二段 TCP:代理服务器 ↔ 目标网站

这种嵌套 TLS in TLS 的特征后续会讲到。

加密基础:AEAD、密钥交换与前向安全

AEAD 同时提供内容保密和完整性验证

AEAD(Authenticated Encryption with Associated Data,带关联数据的认证加密) 是现代对称加密的标准范式(如 AES-GCM、ChaCha20-Poly1305),不仅保证数据的机密性(加密不可读),还同时生成消息认证码(MAC)以保障完整性(防篡改);此外,它允许将未加密的元数据(如网络报头)一同纳入防篡改校验计算。

假设原始内容是 转账金额:100 ,只考虑保密性,是希望旁观者无法读取这个金额。 完整性则要求:攻击者即使截获通信,也无法把它有效修改成 转账金额:900,防止让接收方把修改后的内容当成合法消息接受。

AEAD 可以粗略理解为:

1
2
3
4
5
6
密文 + 认证标签 = AEAD 加密(
    密钥,
    一次性随机数 nonce,
    明文,
    附加认证数据
)

不过,AEAD 也有限制:

  • 它不会自动隐藏密文长度(明文多长,密文加上固定长度的 Tag 就是多长)
  • 它不会自动隐藏通信双方 IP
  • 它不会自动解决所有重放问题(重复发包)
  • 它验证的是数据与密钥之间的关系,不能单独证明密钥持有者就是你想访问的网站

为什么需要密钥交换

对称加密(AES、ChaCha20)速度快,但前提是双方已经有同一把密钥。拿到这把密钥有两条路:

  1. 预共享密钥(PSK):提前约定好,Shadowsocks 的密码就属于这种。
  2. 密钥交换:在公开信道上现场协商出一把只有双方知道的密钥。

就像双方先公开一种底色,各自偷偷加一种只有自己知道的颜色,把混合结果交给对方,再各自往收到的混合色里加上自己的私有色。两人最后得到的颜色相同(协商出的共享密钥),而窃听者只看到两份混合色,没法把私有色分离出来。

数学表达是:公开底数 $g$ 和大素数 $p$,客户端选私钥 $a$ 发送 $g^a \bmod p$,服务端选私钥 $b$ 发送 $g^b \bmod p$,最终双方都能在本地算出相同的共享密钥 $g^{ab} \bmod p$。现代网络协议普遍采用基于椭圆曲线的 ECDH,其中最常见的是 X25519,它的公钥体积非常紧凑,只有固定的 32 字节。

DH 只解决"怎么得到共享密钥",不解决"对面是谁"。没有认证就会被中间人攻击,所以必须配合认证手段:证书签名、PSK、密码,或者 REALITY 里的服务端公钥。

前向安全

定义:长期密钥将来泄露,过去的通信仍然安全。

每次建立新连接时,双方生成一次性的临时密钥对做 DH 密钥交换,用完销毁。服务端长期私钥只负责认证,不参与加密会话密钥。

对比 TLS 1.2 里的 RSA 密钥传输:客户端用服务器公钥加密 premaster secret,如果服务器私钥日后泄露,被录下来的历史流量就能全部解开。这就是 harvest now, decrypt later。TLS 1.3 删掉了静态 RSA 密钥交换,只保留临时密钥交换,所以 TLS 1.3 一定有前向保密。

后量子

前向保密防的是长期私钥将来泄露,不防 量子计算机将来破解 ECDH。被录下来的 X25519 握手,将来同样可能被量子计算机还原。目前主流标准是混合密钥交换:X25519 + ML-KEM-768(X25519MLKEM768),只要两者有一个没被攻破,会话就是安全的。

image.png

浏览器已经在 ClientHello 里带上这个 key_share,比如 Chromium 131,Safari 26 以上。

X25519MLKEM768 key_share 从 32 字节涨到约 1216 字节,ClientHello 整体变成 1.5 KB 以上,常常被拆成两个 TCP 包。

image.png

mihomo 的 uTLS 实现为了规避兼容性问题,在相关场景下回退使用了较老的 Chrome 120 指纹,而 sing-box 则曾用新版浏览器指纹手动 trim 掉 X25519MLKEM768 。这会导致 ClientHello 与新版 Chrome 的 JA4 指纹完全对不上(关于 TLS 指纹与 JA3/JA4 的细节,我们将在系列第三篇中深入剖析)。这种魔改 ClientHello,可能变成一种特征。

Page views — Unique visitors —
Built with Hugo
Theme Stack designed by Jimmy