Skip to content

第 5 章 为什么需要代理协议 ​

返回目录 · 编写进度

第 2 章中的客户端直接建立目标连接。实际使用代理时,浏览器可能先连接本机监听端口,本机代理再连接远端服务器,远端服务器最后连接网站。屏幕上只有一个网页,网络中却存在多个端点和多段独立状态。要理解现代代理,首先必须把这些连接分别画出来。

5.1 转发解决什么问题:把一次访问拆成两段连接 ​

设应用 A 想访问目标 T,但 A 不能直接到达 T,而能够访问中间服务器 P;P 又能够访问 T。最简单的代理安排就是:A 连接 P,告诉 P 最终目标;P 新建到 T 的连接,把两个方向的数据相互转送。

这里最重要的事实是:普通 TCP 代理终止了两段 TCP 连接。A—P 与 P—T 各自握手、确认、重传、控制发送速率、维护缓冲区和结束状态。P 不会把 A 收到的 TCP 首部逐字节发给 T;它从一段连接获得应用层字节,再把这些字节写入另一段连接。两段的端口、序号、分段方式和拥塞状态不必相同。

正在绘制图表…

查看图表原文
flowchart LR
    A[应用 A] -->|TCP 连接一| P[代理 P]
    P -->|TCP 连接二| T[目标 T]

为具体说明,使用文档地址构造一组端点。端口值仅为本例选择,不是操作系统分配保证:

连接客户端端点服务器端点谁建立连接
A—P192.0.2.10:52000198.51.100.20:1080A
P—T198.51.100.20:41000203.0.113.30:443P

T 通常看到 P 的出口地址与端口。若 P 的网络还经过 NAT,T 看到的是 NAT 转换后的端点。A 的原始地址不会因“使用代理”自动成为 T 所见 TCP 对端。P 可以在某些应用协议中附加原始来源信息,但那是额外的应用语义,不是把原来的 TCP 连接延伸到了 T。

为什么还需要代理协议?因为连接 P 的 1080 只告诉操作系统把字节交给 P 的监听服务,没有告诉 P 最终连接哪里。代理协议在已建立的 A—P 字节流上表达目标、请求类别、必要身份信息和结果。P 解析这些控制信息以后,才知道应该建立什么下游连接。若 P 只转发到一个预设目标,可以省掉动态目标请求;那是一种固定转发服务,能力范围与通用代理不同。

连接成功的含义也逐层改变。A—P 的 TCP 建连成功,表示 A 已到达 P 的监听服务;不保证 P 接受了请求。P 返回代理层成功,表示本次请求达到了该代理协议定义的成功阶段;对常见 TCP 连接请求,这通常意味着下游连接已经建立。目标是否返回了有效网页,还要由目标应用判断。某些实现可能使用提前成功等优化,必须依其具体承诺分析,不能把一个实现的行为当成所有协议规则。

这两段连接把故障和信任同时移动了。A 不再自行决定下游 TCP 重传,但要信任 P 按请求连接目标。P 可以知道访问的数值地址,也可能直接得到域名。即使目标业务内容受到端到端安全传输保护,P 仍然参与连接建立并观察时序和字节数量。代理提供了路径选择与转发能力;内容保密、身份验证和元数据隐藏需要分别讨论。

这种工作方式并不限于跨越连接限制。企业出口访问控制、缓存网关、服务入口和代理链都能采用中间服务;差别在于它代表哪一方、终止什么协议、是否理解上层内容以及能够选择什么目标。HTTP 对中间节点与隧道的术语约定见 RFC 9110 §3.7;本节的端点和两连接模型是对常见用户态 TCP 转发路径的机制分析,不涵盖所有透明代理和内核转发方式。

5.2 正向代理与反向代理:中间节点代表哪一方 ​

从拓扑上看,正向代理和反向代理都可能是“客户端—中间服务—后端”。区分两者,不能只看箭头指向,更不能只看软件名字。关键是中间服务替谁接受请求、由谁选择它,以及请求中的目标怎样被解释。

正向代理通常由客户端一侧选择,用来代表客户端访问外部服务。客户端知道自己正通过代理,请求里会以某种方式表达最终目标。浏览器向一个 SOCKS5 服务请求连接某网站,就是这种角色。服务端可以限制可访问目标,但它提供的抽象仍是“替你访问被请求的目的地”。

