【Network】May

HTTP2 队头阻塞

HTTP/2 的多路复用确实解决了 HTTP/1.x 时代应用层的队头阻塞,但它并未根除传输层(TCP)的队头阻塞问题。

🧐 “队头阻塞”是什么?

简单说,这就像只有一个服务窗口的队伍:当前面的人因为业务复杂耗时很久,后面的人就算只想办个简单业务,也必须排队等着,效率极低。在计算机网络中,它被定义为因队列中首个数据包受阻,而导致后续数据包无法被及时处理的现象。

🚀 应用层问题已解决:HTTP/2 的多路复用

在 HTTP/1.1 时代,队头阻塞是性能瓶颈。一个 TCP 连接同一时间只能处理一个请求,上一个响应没完成,下一个请求就不能发。为了解决这个问题,HTTP/2 引入了革命性的**多路复用(Multiplexing)**技术:

  • 将请求切分为帧(Frame):把每个 HTTP 消息切分成更小的二进制帧。
  • 通过流(Stream)并行传输:在同一 TCP 连接上,将不同请求/响应的帧交织在一起传输,每个流的帧能通过 Stream ID 被识别和重组,互不干扰。
  • 实现全双工通信(Full-duplex):由此实现了真正的全双工,多个请求和响应可同时在一个 TCP 连接上交错传输,成功消除了应用层队头阻塞。

⛓️ 深层矛盾犹存:TCP 层的队头阻塞

虽然应用层问题解决了,但 HTTP/2 所有数据还是跑在单一 TCP 连接上,TCP 协议要求数据严格按序到达的特性,导致了新的问题。一旦底层网络发生数据包丢失,后续已到达的包都无法交给上层应用,直到丢失的包被重传,造成所有流瞬间阻塞。在高丢包率的弱网环境下,这种底层阻塞将变得极为常见。

💡 完美之道:向 HTTP/3 演进

针对 HTTP/2 在 TCP 层的瓶颈,使用 QUIC 协议的 HTTP/3 带来了根本性解决方案。QUIC 基于 UDP 独立管理每个流,一个流的丢包完全不影响其他流,从根本上避免了队头阻塞,显著优化了弱网环境下的用户体验。

总结来说,HTTP/2 的多路复用是巨大的飞跃,但它只是把队头阻塞从应用层移到了传输层。因此,它并非终点,而是一个关键的演进阶段。

TLS1.3 加密

上回我们聊到,HTTP/3 基于 QUIC 协议,彻底解决了 TCP 层的队头阻塞。而 QUIC 能实现安全高效的传输,秘诀就在于它深度集成了 TLS 1.3。可以说,TLS 1.3 正是 HTTP/3 安全的基石。

接下来,我就为你详细拆解 TLS 1.3 的加密机制,看看它如何做到既快又安全。


一、TLS 1.3 的核心设计目标

相较于存在了十多年的 TLS 1.2,1.3 版本的改进是颠覆性的,主要针对两大痛点:

  1. 减少握手延迟:将过去常见的 2-RTT 握手大幅优化为 1-RTT,甚至支持 0-RTT 恢复。
  2. 强化安全性:剔除所有已知不安全的加密算法和机制,默认启用完全前向保密

二、握手即加密:1-RTT 握手详解

这是 TLS 1.3 最精髓的部分。我们来看一次完整的首次连接握手过程,注意加密是如何层层推进的。

1. 客户端发起:ClientHello 客户端发送支持的密码套件、密钥交换参数(如 ECDHE 的公钥共享)等。由于是明文,这里只包含最基础的信息。 注:TLS 1.3 的密码套件已极度简化,不再包含密钥交换算法和签名算法,只指定“对称加密算法(AEAD)”和“哈希算法”。例如 TLS_AES_128_GCM_SHA256

2. 服务器回应:ServerHello + 立即加密 这一步服务器同时完成三件事,效率极高:

  • ServerHello:选定密码套件,并发送自己的 ECDHE 公钥共享。
  • 密钥协商完成:此时,双方已拥有足够信息,在内存中计算出握手通信密钥(Handshake Traffic Keys)。从这一刻起,后续所有的握手消息全部被这个密钥加密。
  • 发送加密消息:在同一个包体中,紧接着发送被加密的 [Certificate](服务器证书)、[CertificateVerify](用服务器私钥对握手信息的签名,证明证书持有权)和 [Finished](握手完整性校验)。

3. 客户端验证并完成:Finished 客户端用收到的服务器公钥共享计算出同样的握手通信密钥,解密并验证证书链和签名。验证通过后,双方基于之前的协商数据派生出最终的应用数据通信密钥(Application Traffic Keys)。客户端发送加密的 [Finished] 消息,此后所有应用数据都用最终的应用密钥加密。

可以看到,TLS 1.3 将密钥交换所需要的往返从 2 次降为 1 次,并且大部分握手信息都被加密了,大幅提升了隐私性和速度。

三、三大加密机制保障安全

  1. 强制“完全前向保密” 这是 TLS 1.3 最重要的安全特性。它完全废弃了静态 RSA 和 DH 密钥交换,强制使用带临时密钥的 ECDHE(椭圆曲线迪菲-赫尔曼短时) 或 DHE。这意味着,即使服务器私钥未来被破解,历史上记录的所有会话也无法被解密,因为每一次会话的加密密钥都是一次性的临时密钥。

  2. 纯粹高效的 AEAD 加密 TLS 1.3 只允许使用带关联数据的认证加密(AEAD),彻底告别了“加密后MAC”的古板模式。AEAD 算法(如 AES-GCM、ChaCha20-Poly1305)能同步完成加密和完整性校验,在保证数据机密性的同时,有效防止数据被篡改。其中 ChaCha20-Poly1305 专为移动设备优化,在没有 AES 硬件加速的设备上表现更好。

  3. 安全的密钥派生体系 TLS 1.3 采用 HKDF(基于HMAC的密钥派生函数)这种标准化的密码学工具,将一次密钥协商的结果,安全地派生出不同阶段(握手、应用、恢复等)和不同方向(客户端写、服务器写)的独立密钥。这样即使一个阶段的密钥意外泄露,也不会威胁到其他阶段。

