从第一性原理理解网络与互联网
你是否好奇过,当我们和远在另一大洲的朋友或同事发短信、打电话或视频聊天时,对方的回复为何能在瞬间抵达,仿佛近在咫尺?这背后藏着一连串无形的转换:你的声音、画面或消息先转化为无线电波,传到房间里的Wi-Fi路由器;接着变成铜缆中的电脉冲(如果是光纤网络,则转化为光信号);随后进入海底那根比发丝还细的玻璃光纤,以光脉冲的形式穿梭;最后在另一端完成反向转换,让对方接收。仅仅通过精准操控电、光和无线电的规律波动,就能实现与全球任何人的即时通信,想想都觉得不可思议。
消息在传输过程中,会途经数十家独立公司的设备,这些公司分布在不同国家。它们并未针对这次消息传输专门协作,也没人知道数据完整的传输路径——它们只是把数据交给下一个最近的节点。没有中央计算机指挥流量,也没有哪家公司独自拥有整个互联网基础设施。但这套系统每秒能完成数十亿次传输,可靠性高到我们只有在通话卡顿、视频缓冲时才会意识到它的存在。
此前的《软件篇》聚焦单台机器,从硅芯片中的电子讲到日常运行的软件;本文则关注机器之间的连接。和计算体系一样,互联网并非一蹴而就,而是历经数十年逐步发展而来。每一种协议的诞生,都旨在解决某个具体的技术局限,只有了解这些局限,才能真正理解协议的意义。我们很容易误以为互联网是按完美蓝图设计出来的成品,毕竟故障发生得太少,让这套系统看起来天生就这么可靠。但实际上,本文提到的每一种机制——分组交换、TCP、DNS、TLS——都是为解决特定问题而推出的“补丁”,而且都是在互联网已经“能用”之后的数十年才陆续部署。推动这些机制诞生的压力从未停止:如今,新的物理链路、新的故障场景,以及底层协议设计时根本不存在的软件新需求,都在持续驱动着互联网演进。
我的目标是从基础原理出发,帮你建立对互联网的完整认知。读完本文后,日常使用互联网时遇到的诸多疑问,都能在一套连贯的思维模型中找到直观答案:地址栏里的小锁如何保护你的信用卡信息?网页打不开是网站的问题,还是你这边出了故障?为什么明明是“千兆”网络,网页加载却依然卡顿?你的数据又是如何自动绕开半个地球之外故障的海底电缆?
计算机诞生前,我们就已经在传输比特了
网络的历史比计算机悠久,甚至比电的应用还要早。“network”(网络)一词最初的字面意思,就是由线绳交错构成的网状织物。19世纪初,工程师借用这个词来描述运河、铁路这类相互连通的交通线路。19世纪40年代电报问世后,这个词自然延伸,用来指代承载电报信号的电线和站点系统。
然而,网络链路的基本物理原理,和最简单的机械连接并无二致。把绳子紧紧系在两个锡罐之间,对着其中一个说话,绳子就会将声音的振动以机械运动的形式传到另一个罐子里——不需要放大器或中继器,只是振动波在传播过程中会因摩擦和松弛不断损耗能量。这就是此后所有网络链路的核心原理:在一端改变某个物理量,在另一端测量它。但绳子无法把信号传到很远的地方,否则能量会损耗殆尽。电报真正的突破,不仅是用电线替代了绳子,更在于突破了距离带来的物理限制。
1844年,塞缪尔·摩尔斯通过铜缆从华盛顿向巴尔的摩发送了电报:“What hath God wrought”(上帝创造了何等奇迹)。他使用的摩尔斯码,是一套由长短电脉冲组成的编码系统。请注意,电报本质上是一个数字网络——它传输的不是声音本身,而是来自固定字符集的离散符号。维多利亚时代的人们深知这一设计的优势:线路上的机电继电器不需要传输完整的波形,只需检测是否存在脉冲,然后生成一个全新、清晰的脉冲副本,发送到下一段线路。离散符号加再生机制,让消息可以跨越大洲而不衰减,这是任何模拟信号都做不到的。
还要注意,在电线能传输信号之前,发送方和接收方必须先达成一项约定。电报能工作,是因为双方预先持有相同的对照表,知道哪些脉冲对应哪些字母,以及操作员如何发送“已收到”或“请重复”的信号。这套共享规则就是协议。本文提到的每一种协议(IP、TCP、DNS、TLS)本质都一样:是一份公开约定,规定了消息格式、通信时序等内容,让独立的机器能够相互通信。
下方的模拟器演示了摩尔斯码消息在那条历史线路上的传输过程。你可以看到脉冲在每段电线中逐渐衰减并混入噪声,以及继电器如何处理这些信号;还可以切换到“仅放大”模式,看看为什么离散字符集(现代计算机进一步简化为二进制比特)是如此巧妙的设计。
摩尔斯码脉冲在每段电线中都会衰减并混入噪声。由于网络传输的是离散符号,继电器无需传递完整波形,只需检测脉冲是否存在,然后生成全新的清晰脉冲副本。如果切换到仅放大模式,每段线路的噪声会被带入下一段,最终导致巴尔的摩端误读消息。离散符号加再生机制,正是消息能跨洲传输而不衰减的关键——这是模拟信号无法实现的。
电报网络甚至解决了路由问题,只不过靠的是人。从小镇发往另一个小镇的消息,会经过多个中继站:操作员收到消息后,将其打在纸带上,等通往目标方向的线路空闲时,再重新发送出去。繁忙时段,消息会在箱子里排队等候。请记住这个思路——一百年后,我们用电子设备重现了这套流程,并称之为“路由器”。

摩尔斯自己的线路只需要跨州,而跨洋传输则耗费了更长时间,经历了十年的昂贵挫折,工程师们也痛苦地学到了海底电缆的物理知识。赛勒斯·菲尔德铺设的第一条大西洋电报电缆于1858年8月启用,传递了维多利亚女王和布坎南总统之间的贺电;但三周后,电缆就因操作过程中绝缘层受损(有人认为,工程师为强行传输信号而施加的过电压最终导致了它的报废)而失效。1866年,由当时世界上最大的船只“大东号”(SS Great Eastern)铺设的电缆终于成功——它是唯一能一次性装载约4000公里电缆的船只。从那以后,海底就开始承载通信电缆了,之后我们还会讲到电报线如何演变为同轴铜缆,最终变成玻璃光纤。
这套底层原理适用于此后所有的网络链路:要在两点间传输比特,只需在一端按约定规则改变某个物理量,在另一端测量它即可。比特(bit)是“binary digit”(二进制数字)的缩写,是最小的信息单位,代表在两种状态中做选择,通常记为1或0。本文中所有被测量、传输或存储的内容,本质上都是一定数量的比特(如果你想深入了解,可以参考《软件篇》,它从晶体管和逻辑门讲起,逐步构建这一概念)。把8个比特组合在一起,就构成一个字节(byte),拥有256种状态,足以表示一个文本字符或一个小数字——这就是为什么本文中分组头、帧、文件等的大小都用字节而非比特来计量。
在电线上加电压代表1,不加电压代表0,每秒测量一百万次,电线的传输速率就是每秒一百万比特。光纤则是通过在玻璃纤芯中开关激光来实现:光代表1,无光代表0,每秒开关数百万次;Wi-Fi则通过改变无线电波的形状来传输(具体原理将在后续的《无线篇》中讲解)。
所有这类链路都有两个关键指标,分清它们能解决很多日常困惑:
- 带宽:链路每秒能传输的比特数。
- 延迟:单个比特从一端传到另一端所需的时间。
带宽是工程问题,而工程师们一直在取得突破:单根现代光纤通过同时传输多种波长的光,能实现每秒数太比特的传输速率。延迟则是物理规律决定的:光在玻璃中的传播速度约为每秒20万公里,是真空中光速的三分之二,因此纽约到伦敦的单向延迟下限约为28毫秒(往返则需约56毫秒),无论投入多少资金和技术都无法降低。这就是视频流和视频通话体验截然不同的原因:视频流需要高带宽,对延迟的容忍度高(可以提前缓冲数秒);而视频通话需要低延迟,带宽需求却不高。当你在高速网络上仍感觉网页加载缓慢时,延迟通常是罪魁祸首。我们后面会看到,加载一个网页需要多次往返通信(DNS查询、TCP连接、TLS握手),之后才会开始传输内容。
带宽和延迟之间无法相互替代,因为它们的来源不同:带宽取决于发送端的传输速率,延迟则取决于线路长度。下方的模拟器演示了相同的8个比特在同一条链路上传输:调整带宽,只会改变脉冲之间的间隔;调整距离,只会改变第一个脉冲到达的延迟时间。
带宽仅决定每个脉冲的宽度;距离仅决定波在电线上传播所需的时间。下方的进度条真实比例展示了这两种延迟,上方的演示则经过了放慢处理,并非真实比例。
为每一次通话分配专用电路
电报实现了办公室之间的文本传输,而1876年问世的电话则将实时语音带入了家庭。为实现这一点,电话网络采用了一种完全不同的原理——电路交换。当你打电话时,系统会在你的电话和对方的电话之间建立一条专用的电气通路:最初由人工操作员将插头插入交换台完成,后来则由机电继电器自动完成。在通话期间,这条铜缆链路完全属于你们的对话,端到端专用。