反向代理位于服务一侧,对外代表一个或一组服务。客户端访问的是公开服务入口,通常不需要知道后端地址,也不应凭任意目标字符串让入口连接任何互联网主机。入口依据服务名、路径、既定映射或负载分配规则选择后端。HTTP 规范也用 gateway 一词描述代表源站工作的中间节点,并把它与客户端选择的 proxy 区分。RFC 9110 §3.7。

设 www.example.com 对外落在入口 R,实际内容由 B1、B2 提供。浏览器访问这个站点时,只知道 R 对外提供的服务身份;R 可以把不同请求送给不同后端。反过来,浏览器配置 P 为正向代理,请求它连接 www.example.com:443,P 提供的是访问通道,而非成为这个网站公开身份的代表。

角色与理解深度又是两个维度。中间服务可以仅处理 TCP 字节流,也可以终止、解析并重新生成 HTTP 请求。TCP 转发常以连接为单位选择后端;HTTP 反向代理则可以在一条入口连接上的不同请求之间作出选择。若它还终止了浏览器的 TLS,会在此处得到解密后的 HTTP,再根据策略以明文或另一条 TLS 连接访问后端。密码学细节留到第 16 章;此处只需区分两条安全会话的端点。

设计问题客户端选择的通用正向代理服务侧 HTTP 反向代理
客户端为什么连接它已配置它作为访问中介它代表所访问的服务入口
最终目标怎样确定代理请求表达,受策略限制受控的后端映射和请求路由
后端是否通常向客户端公开客户端通常知道最终网站客户端通常无需知道后端
是否必然解密网站内容否,隧道可以只转送密文若终止 HTTPS,能看到对应明文
安全关注点用户权限、目的地限制、转发隐私服务身份、后端信任、路由与请求解释

表格比较的是两种常见模式,不是对所有代理功能作穷尽分类。同一个程序可以在不同监听端口承担两个角色。一个流量路径也可以同时包含正向代理、服务入口和后端代理,必须按每一跳重新判断。

“透明代理”则描述客户端如何被引流、是否显式感知代理,而不是正向与反向的第三个互斥类别。操作系统、路由器或虚拟网卡可以把原本发向目标的流量交给代理;代理需要从原始目的信息、映射或其它上下文恢复目标。是否配置了浏览器代理,不能直接证明有没有中间处理。

这一区分对 CDN 尤其重要。常见网站 CDN 入口代表特定域名接受 HTTP 或 HTTPS,按其平台规则联系源站;它不是任意 TCP 字节通道。一个代理协议要经由这样的入口,需要落在平台实际支持的应用层服务和转发规则之内。仅仅让一段流量看起来包含 TLS,并不能推导它能经过任意 CDN。后面的承载章节将分别分析 HTTP、WebSocket、gRPC 和 XHTTP,而不把“用了某端口”视为兼容证明。

5.3 本地代理与远程代理:两个入口之间还需要一次翻译 ​

实际客户端常同时展示“本地 SOCKS 端口”和“远端 VLESS 节点”。这不是同一个协议的两个端口。本地入口面向浏览器等应用,远端节点面向代理客户端;二者之间可以采用完全不同的控制格式和安全传输。

用 B 表示浏览器,L 表示本机代理进程,R 表示远端代理,T 表示网站。一个常见的显式代理路径有三段 TCP 连接:B—L、L—R、R—T。B 请求 L 访问 T;L 把目标信息转换成远端协议格式;R 解析后建立到 T 的连接。对浏览器而言,代理是 L;对远端服务器而言,协议客户端是 L,浏览器通常不直接实现远端协议。

正在绘制图表…

查看图表原文
flowchart LR
    B[浏览器 B] -->|本地 SOCKS5 或 HTTP 代理| L[本机代理 L]
    L -->|远端代理协议及其承载| R[远端代理 R]
    R -->|目标连接| T[网站 T]

举例说,浏览器告诉本地 SOCKS5 服务“连接 example.com:443”,本地服务不会把整个 SOCKS5 会话原样当作 VLESS 请求发出。它先解析 SOCKS5 方法协商和目标,选择路由,再生成 VLESS 需要的请求。远端返回的信息也要映射回本地入口所需的语义。详细的格式在相应章节讲解;此处的重点是协议终止和重新表达,不是连续叠放所有握手字节。