四、锦上添花:0-RTT 恢复

对于近期连接过的网站,TLS 1.3 支持“零往返”恢复。客户端可在握手首个包中,用之前保存的预共享密钥(PSK)直接加密应用数据发出,延迟接近于零。 但需要警惕的是,0-RTT 数据不具备前向保密性,且存在重放攻击风险。因此它通常只适用于无副作用(幂等)的请求,如静态资源的 GET 请求。

总结:TLS 1.2 与 1.3 关键区别

特性TLS 1.2 (典型)TLS 1.3
首次握手往返2-RTT1-RTT
密钥协商算法支持RSA, DH, ECDHE仅支持 (EC)DHE,强制前向保密
握手过程加密证书等大部分消息明文传输ServerHello 之后全部加密
对称加密算法支持 CBC 流密码等仅支持 AEAD 加密
密码套件复杂度组合混乱 (四段式)精简 (仅AEAD+Hash)
0-RTT 模式不支持支持

总体来看,TLS 1.3 通过对握手的精妙简化和对过时技术的无情抛弃,构建了一个既快又安全的现代加密通道。它不仅是 QUIC/HTTP3 的先天优势,现今也已广泛应用于各类主流服务中,默默守护着我们每一次的网络通信。

ECDHE & DHE

简单来说,DHE 和 ECDHE 都能提供“前向保密”,但 ECDHE 是 DHE 的高效升级版,用更短的密钥实现了同样的安全性和更快的速度。

两者的核心区别在于底层的数学基础,这直接导致了性能和安全性上的巨大差异。

数学基础不同

  • DHE (临时迪菲-赫尔曼) 基于整数模幂运算,使用的是大质数。为了实现当前安全水平,密钥需要很长,导致计算量大、速度慢,尤其消耗 CPU。

  • ECDHE (临时椭圆曲线迪菲-赫尔曼) 基于椭圆曲线密码学,同样依赖离散对数难题,但解题难度远超于传统的大质数分解。因此,它能用非常短小的密钥,达到极高的安全强度。

关键差异对照

对比维度DHEECDHE
数学基础模幂运算 (Multiplicative Group)椭圆曲线点乘 (Elliptic Curve)
安全强度要达128位安全,需3072位密钥仅需256位密钥即可达128位安全
计算性能计算量大,速度慢,更耗CPU计算量小,速度快,更节能
资源占用密钥大,占用更多内存和带宽密钥极小,高效轻量
前向保密✅ 支持 (由“临时”保证)✅ 支持 (由“临时”保证)

在 TLS 1.3 中的应用

我之前提到,TLS 1.3 强制要求使用临时密钥来保证前向保密,因此它同时支持 DHE 和 ECDHE。但在实际使用中,ECDHE 是绝对的主流,几乎一统天下

因为 ECDHE 在提供同等安全性的前提下,性能远超 DHE,非常适合现代设备,特别是移动端和物联网这类对 CPU、电量和带宽敏感的场景。例如,X25519 就是目前最流行的 ECDHE 曲线,它不仅能做到极致性能,在安全性上也更易于安全实现。

除非是一些非常受限的旧环境或不支持 ECC 的场景,否则你遇到的现代 HTTPS 连接,用的基本都是 ECDHE。

如果想进一步了解 X25519 这类具体曲线是如何工作的,也可以告诉我。

交换律

你的直觉很敏锐。是的,ECDHE 的核心正是基于椭圆曲线数学结构中的“交换律”,但在密码学术语中,这个“交换律”的表述需要更精确一些。


一、核心公式的“交换律”本质

ECDHE 和 DHE 的密钥协商,都依赖于这样一个等式:

客户端算出的共享密钥 = 服务器端算出的共享密钥

我们对比来看,交换律是如何体现的:

  • DHE 的交换律

    • 公式:(g^a)^b = (g^b)^a (模幂运算的交换律)
    • 客户端:有私钥 a,收到公钥 g^b,算出 (g^b)^a
    • 服务端:有私钥 b,收到公钥 g^a,算出 (g^a)^b
  • ECDHE 的交换律

    • 公式:a(bG) = b(aG) (椭圆曲线标量乘法的交换律)
    • 客户端:有私钥 a,收到公钥 bG,算出 a(bG)
    • 服务端:有私钥 b,收到公钥 aG,算出 b(aG)

在 ECDHE 中,G 是曲线上的一个公开基点,ab 是各自的私钥(大整数)。a(bG)b(aG) 的结果都是曲线上的同一个点,这个点的坐标,就是共享密钥的原材料。

二、为何通常不叫“乘法”而叫“标量乘法”

你可能会好奇,为什么有“椭圆曲线不是乘法”这种说法。这其实是为了区分底层的数学操作:

  • DHE 的 g^a:这是乘法群里的指数运算(把 g 自乘 a 次)。
  • ECDHE 的 aG:这是椭圆曲线加法群里的标量乘法(把点 G 与自身相加 a 次)。

椭圆曲线密码学中,基础操作是“点加”,私钥 a 是一个标量。所以,aG 虽然最终体现为“交换律”,但底层是靠反复“点加”实现的,为了和模幂运算区分,才强调这个操作叫“标量乘法”。