对于语音通话来说,这是合理的设计——通话过程中几乎一直有连续信号传输,预留的线路能得到充分利用。因此,当20世纪50至60年代计算机首次需要远程通信时,它们只能沿用电话线路。但电话线路是为传输人类语音(模拟声波)设计的,而非直接传输数字电脉冲,所以计算机必须使用调制解调器(modem,是modulator-demodulator的缩写)。调制解调器将计算机的二进制比特转换为模拟音频信号——就是拨号上网时听到的那些吱吱声、蜂鸣声和静电噪音——这些信号能在语音网络中传输,然后在另一端再将声音转换回数字比特。
最早的调制解调器,比如300波特的贝尔103,采用了**频移键控(FSK)**技术——原理和电报的通断脉冲类似,但针对只能传输连续音频的线路做了调整:每个1比特对应一个固定频率的音频,每个0比特对应另一个更低频率的音频,另一端的调制解调器只需识别当前是哪个频率,就能还原出对应的比特。下方演示了字母“A”对应的字节如何被调制为音频,再解调回比特:
高频音(标记)= 1,低频音(空号)= 0
在预留线路上传输数据之前,两台调制解调器必须先通过这条语音级线路协商:它们能以多快的速率通信,以及如何纠正嘈杂铜缆必然会带来的错误。1981年问世的Hayes Smartmodem内置了扬声器,这样人们就能听到呼叫连接的过程:拨号音、铃声,确认没有遇到忙音或拨错号码后,计算机才会接管;扬声器会一直开启到握手协商完成,所以协商过程也是可听见的。如果你在20世纪90年代到21世纪初使用过互联网,是否还记得这个声音?
实线:下行(互联网服务提供商→你)·虚线:上行(你→互联网服务提供商),双向同时传输,全双工
这些看似噪音的声音,其实是一套完整协议在运行:能力列表、线路探测、均衡器训练——早期调制解调器默认开启扬声器,这样人们就能判断呼叫是否正常进行。录音来源:“拨号调制解调器噪音”,公有领域,维基共享资源。
直到21世纪初宽带普及之前,这种嘈杂的握手协商仍是大多数人接入互联网的方式。DSL和有线电视复用了原有的电话和电视线路,但将其改造为始终在线的数字链路,无需拨号;光纤到户则完全抛弃了语音网络的线路。
然而,更深层的矛盾并非调制解调器的转换问题,早在宽带普及前几十年就已显现。计算机的流量是突发性的:终端在毫秒内发送一个按键或请求,然后线路就会陷入静默,直到用户阅读内容或处理器完成计算。(即使是如今看似连续的视频流,实际上也是以短而密集的数据包突发传输,填满播放缓冲后,线路就会静默,直到你看完缓冲的内容。)从整个会话来看,为计算机通信预留的线路绝大多数时间都是空闲的,却占用了他人无法使用的带宽。更糟的是,通话建立后路径就固定了,沿途任何链路或交换局出现故障,都会直接导致连接中断。
到20世纪60年代初,三种压力共同冲击着电路交换的设计:科研计算机数量激增,需要高效共享昂贵的长途线路;交互式计算让流量的突发性变得极端;处于冷战中的美国军方需要一套指挥网络,在遭受大规模破坏后仍能正常运作——而依赖固定路径和中央交换局的网络根本无法做到这一点。
将消息拆分为数据包
另一种方案由两位互不知情的研究者独立提出:美国兰德公司的保罗·巴兰出于网络生存性的需求设计,英国国家物理实验室的唐纳德·戴维斯则为了实现线路共享而设计,后者还为这个概念命名为“数据包”(packet)。
不再预留专用路径、持续流式传输数据,而是将每条消息拆分为一个个小巧、独立的单元。每个数据包包含一个包头(header)——几字节的控制信息,包括源地址和目的地址——以及载荷(payload),即实际的数据块。沿途的每个交换点(路由器)完整接收一个数据包后,读取包头中的目的地址,查询自己的路由表找到通往目标方向的出站线路,然后将数据包转发出去。这就是存储-转发交换,相当于用电设备重现了电报中继站的流程,只是用纸带换成了内存,用操作员换成了路由表查询。
为了支持数十亿台设备,路由器不会列出每个单独的地址,而是在路由表中列出网络——即一组地址范围,对应一个下一跳节点。地址的具体结构,以及如何与地址范围匹配,取决于数据包包头中的协议(巴兰和戴维斯各自设计了不同的协议)。我们将在《连接网络》一节中详细探讨互联网采用的版本:IP协议。
这种设计下,数千个无关会话的数据包可以在同一条线路上交错传输,因此只要有数据需要发送,线路就不会空闲。而且每个数据包独立路由,网络可以绕开故障链路:如果某个路由器在会话中途故障,后续数据包会自动通过相邻路由器传输。巴兰称之为分布式网络——没有任何一个节点的故障能让网络分裂。
下方的模拟器展示了一个小型分组交换网络。左侧的客户端与右侧的服务器交换数据包,两者都是主机(host)——这是网络上任何可寻址设备的通用名称,包括计算机、手机、服务器等。客户端和服务器这两个角色非常重要,本文会反复提及:客户端发起会话,服务器处于已知的固定地址,等待被访问。中间的每个路由器只做本地决策:将每个数据包转发到目的地方向,同时避开已拥堵的链路。你可以看到同一会话的数据包如何选择不同路径,以及拥堵如何实时改变路由;点击任意路由器使其“故障”,你会发现网络不会立即恢复——最初几秒内,相邻路由器仍会根据过时的信息向故障节点转发数据包,导致这些数据包丢失,直到故障信息扩散,路由重新稳定下来。这个“追赶”过程被称为收敛(convergence),我们将在《路由协议》一节中深入探讨。
一个包含12台路由器的小型网络;点击任意路由器可使其故障/恢复。故障后的最初几秒,相邻路由器会继续向故障节点转发数据包,导致数据包丢失,随后路由会绕开故障节点重新稳定,而专用电路一旦故障则无法恢复。鼠标悬停在路由器上可查看本地路由表。
还要注意模拟器中的一个细节:偶尔会有数据包被丢弃。当数据包到达路由器的速度快于出站线路的处理速度时,路由器会将它们暂存在内存队列中;队列满时,路由器就会丢弃无法容纳的数据包。这并非设计缺陷,而是设计的一部分:网络仅承诺尽力而为(best-effort)的交付,数据包可能丢失、重复或乱序,网络本身不会做任何纠正。保持网络核心的简单性,将所有可靠性责任推给边缘的计算机,是互联网架构中最具影响力的决策,也是它能扩展为全球网络的关键。
第一个分组交换网络
1969年,美国高级研究计划局(ARPA)资助建立了第一个真正的分组交换网络——ARPANET,用于连接其资助的各地大学科研计算机。
当时面临一个现实障碍:每个站点的大型主机来自不同厂商,运行不兼容的操作系统,而且没有多余的算力来处理实时分组交换任务。博尔特·贝拉尼克·纽曼公司(BBN)用一台专用机器解决了这个问题——接口消息处理器(IMP)。这是一台加固型小型计算机,唯一的任务就是将消息拆分为数据包、路由数据包,并在另一端重新组装。每个站点将其大型主机连接到本地IMP,IMP之间通过租用的电话线路通信。ARPANET的协议文档明确区分了每个站点的两台机器:IMP是只负责一项任务的“笨”交换硬件,而它背后的大型主机被称为主机(Host)——真正承载人们关心的计算任务的机器。这个术语沿用至今:如今的主机指任何发送或接收流量的计算机、手机或服务器,而路由器或交换机则继承了IMP的角色,属于网络基础设施,而非主机。IMP就是世界上第一个路由器,它的设计模式——一台专用设备负责处理网络协议,让背后的计算机无需操心——如今就藏在你家里带天线的路由器中。