目标应用的数据则可以在这些翻译后的连接中保持相同顺序。浏览器为网站产生的一串 TLS 字节,可以由本地代理作为不透明数据承载;本地代理无需知道其中网页内容。远端代理也可以仅把它们送给网站。此时浏览器—网站的安全会话跨过代理进程,但底下的 TCP 连接已经分成三段。“安全端点不变”和“传输连接不变”是不同陈述。

若 L—R 另外有安全传输,那又是一组端点:外层保护 L 到 R,内层保护 B 到 T。R 能终止外层而仍看不到内层业务明文。若目标应用本来就是明文,则 R 在去掉外层后会看到明文。双层加密的准确成本和 Vision 的优化,都依赖这里的层次区分;现在只建立端点关系,不提前假定具体密码结构。

本地与远程也是相对部署位置的描述,不是安全等级。监听回环地址可以限制普通远端主机直接连接该端口,却不自动隔离同机其它进程;监听局域网地址则让入口暴露给更多网络主体。认证、监听范围、路由和操作系统权限共同决定谁能使用它。不能仅凭“本地代理”认定入口数据已经可信。

一份配置至少可能包含四种不同的地址:本地监听地址、远端服务器连接地址、远端身份验证所用名称,以及每次请求的最终目标。这些地址可以碰巧相同,也可以完全不同。把用于验证远端身份的名称误当成最终网站,会把两层会话混为一层;把远端服务器地址当作最终目标做全部路由判断,则可能无法表达按网站分类的策略。

还需要区分控制流量与业务流量。本地代理为连接 R 而进行的 DNS 查询、建立连接以及必要的前置访问,是它自身的网络活动;浏览器请求 T 的流量是它所承载的业务。若把建立远端连接的请求再次交回同一个尚未建立的代理路径,可能形成路由环。实际软件用出站绑定、路由例外或其它机制避免它,具体方式由实现决定,不能靠协议名字保证。

因此分析复杂配置时,应先在纸上写出:谁主动连接谁,每一段使用什么控制格式,谁恢复目标信息,哪两方持有同一安全会话。完成这一步以后,节点列表、入站、出站和代理组才有明确的数据路径含义。

5.4 隧道与双向转发:字节复制背后有流量约束 ​

代理建立隧道以后,“把两边数据互相复制”描述了目标,却省略了多数工程问题:两边速度不同,数据可能暂时写不出去,一方可以先结束发送,一次错误又可能使另一个方向的等待失去意义。可靠转发需要管理这些状态,而不只是循环读取。

一个普通 TCP 隧道可以看成两个独立的数据方向。上行从客户端连接读取,写入目标连接;下行从目标连接读取,写回客户端连接。两方向可以同时有未完成操作,不能把“等上行结束,再处理下行”作为一般方法,否则请求需要响应才能继续的应用可能互相等待。

对于上行,设已经从客户端取得 R 字节,其中已向目标的发送接口交付 W 字节,还有 Q 字节待写;在没有额外转换和丢弃时,应始终满足 R=W+Q。这里 W 表示下游本地接口接受的数据,不代表目标应用已经消费。若一次只写入部分,剩余字节仍在 Q 中;重复提交已接受部分会造成重复,直接丢掉剩余部分会造成缺失。

当目标读取慢于客户端发送,Q 会增长。若代理无限读取并分配缓存,它并没有让网络变快,只把瓶颈转化为内存消耗和排队延迟。背压是让下游处理能力限制上游继续生产:达到缓存水位时,代理暂停读取客户端;客户端一侧内核接收空间逐渐耗尽,再通过 TCP 的接收窗口使发送方减速。这个过程有缓冲延迟,不是一个瞬间传遍所有节点的信号。

text
客户端发送
    ↓
客户端—代理 TCP 接收缓冲
    ↓  代理可暂停此处读取
代理的有界待写队列
    ↓  目标较慢时此处受阻
代理—目标 TCP 发送缓冲
    ↓
目标接收与应用读取

链路上缓冲越多,能够临时吸收的突发越大,但积压后的等待也可能越长。多路复用又把问题进一步分成连接级和逻辑流级:一个目标慢,不应必然让所有不相关目标占满同一个无限队列。具体窗口与调度规则到 HTTP/2、QUIC 和 XHTTP 章节再解释;此处只确立资源有界和压力可传递的要求。