三、这个特性带来的安全价值

这个交换律,是密钥协商的精髓。你算出的 a(bG) 和我算出的 b(aG) 完全相同,我们便有了一个共享秘密。但对只看到 GaGbG 的窃听者来说,想通过 aGbG 反推出 ab 是极其困难的,这就是椭圆曲线离散对数难题(ECDLP)。

所以,ECDHE 能够在不安全的信道上,让双方安全地得到一个相同的密钥,正是因为它巧妙地利用了这个数学结构里天然的交换律。

总结一下:ECDHE 完全建立在椭圆曲线的交换律之上,只是这个“乘法”的专业叫法是“标量乘法”,以便和 DHE 的模幂运算相区别。

TLS1.2

为什么 TLS 1.2 需要 2-RTT 才能完成握手,而 TLS 1.3 只用 1-RTT?这背后是握手流程设计上的根本差异:TLS 1.2 的“一问一答”模式必须严格串行,无法提前发送关键材料。


TLS 1.2 的 2-RTT 流程:必须等待的“一问一答”

一个标准的 TLS 1.2 握手(使用 ECDHE)是这样串行的:

🔹 RTT 1:客户端说“你好”,服务器亮明身份并开始密钥协商

  1. 客户端 → 服务器:ClientHello 发送支持的密码套件、随机数等。
  2. 服务器 → 客户端:ServerHello, Certificate, ServerKeyExchange, ServerHelloDone 服务器选定套件,发送证书(身份),发送 ServerKeyExchange(包含自己的 ECDHE 临时公钥),最后发送 ServerHelloDone 表示“我这波说完了”。

此时,客户端已收到服务器的 ECDHE 公钥,但还无法发送自己的公钥。 因为服务器的消息是一个完整的数据块,客户端必须全部接收并验证(至少验证证书)后,才能继续。

🔹 RTT 2:客户端完成密钥协商,双方确认

  1. 客户端 → 服务器:ClientKeyExchange, ChangeCipherSpec, Finished 客户端生成自己的 ECDHE 密钥对,将公钥放在 ClientKeyExchange 中发出。此时客户端已算出共享密钥,于是发送 ChangeCipherSpec(通知将加密)和加密的 Finished
  2. 服务器 → 客户端:ChangeCipherSpec, Finished 服务器用收到的客户端公钥算出共享密钥,解密验证 Finished,然后也发送 ChangeCipherSpec 和加密的 Finished

至此,握手完成,才能发送应用数据。关键点: 客户端的密钥交换消息(ClientKeyExchange)必须等到服务器的整轮消息结束(ServerHelloDone)之后才能发出,无法提前。这一来一回就是固定的 2-RTT。


为何不能并行?核心瓶颈在于“ServerKeyExchange”和“证书验证”

TLS 1.2 无法在第一个 RTT 内完成密钥协商的根本原因是:

  • 服务器必须先把 证书ECDHE 公钥(在 ServerKeyExchange 里)发给客户端。
  • 客户端收到后,需要验证证书链、签名,然后才能生成自己的 ECDHE 公钥并返回。
  • 这些步骤存在严格的数据依赖:客户端不能凭空生成一个能与服务器匹配的 ECDHE 公钥而不需要服务器的公钥,因为密钥协商需要双方公钥共同参与计算。而且证书验证也是必须的。

因此,TLS 1.2 的通信模式是“半双工”式的:一轮对话说完,另一轮才能开始。即使 0-RTT 恢复在某些实现中存在,但完整的首次握手确实是 2-RTT。


TLS 1.3 的改进:化 2-RTT 为 1-RTT 的魔法

TLS 1.3 能够压缩到 1-RTT,是因为它改变了规则:

  1. 客户端预先发送密钥共享 在 ClientHello 中,客户端直接附带了自己猜测的 ECDHE 公钥(针对其支持的曲线组)。如果猜对了服务器会选的曲线,服务器就能立刻使用。

  2. 服务器收到后立即计算密钥,后续消息全加密 服务器在 ServerHello 中选择曲线,并发送自己的 ECDHE 公钥。此时双方已拥有对方公钥,可以立即计算出会话密钥。从服务器发回的 ServerHello 之后的所有消息(包括证书、签名等)全部都用这个刚算出的密钥加密。不再需要 ServerKeyExchange 明文、ServerHelloDone 这类消息。

  3. 一个 RTT 内完成相互验证 客户端收到服务器的加密消息后,解密并验证证书,然后发送自己的 Finished(也是加密的)。此时服务器回复加密的应用数据或 Finished 即可。第一个 RTT 往返就完成了所有身份验证和密钥确认,应用数据可以跟随着客户端 Finished 一起发送(在 HTTP 请求中常见)。

用一张表对比会更加直观:

握手步骤TLS 1.2 (ECDHE)TLS 1.3
客户端第一轮发送ClientHelloClientHello + 客户端 ECDHE 公钥
服务器第一轮发送ServerHello, Certificate, ServerKeyExchange (服务端 ECDHE 公钥), ServerHelloDoneServerHello + 服务端 ECDHE 公钥随后所有消息加密 (Certificate, Finished 等)
此时状态客户端尚未发送自己的公钥,密钥未协商完双方均已算出会话密钥
客户端第二轮发送ClientKeyExchange (客户端 ECDHE 公钥), FinishedFinished (加密)
所需 RTT2-RTT1-RTT

简单总结:TLS 1.2 需要 2-RTT,是因为它必须先用第一个往返交换服务器的证书和密钥材料,再用第二个往返让客户端进行密钥交换和确认。这种依赖性无法在 1-RTT 内完成。而 TLS 1.3 利用“客户端猜测密钥共享参数,服务器即时计算并加密后续流量”的设计,完美将依赖关系融合在一个往返中,从而实现了质的飞跃。