第一次传输于1969年10月29日进行,从加州大学洛杉矶分校(UCLA)到斯坦福研究院。学生程序员查理·克莱恩开始输入LOGIN以登录远程机器:他输入L,通过电话确认对方已收到;输入O后,接收系统崩溃了。互联网的“祖先”传输的第一条消息,只有LO两个字母。到12月,网络已有4个节点;到1973年,它跨过大西洋,连接到了挪威和伦敦。

用一条共享线路连接整个办公室
为ARPANET设计的分组交换网络,通过租用的点对点线路连接远程站点,形成了广域网(WAN)。分组传输的核心思想也解决了一个更本地化的问题:如何连接办公室里的数十台机器(即局域网(LAN)),而无需在每两台机器之间都铺设一条专用线路?
1973年,施乐PARC的罗伯特·梅特卡夫设计了以太网(Ethernet)。它的冲突处理机制直接借鉴了ALOHAnet——一个连接夏威夷群岛的早期无线网络,其核心思想简单直接:只要有数据要发送,站点就直接传输,然后监听是否与其他站点的传输发生冲突,而非先请求许可。梅特卡夫的设计将一栋建筑内的所有计算机连接到一条共享同轴电缆上,他称之为“以太”(ether)。同轴(coaxial)指两个导体共轴:一根铜芯传输信号,外面包裹绝缘层,再外面是圆柱形编织或箔状屏蔽层,最外层是塑料护套。屏蔽层同时作为电流回路,并阻挡外界干扰影响铜芯,这使得单根长电缆能在穿过每个办公桌时仍保持清晰的信号。任何机器都可以向电缆发送数据,所有机器都会接收所有数据,但只保留发送给自己的数据包。
共享介质有一个明显的缺陷:如果两台机器同时传输,信号会相互干扰,导致冲突(collision)。以太网通过几条纯本地规则来处理冲突:传输前先监听线路,如果繁忙则等待;传输过程中持续监听,一旦检测到冲突立即停止;然后等待一段随机延迟后重试。每次冲突后将随机延迟时间加倍(这种策略称为指数退避),发生冲突的机器会错开传输时间,避免线路永久拥堵。这套机制称为带冲突检测的载波侦听多路访问(CSMA/CD):“载波侦听”即传输前监听线路,“冲突检测”即一旦听到干扰就立即停止。和分组路由一样,有序的共享来自相同的本地规则,无需中央协调器。
共享电缆的设计并未持续太久,办公室开始改用交换机,同轴电缆也被淘汰,取而代之的是双绞线铜缆,末端采用RJ-45连接器——一种比电话插孔稍宽的透明塑料接头,每台设备通过一条专用线路连接到交换机,而非让一根线缆穿过每个办公桌。
交换机和路由器的核心目标相同:将数据仅发送到目标设备,但两者的工作机制完全不同,交换机使用本地硬件地址,而非全局网络地址。路由器读取数据包的目的地址,从人工配置的路由表中选择线路;交换机则读取帧(以太网对其数据单元的称呼,相当于底层的“数据包”)中的MAC(媒体访问控制)地址——烧录在设备网络硬件中的标识符,从它自行构建的表中选择端口。交换机的表是通过监听流量被动建立的,而非由人告知网络布局。
MAC地址通常写成六对十六进制数字,用冒号分隔,例如00:1A:2B:3C:4D:5E。
十六进制即基数为16的计数系统,每个数位有16个符号:0到9,以及代表10到15的A到F。选择十六进制并非出于传统,而是有更具体的原因:16是2的幂,因此一个十六进制数字恰好对应4个比特,没有余数;两个十六进制数字恰好对应1个字节,例如1A就是值为26的字节。十进制则没有这种对齐性:三位十进制数字有时能容纳一个字节,有时不能,这就是为什么工程师在查看原始比特但又不想写成一串1和0时,会选择十六进制——它是二进制的紧凑表示,本质上和二进制是同一类数字。
48位MAC地址分为两部分:制造商前缀(前三对,称为OUI,即组织唯一标识符)和序列号(后三对),这确保了地球上每块网卡都有一个全球唯一的ID。每台联网设备,包括你的智能手机,其Wi-Fi和蓝牙芯片都带有这样的标识符。
由于硬件地址是永久固定的,运营公共Wi-Fi网络的人(比如商场)可以通过记录MAC地址来追踪你的物理活动。为防止这种情况,现代操作系统在扫描网络或连接公共Wi-Fi时,会生成临时的随机MAC地址。交换机对此并不在意——它只需要地址在当前环境中唯一即可,而随机生成的48位地址很容易满足这一点。