正常结束要按方向传播。客户端结束发送,并不意味着它不再接收返回。代理应先写完这个方向已经接受的字节,再向目标表达相应的发送方向结束;目标仍可返回剩余内容。若一收到客户端的正常 EOF 就关闭两个 Socket,可能截断最后的响应。反过来,发生不可恢复连接错误时,继续无限等待另一个方向也不合理,需要取消关联操作。具体错误与结束信号怎样映射,要尊重承载层是否支持半关闭。

UDP 转发不是把这套字节流逻辑原样搬过去。UDP 向应用交付的是数据报,报文之间有边界;它本身不承诺可靠送达、按序到达或消除重复,也没有 TCP 式连接关闭序列。代理需为每个报文保留必要的目标和会话关联,并定义映射何时失效。发送成功同样不能证明目标收到。RFC 768。

若 UDP 业务通过一个流式承载转送,代理协议必须额外提供报文定界,才能从连续字节恢复原始数据报。若 UDP 业务通过数据报承载转送,则仍要考虑整个封装后的大小,过大报文可能触发分片或被路径丢弃。“支持 UDP”因而至少包含三个问题:怎样表达目标、怎样保留报文边界、怎样处理丢失和尺寸限制,不能只看配置开关。UDP 应用的拥塞与尺寸责任参见 RFC 8085。

最后,隧道并不意味着内容一定无法观察。它只说明某一层把内部数据当作所承载的内容,不再按原来的控制消息逐条处理。明文 HTTP 穿过 TCP 隧道仍是明文;网站 TLS 穿过隧道可以保持自己的安全端点;隧道本身是否加密又是另一个问题。后续协议名称中出现“隧道”“流”或“数据报”,都应先还原这种服务语义,再讨论其安全属性。

5.5 NAT、VPN、TUN 与代理:相似路径,不同服务抽象 ​

“出口 IP 变了”是一个可见结果,不能据此判断内部采用 NAT、VPN 还是应用代理。三者可能组合使用,也可能产生相似的外观;真正的区别在于它们处理哪种对象、在哪里终止连接,以及向应用提供什么服务。

传统 NAT 修改经过节点的 IP 地址;NAPT 进一步修改 TCP/UDP 端口等传输标识,维护内外映射。典型家庭路由器把多个内网端点映射到一个公网地址的不同端口。对经过它的普通 TCP 流,NAT 转换相关首部和校验信息,但通常不为每条流终止 TCP,再以本机应用身份新建第二条 TCP;两端 TCP 的数据序号空间仍属于端主机那次通信。RFC 3022 §2。

因此“有状态”不能作为应用代理与 NAT 的分界。NAT 有映射状态,防火墙可以有连接跟踪状态,但它们不因此成为终止应用字节流的代理。某些设备兼有应用网关、TCP 优化或代理功能,应该分别分析,而不能把它们的附加能力都归入基本地址转换。

以 10.0.0.2:52000 访问 203.0.113.30:443 为例,NAPT 可以把上行来源改成 198.51.100.20:41000,下行按映射改回原端点。客户端通常仍认为自己连接的是目标地址;它没有向 NAT 发送一条“请替我连接此域名”的 SOCKS5 请求。NAT 按包处理这条连接,应用代理则通过自身协议获知目标并维护另一段传输连接。

VPN 是更宽泛的网络互联或隔离服务类别,不能把所有 VPN 都等同于一种固定加密格式。对本书常见的远程访问、三层隧道式 VPN,客户端通常把待转发的 IP 包封装到另一条通信通道,远端恢复内层包并按路由转发。外层连接的对端是隧道节点,内层 IP 包仍表达业务来源与目标。安全 VPN 还为节点与内容提供相应保护,但“封装”这个动作本身不等于加密。

这种三层隧道可以承载多种上层流量,因为它处理完整 IP 包。应用代理则通常表达“建立到某地址的字节流”或“转发这一个数据报”,不必承载原始 IP 首部。两种模型会影响路由、MTU、ICMP 和传输状态;它们都可能在远端再经过 NAT,使网站看到统一出口。

TUN 又不是 VPN 协议。它是操作系统提供的虚拟网络设备接口。以 Linux 为例,TUN 让用户态进程读取和写入网络层 IP 包,TAP 则面向以太网帧。进程读到 TUN 包之后如何处理,由程序决定:可以加密并隧道转发,可以交给用户态协议栈恢复 TCP/UDP 流,也可以丢弃。仅仅创建 TUN 设备,不会自动建立远端安全连接。Linux TUN/TAP 文档。