Anycast

不,并非任何IP地址都可以用作Anycast IP。

Anycast并非一种特殊的IP地址类型,而是一种将流量路由到“最近”服务器的网络技术。这项技术能否实现,取决于你是在全球公网私有网络还是IPv6环境下部署。不同场景下的规则和要求各不相同。

1. 公网Anycast:需要IP“所有权”

在互联网上实现Anycast,你不能随意指定一个IP地址,因为:

  • 路由广播是核心:其工作机制依赖于通过BGP协议向全球广播一条路由信息,以“吸引”用户流量。这条信息不是单个IP,而是一个IP地址段(IP Prefix,即一组连续的IP地址,例如 /24)
  • 前提是拥有独立IP段:你必须拥有至少一个独立的公网IP地址段(通常是从区域互联网注册机构申请),并且拥有独立的AS号,才有权广播该地址段的路由。
  • 关键限制:/24前缀与独立ASN:为防止全球路由表膨胀,互联网服务提供商通常会过滤掉任何长度大于 /24 的路由广播。如果你只有一个零散的IP,你的路由广播就会被丢弃,Anycast便无法工作。同时,你也需要独立的ASN来建立BGP对等互联并进行路由广播。

2. 私有Anycast:受限但灵活

如果你是在自己的私有网络中(例如公司内网、数据中心间),Anycast的门槛会低很多。只要在你可控的网络范围内,除了特定保留地址(如127.0.0.1/8、专用于组播的D类地址224.0.0.0/4等)和已分配给其他设备的地址外,理论上你可以使用任何标准的单播IP地址来配置Anycast。其原理与公网类似,在多个节点配置相同的IP,然后通过内部路由协议(如OSPF)广播该IP的主机路由(/32),路由器就能将请求转发到最近的节点。

3. IPv6 Anycast:原生支持下的新规

与IPv4把Anycast作为“后来补充”不同,IPv6从设计之初就原生支持Anycast。

  • 语法无区别:在IPv6中,Anycast地址在格式上与普通单播地址完全相同,无法从地址本身进行区分。
  • 配置需明确:正因如此,将一个IPv6地址用作Anycast时,必须在网络节点上进行明确的配置和声明
  • 预留专用地址:IPv6协议标准(RFC 4291)定义了一些功能专用的特定Anycast地址。最典型的是子网路由器任播地址(Subnet-Router Anycast Address),它由子网前缀加上全零的接口ID构成,数据包发往该地址,就会被送达指定子网上的任意一台路由器。

对比总结

下表概括了在各类网络中实现Anycast的技术限制:

环境关键限制
公网 (IPv4)必须拥有/24或更短的独立IP地址段及AS号
公网 (IPv6)同样需要独立IP段用于BGP广播
私有网络受限于网络管理员定义的私有地址范围
IPv6 协议需明确将地址声明为Anycast,且某些特定地址已被预留

💡 常见Anycast应用场景

了解这些限制后,会发现Anycast的用途非常广泛,尤其在需要高可用和低延迟的场景中:

  • 公共DNS服务最典型的应用。如Google DNS (8.8.8.8)和Cloudflare DNS (1.1.1.1)在全球部署数千台服务器,让用户就近访问。
  • 内容分发网络(CDN):将用户请求路由到最近边缘节点,加速静态资源响应。
  • 分布式拒绝服务(DDoS)攻击防护:将攻击流量分散到全球,防止集中攻击瘫痪单一节点。

💎 总结

  • Anycast并非特殊的IP,而是一种**“一地广播,全网就近响应”**的路由技术。
  • 在公网上,你需要**“买路”(拥有独立的IP段和AS号)才能实现;在私网里,规则“家定”,更灵活;在IPv6中,它已是“原生居民”**,但遵守新规。
  • 如果想了解如何在特定环境(如云平台或私有网络)中配置Anycast,可以补充具体信息,我可以为你做更详细的介绍。

Anycast & Multicast

任播和多播虽然都是一对多的通信,但核心目标完全不同。任播是一对“最近的一台”,而多播是一对“特定的一群”。

可以这样理解:

  • 任播:全国连锁快餐店,你去“最近的那家”。
  • 多播:有线电视,你“订阅了频道”才能看,信号发给所有订阅者。

它们的关键区别如下:

对比维度任播 (Anycast)多播 (Multicast)
核心模式一对“最近的一个”一对“特定的一组”
IP地址与单播相同,无法从地址本身区分专用的D类地址 (IPv4: 224.0.0.0/4)
接收方被动,无需显式加入,由路由决定主动,接收方必须显式加入多播组
拓扑感知,严重依赖路由拓扑找到“最近”节点,不关心位置,只关心组关系
主要用途服务高可用、负载均衡、就近接入一对多实时分发,如直播、视频会议、IPTV
典型协议DNS, CDN (基于UDP/TCP等)通常基于UDP (如PIM, IGMP路由协议)

深入理解两者的工作机制

任播 (Anycast)

任播的关键在于路由协议(如BGP)。多个服务器使用相同的单播IP地址向网络宣告自己。当用户发起请求时,路由协议会根据最短路径、跳数等度量,自动将数据包转发到它认为“最近”的、广播了这个地址的服务器。因此,如果最近的节点宕机,路由会自动收敛,将流量切换到下一个最近的节点,实现天然的高可用和负载分散。

多播 (Multicast)

多播的关键在于“组”的管理。它需要一系列协议支持,如接收方使用IGMP(互联网组管理协议)协议告诉本地路由器:“我想加入这个多播组”,而路由器之间则使用PIM(协议无关多播)等协议构建一个从源到所有接收方的分发树。数据包在源只发一次,由网络中的路由器在分叉点复制,精准分发给所有组员,绝不会发给未加入的无关主机


