从0开始搞懂计算机网络:IP、DNS、TLS 到 GFW 与翻墙技术全解析
写在最前
原文来自大神的github。我只是一个卑鄙的搬运工。以下为原文全文:
文章基于我以前的笔记整理而成,原版笔记是接近三万字的长文。我让 Claude 做了表达上的润色,但保留了原文的所有核心内容。
最开始的写作动机是写给自己看的,以前我对相关领域的一些问题感到好奇(例如代理软件是如何工作的),所以自学了这方面的内容。文章对其他读者可能会有少许帮助,所以发布了它。
希望从“第一性原理”出发,让不熟悉计算机网络的读者,能够快速入门这个领域,了解相关的概念与常识。
需要注意的是,本文章仅仅讨论技术原理,不会手把手教读者实操方法,还清谅解!
引言
当你在浏览器里输入 https://github.com 并按下回车,到网页真正显示出来之间,其实发生了非常多层的”接力”:设备要先找到彼此、要确认对方身份、要保证内容不被偷看、还要把域名翻译成地址……这篇文章会把这整个过程拆开,一层一层地讲清楚,最后甚至会讲到”翻墙”技术是如何在这套体系的缝隙里”见缝插针”的。
阅读建议:文章是按照”地基先打好,再往上盖楼”的顺序写的,前面出现的概念,后面会反复用到。如果读到某个地方觉得一个词很陌生,往前翻一翻,多半在前面某一节已经解释过了;如果确实是一个要放到后面才方便讲的概念,我会在当下明确提示”这个我们后面会讲”。建议按顺序读完,而不是挑着看。
目录
- 分层的基本思路:为什么网络通信要分好几层
- IP 地址:网络世界的门牌号
- 网关与子网掩码
- NAT:如何让一群设备共用一个公网 IP
- MAC 地址与 ARP:同一根网线上,怎么区分是谁
- DHCP:谁来自动配置这些参数
- 完整流程串讲:连上 Wi-Fi 之后发生了什么
- DNS:域名与 IP 之间的翻译官
- TCP:把”完整送达”这件事做扎实
- TLS(上):如何确认对方真的是 Google
- TLS(中):被监听的情况下,双方如何凭空生成同一把密钥
- TLS(下):加密算法的演变与量子计算的威胁
- SNI:TLS 握手时,如何告诉服务器你要访问哪个网站
- HTTPS 的握手成本,以及 HTTP/3 带来的改进
- CDN 与边缘缓存:以 YouTube 为例
- BGP:互联网世界是怎么互相”指路”的
- GFW(上):防火墙是怎么识别可疑流量的
- GFW(下):识别之后,会采取哪些封锁手段
- VLESS 协议:轻装上阵的代理协议
- REALITY 技术:伪装成真实网站
- XTLS Vision:抹除”套娃”痕迹
- 一次翻墙请求的完整旅程:六层包装 vs 直连四层结构
- Nginx 反向代理:REALITY 出现之前的”伪装”方案
- 代理软件的分流模式:直连、全局、规则
- 系统代理 vs TUN 模式:为什么有些软件死活连不上代理
- 混合端口:一个端口如何同时兼容两种协议
- Fake-IP:如何防止 DNS 泄露,顺便提速
- HTTP 代理:为什么它必须”看得懂”你的内容
- SOCKS5 协议:纯二进制、还支持 UDP
- 结语
1. 分层的基本思路:为什么网络通信要分好几层
计算机网络的设计者们发现,如果把”怎么找到对方””怎么保证内容不丢””怎么保证内容不被偷看””内容到底是什么”这几件事混在一起做,会非常混乱。于是他们把这些工作拆成了不同的”层”,每一层只管自己的事情,层与层之间通过约定好的接口配合。大致是这样的:
- 最底层负责”怎么把电信号送到隔壁设备”(物理层,这篇文章不涉及,可以先不管);
- 再往上一层负责”怎么在全世界的设备里,找到你要发的那个设备”(网络层,靠的是 IP 地址);
- 再往上一层负责”怎么保证发出去的数据完整、不丢、不错序地到达”(传输层,也就是 TCP,或者它的轻量版 UDP);
- 再往上一层负责”怎么保证这些数据不被路上的人偷看、篡改,并确认对方身份”(这一层主要靠 TLS 协议);
- 最上层才是”数据具体的内容是什么”(应用层,比如网页用的 HTTP 协议)。
这篇文章大致会按”网络层(IP)→ 传输层(TCP)→ 加密层(TLS)→ 应用层(DNS/HTTP 等)”的顺序讲下去,最后再讲跟”防火墙””翻墙”有关的内容——它们其实就是在这几层里”打游击”。现在,先从最基础的”地址”问题开始。
2. IP 地址:网络世界的门牌号
网络里的每一个设备,不管是你的手机、电脑,还是 Google 的服务器,都需要有一个类似”门牌号”的身份,这样数据包才知道要送到哪里去。这个门牌号就是 IP 地址(IP 是 Internet Protocol 的缩写,这里不需要死记这个全称,只要记住”IP 地址 = 网络世界的地址”就够了)。
IP 地址分两种:
公网 IP
这种地址在全世界范围内不能重复,因此管理起来比较复杂,需要有机构统一分配。最顶层是国际机构 ICANN(互联网名称与数字地址分配机构),它下面有几个区域性机构负责具体地区的分配(比如亚太地区的 Asia-Pacific Network Information Centre),再往下才是我们日常打交道的运营商(ISP,比如中国电信、中国联通)。
以前,通常每个家庭路由器都能分到一个专属的公网 IPv4 地址。后来 IPv4 地址不够用了(因为 IPv4 地址一共只有 43 亿个左右,远远不够全世界所有联网设备平分),于是发展出了两条解决路线:
- CGNAT 技术:可以理解成运营商层面的一种”合用地址”技术,让多个用户共享同一个公网 IPv4 地址。它具体是怎么”共用”的,跟下面第 4 节要讲的 NAT 原理是一回事,读完那一节你就会明白了。
- IPv6 协议:简单理解为设计了一套地址空间大得多的新标准,长期来看能从根本上解决”地址不够用”的问题。
内网 IP
在一个局域网(比如你家 Wi-Fi)里,可以给每个设备设一个地址,比如 192.168.1.10。不同局域网(比如你家和邻居家)之间,这些地址是可以重复的,互不影响;但同一个局域网内部,这个地址不应该重复,否则会”打架”。
3. 网关与子网掩码
网关:你的设备要访问局域网以外的世界(也就是互联网)时,需要有一个”出口”帮忙转发,这个出口就叫网关,一般由路由器来担任。你家路由器的内网 IP 通常是 192.168.1.1(这也是为什么很多人在浏览器地址栏输入这个地址就能打开路由器的设置页面)。
子网掩码:前面说 IP 地址像门牌号,但一个 IP 地址其实包含两部分信息——“网络部分”和”主机部分”,有点像”小区名 + 门牌号”。问题是,这两部分各占多长并不是固定的,所以需要一样东西来告诉设备:”IP 地址里,前面多少位是小区名,后面多少位是门牌号”——这就是子网掩码的作用。
子网掩码的写法是一串”前面全是 1、后面全是 0”的数字,比如 255.255.255.0。举个例子:
1 | IP: 192.168.1.10 |
意味着:
1 | 192.168.1 ← 网络部分(可以理解成"小区") |
同一个局域网里,每个设备都会记住自己的内网 IP,以及这个局域网共用的子网掩码。这些设备的 IP 地址不应重复,但子网掩码应该完全一致——因为它决定了”哪些 IP 和我是邻居(同一个小区),哪些不是”,这个判断在后面第 6、7 节会用到。
4. NAT:如何让一群设备共用一个公网 IP
在讲 NAT 之前,先补一个小概念:端口。你可以把端口理解成同一个 IP 地址下,用来区分不同”入口”或”会话”的一个数字标签(范围是 0–65535)。比如同一台电脑上,浏览器同时开着好几个网页,或者同时开着好几个联网软件,操作系统会给每一个连接分配不同的端口号,这样返回的数据才知道该交给哪个具体的程序。
好,现在来看 NAT。设备要访问互联网时,需要经过路由器的一道”翻译”手续,这就是 NAT(Network Address Translation,网络地址转换)。它有点像查表:把”内网 IP + 内部端口”映射成”公网 IP + 外部端口”。对于同一个路由器下的不同设备,它们对外呈现出来的公网 IP 通常是一样的(也就是路由器自己的公网 IP),而”外部端口”这个数字,就是用来区分到底是家里的手机发的包,还是电脑发的包。
(回过头看第 2 节提到的 CGNAT:它就是把这套逻辑再往上抬一层——多个家庭的路由器,共享运营商手里的同一个公网 IP,靠端口号来区分是哪一家的流量。)
5. MAC 地址与 ARP:同一根网线上,怎么区分是谁
既然已经有了 IP 地址,为什么还需要另一种地址——MAC 地址?
MAC 地址可以理解成设备在局域网里的”身份证号”,用来在同一个物理网络(比如一根网线,或同一个 Wi-Fi)上定位到具体是哪台设备。它最初是为了解决一个问题:一根网线上如果接了好几台设备,怎么区分”这个信号该给谁”?
IP 数据包本身是不包含 MAC 地址的。真正记录”某个 IP 对应哪个 MAC 地址”的,是路由器里的一张表,叫 ARP 表(ARP,Address Resolution Protocol,地址解析协议)。路由器收到要发往局域网内某个设备的数据包时,会看一眼目标 IP,再去 ARP 表里查出对应的 MAC 地址,然后把数据包”装进”一个包含 MAC 地址的外壳(这个外壳叫以太网帧)再发送出去。
需要说明的是:虽然理论上,像 Google 这样的远端服务器根本不需要、也不会知道你的 MAC 地址(因为 MAC 地址只在局域网内部起作用,出了你家路由器就不会再被用到),但现实中,有些软件会”耍小聪明”,把设备的 MAC 地址塞进应用层的数据里,用来做设备识别或绑定——不过这并不是规范或推荐的做法。
MAC 地址是怎么来的? 每块网卡出厂时,厂商都会烧录一个 MAC 地址,不同网卡的地址原则上不会重复(但这只是行业约定,并不是 100% 保证)。而且 MAC 地址也是可以用软件手动修改的。
MAC 地址会被缓存吗? 会。前面说的 ARP,本质上就是一张”IP 地址 → MAC 地址”的缓存表。
ARP 的工作过程举例:假设局域网里有电脑 A 和电脑 B,如果 A 想跟 B 通信,但还不知道 B 的 MAC 地址,就会发起一次 ARP 广播(”广播”的意思是喊给局域网里所有设备听):
1 | 电脑A广播:"谁是 192.168.1.20?请告诉我你的 MAC 地址" |
这样 A 和 B 就知道了彼此的 MAC 地址,可以正式建立通信。
ARP 的隐患:从上面的过程可以看出,ARP 依赖的是”广播 + 自报家门”,没有任何验证机制。理论上,一个心怀不轨的人完全可以冒充”我是 192.168.1.20”,从而截获本该发给真正 192.168.1.20 的数据——这种攻击叫 ARP 欺骗。也就是说,ARP 的设计前提是”局域网里的设备都是可信的”,一旦这个前提不成立,ARP 本身没有能力自保。
也正因为像 ARP 这样的底层协议默认”网络是可信的”这件事并不总是成立,所以在网络设计的更上层,比如后面第 10 节要讲的 TLS 协议,才会反过来假设”数据包在传输路上随时可能被截获、被篡改”,并专门针对这种”不可信”的情况设计加密和验证机制。这条线索先记在心里,后面会再提到。
6. DHCP:谁来自动配置这些参数
如果每次有新设备连入局域网,都要手动给它配置 IP 地址、子网掩码、网关地址,会非常麻烦,而且很容易配错(比如不小心配了一个和别的设备重复的 IP)。于是就有了 DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)——可以理解成局域网里的一个”自动管家”,专门负责在有新设备加入时,自动告诉它:你的 IP 地址是多少、子网掩码是什么、网关(出口)是谁,以及需要用到的 DNS 服务器(下一节会讲)在哪里。
在家庭网络里,这个”管家”的工作一般就是由路由器兼任的。
7. 完整流程串讲:连上 Wi-Fi 之后发生了什么
把前面几节的概念串起来,一部手机连上家里 Wi-Fi 后,大致会发生这样一串事情:
1 | 手机连接 Wi-Fi |
有意思的是,家庭网络里其实很少真正用到”局域网内部通信”这条路径,大部分场景都是在访问互联网。局域网内部通信的例子包括:电脑连接家里的网络打印机、手机投屏到电视、访问 NAS(一种放在局域网里的私人存储设备)等等——这类通信只需要经过局域网内部的交换设备,不需要经过路由器的 NAT,也不用真的”出海”到互联网上。
8. DNS:域名与 IP 之间的翻译官
现在有了 IP 地址的概念,可以讲 DNS 了。
我们平时访问一个网站,比如 Google,并不会在浏览器里输入一串 IP 地址,而是输入一个好记的域名(比如 google.com)。但前面讲过,网络世界真正用来”寻址”的是 IP 地址,域名只是给人类用的、方便记忆的”别名”。那么,域名到 IP 的转换是谁来做的呢?——这就是 DNS(Domain Name System,域名系统)的工作,可以把它理解成一本巨大的”电话簿”,专门负责把域名”翻译”成对应的 IP 地址。存储着这些”域名 → IP”对应关系、专门负责回答查询的服务器,就叫 DNS 服务器。
前面第 6 节提到过,路由器会通过 DHCP 把 DNS 服务器的地址一并告诉你的设备,这个 DNS 服务器一般是你的网络运营商维护的,下面简称”ISP DNS”。
查询过程:如果 ISP DNS 这里之前没有缓存过这个域名对应的 IP,它就会代替你去问互联网上最顶层的根 DNS 服务器。根 DNS 服务器不会直接告诉它 google.com 的 IP,而是告诉它:”我不知道 google.com 具体是谁,但我知道该去问谁——去问负责 .com 这个后缀的 DNS 服务器”。ISP DNS 于是转头去问 .com 的 DNS 服务器,对方又告诉它:”具体的 google.com 由这个 DNS 服务器负责,去问它”。这样一层一层地问下去,最终问到了 google.com 真正对应的 IP 地址,ISP DNS 再把这个结果返回给你。
灵活的对应关系:同一个域名,可以被解析到不同的 IP(也就是不同的服务器)上,这在大网站中很常见,通常是为了负载均衡(把访问压力分摊到多台服务器上)。反过来,多个域名对应同一个 IP 地址,也很常见,也就是一台服务器同时托管着好几个不同的网站——这个结论在后面第 13 节讲 SNI 时还会用到,先记住。
DNS 的隐患:跟前面 ARP 类似,DNS 的查询过程本身也是可能被”动手脚”的,比如 DNS 缓存投毒(有人往 DNS 服务器的缓存里,塞入一条虚假的”域名 → 错误 IP”记录)。这也是为什么,访问网站还需要有更上层的安全机制来兜底——这正是我们接下来要讲的 TLS。
关于 DNS 缓存,具体查询顺序:
- 本地查询:先看浏览器自己的缓存 → 再看操作系统的缓存 → 再看本地的 Hosts 文件(一个可以手动指定”域名 → IP”的本地文件)→ 再看路由器自己的缓存。只要有一处命中,就不用再往下问。
- 递归查询:如果本地都没有,操作系统才会把请求发给 LDNS(本地 DNS 服务器,通常就是前面说的 ISP DNS,或者你手动指定的公共 DNS,比如 114.114.114.114)。如果 LDNS 自己也没缓存过,它就会代替你,按照上面”根 DNS →
.comDNS → 具体域名 DNS”的顺序逐级查询,查到结果后缓存下来,再返回给浏览器。
9. TCP:把”完整送达”这件事做扎实
现在可以正式讲 TCP 了。
前面第 1 节提到过,网络层(IP)只负责”寻址”,告诉数据包该往哪个方向送,但它是个“尽力而为”的协议,不保证数据一定能完好无损地到达。在复杂的网络公路上,数据包可能会丢失,也可能因为走了不同的路线,导致后发的包反而先到(错序)。
为了解决这些问题,传输层诞生了两个设计理念截然相反的协议:UDP 和 TCP。
9.1 UDP:追求极致效率的“轻量化”协议
在讲严密的 TCP 之前,我们先来看看它的兄弟 UDP(User Datagram Protocol,用户数据报协议)。
为了追求极致的速度,UDP 选择了一种极为精简直白的通信机制。
- 类比:UDP 就像是寄普通的明信片。你写好地址、贴上邮票往邮筒里一扔就结束了。至于对方有没有收到、收到时明信片有没有污损、阅读的顺序对不对,你一概不进行后续的确认。
- 特点:速度极快,开销极小。因为它不需要在发送数据前与对方“握手”建立连接,也不需要等待对方回复确认。
- 适用场景:对实时性要求极高、丢一点数据也无所谓的场景。比如实时视频通话、在线游戏。在看网络直播或打游戏时,如果因为网络抖动丢失了其中一两帧画面,我们宁可直接跳过去播放最新的画面,也绝不希望画面卡死在原地去等待重传。
9.2 TCP:极其严密的“挂号信”
而 TCP(Transmission Control Protocol,传输控制协议)则相反。它的终极目标是:保证发送方发出去的数据,接收方能一个不落、按顺序、完好无损地收到。
为了实现这个承诺,TCP 会把 IP 数据包封装进一个结构更加复杂的“信封”(TCP 报文),并在上面写满了各种控制信息:
- 端口号(Ports):IP 地址只负责找到你家(电脑),但要把数据准确交给电脑里的哪个软件(是微信还是浏览器?),就得靠端口号。
- 序号(Sequence Number, Seq):给发送的每个数据段编上号(比如:这是第 1 段、第 2 段、第 3 段)。这样即使数据包在路上跑乱了顺序,接收方也能根据序号把它们重新排好。
- 确认号(Acknowledgment Number, Ack):接收方用来回复发送方:“我已经安全收到第 2 段了,接下来请发第 3 段吧。”
- 控制位(Flags):像一排小开关,用来表达当前的状态。比如
SYN(表示“我们建立连接吧”)、ACK(表示“收到!”)、FIN(表示“事情办完了,拆除连接吧”)。
9.3 经典的三次握手:为什么不能是两次?
TCP 是面向连接的。在正式传输任何数据之前,双方必须先进行三次握手(Three-way Handshake),确认彼此都做好了准备。
我们可以用一个“无线电通话”的例子来直观理解:
- 第一次握手(SYN):
- 客户端说:“呼叫服务器,你能听见我说话吗?”
- 第二次握手(SYN + ACK):
- 服务器说:“听到了!你能听见我说话吗?”
- 第三次握手(ACK):
- 客户端说:“我也听到了。完毕,我们开始通信!”
❓ 为什么必须要三次?两次握手(服务器一答应就立刻建立连接)行不行?
不行。 设想一个常见的网络异常场景:
- 客户端发送了第一个“建立连接”的请求,但由于网络太拥堵,这个请求卡在某个路由器里了。
- 客户端等了很久没收到回应,以为丢包了,于是重新发了第二个请求。这一次网络通畅,服务器收到并答应,双方完成了通信,然后关掉了连接。
- 然而,很久之后,那个卡在路上的第一个请求突然“活”了过来,传到了服务器手里。
- 如果只需要两次握手:服务器只要一收到请求,就会立刻认为“对方又要建立新连接了”,于是回复一句“好的”,并在内存里为这个连接做好了准备,傻傻地等待客户端发送数据。但客户端此时根本没有建立连接的需求,服务器的资源就白白浪费了。
- 如果是三次握手:服务器收到滞后的请求后,会问一句:“你能听见我说话吗?”客户端一看,心想“我没发过这个请求啊”,于是拒绝理会。服务器没有收到第三次握手确认,就知道这是一个无效的旧连接,直接将其废弃。这就避免了“幽灵连接”浪费服务器资源。
9.4 四次挥手:体面的告别
既然建立连接要握手,那么事情办完、准备断开连接时,就需要进行四次挥手(Four-way Wave)。
这是因为 TCP 是“双工”的,这意味着两个方向的数据传输是彼此独立的,双方都需要单独关闭自己的通道。
- 第一次挥手(FIN):
- 客户端说:“我的数据发完了,我想关闭我的发送通道了。”
- 第二次挥手(ACK):
- 服务器说:“我知道了。但我手里还有最后一点数据没发完,你等我一下。”(此时客户端不能再发数据,但依然可以接收服务器剩下的数据)。
- 第三次挥手(FIN):
- 服务器数据发完了,说:“好了,我的数据也发完了,我也要关闭我的发送通道了。”
- 第四次挥手(ACK):
- 客户端说:“好的,我知道了,再见!”(客户端等待一小会儿,确保服务器收到这个再见信号后,双方彻底关闭连接)。
9.5 拥塞控制:网络世界的“防踩踏机制”
TCP 不仅关心“你和服务器两端”,它还非常关心“中间经过的网络路径堵不堵”。
如果网络上的所有设备都不管不顾地全速发送数据,中间的路由器就会因为处理不过来而疯狂丢包。这会导致大家一起重传,让网络陷入恶性循环。为了避免这种情况,TCP 拥有一套精妙的 拥塞控制(Congestion Control) 机制。
它的核心思想是:“小心试探,一旦发现拥堵立刻踩刹车。”
- 慢启动(Slow Start):刚开始连接时,TCP 并不知道中间的路况。它不会一上来就倾泻所有数据,而是先发 1 个数据包试试水。如果对方安全收到,说明路况良好,下一次就发 2 个;再下一次发 4 个、8 个……像滚雪球一样指数级增加。
- 拥塞避免:当发送量达到一个安全的临界值时,TCP 会变得谨慎起来,不再成倍增加,而是改成线性增加(9个、10个、11个这样慢慢加),一点点试探网络的极限。
- 自我降速:在试探过程中,一旦发生丢包(说明网络已经开始塞车了),TCP 会立刻触发紧急避险。它会瞬间将发送速度减半,甚至“降回 1 个数据包重新开始试探”。
正因为全球所有联网设备里的 TCP 协议都在默默遵守着这套“发现拥堵就自我降速”的规则,我们的互联网公路才不至于因为车流量太大而陷入永久的瘫痪。
10. TLS(上):如何确认对方真的是 Google
设想一个场景:你在浏览器里访问一个普通的 HTTPS 网站,比如 Google。你的浏览器要如何确信,眼前应答的服务器真的是 Google 本尊,而不是黑客伪装的呢?
这正是 TLS(Transport Layer Security,传输层安全协议)要解决的首要问题。这一层的核心目标,是防止连接被“破解”或“冒充”——如果没有这层保护,黑客理论上可以伪装成 Google 的服务器,从而窃取你发送的账号、密码等隐私信息。
在正式了解 TLS 握手机制之前,我们需要先补充两个密码学里的“基础设施”:哈希(Hash)和非对称加密。
准备工具一:什么是“哈希”(数字指纹)?
在网络世界里,如果我们要比对两份超长的数据是否完全一致,不需要逐字逐句对比,只需要比对它们的“指纹”就行。这个“指纹”生成器就是哈希算法。
你可以把哈希理解为一种极度压缩、且不可逆的“数字摘要”:
- 任意长度输入,固定长度输出:不管输入的是一句话,还是一部 10G 的高清电影,经过哈希算法处理后,都会变成一段固定长度的字符。
- 单向不可逆:你可以轻易通过原文算出指纹,但绝对无法通过指纹反推出原文。
- 雪崩效应:只要原文稍微改动一个字符,算出来的指纹就会千差万别。
因为这些特性,哈希在网络中常被用来做“数据完整性校验”——如果两份数据的哈希值一模一样,那我们就可以确信它们的内容完全一致。
准备工具二:非对称加密与“数字签名”
在密码学里,有一对形影不离的钥匙:公钥和私钥。
- 它们本质上是一串特殊的超长数字:并不是我们物理生活中的钥匙。
- 它们在数学地位上是完全对等的:它们成对诞生,且互为对方的“解药”。用其中任意一把钥匙加密(锁上)的数据,只有用另一把钥匙才能解密(打开)。
为了安全,我们把锁在自己保险柜里、绝不给别人的那串数字叫 “私钥”;把可以大方公开给所有人的那串数字叫 “公钥”。
基于这种“互为唯一解药”的对等特性,便有了以下两种玩法:
玩法 1:公钥加密,私钥解密(用于“机密通信”)
别人用你的公钥把一段普通的明文加密成一段密文。
这封信在网上传输,由于只有手握私钥的你才能把它还原成原本的明文,黑客如果尝试用别的钥匙去解密,只会还原出一堆毫无意义的乱码。这确保了传输内容的绝对机密。
玩法 2:私钥加密,公钥解密(用于“身份认证”,即数字签名)
假设我对外声称我是某位富豪,你手上有这位富豪公开的公钥,但你无法确认屏幕后面坐着的是不是我本人。
- 你拿出一个大家都看得见的公开文本 X(比如写着“你好”)。
- 我用我手里的私钥对这段文本 X 进行加密,得到了另一串密文 X’(这个用私钥加密的行为,就叫做 “签名”;得到的密文 X’,就是我的 “签名结果”)。
- 我把密文 X’ 发给你。你拿到后,尝试用你手里的公钥去解密。
- 解密后,如果你成功还原出了那个公开文本 X(而不是一堆乱码),那就绝对能证明:我确实是私钥的持有者。因为在数学上,除了我手里的这把私钥,世界上没有任何其他钥匙能加密出“可以被这把公钥解密、且刚好能还原成 X”的密文。
工具合体:真正的“数字签名”是如何工作的?
读到这里,你可能会产生一个疑问:既然“玩法 2”已经能完美验证身份了,为什么我们前面还要讲“工具一(哈希)”呢?
因为在实际的网络传输中,我们要签名的网页、证书等文件往往非常大。非对称加密的计算非常消耗算力,如果直接用私钥去对几兆甚至几百兆的整个文件进行加密,电脑和服务器会直接卡死。
所以,聪明的密码学家把这两个工具结合在了一起:
- 先用哈希瘦身:我们要发送一个 10MB 的大文件。我们不直接加密它,而是先用哈希算法,把它提炼出一个极短的、唯一的“数字指纹”(比如只有 32 位字符)。
- 再用私钥签名:我们用私钥,只对这 32 位字符的“数字指纹”进行加密。这个加密出来的极小数据包,就是最终的数字签名。
- 公钥验签:接收方收到大文件和签名后:
- 用公钥解密签名,得到指纹 A。
- 自己对收到的 10MB 大文件算一次哈希,得到指纹 B。
- 比对 A 和 B。如果 A == B,既证明了文件没被篡改过(哈希一致),也证明了发送方确实是私钥持有者(解密成功)。
🚨 接下来,我们将同时用到“两套”不同的公钥/私钥。为了防止混淆,请一定要看清它们的名字:
- 第一套(CA的钥匙):
CA公钥与CA私钥。专门用来证明“证书是不是真的”。- 第二套(网站的钥匙):
网站公钥与网站私钥。专门用来证明“对方确实是网站的主人”。
第一步:Google 是如何“申领身份证”的?
在 Google 刚建站的时候,它不能空口白牙地跟用户说“我是 Google”。它需要一个权威机构来背书。
- 生成密钥对:Google 在自己内部生成了
网站公钥和网站私钥(网站私钥锁在 Google 的核心服务器里,绝不外传)。 - 去 CA 机构申请证书:Google 带着自己的网站信息(如域名 google.com)和
网站公钥,找到了世界上公认的可信机构 —— CA(Certificate Authority,证书颁发机构)。 - CA 制作证书并签名:
CA 机构核实了 Google 的身份后,把 Google 的信息和网站公钥打包,做成一张证书(Certificate)。为了防止这张证书被伪造,CA 用自己的CA私钥对这张证书进行了“签名”(即先计算证书内容的哈希值,再用CA私钥对其进行加密)。
一个简化版的证书结构大概长这样:
1 | +----------------------------------+ |
🔑 关键问题:CA公钥 在哪里?
你可能会问,我们验证 CA 的签名,需要CA公钥。那这个公钥又要去哪里找呢?
答案是:预装在你的系统和浏览器里。微软、苹果、谷歌等巨头在发布操作系统或浏览器时,就已经把世界上最权威的几十家 CA 机构的CA公钥(称为“根证书”)悄悄内置在你的电脑里了。
第二步:TLS 握手时的“双重验真”
在正式传输数据之前,TCP 会先完成“三次握手”(简单理解为双方确认“我们都在线,可以开始通信了”)。TCP 握手完毕后,才开始 TLS 握手。
在 TLS 握手的前两步中:
- Client Hello(客户端问好):客户端(比如你的浏览器)主动发起握手,提出希望使用的 TLS 版本、自己支持的加密算法列表,以及一些用来后续生成密钥的随机数等信息。
- Server Hello + 证书:服务器从客户端给出的选项里,挑出双方都支持的版本和算法进行回应,同时把自己的数字证书发给客户端。
收到证书后,客户端浏览器会进行双重验证,以确保对方不仅“拿着真的证书”,而且“真的是证书的拥有者”:
1. 验证“证书是不是真的”(CA 签名验证)
浏览器收到证书后,开始核对:
- 找 CA公钥:浏览器查看证书上写着是哪家 CA 颁发的,然后从自己本地的“信任列表”里找出该 CA 机构的
CA公钥。 - 解密签名(得到 Hash 1):浏览器用
CA公钥去解密证书上的“CA 签名”,得到一个哈希值(我们称之为 Hash 1)。 - 自行哈希(得到 Hash 2):浏览器把证书里的“网站信息 +
网站公钥”提取出来,用同样的哈希算法自己计算一遍,得到一个新的哈希值(我们称之为 Hash 2)。 - 比对:如果 Hash 1 == Hash 2,说明这张证书确实是由权威 CA 颁发的,且中间没有被任何黑客篡改过。
⚠️ 但是,这里有一个致命的安全漏洞!
证书本身是完全公开的,任何人都可以下载。黑客完全可以去访问真正的 Google,把 Google 的真证书下载下来。当你访问黑客伪装的“假 Google”时,黑客直接把这个“真证书”原封不动地发给你。
此时,你的浏览器去验证 CA 签名,依然会得出
Hash 1 == Hash 2。那么,浏览器要如何戳穿黑客的伪装,证明坐在电脑对面的真的是 Google 服务器本尊呢?
2. 验证“对方是不是真的拥有这张证书”(私钥拥有权验证)
为了证明自己不是拿着别人证书的“冒牌货”,服务器必须证明自己持有与该证书里 网站公钥 相匹配的 网站私钥。
在 TLS 握手的过程中,双方会利用之前的握手信息和随机数进行一次交互:
- 生成挑战:在此前的握手阶段,客户端和服务器已经交换了一些随机数和握手参数。客户端会将这些已有的“握手信息”,结合一个新的随机数(例如
123456789),拼接在一起。 - 私钥签名(服务器证明自己):真正的 Google 服务器会用它绝对保密的
网站私钥,对这一大坨拼接信息进行哈希并加密,生成一个 “私钥签名”(在实际的 TLS 1.3 协议中,这一步对应Certificate Verify消息),发送给客户端。 - 公钥验签(客户端戳穿谎言):客户端收到这个签名后,从之前验证通过的“真证书”里取出
网站公钥,对这个签名进行解密。如果解密出来的结果,刚好等于客户端自己手里记录的握手信息 + 随机数,那就证明:- 对方确实拥有与该证书匹配的
网站私钥!
- 对方确实拥有与该证书匹配的
因为 网站私钥只有真正的 Google 才有,黑客手里即便拿着真的 Google 证书,由于他没有 Google 的网站私钥,也绝对无法制造出能被 网站公钥 正确解密的签名。
至此,双重验证全部通过!你的浏览器终于松了一口气:“对方不仅拿着真证书,而且确实是 Google 本尊。”
确认了对方的身份后,双方就可以安全地进入下一步:在被监听的网络中,凭空生成只有双方知道的加密密钥。而这,正是下一节要讲的核心难题。
11. TLS(中):被监听的情况下,双方如何凭空生成同一把密钥
这里有一个非常关键、也非常有意思的问题:接下来双方需要用同一把密钥来加密通信内容,但这把密钥总不能直接在网络上传一份给对方吧?如果直接传,旁边的监听者不就也拿到了吗?那双方到底是怎么在“不让旁听者知道”的前提下,凭空达成一致、生成同一把密钥的呢?
目前 TLS 1.3 普遍使用的是 DH(Diffie-Hellman,迪菲-赫尔曼)密钥交换算法,具体常用的版本是基于椭圆曲线的 ECDHE。这背后涉及不少数学(椭圆曲线相关的数论),但密码学界有一个非常经典的比喻——“混合油漆”,可以帮我们绕开复杂公式,直观理解这个过程是怎么做到的:
🎨 经典比喻:混合油漆
假设客户端(你)和服务器(VPS,可以理解成你在网上租的一台远程服务器)想要拥有一种相同颜色的油漆(这代表最终生成的密钥),但旁边有一个窃听者(比如防火墙)一直盯着你们之间传送的所有东西。你们不能直接把配方告诉对方,因为窃听者也会看到。
- 确定一个公开的初始颜色:你们在网络上公开约定一个颜色,比如黄色。窃听者也同步知道了这个初始颜色是黄色。
- 各自选择一个私密的颜色:你在心里选了一个秘密颜色——红色(不告诉任何人);VPS 也在心里选了一个秘密颜色——蓝色(同样不告诉任何人)。
- 混合并发送:你把公用的黄色和你的私密红色混合,得到橙色,通过网络发给 VPS;窃听者看到了这个橙色,但油漆一旦混合就极难再分离还原,他没法从橙色里反推出你的私密红色是什么。同样地,VPS 把公用的黄色和它的私密蓝色混合,得到浅蓝色,发给你;窃听者看到浅蓝色,同样无法还原出 VPS 的蓝色。
- 生成最终的秘密颜色(对称密钥):你拿到 VPS 发来的浅蓝色(里面含有黄+蓝),再往里加入你自己的私密红色,得到褐色(最终成分:黄+蓝+红)。VPS 这边,拿到你发来的橙色(里面含有黄+红),再往里加入它自己的私密蓝色,同样得到褐色(最终成分:黄+红+蓝)。
此时,你和 VPS 手里都拥有了完全一样的“褐色”油漆——这就是你们接下来用来加密通信的对称密钥(这里“对称”是指加密和解密用的是同一把钥匙,下一节会具体展开)。而一旁的窃听者手里只有:公开的黄色、传输中截获的橙色、传输中截获的浅蓝色。在不知道你们各自私密颜色的前提下,他绝对无法调配出这瓶“褐色”油漆。
12. TLS(下):加密算法的演变与量子计算的威胁
上一节这种“双方各自持有一部分秘密,通过交换计算结果来生成同一把密钥”的方式,属于非对称加密的一种应用(非对称加密是指:加密和解密用的是两把不同但相关联的钥匙,也就是前面提到的公钥和私钥;与之相对,双方最终共用同一把钥匙来加解密的方式,叫对称加密)。
TLS 里用到的非对称加密算法,早期主要使用 RSA 算法(一种基于“大数分解”这一数学难题的算法),到了 TLS 1.3,主流变成了基于椭圆曲线的算法 ECC(Elliptic Curve Cryptography)——这里的“椭圆曲线”跟上一节 ECDHE 里提到的是同一类数学工具,只是应用场景不同:ECDHE 用来做密钥交换,ECC 这里则更多用在身份验证、签名等场景。
不过,无论是 RSA 还是 ECC,理论上在一台足够强大的量子计算机(需要达到数千个量子比特的规模)面前,都是不安全的——量子计算机的某些特性,能够以传统计算机做不到的效率,破解这类算法背后依赖的数学难题。相比之下,像 AES-256 这样的对称加密算法,即便面对量子计算机,依然被认为是安全的。正因为存在这种“未来可能被攻破”的风险,密码学界目前正在推动向后量子密码算法(专门设计用来抵御量子计算机攻击的新一代算法)迁移。
13. SNI:TLS 握手时,如何告诉服务器你要访问哪个网站
前面第 8 节讲 DNS 时提到过一个结论:多个域名可以对应同一个 IP 地址,也就是说一台服务器上,可能同时托管着好几个不同的网站。
这就带来一个问题:在 TLS 握手阶段,服务器怎么知道你到底想访问哪个网站?——毕竟同一个 IP 背后可能有好几个网站,而每个网站的 TLS 证书是不同的(证书是绑定在具体域名上的),服务器如果不知道你要访问哪个域名,就不知道该把哪张证书发给你。
所以,在 TLS 握手的时候,客户端必须通过某种方式,提前告诉服务器:”我要访问的是 www.google.com “。这个信息,就叫 SNI(Server Name Indication,服务器名称指示)。它其实是 TLS 握手过程中用来传递额外信息的一个可选字段(这类字段统称 TLS 扩展)。
这里有一个容易混淆的地方:在应用层(也就是 HTTP 协议)的请求头里,也有一个字段叫 Host,同样是用来告诉服务器”你想访问哪个域名”的。但 SNI 和 Host 不是一回事,它们属于不同的层——SNI 是 TLS 握手阶段(加密层)传递的信息,而 Host 是 TLS 握手完成、正式建立起加密通道之后,在这条通道里发送 HTTP 请求时才会带上的信息。换句话说,HTTP 是建立在 TLS 之上的:必须先完成 TLS 握手,才能在加密好的通道里发送 HTTP 请求。
一个真实的例子:此前曾有一个在中国大陆”直连” Pixiv(一个日本的插画网站)的小技巧,原理跟 SNI 有关——中国的网络审查系统(下面第 17 节会讲到的 GFW)在拦截 Pixiv 服务器时,判断依据之一正是 SNI 字段里的内容。而 Pixiv 的服务器(以及它使用的 CDN 服务商 Fastly)其实并没有严格要求 SNI 必须正确填写:如果收到一个没有携带 SNI(或者 SNI 不对)的请求,它默认会返回一张颁发给 *.pixiv.net(这种写法代表”pixiv.net 下的所有子域名”)的泛域名证书。这就给了绕过 SNI 检测的可乘之机。
14. 从 HTTP 到 HTTPS,以及 HTTP/3 带来的性能飞跃
在前面几节里,我们详细拆解了 TLS 是如何进行证书验证和密钥交换的。你可能会感到好奇:我们费了这么大劲,究竟是为了保护什么?这就需要提到我们日常上网最核心的协议——HTTP。
14.1 什么是 HTTP:浏览器与服务器的“书信往来”
HTTP(HyperText Transfer Protocol,超文本传输协议)是整个互联网的基石。简单来说,它定义了浏览器和服务器之间如何用“大白话”进行沟通。
你可以把每一次上网,想象成浏览器在给服务器写信。
1. 浏览器写的“信”:HTTP 请求(Request)
当你在浏览器输入网址并回车时,浏览器就会当场写一封信发给服务器。这封信全都是纯文本,长得非常朴素。
一个真实的 GET 请求(用于获取网页)大约长这样:
1 | GET /index.html |
我们来拆解一下这封信的格式:
- 第一行(请求行):
GET:请求类型(也叫请求方法),意思是“请给我……”/index.html:要获取的具体网页路径。HTTP/1.1:使用的协议版本。
- 接下来的几行(请求头 / Headers):这是附加的信息。
Host:告诉服务器你要访问哪个网站(一台服务器可能托管多个网站)。User-Agent:自报家门,告诉服务器“我是 Chrome 浏览器”。Accept-Language:告诉服务器“我希望能看中文网页”。
2. 常见的请求类型(Methods)
在写信时,开头第一个词(请求方法)决定了你想让服务器做什么。最常用的有两种:
GET:获取数据。比如你打开新闻首页、看一张图片、下载一个文件。这种请求只是“读取”数据,不会修改服务器上的东西。POST:提交数据。比如你填写账号密码登录、发表评论、上传头像。这时候,信件的底部会多出一个部分,叫 “请求体”(Body),里面装着你提交的具体数据(例如:username=abc&password=123)。
3. 服务器回的“信”:HTTP 响应(Response)
服务器收到信后,会立刻写一封回信寄给浏览器。回信通常长这样:
1 | 200 OK |
这封回信同样有固定的格式:
- 第一行(状态行):
HTTP/1.1:协议版本。200 OK:状态码。它用数字代表这次请求的结果。200 OK:一切正常,你要的网页拿去吧!404 Not Found:你可能在网上经常见到它,意思是“你要的网页不存在(可能被删了或网址输错了)”。500 Internal Server Error:服务器自己的代码崩了,出了点意外。
- 响应头(Response Headers):
Content-Type: text/html:告诉浏览器,回信里装的是一个 HTML 网页,你准备用网页格式渲染它吧。
- 响应体(Response Body):空一行之后,下面就是实实在在的内容(比如网页的 HTML 代码、图片数据等)。
4. 致命的缺点:完全不加密的“明信片”
看完上面真实的 HTTP 信件格式,你会发现一个巨大的安全隐患:所有的内容都是用人类看得懂的纯文本(明文)写的。
发送 HTTP 请求,就像是在邮寄一张没有信封的明信片。明信片在邮寄过程中会经过很多中转站(比如小区的路由器、运营商的交换机、骨干网节点等)。
任何一个中转站如果动了歪心思,都可以轻而易举地偷看明信片上的内容(比如你在 POST 里面提交的账号密码),甚至可以用橡皮擦改掉里面的字(比如往网页里强行插广告、篡改下载链接)。
14.2 什么是 HTTPS
为了解决明信片被偷看和篡改的问题,人们给 HTTP 套上了一层保护壳,这就是 HTTPS(这里的 “S” 代表 Secure,安全)。
简单来说,它们的关系是:
$$\text{HTTPS} = \text{HTTP} + \text{TLS}$$
在 HTTPS 的世界里,浏览器不再直接发送明文的 HTTP 请求,而是先通过我们前面章节讲过的 TLS 协议,与服务器完成证书验真、并协商出一把安全的对称密钥。
一旦这个安全的“加密通道”建立完毕,浏览器就会把原本的 HTTP 请求(也就是上面那封“信”)放进通道里进行加密传输。此时,在外界(中转站、黑客、运营商)看来,传输的数据只是一堆乱码。只有你和服务器这两端,才能用各自手里的密钥解密并阅读真实内容。这样就安全地解决了窃听和篡改的问题。
14.3 安全的代价:什么是 RTT 与传统 HTTPS 的延迟
虽然 HTTPS 带来了安全,但也带来了一个无法回避的副作用:变慢了。
在解释为什么变慢之前,我们需要先了解一个衡量网络速度的关键指标:RTT(Round-Trip Time,往返时延)。
- RTT 指的是一个数据包从你的设备出发,到达服务器,再折返回到你的设备,这一来一回所花费的时间。
传统的 HTTPS 连接在能够传输第一个真正的网页数据(HTTP 请求)之前,必须要在底层完成一系列的“握手”:
- 第一步(TCP 握手):浏览器与服务器建立底层的 TCP 连接。这需要一问一答,消耗 1 个 RTT。
- 第二步(TLS 握手):在 TCP 连接建立后,双方开始进行 TLS 握手(发送 Client Hello、交换证书、协商密钥)。这同样需要一问一答,又消耗了 1 个 RTT。
也就是说,在你真正能发出“帮我打开网页”(GET /index.html)的 HTTP 请求之前,浏览器和服务器已经悄悄在底下往返跑了 2 趟,消耗了 2 个 RTT 的时间。如果用户的网络延迟比较高(比如在偏远地区、移动信号差、或者访问物理距离很远的海外服务器),这 2 个 RTT 的等待就会让网页加载产生肉眼可见的卡顿。
14.4 HTTP/3 与 QUIC 协议
为了消灭这 2 个 RTT 的多余延迟,互联网工程师们在最新的 HTTP/3 协议中进行了一次极为大胆的重构。
既然 TCP 握手和 TLS 握手各需要一趟,那为什么不把它们合并成一趟呢?
在传统的网络架构中,TCP 和 TLS 是各司其职、相互独立的两个层(TCP 负责传输,TLS 负责安全)。而在 HTTP/3 中,工程师们彻底抛弃了底层的 TCP,改用了一个基于 UDP 的全新传输协议——QUIC。
QUIC 最大的创新之一,就是将 连接建立(传输层) 和 密钥协商(加密层) 这两大步骤合并到了一起:
- 首次连接(1 RTT):当浏览器第一次连接服务器时,QUIC 可以在同一次往返(1 个 RTT)中,同时完成连接的建立和加密密钥的协商。随后,浏览器就能立刻发送加密的 HTTP 数据了。
- 复用连接(0 RTT):更神奇的是,如果浏览器之前已经连接过这台服务器,QUIC 会记住之前的加密参数。当再次连接时,浏览器可以在发送第一个数据包的同时把加密数据一起带过去,实现 0 RTT 的极速响应。
通过这种“合并同类项”的设计,HTTP/3 在保证数据绝对安全的前提下,将连接建立的延迟降低了一半甚至更多,极大地提升了我们在弱网环境下的上网体验。
15. CDN 与边缘缓存:以 YouTube 为例
像 YouTube 这样体量巨大的网站,如果全世界用户都只从同一台(或同一个机房的)服务器下载视频,一来服务器根本扛不住这么大的访问量,二来离服务器物理距离越远,传输就越慢。所以,大型网站通常会在世界各地增设大量的”缓存服务器”,提前把内容复制、放到离用户更近的地方——这种”就近提供内容”的服务和网络,就叫 CDN(Content Delivery Network,内容分发网络)。
以我们访问 YouTube 看视频为例,具体发生了什么:
- 我们首先访问的是 YouTube 的网页服务器,但它并不会直接把整个视频塞给你,而是只返回一个很轻量的”网页外壳”(也就是构成页面结构和样式的 HTML/JS/CSS 代码)。
- 浏览器加载完这个外壳后,开始执行里面负责播放视频的那部分 JavaScript 代码(也就是”播放器”程序)。
- 播放器程序向 YouTube 的接口询问:”这个视频真正的媒体文件存放在哪里?”
- 接口返回一组专属于你的、临时的链接,这些链接指向的是 Google 视频 CDN 的域名(离你比较近的缓存服务器)。
- 浏览器拿到这个链接后,直接绕过一开始的网页服务器,向最近的
googlevideo.com缓存服务器发起请求,一片一片地下载视频分片,边下载边播放。
16. BGP:互联网世界是怎么互相”指路”的
接下来讲一个更”宏观”层面的概念。前面讲的 IP、DNS 等,讨论的都是”某一个具体的连接”是怎么建立的。而 BGP(Border Gateway Protocol,边界网关协议)要解决的,是一个更大的问题:全世界成千上万个互相独立的网络(专业上称为 AS,自治系统,Autonomous System,可以理解成一个个独立运营的”网络地盘”,比如一家大运营商、一家大型云服务商各自管理的网络范围),它们之间是怎么互相”指路”、把数据包一步步送到目的地的?
注:在互联网的骨干网络中,负责规划路径和转发数据包的并不是我们家里常用的小交换机,而是体量巨大、专门处理网络间通信的核心路由器(或网关)。
BGP 大致是这样工作的:
- 路由宣告:每一个 AS(网络地盘),都会通过 BGP 协议,向相邻的 AS”喊话”:”我这里负责
X.X.X.X/24这个 IP 段(一种表示一批连续 IP 地址的写法),如果你有数据包要发到这个 IP 段,交给我,我能送到。” - 路径选择:各个路由器把从全球各处收集来的这些”喊话”信息汇总,形成一张非常庞大的路由表,并据此计算出一条最快、最经济,或者最稳定的路径,把数据包一站接一站地传递过去,直到最终送达目的地。
看起来很完美对吧?但这个庞大的指路系统并不是一朝一夕建成的。它的前身和演变过程,经历了一段从“熟人社会”到“信任危机”的历史。
16.1 历史的前辈:EGP 与“熟人圈子”时代
在 BGP 诞生之前,互联网还处于非常早期的阶段。那时候连接不同网络的协议叫做 EGP(Exterior Gateway Protocol,外部网关协议)。
在那个时代,互联网的规模很小,学校、科研机构和早期的运营商统共就那么多家。这就像是一个“熟人社会”,大家彼此知根知底。
- IP 所有权如何管理:当时没有现在这么复杂的自动化管理系统。谁拥有哪些 IP 地址,基本上是由一个中央机构(比如早期的互联网先驱们)在小本本(或一个简单的文本文件)上人工记录和分配的。
- 邻居如何管理:如果一个新机构想要把自己的网络接入互联网,可不是插上电缆就能自动运行的。当时的路由器没有那么智能,网络管理员必须给对方网络的管理员写信或打电话。大家约定好之后,在各自的核心路由器上手动配置对方为“邻居(Neighbor)”。只有双方的配置文件里都写上了对方的名字,路由器之间才会开始交换路由信息。
这就好比一个封闭的熟人俱乐部。新成员想要加入,必须有老会员“手动拉你进群”,否则你连发言的资格都没有。
16.2 BGP 的诞生与早期的“君子协定”
随着接入互联网的设备和机构呈现爆炸式增长,早期的 EGP 协议因为结构单一、无法支持复杂的网络网格,开始力不从心。于是,BGP 协议在 1989 年诞生了。
BGP 能够支持去中心化的、规模庞大的全球互联网,但它在设计之初,依然保留了早期“熟人社会”的信任惯性。
当时,一个新的 IP 地址要想加入互联网,过程是这样的:
- 某家机构向管理部门申请到了一段新的 IP 地址。
- 该机构的网络管理员登录自己的核心路由器,写下一条指令:“宣告(Advertise)我拥有这段 IP 地址。”
- 路由器开始向周围的邻居高喊:“这段 IP 在我这里,要发往这里的流量请给我!”
然而,早期的 BGP 协议存在一个致命的漏洞:它没有任何验证机制。
这就好比,只要你有一台接入网络的 BGP 路由器,你就可以向全世界高喊“我是某某路 1 号的主人”,而周围的路由器不会去核对任何房产证,它们会无条件相信你,并把发往该地址的数据包全送往你家。这就是一种完全建立在信任基础上的“君子协定”。
16.3 BGP 劫持与 2008 年 YouTube 瘫痪事件
随着互联网逐渐商业化,越来越多素不相识的机构和公司加入了这个网络。当“君子协定”遇到了没有验证机制的协议,灾难就发生了,这也就是著名的 BGP 劫持(BGP Hijacking)。
最经典、也是互联网历史上影响最大的一起事故,就是 2008 年的 YouTube 瘫痪事件。
2008 年 2 月,巴基斯坦政府下令在港内屏蔽 YouTube。巴基斯坦电信(Pakistan Telecom)为了完成这个任务,决定采用一种“物理超度”的方法:他们在自己的核心路由器上,伪造了一条 BGP 宣告,声称自己才是 YouTube 某些 IP 地址的拥有者。这样,巴基斯坦国内的用户想访问 YouTube 时,流量就会被错误地引导到一个“死胡同”(黑洞),从而实现屏蔽。
然而,巴基斯坦电信在操作时犯了一个严重的技术错误。他们不仅在内部屏蔽,还一不小心把这条虚假的 BGP 宣告发送给了他们的上游国际运营商(香港电讯盈科 PCCW)。PCCW 的路由器没有进行任何验证,直接相信了这条宣告,并将其转发给了全球互联网。
这里还涉及到一个路由器的硬性规则——最长前缀匹配(Longest Prefix Match):
- YouTube 官方平时宣告的 IP 范围是
208.65.152.0/22。 - 而巴基斯坦电信宣告的是更精准、范围更小的
208.65.153.0/24。 - 路由器在指路时,永远会优先选择更具体、更精准的地址(就像导航时,“朝阳区朝阳北路1号”永远比“朝阳区”更精准,司机会优先听从精准的指令)。
结果,全球的路由器都认为:“去往 YouTube 最精准的路在巴基斯坦!”
在短短几分钟内,全球发往 YouTube 的滔天流量全部涌向了巴基斯坦电信。后者的网络瞬间被冲垮崩溃,而全球用户在数小时内都无法打开 YouTube。这次意外让整个互联网行业彻底惊醒:原来我们每天依赖的互联网,安全防线竟然如此脆弱。
16.4 现在的解决方案:RPKI
为了解决 BGP 劫持这个历史遗留问题,互联网安全专家们推出了目前全球正在普及推广的解决方案——RPKI(Resource Public Key Infrastructure,资源公钥基础设施)。
RPKI 的核心思想很简单:既然“口说无凭”,那我们就用密码学和数字证书来给 IP 地址办一张“身份证”和“房产证”。
它是这样工作的:
- 申请证书(ROA):IP 地址的合法拥有者(比如 Google、腾讯、脸书等),会去区域性 IP 分配机构(如亚太地区的 APNIC)申请一份数字证书,叫做 ROA(Route Origin Authorization,路由起点授权)。这份证书用密码学技术进行了数字签名,上面明确写着:“我(比如 AS 12345)才是
X.X.X.X/24的合法拥有者。全世界只有我的 AS 路由器能宣告这个 IP 段。” - 下载并核对:全球的骨干路由器现在都会定期从可信的服务器下载这些 ROA 证书库。
- 路由起点验证(ROV):当路由器收到一条 BGP 宣告时,它不再盲目相信,而是先去核对证书:
- 合法(Valid):宣告者与证书上的主人一致,予以通过。
- 非法(Invalid):如果发现某个未授权的 AS(比如当年的巴基斯坦电信)突然跳出来宣告别人的 IP,路由器就会判定其非法,并直接丢弃这条宣告。
通过 RPKI 技术,互联网终于开始摆脱“君子协定”的原始状态,用严谨的密码学验证,守护着我们每一次网络连接的安全。
17. GFW(上):防火墙是怎么识别可疑流量的
接下来这部分会讲到中国大陆特有的网络审查系统,通常被称为 GFW(Great Firewall,防火长城)。这部分内容会用到前面讲过的几乎所有概念(IP、TCP、TLS、SNI、DNS、BGP 等),如果哪个概念记不太清,可以往回翻一翻。
GFW 会针对不同协议,采用不同的判断手段,往往是综合多个维度来做判断:
(1)协议类型以及证书
如果传输的是明文(未加密)内容,GFW 会检测其中是否包含敏感词。如果明文内容看起来像是乱码(可能是被简单加密过、伪装成正常协议的流量),就有可能被判定为”协议伪装”而遭到怀疑。请求头(数据包里描述”这是什么类型请求”的部分字段)里的具体内容,同样会被检测。而对于一些不常见的、自定义的协议,本身就容易引发 GFW 的警惕。
对于 TLS 流量,GFW 会进行”主动探测”——伪装成一个普通客户端,主动向可疑的服务器发起 TLS 连接请求,拿到对方返回的证书后,检查这张证书是不是自签名证书(也就是不是由正规 CA 机构签发,而是自己给自己签发的证书)。自签名证书会显著提高这次连接被判定为可疑的概率。
(2)看 IP 的”信誉”
比如检查这个 IP 是否在白名单里(比如已知的、合法的大型云服务商 IP 段)。
(3)深度包检测(DPI)与协议特征识别
DPI(Deep Packet Inspection,深度包检测)指的是不仅看数据包”外面的信封”(比如用的哪个端口),还会仔细分析数据包内部载荷(实际传输内容)的模式,试图识别出其中的”指纹”特征。很多翻墙工具(比如早期的 OpenVPN、SSH 隧道、没做过混淆处理的 Shadowsocks),虽然本身已经加密,但由于它们在握手阶段或数据传输过程中存在某些固定的、可被识别的协议特征,依然会被 DPI 系统一眼认出并加以阻断。
18. GFW(下):识别之后,会采取哪些封锁手段
(1)应用层干扰:DNS 污染 / 抢答
当用户查询某个域名对应的 IP 时,GFW 会抢在真实的 DNS 服务器之前,返回一个错误的 IP 地址,让用户实际连接到了错误的、甚至无效的地址上。
(2)路由与网络层封锁:IP 封禁与 BGP 黑洞
直接封禁某个 IP,让所有发往它的流量都无法到达。有两种具体的实现手段:
- 手段一(BGP 黑洞/空路由):前面第 16 节讲过,BGP 负责在各个网络之间”指路”。GFW 可以在骨干网边界的路由器上,利用 BGP 协议,故意把”发往目标 IP 的路”指向一个空接口(相当于告诉路由器”这个方向的数据包,直接扔掉、别管了”),使得所有发往这个 IP 的流量在半路就被悄无声息地丢弃。这正是实现大规模 IP 封禁的技术底层。
- 手段二(端口封禁):为了减少”误伤”(比如一个 IP 上除了违规服务,可能还托管着正常的网站),GFW 也常常采用更精细的策略,只针对检测到异常流量的具体 TCP/UDP 端口进行封锁,而保留这个 IP 上其他正常服务(比如常规的 HTTP/HTTPS 网页访问)畅通。
不过,这种按 IP 封禁的方式,缺点也很明显——容易”误伤”同一个 IP 下的其他正常服务。
(3)传输层阻断:TCP 重置(TCP RST)
如果 GFW 通过深度包检测,发现一条 TCP 连接里包含敏感词,或者握手过程中出现了异常特征(比如未知的 TLS 扩展,或者 SNI 看起来不正常),它会伪装成通信的双方,分别向客户端和服务器发送 TCP RST 数据包(RST 是 TCP 协议里用来”强制中断连接”的一种信号),从而强行打断这条 TCP 连接,让双方都以为是对方主动断开的。
(4)连接限速、主动丢包
这种手段不会直接切断连接,而是通过流量整形或 QoS(服务质量控制)策略,针对特定 IP、端口或协议的流量,进行大比例的随机丢包,或严格限制带宽速度。它的”阴险”之处在于,用户体验到的现象只是”网速很慢、经常卡顿”,很容易误以为是自己网络本身的问题,而不会立刻联想到是被针对性限制了——这大概是最让人头疼的一种手段。
(5)行为验证与确认:主动探测
原理是这样的:当 GFW 检测到某个 IP 存在疑似代理、或使用了未知加密协议的可疑数据包时,并不会立刻一封了之,而是先触发一套”验证”机制——GFW 会用伪装的客户端,主动向这个可疑 IP 的对应端口,发起多种代理协议的连接测试(比如尝试用 Shadowsocks、Vmess、Trojan 等协议的握手方式去连接它)。如果目标服务器做出了符合某种代理协议特征的响应,就会坐实”这是一个代理服务器”的判断,进而触发上面提到的那些封锁手段。
19. VLESS 协议:轻装上阵的代理协议
理解了 GFW 的检测和封锁逻辑之后,就能更好地理解目前主流翻墙技术的设计思路了——它们几乎都是针对上面这几种检测手段,专门设计出来的”反制方案”。
先讲 VLESS 协议(全称 V2Ray/Xray Less,是一种应用层的代理协议,负责规定”客户端和你的代理服务器之间,该用什么格式来打包和识别数据”)。
在 VLESS 之前,主流的协议是 VMess。VMess 有一个特点:它会在应用层对数据再进行一次加密。但问题在于,前面讲过,TLS 这一层本身就已经提供了很强的加密保护,如果在 TLS 加密的基础上,VMess 还要再加密一遍,就相当于把同一件东西反复裹了两层保险,属于性能上的浪费。所以,VLESS 在设计时取消了协议自身这一层”二次加密”,把加密的工作完全交给外层的 TLS 去做。
20. REALITY 技术:伪装成真实网站
再讲 REALITY 技术,它是为了对抗前面第 17 节提到的”GFW 主动探测证书是否自签名”这一检测手段而设计的。想象以下情景:
客户端发起一次看起来完全正常的 TLS 1.3 握手(也就是第 10 节讲过的 Client Hello),SNI 填的是 microsoft.com,而不是真正想访问的代理服务器。与此同时,客户端会利用 REALITY 公钥,在这次 Client Hello 消息里的随机数、或密钥交换字段中,加密混入一段临时的验证暗号。在 GFW 看来,这串暗号跟一段完全标准、毫无规律的 TLS 1.3 随机数没有任何区别,无法分辨出异样。
当你自己搭建的服务器(下面用 VPS 来指代,也就是”虚拟专用服务器”,可以理解成你在网上租的一台远程电脑)收到这个连接后,会用只有它自己持有的 REALITY 私钥,去尝试解密这段暗号,这时会出现两种情况:
【情况 A:解密成功(确认是自己人)】 VPS 确认这是自己人发起的连接,于是拦截这个连接,不再转发给真正的 microsoft.com,而是直接和客户端建立起 TLS 通道,并把解密出来的流量导向内部真正的 VLESS 代理程序。
【情况 B:解密失败(可能是 GFW 的主动探测,或只是碰巧访问到这个 IP 的普通路人)】 VPS 意识到这不是自己人,为了不暴露”自己其实是个代理服务器”,它会立刻切换身份,变成一个透明的中间人:把这个连接原封不动地实时转发给真正的 microsoft.com,让 microsoft.com 返回真实的证书和握手数据,VPS 再把这些内容原样转发回去。这样一来,对方看到的就是一次跟 microsoft.com 完全正常的 TLS 握手,没有任何破绽。
21. XTLS Vision:抹除”套娃”痕迹
上面 REALITY 解决了”证书是否自签名”这一层的伪装问题,但还留下了一个更细节的破绽。设想你正在通过这套代理访问一个 HTTPS 网站,比如 https://github.com,这时实际上存在两层 TLS:
- 内层:你的浏览器和 GitHub 之间,标准的 TLS 握手(保护你和 GitHub 之间通信内容的那一层,就是第 10 节讲的普通 TLS)。
- 外层:你和 VPS 之间的 REALITY TLS 握手(伪装成访问 microsoft.com 的那一层)。
虽然外层看起来是完全加密、正常的 TLS 流量,但 GFW 的深度包检测引擎,可以只通过分析数据包的长度和收发的时间间隔(不需要真正解密内容),观察到这样一种规律:外层 TLS 建立成功之后,里面立刻传出来一个长度大约在 512 字节左右的数据包(这其实是内层 TLS 的 Client Hello);紧接着,VPS 又回传了一个大约 1.5 KB 左右的数据包(这其实是内层 TLS 的 Server Hello 和证书)。这种”在一个大的 TLS 通道里,还套着另一个具备特定长度和先后顺序的 TLS 握手”的模式,在统计学上具有非常高的辨识度——GFW 完全不需要破解加密内容,仅凭数据包的长度和节奏,就能判断出”这里面在套娃翻墙”。
XTLS Vision 要解决的正是这个问题。它会严密监控经过 VLESS 的数据,一旦检测到内部正在传输 TLS 1.3 的握手数据,就会立刻启动混淆引擎,对这些特定的握手数据包强制进行拆分(把一个数据包拆成好几个小包发送,这个过程叫分帧),或者填充一些随机长度的无用字节(Padding,填充)。
经过这样的”整容”之后,数据包在网络上传输时表现出来的长度、时序特征,都被彻底打乱,从外部看起来更像是在传输一段普通的网页文字,或一段普通的视频流。GFW 的算法(哪怕是比较先进的深度学习算法)也就没办法再从中提取出”TLS 套 TLS”这种特定的套娃特征了。
22. 一次翻墙请求的完整旅程:六层包装 vs 直连四层结构
现在把前面第 19–21 节讲的这几种技术(VLESS、REALITY、XTLS Vision)串起来,看一个具体例子:假设你正通过这套代理访问 https://github.com,一个数据包从浏览器产生,到真正发送到网线里,一共经历了六层包装:
【第一层】用户原始数据(HTTP)
内容:GET /index.html(你想看的内容)
【第二层】用户安全层(内层 TLS)
内容:[ 加密后的 HTTP 数据 ]
由你的浏览器和目标网站(如 GitHub)直接建立,确保你的密码、Cookie、信用卡号等隐私信息,全程只有你和 GitHub 能看到,中间任何一方(包括你自己的 VPS)都无法窥探。
【第三层】代理应用层(VLESS 协议)
内容:[ VLESS 头部(UUID 身份标识 + 真正想访问的目标 github.com) ] + [ 上面已经加密好的数据 ]
(这里的 UUID,可以理解成一长串独一无二、专属于你的”暗号”或身份令牌。)
为什么需要这一层?因为需要让 VPS 知道数据包最终的目的地是哪里,以及确认这个请求确实是 VPS 持有人(也就是你)发的——回忆第 20 节:VPS 解密 REALITY 暗号成功后,会把流量导向内部的 VLESS 程序,而 VLESS 这一层,就是负责告诉 VPS”具体该把内容转发到哪里去”的。
【第四层】代理加密控制层(REALITY TLS + XTLS Vision)
内容:[ 伪装成 microsoft.com 的 TLS 外壳 ] + [ 被 Vision 处理过、做了填充/分帧的 VLESS 包装箱 ]
↓(交给操作系统的网络协议栈)
【第五层】传输层(TCP)
内容:[ TCP 头部(源端口/目的端口) ] + [ 上述全部内容 ]
↓(交给网卡准备发送)
【第六层】网络层(IP)
内容:[ IP 头部(你的公网 IP → VPS 的公网 IP) ] + [ 上述全部内容 ]
对比一下,如果不使用代理、直接连接的话,整个结构会简化很多,只有四层:
1 | +-------------------------------------------------------+ |
可以看到,使用代理相当于在正常的四层结构之外,又额外”套”上了两层伪装(VLESS 的身份标识层,以及 REALITY + Vision 的伪装外壳),这也是为什么翻墙连接通常会比直连多消耗一些性能和延迟——多包了两层”包装箱”,自然需要更多的处理时间。
23. Nginx 反向代理:REALITY 出现之前的”伪装”方案
接下来讲一个跟”伪装”密切相关的老朋友——Nginx 反向代理。
Nginx 可以理解成架在网站服务器前面的一个”接待员”或”交通指挥员”,负责接收用户的请求,再把请求分发给合适的后端程序去处理。
在 REALITY 这种技术出现之前,为了防范 GFW 的主动探测,大家常用的做法是在 VPS 上部署 Nginx,搭出一个”障眼法”:
- 伪装网站:用 Nginx 在 VPS 的 443 端口(HTTPS 默认使用的端口)上,搭建一个看起来完全正常的”个人博客”或”静态网页”。
- 分流(回落):当 GFW 来探测这个 443 端口时,Nginx 会热情接待,老老实实把博客网页内容返回给它,GFW 因此认为这就是一个普通的正常网站。而当你自己的代理客户端(比如 VLESS,或者更早期的 Trojan 协议)来连接时,由于请求里带有特殊的 UUID 暗号,前端的分流程序(可能是 Nginx 自己,也可能是配合它的其他分流工具)能识别出这个暗号,于是把这条连接转交给藏在后台真正干活的 Xray 代理程序(Xray 是实现 VLESS 等协议的一种具体软件),建立起真正的代理通道。
可以看出,Nginx 在这里扮演的角色,跟第 20 节 REALITY 技术里”把探测流量转发给真正的 microsoft.com”的思路,本质上是同一种策略——都是”先装成正常人,只有对上暗号的自己人才会被特殊对待”。
24. 代理软件的分流模式:直连、全局、规则
前面讲的都是 VPS(服务器)那一端在做什么,接下来把视角切换到你自己的电脑或手机上,讲讲代理软件本身的一些设计。
代理路由模式(也叫分流控制):这是用来决定”哪些流量该走代理、哪些流量该直接连接”的开关,常见的有这几种:
- 直连(Direct):所有流量都不经过 VPS 代理,该怎么连就怎么连。
- 全局代理(Global):所有流量,统统打包发给 VPS 代理。
- 规则分流(Rule / 规则模式):软件内部维护着一张”规则表”(通常包含国内网站的域名清单、已知的中国 IP 段等)。比如,当你想访问
baidu.com时,软件一看它在”国内域名”表里,就直接走 Direct(直连),不经过 VPS,节省资源、也更快;而当你想访问google.com时,软件发现它不在国内域名表里(或者其 IP 不属于中国 IP 段),就会把这条流量打包塞进 VLESS,送去 VPS,走 Proxy(代理)这条路径。
25. 系统代理 vs TUN 模式:为什么有些软件死活连不上代理
这里有一个很多人实际使用时会遇到的困惑:为什么开启了代理软件之后,浏览器能正常访问 Google,但命令行终端(比如 git、curl 这类工具)、或者某些电脑游戏,却依然连不上网?这跟这些软件”在哪个层面”处理网络流量有关。
系统代理(System Proxy):代理软件启动后,通常会在你的电脑本地开放一个端口(比如 127.0.0.1:7890,127.0.0.1 代表”本机自己”),然后修改操作系统的”代理设置”,相当于挂出一个通知:”各位软件,如果有流量要发,麻烦主动送到 7890 端口来。”
问题在于,这是一种自愿遵守的机制:浏览器(Chrome、Edge 等)和大部分现代软件,确实会乖乖遵守系统代理设置,主动把流量送过去。但命令行终端、绝大多数联网游戏,以及许多后台更新服务,并不会去理会系统代理设置,它们依然会固执地按照自己默认的路径,直接往外发送数据包。这就是为什么,哪怕开着代理,Git 依然拉不动代码,游戏依然卡在登录界面——它们压根没去”问”过系统代理设置这回事。
TUN 模式(虚拟网卡模式):为了解决上面这个问题,代理软件还提供了另一种更”强势”的工作方式——TUN 模式。
原理是:代理软件会在你的电脑里虚拟出一张网卡,叫 TUN 虚拟网卡。它工作在前面讲过的”网络层(IP 层)”,具体机制是:代理软件会修改操作系统的”路由表”(路由表决定了”发往某个方向的数据包,该从哪个出口发出去”),把默认的出口指向这张虚拟网卡。
这样一来,你电脑上任何软件、任何游戏、任何命令行工具发出的每一个 IP 数据包,都会毫无例外地被操作系统”扔”进这张虚拟网卡里,不管这个软件本身知不知道、愿不愿意配合”代理”这件事。代理软件在虚拟网卡这一层把这些数据包接过来,判断该走的规则后,再统一打包发给 VPS。
这种方式的好处是:从更底层、更强制的层面接管了整台电脑的所有流量,游戏、终端、各种软件完全不需要自己做任何额外设置,就能全部被代理覆盖——这也是为什么 TUN 模式特别适合用来做游戏加速,或者搭建开发环境。
26. 混合端口:一个端口如何同时兼容两种协议
在早年(甚至现在一些比较老旧的代理软件里),代理软件会在本地同时开启两个不同的端口,比如:Socks5 端口监听在 10808(用来走通用的 Socks5 协议,下一节会具体讲);HTTP 端口监听在 10809(专门用来走 HTTP 协议)。
为什么以前要分开两个端口?因为不同软件,支持连接代理时使用的协议不一样——有的软件只认 HTTP 代理,有的只认 Socks5 代理。为了让所有软件都能正常工作,代理软件在本地就必须”一心二用”,同时开着两个端口伺候不同的客人。
为了彻底解决这个麻烦,现代代理软件(比如 Clash,它引入的 7890 端口就很有名)引入了混合端口技术:本地只开一个端口(比如 7890),但这个端口内部带有一个智能识别引擎——不管什么软件连进来,混合端口都会先看一眼这个软件发过来的第一个数据包(首包)长什么样:如果首包内容一上来就是 CONNECT ... 或者 GET ... 这样的明文字样(这是 HTTP 协议的特征),混合端口就判断”这是个 HTTP 客户端”,用 HTTP 协议的方式接待它;如果首包内容一上来是 0x05 0x01 ... 这样的二进制字节(这是 Socks5 协议握手时的特征),混合端口就判断”这是个 Socks5 客户端”,转而用 Socks5 协议的方式接待它。这样一个端口就能同时兼容两种协议的客户端,不再需要分开占用两个端口了。
27. Fake-IP:如何防止 DNS 泄露,顺便提速
Fake-IP(虚拟 IP 模式):在 Clash 等现代代理客户端的 DNS 设置里,经常会看到这个选项,它是专门为了解决”DNS 泄露”和”提速”这两个问题设计的。
先说说不用 Fake-IP 时会有什么问题:传统流程下,浏览器要访问 google.com,必须先发起一次 DNS 查询(回忆第 8 节讲过的 DNS 查询过程)。如果这次查询走的是本地运营商的 DNS 服务器,运营商就会知道你正在尝试访问 Google——这本身就是一种隐私泄露(叫 DNS 泄露);更麻烦的是,运营商的 DNS 服务器还可能返回一个被污染过的错误 IP(回忆第 18 节讲的 DNS 污染)。
Fake-IP 的流程是这样的:
- 浏览器向代理软件(而不是真正的 DNS 服务器)询问:”
google.com的 IP 是多少?” - 代理软件并不会真的去做 DNS 解析,而是当场随手编造一个局域网内根本不存在、不会被实际使用的虚假 IP(比如
198.18.0.5)返回给浏览器。 - 浏览器拿到这个假 IP,完全不知情地直接发起连接(往
198.18.0.5发送 TCP 数据包)。 - 代理软件在前面第 25 节提到的虚拟网卡(TUN)这一层,拦截住这个发往
198.18.0.5的数据包,把目标地址还原替换回真正的google.com,再通过前面讲过的 VLESS + REALITY 这套代理通道,发送给 VPS。 - 由 VPS 在境外,帮你完成真正的、最安全精准的 DNS 解析。
这样做的好处是:速度更快(省去了在本地等待 DNS 解析结果的时间),而且彻底避免了”本地 DNS 查询”这个环节,从根源上解决了 DNS 泄露和 DNS 污染的问题。
补充一点:Fake-IP 只在 TUN 模式下才需要启用。如果用的是前面讲的系统代理模式,浏览器一般根本不会自己发起 DNS 查询,而是会直接把域名”原样”交给代理软件去处理,所以也就不存在 DNS 泄露的问题,自然也不需要 Fake-IP 来兜底。
28. HTTP 代理:为什么它必须”看得懂”你的内容
最后再讲两种更基础的代理协议——HTTP 代理和 SOCKS5,理解它们能帮你更好地理解前面 VLESS 这类代理协议是”进化”自什么。
HTTP 代理是什么
HTTP 代理(HTTP Proxy)是专门为 HTTP 协议(也就是普通网页流量)量身定制的代理服务器,工作在前面讲过的”应用层”。它的工作逻辑,很像一个帮人跑腿代购网页的秘书。
对比一下正常访问和使用 HTTP 代理访问的区别:
- 正常访问网页(不使用代理):浏览器直接给目标服务器(比如百度)发信——信封上(也就是 TCP/IP 头部)写的收件人是百度的 IP;信件内容(HTTP 请求)是
GET /index.html HTTP/1.1(意思是”请把 index.html 这个页面给我”)。 - 使用 HTTP 代理访问网页:浏览器把信交给了 HTTP 代理服务器——信封上写的收件人变成了代理服务器的 IP;信件内容被浏览器改写成了
GET http://baidu.com/index.html HTTP/1.1(明确写出了完整的目标网址)。代理服务器收到这封信后拆开信件,看到里面写着”我想要百度的网页”,于是亲自跑一趟帮你把网页内容取回来,再重新装进新信封寄给你。
为什么 HTTP 代理”必须看得懂”你传输的内容?
这里有一个很核心的问题:为什么 HTTP 代理不能像下一节要讲的 SOCKS5 那样,闭着眼睛照单全收地转发数据包,而非要”拆开信件、看懂内容”不可?主要有两个原因:
1. 致命原因:目的地信息藏在信件正文里
前面说过,使用 HTTP 代理时,数据包外面的”信封”上写的收件人其实是代理服务器自己的 IP。如果代理服务器不拆开信件(也就是不解析 HTTP 内容),它就只知道”这封信是寄给我的”,但根本无从得知这封信最终应该转交给谁——它必须具备读懂 HTTP 语法的能力,主动去读信件正文里的第一行内容,比如 GET http://baidu.com/index.html,或者请求头里的 Host: baidu.com 字段(回忆第 13 节讲 SNI 时提到过的 Host 字段)。只有读懂了这段文本,它才能提取出 baidu.com 这个真正的目的地,然后才知道该去哪里帮你把网页取回来。如果它根本看不懂 HTTP 协议,这封信就会”死”在代理服务器手里,哪儿也去不了。
2. 功能原因:它能提供”深度增值服务”
正因为 HTTP 代理天生能读懂 HTTP 协议的内容,它在企业网络和网络管理中,常常被赋予很多”特权功能”:
- 网页缓存:代理服务器既然能读懂你想下载的是某张具体的大图片,如果之前已经有别人下载过同一张图片,代理服务器就可以直接从自己本地的硬盘里把图片给你,不需要重新跑一趟去外网下载,大大节省企业的对外带宽。
- 内容过滤与屏蔽:学校或公司的 HTTP 代理,可以读取你传输的网页内容,一旦发现里面包含游戏、小说,或某些违规的网址路径,就直接拒绝帮你”跑腿”,或返回一个”禁止访问”的提示页面。
- 注入与修改:一些公共场所提供的免费 WiFi 代理,会偷偷读懂你请求的网页 HTML 内容,并往里面强行插入一小段属于它们自己的广告代码,再把改过的内容发给你的浏览器。
要实现上面任何一项功能,代理服务器都必须彻彻底底地看懂、甚至有能力修改你传输的 HTTP 文本内容才行。
一个有趣的悖论:HTTPS(加密流量)是怎么走 HTTP 代理的?
讲到这里,你可能已经想到一个巨大的矛盾:HTTPS 通信是全程端到端加密的(回忆第 10 节讲过的内容),HTTP 代理根本看不懂里面加密后的内容(在它看来只是一堆乱码)。那 HTTPS 流量到底是怎么通过 HTTP 代理传输出去的呢?
为了解决这个冲突,设计者给 HTTP 代理引入了一个非常巧妙的”妥协机制”——CONNECT 方法(也叫 HTTP 隧道技术):
- 第一步(发送明文暗号):当你想通过 HTTP 代理访问一个加密网站,比如
https://github.com时,浏览器会先向 HTTP 代理发送一个明文的、特殊的 HTTP 请求:CONNECT github.com:443 HTTP/1.1。 - 第二步(代理读懂并连接):因为这一步用的是明文,HTTP 代理能读懂这个 CONNECT 请求的意思——“我接下来要传输加密数据了,你不用看内容,直接帮我往 github.com 的 443 端口拉一条纯粹的 TCP 管道就行”。
- 第三步(接通管道):代理服务器于是主动向真正的 GitHub 发起一次 TCP 连接,连接建立成功后,回复浏览器一封明文信:
HTTP/1.1 200 Connection Established(意思是”管道已经接通了,你可以开始发货了”)。 - 第四步(装甲车通过):从这一刻起,HTTP 代理立刻”装瞎”——它不再尝试解析任何 HTTP 语法,而是彻底退化成一根纯粹的、闭着眼睛只管转发原始字节的”管道”(这时它的行为就跟下一节要讲的 SOCKS5 非常相似了),让你和 GitHub 之间加密好的 TLS 流量安全地穿过去。
29. SOCKS5 协议:纯二进制、还支持 UDP
SOCKS5 是一种专门为”做代理”这件事而生的应用层协议,跟 HTTP 代理相比,它们的分工方式完全不同:
HTTP 代理用的是文本协议——也就是说,它发送和接收的是像 CONNECT github.com:443 HTTP/1.1 这样,人类可以直接读懂的英文单词和换行符组成的内容(上一节已经详细讲过);而 SOCKS5 用的是纯二进制协议——它的握手和通信过程,用的是一串串对人类不友好、但对机器更高效的二进制字节,不追求”人类可读”。
另外,和只支持 TCP 的 HTTP 代理不同(因为 HTTP 协议本身就是构建在 TCP 之上的),SOCKS5 还额外支持 UDP(第 9 节提到过的、更轻量但不保证完整送达的协议),这让它的适用场景比 HTTP 代理更广。
也正因为 SOCKS5 从设计之初就不像 HTTP 代理那样需要”读懂”应用层的具体内容,它天生就更适合被更上层的、功能更丰富的代理协议(比如第 19 节讲过的 VLESS)所借鉴或搭配使用。
30. 结语
回顾一下整篇文章走过的路径:先解决了”两个设备怎么找到彼此”(IP、网关、子网掩码、NAT、MAC、ARP、DHCP),再解决了”名字怎么变成地址”(DNS),然后解决了”怎么保证内容不被偷看、对方身份可信”(TCP、TLS、SNI),顺带看了大网站如何靠 CDN 就近服务全球用户、互联网各个网络之间又是靠 BGP 互相”指路”的。
有了这些地基,后半部分其实都是在讲一件事:一场关于”识别”与”伪装”的攻防战——GFW 靠分析协议特征、证书、流量模式来识别可疑连接(第 17、18 节),而 VLESS、REALITY、XTLS Vision 这些技术,则是针对每一种识别手段,逐一设计出对应的伪装方案(第 19–23 节);再往上,才是你电脑里代理软件的调度逻辑——分流规则决定哪些流量要绕这一圈、系统代理和 TUN 模式决定哪些软件的流量能被接管、Fake-IP 负责堵住 DNS 这个漏洞(第 24–27 节);而 HTTP 代理和 SOCKS5,则是这整套代理思想最初、最基础的两种实现方式(第 28、29 节)。
如果现在回头看这些概念,应该已经能感觉到,它们并不是孤立的知识点,而是一环扣一环搭建起来的——每一层的设计,几乎都是在回应上一层留下的某个具体问题。