由此可以理解常见的 TUN 代理模式。应用以为自己在按普通 IP 路由访问目标;系统路由把包交给 TUN;代理软件通过协议栈或相关机制恢复连接与数据,再转成远端代理请求。这里至少包含“捕获 IP 包”和“建立代理流”两项职责。它提供了无需每个应用显式支持代理的入口方式,却没有因此把远端 VLESS 或 SOCKS5 变成完整 IP 隧道。

机制主要处理对象典型 TCP 状态位置是否天然含通用保密能力
传统 NAT/NAPTIP 包与地址、端口映射端主机;设备另有映射状态否
普通 TCP 应用代理请求目标和应用字节流各段连接端点,包括代理否,取决于保护层
三层隧道式 VPN内层 IP 包与外层通道内层端主机;外层另有状态安全性取决于具体 VPN 协议
TUN 接口供用户态收发的 IP 包由读取它的程序架构决定否

判断实际软件模式时,要顺着数据从入口走到出口。若只是把“使用虚拟网卡”“网站看到不同 IP”“设置了代理端口”列成特征,就可能把完全不同的处理路径混在一起。后面的性能章节也不能只比较界面上的 TUN 开关:用户态协议栈、原生网络栈、隧道封装和应用流转发的成本不同,需以真实路径归因。

5.6 DNS 的责任在哪里:解析路径与业务路径分别画 ​

访问域名最终通常要选出可连接的数值地址。这个选择可以发生在应用、本机解析器、本地代理、远端代理或服务入口。代理请求支持域名,只是提供了把名称交给另一方的表达能力,并不强制整台计算机所有 DNS 都在远端进行。

一个典型解析过程是:应用向本机的解析接口询问名称,本机向递归解析器请求结果;递归解析器利用缓存或继续查询,返回地址等记录。DNS 自身区分多种角色,应用并不一定直接问权威服务器。缓存可能使某次业务访问没有新 DNS 包,也可能让当前连接使用先前获得的地址。RFC 1034 §5。

把代理插入以后,至少有三种不同输入:

应用交给代理的内容谁为目标选择地址对应责任
已选定的 IPv4/IPv6 地址应用或它使用的解析系统代理主要按数值地址连接
原始域名接收该域名并建立连接的一方代理一侧解析并选择可达地址
由本地代理分配的合成地址本地映射先恢复名称,再按路由决定需要保持合成地址到名称的映射有效

第三种机制只用于解释一种架构,不是所有 TUN 模式的共同要求。合成地址不代表真实服务器就在这个地址上;它可能只是本地系统中的查询索引。若绕过处理该映射的组件,把它当普通公网地址送给不知情的远端,就无法按原意访问网站。理解这类机制必须把 DNS 应答、映射表、连接捕获与名称恢复连起来。

即使远端解析目标,本机也可能需要解析远端代理服务器自己的域名。这是建立代理路径的前置工作,与路径建好后为业务目标解析不是同一次查询。如果解析服务本身还要通过代理访问,就需要明确启动时如何获得代理端点,否则可能形成循环依赖。已经缓存的地址可以暂时掩盖问题,却不改变依赖关系。

DNS 隐私应按观察者分别说明。未加密 DNS 路径上的观察者可能看到问题名称与应答;递归解析服务能够处理查询,所以即使连接使用加密传输,它仍然知道接收到的名称;权威服务器可见的查询与来源则取决于递归流程、缓存和查询方式。使用远端解析可能把可见性从本地网络移动到另一方,不会让名称从系统中消失。RFC 9076 §5—6。

“DNS 泄漏”因此需要一个预期作为参照。若期望所有业务名称只交给选定的远端解析服务,但应用仍向本地网络的解析器查询,就是相对于该策略的外泄;若策略本来允许某些内网名称本地解析,就不能仅因出现本地查询而判定失败。具体判断应包含名称类别、发起进程、预期解析服务、实际路径与保护范围。

加密 DNS 也不保证与业务连接走同一路由。它可以经本地直连访问解析服务,而网站连接经代理;也可以相反。网络观察者仍可能看到解析服务的 IP、连接时序和数据量,而解析服务仍能看到查询。这里所说“加密”只用于说明可见性边界,DoH、TLS 和连接复用的具体格式在后文展开。