💡 为什么任播通常应用于无状态服务?

任播一个关键的使用限制是“路由抖动”。因为路由变化可能导致同一个用户连接的后续数据包,被发往另一个任播节点。

这对于无状态服务(如DNS查询,一次一个UDP包,查完即走)完全无碍。但对于TCP等有状态长连接,如果中途切换节点,新节点没有连接上下文,连接就会中断。不过,如果数据中心间有状态同步,或使用QUIC(连接ID不依赖IP)等现代协议,也能在一定程度上缓解此问题。

简单来说,想就近服务、分散负载就用任播;想定向分发、一对多实时广播就用多播。

根域名服务器

根域名服务器只有13组,这通常被误解为全球只有13台物理服务器。实际上,这个“13”指的是13个逻辑上的身份标识(用字母A到M命名)。它的背后是早期互联网的一个技术限制,而强大的任播(Anycast)技术则让这个“13”的意义远超其数字本身。

📜 历史溯源:从4台到13组的演进

根服务器的数量并非一步到位,而是随着互联网发展逐步增加的:

  • 1985年:最初只有4台根服务器。
  • 1987-1991年:增加到7台
  • 1993年:增加到8台,开始面临数据包超限问题。
  • 1995年:改为统一命名格式 a~m.root-servers.net 以节省空间。
  • 1997年:最终增加到13组,沿用至今。

⛓️ 核心瓶颈:512字节的“铁律”

这个数字的根源,在于早期DNS协议必须遵守的 512字节数据包大小限制。所有13个根服务器的地址信息必须能塞进一个512字节的包中,原因是:

  • 协议基础:DNS主要依赖UDP协议,具有“即发即忘”的高效特性。
  • 避免IP分片:早期网络设备处理分片能力弱,为保可靠,DNS规定UDP包上限为512字节。
  • 精确计算的结果:一个标准的DNS响应包固定开销(Header等)约416字节,剩下给每个根服务器的地址信息只有96字节。因此最多只能容纳13个(实际经压缩极限可达14-15个,13个是为安全特意留的余量)。

🚀 技术飞跃:任播让“13”不再受限

如果“13”代表物理数量上限,互联网早就崩溃了。拯救它的是任播(Anycast)技术,你可以把它理解为互联网世界的“分身术”:

  • 逻辑与物理分离:A到M这13个字母是逻辑根服务器的身份ID。全球有超过1500个物理根服务器实例,都可以共享同一个逻辑IP。
  • 智能路由:当查询发出时,BGP路由协议会将其自动导向离你网络距离最近的那个物理节点。
  • 巨大优势:这套机制带来了高可用与负载均衡(一个节点故障自动切换)、低延迟(就近接入),以及让物理数量不受限制的高扩展性

💎 总结

“13组根服务器”并非指物理机器数,而是早期DNS协议为了确保兼容性与稳定性,所做出的巧妙设计。这个在今天看似“小”的数字,恰好是互联网基石稳健性的体现。如今,我们实际拥有的是一张遍布全球、由超过1500个物理节点支撑的“十三罗汉”网络。正是任播技术的赋能,让这套半个世纪前设计的系统,至今仍能稳定、高效地支撑起整个数字世界。

DNS 记录

DNS的记录类型可以看作是不同的“指令”,共同构成了互联网的导航和功能系统。它们种类繁多,可以大致分为常用核心类型安全验证类型,以及高级功能与新兴类型

📌 常见核心记录类型

这些是支撑网站访问、邮件收发等基本功能的基础记录,也是最常打交道的类型。

记录类型功能说明应用示例
A记录域名 → IPv4,最基础、最核心的记录。example.com 解析到 93.184.216.34
AAAA记录域名 → IPv6,A记录的IPv6升级版。example.com 解析到 2606:2800:220:1:248:1893:25c8:1946
CNAME记录域名 → 域名(别名),将一个域名指向另一个域名。www.example.com 指向 example.com,实现统一管理。
MX记录邮件交换,指定负责接收域名的邮件的服务器。example.com 的邮件指向 mail.google.com
NS记录指定DNS服务器,标识由哪个DNS服务器负责解析你的域名。域名 example.com 的解析由 ns1.dnsprovider.com 负责。
TXT记录存储文本信息,常用于域名身份验证、安全策略声明(SPF/DKIM/DMARC)等。在TXT记录中写入一个特定的验证字符串来证明你对域名的所有权。
SOA记录起始授权,记录DNS区域的版本信息、管理员邮箱、更新规则等核心管理参数。DNS区域的管理员是 admin@example.com,刷新时间为3600秒。
PTR记录IP → 域名(反向解析),A/AAAA记录的逆向操作,用于验证IP身份。将IP 93.184.216.34 解析回 example.com

🛡️ 安全与验证相关记录类型

随着网络安全日益重要,以下记录类型专门用于增强DNS和域名的安全性。

记录类型功能说明应用示例
CAA记录指定哪些证书颁发机构有权为该域名签发SSL/TLS证书,防止证书被冒用。仅允许 letsencrypt.orgexample.com 颁发证书。
DNSSEC相关用于验证DNS数据的真实性和完整性,防止DNS欺骗和缓存投毒。
RRSIG包含资源记录的数字签名,用于验证数据真实性。验证A记录 example.com → 93.184.216.34 确实来自权威DNS服务器。
DNSKEY存储区域用于签名的公钥DNS解析器通过DNSKEY中的公钥,验证RRSIG签名是否有效。
DS存储DNSKEY记录的摘要,用于在父子域之间建立信任链验证子域 blog.example.com 的DNSKEY是真实的。
NSEC/NSEC3用于证明某个DNS名称不存在,防止恶意否定应答。权威服务器用NSEC记录告知查询者 test.example.com 这个域名不存在。