交换机开机时,其转发表是空的。每当一个帧到达,交换机首先记录它的来源:将源MAC地址与它到达的端口配对,添加到转发表中。这个过程称为学习(learning),它会在每个经过的帧上自动进行,而不仅仅针对发送给交换机的帧——因为交换机是通过被动观察流量来构建表的,而非由人告知网络布局。之后,交换机会检查表中是否存在帧的目的MAC地址,有两种可能的结果:
- 如果目的地址未知(即该设备尚未发送过帧),交换机就会将帧泛洪(flood)到所有其他端口,确保目标设备能收到,而其他设备会忽略该帧。
- 如果目的地址已知,交换机就仅将帧转发到对应的端口,其他端口保持静默。
只需几帧流量,交换机就能学习到所有设备的位置,泛洪只是例外情况。下方演示了交换机如何构建转发表:已知目的地址时直接转发,未知时则泛洪。
路由器和交换机的核心目标相同:无需中央权威,仅通过本地决策实现正确转发,但它们构建表的机制完全不同。路由器的表必须显式填充——要么手动配置,要么通过路由协议自动发现网络拓扑;而交换机的表则是通过被动观察流量推断出来的。而且交换机的表具有严格的本地性:MAC地址仅在学习到它的本地网段内有意义。这正是两者分工处理互联网地址的原因:交换机使用本地地址在网段内转发帧,而路由器使用全局地址在网段之间转发数据包。
交换机普及后,还有一个悄然发生的变化:CSMA/CD存在的全部意义,是解决多台机器共享一条电缆时的冲突问题。共享电缆是半双工的(同一时间只能有一个信号在传输),因此发送和接收必须交替进行,冲突会导致两个信号相互干扰、数据损坏。而交换机为每台设备提供了专用线路,两台机器同时发送数据时不会发生冲突——交换机只需将其中一个帧暂存队列中几微秒,先发送另一个即可。现代以太网端口还支持全双工:发送和接收在独立的内部路径上同时进行,从根本上避免了冲突的可能。冲突检测机制就此过时,成为现代全双工链路上无用的遗留机制。
后来Wi-Fi又把共享介质带了回来——这次的介质是空气,冲突问题以更复杂的形式重现,后续的《无线篇》会详细讲解这一点。
连接不同的网络
ARPANET证明了分组交换的可行性,但它的成功也带来了新问题。20世纪70年代,其他分组网络陆续出现,基于完全不同的物理介质:卫星链路(SATNET)、车载分组无线电(PRNET)、建筑内的本地电缆(即刚才介绍的以太网)。每个网络都有自己的数据包格式、最大数据包大小和寻址方案,一个网络上的机器无法与另一个网络上的机器通信。要求所有网络彻底改造、采用相同标准,显然是不现实的。
1973年,温顿·瑟夫和鲍勃·卡恩设计了一套架构,无需统一各个网络就能将它们连接起来。他们提出的不是单一网络,而是一个互联网络(internetwork)——即网络的网络,这就是“互联网”(Internet)的字面含义。被称为网关(gateway)的路由器会部署在网络之间的边界,整个方案的核心是将问题拆分为两个层次,各自承担截然不同的任务。
互联网协议(IP)是所有人必须达成一致的唯一标准,而且它被刻意设计得非常简洁。它定义了通用地址——IP地址,用于标识所有参与网络中的每台机器;还定义了通用数据包格式,每个网络都同意承载这种格式的数据包,将其封装在自身的本地格式中传输。关键在于,IP几乎不做任何承诺:它是无状态的,路由器不会记住会话中数据包的任何信息;它也是不可靠的,只会尽力将数据包转发到目的地,如果数据包丢失,IP不会察觉也不会在意。这两个特性共同构成了无连接的含义:没有建立连接的过程,每个数据包之间也没有关联,每个数据包都被当作IP收到的第一个数据包处理。正因为要求极低,任何网络都能满足——铜缆、光纤、无线电、卫星,任何能传输字节的介质都能承载IP。这种架构形成了一个沙漏形状:底层是各种物理介质,顶层是各种应用,它们通过中间一层简单的协议实现互联互通。
下面我们来看看IP地址和路由器的匹配表到底是什么样的。IP地址本质上就是32个比特,即4个字节(在网络领域通常称为八位组),写成十进制数字并用点分隔。路由器表中的路由条目通过前缀来标识一个网络,前缀表示前多少位比特是固定的。例如/24表示前24位是固定的,剩下的8位是可变的。匹配路由时只需对比比特:如果一个地址的前N位(N为前缀长度)与路由条目的前N位一致,就匹配成功。路由器硬件会通过一次操作完成这个对比:将地址与路由条目进行异或运算,然后屏蔽掉前缀长度之外的部分,如果结果全为零,则说明匹配成功——异或运算会在两个比特不同的位置产生1,因此“固定部分内没有不同比特”就意味着匹配。这和旧的子网掩码表示法是同一个意思:子网掩码用一个点分十进制数来标识哪些比特是固定的,例如255.255.255.0和/24都表示前24位是固定的,只是前者按八位组书写,后者直接写位数。下方的模拟器将一个地址逐八位组展开为32个比特,并演示如何与多个候选路由条目进行对比。
这就是路由匹配的全部机制:实际的路由表会同时对所有路由条目进行对比,选择最长前缀匹配(即固定比特数最多的匹配条目),只有当没有更具体的匹配时,才会使用默认路由(0.0.0.0/0)。这一条规则让路由表保持精简:路由器不需要为地球上的每个地址单独建立条目,只需为它有更具体理由了解的网络建立条目;其他所有流量都通过默认路由转发到更靠近“互联网其他部分”的邻居。没有路由器需要掌握全局网络的完整信息,它只需要了解自己的邻居,以及一个更优的转发方向即可——因为下一个路由器也会应用同样的局部信息,如此传递下去,直到数据包足够接近目的地,总有某个路由器能精确知道该往哪发。在一个无人能完全测绘的网络中实现正确交付,正是每一跳都执行这个简单局部操作所产生的涌现特性。
这种长度灵活的前缀并非最初的设计。1981年IP寻址方案正式确立时,采用的是分类寻址:地址的网络部分不是可调整的前缀,而是由地址本身决定的三种固定长度。A类地址给一个组织分配1600万个主机地址,B类是65536个,C类只有256个,没有中间选项。大多数组织只需要几千个地址,但在这种僵化的体系下,他们只能二选一:要么拿一个完整的B类地址(浪费数万个地址),要么申请多个独立的C类网络。而分类路由无法将这些独立的C类网络合并,因此每个C类网段都必须在互联网核心路由器的路由表中单独列一条条目。到20世纪90年代初,全球路由表的增长速度超过了路由器内存的承载能力,可用地址空间也即将耗尽。1993年,解决方案出炉——无类别域间路由(CIDR)。它允许网络部分的长度是任意值,而非只有三种固定长度,因此网络可以根据组织的实际需求精确分配大小,相邻的网段还可以合并为一个路由条目。模拟器中的/24前缀就是CIDR前缀,至今仍是互联网的标准路由方案。
这些网段实际上来自哪里?它们的分配遵循与DNS相同的层级授权(我们将在《用名称代替数字记忆》一节中探讨DNS),只是针对数字而非名称。互联网编号分配机构(IANA)——同时负责管理DNS根域的机构——掌控着整个32位地址空间。它将大地址块分配给五个区域注册机构,分别对应北美、欧洲、亚太、非洲和拉丁美洲。每个区域注册机构再将其地址块划分为更小的部分,分配给本地区的互联网服务提供商;服务商再进一步拆分,将/24或更小的网段分配给企业和家庭。实际上,你家里路由器的公网IP地址就来自这样的网段。没有人掌握完整的地址分配地图,每个注册机构只需要了解自己负责的区域即可。这种层级授权设计,与DNS从斯坦福大学的单个文件扩展到全球的设计完全相同,只是用于解决IP地址分配的管理难题。
到目前为止,我们还没讲路由表中的条目是怎么来的——下方的模拟器直接给路由器提供了一个现成的表,然后演示如何使用它。最简单的来源是静态路由:手动配置,例如“所有发往10.0.0.0/8的流量从5号线路转发”。但这无法扩展到大规模网络,因此真实路由器的表大多是自动填充的:同一组织内部的路由器,会通过某种机制共享各自观察到的拓扑;而不同组织的路由器互不信任,会通过另一种机制向邻居通告自己能到达哪些网络。这两种机制的详细解释,以及路由表如何动态生成,将在《路由从何而来,谁在传输你的数据包》一节中展开。现在我们先假设路由表是固定的,看看路由器收到数据包后会做什么。
演示路由器硬件如何执行这条规则:数据包到达某个接口,读取包头,将TTL减1,查询路由表,然后从匹配的线路转发。切换不同的目的地,可以看到匹配结果的变化。
包头中有一个字段值得单独解释:生存时间(TTL)。发送方会设置一个初始值,沿途每个路由器在转发前都会将其减1;当TTL变为0时,路由器会立即丢弃该数据包。这个值与实际时间无关,而是跳数,它的存在是因为在没有中央权威的网络中,路由表可能会形成环路——两台路由器都认为对方离目的地更近,导致数据包在它们之间来回转发,不断增加网络负载。TTL保证了数据包不会无限循环。
路由器丢弃TTL过期的数据包时,不会默默处理,而是会向发送方返回一个小型的互联网控制消息协议(ICMP)消息:“超时”。ICMP是IP的维护通道,路由器通过它向源端报告问题,而非直接丢弃流量。互联网上最简单的诊断工具ping就基于ICMP:ping发送一种ICMP消息——“回显请求”——到目标地址,只要求对方返回一个ICMP“回显应答”。没有端口,没有握手,没有有意义的载荷,只是一个问题(“你在吗?”)和一个回答。发送请求到收到应答的往返时间,就是人们常说的“ping值”,它是对到某台机器延迟的直接、无过滤测量。下方是对真实地址执行ping的结果:8次请求,8次应答,每次都标注了耗时。
刚才描述的ICMP超时行为,正是traceroute工具的核心机制——这是所有操作系统都自带的诊断工具。它发送多个数据包,依次将TTL设置为1、2、3……TTL=1的数据包总会在第一个路由器处过期,该路由器会返回“超时”消息并告知自己的地址;TTL=2的数据包会在第二个路由器处过期,以此类推,直到数据包最终到达目的地。traceroute并非协议设计时就有的功能,而是完全源于原本用于防止环路的跳数计数器。对真实地址执行traceroute,会列出你和目标之间的每一个路由器,每一行对应一个TTL值,代表离你更远的一跳。
前面提到的另一个问题——每个网络的“最大数据包大小”——还没有解决。每种链路技术都对单个数据单元的字节数有上限,即最大传输单元(MTU):以太网的MTU是1500字节,其他链路的MTU各不相同。为某个宽松链路设计的数据包,可能远大于后续某个链路的MTU。IP的传统解决方案是分片:当路由器遇到一个大于下一跳链路MTU的数据包时,会将其拆分为多个更小的片段,每个片段都是一个独立的有效数据包,目的地会根据片段头中的编号重新组装。这种方法可行,但成本很高——路由器需要为每个片段做大量处理,而且只要丢失一个片段,整个原始数据包就必须重新发送。
现代IP大多避免使用分片:发送方会标记数据包“不要分片”,如果路由器无法转发完整的数据包,就会返回一个ICMP消息:“数据包过大,请尝试使用这个MTU”。源端随后会缩小后续发送的数据包大小,这个过程称为路径MTU发现,将大小问题推回给真正能解决它的发送方。IPv6更进一步,完全从协议中移除了路由器分片功能,只保留路径MTU发现机制。下方演示了同一个超大数据包遇到窄链路时的两种处理方式:旧的分片路由器,以及现代的拒绝转发并告知MTU的路由器。
到目前为止,我们讨论的都是一个发送方对应一个接收方的情况,即单播(unicast),这覆盖了绝大多数流量,但并非全部。你的IP地址前缀(即前面CIDR讨论中的/24或其他长度)标识了你的子网,子网既是一个数值范围,也是一个物理区域——即直接共享一条线路或一个无线信道的设备集合,无需路由器就能通过MAC地址直接通信。发送给子网内所有主机的数据包,称为广播(broadcast),适用于主机不知道该向谁请求的情况:例如动态主机配置协议(DHCP),为没有地址的设备分配地址;还有地址解析协议(ARP),询问“我们子网中谁拥有这个IP地址”以获取对应的MAC地址。广播会在子网边界停止,路由器不会转发广播——否则一个广播就会传遍整个互联网,迫使地球上每台计算机都处理它。
ARP值得我们稍作停留,因为它正是本文花大量篇幅介绍的两种地址之间的关键纽带。你的机器会生成一个目标IP地址的IP数据包,但这个数据包仍需通过以太网或Wi-Fi发送,而帧需要的是目的MAC地址,而非IP地址——IP根本不知道MAC地址的存在。因此,在新会话的第一个帧发送之前,你的机器会向整个子网广播一个问题:“谁拥有这个IP地址?请回复我的IP地址。”子网内的其他所有主机都会将这个问题与自己的地址对比,只有被询问的主机才会回复,而且是单播回复,直接发送给提问者,而非整个子网。回复会被缓存几分钟,因此整个交换过程只需针对每个邻居进行一次,而非每个数据包都要重复。
还要注意你实际解析的是哪个IP地址:如果目标IP在你的子网之外(几乎所有请求都是如此),你不会ARP请求远程服务器的MAC地址——这毫无意义,因为你的机器发送的帧无法到达不在本地线路上的MAC地址。相反,你会ARP请求默认网关的MAC地址——也就是瑟夫和卡恩在本节开头提到的边界路由器的日常名称。你将帧交给网关,由它接管后续的传输,逐跳转发,就像前面的路由器跳数模拟器演示的那样,只是第一跳的地址是通过ARP机制获取的。下方演示了一台主机如何解析邻居的MAC地址,然后从缓存中读取已解析的地址,再解析网关的MAC地址——因为子网外的流量只需要下一跳的地址,而非最终目的地的地址。
在单播和广播之间,还有一种中间情况——多播(multicast):一个数据包发送给一组主机,仅转发给请求加入该组的主机。这是IPTV这类流量的天然形态:有很多感兴趣的接收者,但远非所有主机。路由协议内部也会依赖多播,但在公网上你很少会直接遇到它。
最初的IP地址设计是32位,约有43亿个可能的地址,在当时的科研网络中看似无穷无尽,但这个设计一直困扰着互联网——我们将在本文后面探讨它带来的后果(以及你家中正在使用的解决方案)。它的继任者是IPv6,拥有几乎无限的128位地址,采用与MAC地址相同的十六进制表示法,写成八组用冒号分隔的数字,例如2607:f8b0:4004:c07::66。IPv6已经部署了二十年,如今承载了近一半的互联网流量,与IPv4并行运行——这也是分层设计悄悄带来的可能性之一。
让数据包交付变得可靠
传输控制协议(TCP)提供了IP拒绝承担的所有可靠性保障,而且它只在会话两端的计算机上运行,中间的路由器甚至不知道它的存在。TCP会为每个发送的字节编号,接收方则持续回复它期望接收的下一个字节编号,即确认(ACK)。任何未及时得到确认的数据,发送方都会重新发送。乱序到达的数据包会在数据向上层交付前,通过编号重新排序。TCP从IP尽力而为的混乱中,构建出了每个网络程序都依赖的抽象:一个可靠、有序的字节流。要实现这一点,通信双方需要在会话期间记住交换的进度,这就是为什么TCP被称为面向连接的——与IP的“健忘”完全相反,TCP会话有明确的开始和结束,而单个IP数据包则没有。
在深入探讨之前,先澄清一个命名问题:本文一直用“数据包”指代任何层级的数据单元,因为这种区分对大多数讨论的要点影响不大,但TCP自己的数据单元有专门的名称——段(segment),即TCP交给IP传输的字节流片段,对应以太网的“帧”和IP的“数据包”。
TCP会话的明确开始是三次握手:通过三个段的交换,在传输任何数据之前同步双方的字节编号。双方各自为即将发送的字节选择一个随机的起始序列号,然后交换这些序列号:客户端发送SYN, seq=5000(“我的字节将从5000开始计数”),服务器回复SYN-ACK, seq=9000, ack=5001(“我的字节从9000开始;我已收到你所有到5000的字节,请发送5001及以后的”),客户端再回复ACK, ack=9001(“请发送9001及以后的字节”)。三个段之后,双方就字节计数的起始达成一致,这让TCP后续能精确指出哪些字节丢失了。下方演示了这个交换过程。
确认和重传解决了“数据是否到达”的问题,但数据包到达后可能并非正确的:电噪声、不良连接器、宇宙射线击中内存芯片,都可能导致传输过程中某个比特翻转,而数据包并未丢失。要检测这种情况,需要另一种更古老、更简单的机制——校验和(checksum)。发送方对要发送的字节执行一个简单的算术运算,将结果附加在数据包后;接收方对收到的字节执行相同的运算,并对比结果。如果不一致,说明传输过程中发生了变化,接收方会直接丢弃数据包,就像它从未到达过一样,发送方会因为没有收到确认而自动重新发送。
校验和的设计目的并非识别或修复错误,而只是廉价地检测错误——它会在每个帧和数据包上执行。正因如此,几乎每个层级都有校验和:以太网的帧校验序列、IP的包头校验和、TCP自己的段校验和。请注意校验和能防范什么、不能防范什么:它能检测链路噪声导致的意外损坏,但无法防范故意篡改——攻击者可以修改数据后重新计算校验和,使其匹配篡改后的数据。这种威胁正是本文后面要讲的TLS要解决的问题。下方演示了正常传输和损坏传输的对比,以及接收方如何检测到损坏:
TCP还会决定同时传输多少数据。发送方维护一个滑动窗口,即未确认数据包的最大数量上限;达到上限后,发送方必须暂停等待。窗口不会自行移动,只有收到确认后才会移动:每个确认都会确认最早的未确认数据包,让新的数据包可以加入窗口的末尾——这就是“滑动”一词的由来,每确认一个数据包就向前滑动一次。这种机制能防止快速发送方淹没慢速接收方。当数据包丢失时,TCP会将其解读为一个信号:路径上某个路由器的队列已满,于是它会缩小窗口以减少发送量。这种拥塞控制的反射机制,是通过惨痛教训才加入的。1986年10月,劳伦斯伯克利实验室和加州大学伯克利分校之间的链路(仅400米),传输速率从每秒32000比特骤降至每秒40比特——下降了一千倍,原因是每个发送方都因数据丢失而拼命重传,形成了一个恶性循环,称为拥塞崩溃。范·雅各布森提出的解决方案——数据丢失时按比例减少发送量,传输成功时缓慢增加发送量——至今仍在地球上几乎所有TCP实现中运行。数十亿个连接各自遵循相同的本地规则,无需中央协调,就能防止共享网络陷入瘫痪——这与我们在分组路由中看到的涌现式协作如出一辙,只是应用于带宽管理。
下方的模拟器演示了TCP发送方在滑动窗口限制下传输数据包的过程。当一个数据包丢失时,接收方会持续确认它收到的最后一个有序字节,大量重复的确认会提示发送方重新发送丢失的数据包,而无需等待超时。
还有两个细节完善了传输层的图景:IP地址标识一台机器,但一台机器同时运行多个程序,因此TCP添加了端口号,IP地址加端口的组合能将每个字节流交付给正确的程序——例如,Web服务器通常在443端口监听加密流量。IP地址加端口加协议的组合,就是套接字(socket)所指的内容,即应用程序打开的实际端点,而非仅仅是它所在的机器。服务器的端口是固定的,这样客户端知道该连接哪个端口;但客户端也需要一个端口,因为回复需要发送到比机器更具体的目标,所以操作系统会从专门预留的范围内随机选择一个临时端口,仅在一个连接的生命周期内使用,之后归还。这就是本文末尾数据包图中源端口字段的54211——不是某个正在监听的端口,只是你的机器为那个随机请求选择的编号。
对于那些重传过期数据比丢失数据更糟糕的应用(比如实时视频通话,两秒前的帧毫无用处),还有用户数据报协议(UDP)——一种轻量级替代方案,仅在IP之上添加了端口,其他什么都不做,让每个应用自行以适合的方式处理数据丢失。UDP自己的数据单元称为数据报(datagram),与以太网的帧、IP的数据包、TCP的段对应。这个词本身就是一种描述:一个包含所有必要地址信息的独立包裹,无需建立连接,也不记得之前的传输。UDP是无连接的,原因和IP一样:它不保存状态,无需建立连接,只是添加足够的寻址信息以到达正确的程序。这并非TCP的简化版,而是一种不同的取舍:TCP适用于网页、电子邮件或文件传输,因为不完整的副本比稍晚到达的完整副本更糟糕;UDP适用于视频通话、多人游戏或DNS查询,因为迟到的回复比丢失的回复更糟糕(或者应用会直接请求最新状态)。
1983年1月1日,TCP/IP成为ARPANET的官方协议,从此互联网的定义变得简单:所有同意承载IP数据包的网络,共同构成了互联网。没有会员办公室,只要遵循它的协议,就加入了互联网。
路由从何而来,谁在传输你的数据包
从分组交换一节开始,我们一直回避一个问题:路由器如何“知道”哪条线路通往目的地方向?答案与本文中其他问题一样,清晰地分为两类:组织内部的规则,与组织之间的规则截然不同。
在单个组织的网络内部,路由器运行内部网关协议(IGP),交换各自直接观察到的拓扑信息(哪些链路可用、每条链路的拥堵情况等),自动计算路由。主要有两类协议,它们在一个基本问题上存在分歧:路由器需要掌握整个网络的拓扑,还是只需知道一个间接的数值?
链路状态协议(如开放最短路径优先(OSPF))的答案是“掌握整个网络拓扑”。每个路由器会将自己的本地连接信息泛洪到所有其他路由器,因此每个路由器最终都持有相同的完整拓扑图,并独立运行相同的最短路径计算。
距离矢量协议(如较早的路由信息协议(RIP))的答案是“只需一个数值”。每个路由器只告诉邻居“我能在N跳内到达这个网络”,自己看不到拓扑图,只是信任这个间接的计数并加1。这种方式交换的数据量远小于泛洪拓扑图,但路由器无法验证邻居的计数是否仍然有效。下方演示了两种协议的对比。每个循环开始时,一个普通数据包会先穿过所有三台路由器,执行与单个路由器模拟器相同的最长前缀匹配查询,只是由三台从不交换拓扑信息的路由器依次执行——请注意每台路由器实际上知道的信息有多么有限。
当一条链路故障时,每个路由器的表都会暂时失效,直到新状态传播完成;正如我们在分组交换网络中看到的,收敛是行业对这个“追赶”过程的称呼。链路状态协议只需两次泛洪消息就能完成收敛,因为泛洪传递的是实际的拓扑变化,而非猜测。距离矢量协议没有这样的确定性,RIP演示中展示了它的故障模式:两个邻居会短暂地相互路由,各自信任对方过时的计数,导致发往该目的地的数据包在它们之间来回跳转,直到TTL过期。生产环境中会给RIP添加各种缓解措施,但没有任何措施能让间接计数的收敛速度快于经过验证的拓扑图,这就是OSPF在大多数生产网络中取代RIP的原因。无论哪种情况,这都是单个管理员需要解决的问题:一家公司的路由器,由该公司决定运行哪种协议。
组织之间的情况要复杂得多。没有统一的管理员或共享信任,解决方案正是互联网去中心化特性真正形成的地方。
1983年TCP/IP成为官方协议时,这种由多个组织构成的网络格局尚未形成,当时的网络本质上仍是一个由联邦资助的单一系统。真正让它分裂的是美国国家科学基金会的NSFNET,它于1985年建成,作为骨干网连接各个地区性学术网络(每个网络由不同的大学联盟运营),形成一个全国性网络。最初几年,NSFNET的《可接受使用政策》完全禁止商业流量:不允许商业电子邮件,不允许广告,仅用于科研和教育。1991年,这一限制被取消;1995年NSFNET本身被关闭时,其骨干网角色并未交给单一继任者,而是交给了几家相互竞争的商业运营商——它们没有理由合并,反而有充分的理由保持独立,作为竞争对手向相同的客户提供相同的服务。这次交接——一个政府网络演变为由竞争对手构成的市场——标志着互联网从此不再有所有者,成为一个由BGP协议维系的拼凑网络。
互联网在物理上是由数万个独立运营的网络(称为自治系统)组成的拼凑体,包括你的家庭互联网提供商、大学、国家电信公司、云服务商等。它们使用边界网关协议(BGP)交换路由信息。每个网络向邻居通告“我能到达这些地址块”,并将从其他邻居收到的通告(附加自己的路径)再转发出去。通往互联网各个角落的路由信息在网络之间传播,每个网络根据商业策略(而非速度)选择它收到的路径——这涉及资金往来。抛开商业策略,底层的最短路径平局规则很简单:一条路由逐跳传播,每一跳都将自己添加到路径中,直到某个路由器收到两条不同的路径,需要从中选择。
小型提供商向大型提供商支付传输费用,以获得通往整个互联网的通道;而规模相当的网络之间则进行对等互联,免费交换各自客户之间的流量(行业术语称为“无结算”),因为这能为双方节省传输费用。对等互联通常在互联网交换点进行:这些建筑中,数百家网络接入同一个公共交换结构,最大的交换点(如法兰克福的DE-CIX和阿姆斯特丹的AMS-IX)连接着约一千家网络。这两种合同正是前面路由选择中“商业策略”的含义:对于任何目的地,通过邻居的路由分为三类成本:通过客户(客户向你付费以传输流量)、通过对等方(免费)、通过提供商(你为每个字节付费)。因此,网络会配置BGP优先选择客户路由,然后是对等路由,最后是提供商路由——即使付费路由的跳数更多;只有当商业地位相同时,才会通过最短自治系统路径来决定。
顶层是少数几个一级骨干网,它们之间相互对等互联,无需向任何方付费——这些公司大多不为大众所知,如Lumen、Arelion、NTT等。跨洋传输则依赖约600条海底光纤电缆,几乎所有洲际流量都通过它们传输。互联网并非靠权威维系,而是靠这种合同、握手和BGP通告构成的网络。
这些电缆是电报时代海底线路的后代,已经历了几代演进。第一条跨大西洋电话电缆TAT-1于1956年投入使用,是同轴铜缆,可承载36条语音电路,大约每70公里就接入一个放大器,在信号衰减为噪声之前对其进行放大。这又回到了电报一节中的模拟信号问题:放大器无法区分信号和噪声,会同时放大两者,而且与电报脉冲不同,语音波形无法在每一跳都清晰地再生为原始形状。光纤打破了这种权衡。1988年铺设的TAT-8是第一条跨大西洋光纤电缆,它不仅改进了TAT-1,还让后者一夜之间过时:两根玻璃纤芯就能承载数万个等效语音电路,因为玻璃中的光可以分割成比铜缆电压多得多的独立信道。此后铺设的所有跨大西洋电缆都是光纤,如今运行的约600条电缆都是TAT-8的后代,而非TAT-1的。