还有两个容易混淆的结果。其一,DNS 得到地址不等于该地址从代理出口可达:本地与远端可能处在不同网络,分区解析也可能返回只在某一网络有效的结果。其二,DNS 返回一个地址不等于完成网站身份验证:名称到地址的映射和连接对方证明身份,属于不同阶段。错误答案、缓存旧值、地址切换与身份不符也需要分别解释。

按域名或 IP 制定路由策略时,还要看决策发生在什么时候。拿到域名就能匹配名称规则;只有数值地址时,若没有可靠映射或可见上层信息,就不能假装仍知道原始名称。相同 IP 可以承载许多名称;根据某次 DNS 记录把 IP 永久等同于一个网站会过度推断。第 8 章分析多级代理时,将把每一跳看到的名称和地址逐一列出。

因此一幅完整通信图至少应有两组箭头:解析请求及其返回,业务连接及其数据。它们可以重合、交叉或经过不同出口。只检查网站出口 IP 变了,不能证明 DNS 满足策略;只看到 DNS 在代理内,也不能证明所有业务连接都经过代理。

5.7 完整转发实例:一次网页请求经过哪些解释器 ​

现在让浏览器 B 通过本地代理 L、远端代理 R,访问 http://example.com/。这是依据已说明机制构造的通信案例,不是一次现场抓包;端口和地址均为教学值。为了看清谁能够解释业务字节,先使用明文 HTTP。HTTP 完整规则在第 9 章展开,此处只使用第 1—2 章已有的最小请求响应。

假设浏览器通过本地 SOCKS 入口提交原始域名,本地代理按路由选择 R,R 负责解析最终目标;L—R 使用一个能传递目标和字节流的远端代理协议,不在此虚构其具体头部。R 的地址已经由明确的前置解析过程获得。

阶段当前对象该阶段得到的信息
浏览器准备访问URL http://example.com/网站名称、路径和默认端口 80
浏览器连接 L127.0.0.1:1080本地代理入口,不是网站地址
L 接收代理请求域名 example.com、端口 80建立业务连接的意图
L 连接 R例如 198.51.100.20:443远端代理端点;这里的 443 不表示网站端口
R 解析目标example.com本例假设选择 203.0.113.30
R 连接目标203.0.113.30:80实际业务连接

表中的 DNS 结果仅为演示选址,不声称公共 example.com 实际解析到此文档地址。浏览器虽未对最终目标发起本地 DNS,L 与 R 仍要处理收到的名称,R 使用的解析服务也会得到相应查询。

当远端请求被接受并建立目标连接后,R 的成功信息沿 L 返回。L 按本地入口协议完成对应响应,浏览器才开始发送 HTTP 请求。真实协议可能允许提前数据;本例选择严格等待成功的路径,以明确各阶段的依赖。

浏览器交给本地已建立隧道的目标内容是以下 37 字节,\r\n 表示真实的 0d 0a,而不是四个可见字符:

text
GET / HTTP/1.1\r\n
Host: example.com\r\n
\r\n

L 的 SOCKS 请求解析器已经退出控制阶段,这 37 字节不能再次解释成 SOCKS 方法协商。L 把它们交给远端出站编码,R 去掉属于该出站协议的控制与承载信息,再把同样顺序的 37 字节写进目标连接。中间可以发生多次短读短写、分段或记录切分;只要语义是可靠字节流,最终 HTTP 解释不应依赖这些切分。

目标在本例返回一条 40 字节的完整响应:

text
HTTP/1.1 200 OK\r\n
Content-Length: 2\r\n
\r\n
OK

其状态行连同行结束是 17 字节,长度字段连同结束是 19 字节,空行二字节,正文二字节。R 转发完整响应,L 恢复目标流,浏览器按 HTTP 解析出正文 OK。页面内容只有二字节,却不意味着整次访问只传了二字节:目标 HTTP 有请求和响应元数据,代理控制、承载、安全握手和传输层还有各自成本。

正在绘制图表…