🚀 高级功能与新兴记录类型

这些记录服务于更复杂的网络架构和现代化的应用需求。

记录类型功能说明应用示例
SRV记录服务定位,用于定义特定服务(如SIP、LDAP)的服务器主机名和端口。将公司的即时通讯服务 _sip._tcp.example.com 指向 sipserver.example.com 的5060端口。
DNAME记录域名重定向,类似于CNAME,但可以批量重定向一个域名及其所有子域名。设置 example.com 的DNAME指向 example.net 后,blog.example.com 也会自动指向 example.net
HTTPS/SVCB记录这是HTTP/3的重要组成部分,可提供替代服务的连接信息(如端口、协议、ECH公钥等),优化连接体验。浏览器通过查询example.com的HTTPS记录,发现其支持HTTP/3,从而直接建立QUIC连接。

当然,DNS记录类型远不止这些,还有存储地理位置的LOC记录、存放SSH密钥指纹的SSHFP记录等。不过对于多数需求而言,熟悉上面这些已经能覆盖绝大部分场景了。

私有地址选择

A、B、C类私有IP地址并非随意选出,而是一个历史、技术和现实需求相互交织的产物。主要原因是为了缓解IPv4地址枯竭,同时兼容当时大量仍在使用的“有类(Classful)”网络设备

具体的地址选择,则是一个务实决策的过程,具体考量如下:

  • A类 (10.0.0.0/8):原属已退役的ARPANET(互联网前身,1990年正式退役)。为避免它在公网被重新启用引发历史遗留问题,不如直接“回收”到内网。
  • B类 (172.16.0.0/12):在当时的B类地址空间里,这是最低的一段连续未分配地址块
  • C类 (192.168.0.0/16):同理,在192.0.0.0/8这个C类大段里,这也是最低的一段连续未分配地址块

这就是今天A、B、C类私有地址的由来: 10.0.0.0/8 是对历史资产的巧妙复用,而 172.16.0.0/12192.168.0.0/16 则是简单地从可用空间里选取了最“干净”的连续地址块。整个过程就像一个有条不紊的仓库管理员,优先把那些闲置的、好管理的货架区域分配出去,而没有刻意去“挑选”特定号码。

流量 && 拥塞控制

TCP 的传输控制包含两大核心机制:流量控制是为了防止“发送方”淹没“接收方”,而拥塞控制是为了防止“发送方”淹没“网络”。它们共同决定了数据的发送速率,但出发点和作用范围完全不同。


一、流量控制:端到端的平衡

流量控制解决的是通信双方能力不匹配的问题。它是一种由接收方驱动的机制。

1. 核心原理:滑动窗口 TCP 头部有一个“窗口大小”(Window Size)字段,接收方通过它告诉发送方自己还能接收多少数据。这个值被称为接收窗口 (rwnd),代表接收方缓冲区的剩余大小。发送方在发送数据时,会确保已发送但未确认的数据总量不超过 rwnd

2. 零窗口探测 如果接收方缓冲区满了,它会发送一个 rwnd=0 的窗口通告。此时发送方会停止发送,并启动一个“坚持定时器”,定期发送零窗口探测报文,询问接收方是否已有空间。这避免了死锁。

3. 糊涂窗口综合征 当接收方每次只腾出很少空间(如1字节),发送方立刻填入小数据包,导致网络效率低下。TCP 通过两端算法避免:接收方在窗口很小时延迟发送窗口更新,直到可用空间增大;发送方也避免发送过小的数据段(Nagle算法配合)。这部分在流量控制范畴内。


二、拥塞控制:对网络全局的感知

拥塞控制解决的是发送方与整个网络之间的平衡。网络是共享的,如果所有发送方都拼命发数据,路由器缓存会溢出,导致丢包和延迟剧增。拥塞控制要求每个 TCP 连接自适应地调整发送速率

发送方维护一个拥塞窗口 (cwnd),它是对网络能承受的数据量的估计。实际发送窗口 = min(rwnd, cwnd)。拥塞控制的核心算法经历了从经典 Reno 到现代 BBR 的演进。

经典 Reno 系列算法 (基于丢包反馈)

Reno 算法将拥塞控制划分为四个阶段,由慢启动拥塞避免快速重传快速恢复组成,通过两个关键变量控制:cwnd慢启动阈值 (ssthresh)

  1. 慢启动 连接建立或超时后,cwnd 初始很小(如1-10 MSS)。每收到一个 ACK,cwnd 增加一个 MSS(即每过一个 RTT,cwnd 翻倍)。这是一种探测性、指数级的窗口增长速度,直到 cwnd >= ssthresh 或发生丢包。

  2. 拥塞避免cwnd >= ssthresh 时,进入该阶段,窗口改为每个 RTT 线性增加大约1个 MSS(即 cwnd += 1/cwnd),缓慢逼近网络容量,防止骤然拥塞。

  3. 快速重传 如果发送方收到3个或以上重复ACK,意味着有后续数据到达了接收方,只是中间有一个报文丢失,网络并未完全瘫痪。此时不必等待超时,立刻重传那个丢失的报文

  4. 快速恢复 在快速重传后,网络仍能通信,所以不降到慢启动,而是:

    • ssthresh 设为当前 cwnd 的一半。
    • cwnd 设为 ssthresh + 3个报文段(考虑到已触发的重复ACK),然后进入拥塞避免阶段,线性增长。
    • 如果是超时重传,意味着网络可能严重堵塞,则 ssthresh 设为 cwnd/2cwnd 重置为初始值,重新进入慢启动