要让这一切可行,光必须保持在玻璃内部。光纤的纤芯是折射率略高于外层包层的玻璃,光在纤芯中传播时,会以小于临界角的角度到达纤芯与包层的边界,因此不会折射出去,而是完全反射回来。这种完全反射是关键:普通镜子每次反射都会吸收千分之几的光,对于每公里要反射数百万次的信号来说,这会导致信号迅速消失;而全内反射在边界处没有能量损失,这就是为什么一根发丝细的玻璃纤维能引导脉冲传输约100公里,穿过海床的每一处起伏和弯曲,才需要放大器介入。
将光分割为独立信道的技术称为波分复用,下方的模拟器演示了它的工作原理:每个激光器将自己的比特流调制到特定波长的光上,合波器将所有波长的光送入单根纤芯。由于玻璃是线性介质,不同的波会叠加(振幅相加),在同一纤芯中传输而不会相互干扰,另一端的分波器可以将每个波长的光完整过滤出来。无需让单个激光器更快闪烁,只需用不同颜色的光并行传输多个比特流即可。添加一个波长,就能让已铺设在海底的光纤的吞吐量翻倍。
每个比特流保持自己的速率;添加一个波长,就能在现有光纤基础上增加一个完整的比特流。实际系统中,每根纤芯大约可以容纳100个波长。