查看图表原文
sequenceDiagram
    participant B as 浏览器
    participant L as 本地代理
    participant R as 远端代理
    participant N as R 使用的解析服务
    participant T as 目标 HTTP 服务
    B->>L: 本地代理协商与域名连接请求
    L->>R: 建立远端通道并表达目标
    R->>N: 查询 example.com
    N-->>R: 本例选择的数值地址
    R->>T: 建立 TCP 到目标端口 80
    T-->>R: 建连完成
    R-->>L: 远端代理请求成功
    L-->>B: 本地代理请求成功
    B->>L: HTTP 请求 37 字节
    L->>R: 承载该请求字节
    R->>T: 恢复后的 HTTP 请求
    T-->>R: HTTP 响应 40 字节
    R-->>L: 承载返回字节
    L-->>B: 恢复后的 HTTP 响应

把 URL 改成 HTTPS 后,连接请求的最终端口通常变为 443,随后浏览器首先发送的是给网站的 TLS 握手数据,HTTP 请求位于网站安全会话内部。L、R 若只做隧道转发,不会因终止自己的代理协议就获得网站 HTTP 明文;但是目标、连接量和时序仍可能可见。若只有 L—R 加密而网站端是本例的明文 HTTP,R 去掉外层后就能看到完整请求响应。两种情况不能混称为“代理已经加密,所以所有环节都看不到内容”。

若 R 无法解析,浏览器的失败发生在代理控制阶段;若 R 能建连但目标稍后关闭,则发生在业务阶段;若收到 HTTP 错误状态,则说明目标应用已经有可解析的响应。对使用者而言都可能呈现“网页打不开”,但三者对应不同解释器和不同责任方。精确画出路径,才能判断错误究竟在哪一层产生。

5.8 “支持代理”究竟支持到哪一层 ​

同一台计算机上,浏览器能访问网站,某个实时应用却连不上;启用 TUN 后,网页工作正常,某种诊断流量仍没有结果。这些现象不必然说明代理协议失效。应用需要的服务抽象,可能超出了入口或整条链路实际提供的能力。

如果一条路径只支持 TCP 连接请求,它可以承载基于 TCP 的 HTTP、网站 TLS 以及其它合适的字节流应用,但不能因此原样转发 UDP 数据报。一个应用可能同时使用 TCP 登录和 UDP 实时数据;前者成功不能证明后者有可用通道。入口即使能够捕获 UDP,下游每一跳也都需要提供相容的数据报服务或明确的转换机制。

同样,典型流代理不承载任意 IP 包。ICMP 回显、某些多播通信、原始 IP 协议和应用直接访问本地网络的行为,都可能不落在“建立 TCP 流或转发 UDP 数据报”的范围里。不能用一个依赖 ICMP 的诊断结果,单独证明某网站的 TCP 代理请求一定不能成功;也不能用 TCP 成功推断所有网络工具都应工作。

应用层回调又有另一种要求。若应用把“请连接回我的地址和端口”写进业务内容,普通出口代理不会自动把这个地址改成别人可达的入口,更不会自动开放回程连接。此时仅转送原始字节可能完全正确,业务却仍无法完成。支持这种交互需要协议或网络架构另外提供相应能力;后面的 SOCKS5 BIND 就是一个与单向 CONNECT 不同的命令,但它也不是对所有应用回调问题的通用自动修复。

另一类误判来自代理协议与目标协议的版本混淆。本地入口使用 HTTP 代理,不代表网站连接必须使用 HTTP/1.1;一个 CONNECT 隧道可以承载目标自己的安全会话,随后由端点决定里面的应用协议。反过来,外层承载使用 HTTP/2,也不代表它自动为任意目标提供原生 HTTP/2 语义;它可以只是在传递一条内层字节流。

实际判断可以沿一条明确的请求展开:应用向哪种入口发了什么,入口成功恢复了什么目标,路由选择了哪种出站,每一跳是否提供应用需要的流或数据报服务,最终目标是否接受这类通信。这里没有一个“全部开启”的标志可以替代整条路径的条件。

第 6 章从这些服务需求进入 SOCKS5。它的三个主要命令分别表达建立出站连接、等待入站连接和建立 UDP 关联,不是一个含义模糊的“转发开关”。理解它们各自的状态、端点和响应字段,也就能看到真实代理协议怎样把本章的抽象变成线上字节。

本章的基础依据包括 HTTP 中间节点术语、传统 NAT、UDP 服务、UDP 使用约束、Linux TUN/TAP 接口、DNS 解析角色 与 DNS 隐私边界。其中 Linux 接口描述不作为所有操作系统的共同实现;转发路径和有界缓存分析按正文声明的模型成立。

从字节与状态机出发,理解每一层的职责。