现代改进与趋势

  • CUBIC:Linux 默认算法,替代 Reno。它不再依赖简单的线性增加,而是使用一个三次函数来调整 cwnd,当网络接近饱和时增长更慢,远离饱和点时增长更快,极大地改善了高速长距离网络(如跨洋链路)的利用率,同时保持良好的公平性。
  • BBR:谷歌提出的基于瓶颈带宽和往返时间的算法。它不再依赖丢包作为拥塞信号(因为丢包可能只是路由器缓存满后的被动丢弃,此时延迟已高),而是持续估计网络链路的实际可用带宽和最小RTT,直接使发送速率匹配管道速度,能显著降低延迟,且不怕偶然丢包

三、两者协同与对比

维度流量控制拥塞控制
控制对象点到点,发送方 vs 接收方端到网络,发送方 vs 整个网络
核心变量rwnd (接收方通告)cwnd (发送方自行探测与计算)
触发信号接收方的ACK中的窗口字段丢包(超时/重复ACK)、延迟增大
目的防止接收方缓冲区溢出防止网络过载,公平分享带宽
最终速率发送窗口 = min(rwnd, cwnd)

掌握这些机制,就能理解一个看似简单的文件传输,其背后 TCP 是如何在保证可靠的同时,尽可能高效、公平地利用网络资源。

cwnd

你之所以会觉得“每个 ACK 增加一个 MSS”和“每轮指数级上升”像是两种描述,是因为前者是规则,后者是结果。关键在于:在慢启动阶段,一轮 RTT 内返回的 ACK 数量,恰好等于当前拥塞窗口(cwnd)的大小

下面我们来具体拆解这个过程。


1. 慢启动的规则

慢启动的核心算法非常简单:每收到一个 ACK,拥塞窗口 cwnd 就增加 1 个 MSS(最大报文段长度)。
这看起来只是线性增长,每确认一个包才多一个包的空间。

2. 指数级增长是如何“涌现”的?

关键在于 TCP 的数据发送方式:发送方一次会发出 cwnd 个报文段(相当于将整个窗口“填满”),然后等待这些报文段的确认。
我们以初始 cwnd = 1 为例,模拟前几轮:

第一轮:cwnd = 1

  • 发送方发出 1 个包
  • 经过 1 个 RTT 后,收到这 1 个包的 ACK。
  • 根据规则:收到 1 个 ACK → cwnd = 1 + 1 = 2

第二轮:cwnd = 2

  • 发送方发出 2 个包
  • 经过 1 个 RTT 后,收到这 2 个包的 ACK。
  • 根据规则:收到 2 个 ACK → cwnd = 2 + 2 = 4

第三轮:cwnd = 4

  • 发送方发出 4 个包
  • 收到 4 个 ACK → cwnd = 4 + 4 = 8

第四轮:cwnd = 8

  • 收到 8 个 ACK → cwnd = 8 + 8 = 16

以此类推。
可以看到,每一轮结束后,cwnd 都变成了原来的两倍,呈现出标准的指数级增长(1 → 2 → 4 → 8 → 16…)。

3. 为什么说“每轮指数级”是自然的体现?

根本原因在于:

当前轮次发出的报文数 = 当前 cwnd
当前轮次收到的 ACK 总数 ≈ 当前 cwnd

因此,每一轮结束后: 新 cwnd = 旧 cwnd + 收到的 ACK 数 ≈ 旧 cwnd + 旧 cwnd = 2 × 旧 cwnd

这就是每个 ACK 加 1 的线性规则,叠加窗口内所有报文全部被确认这一事实后,在每一轮 RTT 边界上自然表现出的指数倍增效果

4. 这种增长的真实含义

慢启动的指数增长其实非常**“快”:只经过大约 10 个 RTT,窗口就能从 1 增长到约 1024。它的目的就是让 TCP 在连接初期快速逼近网络的实际可用带宽,而不是小心翼翼地线性增长。因此,与其说它是“慢启动”,不如说它是“迅猛的初始探测”**——只是在现代网络面前,这种探测看起来比较“慢”而已(古老术语沿用至今)。


总结

  • 规则视角:每收到一个 ACK,cwnd 增加 1 MSS(线性累加)。
  • 结果视角:由于窗口大小会决定发出的包数,而发出的包数又决定下一轮收到的 ACK 数,因此每一轮 RTT 结束时,cwnd 都会翻倍,表现出指数级增长
  • 本质原因ACK 返回的数量与当前窗口大小成正比,导致线性规则在轮次尺度上转化为了指数级上升。

所以,“每个 ACK cwnd 体现为每轮指数级上升”是同一个机制的微观规则和宏观效果,两者完全一致。

CSRF Cross-Site Request Forgery

CSRF(跨站请求伪造) 是一种利用用户已登录身份,在用户不知情的情况下,以用户的名义发起恶意请求的攻击方式。简单说,攻击者可以诱导你点击一个链接或访问一个页面,你的浏览器就自动替你向银行、社交网站等发送转账、改密码的请求。


一、攻击原理:借用你的“身份证”

CSRF 之所以能成功,依赖于两个关键条件:

  1. 用户已登录目标网站,浏览器保存了该网站的认证凭证(Cookie、Session 等)。
  2. 攻击者构造了一个恶意的跨站请求,诱使用户点击或自动发起。