BGP的机制并未要求一个地址只能从一个地方通告,而故意打破这一点就是任播(anycast)的工作原理。多台服务器(通常位于不同大洲)被配置为使用相同的IP地址,每台服务器所在的网络都会像它是唯一所有者一样,通告通往该地址的路由。BGP不知道也不关心它同时从圣保罗、伦敦和东京收到同一个地址的通告,它只会做自己一直做的事:选择路径最短的通告,这意味着每个客户端的流量会被路由到拓扑上最近的服务器副本。两个不同城市的人请求同一个地址,会被路由到不同的物理机器,而DNS查询或客户端配置中没有任何迹象表明这一点。下方演示了三个位于不同大洲的客户端向同一个地址发送请求,各自连接到不同的服务器。
这种在路由层面将流量引导到共享同一IP地址的最近机器的技术,是**内容分发网络(CDN)**的两大核心技术之一。Cloudflare(包括本网站)或Akamai等CDN会在全球各地的服务器上存储相同内容的副本(视频、图片、整个网站),目的就是让请求从附近的硬件得到响应,而非从可能遥远的源服务器获取。另一种技术通常叠加在任播之上,或单独使用:地理感知域名服务器,它会根据查询来源的位置,返回附近数据中心的地址,而非给所有人相同的答案——这是DNS(我们将在《用名称代替数字记忆》一节中探讨的命名系统)内部的技巧。无论哪种方式,收益都可以追溯到本文的第一个概念:延迟是光速设定的下限,击败它的唯一方法是在用户请求前,将数据移动到离用户更近的地方。
BGP的设计惊人地信任他人:网络大多相信邻居通告的内容。2008年,巴基斯坦电信为了在国内屏蔽YouTube,通告了一条比YouTube自身更具体的路由。这条通告泄露到了境外,几分钟内传遍全球,导致全球大部分YouTube流量都流向巴基斯坦,然后消失不见。此类事件就是为什么有时某个服务会突然从半个世界消失的原因。这也是工程师多年来一直在为BGP添加加密验证的原因——这项工作称为资源公钥基础设施(RPKI),让路由器可以对照签名注册表检查路由通告,而非仅凭信任接受。什么是加密签名,为什么它可以被验证但无法伪造,正是我们将在《通过公共线路交换秘密》一节中构建的机制。
你的家庭私有网络
你的家庭网络将上述多个概念浓缩进了一个塑料盒子里。你书架上的路由器内置了一个小型以太网交换机(采用《用一条共享线路连接整个办公室》一节中的MAC地址学习机制),以及用于无线连接的Wi-Fi无线电;它通过《连接网络》一节中提到的DHCP广播,为每个接入的设备分配地址,回复中还包含新设备需要的另外两项信息(无需手动输入):发送子网外流量的网关地址,以及DNS解析器的地址(即我们将在《用名称代替数字记忆》一节中探讨的名称查询系统)。而且,由于32位IPv4地址稀缺,这些地址是私有地址,在家庭网络之外没有意义。有几个地址块被专门预留用于此目的,不会被公开分配,也不会在互联网上路由——这就是为什么这些地址块可以被无数家庭重复使用。下表中还有一个预留地址块,是你的机器仅用于与自身通信的地址,我们稍后再讲。
前三个地址块永远不会在公网上路由,因此你路由器的192.168.1.5和地球另一端陌生人的192.168.1.5是完全不同的设备,永远不会冲突——因为携带这些地址的数据包永远不会离开它们所属的私有网络。路由器通过在数据包通过时重写地址,将自己的单个公网地址(由ISP的DHCP分配,与上一级网络的机制相同)共享给所有设备,这就是网络地址转换(NAT)。路由器会维护一个表,记录每个外部回复对应哪个内部会话。下方演示了两个无关家庭重复使用相同私有地址,以及NAT如何防止它们冲突;还演示了同一个家庭中的第二台设备如何共享路由器的单个公网地址。
NAT有一个盲点,悄然改变了互联网的形态:由于路由器只记录从内部发起的会话,它会丢弃所有未经请求的外部流量。这打破了对称的服务器角色:你的笔记本电脑可以访问任何服务器,但没有任何设备能未经邀请访问你的笔记本电脑。这就是为什么家庭托管服务需要手动设置“端口转发”(告诉路由器哪个内部机器应该接收特定端口的外部流量),并跟踪ISP定期轮换的公网IP地址(以节省地址)。这也是为什么视频通话等点对点应用必须使用复杂的变通方法(比如双方同时发送数据包,让两台路由器都认为是自己的用户发起了会话)才能直接连接。一个解决地址稀缺问题的临时方案,最终将互联网分为两类:提供服务的一方,和仅提出请求的一方。
最后一行127.0.0.0/8的地址更本地化:发送到127.0.0.1(通常称为localhost,即本地主机)的数据包,根本不会到达你的网卡,而是由操作系统直接回环到你自己机器上正在监听的程序。这就是为什么你在笔记本电脑上启动的开发服务器,只能在这台笔记本电脑上通过127.0.0.1:3000访问,而地球上其他任何地方都无法访问。而且不止127.0.0.1,整个地址块都用于回环——多达1670万个地址,与最大的私有地址块大小相同,这是一个来自没人能想象32位地址空间会耗尽的时代的“奢侈”预留。
有一个分层细节值得记住:你的设备同时拥有两个地址:一个是属于网络硬件的MAC地址,回答“这条线路或信道上的哪台机器”;另一个是IP地址,由你接入的网络分配,在整个互联网上都有意义。MAC地址永远不会离开你的本地网络,IP地址才是全球路由的依据。我们将在《点击链接时实际发生了什么》一节中,追踪一次点击的完整过程,届时会看到这两个地址同时出现在同一个数据包中。
用名称代替数字记忆
路由器将数据包发送到208.80.154.224这类数字地址,但我们几乎不可能记住这些数字。如果没有名称,你需要一本个人日记才能记录喜欢的网站的IP地址。在ARPANET时代,整个地址映射都保存在一个名为HOSTS.TXT的文件中,由斯坦福研究院的伊丽莎白·芬勒手动维护,网络上的每台计算机定期下载最新副本。到20世纪80年代初,这种方式显然已经失效:一个办公室维护一个文件,根本跟不上指数级增长的网络;所有人都从同一个地方获取文件,这本身就是一个瓶颈。
以下是1984年3月27日版本文件中的几行内容:每台机器占一行,列出其地址(或多个地址)、名称、硬件和操作系统,以及支持的协议——每个字段都是手动维护的。
; DoD Internet Host Table
; 27-Mar-84
;
; Changes, corrections, comments or questions to (HOSTMASTER@SRI-NIC)
NET : 10.0.0.0 : ARPANET :
HOST : 10.0.0.51, 26.0.0.73 : SRI-NIC.ARPA,SRI-NIC,NIC : DEC-2060 : TOPS20 : TCP/TELNET,TCP/SMTP,TCP/TIME,TCP/FTP,TCP/ECHO,ICMP :
HOST : 10.0.0.1 : UCLA-CS.ARPA,UCLA-CS,UCLA-CECS : VAX-11/750 : LOCUS : TCP/TELNET,TCP/FTP,TCP/SMTP :
HOST : 10.0.0.6 : MIT-MULTICS.ARPA,MIT-MULTICS,MULTICS : HONEYWELL-DPS-8/70M : MULTICS : TCP/TELNET,TCP/SMTP,TCP/FTP,TCP/FINGER,TCP/ECHO,TCP/DISCARD,ICMP :
HOST : 10.0.0.4, 192.5.12.21 : UTAH-CS.ARPA,UTAH-CS : VAX-11/750 : UNIX : TCP/TELNET,TCP/FTP,TCP/SMTP :
第一条条目描述了文件本身的宿主:SRI-NIC就是所有人下载HOSTS.TXT的机器,标题中的HOSTMASTER@SRI-NIC就是芬勒的团队。要查询一个名称,计算机只需在本地副本中搜索,文件中没有的机器,实际上就相当于不存在。
1983年,保罗·莫卡佩特里斯设计了它的替代方案——域名系统(DNS),基于授权机制构建。域名空间是一个从右到左读取的层级结构:对于en.wikipedia.org,层级的根服务器只知道谁运行org这类顶级域名;org服务器只知道谁运行wikipedia.org;而维基百科自己的域名服务器则持有en.wikipedia.org的实际地址。没有人存储完整的映射,相反,域名空间被划分为称为区域的管理边界。每个组织控制自己区域(例如公司管理yourdomain.com)的权威域名服务器,可以自行修改记录或授权子区域(如sub.yourdomain.com)给他人,无需征得任何人同意。要查询一个名称,你的设备会向递归解析器(通常由你的互联网提供商运行,或Cloudflare的1.1.1.1这类公共解析器)发送请求,递归解析器会代表你遍历整个授权链,返回最终的地址。
查询en.wikipedia.org的过程如下,每个回答都会告诉解析器下一步该去哪里:
- 解析器向根服务器询问:“
en.wikipedia.org的地址是什么?”根服务器不知道,但会回答:“我不管理org,但这是管理org的服务器地址。”并返回.org域名服务器的地址。 - 解析器向
.org服务器询问,对方同样不知道en.wikipedia.org的地址,但会回答:“维基百科运行自己的服务器,这是它们的地址。”并返回wikipedia.org域名服务器的地址。 - 解析器向维基百科自己的域名服务器询问——这是链中的最后一次授权——最终得到真实答案:
en.wikipedia.org的地址是91.198.174.192,有效期为3600秒。
这个3600也称为TTL(生存时间),和前面IP的跳数计数器使用了相同的三个字母,容易混淆,但这里它指的是秒为单位的有效期,而非路由器跳数——允许解析器缓存该答案一小时,之后再重新查询。解析器将91.198.174.192返回给你的浏览器,之后使用该解析器的任何人查询en.wikipedia.org,都会直接从缓存中得到答案,无需遍历整个授权链,直到一小时有效期结束。
在互联网的规模下,每次查询都遍历授权链会压垮上层服务器,因此每个回答都带有TTL,并在靠近请求者的位置被缓存——浏览器、操作系统,尤其是服务大量用户的递归解析器都会缓存。家庭路由器可能会转发DNS请求,也可能自己运行解析器,但普通的数据包路由与缓存无关。热门名称几乎总是从几毫秒外的缓存中得到回答,根服务器很少会重复处理相同的查询。
这个遍历过程并非隐藏的机制,你可以自己执行。下方使用标准DNS查询工具dig手动执行了这三次授权查询:根服务器返回.org服务器地址,.org服务器返回维基百科域名服务器地址,后者最终返回IP地址——与模拟器演示的流程完全一致。
DNS只做一件事:将名称转换为数字。得到91.198.174.192后,你的浏览器终于可以连接到该地址,通常是连接到加密Web服务使用的443端口。接下来的数据包不再是DNS查询,而是发送到Web服务器的TCP数据包——模拟器末尾展示了这个交接过程。
加入这个层级结构并非自动完成,了解名称如何加入其中很有意义,因为机制还是授权。你从注册商处购买yourdomain.com——注册商是某个顶级域名(如.com由Verisign运营)注册机构的授权经销商。注册商的实际工作是在注册机构的区域文件中写入一条NS记录,指定你作为域名权威的域名服务器——这正是解析器询问.com服务器“谁管理你的域名”时遵循的授权链。这些域名服务器可以由你的注册商、Cloudflare或Route 53等提供商运行,也可以是你自己的机器,它们持有你的域名区域文件——你可以直接控制的记录。回答“如何将我的域名指向我的服务器”的是A记录(IPv6则是AAAA记录),一行内容声明yourdomain.com的地址是192.0.2.7这类IP。添加该记录,等待之前缓存的答案过期,全球所有解析器都会开始将你的域名流量发送到你的机器:无需批准,只需在为这个目的构建的授权链中写入一条记录即可。CDN的地理感知技巧(任播一节提到的)也在这最后一步实现:域名服务器无需给所有请求者相同的答案,它可以自由返回离请求者最近的数据中心地址。
缓存的代价是数据过时。当网站迁移到新地址时,旧答案会在全球缓存中保留,直到其TTL过期。这种延迟就是DNS更改需要时间才能生效的原因,也是家庭网络和数十亿美元云服务中,相当一部分神秘故障最终追溯到DNS的原因。因为名称查询几乎是所有连接的第一步,它仍是整个互联网无声却至关重要的基础。
1983年设计的DNS和BGP一样,存在无防备的信任问题:当解析器发送查询时,它会简单地相信第一个到达的、与查询匹配的回答。2008年,研究者丹·卡明斯基展示了这种信任多么容易被滥用。解析器用一个16位的事务ID来识别哪个回答对应哪个查询,只有65536种可能;攻击者如果能在真实回答到达前猜出这个ID,就可以返回一个伪造的回答——不仅回答被查询的名称,还会附加一个未经请求的信息:“顺便说一下,这是yourbank.com其余部分的真实域名服务器,以后请向它们查询。”解析器没有理由怀疑这个“意外之喜”,会接受并缓存它。如果攻击者抢先一步,解析器就会将这个谎言缓存到攻击者设定的TTL过期为止,悄悄将该解析器的所有用户对整个域名的请求,都重定向到伪造的服务器。
解决方案是DNS安全扩展(DNSSEC),它填补了这个漏洞:每个区域都会对自己的记录进行加密签名,解析器会验证签名链,直到它信任的根密钥(使用我们将在