典型攻击流程:

  1. 你登录了银行网站 bank.com,浏览器存下了你的 Session Cookie。
  2. 你在同一浏览器中,不小心打开了恶意网站 evil.com
  3. evil.com 页面中藏有一段代码,例如一个隐藏的表单或一段脚本,它会向 bank.com/transfer 发起一个 POST 请求,转账给黑客。
  4. 浏览器在看到这个跨域请求时,会自动带上你之前登录过的 bank.com 的 Cookie
  5. bank.com 服务器收到请求,验证了 Cookie,认为是你本人发起的操作,转账成功。

核心问题:浏览器对于跨域请求,出于兼容性考虑,会自动附加目标域的 Cookie,但无法判断这个请求是否是用户真实意愿发起的。


二、常见攻击形式

攻击者可通过多种方式触发恶意请求:

  • 隐藏表单自动提交:在 evil.com 中嵌入一个不可见的 <form>,设置 action 指向 bank.com/transfer,用 JavaScript 自动提交。
  • 伪装成 GET 请求的链接:虽然规范上 GET 不应有副作用,但很多早期应用直接用 GET 接受敏感操作。攻击者只需诱导点击 <img src="http://bank.com/changePassword?new=hacked"> 即可。
  • 利用 AJAX 发送请求:如果目标服务器允许简单跨域请求(无自定义 Header),攻击者也可以用 JavaScript 的 fetchXMLHttpRequest 发送 POST 请求,同样会带上 Cookie。

三、危害举例

任何可通过 Web 请求触发的操作都可能被 CSRF 攻击利用:

  • 修改账户信息:改密码、改绑邮箱/手机、改密保问题。
  • 资金操作:转账、购物、提现。
  • 发布内容:以用户名义发帖、发消息、关注他人。
  • 权限提升:如果攻击者利用管理员身份发起 CSRF,可以创建新管理员账号,完全控制系统。

四、防御措施(重点)

防御 CSRF 的核心思路是:让服务器能够区分一个请求是“用户真实发起的”还是“跨站伪造的”

1. 反 CSRF Token(最经典有效)

这是目前最广泛使用的防御手段。

  • 服务器生成一个随机、不可预测的 Token,存入用户的 Session 中,并将其嵌入到前端页面(如表单隐藏域、自定义请求头)。
  • 当用户提交请求时,必须携带这个 Token。攻击者无法获取到这个 Token(因同源策略限制,跨站脚本无法读取其他站点的内容),因此无法构造出合法的请求。
  • 服务器验证请求中的 Token 与 Session 中的是否一致。

这是一个由浏览器原生支持的、极其有效的防御机制。

  • SameSite 属性可以告诉浏览器:仅在请求来自同一站点时,才附带 Cookie
  • SameSite=Strict:完全禁止跨站携带 Cookie,最为严格,但可能影响从第三方跳转过来的用户体验(比如从邮件点链接进入已登录网站会需要重新登录)。
  • SameSite=Lax:相对宽松,允许在顶级导航(如点击链接)时附带 Cookie,但禁止在 POST 表单、iframe、AJAX 等跨站上下文加载时附带。这是现代浏览器的默认值(Chrome、Firefox 等),能阻断绝大多数 CSRF。
  • 结合使用SameSite=Lax + 关键操作的反 CSRF Token,是目前较佳实践。

3. 验证 Referer / Origin 头

检查请求的来源是否合法。

  • Referer 头包含发起请求的页面完整 URL。
  • Origin 头包含协议、主机名和端口,没有路径和查询参数,更简单且隐私。对于 AJAX 请求,浏览器总会发送 Origin。
  • 服务器可以检验这两个头,确认请求来自自己信任的域名。但依赖 HTTP 头有被伪造或缺失的风险(如某些旧浏览器、隐私设置可能不发送 Referer),一般作为辅助手段。

将 Token 同时放在 Cookie 和请求参数中,服务器验证两者是否一致。攻击者可以写入 Cookie(通过跨站请求),但无法读取 Cookie 值,故无法让参数与 Cookie 匹配。这种方法不需要服务器存储状态,但安全性略逊于 Session Token。

5. 关键操作强制用户交互

对于极其敏感的操作,可以要求重新输入密码、短信验证码、图形验证码,从根本上杜绝自动化请求。

6. 避免使用 GET 进行副作用操作

严格遵守 RESTful 规范,GET 请求仅用于读取数据。这样,即使攻击者嵌入 <img> 等 GET 请求,也无法造成破坏(因为服务端不接受 GET 执行修改操作)。


五、CSRF 与 XSS 的区别

  • XSS(跨站脚本):攻击者在目标网站中注入恶意脚本,能读取同源数据(Cookie、Token 等),破坏性更大。XSS 可以彻底绕过 CSRF 防御(因为能拿到 Token)。
  • CSRF:攻击者无法读取目标站点的内容,只是“借用”用户的 Cookie 发起跨站请求。

关系:如果存在 XSS 漏洞,CSRF 防御通常形同虚设;反之,防御 CSRF 的措施不一定能防 XSS。两者需要共同防范。


六、现状与趋势

随着 SameSite=Lax 成为浏览器默认行为,简单的 CSRF 攻击已经越来越难以奏效。很多现代 Web 框架(如 React、Vue 的 CSR 模式)也默认使用 Authorization 请求头(如 Bearer Token)来做身份验证,而非依赖 Cookie,天然免疫 CSRF。但在许多传统的、基于 Cookie/Session 的服务端渲染应用中,CSRF 防护仍然不可或缺。

总结:CSRF 利用了 HTTP 的无状态特性和浏览器自动携带 Cookie 的机制,属于一种“信任上的漏洞”。通过在每一次状态改变请求中加入不可预测的校验因子(CSRF Token),或者利用现代浏览器的 SameSite 机制,就能有效防范。

Licensed under CC BY-NC-SA 4.0
Last updated on Jun 09, 2026 11:16 CST
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy